From b96c8a661f554bb92abd68bb5b8117c721688a9e Mon Sep 17 00:00:00 2001 From: WaDadidou Date: Tue, 26 Nov 2024 14:26:05 -0500 Subject: [PATCH 01/22] wip(article): Add MD support --- package.json | 4 + .../navigation/getNormalModeScreens.tsx | 8 +- .../socialFeed/NewsFeed/NewsFeedInput.tsx | 71 +- .../SocialCard/cards/SocialArticleCard.tsx | 325 +++++---- .../FeedNewArticleMarkdownScreen.tsx | 677 ++++++++++++++++++ .../components/SocialArticleCardEdition.tsx | 264 +++++++ packages/utils/navigation.ts | 7 + packages/utils/types/feed.ts | 1 + yarn.lock | 102 ++- 9 files changed, 1274 insertions(+), 185 deletions(-) create mode 100644 packages/screens/FeedNewArticle/FeedNewArticleMarkdownScreen.tsx create mode 100644 packages/screens/FeedNewArticle/components/SocialArticleCardEdition.tsx diff --git a/package.json b/package.json index 6bda65456a..65a64044ec 100644 --- a/package.json +++ b/package.json @@ -89,6 +89,7 @@ "cosmos-endpoints-scorer": "^0.1.0", "crypto-browserify": "^3.12.0", "crypto-js": "^4.2.0", + "css-to-react-native": "^3.2.0", "draft-convert": "^2.1.13", "draft-js": "^0.11.7", "electron-store": "^8.1.0", @@ -129,6 +130,7 @@ "papaparse": "^5.4.1", "plausible-tracker": "^0.3.8", "pluralize": "^8.0.0", + "prop-types": "^15.8.1", "protobufjs": "^7.2.5", "react": "18.2.0", "react-dom": "18.2.0", @@ -141,6 +143,7 @@ "react-native-confetti-cannon": "^1.5.2", "react-native-countdown-component": "^2.7.1", "react-native-drax": "^0.10.2", + "react-native-fit-image": "^1.5.5", "react-native-fs": "^2.20.0", "react-native-gesture-handler": "~2.14.0", "react-native-get-random-values": "~1.8.0", @@ -149,6 +152,7 @@ "react-native-image-picker": "^7.1.0", "react-native-keyboard-aware-scrollview": "^2.1.0", "react-native-leaflet-view": "^0.1.2", + "react-native-markdown-display": "^7.0.2", "react-native-paper": "^4.12.5", "react-native-pell-rich-editor": "^1.8.8", "react-native-pie-chart": "^3.0.1", diff --git a/packages/components/navigation/getNormalModeScreens.tsx b/packages/components/navigation/getNormalModeScreens.tsx index c7145bb147..435b72388b 100644 --- a/packages/components/navigation/getNormalModeScreens.tsx +++ b/packages/components/navigation/getNormalModeScreens.tsx @@ -9,7 +9,8 @@ import { CoreDAOScreen } from "@/screens/CoreDAO/CoreDAOScreen"; import { DAppStoreScreen } from "@/screens/DAppStore/DAppStoreScreen"; import { ToriPunks } from "@/screens/DAppStore/apps/toripunks/HomeScreen"; import { FeedScreen } from "@/screens/Feed/FeedScreen"; -import { FeedNewArticleScreen } from "@/screens/FeedNewArticle/FeedNewArticleScreen"; +import { FeedNewArticleMarkdownScreen } from "@/screens/FeedNewArticle/FeedNewArticleMarkdownScreen.tsx"; +import { FeedNewArticleScreen } from "@/screens/FeedNewArticle/FeedNewArticleScreen.tsx"; import { FeedPostViewScreen } from "@/screens/FeedPostView/FeedPostViewScreen"; import { GovernanceProposalScreen } from "@/screens/Governance/GovernanceProposal/GovernanceProposalScreen"; import { GovernanceScreen } from "@/screens/Governance/GovernanceScreen"; @@ -453,6 +454,11 @@ export const getNormalModeScreens = ({ component={FeedNewArticleScreen} options={{ header: () => null, title: screenTitle("New Article") }} /> + null, title: screenTitle("New Article") }} + /> {type === "post" && ( - - - SOCIAL_FEED_ARTICLE_MIN_CHARS_LIMIT - ? primaryTextColor - : primaryColor - } - borderColor={primaryColor} - touchableStyle={{ - marginRight: layout.spacing_x2, - }} - backgroundColor={ - formValues?.message.length > - SOCIAL_FEED_ARTICLE_MIN_CHARS_LIMIT - ? primaryColor - : neutral17 - } - text="Create an Article" - squaresBackgroundColor={neutral17} - /> - + <> + + + SOCIAL_FEED_ARTICLE_MIN_CHARS_LIMIT + ? primaryTextColor + : primaryColor + } + borderColor={primaryColor} + touchableStyle={{ + marginRight: layout.spacing_x2, + }} + backgroundColor={ + formValues?.message.length > + SOCIAL_FEED_ARTICLE_MIN_CHARS_LIMIT + ? primaryColor + : neutral17 + } + text="Create an Article" + squaresBackgroundColor={neutral17} + /> + + + + SOCIAL_FEED_ARTICLE_MIN_CHARS_LIMIT + ? primaryTextColor + : primaryColor + } + borderColor={primaryColor} + touchableStyle={{ + marginRight: layout.spacing_x2, + }} + backgroundColor={ + formValues?.message.length > + SOCIAL_FEED_ARTICLE_MIN_CHARS_LIMIT + ? primaryColor + : neutral17 + } + text="MD" + squaresBackgroundColor={neutral17} + /> + + )} ; refetchFeed?: () => Promise; isFlagged?: boolean; -}> = memo(({ post, isPostConsultation, refetchFeed, style, isFlagged }) => { - const navigation = useAppNavigation(); - const [localPost, setLocalPost] = useState(post); - const [viewWidth, setViewWidth] = useState(0); - const { width: windowWidth } = useWindowDimensions(); + disabled?: boolean; +}> = memo( + ({ post, isPostConsultation, refetchFeed, style, isFlagged, disabled }) => { + const navigation = useAppNavigation(); + const [localPost, setLocalPost] = useState(post); + const [viewWidth, setViewWidth] = useState(0); + const { width: windowWidth } = useWindowDimensions(); - const articleCardHeight = windowWidth < SOCIAL_FEED_BREAKPOINT_M ? 214 : 254; - const thumbnailImageWidth = viewWidth / 3; - const borderRadius = - windowWidth < RESPONSIVE_BREAKPOINT_S ? 0 : SOCIAl_CARD_BORDER_RADIUS; + const articleCardHeight = + windowWidth < SOCIAL_FEED_BREAKPOINT_M ? 214 : 254; + const thumbnailImageWidth = viewWidth / 3; + const borderRadius = + windowWidth < RESPONSIVE_BREAKPOINT_S ? 0 : SOCIAl_CARD_BORDER_RADIUS; - const metadata = zodTryParseJSON( - ZodSocialFeedArticleMetadata, - localPost.metadata, - ); - const oldMetadata = zodTryParseJSON( - ZodSocialFeedPostMetadata, - localPost.metadata, - ); - const thumbnailImage = - metadata?.thumbnailImage || - // Old articles doesn't have thumbnailImage, but they have a file with a isCoverImage flag - oldMetadata?.files?.find((file) => file.isCoverImage); - const simplePostMetadata = metadata || oldMetadata; - const message = simplePostMetadata?.message; + const metadata = zodTryParseJSON( + ZodSocialFeedArticleMetadata, + localPost.metadata, + ); + const oldMetadata = zodTryParseJSON( + ZodSocialFeedPostMetadata, + localPost.metadata, + ); + const thumbnailImage = + metadata?.thumbnailImage || + // Old articles doesn't have thumbnailImage, but they have a file with a isCoverImage flag + oldMetadata?.files?.find((file) => file.isCoverImage); + const simplePostMetadata = metadata || oldMetadata; + const message = simplePostMetadata?.message; - const shortDescription = useMemo(() => { - if (metadata?.shortDescription) { - return metadata.shortDescription; - } - if (!message) return ""; - if (isArticleHTMLNeedsTruncate(message, true)) { - const { truncatedHtml } = getTruncatedArticleHTML(message); - const contentState = - createStateFromHTML(truncatedHtml).getCurrentContent(); - return ( - metadata?.shortDescription || - // Old articles doesn't have shortDescription, so we use the start of the html content - contentState.getPlainText() - ); - } - return ""; - }, [message, metadata?.shortDescription]); + const shortDescription = useMemo(() => { + if (metadata?.shortDescription) { + return metadata.shortDescription; + } + if (!message) return ""; + if (isArticleHTMLNeedsTruncate(message, true)) { + const { truncatedHtml } = getTruncatedArticleHTML(message); + const contentState = + createStateFromHTML(truncatedHtml).getCurrentContent(); + return ( + metadata?.shortDescription || + // Old articles doesn't have shortDescription, so we use the start of the html content + contentState.getPlainText() + ); + } + return ""; + }, [message, metadata?.shortDescription]); - useEffect(() => { - setLocalPost(post); - }, [post]); + useEffect(() => { + setLocalPost(post); + }, [post]); - const thumbnailURI = thumbnailImage?.url - ? thumbnailImage.url.includes("://") - ? thumbnailImage.url - : "ipfs://" + thumbnailImage.url // we need this hack because ipfs "urls" in feed are raw CIDs - : defaultThumbnailImage; + const thumbnailURI = thumbnailImage?.url + ? thumbnailImage.url.includes("://") + ? thumbnailImage.url + : "ipfs://" + thumbnailImage.url // we need this hack because ipfs "urls" in feed are raw CIDs + : defaultThumbnailImage; - const title = simplePostMetadata?.title; + const title = simplePostMetadata?.title; - return ( - - - navigation.navigate("FeedPostView", { - id: localPost.id, - }) - } - onLayout={(e) => setViewWidth(e.nativeEvent.layout.width)} - style={[ - { - borderWidth: 1, - borderColor: withAlpha(neutral33, 0.5), - borderRadius, - backgroundColor: neutral00, - width: "100%", - flexDirection: "row", - justifyContent: "space-between", - height: articleCardHeight, - flex: 1, - }, - style, - ]} + return ( + - + navigation.navigate("FeedPostView", { + id: localPost.id, + }) + } + onLayout={(e) => setViewWidth(e.nativeEvent.layout.width)} + style={[ + { + borderWidth: 1, + borderColor: withAlpha(neutral33, 0.5), + borderRadius, + backgroundColor: neutral00, + width: "100%", + flexDirection: "row", + justifyContent: "space-between", + height: articleCardHeight, + flex: 1, + }, + style, + ]} > - - + + + - - - {title?.trim().replace("\n", " ")} - + + + {title?.trim().replace("\n", " ")} + - - - {shortDescription.trim().replace("\n", " ")} - + + + {shortDescription.trim().replace("\n", " ")} + + - - {/*We use a shadow to highlight the footer when it's onto the thumbnail image (mobile)*/} - - {isFlagged ? ( - - ) : ( - - )} - + {/*We use a shadow to highlight the footer when it's onto the thumbnail image (mobile)*/} + + {isFlagged ? ( + + ) : ( + + )} + - - - - ); -}); + + + + ); + }, +); diff --git a/packages/screens/FeedNewArticle/FeedNewArticleMarkdownScreen.tsx b/packages/screens/FeedNewArticle/FeedNewArticleMarkdownScreen.tsx new file mode 100644 index 0000000000..82476f3296 --- /dev/null +++ b/packages/screens/FeedNewArticle/FeedNewArticleMarkdownScreen.tsx @@ -0,0 +1,677 @@ +import pluralize from "pluralize"; +import React, { useEffect, useRef, useState } from "react"; +import { Controller, useForm } from "react-hook-form"; +import { + ScrollView, + TextInput, + TextStyle, + useWindowDimensions, + View, +} from "react-native"; +import Markdown from "react-native-markdown-display"; +import { useSelector } from "react-redux"; + +import { fontSemibold12 } from "./../../utils/style/fonts"; +import priceSVG from "../../../assets/icons/price.svg"; +import useSelectedWallet from "../../hooks/useSelectedWallet"; + +import penSVG from "@/assets/icons/pen.svg"; +import { BrandText } from "@/components/BrandText"; +import { SVG } from "@/components/SVG"; +import { ScreenContainer } from "@/components/ScreenContainer"; +import { WalletStatusBox } from "@/components/WalletStatusBox"; +import { TertiaryBox } from "@/components/boxes/TertiaryBox"; +import { CustomPressable } from "@/components/buttons/CustomPressable"; +import { Label, TextInputCustom } from "@/components/inputs/TextInputCustom"; +import { FileUploader } from "@/components/inputs/fileUploader"; +import { FeedPostingProgressBar } from "@/components/loaders/FeedPostingProgressBar"; +import { PublishValues } from "@/components/socialFeed/RichText/RichText.type"; +import { SocialArticleCard } from "@/components/socialFeed/SocialCard/cards/SocialArticleCard"; +import { MapModal } from "@/components/socialFeed/modals/MapModal/MapModal"; +import { SpacerColumn, SpacerRow } from "@/components/spacer"; +import { useFeedbacks } from "@/context/FeedbacksProvider"; +import { useWalletControl } from "@/context/WalletControlProvider"; +import { useFeedPosting } from "@/hooks/feed/useFeedPosting"; +import { useIpfs } from "@/hooks/useIpfs"; +import { useIsMobile } from "@/hooks/useIsMobile"; +import { useMaxResolution } from "@/hooks/useMaxResolution"; +import { useSelectedNetworkId } from "@/hooks/useSelectedNetwork"; +import { NetworkFeature } from "@/networks"; +import { selectNFTStorageAPI } from "@/store/slices/settings"; +import { feedPostingStep, FeedPostingStepId } from "@/utils/feed/posting"; +import { generateArticleMetadata } from "@/utils/feed/queries"; +import { generateIpfsKey } from "@/utils/ipfs"; +import { IMAGE_MIME_TYPES } from "@/utils/mime"; +import { ScreenFC, useAppNavigation } from "@/utils/navigation"; +import { ARTICLE_MAX_WIDTH } from "@/utils/social-feed"; +import { + neutral00, + neutral11, + neutral17, + neutral1A, + neutral33, + neutral77, + neutralA3, + neutralFF, + secondaryColor, +} from "@/utils/style/colors"; +import { fontSemibold13, fontSemibold20 } from "@/utils/style/fonts"; +import { + layout, + RESPONSIVE_BREAKPOINT_S, + screenContentMaxWidth, +} from "@/utils/style/layout"; +import { + CustomLatLngExpression, + NewArticleFormValues, + PostCategory, + SocialFeedArticleMetadata, +} from "@/utils/types/feed"; +import { RemoteFileData } from "@/utils/types/files"; + +//TODO: In mobile : Make ActionsContainer accessible (floating button ?) + +export const FeedNewArticleMarkdownScreen: ScreenFC< + "FeedNewArticleMarkdown" +> = () => { + const { width } = useMaxResolution(); + const { width: windowWidth } = useWindowDimensions(); + const isSmallScreen = windowWidth < RESPONSIVE_BREAKPOINT_S; + const isMobile = useIsMobile(); + const wallet = useSelectedWallet(); + const selectedNetworkId = useSelectedNetworkId(); + const userId = wallet?.userId; + const userIPFSKey = useSelector(selectNFTStorageAPI); + const { uploadFilesToPinata, ipfsUploadProgress } = useIpfs(); + const [isUploadLoading, setIsUploadLoading] = useState(false); + const [isProgressBarShown, setIsProgressBarShown] = useState(false); + const postCategory = PostCategory.Article; + const { + makePost, + isProcessing, + canPayForPost, + freePostCount, + prettyPublishingFee, + publishingFee, + step, + setStep, + } = useFeedPosting(selectedNetworkId, userId, postCategory, () => { + // Timeout here to let a few time to see the progress bar "100% Done" + setTimeout(() => { + setIsUploadLoading(false); + setIsProgressBarShown(false); + setToast({ + mode: "normal", + type: "success", + title: "Post submitted successfully.", + message: "", + }); + navigateBack(); + newArticleForm.reset(); + }, 1000); + }); + const forceNetworkFeature = NetworkFeature.SocialFeed; + const { showNotEnoughFundsModal, showConnectWalletModal } = + useWalletControl(); + const isLoading = isUploadLoading || isProcessing; + const { setToast } = useFeedbacks(); + const navigation = useAppNavigation(); + const scrollViewRef = useRef(null); + const [isThumbnailButtonHovered, setThumbnailButtonHovered] = useState(false); + const [location, setLocation] = useState(); + const [isMapShown, setIsMapShown] = useState(false); + const cardStyle = isSmallScreen && { + borderRadius: 0, + borderLeftWidth: 0, + borderRightWidth: 0, + }; + const newArticleForm = useForm({ + defaultValues: { + title: "", + message: "", + files: [], + gifs: [], + hashtags: [], + mentions: [], + thumbnailImage: undefined, + shortDescription: "", + }, + mode: "onBlur", + }); + const formValues = newArticleForm.watch(); + const previewMetadata: SocialFeedArticleMetadata = { + title: formValues.title, + shortDescription: formValues.shortDescription || "", + thumbnailImage: formValues.thumbnailImage, + message: "", + hashtags: [], + mentions: [], + }; + + const onPublish = async (values: PublishValues) => { + const action = "Publish an Article"; + if (!wallet?.address || !wallet.connected) { + showConnectWalletModal({ + forceNetworkFeature, + action, + }); + return; + } + if (!canPayForPost) { + showNotEnoughFundsModal({ + action, + cost: { + amount: publishingFee.amount.toString(), + denom: publishingFee.denom || "", + }, + }); + return; + } + setIsUploadLoading(true); + setIsProgressBarShown(true); + try { + const localFiles = [ + ...(formValues.files || []), + ...values.images, + ...values.audios, + ...values.videos, + ]; + if (formValues.thumbnailImage) localFiles.push(formValues.thumbnailImage); + if (formValues.coverImage) localFiles.push(formValues.coverImage); + + let pinataJWTKey = undefined; + if (localFiles?.length) { + setStep(feedPostingStep(FeedPostingStepId.GENERATING_KEY)); + + pinataJWTKey = + userIPFSKey || (await generateIpfsKey(selectedNetworkId, userId)); + } + + // Upload files to IPFS + let remoteFiles: RemoteFileData[] = []; + if (pinataJWTKey) { + setStep(feedPostingStep(FeedPostingStepId.UPLOADING_FILES)); + + remoteFiles = await uploadFilesToPinata({ + files: localFiles, + pinataJWTKey, + }); + } + + // If the user uploaded files, but they are not pinned to IPFS, it returns files with empty url, so this is an error. + if (formValues.files?.length && !remoteFiles.find((file) => file.url)) { + console.error("upload file err : Fail to pin to IPFS"); + setToast({ + mode: "normal", + type: "error", + title: "File upload failed", + message: "Fail to pin to IPFS, please try to Publish again", + }); + setIsUploadLoading(false); + return; + } + + let message = values.html; + if (remoteFiles.length) { + localFiles?.map((file, index) => { + // Audio are not in the HTML for now + if (remoteFiles[index]?.fileType !== "audio") { + message = message.replace(file.url, remoteFiles[index].url); + } + }); + } + + const metadata = generateArticleMetadata({ + ...formValues, + thumbnailImage: remoteFiles.find( + (remoteFile) => remoteFile.isThumbnailImage, + ), + coverImage: remoteFiles.find((remoteFile) => remoteFile.isCoverImage), + gifs: values.gifs, + files: remoteFiles, + mentions: values.mentions, + hashtags: values.hashtags, + message, + location, + }); + + await makePost(JSON.stringify(metadata)); + } catch (err) { + console.error("post submit error", err); + setIsUploadLoading(false); + setIsProgressBarShown(false); + setToast({ + mode: "normal", + type: "error", + title: "Something went wrong.", + message: err instanceof Error ? err.message : `${err}`, + }); + } + }; + + //TODO: Keep short post formValues when returning to short post + const navigateBack = () => navigation.navigate("Feed"); + + //TODO: Not handled for now + // const { mutate: openGraphMutate, data: openGraphData } = useOpenGraph(); + + // // OpenGraph URL preview + // useEffect(() => { + // addedUrls.forEach(url => { + // openGraphMutate({ + // url, + // }); + // + // }) + // }, [addedUrls]) + + // Scroll to bottom when the loading bar appears + useEffect(() => { + if (step.id !== "UNDEFINED" && isLoading) + scrollViewRef.current?.scrollToEnd(); + }, [step, isLoading]); + + /////////// MD EDITOR ///////////////////// + const { height } = useMaxResolution(); + const inputRef = useRef(null); + const [isEditorHovered, setEditorHovered] = useState(false); + const borderWidth = 1; + + const editorMinHeight = height - 140; + const [editorHeight, setEditorHeight] = useState(editorMinHeight); + const [mode, setMode] = useState<"edition" | "both" | "preview">("both"); + const editorPadding = + windowWidth < RESPONSIVE_BREAKPOINT_S + ? 0 + : layout.spacing_x2 - borderWidth * 2; + /////////////////////////////////////////// + + return ( + New Article} + onBackPress={navigateBack} + footerChildren + noMargin + noScroll + > + + + + + + + + + {freePostCount + ? `You have ${freePostCount} free ${pluralize( + "Article", + freePostCount, + )} left` + : `The cost for this Article is ${prettyPublishingFee}`} + + + + + noBrokenCorners + rules={{ required: true }} + height={48} + label="Preview title" + placeHolder="Type title here" + name="title" + control={newArticleForm.control} + variant="labelOutside" + containerStyle={{ marginVertical: layout.spacing_x3 }} + boxMainContainerStyle={{ + backgroundColor: neutral00, + borderRadius: 12, + }} + /> + + + noBrokenCorners + rules={{ required: true }} + multiline + label="Preview subtitle" + placeHolder="Type short description here" + name="shortDescription" + control={newArticleForm.control} + variant="labelOutside" + containerStyle={{ marginBottom: layout.spacing_x3 }} + boxMainContainerStyle={{ + backgroundColor: neutral00, + borderRadius: 12, + }} + /> + + + + + + + + + newArticleForm.setValue("thumbnailImage", { + isThumbnailImage: true, + ...files[0], + }) + } + mimeTypes={IMAGE_MIME_TYPES} + > + {({ onPress }) => ( + setThumbnailButtonHovered(true)} + onHoverOut={() => setThumbnailButtonHovered(false)} + onPress={onPress} + style={{ + position: "absolute", + right: 8, + top: 8, + zIndex: 1, + backgroundColor: neutral00, + borderColor: isThumbnailButtonHovered + ? neutralFF + : neutral33, + borderWidth: 1, + borderRadius: 999, + height: 36, + width: 36, + justifyContent: "center", + alignItems: "center", + }} + > + + + )} + + + + + + + + + {/* ==== Article content views button */} + + setMode("edition")} + style={{ + backgroundColor: neutral1A, + padding: 8, + borderRadius: 8, + }} + > + Edition + + + setMode("both")} + style={{ + backgroundColor: neutral1A, + padding: 8, + borderRadius: 8, + }} + > + Edition | Preview + + + setMode("preview")} + style={{ + backgroundColor: neutral1A, + padding: 8, + borderRadius: 8, + }} + > + Preview + + + + + {/* ==== Editor and preview */} + + {/* ==== Article content edition */} + {(mode === "both" || mode === "edition") && ( + inputRef.current?.focus()} + onHoverIn={() => setEditorHovered(true)} + onHoverOut={() => setEditorHovered(false)} + > + + + + + name="message" + control={newArticleForm.control} + render={({ field }) => { + const { value, onChange } = field as { + value: string; + onChange: (value: string) => void; + }; + return ( + = RESPONSIVE_BREAKPOINT_S && { + borderWidth, + borderColor: isEditorHovered + ? neutralFF + : neutral00, + }, + ]} + > + { + // The editor input grows depending on the content height + setEditorHeight(e.nativeEvent.contentSize.height); + }} + ref={inputRef} + /> + + ); + }} + /> + + )} + + {/* ==== Article content preview */} + {(mode === "both" || mode === "preview") && ( + + + + + + + {formValues.message} + + + + )} + + + + {step.id !== "UNDEFINED" && isProgressBarShown && ( + <> + + + + )} + + + {isMapShown && ( + setIsMapShown(false)} + setLocation={setLocation} + location={location} + postCategory={postCategory} + /> + )} + + ); +}; diff --git a/packages/screens/FeedNewArticle/components/SocialArticleCardEdition.tsx b/packages/screens/FeedNewArticle/components/SocialArticleCardEdition.tsx new file mode 100644 index 0000000000..e2b271a718 --- /dev/null +++ b/packages/screens/FeedNewArticle/components/SocialArticleCardEdition.tsx @@ -0,0 +1,264 @@ +import React, { FC, memo, useRef, useState } from "react"; +import { Path, useFormContext } from "react-hook-form"; +import { + StyleProp, + TextInput, + TextStyle, + useWindowDimensions, + View, + ViewStyle, +} from "react-native"; +import { Hoverable } from "react-native-hoverable"; + +import defaultThumbnailImage from "@/assets/default-images/default-article-thumbnail.png"; +import { OptimizedImage } from "@/components/OptimizedImage"; +import { CustomPressable } from "@/components/buttons/CustomPressable"; +import { FileUploader } from "@/components/inputs/fileUploader"; +import { SocialCardHeader } from "@/components/socialFeed/SocialCard/SocialCardHeader"; +import { SpacerColumn } from "@/components/spacer"; +import useSelectedWallet from "@/hooks/useSelectedWallet"; +import { IMAGE_MIME_TYPES } from "@/utils/mime"; +import { SOCIAl_CARD_BORDER_RADIUS } from "@/utils/social-feed"; +import { + neutral00, + neutral33, + neutralA3, + neutralFF, +} from "@/utils/style/colors"; +import { + fontSemibold14, + fontSemibold16, + fontSemibold20, +} from "@/utils/style/fonts"; +import { + layout, + RESPONSIVE_BREAKPOINT_S, + SOCIAL_FEED_BREAKPOINT_M, +} from "@/utils/style/layout"; +import { NewArticleFormValues } from "@/utils/types/feed"; + +// ========= IT'S A TEST. I WOULD LIKE AN EDITABLE CARD WYSIWYG ======= + +const ARTICLE_CARD_PADDING_VERTICAL = layout.spacing_x2; +const ARTICLE_CARD_PADDING_HORIZONTAL = layout.spacing_x2_5; + +export const SocialArticleCardEdition: FC<{ + style?: StyleProp; +}> = memo(({ style }) => { + const selectedWallet = useSelectedWallet(); + const newArticleForm = useFormContext(); + const title = newArticleForm.watch("title"); + const shortDescription = newArticleForm.watch("shortDescription"); + const thumbnailImage = newArticleForm.watch("thumbnailImage"); + const [viewWidth, setViewWidth] = useState(0); + const { width: windowWidth } = useWindowDimensions(); + const [hoveredInput, setHoveredInput] = + useState | null>(null); + + const titleInputMinHeight = 24; + const titleInputMaxHeight = windowWidth < SOCIAL_FEED_BREAKPOINT_M ? 44 : 64; + const [titleInputHeight, setTitleInputHeight] = useState(titleInputMinHeight); + const shortDescInputMaxHeight = 80; + const [shortDescInputHeight, setShortDescInputHeight] = useState( + shortDescInputMaxHeight, + ); + + const articleCardHeight = windowWidth < SOCIAL_FEED_BREAKPOINT_M ? 214 : 254; + const titleFont = + windowWidth < SOCIAL_FEED_BREAKPOINT_M ? fontSemibold16 : fontSemibold20; + const thumbnailImageWidth = viewWidth / 3; + const borderRadius = + windowWidth < RESPONSIVE_BREAKPOINT_S ? 0 : SOCIAl_CARD_BORDER_RADIUS; + + const thumbnailURI = thumbnailImage?.url + ? thumbnailImage.url.includes("://") + ? thumbnailImage.url + : "ipfs://" + thumbnailImage.url // we need this hack because ipfs "urls" in feed are raw CIDs + : defaultThumbnailImage; + + const titleInputRef = useRef(null); + const shortDescInputRef = useRef(null); + + return ( + setViewWidth(e.nativeEvent.layout.width)} + style={[ + { + borderWidth: 1, + borderColor: neutral33, + borderRadius, + backgroundColor: neutral00, + width: "100%", + flexDirection: "row", + justifyContent: "space-between", + height: articleCardHeight, + flex: 1, + }, + style, + ]} + > + + + + + + + {/* // TODO: Good growth and shrink input. Maybe need a maxLength */} + setHoveredInput("title")} + onMouseLeave={() => setHoveredInput(null)} + > + { + newArticleForm.setValue("title", text); + + // The idea was to prevent typing more than 2 lines + newArticleForm.setValue("title", text); + if (titleInputRef.current) { + titleInputRef.current.measure((x, y, width, height) => { + console.log("heightheight", height); + if (height > titleInputMaxHeight) { + newArticleForm.setValue("title", title.slice(0, -1)); + } else { + setTitleInputHeight(height); + } + }); + } + }} + onContentSizeChange={(e) => + setTitleInputHeight(e.nativeEvent.contentSize.height) + } + /> + + + + + setHoveredInput("shortDescription")} + onMouseLeave={() => setHoveredInput(null)} + > + { + newArticleForm.setValue("shortDescription", text); + + // The idea was to prevent typing more than 3 lines + if (shortDescInputRef.current && shortDescription) { + shortDescInputRef.current.measure((x, y, width, height) => { + console.log("heightheight", height); + if (height > shortDescInputMaxHeight) { + newArticleForm.setValue( + "shortDescription", + shortDescription.slice(0, -1), + ); + } else { + setShortDescInputHeight(height); + } + }); + } + }} + onContentSizeChange={(e) => + setShortDescInputHeight(e.nativeEvent.contentSize.height) + } + /> + + + + + {/*TODO: Input*/} + + newArticleForm.setValue("thumbnailImage", files?.[0]) + } + mimeTypes={IMAGE_MIME_TYPES} + > + {({ onPress }) => ( + setHoveredInput("thumbnailImage")} + onHoverOut={() => setHoveredInput(null)} + onPress={onPress} + style={ + hoveredInput === "thumbnailImage" && { + borderColor: neutralFF, + borderWidth: 1, + } + } + > + + + )} + + + ); +}); diff --git a/packages/utils/navigation.ts b/packages/utils/navigation.ts index b13e114a2d..16248b7d63 100644 --- a/packages/utils/navigation.ts +++ b/packages/utils/navigation.ts @@ -61,6 +61,12 @@ export type RootStackParamList = { additionalHashtag?: string; }) | undefined; + FeedNewArticleMarkdown: + | (NewPostFormValues & { + additionalMention?: string; + additionalHashtag?: string; + }) + | undefined; FeedPostView: { id: string }; HashtagFeed: { hashtag: string }; @@ -239,6 +245,7 @@ const getNavConfig: (homeScreen: keyof RootStackParamList) => NavConfig = ( NFTDetail: "nft/:id", Feed: "feed/:tab?", FeedNewArticle: "feed/new", + FeedNewArticleMarkdown: "feed/new-md", FeedPostView: "feed/post/:id", HashtagFeed: "feed/tag/:hashtag", diff --git a/packages/utils/types/feed.ts b/packages/utils/types/feed.ts index b80f2d679b..3b7a7b220b 100644 --- a/packages/utils/types/feed.ts +++ b/packages/utils/types/feed.ts @@ -19,6 +19,7 @@ export enum PostCategory { Flagged, MusicAudio, Video, + MarkdownArticle, } export interface NewArticleFormValues { diff --git a/yarn.lock b/yarn.lock index 240fc5ed44..825929ee9a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8793,6 +8793,13 @@ __metadata: languageName: node linkType: hard +"camelize@npm:^1.0.0": + version: 1.0.1 + resolution: "camelize@npm:1.0.1" + checksum: 91d8611d09af725e422a23993890d22b2b72b4cabf7239651856950c76b4bf53fe0d0da7c5e4db05180e898e4e647220e78c9fbc976113bd96d603d1fcbfcb99 + languageName: node + linkType: hard + "caniuse-lite@npm:^1.0.30001565": version: 1.0.30001579 resolution: "caniuse-lite@npm:1.0.30001579" @@ -9668,6 +9675,13 @@ __metadata: languageName: node linkType: hard +"css-color-keywords@npm:^1.0.0": + version: 1.0.0 + resolution: "css-color-keywords@npm:1.0.0" + checksum: 8f125e3ad477bd03c77b533044bd9e8a6f7c0da52d49bbc0bbe38327b3829d6ba04d368ca49dd9ff3b667d2fc8f1698d891c198bbf8feade1a5501bf5a296408 + languageName: node + linkType: hard + "css-in-js-utils@npm:^3.1.0": version: 3.1.0 resolution: "css-in-js-utils@npm:3.1.0" @@ -9690,6 +9704,17 @@ __metadata: languageName: node linkType: hard +"css-to-react-native@npm:^3.0.0, css-to-react-native@npm:^3.2.0": + version: 3.2.0 + resolution: "css-to-react-native@npm:3.2.0" + dependencies: + camelize: ^1.0.0 + css-color-keywords: ^1.0.0 + postcss-value-parser: ^4.0.2 + checksum: 263be65e805aef02c3f20c064665c998a8c35293e1505dbe6e3054fb186b01a9897ac6cf121f9840e5a9dfe3fb3994f6fcd0af84a865f1df78ba5bf89e77adce + languageName: node + linkType: hard + "css-tree@npm:^1.1.3": version: 1.1.3 resolution: "css-tree@npm:1.1.3" @@ -10618,6 +10643,13 @@ __metadata: languageName: node linkType: hard +"entities@npm:~2.0.0": + version: 2.0.3 + resolution: "entities@npm:2.0.3" + checksum: 5a7899fcc622e0d76afdeafe4c58a6b40ae3a8ee4772e5825a648c11a2ca324a9a02515386f512e466baac4aeb551f3d3b79eaece5cd98369b9f8601be336b1a + languageName: node + linkType: hard + "env-editor@npm:^0.4.1": version: 0.4.2 resolution: "env-editor@npm:0.4.2" @@ -14820,6 +14852,15 @@ __metadata: languageName: node linkType: hard +"linkify-it@npm:^2.0.0": + version: 2.2.0 + resolution: "linkify-it@npm:2.2.0" + dependencies: + uc.micro: ^1.0.1 + checksum: d198871d0b3f3cfdb745dae564bfd6743474f20cd0ef1057e6ca29451834749e7f3da52b59b4de44e98f31a1e5c71bdad160490d4ae54de251cbcde57e4d7837 + languageName: node + linkType: hard + "linkify-it@npm:^5.0.0": version: 5.0.0 resolution: "linkify-it@npm:5.0.0" @@ -15176,6 +15217,21 @@ __metadata: languageName: node linkType: hard +"markdown-it@npm:^10.0.0": + version: 10.0.0 + resolution: "markdown-it@npm:10.0.0" + dependencies: + argparse: ^1.0.7 + entities: ~2.0.0 + linkify-it: ^2.0.0 + mdurl: ^1.0.1 + uc.micro: ^1.0.5 + bin: + markdown-it: bin/markdown-it.js + checksum: 69f5ee640cbebb451b80d3cce308fff7230767e05c0f8c206a1e413775b7a6e5a08e91e9f3ec59f9b5c5a45493f9ce7ac089379cffb60c9d3e6677ed9d535086 + languageName: node + linkType: hard + "markdown-it@npm:^14.1.0": version: 14.1.0 resolution: "markdown-it@npm:14.1.0" @@ -15280,6 +15336,13 @@ __metadata: languageName: node linkType: hard +"mdurl@npm:^1.0.1": + version: 1.0.1 + resolution: "mdurl@npm:1.0.1" + checksum: 71731ecba943926bfbf9f9b51e28b5945f9411c4eda80894221b47cc105afa43ba2da820732b436f0798fd3edbbffcd1fc1415843c41a87fea08a41cc1e3d02b + languageName: node + linkType: hard + "mdurl@npm:^2.0.0": version: 2.0.0 resolution: "mdurl@npm:2.0.0" @@ -17028,7 +17091,7 @@ __metadata: languageName: node linkType: hard -"postcss-value-parser@npm:^4.2.0": +"postcss-value-parser@npm:^4.0.2, postcss-value-parser@npm:^4.2.0": version: 4.2.0 resolution: "postcss-value-parser@npm:4.2.0" checksum: 819ffab0c9d51cf0acbabf8996dffbfafbafa57afc0e4c98db88b67f2094cb44488758f06e5da95d7036f19556a4a732525e84289a425f4f6fd8e412a9d7442f @@ -17232,7 +17295,7 @@ __metadata: languageName: node linkType: hard -"prop-types@npm:^15.5.6, prop-types@npm:^15.5.8, prop-types@npm:^15.7.2, prop-types@npm:^15.8.0, prop-types@npm:^15.8.1": +"prop-types@npm:^15.5.10, prop-types@npm:^15.5.6, prop-types@npm:^15.5.8, prop-types@npm:^15.7.2, prop-types@npm:^15.8.0, prop-types@npm:^15.8.1": version: 15.8.1 resolution: "prop-types@npm:15.8.1" dependencies: @@ -17666,6 +17729,15 @@ __metadata: languageName: node linkType: hard +"react-native-fit-image@npm:^1.5.5": + version: 1.5.5 + resolution: "react-native-fit-image@npm:1.5.5" + dependencies: + prop-types: ^15.5.10 + checksum: 2f3ce06b43191efe3a4bd0698a4117342d821455c96433e9561d50f156ee446d2c449bf9e0908f9a8b7bbb8f95c02478d4fca8d8d103f38b6ce3b8b75557af15 + languageName: node + linkType: hard + "react-native-fs@npm:^2.20.0": version: 2.20.0 resolution: "react-native-fs@npm:2.20.0" @@ -17768,6 +17840,21 @@ __metadata: languageName: node linkType: hard +"react-native-markdown-display@npm:^7.0.2": + version: 7.0.2 + resolution: "react-native-markdown-display@npm:7.0.2" + dependencies: + css-to-react-native: ^3.0.0 + markdown-it: ^10.0.0 + prop-types: ^15.7.2 + react-native-fit-image: ^1.5.5 + peerDependencies: + react: ">=16.2.0" + react-native: ">=0.50.4" + checksum: 69c64f2d21f4ebf001c71d6dff31ef5e13946a0b46b2d2f2830efa89d648afbd8c336be8552264b42d693446e13a3f81fdbfdd5aa9b20878441acabd59f0a54e + languageName: node + linkType: hard + "react-native-paper@npm:^4.12.5": version: 4.12.8 resolution: "react-native-paper@npm:4.12.8" @@ -20200,6 +20287,7 @@ __metadata: cross-env: ^7.0.3 crypto-browserify: ^3.12.0 crypto-js: ^4.2.0 + css-to-react-native: ^3.2.0 cypress: ^13.9.0 depcheck: ^1.4.7 dotenv: ^16.3.1 @@ -20251,6 +20339,7 @@ __metadata: pluralize: ^8.0.0 postinstall-postinstall: ^2.1.0 prettier: ^3.0.3 + prop-types: ^15.8.1 protobufjs: ^7.2.5 react: 18.2.0 react-dom: 18.2.0 @@ -20263,6 +20352,7 @@ __metadata: react-native-confetti-cannon: ^1.5.2 react-native-countdown-component: ^2.7.1 react-native-drax: ^0.10.2 + react-native-fit-image: ^1.5.5 react-native-fs: ^2.20.0 react-native-gesture-handler: ~2.14.0 react-native-get-random-values: ~1.8.0 @@ -20271,6 +20361,7 @@ __metadata: react-native-image-picker: ^7.1.0 react-native-keyboard-aware-scrollview: ^2.1.0 react-native-leaflet-view: ^0.1.2 + react-native-markdown-display: ^7.0.2 react-native-paper: ^4.12.5 react-native-pell-rich-editor: ^1.8.8 react-native-pie-chart: ^3.0.1 @@ -20846,6 +20937,13 @@ __metadata: languageName: node linkType: hard +"uc.micro@npm:^1.0.1, uc.micro@npm:^1.0.5": + version: 1.0.6 + resolution: "uc.micro@npm:1.0.6" + checksum: 6898bb556319a38e9cf175e3628689347bd26fec15fc6b29fa38e0045af63075ff3fea4cf1fdba9db46c9f0cbf07f2348cd8844889dd31ebd288c29fe0d27e7a + languageName: node + linkType: hard + "uc.micro@npm:^2.0.0, uc.micro@npm:^2.1.0": version: 2.1.0 resolution: "uc.micro@npm:2.1.0" From 54af8157cd88ab7f4d1dc2752eefadd38ee95b5c Mon Sep 17 00:00:00 2001 From: WaDadidou Date: Mon, 9 Dec 2024 15:10:17 -0500 Subject: [PATCH 02/22] wip(article): Shrink content input --- .../FeedNewArticleMarkdownScreen.tsx | 29 +++++++++++++++---- 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/packages/screens/FeedNewArticle/FeedNewArticleMarkdownScreen.tsx b/packages/screens/FeedNewArticle/FeedNewArticleMarkdownScreen.tsx index 82476f3296..d261866ec7 100644 --- a/packages/screens/FeedNewArticle/FeedNewArticleMarkdownScreen.tsx +++ b/packages/screens/FeedNewArticle/FeedNewArticleMarkdownScreen.tsx @@ -277,13 +277,26 @@ export const FeedNewArticleMarkdownScreen: ScreenFC< const [isEditorHovered, setEditorHovered] = useState(false); const borderWidth = 1; - const editorMinHeight = height - 140; - const [editorHeight, setEditorHeight] = useState(editorMinHeight); + const contentInputMinHeight = height - 140; + const [contentInputHeight, setContentInputHeight] = useState( + contentInputMinHeight, + ); const [mode, setMode] = useState<"edition" | "both" | "preview">("both"); const editorPadding = windowWidth < RESPONSIVE_BREAKPOINT_S ? 0 : layout.spacing_x2 - borderWidth * 2; + + // TODO: Find type: evt.nativeEvent.target was not recognised by LayoutChangeEvent, but the object target exist, so i don't understand (Same as NewsFeedInput) + // https://github.com/necolas/react-native-web/issues/795#issuecomment-1297511068, fix that i found for shrink lines when we deleting lines in the editor + const adjustTextInputSize = (evt: any) => { + const el = evt?.nativeEvent?.target; + if (el) { + el.style.height = 0; + const newHeight = el.offsetHeight - el.clientHeight + el.scrollHeight; + el.style.height = `${newHeight < contentInputMinHeight ? contentInputMinHeight : newHeight}px`; + } + }; /////////////////////////////////////////// return ( @@ -559,16 +572,20 @@ export const FeedNewArticleMarkdownScreen: ScreenFC< value={value} style={[ { - height: editorHeight, + height: contentInputHeight, outlineStyle: "none", color: neutralA3, border: "none", } as TextStyle, ]} + onChange={adjustTextInputSize} + onLayout={adjustTextInputSize} onChangeText={onChange} onContentSizeChange={(e) => { // The editor input grows depending on the content height - setEditorHeight(e.nativeEvent.contentSize.height); + setContentInputHeight( + e.nativeEvent.contentSize.height, + ); }} ref={inputRef} /> @@ -603,9 +620,9 @@ export const FeedNewArticleMarkdownScreen: ScreenFC< ]} contentContainerStyle={[ mode !== "preview" && { - height: editorHeight + editorPadding, + height: contentInputHeight + editorPadding, }, - { minHeight: editorMinHeight + editorPadding }, + { minHeight: contentInputMinHeight + editorPadding }, ]} // + editorPadding to offset the inexistant ScrollView paddingTop > Date: Mon, 9 Dec 2024 19:27:47 -0500 Subject: [PATCH 03/22] wip(article): Handle local images --- .../FeedNewArticleMarkdownScreen.tsx | 248 +-------------- .../ArticleContentEditor.tsx | 300 ++++++++++++++++++ .../ArticleContentEditor/Toolbar/Toolbar.tsx | 125 ++++++++ .../components/ArticleContentEditor/utils.ts | 1 + 4 files changed, 435 insertions(+), 239 deletions(-) create mode 100644 packages/screens/FeedNewArticle/components/ArticleContentEditor/ArticleContentEditor.tsx create mode 100644 packages/screens/FeedNewArticle/components/ArticleContentEditor/Toolbar/Toolbar.tsx create mode 100644 packages/screens/FeedNewArticle/components/ArticleContentEditor/utils.ts diff --git a/packages/screens/FeedNewArticle/FeedNewArticleMarkdownScreen.tsx b/packages/screens/FeedNewArticle/FeedNewArticleMarkdownScreen.tsx index d261866ec7..55ffac7780 100644 --- a/packages/screens/FeedNewArticle/FeedNewArticleMarkdownScreen.tsx +++ b/packages/screens/FeedNewArticle/FeedNewArticleMarkdownScreen.tsx @@ -1,21 +1,13 @@ import pluralize from "pluralize"; -import React, { useEffect, useRef, useState } from "react"; -import { Controller, useForm } from "react-hook-form"; -import { - ScrollView, - TextInput, - TextStyle, - useWindowDimensions, - View, -} from "react-native"; -import Markdown from "react-native-markdown-display"; +import { useEffect, useRef, useState } from "react"; +import { FormProvider, useForm } from "react-hook-form"; +import { ScrollView, useWindowDimensions, View } from "react-native"; import { useSelector } from "react-redux"; -import { fontSemibold12 } from "./../../utils/style/fonts"; -import priceSVG from "../../../assets/icons/price.svg"; import useSelectedWallet from "../../hooks/useSelectedWallet"; import penSVG from "@/assets/icons/pen.svg"; +import priceSVG from "@/assets/icons/price.svg"; import { BrandText } from "@/components/BrandText"; import { SVG } from "@/components/SVG"; import { ScreenContainer } from "@/components/ScreenContainer"; @@ -28,7 +20,7 @@ import { FeedPostingProgressBar } from "@/components/loaders/FeedPostingProgress import { PublishValues } from "@/components/socialFeed/RichText/RichText.type"; import { SocialArticleCard } from "@/components/socialFeed/SocialCard/cards/SocialArticleCard"; import { MapModal } from "@/components/socialFeed/modals/MapModal/MapModal"; -import { SpacerColumn, SpacerRow } from "@/components/spacer"; +import { SpacerColumn } from "@/components/spacer"; import { useFeedbacks } from "@/context/FeedbacksProvider"; import { useWalletControl } from "@/context/WalletControlProvider"; import { useFeedPosting } from "@/hooks/feed/useFeedPosting"; @@ -37,21 +29,18 @@ import { useIsMobile } from "@/hooks/useIsMobile"; import { useMaxResolution } from "@/hooks/useMaxResolution"; import { useSelectedNetworkId } from "@/hooks/useSelectedNetwork"; import { NetworkFeature } from "@/networks"; +import { ArticleContentEditor } from "@/screens/FeedNewArticle/components/ArticleContentEditor/ArticleContentEditor"; import { selectNFTStorageAPI } from "@/store/slices/settings"; import { feedPostingStep, FeedPostingStepId } from "@/utils/feed/posting"; import { generateArticleMetadata } from "@/utils/feed/queries"; import { generateIpfsKey } from "@/utils/ipfs"; import { IMAGE_MIME_TYPES } from "@/utils/mime"; import { ScreenFC, useAppNavigation } from "@/utils/navigation"; -import { ARTICLE_MAX_WIDTH } from "@/utils/social-feed"; import { neutral00, neutral11, - neutral17, - neutral1A, neutral33, neutral77, - neutralA3, neutralFF, secondaryColor, } from "@/utils/style/colors"; @@ -271,34 +260,6 @@ export const FeedNewArticleMarkdownScreen: ScreenFC< scrollViewRef.current?.scrollToEnd(); }, [step, isLoading]); - /////////// MD EDITOR ///////////////////// - const { height } = useMaxResolution(); - const inputRef = useRef(null); - const [isEditorHovered, setEditorHovered] = useState(false); - const borderWidth = 1; - - const contentInputMinHeight = height - 140; - const [contentInputHeight, setContentInputHeight] = useState( - contentInputMinHeight, - ); - const [mode, setMode] = useState<"edition" | "both" | "preview">("both"); - const editorPadding = - windowWidth < RESPONSIVE_BREAKPOINT_S - ? 0 - : layout.spacing_x2 - borderWidth * 2; - - // TODO: Find type: evt.nativeEvent.target was not recognised by LayoutChangeEvent, but the object target exist, so i don't understand (Same as NewsFeedInput) - // https://github.com/necolas/react-native-web/issues/795#issuecomment-1297511068, fix that i found for shrink lines when we deleting lines in the editor - const adjustTextInputSize = (evt: any) => { - const el = evt?.nativeEvent?.target; - if (el) { - el.style.height = 0; - const newHeight = el.offsetHeight - el.clientHeight + el.scrollHeight; - el.style.height = `${newHeight < contentInputMinHeight ? contentInputMinHeight : newHeight}px`; - } - }; - /////////////////////////////////////////// - return ( - - {/* ==== Article content views button */} - - setMode("edition")} - style={{ - backgroundColor: neutral1A, - padding: 8, - borderRadius: 8, - }} - > - Edition - - - setMode("both")} - style={{ - backgroundColor: neutral1A, - padding: 8, - borderRadius: 8, - }} - > - Edition | Preview - - - setMode("preview")} - style={{ - backgroundColor: neutral1A, - padding: 8, - borderRadius: 8, - }} - > - Preview - - - - - {/* ==== Editor and preview */} - - {/* ==== Article content edition */} - {(mode === "both" || mode === "edition") && ( - inputRef.current?.focus()} - onHoverIn={() => setEditorHovered(true)} - onHoverOut={() => setEditorHovered(false)} - > - - - - - name="message" - control={newArticleForm.control} - render={({ field }) => { - const { value, onChange } = field as { - value: string; - onChange: (value: string) => void; - }; - return ( - = RESPONSIVE_BREAKPOINT_S && { - borderWidth, - borderColor: isEditorHovered - ? neutralFF - : neutral00, - }, - ]} - > - { - // The editor input grows depending on the content height - setContentInputHeight( - e.nativeEvent.contentSize.height, - ); - }} - ref={inputRef} - /> - - ); - }} - /> - - )} - - {/* ==== Article content preview */} - {(mode === "both" || mode === "preview") && ( - - - - - - - {formValues.message} - - - - )} - - + + + {step.id !== "UNDEFINED" && isProgressBarShown && ( <> diff --git a/packages/screens/FeedNewArticle/components/ArticleContentEditor/ArticleContentEditor.tsx b/packages/screens/FeedNewArticle/components/ArticleContentEditor/ArticleContentEditor.tsx new file mode 100644 index 0000000000..be7c5c66d4 --- /dev/null +++ b/packages/screens/FeedNewArticle/components/ArticleContentEditor/ArticleContentEditor.tsx @@ -0,0 +1,300 @@ +import { FC, useRef, useState } from "react"; +import { Controller, useFormContext } from "react-hook-form"; +import { + ScrollView, + TextInput, + TextStyle, + useWindowDimensions, + View, +} from "react-native"; +import Markdown from "react-native-markdown-display"; + +import { OptimizedImage } from "@/components/OptimizedImage"; +import { CustomPressable } from "@/components/buttons/CustomPressable"; +import { Label } from "@/components/inputs/TextInputCustom"; +import { SpacerColumn } from "@/components/spacer"; +import { useMaxResolution } from "@/hooks/useMaxResolution"; +import { Toolbar } from "@/screens/FeedNewArticle/components/ArticleContentEditor/Toolbar/Toolbar"; +import { ContentMode } from "@/screens/FeedNewArticle/components/ArticleContentEditor/utils"; +import { ARTICLE_MAX_WIDTH } from "@/utils/social-feed"; +import { + neutral00, + neutral17, + neutralA3, + neutralFF, +} from "@/utils/style/colors"; +import { layout, RESPONSIVE_BREAKPOINT_S } from "@/utils/style/layout"; +import { NewArticleFormValues } from "@/utils/types/feed"; +import { LocalFileData } from "@/utils/types/files"; + +interface Props { + width: number; +} + +export const ArticleContentEditor: FC = ({ width }) => { + // ========== Layout + const { width: windowWidth } = useWindowDimensions(); + const { height } = useMaxResolution(); + const textInputRef = useRef(null); + const [isTextInputHovered, setTextInputHovered] = useState(false); + const borderWidth = 1; + const textInputMinHeight = height - 140; + const [textInputHeight, setTextInputHeight] = useState(textInputMinHeight); + const [mode, setMode] = useState("BOTH"); + const textInputContainerPadding = + windowWidth < RESPONSIVE_BREAKPOINT_S + ? 0 + : layout.spacing_x2 - borderWidth * 2; + + // TODO: Find type: evt.nativeEvent.target was not recognised by LayoutChangeEvent, but the object target exist, so i don't understand (Same as NewsFeedInput) + // https://github.com/necolas/react-native-web/issues/795#issuecomment-1297511068, fix that i found for shrink lines when we deleting lines in the input + const adjustTextInputSize = (evt: any) => { + const el = evt?.nativeEvent?.target; + if (el) { + el.style.height = 0; + const newHeight = el.offsetHeight - el.clientHeight + el.scrollHeight; + el.style.height = `${newHeight < textInputMinHeight ? textInputMinHeight : newHeight}px`; + } + }; + + // ========== Form + const { watch, control, setValue } = useFormContext(); + const message = watch("message"); + + const addImage = (file: LocalFileData) => { + if (!file?.url) { + console.error("Invalid file: Missing URL or MIME type"); + return; + } + setValue("message", `![image](${file.url})`); + }; + + // ========== Markdown + const markdownStyle = { + body: { + color: neutralA3, + lineBreak: "anywhere", + } as TextStyle, + // p:{ + // // overflowWrap: 'break-word', // Gère le découpage des mots longs + // // wordWrap: 'break-word', + // width: '100%', + // maxWidth: 400, color: neutralA3, display: "flex",flexWrap: "wrap", flex: 1, flexShrink: 1}, + // span:{ + // // overflowWrap: 'break-word', // Gère le découpage des mots longs + // // wordWrap: 'break-word', + // width: '100%', + // maxWidth: 400, color: neutralA3, display: "flex",flexWrap: "wrap", flex: 1, flexShrink: 1}, + // div:{ + // // overflowWrap: 'break-word', // Gère le découpage des mots longs + // // wordWrap: 'break-word', + // width: '100%', + // maxWidth: 400, color: neutralA3, display: "flex", flexWrap: "wrap", flex: 1, flexShrink: 1}, + hr: { backgroundColor: neutralA3 }, + code_inline: { + backgroundColor: neutral17, + borderWidth: 0, + }, + code_block: { + backgroundColor: neutral17, + borderWidth: 0, + }, + fence: { + backgroundColor: neutral17, + borderWidth: 0, + }, + }; + + const markdownRules = { + // TODO: type this + image: (node, children, parent, styles) => { + const { src } = node.attributes; + return ( + + ); + }, + }; + + // ========== JSX + return ( + + {/* ==== Editor views button */} + {/**/} + {/* setMode("edition")}*/} + {/* style={{*/} + {/* backgroundColor: neutral1A,*/} + {/* padding: 8,*/} + {/* borderRadius: 8,*/} + {/* }}*/} + {/* >*/} + {/* Edition*/} + {/* */} + {/* */} + {/* setMode("both")}*/} + {/* style={{*/} + {/* backgroundColor: neutral1A,*/} + {/* padding: 8,*/} + {/* borderRadius: 8,*/} + {/* }}*/} + {/* >*/} + {/* Edition | Preview*/} + {/* */} + {/* */} + {/* setMode("preview")}*/} + {/* style={{*/} + {/* backgroundColor: neutral1A,*/} + {/* padding: 8,*/} + {/* borderRadius: 8,*/} + {/* }}*/} + {/* >*/} + {/* Preview*/} + {/* */} + {/**/} + + {/* ==== Toolbar */} + { + /*TODO*/ + }} + addGIF={() => { + /*TODO*/ + }} + addAudio={() => { + /*TODO*/ + }} + addVideo={() => { + /*TODO*/ + }} + addImage={addImage} + setMode={setMode} + /> + + + {/* ==== Edition and preview */} + + {/* ==== Edition */} + {(mode === "BOTH" || mode === "EDITION") && ( + textInputRef.current?.focus()} + onHoverIn={() => setTextInputHovered(true)} + onHoverOut={() => setTextInputHovered(false)} + > + + + + + name="message" + control={control} + render={({ field }) => { + const { value, onChange } = field as { + value: string; + onChange: (value: string) => void; + }; + return ( + = RESPONSIVE_BREAKPOINT_S && { + borderWidth, + borderColor: isTextInputHovered ? neutralFF : neutral00, + }, + ]} + > + { + // The input grows depending on the content height + setTextInputHeight(e.nativeEvent.contentSize.height); + }} + ref={textInputRef} + /> + + ); + }} + /> + + )} + + {/* ==== Preview */} + {(mode === "BOTH" || mode === "PREVIEW") && ( + + + + + + + {message} + + + + )} + + + ); +}; diff --git a/packages/screens/FeedNewArticle/components/ArticleContentEditor/Toolbar/Toolbar.tsx b/packages/screens/FeedNewArticle/components/ArticleContentEditor/Toolbar/Toolbar.tsx new file mode 100644 index 0000000000..7eb39a01de --- /dev/null +++ b/packages/screens/FeedNewArticle/components/ArticleContentEditor/Toolbar/Toolbar.tsx @@ -0,0 +1,125 @@ +import { Dispatch, FC, SetStateAction } from "react"; +import { View, ViewStyle } from "react-native"; + +import audioSVG from "@/assets/icons/audio.svg"; +import cameraSVG from "@/assets/icons/camera.svg"; +import videoSVG from "@/assets/icons/video.svg"; +import { BrandText } from "@/components/BrandText"; +import { IconBox } from "@/components/IconBox"; +import { CustomPressable } from "@/components/buttons/CustomPressable"; +import { FileUploader } from "@/components/inputs/fileUploader"; +import { EmojiSelector } from "@/components/socialFeed/EmojiSelector"; +import { GIFSelector } from "@/components/socialFeed/GIFSelector"; +import { SpacerRow } from "@/components/spacer"; +import { ContentMode } from "@/screens/FeedNewArticle/components/ArticleContentEditor/utils"; +import { + AUDIO_MIME_TYPES, + IMAGE_MIME_TYPES, + VIDEO_MIME_TYPES, +} from "@/utils/mime"; +import { fontSemibold12 } from "@/utils/style/fonts"; +import { layout } from "@/utils/style/layout"; +import { LocalFileData } from "@/utils/types/files"; + +interface Props { + addEmoji: (emoji: string) => void; + addGIF: (url: string) => void; + addAudio: (file: LocalFileData) => void; + addVideo: (file: LocalFileData) => void; + addImage: (file: LocalFileData) => void; + setMode: Dispatch>; +} + +export const Toolbar: FC = ({ + addEmoji, + addGIF, + addAudio, + addVideo, + addImage, + setMode, +}) => { + return ( + + setMode("EDITION")}> + [] + + + + + setMode("BOTH")}> + []() + + + + + setMode("PREVIEW")}> + () + + + + + addEmoji(emoji)} + buttonStyle={toolbarCustomButtonCStyle} + iconStyle={toolbarCustomButtonIconCStyle} + /> + + (url ? addGIF(url) : undefined)} + buttonStyle={toolbarCustomButtonCStyle} + iconStyle={toolbarCustomButtonIconCStyle} + // disabled={isGIFSelectorDisabled} + /> + + addAudio(files?.[0])} + mimeTypes={AUDIO_MIME_TYPES} + > + {({ onPress }) => ( + + )} + + + addVideo(files?.[0])} + mimeTypes={VIDEO_MIME_TYPES} + > + {({ onPress }) => ( + + )} + + + addImage(files?.[0])} + mimeTypes={IMAGE_MIME_TYPES} + > + {({ onPress }) => ( + + )} + + + ); +}; + +const toolbarCustomButtonIconCStyle: ViewStyle = { + borderRadius: 4, + height: 30, + width: 30, +}; +const toolbarCustomButtonCStyle: ViewStyle = { + margin: layout.spacing_x0_5, +}; diff --git a/packages/screens/FeedNewArticle/components/ArticleContentEditor/utils.ts b/packages/screens/FeedNewArticle/components/ArticleContentEditor/utils.ts new file mode 100644 index 0000000000..075d6fc46f --- /dev/null +++ b/packages/screens/FeedNewArticle/components/ArticleContentEditor/utils.ts @@ -0,0 +1 @@ +export type ContentMode = "EDITION" | "BOTH" | "PREVIEW"; From 78fb7e1ef19a5a06a5eb47e9fa172a5ed7fb0c13 Mon Sep 17 00:00:00 2001 From: WaDadidou Date: Tue, 10 Dec 2024 21:33:22 -0500 Subject: [PATCH 04/22] wip(article): Change lib for markdown render --- package.json | 5 +- .../ArticleContentEditor.tsx | 159 +++------- yarn.lock | 292 +++++++++++++----- 3 files changed, 260 insertions(+), 196 deletions(-) diff --git a/package.json b/package.json index 65a64044ec..288db071fa 100644 --- a/package.json +++ b/package.json @@ -89,7 +89,6 @@ "cosmos-endpoints-scorer": "^0.1.0", "crypto-browserify": "^3.12.0", "crypto-js": "^4.2.0", - "css-to-react-native": "^3.2.0", "draft-convert": "^2.1.13", "draft-js": "^0.11.7", "electron-store": "^8.1.0", @@ -152,7 +151,6 @@ "react-native-image-picker": "^7.1.0", "react-native-keyboard-aware-scrollview": "^2.1.0", "react-native-leaflet-view": "^0.1.2", - "react-native-markdown-display": "^7.0.2", "react-native-paper": "^4.12.5", "react-native-pell-rich-editor": "^1.8.8", "react-native-pie-chart": "^3.0.1", @@ -161,6 +159,7 @@ "react-native-reanimated": "^3.6.2", "react-native-reanimated-carousel": "4.0.0-alpha.9", "react-native-reanimated-table": "^0.0.2", + "react-native-render-html": "^6.3.4", "react-native-safe-area-context": "4.8.2", "react-native-screens": "~3.29.0", "react-native-smooth-slider": "^1.3.6", @@ -201,7 +200,7 @@ "@types/draft-convert": "^2.1.4", "@types/draft-js": "^0.11.9", "@types/html-to-draftjs": "^1.4.0", - "@types/markdown-it": "^13.0.7", + "@types/markdown-it": "^14.1.2", "@types/node": "^20.9.1", "@types/react": "~18.2.45", "@types/react-native-countdown-component": "^2.7.0", diff --git a/packages/screens/FeedNewArticle/components/ArticleContentEditor/ArticleContentEditor.tsx b/packages/screens/FeedNewArticle/components/ArticleContentEditor/ArticleContentEditor.tsx index be7c5c66d4..56760cb5fd 100644 --- a/packages/screens/FeedNewArticle/components/ArticleContentEditor/ArticleContentEditor.tsx +++ b/packages/screens/FeedNewArticle/components/ArticleContentEditor/ArticleContentEditor.tsx @@ -1,3 +1,5 @@ +import type { MixedStyleRecord } from "@native-html/transient-render-engine"; +import markdownit from "markdown-it"; import { FC, useRef, useState } from "react"; import { Controller, useFormContext } from "react-hook-form"; import { @@ -7,9 +9,8 @@ import { useWindowDimensions, View, } from "react-native"; -import Markdown from "react-native-markdown-display"; +import RenderHtml from "react-native-render-html"; -import { OptimizedImage } from "@/components/OptimizedImage"; import { CustomPressable } from "@/components/buttons/CustomPressable"; import { Label } from "@/components/inputs/TextInputCustom"; import { SpacerColumn } from "@/components/spacer"; @@ -17,12 +18,7 @@ import { useMaxResolution } from "@/hooks/useMaxResolution"; import { Toolbar } from "@/screens/FeedNewArticle/components/ArticleContentEditor/Toolbar/Toolbar"; import { ContentMode } from "@/screens/FeedNewArticle/components/ArticleContentEditor/utils"; import { ARTICLE_MAX_WIDTH } from "@/utils/social-feed"; -import { - neutral00, - neutral17, - neutralA3, - neutralFF, -} from "@/utils/style/colors"; +import { neutral00, neutralA3, neutralFF } from "@/utils/style/colors"; import { layout, RESPONSIVE_BREAKPOINT_S } from "@/utils/style/layout"; import { NewArticleFormValues } from "@/utils/types/feed"; import { LocalFileData } from "@/utils/types/files"; @@ -32,7 +28,7 @@ interface Props { } export const ArticleContentEditor: FC = ({ width }) => { - // ========== Layout + // ========== UI const { width: windowWidth } = useWindowDimensions(); const { height } = useMaxResolution(); const textInputRef = useRef(null); @@ -40,7 +36,12 @@ export const ArticleContentEditor: FC = ({ width }) => { const borderWidth = 1; const textInputMinHeight = height - 140; const [textInputHeight, setTextInputHeight] = useState(textInputMinHeight); + const [textInputSelection, setTextInputSelection] = useState({ + start: 0, + end: 0, + }); // Used to get the cursor position const [mode, setMode] = useState("BOTH"); + const [renderWidth, setRenderWidth] = useState(0); const textInputContainerPadding = windowWidth < RESPONSIVE_BREAKPOINT_S ? 0 @@ -66,59 +67,27 @@ export const ArticleContentEditor: FC = ({ width }) => { console.error("Invalid file: Missing URL or MIME type"); return; } - setValue("message", `![image](${file.url})`); + const textBefore = message.substring(0, textInputSelection.start); + const textAfter = message.substring( + textInputSelection.start, + message.length - 1, + ); + const result = `${textBefore}![image](${file.url})${textAfter}`; + setValue("message", result); }; // ========== Markdown - const markdownStyle = { - body: { - color: neutralA3, - lineBreak: "anywhere", - } as TextStyle, - // p:{ - // // overflowWrap: 'break-word', // Gère le découpage des mots longs - // // wordWrap: 'break-word', - // width: '100%', - // maxWidth: 400, color: neutralA3, display: "flex",flexWrap: "wrap", flex: 1, flexShrink: 1}, - // span:{ - // // overflowWrap: 'break-word', // Gère le découpage des mots longs - // // wordWrap: 'break-word', - // width: '100%', - // maxWidth: 400, color: neutralA3, display: "flex",flexWrap: "wrap", flex: 1, flexShrink: 1}, - // div:{ - // // overflowWrap: 'break-word', // Gère le découpage des mots longs - // // wordWrap: 'break-word', - // width: '100%', - // maxWidth: 400, color: neutralA3, display: "flex", flexWrap: "wrap", flex: 1, flexShrink: 1}, - hr: { backgroundColor: neutralA3 }, - code_inline: { - backgroundColor: neutral17, - borderWidth: 0, - }, - code_block: { - backgroundColor: neutral17, - borderWidth: 0, - }, - fence: { - backgroundColor: neutral17, - borderWidth: 0, - }, - }; + const md = markdownit(); + const html = md.render(message); - const markdownRules = { - // TODO: type this - image: (node, children, parent, styles) => { - const { src } = node.attributes; - return ( - - ); - }, + // TODO: Style this + const markdownTagStyles: MixedStyleRecord = { + body: { fontSize: 14, color: neutralA3 }, + h1: { fontSize: 24, fontWeight: "bold" }, + p: { margin: 0 }, + strong: { fontWeight: "bold", color: "yellow" }, + a: { color: "blue", textDecorationLine: "underline" }, + // img: {resizeMode: "contain"} }; // ========== JSX @@ -129,42 +98,6 @@ export const ArticleContentEditor: FC = ({ width }) => { width, }} > - {/* ==== Editor views button */} - {/**/} - {/* setMode("edition")}*/} - {/* style={{*/} - {/* backgroundColor: neutral1A,*/} - {/* padding: 8,*/} - {/* borderRadius: 8,*/} - {/* }}*/} - {/* >*/} - {/* Edition*/} - {/* */} - {/* */} - {/* setMode("both")}*/} - {/* style={{*/} - {/* backgroundColor: neutral1A,*/} - {/* padding: 8,*/} - {/* borderRadius: 8,*/} - {/* }}*/} - {/* >*/} - {/* Edition | Preview*/} - {/* */} - {/* */} - {/* setMode("preview")}*/} - {/* style={{*/} - {/* backgroundColor: neutral1A,*/} - {/* padding: 8,*/} - {/* borderRadius: 8,*/} - {/* }}*/} - {/* >*/} - {/* Preview*/} - {/* */} - {/**/} - {/* ==== Toolbar */} { @@ -182,7 +115,7 @@ export const ArticleContentEditor: FC = ({ width }) => { addImage={addImage} setMode={setMode} /> - + {/* ==== Edition and preview */} = ({ width }) => { onChange={adjustTextInputSize} onLayout={adjustTextInputSize} onChangeText={onChange} + onSelectionChange={(e) => + setTextInputSelection(e.nativeEvent.selection) + } onContentSizeChange={(e) => { // The input grows depending on the content height setTextInputHeight(e.nativeEvent.contentSize.height); @@ -271,26 +207,27 @@ export const ArticleContentEditor: FC = ({ width }) => { setRenderWidth(e.nativeEvent.layout.width)} > - - {message} - + )} diff --git a/yarn.lock b/yarn.lock index 825929ee9a..a7606b85b4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4832,6 +4832,24 @@ __metadata: languageName: node linkType: hard +"@jsamr/counter-style@npm:^2.0.1": + version: 2.0.2 + resolution: "@jsamr/counter-style@npm:2.0.2" + checksum: 9434d6e52dcbf6a3422137e3397d801aa3b4f3fd780fc5a12c47db171502f281eaa8ae69b953a1d1bdaf4effeac7c674e7dbdd8341157a6f21a087ccb7af5bfe + languageName: node + linkType: hard + +"@jsamr/react-native-li@npm:^2.3.0": + version: 2.3.1 + resolution: "@jsamr/react-native-li@npm:2.3.1" + peerDependencies: + "@jsamr/counter-style": ^1.0.0 || ^2.0.0 + react: "*" + react-native: "*" + checksum: 3465ac894d125261660cc5d779c226560578927354c8c661be9bcdc46438121cd5561079dd76ad82bb9970c0adf753e62726d6d8849b1b66484aa8090701916b + languageName: node + linkType: hard + "@jsdevtools/ono@npm:^7.1.3": version: 7.1.3 resolution: "@jsdevtools/ono@npm:7.1.3" @@ -5049,6 +5067,38 @@ __metadata: languageName: node linkType: hard +"@native-html/css-processor@npm:1.11.0": + version: 1.11.0 + resolution: "@native-html/css-processor@npm:1.11.0" + dependencies: + css-to-react-native: ^3.0.0 + csstype: ^3.0.8 + peerDependencies: + "@types/react": "*" + "@types/react-native": "*" + checksum: 741ff04c6bfb7f004670ed03c230f417266002c59bd0314e066df28044f5d6ce76ff62db85ff801b9e14dee5a048a87b77d2213bc6f869de31f4d93802c54fd0 + languageName: node + linkType: hard + +"@native-html/transient-render-engine@npm:11.2.3": + version: 11.2.3 + resolution: "@native-html/transient-render-engine@npm:11.2.3" + dependencies: + "@native-html/css-processor": 1.11.0 + "@types/ramda": ^0.27.44 + csstype: ^3.0.9 + domelementtype: ^2.2.0 + domhandler: ^4.2.2 + domutils: ^2.8.0 + htmlparser2: ^7.1.2 + ramda: ^0.27.2 + peerDependencies: + "@types/react-native": "*" + react-native: ^* + checksum: 13248216b19c07703fa5ff9942889ea7dc669d6fd9c944d3d5cf2757088c3e66a5b760f194ac0193ddbbb3f4556655fe10c6e4e5a5efd030da8ec1360b08a605 + languageName: node + linkType: hard + "@noble/curves@npm:1.4.2, @noble/curves@npm:~1.4.0": version: 1.4.2 resolution: "@noble/curves@npm:1.4.2" @@ -6785,10 +6835,10 @@ __metadata: languageName: node linkType: hard -"@types/linkify-it@npm:*": - version: 3.0.5 - resolution: "@types/linkify-it@npm:3.0.5" - checksum: fac28f41a6e576282300a459d70ea0d33aab70dbb77c3d09582bb0335bb00d862b6de69585792a4d590aae4173fbab0bf28861e2d90ca7b2b1439b52688e9ff6 +"@types/linkify-it@npm:^5": + version: 5.0.0 + resolution: "@types/linkify-it@npm:5.0.0" + checksum: ec98e03aa883f70153a17a1e6ed9e28b39a604049b485daeddae3a1482ec65cac0817520be6e301d99fd1a934b3950cf0f855655aae6ec27da2bb676ba4a148e languageName: node linkType: hard @@ -6806,20 +6856,20 @@ __metadata: languageName: node linkType: hard -"@types/markdown-it@npm:^13.0.7": - version: 13.0.7 - resolution: "@types/markdown-it@npm:13.0.7" +"@types/markdown-it@npm:^14.1.2": + version: 14.1.2 + resolution: "@types/markdown-it@npm:14.1.2" dependencies: - "@types/linkify-it": "*" - "@types/mdurl": "*" - checksum: c9e9af441340eb870a7b90b298f6197aa80b55bee28f179a4f85052333f0cb3d3f2763981359d58cf09024961f013999c1c743c1e52a185ca36576d4403f7eb9 + "@types/linkify-it": ^5 + "@types/mdurl": ^2 + checksum: ad66e0b377d6af09a155bb65f675d1e2cb27d20a3d407377fe4508eb29cde1e765430b99d5129f89012e2524abb5525d629f7057a59ff9fd0967e1ff645b9ec6 languageName: node linkType: hard -"@types/mdurl@npm:*": - version: 1.0.5 - resolution: "@types/mdurl@npm:1.0.5" - checksum: e8e872e8da8f517a9c748b06cec61c947cb73fd3069e8aeb0926670ec5dfac5d30549b3d0f1634950401633e812f9b7263f2d5dbe7e98fce12bcb2c659aa4b21 +"@types/mdurl@npm:^2": + version: 2.0.0 + resolution: "@types/mdurl@npm:2.0.0" + checksum: 78746e96c655ceed63db06382da466fd52c7e9dc54d60b12973dfdd110cae06b9439c4b90e17bb8d4461109184b3ea9f3e9f96b3e4bf4aa9fe18b6ac35f283c8 languageName: node linkType: hard @@ -6899,6 +6949,15 @@ __metadata: languageName: node linkType: hard +"@types/ramda@npm:^0.27.40, @types/ramda@npm:^0.27.44": + version: 0.27.66 + resolution: "@types/ramda@npm:0.27.66" + dependencies: + ts-toolbelt: ^6.15.1 + checksum: eea577e4a0934849b4103c1452a7c8ddbc9bbf0e2aafb908467212654555145f846a16fe737563b582e8fb5bd6698481ebec1237537e5e662587c47f626e4c92 + languageName: node + linkType: hard + "@types/react-native-countdown-component@npm:^2.7.0": version: 2.7.4 resolution: "@types/react-native-countdown-component@npm:2.7.4" @@ -6982,6 +7041,13 @@ __metadata: languageName: node linkType: hard +"@types/urijs@npm:^1.19.15": + version: 1.19.25 + resolution: "@types/urijs@npm:1.19.25" + checksum: cce3fd2845d5e143f4130134a5f6ff7e02b4dfc05f4d13c7b28a404fd9420bb8a6483a572c0662693bb18c5b3d8f814270aa75f3fd539f32fae22d005e755b5d + languageName: node + linkType: hard + "@types/use-sync-external-store@npm:^0.0.3": version: 0.0.3 resolution: "@types/use-sync-external-store@npm:0.0.3" @@ -8882,6 +8948,20 @@ __metadata: languageName: node linkType: hard +"character-entities-html4@npm:^1.0.0": + version: 1.1.4 + resolution: "character-entities-html4@npm:1.1.4" + checksum: 22536aba07a378a2326420423ceadd65c0121032c527f80e84dfc648381992ed5aa666d7c2b267cd269864b3682d5b0315fc2f03a9e7c017d1a96d24ec292d5f + languageName: node + linkType: hard + +"character-entities-legacy@npm:^1.0.0": + version: 1.1.4 + resolution: "character-entities-legacy@npm:1.1.4" + checksum: fe03a82c154414da3a0c8ab3188e4237ec68006cbcd681cf23c7cfb9502a0e76cd30ab69a2e50857ca10d984d57de3b307680fff5328ccd427f400e559c3a811 + languageName: node + linkType: hard + "chardet@npm:^0.4.0": version: 0.4.2 resolution: "chardet@npm:0.4.2" @@ -9704,7 +9784,7 @@ __metadata: languageName: node linkType: hard -"css-to-react-native@npm:^3.0.0, css-to-react-native@npm:^3.2.0": +"css-to-react-native@npm:^3.0.0": version: 3.2.0 resolution: "css-to-react-native@npm:3.2.0" dependencies: @@ -9761,7 +9841,7 @@ __metadata: languageName: node linkType: hard -"csstype@npm:^3.0.2": +"csstype@npm:^3.0.2, csstype@npm:^3.0.8, csstype@npm:^3.0.9": version: 3.1.3 resolution: "csstype@npm:3.1.3" checksum: 8db785cc92d259102725b3c694ec0c823f5619a84741b5c7991b8ad135dfaa66093038a1cc63e03361a6cd28d122be48f2106ae72334e067dd619a51f49eddf7 @@ -10354,6 +10434,17 @@ __metadata: languageName: node linkType: hard +"dom-serializer@npm:^1.0.1": + version: 1.4.1 + resolution: "dom-serializer@npm:1.4.1" + dependencies: + domelementtype: ^2.0.1 + domhandler: ^4.2.0 + entities: ^2.0.0 + checksum: fbb0b01f87a8a2d18e6e5a388ad0f7ec4a5c05c06d219377da1abc7bb0f674d804f4a8a94e3f71ff15f6cb7dcfc75704a54b261db672b9b3ab03da6b758b0b22 + languageName: node + linkType: hard + "dom-serializer@npm:^2.0.0": version: 2.0.0 resolution: "dom-serializer@npm:2.0.0" @@ -10365,13 +10456,22 @@ __metadata: languageName: node linkType: hard -"domelementtype@npm:^2.3.0": +"domelementtype@npm:^2.0.1, domelementtype@npm:^2.2.0, domelementtype@npm:^2.3.0": version: 2.3.0 resolution: "domelementtype@npm:2.3.0" checksum: ee837a318ff702622f383409d1f5b25dd1024b692ef64d3096ff702e26339f8e345820f29a68bcdcea8cfee3531776b3382651232fbeae95612d6f0a75efb4f6 languageName: node linkType: hard +"domhandler@npm:^4.2.0, domhandler@npm:^4.2.2": + version: 4.3.1 + resolution: "domhandler@npm:4.3.1" + dependencies: + domelementtype: ^2.2.0 + checksum: 4c665ceed016e1911bf7d1dadc09dc888090b64dee7851cccd2fcf5442747ec39c647bb1cb8c8919f8bbdd0f0c625a6bafeeed4b2d656bbecdbae893f43ffaaa + languageName: node + linkType: hard + "domhandler@npm:^5.0.2, domhandler@npm:^5.0.3": version: 5.0.3 resolution: "domhandler@npm:5.0.3" @@ -10381,6 +10481,17 @@ __metadata: languageName: node linkType: hard +"domutils@npm:^2.8.0": + version: 2.8.0 + resolution: "domutils@npm:2.8.0" + dependencies: + dom-serializer: ^1.0.1 + domelementtype: ^2.2.0 + domhandler: ^4.2.0 + checksum: abf7434315283e9aadc2a24bac0e00eab07ae4313b40cc239f89d84d7315ebdfd2fb1b5bf750a96bc1b4403d7237c7b2ebf60459be394d625ead4ca89b934391 + languageName: node + linkType: hard + "domutils@npm:^3.0.1": version: 3.1.0 resolution: "domutils@npm:3.1.0" @@ -10636,6 +10747,20 @@ __metadata: languageName: node linkType: hard +"entities@npm:^2.0.0": + version: 2.2.0 + resolution: "entities@npm:2.2.0" + checksum: 19010dacaf0912c895ea262b4f6128574f9ccf8d4b3b65c7e8334ad0079b3706376360e28d8843ff50a78aabcb8f08f0a32dbfacdc77e47ed77ca08b713669b3 + languageName: node + linkType: hard + +"entities@npm:^3.0.1": + version: 3.0.1 + resolution: "entities@npm:3.0.1" + checksum: aaf7f12033f0939be91f5161593f853f2da55866db55ccbf72f45430b8977e2b79dbd58c53d0fdd2d00bd7d313b75b0968d09f038df88e308aa97e39f9456572 + languageName: node + linkType: hard + "entities@npm:^4.2.0, entities@npm:^4.4.0, entities@npm:^4.5.0": version: 4.5.0 resolution: "entities@npm:4.5.0" @@ -10643,13 +10768,6 @@ __metadata: languageName: node linkType: hard -"entities@npm:~2.0.0": - version: 2.0.3 - resolution: "entities@npm:2.0.3" - checksum: 5a7899fcc622e0d76afdeafe4c58a6b40ae3a8ee4772e5825a648c11a2ca324a9a02515386f512e466baac4aeb551f3d3b79eaece5cd98369b9f8601be336b1a - languageName: node - linkType: hard - "env-editor@npm:^0.4.1": version: 0.4.2 resolution: "env-editor@npm:0.4.2" @@ -13079,6 +13197,18 @@ __metadata: languageName: node linkType: hard +"htmlparser2@npm:^7.1.2": + version: 7.2.0 + resolution: "htmlparser2@npm:7.2.0" + dependencies: + domelementtype: ^2.0.1 + domhandler: ^4.2.2 + domutils: ^2.8.0 + entities: ^3.0.1 + checksum: 96563d9965729cfcb3f5f19c26d013c6831b4cb38d79d8c185e9cd669ea6a9ffe8fb9ccc74d29a068c9078aa0e2767053ed6b19aa32723c41550340d0094bea0 + languageName: node + linkType: hard + "http-cache-semantics@npm:^4.0.0, http-cache-semantics@npm:^4.1.1": version: 4.1.1 resolution: "http-cache-semantics@npm:4.1.1" @@ -14852,15 +14982,6 @@ __metadata: languageName: node linkType: hard -"linkify-it@npm:^2.0.0": - version: 2.2.0 - resolution: "linkify-it@npm:2.2.0" - dependencies: - uc.micro: ^1.0.1 - checksum: d198871d0b3f3cfdb745dae564bfd6743474f20cd0ef1057e6ca29451834749e7f3da52b59b4de44e98f31a1e5c71bdad160490d4ae54de251cbcde57e4d7837 - languageName: node - linkType: hard - "linkify-it@npm:^5.0.0": version: 5.0.0 resolution: "linkify-it@npm:5.0.0" @@ -15217,21 +15338,6 @@ __metadata: languageName: node linkType: hard -"markdown-it@npm:^10.0.0": - version: 10.0.0 - resolution: "markdown-it@npm:10.0.0" - dependencies: - argparse: ^1.0.7 - entities: ~2.0.0 - linkify-it: ^2.0.0 - mdurl: ^1.0.1 - uc.micro: ^1.0.5 - bin: - markdown-it: bin/markdown-it.js - checksum: 69f5ee640cbebb451b80d3cce308fff7230767e05c0f8c206a1e413775b7a6e5a08e91e9f3ec59f9b5c5a45493f9ce7ac089379cffb60c9d3e6677ed9d535086 - languageName: node - linkType: hard - "markdown-it@npm:^14.1.0": version: 14.1.0 resolution: "markdown-it@npm:14.1.0" @@ -15336,13 +15442,6 @@ __metadata: languageName: node linkType: hard -"mdurl@npm:^1.0.1": - version: 1.0.1 - resolution: "mdurl@npm:1.0.1" - checksum: 71731ecba943926bfbf9f9b51e28b5945f9411c4eda80894221b47cc105afa43ba2da820732b436f0798fd3edbbffcd1fc1415843c41a87fea08a41cc1e3d02b - languageName: node - linkType: hard - "mdurl@npm:^2.0.0": version: 2.0.0 resolution: "mdurl@npm:2.0.0" @@ -17295,7 +17394,7 @@ __metadata: languageName: node linkType: hard -"prop-types@npm:^15.5.10, prop-types@npm:^15.5.6, prop-types@npm:^15.5.8, prop-types@npm:^15.7.2, prop-types@npm:^15.8.0, prop-types@npm:^15.8.1": +"prop-types@npm:^15.5.10, prop-types@npm:^15.5.6, prop-types@npm:^15.5.7, prop-types@npm:^15.5.8, prop-types@npm:^15.7.2, prop-types@npm:^15.8.0, prop-types@npm:^15.8.1": version: 15.8.1 resolution: "prop-types@npm:15.8.1" dependencies: @@ -17528,6 +17627,13 @@ __metadata: languageName: node linkType: hard +"ramda@npm:^0.27.2": + version: 0.27.2 + resolution: "ramda@npm:0.27.2" + checksum: 28d6735dd1eea1a796c56cf6111f3673c6105bbd736e521cdd7826c46a18eeff337c2dba4668f6eed990d539b9961fd6db19aa46ccc1530ba67a396c0a9f580d + languageName: node + linkType: hard + "randexp@npm:0.4.6": version: 0.4.6 resolution: "randexp@npm:0.4.6" @@ -17840,21 +17946,6 @@ __metadata: languageName: node linkType: hard -"react-native-markdown-display@npm:^7.0.2": - version: 7.0.2 - resolution: "react-native-markdown-display@npm:7.0.2" - dependencies: - css-to-react-native: ^3.0.0 - markdown-it: ^10.0.0 - prop-types: ^15.7.2 - react-native-fit-image: ^1.5.5 - peerDependencies: - react: ">=16.2.0" - react-native: ">=0.50.4" - checksum: 69c64f2d21f4ebf001c71d6dff31ef5e13946a0b46b2d2f2830efa89d648afbd8c336be8552264b42d693446e13a3f81fdbfdd5aa9b20878441acabd59f0a54e - languageName: node - linkType: hard - "react-native-paper@npm:^4.12.5": version: 4.12.8 resolution: "react-native-paper@npm:4.12.8" @@ -17964,6 +18055,26 @@ __metadata: languageName: node linkType: hard +"react-native-render-html@npm:^6.3.4": + version: 6.3.4 + resolution: "react-native-render-html@npm:6.3.4" + dependencies: + "@jsamr/counter-style": ^2.0.1 + "@jsamr/react-native-li": ^2.3.0 + "@native-html/transient-render-engine": 11.2.3 + "@types/ramda": ^0.27.40 + "@types/urijs": ^1.19.15 + prop-types: ^15.5.7 + ramda: ^0.27.2 + stringify-entities: ^3.1.0 + urijs: ^1.19.6 + peerDependencies: + react: "*" + react-native: "*" + checksum: 9fd0c915664d4d25d23f48b4b33101385f2e497c643664c09b457eb091f90cd1d60f9c2c4bfad1a55403c8037d52de5dcbdebe0b1ebc9e4883d8a3099a23633b + languageName: node + linkType: hard + "react-native-safe-area-context@npm:4.8.2": version: 4.8.2 resolution: "react-native-safe-area-context@npm:4.8.2" @@ -19876,6 +19987,17 @@ __metadata: languageName: node linkType: hard +"stringify-entities@npm:^3.1.0": + version: 3.1.0 + resolution: "stringify-entities@npm:3.1.0" + dependencies: + character-entities-html4: ^1.0.0 + character-entities-legacy: ^1.0.0 + xtend: ^4.0.0 + checksum: 5b6212e2985101ddb8197d999a6c01abb610f2ba6efd6f8f7d7ec763b61cb08b55735b03febdf501c2091f484df16bc82412419ef35ee21135548f6a15881044 + languageName: node + linkType: hard + "strip-ansi-cjs@npm:strip-ansi@^6.0.1, strip-ansi@npm:^6.0.0, strip-ansi@npm:^6.0.1": version: 6.0.1 resolution: "strip-ansi@npm:6.0.1" @@ -20265,7 +20387,7 @@ __metadata: "@types/html-to-draftjs": ^1.4.0 "@types/leaflet": ^1.9.12 "@types/leaflet.markercluster": ^1.5.4 - "@types/markdown-it": ^13.0.7 + "@types/markdown-it": ^14.1.2 "@types/node": ^20.9.1 "@types/papaparse": ^5.3.14 "@types/pluralize": ^0.0.33 @@ -20287,7 +20409,6 @@ __metadata: cross-env: ^7.0.3 crypto-browserify: ^3.12.0 crypto-js: ^4.2.0 - css-to-react-native: ^3.2.0 cypress: ^13.9.0 depcheck: ^1.4.7 dotenv: ^16.3.1 @@ -20361,7 +20482,6 @@ __metadata: react-native-image-picker: ^7.1.0 react-native-keyboard-aware-scrollview: ^2.1.0 react-native-leaflet-view: ^0.1.2 - react-native-markdown-display: ^7.0.2 react-native-paper: ^4.12.5 react-native-pell-rich-editor: ^1.8.8 react-native-pie-chart: ^3.0.1 @@ -20370,6 +20490,7 @@ __metadata: react-native-reanimated: ^3.6.2 react-native-reanimated-carousel: 4.0.0-alpha.9 react-native-reanimated-table: ^0.0.2 + react-native-render-html: ^6.3.4 react-native-safe-area-context: 4.8.2 react-native-screens: ~3.29.0 react-native-smooth-slider: ^1.3.6 @@ -20661,6 +20782,13 @@ __metadata: languageName: node linkType: hard +"ts-toolbelt@npm:^6.15.1": + version: 6.15.5 + resolution: "ts-toolbelt@npm:6.15.5" + checksum: 24ad00cfd9ce735c76c873a9b1347eac475b94e39ebbdf100c9019dce88dd5f4babed52884cf82bb456a38c28edd0099ab6f704b84b2e5e034852b618472c1f3 + languageName: node + linkType: hard + "ts-unused-exports@npm:^10.0.1": version: 10.0.1 resolution: "ts-unused-exports@npm:10.0.1" @@ -20937,13 +21065,6 @@ __metadata: languageName: node linkType: hard -"uc.micro@npm:^1.0.1, uc.micro@npm:^1.0.5": - version: 1.0.6 - resolution: "uc.micro@npm:1.0.6" - checksum: 6898bb556319a38e9cf175e3628689347bd26fec15fc6b29fa38e0045af63075ff3fea4cf1fdba9db46c9f0cbf07f2348cd8844889dd31ebd288c29fe0d27e7a - languageName: node - linkType: hard - "uc.micro@npm:^2.0.0, uc.micro@npm:^2.1.0": version: 2.1.0 resolution: "uc.micro@npm:2.1.0" @@ -21206,6 +21327,13 @@ __metadata: languageName: node linkType: hard +"urijs@npm:^1.19.6": + version: 1.19.11 + resolution: "urijs@npm:1.19.11" + checksum: f9b95004560754d30fd7dbee44b47414d662dc9863f1cf5632a7c7983648df11d23c0be73b9b4f9554463b61d5b0a520b70df9e1ee963ebb4af02e6da2cc80f3 + languageName: node + linkType: hard + "url-join@npm:4.0.0": version: 4.0.0 resolution: "url-join@npm:4.0.0" @@ -22332,7 +22460,7 @@ __metadata: languageName: node linkType: hard -"xtend@npm:~4.0.1": +"xtend@npm:^4.0.0, xtend@npm:~4.0.1": version: 4.0.2 resolution: "xtend@npm:4.0.2" checksum: ac5dfa738b21f6e7f0dd6e65e1b3155036d68104e67e5d5d1bde74892e327d7e5636a076f625599dc394330a731861e87343ff184b0047fef1360a7ec0a5a36a From 3ea76d0669672deac35697cc80a58a59aa13d6c2 Mon Sep 17 00:00:00 2001 From: WaDadidou Date: Wed, 11 Dec 2024 16:19:21 -0500 Subject: [PATCH 05/22] wip(article): Fix styles, looks good, remove unused libs, remove Article tools --- package.json | 4 +- .../navigation/getNormalModeScreens.tsx | 6 - .../socialFeed/NewsFeed/NewsFeedInput.tsx | 71 +-- .../FeedNewArticleMarkdownScreen.tsx | 464 ------------------ .../FeedNewArticle/FeedNewArticleScreen.tsx | 360 +++++++------- .../ArticleContentEditor.tsx | 193 +++++--- .../ArticleContentEditor/Toolbar/Toolbar.tsx | 152 ++---- packages/utils/navigation.ts | 7 - yarn.lock | 84 +++- 9 files changed, 468 insertions(+), 873 deletions(-) delete mode 100644 packages/screens/FeedNewArticle/FeedNewArticleMarkdownScreen.tsx diff --git a/package.json b/package.json index 288db071fa..a6d2c94066 100644 --- a/package.json +++ b/package.json @@ -76,6 +76,7 @@ "@types/crypto-js": "^4.2.2", "@types/leaflet": "^1.9.12", "@types/leaflet.markercluster": "^1.5.4", + "@types/markdown-it-emoji": "^3.0.1", "@types/papaparse": "^5.3.14", "@types/pluralize": "^0.0.33", "assert": "^2.1.0", @@ -121,6 +122,7 @@ "long": "^5.2.1", "lottie-react-native": "6.5.1", "markdown-it": "^14.1.0", + "markdown-it-emoji": "^3.0.0", "merkletreejs": "^0.4.0", "metamask-react": "^2.4.1", "moment": "^2.29.4", @@ -129,7 +131,6 @@ "papaparse": "^5.4.1", "plausible-tracker": "^0.3.8", "pluralize": "^8.0.0", - "prop-types": "^15.8.1", "protobufjs": "^7.2.5", "react": "18.2.0", "react-dom": "18.2.0", @@ -142,7 +143,6 @@ "react-native-confetti-cannon": "^1.5.2", "react-native-countdown-component": "^2.7.1", "react-native-drax": "^0.10.2", - "react-native-fit-image": "^1.5.5", "react-native-fs": "^2.20.0", "react-native-gesture-handler": "~2.14.0", "react-native-get-random-values": "~1.8.0", diff --git a/packages/components/navigation/getNormalModeScreens.tsx b/packages/components/navigation/getNormalModeScreens.tsx index 435b72388b..d19e323427 100644 --- a/packages/components/navigation/getNormalModeScreens.tsx +++ b/packages/components/navigation/getNormalModeScreens.tsx @@ -9,7 +9,6 @@ import { CoreDAOScreen } from "@/screens/CoreDAO/CoreDAOScreen"; import { DAppStoreScreen } from "@/screens/DAppStore/DAppStoreScreen"; import { ToriPunks } from "@/screens/DAppStore/apps/toripunks/HomeScreen"; import { FeedScreen } from "@/screens/Feed/FeedScreen"; -import { FeedNewArticleMarkdownScreen } from "@/screens/FeedNewArticle/FeedNewArticleMarkdownScreen.tsx"; import { FeedNewArticleScreen } from "@/screens/FeedNewArticle/FeedNewArticleScreen.tsx"; import { FeedPostViewScreen } from "@/screens/FeedPostView/FeedPostViewScreen"; import { GovernanceProposalScreen } from "@/screens/Governance/GovernanceProposal/GovernanceProposalScreen"; @@ -454,11 +453,6 @@ export const getNormalModeScreens = ({ component={FeedNewArticleScreen} options={{ header: () => null, title: screenTitle("New Article") }} /> - null, title: screenTitle("New Article") }} - /> {type === "post" && ( - <> - - - SOCIAL_FEED_ARTICLE_MIN_CHARS_LIMIT - ? primaryTextColor - : primaryColor - } - borderColor={primaryColor} - touchableStyle={{ - marginRight: layout.spacing_x2, - }} - backgroundColor={ - formValues?.message.length > - SOCIAL_FEED_ARTICLE_MIN_CHARS_LIMIT - ? primaryColor - : neutral17 - } - text="Create an Article" - squaresBackgroundColor={neutral17} - /> - - - - SOCIAL_FEED_ARTICLE_MIN_CHARS_LIMIT - ? primaryTextColor - : primaryColor - } - borderColor={primaryColor} - touchableStyle={{ - marginRight: layout.spacing_x2, - }} - backgroundColor={ - formValues?.message.length > - SOCIAL_FEED_ARTICLE_MIN_CHARS_LIMIT - ? primaryColor - : neutral17 - } - text="MD" - squaresBackgroundColor={neutral17} - /> - - + + + SOCIAL_FEED_ARTICLE_MIN_CHARS_LIMIT + ? primaryTextColor + : primaryColor + } + borderColor={primaryColor} + touchableStyle={{ + marginRight: layout.spacing_x2, + }} + backgroundColor={ + formValues?.message.length > + SOCIAL_FEED_ARTICLE_MIN_CHARS_LIMIT + ? primaryColor + : neutral17 + } + text="Create an Article" + squaresBackgroundColor={neutral17} + /> + )} = () => { - const { width } = useMaxResolution(); - const { width: windowWidth } = useWindowDimensions(); - const isSmallScreen = windowWidth < RESPONSIVE_BREAKPOINT_S; - const isMobile = useIsMobile(); - const wallet = useSelectedWallet(); - const selectedNetworkId = useSelectedNetworkId(); - const userId = wallet?.userId; - const userIPFSKey = useSelector(selectNFTStorageAPI); - const { uploadFilesToPinata, ipfsUploadProgress } = useIpfs(); - const [isUploadLoading, setIsUploadLoading] = useState(false); - const [isProgressBarShown, setIsProgressBarShown] = useState(false); - const postCategory = PostCategory.Article; - const { - makePost, - isProcessing, - canPayForPost, - freePostCount, - prettyPublishingFee, - publishingFee, - step, - setStep, - } = useFeedPosting(selectedNetworkId, userId, postCategory, () => { - // Timeout here to let a few time to see the progress bar "100% Done" - setTimeout(() => { - setIsUploadLoading(false); - setIsProgressBarShown(false); - setToast({ - mode: "normal", - type: "success", - title: "Post submitted successfully.", - message: "", - }); - navigateBack(); - newArticleForm.reset(); - }, 1000); - }); - const forceNetworkFeature = NetworkFeature.SocialFeed; - const { showNotEnoughFundsModal, showConnectWalletModal } = - useWalletControl(); - const isLoading = isUploadLoading || isProcessing; - const { setToast } = useFeedbacks(); - const navigation = useAppNavigation(); - const scrollViewRef = useRef(null); - const [isThumbnailButtonHovered, setThumbnailButtonHovered] = useState(false); - const [location, setLocation] = useState(); - const [isMapShown, setIsMapShown] = useState(false); - const cardStyle = isSmallScreen && { - borderRadius: 0, - borderLeftWidth: 0, - borderRightWidth: 0, - }; - const newArticleForm = useForm({ - defaultValues: { - title: "", - message: "", - files: [], - gifs: [], - hashtags: [], - mentions: [], - thumbnailImage: undefined, - shortDescription: "", - }, - mode: "onBlur", - }); - const formValues = newArticleForm.watch(); - const previewMetadata: SocialFeedArticleMetadata = { - title: formValues.title, - shortDescription: formValues.shortDescription || "", - thumbnailImage: formValues.thumbnailImage, - message: "", - hashtags: [], - mentions: [], - }; - - const onPublish = async (values: PublishValues) => { - const action = "Publish an Article"; - if (!wallet?.address || !wallet.connected) { - showConnectWalletModal({ - forceNetworkFeature, - action, - }); - return; - } - if (!canPayForPost) { - showNotEnoughFundsModal({ - action, - cost: { - amount: publishingFee.amount.toString(), - denom: publishingFee.denom || "", - }, - }); - return; - } - setIsUploadLoading(true); - setIsProgressBarShown(true); - try { - const localFiles = [ - ...(formValues.files || []), - ...values.images, - ...values.audios, - ...values.videos, - ]; - if (formValues.thumbnailImage) localFiles.push(formValues.thumbnailImage); - if (formValues.coverImage) localFiles.push(formValues.coverImage); - - let pinataJWTKey = undefined; - if (localFiles?.length) { - setStep(feedPostingStep(FeedPostingStepId.GENERATING_KEY)); - - pinataJWTKey = - userIPFSKey || (await generateIpfsKey(selectedNetworkId, userId)); - } - - // Upload files to IPFS - let remoteFiles: RemoteFileData[] = []; - if (pinataJWTKey) { - setStep(feedPostingStep(FeedPostingStepId.UPLOADING_FILES)); - - remoteFiles = await uploadFilesToPinata({ - files: localFiles, - pinataJWTKey, - }); - } - - // If the user uploaded files, but they are not pinned to IPFS, it returns files with empty url, so this is an error. - if (formValues.files?.length && !remoteFiles.find((file) => file.url)) { - console.error("upload file err : Fail to pin to IPFS"); - setToast({ - mode: "normal", - type: "error", - title: "File upload failed", - message: "Fail to pin to IPFS, please try to Publish again", - }); - setIsUploadLoading(false); - return; - } - - let message = values.html; - if (remoteFiles.length) { - localFiles?.map((file, index) => { - // Audio are not in the HTML for now - if (remoteFiles[index]?.fileType !== "audio") { - message = message.replace(file.url, remoteFiles[index].url); - } - }); - } - - const metadata = generateArticleMetadata({ - ...formValues, - thumbnailImage: remoteFiles.find( - (remoteFile) => remoteFile.isThumbnailImage, - ), - coverImage: remoteFiles.find((remoteFile) => remoteFile.isCoverImage), - gifs: values.gifs, - files: remoteFiles, - mentions: values.mentions, - hashtags: values.hashtags, - message, - location, - }); - - await makePost(JSON.stringify(metadata)); - } catch (err) { - console.error("post submit error", err); - setIsUploadLoading(false); - setIsProgressBarShown(false); - setToast({ - mode: "normal", - type: "error", - title: "Something went wrong.", - message: err instanceof Error ? err.message : `${err}`, - }); - } - }; - - //TODO: Keep short post formValues when returning to short post - const navigateBack = () => navigation.navigate("Feed"); - - //TODO: Not handled for now - // const { mutate: openGraphMutate, data: openGraphData } = useOpenGraph(); - - // // OpenGraph URL preview - // useEffect(() => { - // addedUrls.forEach(url => { - // openGraphMutate({ - // url, - // }); - // - // }) - // }, [addedUrls]) - - // Scroll to bottom when the loading bar appears - useEffect(() => { - if (step.id !== "UNDEFINED" && isLoading) - scrollViewRef.current?.scrollToEnd(); - }, [step, isLoading]); - - return ( - New Article} - onBackPress={navigateBack} - footerChildren - noMargin - noScroll - > - - - - - - - - - {freePostCount - ? `You have ${freePostCount} free ${pluralize( - "Article", - freePostCount, - )} left` - : `The cost for this Article is ${prettyPublishingFee}`} - - - - - noBrokenCorners - rules={{ required: true }} - height={48} - label="Preview title" - placeHolder="Type title here" - name="title" - control={newArticleForm.control} - variant="labelOutside" - containerStyle={{ marginVertical: layout.spacing_x3 }} - boxMainContainerStyle={{ - backgroundColor: neutral00, - borderRadius: 12, - }} - /> - - - noBrokenCorners - rules={{ required: true }} - multiline - label="Preview subtitle" - placeHolder="Type short description here" - name="shortDescription" - control={newArticleForm.control} - variant="labelOutside" - containerStyle={{ marginBottom: layout.spacing_x3 }} - boxMainContainerStyle={{ - backgroundColor: neutral00, - borderRadius: 12, - }} - /> - - - - - - - - - newArticleForm.setValue("thumbnailImage", { - isThumbnailImage: true, - ...files[0], - }) - } - mimeTypes={IMAGE_MIME_TYPES} - > - {({ onPress }) => ( - setThumbnailButtonHovered(true)} - onHoverOut={() => setThumbnailButtonHovered(false)} - onPress={onPress} - style={{ - position: "absolute", - right: 8, - top: 8, - zIndex: 1, - backgroundColor: neutral00, - borderColor: isThumbnailButtonHovered - ? neutralFF - : neutral33, - borderWidth: 1, - borderRadius: 999, - height: 36, - width: 36, - justifyContent: "center", - alignItems: "center", - }} - > - - - )} - - - - - - - - - - - - {step.id !== "UNDEFINED" && isProgressBarShown && ( - <> - - - - )} - - - {isMapShown && ( - setIsMapShown(false)} - setLocation={setLocation} - location={location} - postCategory={postCategory} - /> - )} - - ); -}; diff --git a/packages/screens/FeedNewArticle/FeedNewArticleScreen.tsx b/packages/screens/FeedNewArticle/FeedNewArticleScreen.tsx index 61eaec0dfc..dbea189eb2 100644 --- a/packages/screens/FeedNewArticle/FeedNewArticleScreen.tsx +++ b/packages/screens/FeedNewArticle/FeedNewArticleScreen.tsx @@ -1,23 +1,24 @@ import pluralize from "pluralize"; -import React, { useEffect, useRef, useState } from "react"; -import { Controller, useForm } from "react-hook-form"; -import { ScrollView, View } from "react-native"; +import { useEffect, useRef, useState } from "react"; +import { FormProvider, useForm } from "react-hook-form"; +import { ScrollView, useWindowDimensions, View } from "react-native"; import { useSelector } from "react-redux"; -import priceSVG from "../../../assets/icons/price.svg"; import useSelectedWallet from "../../hooks/useSelectedWallet"; +import penSVG from "@/assets/icons/pen.svg"; +import priceSVG from "@/assets/icons/price.svg"; import { BrandText } from "@/components/BrandText"; import { SVG } from "@/components/SVG"; import { ScreenContainer } from "@/components/ScreenContainer"; -import { ScreenTitle } from "@/components/ScreenContainer/ScreenTitle"; import { WalletStatusBox } from "@/components/WalletStatusBox"; import { TertiaryBox } from "@/components/boxes/TertiaryBox"; +import { CustomPressable } from "@/components/buttons/CustomPressable"; import { Label, TextInputCustom } from "@/components/inputs/TextInputCustom"; import { FileUploader } from "@/components/inputs/fileUploader"; import { FeedPostingProgressBar } from "@/components/loaders/FeedPostingProgressBar"; -import { RichText } from "@/components/socialFeed/RichText"; import { PublishValues } from "@/components/socialFeed/RichText/RichText.type"; +import { SocialArticleCard } from "@/components/socialFeed/SocialCard/cards/SocialArticleCard"; import { MapModal } from "@/components/socialFeed/modals/MapModal/MapModal"; import { SpacerColumn } from "@/components/spacer"; import { useFeedbacks } from "@/context/FeedbacksProvider"; @@ -25,38 +26,44 @@ import { useWalletControl } from "@/context/WalletControlProvider"; import { useFeedPosting } from "@/hooks/feed/useFeedPosting"; import { useIpfs } from "@/hooks/useIpfs"; import { useIsMobile } from "@/hooks/useIsMobile"; +import { useMaxResolution } from "@/hooks/useMaxResolution"; import { useSelectedNetworkId } from "@/hooks/useSelectedNetwork"; import { NetworkFeature } from "@/networks"; +import { ArticleContentEditor } from "@/screens/FeedNewArticle/components/ArticleContentEditor/ArticleContentEditor"; import { selectNFTStorageAPI } from "@/store/slices/settings"; import { feedPostingStep, FeedPostingStepId } from "@/utils/feed/posting"; import { generateArticleMetadata } from "@/utils/feed/queries"; import { generateIpfsKey } from "@/utils/ipfs"; import { IMAGE_MIME_TYPES } from "@/utils/mime"; import { ScreenFC, useAppNavigation } from "@/utils/navigation"; -import { - ARTICLE_COVER_IMAGE_MAX_HEIGHT, - ARTICLE_COVER_IMAGE_RATIO, - ARTICLE_THUMBNAIL_IMAGE_MAX_HEIGHT, - ARTICLE_THUMBNAIL_IMAGE_MAX_WIDTH, -} from "@/utils/social-feed"; import { neutral00, neutral11, + neutral33, neutral77, + neutralFF, secondaryColor, } from "@/utils/style/colors"; -import { fontSemibold13 } from "@/utils/style/fonts"; -import { layout, screenContentMaxWidth } from "@/utils/style/layout"; +import { fontSemibold13, fontSemibold20 } from "@/utils/style/fonts"; +import { + layout, + RESPONSIVE_BREAKPOINT_S, + screenContentMaxWidth, +} from "@/utils/style/layout"; import { CustomLatLngExpression, NewArticleFormValues, PostCategory, + SocialFeedArticleMetadata, } from "@/utils/types/feed"; import { RemoteFileData } from "@/utils/types/files"; //TODO: In mobile : Make ActionsContainer accessible (floating button ?) export const FeedNewArticleScreen: ScreenFC<"FeedNewArticle"> = () => { + const { width } = useMaxResolution(); + const { width: windowWidth } = useWindowDimensions(); + const isSmallScreen = windowWidth < RESPONSIVE_BREAKPOINT_S; const isMobile = useIsMobile(); const wallet = useSelectedWallet(); const selectedNetworkId = useSelectedNetworkId(); @@ -87,7 +94,7 @@ export const FeedNewArticleScreen: ScreenFC<"FeedNewArticle"> = () => { message: "", }); navigateBack(); - reset(); + newArticleForm.reset(); }, 1000); }); const forceNetworkFeature = NetworkFeature.SocialFeed; @@ -97,15 +104,15 @@ export const FeedNewArticleScreen: ScreenFC<"FeedNewArticle"> = () => { const { setToast } = useFeedbacks(); const navigation = useAppNavigation(); const scrollViewRef = useRef(null); + const [isThumbnailButtonHovered, setThumbnailButtonHovered] = useState(false); const [location, setLocation] = useState(); const [isMapShown, setIsMapShown] = useState(false); - const { - control, - setValue, - reset, - watch, - formState: { errors }, - } = useForm({ + const cardStyle = isSmallScreen && { + borderRadius: 0, + borderLeftWidth: 0, + borderRightWidth: 0, + }; + const newArticleForm = useForm({ defaultValues: { title: "", message: "", @@ -118,13 +125,15 @@ export const FeedNewArticleScreen: ScreenFC<"FeedNewArticle"> = () => { }, mode: "onBlur", }); - //TODO: Not handled for now - // const { mutate: openGraphMutate, data: openGraphData } = useOpenGraph(); - - const formValues = watch(); - - //TODO: Keep short post formValues when returning to short post - const navigateBack = () => navigation.navigate("Feed"); + const formValues = newArticleForm.watch(); + const previewMetadata: SocialFeedArticleMetadata = { + title: formValues.title, + shortDescription: formValues.shortDescription || "", + thumbnailImage: formValues.thumbnailImage, + message: "", + hashtags: [], + mentions: [], + }; const onPublish = async (values: PublishValues) => { const action = "Publish an Article"; @@ -227,11 +236,11 @@ export const FeedNewArticleScreen: ScreenFC<"FeedNewArticle"> = () => { } }; - // Scroll to bottom when the loading bar appears - useEffect(() => { - if (step.id !== "UNDEFINED" && isLoading) - scrollViewRef.current?.scrollToEnd(); - }, [step, isLoading]); + //TODO: Keep short post formValues when returning to short post + const navigateBack = () => navigation.navigate("Feed"); + + //TODO: Not handled for now + // const { mutate: openGraphMutate, data: openGraphData } = useOpenGraph(); // // OpenGraph URL preview // useEffect(() => { @@ -243,21 +252,29 @@ export const FeedNewArticleScreen: ScreenFC<"FeedNewArticle"> = () => { // }) // }, [addedUrls]) + // Scroll to bottom when the loading bar appears + useEffect(() => { + if (step.id !== "UNDEFINED" && isLoading) + scrollViewRef.current?.scrollToEnd(); + }, [step, isLoading]); + return ( New Article} + headerChildren={New Article} onBackPress={navigateBack} footerChildren + noMargin noScroll > = () => { alignSelf: "center", }} > - - - - - - + + + - {freePostCount - ? `You have ${freePostCount} free ${pluralize( - "Article", - freePostCount, - )} left` - : `The cost for this Article is ${prettyPublishingFee}`} - - + + + {freePostCount + ? `You have ${freePostCount} free ${pluralize( + "Article", + freePostCount, + )} left` + : `The cost for this Article is ${prettyPublishingFee}`} + + - - setValue("thumbnailImage", { - isThumbnailImage: true, - ...files[0], - }) - } - mimeTypes={IMAGE_MIME_TYPES} - /> + + noBrokenCorners + rules={{ required: true }} + height={48} + label="Preview title" + placeHolder="Type title here" + name="title" + control={newArticleForm.control} + variant="labelOutside" + containerStyle={{ marginVertical: layout.spacing_x3 }} + boxMainContainerStyle={{ + backgroundColor: neutral00, + borderRadius: 12, + }} + /> - - setValue("coverImage", { - isCoverImage: true, - ...files[0], - }) - } - mimeTypes={IMAGE_MIME_TYPES} - /> + + noBrokenCorners + rules={{ required: true }} + multiline + label="Preview subtitle" + placeHolder="Type short description here" + name="shortDescription" + control={newArticleForm.control} + variant="labelOutside" + containerStyle={{ marginBottom: layout.spacing_x3 }} + boxMainContainerStyle={{ + backgroundColor: neutral00, + borderRadius: 12, + }} + /> - - noBrokenCorners - rules={{ required: true }} - height={48} - label="Title" - placeHolder="Type title here" - name="title" - control={control} - variant="labelOutside" - containerStyle={{ marginVertical: layout.spacing_x3 }} - boxMainContainerStyle={{ - backgroundColor: neutral00, - borderRadius: 12, - }} - /> + + + - - noBrokenCorners - rules={{ required: true }} - multiline - label="Short description" - placeHolder="Type short description here" - name="shortDescription" - control={control} - variant="labelOutside" - containerStyle={{ marginBottom: layout.spacing_x3 }} - boxMainContainerStyle={{ - backgroundColor: neutral00, - borderRadius: 12, + + > + + + newArticleForm.setValue("thumbnailImage", { + isThumbnailImage: true, + ...files[0], + }) + } + mimeTypes={IMAGE_MIME_TYPES} + > + {({ onPress }) => ( + setThumbnailButtonHovered(true)} + onHoverOut={() => setThumbnailButtonHovered(false)} + onPress={onPress} + style={{ + position: "absolute", + right: 8, + top: 8, + zIndex: 1, + backgroundColor: neutral00, + borderColor: isThumbnailButtonHovered + ? neutralFF + : neutral33, + borderWidth: 1, + borderRadius: 999, + height: 36, + width: 36, + justifyContent: "center", + alignItems: "center", + }} + > + + + )} + - - - - ( - - )} - /> + + + + + + + {step.id !== "UNDEFINED" && isProgressBarShown && ( <> diff --git a/packages/screens/FeedNewArticle/components/ArticleContentEditor/ArticleContentEditor.tsx b/packages/screens/FeedNewArticle/components/ArticleContentEditor/ArticleContentEditor.tsx index 56760cb5fd..0e9155f7f6 100644 --- a/packages/screens/FeedNewArticle/components/ArticleContentEditor/ArticleContentEditor.tsx +++ b/packages/screens/FeedNewArticle/components/ArticleContentEditor/ArticleContentEditor.tsx @@ -1,5 +1,6 @@ import type { MixedStyleRecord } from "@native-html/transient-render-engine"; import markdownit from "markdown-it"; +import { full as emoji } from "markdown-it-emoji"; import { FC, useRef, useState } from "react"; import { Controller, useFormContext } from "react-hook-form"; import { @@ -18,10 +19,15 @@ import { useMaxResolution } from "@/hooks/useMaxResolution"; import { Toolbar } from "@/screens/FeedNewArticle/components/ArticleContentEditor/Toolbar/Toolbar"; import { ContentMode } from "@/screens/FeedNewArticle/components/ArticleContentEditor/utils"; import { ARTICLE_MAX_WIDTH } from "@/utils/social-feed"; -import { neutral00, neutralA3, neutralFF } from "@/utils/style/colors"; +import { + neutral00, + neutral17, + neutralA3, + neutralFF, + primaryColor, +} from "@/utils/style/colors"; import { layout, RESPONSIVE_BREAKPOINT_S } from "@/utils/style/layout"; import { NewArticleFormValues } from "@/utils/types/feed"; -import { LocalFileData } from "@/utils/types/files"; interface Props { width: number; @@ -34,12 +40,9 @@ export const ArticleContentEditor: FC = ({ width }) => { const textInputRef = useRef(null); const [isTextInputHovered, setTextInputHovered] = useState(false); const borderWidth = 1; - const textInputMinHeight = height - 140; + const textInputMinHeight = height - 142; + const editionAndPreviewHeight = height - 80; const [textInputHeight, setTextInputHeight] = useState(textInputMinHeight); - const [textInputSelection, setTextInputSelection] = useState({ - start: 0, - end: 0, - }); // Used to get the cursor position const [mode, setMode] = useState("BOTH"); const [renderWidth, setRenderWidth] = useState(0); const textInputContainerPadding = @@ -47,47 +50,115 @@ export const ArticleContentEditor: FC = ({ width }) => { ? 0 : layout.spacing_x2 - borderWidth * 2; - // TODO: Find type: evt.nativeEvent.target was not recognised by LayoutChangeEvent, but the object target exist, so i don't understand (Same as NewsFeedInput) - // https://github.com/necolas/react-native-web/issues/795#issuecomment-1297511068, fix that i found for shrink lines when we deleting lines in the input - const adjustTextInputSize = (evt: any) => { - const el = evt?.nativeEvent?.target; - if (el) { - el.style.height = 0; - const newHeight = el.offsetHeight - el.clientHeight + el.scrollHeight; - el.style.height = `${newHeight < textInputMinHeight ? textInputMinHeight : newHeight}px`; - } - }; - // ========== Form - const { watch, control, setValue } = useFormContext(); + const { watch, control } = useFormContext(); const message = watch("message"); - const addImage = (file: LocalFileData) => { - if (!file?.url) { - console.error("Invalid file: Missing URL or MIME type"); - return; - } - const textBefore = message.substring(0, textInputSelection.start); - const textAfter = message.substring( - textInputSelection.start, - message.length - 1, - ); - const result = `${textBefore}![image](${file.url})${textAfter}`; - setValue("message", result); - }; - // ========== Markdown - const md = markdownit(); + const md = markdownit({ + linkify: true, + breaks: true, + }).use(emoji); const html = md.render(message); - // TODO: Style this const markdownTagStyles: MixedStyleRecord = { - body: { fontSize: 14, color: neutralA3 }, - h1: { fontSize: 24, fontWeight: "bold" }, - p: { margin: 0 }, - strong: { fontWeight: "bold", color: "yellow" }, - a: { color: "blue", textDecorationLine: "underline" }, - // img: {resizeMode: "contain"} + body: { + color: neutralA3, + fontSize: 14, + letterSpacing: -(14 * 0.04), + lineHeight: 22, + fontFamily: "Exo_500Medium", + fontWeight: "500", + }, + p: { + marginVertical: 4, + color: neutralA3, + fontSize: 14, + letterSpacing: -(14 * 0.04), + lineHeight: 22, + fontFamily: "Exo_500Medium", + fontWeight: "500", + }, + code: { + color: neutralA3, + fontSize: 13, + letterSpacing: -(13 * 0.04), + lineHeight: 22, + fontFamily: "Exo_500Medium", + fontWeight: "500", + + backgroundColor: neutral17, + marginVertical: 4, + paddingHorizontal: 4, + paddingVertical: 2, + borderRadius: 4, + alignSelf: "flex-start", + }, + pre: { + fontSize: 13, + letterSpacing: -(13 * 0.04), + fontFamily: "Exo_500Medium", + fontWeight: "500", + + backgroundColor: neutral17, + paddingHorizontal: 8, + paddingVertical: 8, + borderRadius: 4, + }, + strong: { fontWeight: "700" }, + a: { + color: primaryColor, + textDecorationLine: "none", + }, + hr: { backgroundColor: neutralA3 }, + h1: { + color: neutralFF, + fontSize: 28, + letterSpacing: -(28 * 0.02), + lineHeight: 37, + fontFamily: "Exo_500Medium", + fontWeight: "500", + }, + h2: { + color: neutralFF, + fontSize: 21, + letterSpacing: -(21 * 0.02), + lineHeight: 28, + fontFamily: "Exo_500Medium", + fontWeight: "500", + }, + h3: { + color: neutralFF, + fontSize: 16, + letterSpacing: -(16 * 0.02), + lineHeight: 23, + fontFamily: "Exo_500Medium", + fontWeight: "500", + }, + h4: { + color: neutralFF, + fontSize: 14, + letterSpacing: -(14 * 0.04), + lineHeight: 20, + fontFamily: "Exo_500Medium", + fontWeight: "500", + }, + h5: { + color: neutralA3, + fontSize: 14, + letterSpacing: -(14 * 0.04), + lineHeight: 20, + fontFamily: "Exo_500Medium", + fontWeight: "500", + }, + h6: { + color: neutralA3, + fontSize: 12, + letterSpacing: -(12 * 0.04), + lineHeight: 16, + fontFamily: "Exo_500Medium", + fontWeight: "500", + }, }; // ========== JSX @@ -99,22 +170,7 @@ export const ArticleContentEditor: FC = ({ width }) => { }} > {/* ==== Toolbar */} - { - /*TODO*/ - }} - addGIF={() => { - /*TODO*/ - }} - addAudio={() => { - /*TODO*/ - }} - addVideo={() => { - /*TODO*/ - }} - addImage={addImage} - setMode={setMode} - /> + {/* ==== Edition and preview */} @@ -122,8 +178,8 @@ export const ArticleContentEditor: FC = ({ width }) => { style={{ flexDirection: "row", width: windowWidth < RESPONSIVE_BREAKPOINT_S ? "100%" : width, - maxWidth: ARTICLE_MAX_WIDTH + layout.spacing_x2 * 2, - // height + maxWidth: ARTICLE_MAX_WIDTH + 16 * 2, + height: editionAndPreviewHeight, }} > {/* ==== Edition */} @@ -153,9 +209,11 @@ export const ArticleContentEditor: FC = ({ width }) => { onChange: (value: string) => void; }; return ( - = ({ width }) => { ]} > = ({ width }) => { outlineStyle: "none", color: neutralA3, border: "none", + textAlignVertical: "top", } as TextStyle, ]} - onChange={adjustTextInputSize} - onLayout={adjustTextInputSize} onChangeText={onChange} - onSelectionChange={(e) => - setTextInputSelection(e.nativeEvent.selection) - } onContentSizeChange={(e) => { // The input grows depending on the content height setTextInputHeight(e.nativeEvent.contentSize.height); }} ref={textInputRef} /> - + ); }} /> @@ -215,12 +270,6 @@ export const ArticleContentEditor: FC = ({ width }) => { paddingHorizontal: textInputContainerPadding, marginTop: textInputContainerPadding, }} - contentContainerStyle={[ - mode !== "PREVIEW" && { - height: textInputHeight, - }, - { minHeight: textInputMinHeight }, - ]} onLayout={(e) => setRenderWidth(e.nativeEvent.layout.width)} > void; - addGIF: (url: string) => void; - addAudio: (file: LocalFileData) => void; - addVideo: (file: LocalFileData) => void; - addImage: (file: LocalFileData) => void; setMode: Dispatch>; } -export const Toolbar: FC = ({ - addEmoji, - addGIF, - addAudio, - addVideo, - addImage, - setMode, -}) => { +export const Toolbar: FC = ({ setMode }) => { return ( - - setMode("EDITION")}> - [] - - - - - setMode("BOTH")}> - []() - - - - - setMode("PREVIEW")}> - () - - - - - addEmoji(emoji)} - buttonStyle={toolbarCustomButtonCStyle} - iconStyle={toolbarCustomButtonIconCStyle} - /> - - (url ? addGIF(url) : undefined)} - buttonStyle={toolbarCustomButtonCStyle} - iconStyle={toolbarCustomButtonIconCStyle} - // disabled={isGIFSelectorDisabled} - /> - - addAudio(files?.[0])} - mimeTypes={AUDIO_MIME_TYPES} - > - {({ onPress }) => ( - - )} - - - addVideo(files?.[0])} - mimeTypes={VIDEO_MIME_TYPES} - > - {({ onPress }) => ( - - )} - - - addImage(files?.[0])} - mimeTypes={IMAGE_MIME_TYPES} - > - {({ onPress }) => ( - - )} - + + + setMode("EDITION")} + style={{ + backgroundColor: neutral1A, + padding: layout.spacing_x1, + borderRadius: 8, + }} + > + Edition + + + setMode("BOTH")} + style={{ + backgroundColor: neutral1A, + padding: layout.spacing_x1, + borderRadius: 8, + }} + > + Edition | Preview + + + setMode("PREVIEW")} + style={{ + backgroundColor: neutral1A, + padding: layout.spacing_x1, + borderRadius: 8, + }} + > + Preview + + ); }; - -const toolbarCustomButtonIconCStyle: ViewStyle = { - borderRadius: 4, - height: 30, - width: 30, -}; -const toolbarCustomButtonCStyle: ViewStyle = { - margin: layout.spacing_x0_5, -}; diff --git a/packages/utils/navigation.ts b/packages/utils/navigation.ts index 16248b7d63..b13e114a2d 100644 --- a/packages/utils/navigation.ts +++ b/packages/utils/navigation.ts @@ -61,12 +61,6 @@ export type RootStackParamList = { additionalHashtag?: string; }) | undefined; - FeedNewArticleMarkdown: - | (NewPostFormValues & { - additionalMention?: string; - additionalHashtag?: string; - }) - | undefined; FeedPostView: { id: string }; HashtagFeed: { hashtag: string }; @@ -245,7 +239,6 @@ const getNavConfig: (homeScreen: keyof RootStackParamList) => NavConfig = ( NFTDetail: "nft/:id", Feed: "feed/:tab?", FeedNewArticle: "feed/new", - FeedNewArticleMarkdown: "feed/new-md", FeedPostView: "feed/post/:id", HashtagFeed: "feed/tag/:hashtag", diff --git a/yarn.lock b/yarn.lock index a7606b85b4..d60b34c331 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6856,7 +6856,16 @@ __metadata: languageName: node linkType: hard -"@types/markdown-it@npm:^14.1.2": +"@types/markdown-it-emoji@npm:^3.0.1": + version: 3.0.1 + resolution: "@types/markdown-it-emoji@npm:3.0.1" + dependencies: + "@types/markdown-it": ^14 + checksum: cf11b177dca826d7617bc89b8d1ee2a5203bd1a370a62a699e3c6eb0299e7c10c71694d796dedfc05f888834c00e662274c0b2b71c4e73927ac57d189fc6f99c + languageName: node + linkType: hard + +"@types/markdown-it@npm:^14, @types/markdown-it@npm:^14.1.2": version: 14.1.2 resolution: "@types/markdown-it@npm:14.1.2" dependencies: @@ -9784,7 +9793,7 @@ __metadata: languageName: node linkType: hard -"css-to-react-native@npm:^3.0.0": +"css-to-react-native@npm:^3.0.0, css-to-react-native@npm:^3.2.0": version: 3.2.0 resolution: "css-to-react-native@npm:3.2.0" dependencies: @@ -10768,6 +10777,13 @@ __metadata: languageName: node linkType: hard +"entities@npm:~2.0.0": + version: 2.0.3 + resolution: "entities@npm:2.0.3" + checksum: 5a7899fcc622e0d76afdeafe4c58a6b40ae3a8ee4772e5825a648c11a2ca324a9a02515386f512e466baac4aeb551f3d3b79eaece5cd98369b9f8601be336b1a + languageName: node + linkType: hard + "env-editor@npm:^0.4.1": version: 0.4.2 resolution: "env-editor@npm:0.4.2" @@ -14982,6 +14998,15 @@ __metadata: languageName: node linkType: hard +"linkify-it@npm:^2.0.0": + version: 2.2.0 + resolution: "linkify-it@npm:2.2.0" + dependencies: + uc.micro: ^1.0.1 + checksum: d198871d0b3f3cfdb745dae564bfd6743474f20cd0ef1057e6ca29451834749e7f3da52b59b4de44e98f31a1e5c71bdad160490d4ae54de251cbcde57e4d7837 + languageName: node + linkType: hard + "linkify-it@npm:^5.0.0": version: 5.0.0 resolution: "linkify-it@npm:5.0.0" @@ -15338,6 +15363,28 @@ __metadata: languageName: node linkType: hard +"markdown-it-emoji@npm:^3.0.0": + version: 3.0.0 + resolution: "markdown-it-emoji@npm:3.0.0" + checksum: 421290e310285b9ef979e409ea056623489541013ee7307956a3450a06e0de034c585e217ed43a7bf9a6f16102542cb75799b975a861ba01a2db2b7105e16871 + languageName: node + linkType: hard + +"markdown-it@npm:^10.0.0": + version: 10.0.0 + resolution: "markdown-it@npm:10.0.0" + dependencies: + argparse: ^1.0.7 + entities: ~2.0.0 + linkify-it: ^2.0.0 + mdurl: ^1.0.1 + uc.micro: ^1.0.5 + bin: + markdown-it: bin/markdown-it.js + checksum: 69f5ee640cbebb451b80d3cce308fff7230767e05c0f8c206a1e413775b7a6e5a08e91e9f3ec59f9b5c5a45493f9ce7ac089379cffb60c9d3e6677ed9d535086 + languageName: node + linkType: hard + "markdown-it@npm:^14.1.0": version: 14.1.0 resolution: "markdown-it@npm:14.1.0" @@ -15442,6 +15489,13 @@ __metadata: languageName: node linkType: hard +"mdurl@npm:^1.0.1": + version: 1.0.1 + resolution: "mdurl@npm:1.0.1" + checksum: 71731ecba943926bfbf9f9b51e28b5945f9411c4eda80894221b47cc105afa43ba2da820732b436f0798fd3edbbffcd1fc1415843c41a87fea08a41cc1e3d02b + languageName: node + linkType: hard + "mdurl@npm:^2.0.0": version: 2.0.0 resolution: "mdurl@npm:2.0.0" @@ -17946,6 +18000,21 @@ __metadata: languageName: node linkType: hard +"react-native-markdown-display@npm:^7.0.2": + version: 7.0.2 + resolution: "react-native-markdown-display@npm:7.0.2" + dependencies: + css-to-react-native: ^3.0.0 + markdown-it: ^10.0.0 + prop-types: ^15.7.2 + react-native-fit-image: ^1.5.5 + peerDependencies: + react: ">=16.2.0" + react-native: ">=0.50.4" + checksum: 69c64f2d21f4ebf001c71d6dff31ef5e13946a0b46b2d2f2830efa89d648afbd8c336be8552264b42d693446e13a3f81fdbfdd5aa9b20878441acabd59f0a54e + languageName: node + linkType: hard + "react-native-paper@npm:^4.12.5": version: 4.12.8 resolution: "react-native-paper@npm:4.12.8" @@ -20388,6 +20457,7 @@ __metadata: "@types/leaflet": ^1.9.12 "@types/leaflet.markercluster": ^1.5.4 "@types/markdown-it": ^14.1.2 + "@types/markdown-it-emoji": ^3.0.1 "@types/node": ^20.9.1 "@types/papaparse": ^5.3.14 "@types/pluralize": ^0.0.33 @@ -20409,6 +20479,7 @@ __metadata: cross-env: ^7.0.3 crypto-browserify: ^3.12.0 crypto-js: ^4.2.0 + css-to-react-native: ^3.2.0 cypress: ^13.9.0 depcheck: ^1.4.7 dotenv: ^16.3.1 @@ -20449,6 +20520,7 @@ __metadata: long: ^5.2.1 lottie-react-native: 6.5.1 markdown-it: ^14.1.0 + markdown-it-emoji: ^3.0.0 merkletreejs: ^0.4.0 metamask-react: ^2.4.1 moment: ^2.29.4 @@ -20482,6 +20554,7 @@ __metadata: react-native-image-picker: ^7.1.0 react-native-keyboard-aware-scrollview: ^2.1.0 react-native-leaflet-view: ^0.1.2 + react-native-markdown-display: ^7.0.2 react-native-paper: ^4.12.5 react-native-pell-rich-editor: ^1.8.8 react-native-pie-chart: ^3.0.1 @@ -21065,6 +21138,13 @@ __metadata: languageName: node linkType: hard +"uc.micro@npm:^1.0.1, uc.micro@npm:^1.0.5": + version: 1.0.6 + resolution: "uc.micro@npm:1.0.6" + checksum: 6898bb556319a38e9cf175e3628689347bd26fec15fc6b29fa38e0045af63075ff3fea4cf1fdba9db46c9f0cbf07f2348cd8844889dd31ebd288c29fe0d27e7a + languageName: node + linkType: hard + "uc.micro@npm:^2.0.0, uc.micro@npm:^2.1.0": version: 2.1.0 resolution: "uc.micro@npm:2.1.0" From 271f701c45bbc8a9b93f63def82a5c44eb13bbdf Mon Sep 17 00:00:00 2001 From: WaDadidou Date: Wed, 11 Dec 2024 19:51:49 -0500 Subject: [PATCH 06/22] feat(article): Integrate Article MD creation and consultation --- .../components/gradientText/GradientText.tsx | 2 + .../gradientText/GradientText.web.tsx | 2 + .../components/socialFeed/Map/Map.web.tsx | 4 + .../socialFeed/NewsFeed/LocationButton.tsx | 15 +- .../socialFeed/NewsFeed/NewsFeed.tsx | 7 + .../cards/SocialArticleMarkdownCard.tsx | 197 +++++++++ .../FeedNewArticle/FeedNewArticleScreen.tsx | 237 +++++------ .../ArticleContentEditor.tsx | 120 +----- .../components/ArticleContentEditor/utils.ts | 109 +++++ .../components/NewArticleLocationButton.tsx | 33 ++ .../components/SocialArticleCardEdition.tsx | 264 ------------ .../screens/FeedPostView/FeedPostView.tsx | 9 + .../FeedPostArticleMarkdownView.tsx | 391 ++++++++++++++++++ .../screens/Mini/Feed/ArticlesFeedScreen.tsx | 2 +- packages/utils/feed/map.ts | 6 + packages/utils/types/feed.ts | 2 +- 16 files changed, 884 insertions(+), 516 deletions(-) create mode 100644 packages/components/socialFeed/SocialCard/cards/SocialArticleMarkdownCard.tsx create mode 100644 packages/screens/FeedNewArticle/components/NewArticleLocationButton.tsx delete mode 100644 packages/screens/FeedNewArticle/components/SocialArticleCardEdition.tsx create mode 100644 packages/screens/FeedPostView/components/FeedPostArticleMarkdownView.tsx diff --git a/packages/components/gradientText/GradientText.tsx b/packages/components/gradientText/GradientText.tsx index 846dbd7f63..88077577fe 100644 --- a/packages/components/gradientText/GradientText.tsx +++ b/packages/components/gradientText/GradientText.tsx @@ -123,6 +123,8 @@ const gradient = (type: GradientType): LinearGradientProps => { return getMapPostTextGradient(PostCategory.Normal); case getMapPostTextGradientType(PostCategory.Article): return getMapPostTextGradient(PostCategory.Article); + case getMapPostTextGradientType(PostCategory.ArticleMarkdown): + return getMapPostTextGradient(PostCategory.Article); case getMapPostTextGradientType(PostCategory.Video): return getMapPostTextGradient(PostCategory.Video); case getMapPostTextGradientType(PostCategory.Picture): diff --git a/packages/components/gradientText/GradientText.web.tsx b/packages/components/gradientText/GradientText.web.tsx index e7ddd31819..362623ad4a 100644 --- a/packages/components/gradientText/GradientText.web.tsx +++ b/packages/components/gradientText/GradientText.web.tsx @@ -48,6 +48,8 @@ const gradient = (type: GradientType) => { return getMapPostTextGradientString(PostCategory.Normal); case getMapPostTextGradientType(PostCategory.Article): return getMapPostTextGradientString(PostCategory.Article); + case getMapPostTextGradientType(PostCategory.ArticleMarkdown): + return getMapPostTextGradientString(PostCategory.Article); case getMapPostTextGradientType(PostCategory.Video): return getMapPostTextGradientString(PostCategory.Video); case getMapPostTextGradientType(PostCategory.Picture): diff --git a/packages/components/socialFeed/Map/Map.web.tsx b/packages/components/socialFeed/Map/Map.web.tsx index 9da11a5890..8d2928ffa3 100644 --- a/packages/components/socialFeed/Map/Map.web.tsx +++ b/packages/components/socialFeed/Map/Map.web.tsx @@ -287,6 +287,10 @@ export const Map: FC = ({ + ) : marker.post.category === PostCategory.ArticleMarkdown ? ( + + + ) : marker.post.category === PostCategory.Article ? ( diff --git a/packages/components/socialFeed/NewsFeed/LocationButton.tsx b/packages/components/socialFeed/NewsFeed/LocationButton.tsx index 544e821ba4..e083106f9b 100644 --- a/packages/components/socialFeed/NewsFeed/LocationButton.tsx +++ b/packages/components/socialFeed/NewsFeed/LocationButton.tsx @@ -1,5 +1,6 @@ import { FC } from "react"; -import { ColorValue } from "react-native"; +import { ColorValue, StyleProp, ViewStyle } from "react-native"; +import { MouseEvent } from "react-native/Libraries/Types/CoreEventTypes"; import locationRefinedSVG from "@/assets/icons/location-refined.svg"; import { SVG } from "@/components/SVG"; @@ -9,9 +10,17 @@ export const LocationButton: FC<{ onPress: () => void; color?: ColorValue; stroke?: ColorValue; -}> = ({ onPress, stroke, color }) => { + style?: StyleProp; + onHoverIn?: (event: MouseEvent) => void; + onHoverOut?: (event: MouseEvent) => void; +}> = ({ onPress, stroke, color, style, onHoverIn, onHoverOut }) => { return ( - + = ({ style={cardStyle} refetchFeed={refetch} /> + ) : post.category === PostCategory.ArticleMarkdown ? ( + ) : post.category === PostCategory.Video ? ( ; + refetchFeed?: () => Promise; + isFlagged?: boolean; + disabled?: boolean; +}> = memo( + ({ post, isPostConsultation, refetchFeed, style, isFlagged, disabled }) => { + const navigation = useAppNavigation(); + const [localPost, setLocalPost] = useState(post); + const [viewWidth, setViewWidth] = useState(0); + const { width: windowWidth } = useWindowDimensions(); + + const articleCardHeight = + windowWidth < SOCIAL_FEED_BREAKPOINT_M ? 214 : 254; + const thumbnailImageWidth = viewWidth / 3; + const borderRadius = + windowWidth < RESPONSIVE_BREAKPOINT_S ? 0 : SOCIAl_CARD_BORDER_RADIUS; + + const metadata = zodTryParseJSON( + ZodSocialFeedArticleMetadata, + localPost.metadata, + ); + const thumbnailImage = metadata?.thumbnailImage; + const shortDescription = metadata?.shortDescription || ""; + const title = metadata?.title; + + useEffect(() => { + setLocalPost(post); + }, [post]); + + const thumbnailURI = thumbnailImage?.url + ? thumbnailImage.url.includes("://") + ? thumbnailImage.url + : "ipfs://" + thumbnailImage.url // we need this hack because ipfs "urls" in feed are raw CIDs + : defaultThumbnailImage; + + return ( + + + navigation.navigate("FeedPostView", { + id: localPost.id, + }) + } + onLayout={(e) => setViewWidth(e.nativeEvent.layout.width)} + style={[ + { + borderWidth: 1, + borderColor: withAlpha(neutral33, 0.5), + borderRadius, + backgroundColor: neutral00, + width: "100%", + flexDirection: "row", + justifyContent: "space-between", + height: articleCardHeight, + flex: 1, + }, + style, + ]} + > + + + + + + + {title?.trim().replace("\n", " ")} + + + + + {shortDescription.trim().replace("\n", " ")} + + + + + {/*We use a shadow to highlight the footer when it's onto the thumbnail image (mobile)*/} + + {isFlagged ? ( + + ) : ( + + )} + + + + + + ); + }, +); diff --git a/packages/screens/FeedNewArticle/FeedNewArticleScreen.tsx b/packages/screens/FeedNewArticle/FeedNewArticleScreen.tsx index dbea189eb2..b76d78998f 100644 --- a/packages/screens/FeedNewArticle/FeedNewArticleScreen.tsx +++ b/packages/screens/FeedNewArticle/FeedNewArticleScreen.tsx @@ -1,5 +1,5 @@ import pluralize from "pluralize"; -import { useEffect, useRef, useState } from "react"; +import { useRef, useState } from "react"; import { FormProvider, useForm } from "react-hook-form"; import { ScrollView, useWindowDimensions, View } from "react-native"; import { useSelector } from "react-redux"; @@ -14,13 +14,13 @@ import { ScreenContainer } from "@/components/ScreenContainer"; import { WalletStatusBox } from "@/components/WalletStatusBox"; import { TertiaryBox } from "@/components/boxes/TertiaryBox"; import { CustomPressable } from "@/components/buttons/CustomPressable"; +import { PrimaryButton } from "@/components/buttons/PrimaryButton"; import { Label, TextInputCustom } from "@/components/inputs/TextInputCustom"; import { FileUploader } from "@/components/inputs/fileUploader"; import { FeedPostingProgressBar } from "@/components/loaders/FeedPostingProgressBar"; -import { PublishValues } from "@/components/socialFeed/RichText/RichText.type"; -import { SocialArticleCard } from "@/components/socialFeed/SocialCard/cards/SocialArticleCard"; +import { SocialArticleMarkdownCard } from "@/components/socialFeed/SocialCard/cards/SocialArticleMarkdownCard"; import { MapModal } from "@/components/socialFeed/modals/MapModal/MapModal"; -import { SpacerColumn } from "@/components/spacer"; +import { SpacerColumn, SpacerRow } from "@/components/spacer"; import { useFeedbacks } from "@/context/FeedbacksProvider"; import { useWalletControl } from "@/context/WalletControlProvider"; import { useFeedPosting } from "@/hooks/feed/useFeedPosting"; @@ -30,9 +30,9 @@ import { useMaxResolution } from "@/hooks/useMaxResolution"; import { useSelectedNetworkId } from "@/hooks/useSelectedNetwork"; import { NetworkFeature } from "@/networks"; import { ArticleContentEditor } from "@/screens/FeedNewArticle/components/ArticleContentEditor/ArticleContentEditor"; +import { NewArticleLocationButton } from "@/screens/FeedNewArticle/components/NewArticleLocationButton"; import { selectNFTStorageAPI } from "@/store/slices/settings"; import { feedPostingStep, FeedPostingStepId } from "@/utils/feed/posting"; -import { generateArticleMetadata } from "@/utils/feed/queries"; import { generateIpfsKey } from "@/utils/ipfs"; import { IMAGE_MIME_TYPES } from "@/utils/mime"; import { ScreenFC, useAppNavigation } from "@/utils/navigation"; @@ -56,9 +56,6 @@ import { PostCategory, SocialFeedArticleMetadata, } from "@/utils/types/feed"; -import { RemoteFileData } from "@/utils/types/files"; - -//TODO: In mobile : Make ActionsContainer accessible (floating button ?) export const FeedNewArticleScreen: ScreenFC<"FeedNewArticle"> = () => { const { width } = useMaxResolution(); @@ -72,7 +69,8 @@ export const FeedNewArticleScreen: ScreenFC<"FeedNewArticle"> = () => { const { uploadFilesToPinata, ipfsUploadProgress } = useIpfs(); const [isUploadLoading, setIsUploadLoading] = useState(false); const [isProgressBarShown, setIsProgressBarShown] = useState(false); - const postCategory = PostCategory.Article; + const [isMapShown, setIsMapShown] = useState(false); + const postCategory = PostCategory.ArticleMarkdown; const { makePost, isProcessing, @@ -106,7 +104,6 @@ export const FeedNewArticleScreen: ScreenFC<"FeedNewArticle"> = () => { const scrollViewRef = useRef(null); const [isThumbnailButtonHovered, setThumbnailButtonHovered] = useState(false); const [location, setLocation] = useState(); - const [isMapShown, setIsMapShown] = useState(false); const cardStyle = isSmallScreen && { borderRadius: 0, borderLeftWidth: 0, @@ -116,15 +113,12 @@ export const FeedNewArticleScreen: ScreenFC<"FeedNewArticle"> = () => { defaultValues: { title: "", message: "", - files: [], - gifs: [], - hashtags: [], - mentions: [], thumbnailImage: undefined, shortDescription: "", }, mode: "onBlur", }); + const formValues = newArticleForm.watch(); const previewMetadata: SocialFeedArticleMetadata = { title: formValues.title, @@ -135,7 +129,7 @@ export const FeedNewArticleScreen: ScreenFC<"FeedNewArticle"> = () => { mentions: [], }; - const onPublish = async (values: PublishValues) => { + const onPublish = async () => { const action = "Publish an Article"; if (!wallet?.address || !wallet.connected) { showConnectWalletModal({ @@ -156,71 +150,42 @@ export const FeedNewArticleScreen: ScreenFC<"FeedNewArticle"> = () => { } setIsUploadLoading(true); setIsProgressBarShown(true); - try { - const localFiles = [ - ...(formValues.files || []), - ...values.images, - ...values.audios, - ...values.videos, - ]; - if (formValues.thumbnailImage) localFiles.push(formValues.thumbnailImage); - if (formValues.coverImage) localFiles.push(formValues.coverImage); - - let pinataJWTKey = undefined; - if (localFiles?.length) { - setStep(feedPostingStep(FeedPostingStepId.GENERATING_KEY)); - - pinataJWTKey = - userIPFSKey || (await generateIpfsKey(selectedNetworkId, userId)); - } - - // Upload files to IPFS - let remoteFiles: RemoteFileData[] = []; - if (pinataJWTKey) { - setStep(feedPostingStep(FeedPostingStepId.UPLOADING_FILES)); - remoteFiles = await uploadFilesToPinata({ - files: localFiles, - pinataJWTKey, - }); - } - - // If the user uploaded files, but they are not pinned to IPFS, it returns files with empty url, so this is an error. - if (formValues.files?.length && !remoteFiles.find((file) => file.url)) { - console.error("upload file err : Fail to pin to IPFS"); + try { + // Upload thumbnail to IPFS + const pinataJWTKey = + userIPFSKey || (await generateIpfsKey(selectedNetworkId, userId)); + if (!pinataJWTKey) { + console.error("upload file err : No Pinata JWT"); setToast({ - mode: "normal", - type: "error", title: "File upload failed", - message: "Fail to pin to IPFS, please try to Publish again", + message: "No Pinata JWT", + type: "error", + mode: "normal", }); setIsUploadLoading(false); return; } + setStep(feedPostingStep(FeedPostingStepId.UPLOADING_FILES)); - let message = values.html; - if (remoteFiles.length) { - localFiles?.map((file, index) => { - // Audio are not in the HTML for now - if (remoteFiles[index]?.fileType !== "audio") { - message = message.replace(file.url, remoteFiles[index].url); - } - }); - } + const remoteThumbnail = formValues.thumbnailImage + ? ( + await uploadFilesToPinata({ + files: [formValues.thumbnailImage], + pinataJWTKey, + }) + )[0] + : undefined; - const metadata = generateArticleMetadata({ - ...formValues, - thumbnailImage: remoteFiles.find( - (remoteFile) => remoteFile.isThumbnailImage, - ), - coverImage: remoteFiles.find((remoteFile) => remoteFile.isCoverImage), - gifs: values.gifs, - files: remoteFiles, - mentions: values.mentions, - hashtags: values.hashtags, - message, + const metadata: SocialFeedArticleMetadata = { + thumbnailImage: remoteThumbnail, + title: formValues.title, + shortDescription: formValues.shortDescription || "", + message: formValues.message, location, - }); + hashtags: [], + mentions: [], + }; await makePost(JSON.stringify(metadata)); } catch (err) { @@ -236,28 +201,6 @@ export const FeedNewArticleScreen: ScreenFC<"FeedNewArticle"> = () => { } }; - //TODO: Keep short post formValues when returning to short post - const navigateBack = () => navigation.navigate("Feed"); - - //TODO: Not handled for now - // const { mutate: openGraphMutate, data: openGraphData } = useOpenGraph(); - - // // OpenGraph URL preview - // useEffect(() => { - // addedUrls.forEach(url => { - // openGraphMutate({ - // url, - // }); - // - // }) - // }, [addedUrls]) - - // Scroll to bottom when the loading bar appears - useEffect(() => { - if (step.id !== "UNDEFINED" && isLoading) - scrollViewRef.current?.scrollToEnd(); - }, [step, isLoading]); - return ( = () => { mobileTitle="NEW ARTICLE" fullWidth headerChildren={New Article} - onBackPress={navigateBack} + onBackPress={() => navigation.navigate("Feed")} footerChildren noMargin noScroll @@ -291,37 +234,71 @@ export const FeedNewArticleScreen: ScreenFC<"FeedNewArticle"> = () => { - - - + - {freePostCount - ? `You have ${freePostCount} free ${pluralize( - "Article", - freePostCount, - )} left` - : `The cost for this Article is ${prettyPublishingFee}`} - - + + + {freePostCount + ? `You have ${freePostCount} free ${pluralize( + "Article", + freePostCount, + )} left` + : `The cost for this Article is ${prettyPublishingFee}`} + + + + + + + + + + + {step.id !== "UNDEFINED" && isProgressBarShown && ( + <> + + + + )} noBrokenCorners @@ -409,11 +386,11 @@ export const FeedNewArticleScreen: ScreenFC<"FeedNewArticle"> = () => { )} - = () => { - - {step.id !== "UNDEFINED" && isProgressBarShown && ( - <> - - - - )} {isMapShown && ( diff --git a/packages/screens/FeedNewArticle/components/ArticleContentEditor/ArticleContentEditor.tsx b/packages/screens/FeedNewArticle/components/ArticleContentEditor/ArticleContentEditor.tsx index 0e9155f7f6..57cb79478d 100644 --- a/packages/screens/FeedNewArticle/components/ArticleContentEditor/ArticleContentEditor.tsx +++ b/packages/screens/FeedNewArticle/components/ArticleContentEditor/ArticleContentEditor.tsx @@ -1,4 +1,3 @@ -import type { MixedStyleRecord } from "@native-html/transient-render-engine"; import markdownit from "markdown-it"; import { full as emoji } from "markdown-it-emoji"; import { FC, useRef, useState } from "react"; @@ -17,15 +16,12 @@ import { Label } from "@/components/inputs/TextInputCustom"; import { SpacerColumn } from "@/components/spacer"; import { useMaxResolution } from "@/hooks/useMaxResolution"; import { Toolbar } from "@/screens/FeedNewArticle/components/ArticleContentEditor/Toolbar/Toolbar"; -import { ContentMode } from "@/screens/FeedNewArticle/components/ArticleContentEditor/utils"; -import { ARTICLE_MAX_WIDTH } from "@/utils/social-feed"; import { - neutral00, - neutral17, - neutralA3, - neutralFF, - primaryColor, -} from "@/utils/style/colors"; + ContentMode, + markdownTagStyles, +} from "@/screens/FeedNewArticle/components/ArticleContentEditor/utils"; +import { ARTICLE_MAX_WIDTH } from "@/utils/social-feed"; +import { neutral00, neutralA3, neutralFF } from "@/utils/style/colors"; import { layout, RESPONSIVE_BREAKPOINT_S } from "@/utils/style/layout"; import { NewArticleFormValues } from "@/utils/types/feed"; @@ -44,7 +40,7 @@ export const ArticleContentEditor: FC = ({ width }) => { const editionAndPreviewHeight = height - 80; const [textInputHeight, setTextInputHeight] = useState(textInputMinHeight); const [mode, setMode] = useState("BOTH"); - const [renderWidth, setRenderWidth] = useState(0); + const [renderHtmlWidth, setRenderHtmlWidth] = useState(0); const textInputContainerPadding = windowWidth < RESPONSIVE_BREAKPOINT_S ? 0 @@ -61,106 +57,6 @@ export const ArticleContentEditor: FC = ({ width }) => { }).use(emoji); const html = md.render(message); - const markdownTagStyles: MixedStyleRecord = { - body: { - color: neutralA3, - fontSize: 14, - letterSpacing: -(14 * 0.04), - lineHeight: 22, - fontFamily: "Exo_500Medium", - fontWeight: "500", - }, - p: { - marginVertical: 4, - color: neutralA3, - fontSize: 14, - letterSpacing: -(14 * 0.04), - lineHeight: 22, - fontFamily: "Exo_500Medium", - fontWeight: "500", - }, - code: { - color: neutralA3, - fontSize: 13, - letterSpacing: -(13 * 0.04), - lineHeight: 22, - fontFamily: "Exo_500Medium", - fontWeight: "500", - - backgroundColor: neutral17, - marginVertical: 4, - paddingHorizontal: 4, - paddingVertical: 2, - borderRadius: 4, - alignSelf: "flex-start", - }, - pre: { - fontSize: 13, - letterSpacing: -(13 * 0.04), - fontFamily: "Exo_500Medium", - fontWeight: "500", - - backgroundColor: neutral17, - paddingHorizontal: 8, - paddingVertical: 8, - borderRadius: 4, - }, - strong: { fontWeight: "700" }, - a: { - color: primaryColor, - textDecorationLine: "none", - }, - hr: { backgroundColor: neutralA3 }, - h1: { - color: neutralFF, - fontSize: 28, - letterSpacing: -(28 * 0.02), - lineHeight: 37, - fontFamily: "Exo_500Medium", - fontWeight: "500", - }, - h2: { - color: neutralFF, - fontSize: 21, - letterSpacing: -(21 * 0.02), - lineHeight: 28, - fontFamily: "Exo_500Medium", - fontWeight: "500", - }, - h3: { - color: neutralFF, - fontSize: 16, - letterSpacing: -(16 * 0.02), - lineHeight: 23, - fontFamily: "Exo_500Medium", - fontWeight: "500", - }, - h4: { - color: neutralFF, - fontSize: 14, - letterSpacing: -(14 * 0.04), - lineHeight: 20, - fontFamily: "Exo_500Medium", - fontWeight: "500", - }, - h5: { - color: neutralA3, - fontSize: 14, - letterSpacing: -(14 * 0.04), - lineHeight: 20, - fontFamily: "Exo_500Medium", - fontWeight: "500", - }, - h6: { - color: neutralA3, - fontSize: 12, - letterSpacing: -(12 * 0.04), - lineHeight: 16, - fontFamily: "Exo_500Medium", - fontWeight: "500", - }, - }; - // ========== JSX return ( = ({ width }) => { paddingHorizontal: textInputContainerPadding, marginTop: textInputContainerPadding, }} - onLayout={(e) => setRenderWidth(e.nativeEvent.layout.width)} + onLayout={(e) => setRenderHtmlWidth(e.nativeEvent.layout.width)} > diff --git a/packages/screens/FeedNewArticle/components/ArticleContentEditor/utils.ts b/packages/screens/FeedNewArticle/components/ArticleContentEditor/utils.ts index 075d6fc46f..711094efa6 100644 --- a/packages/screens/FeedNewArticle/components/ArticleContentEditor/utils.ts +++ b/packages/screens/FeedNewArticle/components/ArticleContentEditor/utils.ts @@ -1 +1,110 @@ +import type { MixedStyleRecord } from "@native-html/transient-render-engine"; + +import { + neutral17, + neutralA3, + neutralFF, + primaryColor, +} from "@/utils/style/colors"; + export type ContentMode = "EDITION" | "BOTH" | "PREVIEW"; + +export const markdownTagStyles: MixedStyleRecord = { + body: { + color: neutralA3, + fontSize: 14, + letterSpacing: -(14 * 0.04), + lineHeight: 22, + fontFamily: "Exo_500Medium", + fontWeight: "500", + }, + p: { + marginVertical: 4, + color: neutralA3, + fontSize: 14, + letterSpacing: -(14 * 0.04), + lineHeight: 22, + fontFamily: "Exo_500Medium", + fontWeight: "500", + }, + code: { + color: neutralA3, + fontSize: 13, + letterSpacing: -(13 * 0.04), + lineHeight: 22, + fontFamily: "Exo_500Medium", + fontWeight: "500", + + backgroundColor: neutral17, + marginVertical: 4, + paddingHorizontal: 4, + paddingVertical: 2, + borderRadius: 4, + alignSelf: "flex-start", + }, + pre: { + fontSize: 13, + letterSpacing: -(13 * 0.04), + fontFamily: "Exo_500Medium", + fontWeight: "500", + + backgroundColor: neutral17, + paddingHorizontal: 8, + paddingVertical: 8, + borderRadius: 4, + }, + strong: { fontWeight: "700" }, + a: { + color: primaryColor, + textDecorationLine: "none", + }, + hr: { backgroundColor: neutralA3 }, + h1: { + color: neutralFF, + fontSize: 28, + letterSpacing: -(28 * 0.02), + lineHeight: 37, + fontFamily: "Exo_500Medium", + fontWeight: "500", + }, + h2: { + color: neutralFF, + fontSize: 21, + letterSpacing: -(21 * 0.02), + lineHeight: 28, + fontFamily: "Exo_500Medium", + fontWeight: "500", + }, + h3: { + color: neutralFF, + fontSize: 16, + letterSpacing: -(16 * 0.02), + lineHeight: 23, + fontFamily: "Exo_500Medium", + fontWeight: "500", + }, + h4: { + color: neutralFF, + fontSize: 14, + letterSpacing: -(14 * 0.04), + lineHeight: 20, + fontFamily: "Exo_500Medium", + fontWeight: "500", + }, + h5: { + color: neutralA3, + fontSize: 14, + letterSpacing: -(14 * 0.04), + lineHeight: 20, + fontFamily: "Exo_500Medium", + fontWeight: "500", + }, + h6: { + color: neutralA3, + fontSize: 12, + letterSpacing: -(12 * 0.04), + lineHeight: 16, + fontFamily: "Exo_500Medium", + fontWeight: "500", + }, +}; diff --git a/packages/screens/FeedNewArticle/components/NewArticleLocationButton.tsx b/packages/screens/FeedNewArticle/components/NewArticleLocationButton.tsx new file mode 100644 index 0000000000..77bf134f96 --- /dev/null +++ b/packages/screens/FeedNewArticle/components/NewArticleLocationButton.tsx @@ -0,0 +1,33 @@ +import React, { Dispatch, FC, SetStateAction, useState } from "react"; + +import { LocationButton } from "@/components/socialFeed/NewsFeed/LocationButton"; +import { neutral33, neutralFF } from "@/utils/style/colors"; +import { CustomLatLngExpression } from "@/utils/types/feed"; + +export const NewArticleLocationButton: FC<{ + location?: CustomLatLngExpression; + setIsMapShown: Dispatch>; +}> = ({ location, setIsMapShown }) => { + const [isHovered, setHovered] = useState(false); + + return ( + <> + setIsMapShown(true)} + onHoverIn={() => setHovered(true)} + onHoverOut={() => setHovered(false)} + stroke={!location ? neutralFF : undefined} + color={!location ? undefined : neutralFF} + style={{ + height: 48, + width: 48, + borderWidth: 1, + borderColor: isHovered ? neutralFF : neutral33, + borderRadius: 6, + alignItems: "center", + justifyContent: "center", + }} + /> + + ); +}; diff --git a/packages/screens/FeedNewArticle/components/SocialArticleCardEdition.tsx b/packages/screens/FeedNewArticle/components/SocialArticleCardEdition.tsx deleted file mode 100644 index e2b271a718..0000000000 --- a/packages/screens/FeedNewArticle/components/SocialArticleCardEdition.tsx +++ /dev/null @@ -1,264 +0,0 @@ -import React, { FC, memo, useRef, useState } from "react"; -import { Path, useFormContext } from "react-hook-form"; -import { - StyleProp, - TextInput, - TextStyle, - useWindowDimensions, - View, - ViewStyle, -} from "react-native"; -import { Hoverable } from "react-native-hoverable"; - -import defaultThumbnailImage from "@/assets/default-images/default-article-thumbnail.png"; -import { OptimizedImage } from "@/components/OptimizedImage"; -import { CustomPressable } from "@/components/buttons/CustomPressable"; -import { FileUploader } from "@/components/inputs/fileUploader"; -import { SocialCardHeader } from "@/components/socialFeed/SocialCard/SocialCardHeader"; -import { SpacerColumn } from "@/components/spacer"; -import useSelectedWallet from "@/hooks/useSelectedWallet"; -import { IMAGE_MIME_TYPES } from "@/utils/mime"; -import { SOCIAl_CARD_BORDER_RADIUS } from "@/utils/social-feed"; -import { - neutral00, - neutral33, - neutralA3, - neutralFF, -} from "@/utils/style/colors"; -import { - fontSemibold14, - fontSemibold16, - fontSemibold20, -} from "@/utils/style/fonts"; -import { - layout, - RESPONSIVE_BREAKPOINT_S, - SOCIAL_FEED_BREAKPOINT_M, -} from "@/utils/style/layout"; -import { NewArticleFormValues } from "@/utils/types/feed"; - -// ========= IT'S A TEST. I WOULD LIKE AN EDITABLE CARD WYSIWYG ======= - -const ARTICLE_CARD_PADDING_VERTICAL = layout.spacing_x2; -const ARTICLE_CARD_PADDING_HORIZONTAL = layout.spacing_x2_5; - -export const SocialArticleCardEdition: FC<{ - style?: StyleProp; -}> = memo(({ style }) => { - const selectedWallet = useSelectedWallet(); - const newArticleForm = useFormContext(); - const title = newArticleForm.watch("title"); - const shortDescription = newArticleForm.watch("shortDescription"); - const thumbnailImage = newArticleForm.watch("thumbnailImage"); - const [viewWidth, setViewWidth] = useState(0); - const { width: windowWidth } = useWindowDimensions(); - const [hoveredInput, setHoveredInput] = - useState | null>(null); - - const titleInputMinHeight = 24; - const titleInputMaxHeight = windowWidth < SOCIAL_FEED_BREAKPOINT_M ? 44 : 64; - const [titleInputHeight, setTitleInputHeight] = useState(titleInputMinHeight); - const shortDescInputMaxHeight = 80; - const [shortDescInputHeight, setShortDescInputHeight] = useState( - shortDescInputMaxHeight, - ); - - const articleCardHeight = windowWidth < SOCIAL_FEED_BREAKPOINT_M ? 214 : 254; - const titleFont = - windowWidth < SOCIAL_FEED_BREAKPOINT_M ? fontSemibold16 : fontSemibold20; - const thumbnailImageWidth = viewWidth / 3; - const borderRadius = - windowWidth < RESPONSIVE_BREAKPOINT_S ? 0 : SOCIAl_CARD_BORDER_RADIUS; - - const thumbnailURI = thumbnailImage?.url - ? thumbnailImage.url.includes("://") - ? thumbnailImage.url - : "ipfs://" + thumbnailImage.url // we need this hack because ipfs "urls" in feed are raw CIDs - : defaultThumbnailImage; - - const titleInputRef = useRef(null); - const shortDescInputRef = useRef(null); - - return ( - setViewWidth(e.nativeEvent.layout.width)} - style={[ - { - borderWidth: 1, - borderColor: neutral33, - borderRadius, - backgroundColor: neutral00, - width: "100%", - flexDirection: "row", - justifyContent: "space-between", - height: articleCardHeight, - flex: 1, - }, - style, - ]} - > - - - - - - - {/* // TODO: Good growth and shrink input. Maybe need a maxLength */} - setHoveredInput("title")} - onMouseLeave={() => setHoveredInput(null)} - > - { - newArticleForm.setValue("title", text); - - // The idea was to prevent typing more than 2 lines - newArticleForm.setValue("title", text); - if (titleInputRef.current) { - titleInputRef.current.measure((x, y, width, height) => { - console.log("heightheight", height); - if (height > titleInputMaxHeight) { - newArticleForm.setValue("title", title.slice(0, -1)); - } else { - setTitleInputHeight(height); - } - }); - } - }} - onContentSizeChange={(e) => - setTitleInputHeight(e.nativeEvent.contentSize.height) - } - /> - - - - - setHoveredInput("shortDescription")} - onMouseLeave={() => setHoveredInput(null)} - > - { - newArticleForm.setValue("shortDescription", text); - - // The idea was to prevent typing more than 3 lines - if (shortDescInputRef.current && shortDescription) { - shortDescInputRef.current.measure((x, y, width, height) => { - console.log("heightheight", height); - if (height > shortDescInputMaxHeight) { - newArticleForm.setValue( - "shortDescription", - shortDescription.slice(0, -1), - ); - } else { - setShortDescInputHeight(height); - } - }); - } - }} - onContentSizeChange={(e) => - setShortDescInputHeight(e.nativeEvent.contentSize.height) - } - /> - - - - - {/*TODO: Input*/} - - newArticleForm.setValue("thumbnailImage", files?.[0]) - } - mimeTypes={IMAGE_MIME_TYPES} - > - {({ onPress }) => ( - setHoveredInput("thumbnailImage")} - onHoverOut={() => setHoveredInput(null)} - onPress={onPress} - style={ - hoveredInput === "thumbnailImage" && { - borderColor: neutralFF, - borderWidth: 1, - } - } - > - - - )} - - - ); -}); diff --git a/packages/screens/FeedPostView/FeedPostView.tsx b/packages/screens/FeedPostView/FeedPostView.tsx index b4006a2ade..7e55d95fc8 100644 --- a/packages/screens/FeedPostView/FeedPostView.tsx +++ b/packages/screens/FeedPostView/FeedPostView.tsx @@ -10,6 +10,7 @@ import { ScreenContainer } from "@/components/ScreenContainer"; import { ScreenTitle } from "@/components/ScreenContainer/ScreenTitle"; import { usePost } from "@/hooks/feed/usePost"; import { parseNetworkObjectId } from "@/networks"; +import { FeedPostArticleMarkdownView } from "@/screens/FeedPostView/components/FeedPostArticleMarkdownView"; import { convertLegacyPostId } from "@/utils/feed/queries"; import { ScreenFC, useAppNavigation } from "@/utils/navigation"; import { primaryColor } from "@/utils/style/colors"; @@ -76,6 +77,14 @@ export const FeedPostView: ScreenFC<"FeedPostView"> = ({ if (post.category === PostCategory.Video) { return ; + } else if (post.category === PostCategory.ArticleMarkdown) { + return ( + + ); } else if (post.category === PostCategory.Article) { return ( Promise; + isLoadingPost?: boolean; +}> = ({ post, refetchPost, isLoadingPost }) => { + const navigation = useAppNavigation(); + const { width: windowWidth } = useWindowDimensions(); + const { width } = useMaxResolution(); + const isMobile = useIsMobile(); + const [parentOffsetValue, setParentOffsetValue] = useState(0); + + const authorId = post?.authorId; + const authorNSInfo = useNSUserInfo(authorId); + const [, authorAddress] = parseUserId(post?.authorId); + const username = authorNSInfo?.metadata?.tokenId || authorAddress; + + const [localPost, setLocalPost] = useState(post); + const feedInputRef = useRef(null); + const [replyTo, setReplyTo] = useState(); + const aref = useAnimatedRef(); + const [flatListContentOffsetY, setFlatListContentOffsetY] = useState(0); + const [articleOffsetY, setArticleOffsetY] = useState(0); + const [articleWidth, setArticleWidth] = useState(0); + const [renderHtmlWidth, setRenderHtmlWidth] = useState(0); + const isGoingUp = useSharedValue(false); + const isLoadingSharedValue = useSharedValue(true); + const [isCreateModalVisible, setCreateModalVisible] = useState(false); + const { + data: comments, + refetch: refetchComments, + hasNextPage, + fetchNextPage, + isLoading: isLoadingComments, + } = useFetchComments({ + parentId: post.id, + totalCount: post.subPostLength, + enabled: true, + }); + const isNextPageAvailable = useSharedValue(hasNextPage); + + const articleMetadata = zodTryParseJSON( + ZodSocialFeedArticleMetadata, + post.metadata, + ); + const message = articleMetadata?.message; + const title = articleMetadata?.title; + const location = articleMetadata?.location; + + const headerLabel = useMemo(() => { + const authorDisplayName = + authorNSInfo?.metadata?.tokenId || + tinyAddress(authorAddress) || + DEFAULT_USERNAME; + return `Article by ${authorDisplayName}`; + }, [authorNSInfo?.metadata?.tokenId, authorAddress]); + + const onPressReply: OnPressReplyType = (data) => { + feedInputRef.current?.resetForm(); + setReplyTo(data); + feedInputRef.current?.setValue(`@${username} `); + feedInputRef.current?.focusInput(); + }; + + const handleSubmitInProgress = () => { + if (replyTo?.parentId && replyTo.yOffsetValue) + aref.current?.scrollTo(replyTo.yOffsetValue); + else aref.current?.scrollTo(0); + }; + + const scrollHandler = useAnimatedScrollHandler( + { + onScroll: (event) => { + let offsetPadding = 40; + offsetPadding += event.layoutMeasurement.height; + if ( + event.contentOffset.y >= event.contentSize.height - offsetPadding && + isNextPageAvailable.value + ) { + fetchNextPage(); + } + + if (flatListContentOffsetY > event.contentOffset.y) { + isGoingUp.value = true; + } else if (flatListContentOffsetY < event.contentOffset.y) { + isGoingUp.value = false; + } + setFlatListContentOffsetY(event.contentOffset.y); + }, + }, + [post.id], + ); + + useEffect(() => { + isLoadingSharedValue.value = isLoadingPost || isLoadingComments; + }, [isLoadingPost, isLoadingComments, isLoadingSharedValue]); + + useEffect(() => { + if (post.category === PostCategory.Video) + navigation.replace("FeedPostView", { + id: post.id, + }); + }, [post.category, post.id, navigation]); + + useEffect(() => { + // HECK: updated state was not showing up in scrollhander + isNextPageAvailable.value = hasNextPage; + }, [hasNextPage, isNextPageAvailable]); + + if (!articleMetadata) return null; + return ( + {headerLabel}} + onBackPress={() => + post?.parentPostIdentifier + ? navigation.navigate("FeedPostView", { + id: post.id, + }) + : navigation.canGoBack() + ? navigation.goBack() + : navigation.navigate("Feed") + } + footerChildren + noScroll + > + + {/* ScreenContainer has noScroll, so we need to add MobileTitle here */} + {isMobile && } + + { + setArticleOffsetY(height); + setArticleWidth(width); + }} + style={{ + width: "100%", + maxWidth: ARTICLE_MAX_WIDTH + contentPaddingHorizontal * 2, + borderBottomWidth: 1, + borderBottomColor: neutral33, + borderRadius: + windowWidth < RESPONSIVE_BREAKPOINT_S + ? 0 + : SOCIAl_CARD_BORDER_RADIUS, + paddingHorizontal: contentPaddingHorizontal, + paddingBottom: layout.spacing_x2, + }} + > + + {/*========== Article title, author info */} + {!!title && {title}} + + + + + + {/*========== Article content */} + + + setRenderHtmlWidth(e.nativeEvent.layout.width) + } + > + + + + + + {/*========== Actions */} + onPressReply({ username })} + refetchFeed={refetchPost} + setPost={setLocalPost} + /> + + + + {/*========== Refresh button no mobile */} + {!isMobile && ( + + { + refetchComments(); + }} + /> + + )} + + setParentOffsetValue(e.nativeEvent.layout.y)} + style={{ width: "100%" }} + > + + + + + {/*========== Comment input */} + {!isMobile && ( + <> + + { + setReplyTo(undefined); + refetchComments(); + }} + /> + + )} + + + {/*========== Refresh button mobile */} + {flatListContentOffsetY >= articleOffsetY + 66 && !isMobile && ( + + + + )} + + {/*========== Refresh button and Comment button mobile */} + {isMobile && ( + <> + + + setCreateModalVisible(true)} + /> + + { + refetchComments(); + }} + /> + + + + )} + + setCreateModalVisible(false)} + onSubmitSuccess={() => { + setReplyTo(undefined); + refetchComments(); + }} + replyTo={replyTo} + parentId={post.localIdentifier} + /> + + ); +}; + +const contentContainerCStyle: ViewStyle = { + alignItems: "center", + alignSelf: "center", +}; +const floatingActionsCStyle: ViewStyle = { + position: "absolute", + justifyContent: "center", + alignItems: "center", + right: 68, + bottom: 230, +}; diff --git a/packages/screens/Mini/Feed/ArticlesFeedScreen.tsx b/packages/screens/Mini/Feed/ArticlesFeedScreen.tsx index 7212af2b5c..e6798128c2 100644 --- a/packages/screens/Mini/Feed/ArticlesFeedScreen.tsx +++ b/packages/screens/Mini/Feed/ArticlesFeedScreen.tsx @@ -14,7 +14,7 @@ export const ArticlesFeedScreen = () => { const req: Partial = { filter: { networkId: selectedNetworkId, - categories: [PostCategory.Article], + categories: [PostCategory.Article, PostCategory.ArticleMarkdown], user: "", mentions: [], hashtags: [], diff --git a/packages/utils/feed/map.ts b/packages/utils/feed/map.ts index 5e2b9e7d4f..4808f1e29c 100644 --- a/packages/utils/feed/map.ts +++ b/packages/utils/feed/map.ts @@ -187,6 +187,7 @@ export const getMapPostIconSVG = ( case PostCategory.VideoNote: return videoPostSvg; case PostCategory.Article: + case PostCategory.ArticleMarkdown: return articlePostSvg; case PostCategory.Normal: return normalPostSvg; @@ -206,6 +207,7 @@ export const getMapPostIconSVGString = (postCategory: PostCategory) => { case PostCategory.VideoNote: return videoPostSvgString; case PostCategory.Article: + case PostCategory.ArticleMarkdown: return articlePostSvgString; case PostCategory.Normal: return normalPostSvgString; @@ -225,6 +227,7 @@ export const getMapPostIconColorRgba = (postCategory: PostCategory) => { case PostCategory.VideoNote: return "198,171,255,.40"; case PostCategory.Article: + case PostCategory.ArticleMarkdown: return "255,252,207,.40"; case PostCategory.Normal: return "255,178,107,.40"; @@ -244,6 +247,7 @@ export const getMapPostTextGradientType = (postCategory: PostCategory) => { case PostCategory.VideoNote: return "feed-map-video-post"; case PostCategory.Article: + case PostCategory.ArticleMarkdown: return "feed-map-article-post"; case PostCategory.Normal: return "feed-map-normal-post"; @@ -271,6 +275,7 @@ export const getMapPostTextGradient = (postCategory: PostCategory) => { gradientProps.colors = ["#C6ABFF", "#A57AFF"]; break; case PostCategory.Article: + case PostCategory.ArticleMarkdown: gradientProps.colors = ["#FFFC6B", "#E5E13B"]; break; case PostCategory.Normal: @@ -294,6 +299,7 @@ export const getMapPostTextGradientString = (postCategory: PostCategory) => { case PostCategory.VideoNote: return `180deg, #C6ABFF 100%, #A57AFF 100%`; case PostCategory.Article: + case PostCategory.ArticleMarkdown: return `180deg, #FFFC6B 100%, #E5E13B 100%`; case PostCategory.Normal: return `180deg, #FFB26B 100%, #E58C3B 100%`; diff --git a/packages/utils/types/feed.ts b/packages/utils/types/feed.ts index 3b7a7b220b..7a61b457ba 100644 --- a/packages/utils/types/feed.ts +++ b/packages/utils/types/feed.ts @@ -19,7 +19,7 @@ export enum PostCategory { Flagged, MusicAudio, Video, - MarkdownArticle, + ArticleMarkdown, } export interface NewArticleFormValues { From b6ec92fd67ea4ab589f28d948111d65059a7f2f4 Mon Sep 17 00:00:00 2001 From: WaDadidou Date: Wed, 11 Dec 2024 19:55:40 -0500 Subject: [PATCH 07/22] chore: yarn install --- yarn.lock | 77 ++----------------------------------------------------- 1 file changed, 2 insertions(+), 75 deletions(-) diff --git a/yarn.lock b/yarn.lock index d60b34c331..d74bd2521c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9793,7 +9793,7 @@ __metadata: languageName: node linkType: hard -"css-to-react-native@npm:^3.0.0, css-to-react-native@npm:^3.2.0": +"css-to-react-native@npm:^3.0.0": version: 3.2.0 resolution: "css-to-react-native@npm:3.2.0" dependencies: @@ -10777,13 +10777,6 @@ __metadata: languageName: node linkType: hard -"entities@npm:~2.0.0": - version: 2.0.3 - resolution: "entities@npm:2.0.3" - checksum: 5a7899fcc622e0d76afdeafe4c58a6b40ae3a8ee4772e5825a648c11a2ca324a9a02515386f512e466baac4aeb551f3d3b79eaece5cd98369b9f8601be336b1a - languageName: node - linkType: hard - "env-editor@npm:^0.4.1": version: 0.4.2 resolution: "env-editor@npm:0.4.2" @@ -14998,15 +14991,6 @@ __metadata: languageName: node linkType: hard -"linkify-it@npm:^2.0.0": - version: 2.2.0 - resolution: "linkify-it@npm:2.2.0" - dependencies: - uc.micro: ^1.0.1 - checksum: d198871d0b3f3cfdb745dae564bfd6743474f20cd0ef1057e6ca29451834749e7f3da52b59b4de44e98f31a1e5c71bdad160490d4ae54de251cbcde57e4d7837 - languageName: node - linkType: hard - "linkify-it@npm:^5.0.0": version: 5.0.0 resolution: "linkify-it@npm:5.0.0" @@ -15370,21 +15354,6 @@ __metadata: languageName: node linkType: hard -"markdown-it@npm:^10.0.0": - version: 10.0.0 - resolution: "markdown-it@npm:10.0.0" - dependencies: - argparse: ^1.0.7 - entities: ~2.0.0 - linkify-it: ^2.0.0 - mdurl: ^1.0.1 - uc.micro: ^1.0.5 - bin: - markdown-it: bin/markdown-it.js - checksum: 69f5ee640cbebb451b80d3cce308fff7230767e05c0f8c206a1e413775b7a6e5a08e91e9f3ec59f9b5c5a45493f9ce7ac089379cffb60c9d3e6677ed9d535086 - languageName: node - linkType: hard - "markdown-it@npm:^14.1.0": version: 14.1.0 resolution: "markdown-it@npm:14.1.0" @@ -15489,13 +15458,6 @@ __metadata: languageName: node linkType: hard -"mdurl@npm:^1.0.1": - version: 1.0.1 - resolution: "mdurl@npm:1.0.1" - checksum: 71731ecba943926bfbf9f9b51e28b5945f9411c4eda80894221b47cc105afa43ba2da820732b436f0798fd3edbbffcd1fc1415843c41a87fea08a41cc1e3d02b - languageName: node - linkType: hard - "mdurl@npm:^2.0.0": version: 2.0.0 resolution: "mdurl@npm:2.0.0" @@ -17448,7 +17410,7 @@ __metadata: languageName: node linkType: hard -"prop-types@npm:^15.5.10, prop-types@npm:^15.5.6, prop-types@npm:^15.5.7, prop-types@npm:^15.5.8, prop-types@npm:^15.7.2, prop-types@npm:^15.8.0, prop-types@npm:^15.8.1": +"prop-types@npm:^15.5.6, prop-types@npm:^15.5.7, prop-types@npm:^15.5.8, prop-types@npm:^15.7.2, prop-types@npm:^15.8.0, prop-types@npm:^15.8.1": version: 15.8.1 resolution: "prop-types@npm:15.8.1" dependencies: @@ -17889,15 +17851,6 @@ __metadata: languageName: node linkType: hard -"react-native-fit-image@npm:^1.5.5": - version: 1.5.5 - resolution: "react-native-fit-image@npm:1.5.5" - dependencies: - prop-types: ^15.5.10 - checksum: 2f3ce06b43191efe3a4bd0698a4117342d821455c96433e9561d50f156ee446d2c449bf9e0908f9a8b7bbb8f95c02478d4fca8d8d103f38b6ce3b8b75557af15 - languageName: node - linkType: hard - "react-native-fs@npm:^2.20.0": version: 2.20.0 resolution: "react-native-fs@npm:2.20.0" @@ -18000,21 +17953,6 @@ __metadata: languageName: node linkType: hard -"react-native-markdown-display@npm:^7.0.2": - version: 7.0.2 - resolution: "react-native-markdown-display@npm:7.0.2" - dependencies: - css-to-react-native: ^3.0.0 - markdown-it: ^10.0.0 - prop-types: ^15.7.2 - react-native-fit-image: ^1.5.5 - peerDependencies: - react: ">=16.2.0" - react-native: ">=0.50.4" - checksum: 69c64f2d21f4ebf001c71d6dff31ef5e13946a0b46b2d2f2830efa89d648afbd8c336be8552264b42d693446e13a3f81fdbfdd5aa9b20878441acabd59f0a54e - languageName: node - linkType: hard - "react-native-paper@npm:^4.12.5": version: 4.12.8 resolution: "react-native-paper@npm:4.12.8" @@ -20479,7 +20417,6 @@ __metadata: cross-env: ^7.0.3 crypto-browserify: ^3.12.0 crypto-js: ^4.2.0 - css-to-react-native: ^3.2.0 cypress: ^13.9.0 depcheck: ^1.4.7 dotenv: ^16.3.1 @@ -20532,7 +20469,6 @@ __metadata: pluralize: ^8.0.0 postinstall-postinstall: ^2.1.0 prettier: ^3.0.3 - prop-types: ^15.8.1 protobufjs: ^7.2.5 react: 18.2.0 react-dom: 18.2.0 @@ -20545,7 +20481,6 @@ __metadata: react-native-confetti-cannon: ^1.5.2 react-native-countdown-component: ^2.7.1 react-native-drax: ^0.10.2 - react-native-fit-image: ^1.5.5 react-native-fs: ^2.20.0 react-native-gesture-handler: ~2.14.0 react-native-get-random-values: ~1.8.0 @@ -20554,7 +20489,6 @@ __metadata: react-native-image-picker: ^7.1.0 react-native-keyboard-aware-scrollview: ^2.1.0 react-native-leaflet-view: ^0.1.2 - react-native-markdown-display: ^7.0.2 react-native-paper: ^4.12.5 react-native-pell-rich-editor: ^1.8.8 react-native-pie-chart: ^3.0.1 @@ -21138,13 +21072,6 @@ __metadata: languageName: node linkType: hard -"uc.micro@npm:^1.0.1, uc.micro@npm:^1.0.5": - version: 1.0.6 - resolution: "uc.micro@npm:1.0.6" - checksum: 6898bb556319a38e9cf175e3628689347bd26fec15fc6b29fa38e0045af63075ff3fea4cf1fdba9db46c9f0cbf07f2348cd8844889dd31ebd288c29fe0d27e7a - languageName: node - linkType: hard - "uc.micro@npm:^2.0.0, uc.micro@npm:^2.1.0": version: 2.1.0 resolution: "uc.micro@npm:2.1.0" From 6408c7a493e980694a7be4085220b14247f9176a Mon Sep 17 00:00:00 2001 From: WaDadidou Date: Wed, 11 Dec 2024 20:04:56 -0500 Subject: [PATCH 08/22] fix(article): fix CI --- .../navigation/getNormalModeScreens.tsx | 2 +- .../FeedNewArticle/FeedNewArticleScreen.tsx | 18 +++++++++++------- .../components/ArticleContentEditor/utils.ts | 2 +- .../components/FeedPostArticleMarkdownView.tsx | 2 +- 4 files changed, 14 insertions(+), 10 deletions(-) diff --git a/packages/components/navigation/getNormalModeScreens.tsx b/packages/components/navigation/getNormalModeScreens.tsx index d19e323427..c7145bb147 100644 --- a/packages/components/navigation/getNormalModeScreens.tsx +++ b/packages/components/navigation/getNormalModeScreens.tsx @@ -9,7 +9,7 @@ import { CoreDAOScreen } from "@/screens/CoreDAO/CoreDAOScreen"; import { DAppStoreScreen } from "@/screens/DAppStore/DAppStoreScreen"; import { ToriPunks } from "@/screens/DAppStore/apps/toripunks/HomeScreen"; import { FeedScreen } from "@/screens/Feed/FeedScreen"; -import { FeedNewArticleScreen } from "@/screens/FeedNewArticle/FeedNewArticleScreen.tsx"; +import { FeedNewArticleScreen } from "@/screens/FeedNewArticle/FeedNewArticleScreen"; import { FeedPostViewScreen } from "@/screens/FeedPostView/FeedPostViewScreen"; import { GovernanceProposalScreen } from "@/screens/Governance/GovernanceProposal/GovernanceProposalScreen"; import { GovernanceScreen } from "@/screens/Governance/GovernanceScreen"; diff --git a/packages/screens/FeedNewArticle/FeedNewArticleScreen.tsx b/packages/screens/FeedNewArticle/FeedNewArticleScreen.tsx index b76d78998f..8abfa7e6fe 100644 --- a/packages/screens/FeedNewArticle/FeedNewArticleScreen.tsx +++ b/packages/screens/FeedNewArticle/FeedNewArticleScreen.tsx @@ -33,6 +33,7 @@ import { ArticleContentEditor } from "@/screens/FeedNewArticle/components/Articl import { NewArticleLocationButton } from "@/screens/FeedNewArticle/components/NewArticleLocationButton"; import { selectNFTStorageAPI } from "@/store/slices/settings"; import { feedPostingStep, FeedPostingStepId } from "@/utils/feed/posting"; +import { generateArticleMetadata } from "@/utils/feed/queries"; import { generateIpfsKey } from "@/utils/ipfs"; import { IMAGE_MIME_TYPES } from "@/utils/mime"; import { ScreenFC, useAppNavigation } from "@/utils/navigation"; @@ -129,6 +130,8 @@ export const FeedNewArticleScreen: ScreenFC<"FeedNewArticle"> = () => { mentions: [], }; + const navigateBack = () => navigation.navigate("Feed"); + const onPublish = async () => { const action = "Publish an Article"; if (!wallet?.address || !wallet.connected) { @@ -177,15 +180,16 @@ export const FeedNewArticleScreen: ScreenFC<"FeedNewArticle"> = () => { )[0] : undefined; - const metadata: SocialFeedArticleMetadata = { + const metadata = generateArticleMetadata({ + ...formValues, thumbnailImage: remoteThumbnail, - title: formValues.title, - shortDescription: formValues.shortDescription || "", + gifs: [], + files: [], + mentions: [], + hashtags: [], message: formValues.message, location, - hashtags: [], - mentions: [], - }; + }); await makePost(JSON.stringify(metadata)); } catch (err) { @@ -208,7 +212,7 @@ export const FeedNewArticleScreen: ScreenFC<"FeedNewArticle"> = () => { mobileTitle="NEW ARTICLE" fullWidth headerChildren={New Article} - onBackPress={() => navigation.navigate("Feed")} + onBackPress={navigateBack} footerChildren noMargin noScroll diff --git a/packages/screens/FeedNewArticle/components/ArticleContentEditor/utils.ts b/packages/screens/FeedNewArticle/components/ArticleContentEditor/utils.ts index 711094efa6..7030d60387 100644 --- a/packages/screens/FeedNewArticle/components/ArticleContentEditor/utils.ts +++ b/packages/screens/FeedNewArticle/components/ArticleContentEditor/utils.ts @@ -1,4 +1,4 @@ -import type { MixedStyleRecord } from "@native-html/transient-render-engine"; +import { MixedStyleRecord } from "react-native-render-html"; import { neutral17, diff --git a/packages/screens/FeedPostView/components/FeedPostArticleMarkdownView.tsx b/packages/screens/FeedPostView/components/FeedPostArticleMarkdownView.tsx index 328077b97d..56cc6fc7e3 100644 --- a/packages/screens/FeedPostView/components/FeedPostArticleMarkdownView.tsx +++ b/packages/screens/FeedPostView/components/FeedPostArticleMarkdownView.tsx @@ -163,7 +163,7 @@ export const FeedPostArticleMarkdownView: FC<{ isNextPageAvailable.value = hasNextPage; }, [hasNextPage, isNextPageAvailable]); - if (!articleMetadata) return null; + if (!articleMetadata || !message) return null; return ( Date: Thu, 12 Dec 2024 13:19:45 -0500 Subject: [PATCH 09/22] fix(article): ArticleMarkdown md consultation, fix also ArticleMapPost title numberOfLines --- .../socialFeed/Map/MapPosts/ArticleMapPost.tsx | 4 +++- .../ArticleContentEditor/ArticleContentEditor.tsx | 8 ++------ .../components/ArticleContentEditor/utils.ts | 10 ++++++++++ .../components/FeedPostArticleMarkdownView.tsx | 11 ++++++++--- 4 files changed, 23 insertions(+), 10 deletions(-) diff --git a/packages/components/socialFeed/Map/MapPosts/ArticleMapPost.tsx b/packages/components/socialFeed/Map/MapPosts/ArticleMapPost.tsx index 0349acc156..161be7549e 100644 --- a/packages/components/socialFeed/Map/MapPosts/ArticleMapPost.tsx +++ b/packages/components/socialFeed/Map/MapPosts/ArticleMapPost.tsx @@ -48,7 +48,9 @@ export const ArticleMapPost: FC<{ return ( - {title} + + {title} + diff --git a/packages/screens/FeedNewArticle/components/ArticleContentEditor/ArticleContentEditor.tsx b/packages/screens/FeedNewArticle/components/ArticleContentEditor/ArticleContentEditor.tsx index 57cb79478d..79491e17f6 100644 --- a/packages/screens/FeedNewArticle/components/ArticleContentEditor/ArticleContentEditor.tsx +++ b/packages/screens/FeedNewArticle/components/ArticleContentEditor/ArticleContentEditor.tsx @@ -1,5 +1,3 @@ -import markdownit from "markdown-it"; -import { full as emoji } from "markdown-it-emoji"; import { FC, useRef, useState } from "react"; import { Controller, useFormContext } from "react-hook-form"; import { @@ -18,6 +16,7 @@ import { useMaxResolution } from "@/hooks/useMaxResolution"; import { Toolbar } from "@/screens/FeedNewArticle/components/ArticleContentEditor/Toolbar/Toolbar"; import { ContentMode, + initializeArticleMd, markdownTagStyles, } from "@/screens/FeedNewArticle/components/ArticleContentEditor/utils"; import { ARTICLE_MAX_WIDTH } from "@/utils/social-feed"; @@ -51,10 +50,7 @@ export const ArticleContentEditor: FC = ({ width }) => { const message = watch("message"); // ========== Markdown - const md = markdownit({ - linkify: true, - breaks: true, - }).use(emoji); + const md = initializeArticleMd(); const html = md.render(message); // ========== JSX diff --git a/packages/screens/FeedNewArticle/components/ArticleContentEditor/utils.ts b/packages/screens/FeedNewArticle/components/ArticleContentEditor/utils.ts index 7030d60387..7a10e50aa7 100644 --- a/packages/screens/FeedNewArticle/components/ArticleContentEditor/utils.ts +++ b/packages/screens/FeedNewArticle/components/ArticleContentEditor/utils.ts @@ -1,3 +1,5 @@ +import markdownit from "markdown-it"; +import { full as emoji } from "markdown-it-emoji/dist/index.cjs"; import { MixedStyleRecord } from "react-native-render-html"; import { @@ -108,3 +110,11 @@ export const markdownTagStyles: MixedStyleRecord = { fontWeight: "500", }, }; + +// Used to get the same parameters at Article creation and consultation +export const initializeArticleMd = () => { + return markdownit({ + linkify: true, + breaks: true, + }).use(emoji); +}; diff --git a/packages/screens/FeedPostView/components/FeedPostArticleMarkdownView.tsx b/packages/screens/FeedPostView/components/FeedPostArticleMarkdownView.tsx index 56cc6fc7e3..654a5dbfd4 100644 --- a/packages/screens/FeedPostView/components/FeedPostArticleMarkdownView.tsx +++ b/packages/screens/FeedPostView/components/FeedPostArticleMarkdownView.tsx @@ -31,7 +31,10 @@ import { useIsMobile } from "@/hooks/useIsMobile"; import { useMaxResolution } from "@/hooks/useMaxResolution"; import { useNSUserInfo } from "@/hooks/useNSUserInfo"; import { parseUserId } from "@/networks"; -import { markdownTagStyles } from "@/screens/FeedNewArticle/components/ArticleContentEditor/utils"; +import { + initializeArticleMd, + markdownTagStyles, +} from "@/screens/FeedNewArticle/components/ArticleContentEditor/utils"; import { zodTryParseJSON } from "@/utils/sanitize"; import { ARTICLE_MAX_WIDTH, @@ -99,7 +102,9 @@ export const FeedPostArticleMarkdownView: FC<{ ZodSocialFeedArticleMetadata, post.metadata, ); + const md = initializeArticleMd(); const message = articleMetadata?.message; + const html = message ? md.render(message) : null; const title = articleMetadata?.title; const location = articleMetadata?.location; @@ -163,7 +168,7 @@ export const FeedPostArticleMarkdownView: FC<{ isNextPageAvailable.value = hasNextPage; }, [hasNextPage, isNextPageAvailable]); - if (!articleMetadata || !message) return null; + if (!articleMetadata || !html) return null; return ( From 015b7de126abd35df2ca3220799e97aac4970660 Mon Sep 17 00:00:00 2001 From: WaDadidou Date: Mon, 16 Dec 2024 17:35:21 -0500 Subject: [PATCH 10/22] fix(article-md): Instantiate once markdownit --- .../ArticleContentEditor/ArticleContentEditor.tsx | 5 ++--- .../ArticleContentEditor/Toolbar/Toolbar.tsx | 2 +- .../components/FeedPostArticleMarkdownView.tsx | 6 +----- .../utils.ts => utils/feed/markdown.ts} | 12 +++++------- 4 files changed, 9 insertions(+), 16 deletions(-) rename packages/{screens/FeedNewArticle/components/ArticleContentEditor/utils.ts => utils/feed/markdown.ts} (92%) diff --git a/packages/screens/FeedNewArticle/components/ArticleContentEditor/ArticleContentEditor.tsx b/packages/screens/FeedNewArticle/components/ArticleContentEditor/ArticleContentEditor.tsx index 79491e17f6..fbd89072d5 100644 --- a/packages/screens/FeedNewArticle/components/ArticleContentEditor/ArticleContentEditor.tsx +++ b/packages/screens/FeedNewArticle/components/ArticleContentEditor/ArticleContentEditor.tsx @@ -16,9 +16,9 @@ import { useMaxResolution } from "@/hooks/useMaxResolution"; import { Toolbar } from "@/screens/FeedNewArticle/components/ArticleContentEditor/Toolbar/Toolbar"; import { ContentMode, - initializeArticleMd, markdownTagStyles, -} from "@/screens/FeedNewArticle/components/ArticleContentEditor/utils"; + articleMd as md, +} from "@/utils/feed/markdown"; import { ARTICLE_MAX_WIDTH } from "@/utils/social-feed"; import { neutral00, neutralA3, neutralFF } from "@/utils/style/colors"; import { layout, RESPONSIVE_BREAKPOINT_S } from "@/utils/style/layout"; @@ -50,7 +50,6 @@ export const ArticleContentEditor: FC = ({ width }) => { const message = watch("message"); // ========== Markdown - const md = initializeArticleMd(); const html = md.render(message); // ========== JSX diff --git a/packages/screens/FeedNewArticle/components/ArticleContentEditor/Toolbar/Toolbar.tsx b/packages/screens/FeedNewArticle/components/ArticleContentEditor/Toolbar/Toolbar.tsx index c44a84d879..96e765fd26 100644 --- a/packages/screens/FeedNewArticle/components/ArticleContentEditor/Toolbar/Toolbar.tsx +++ b/packages/screens/FeedNewArticle/components/ArticleContentEditor/Toolbar/Toolbar.tsx @@ -4,7 +4,7 @@ import { View } from "react-native"; import { BrandText } from "@/components/BrandText"; import { CustomPressable } from "@/components/buttons/CustomPressable"; import { SpacerRow } from "@/components/spacer"; -import { ContentMode } from "@/screens/FeedNewArticle/components/ArticleContentEditor/utils"; +import { ContentMode } from "@/utils/feed/markdown"; import { neutral1A } from "@/utils/style/colors"; import { fontSemibold12 } from "@/utils/style/fonts"; import { layout } from "@/utils/style/layout"; diff --git a/packages/screens/FeedPostView/components/FeedPostArticleMarkdownView.tsx b/packages/screens/FeedPostView/components/FeedPostArticleMarkdownView.tsx index 654a5dbfd4..6566a3d2b5 100644 --- a/packages/screens/FeedPostView/components/FeedPostArticleMarkdownView.tsx +++ b/packages/screens/FeedPostView/components/FeedPostArticleMarkdownView.tsx @@ -31,10 +31,7 @@ import { useIsMobile } from "@/hooks/useIsMobile"; import { useMaxResolution } from "@/hooks/useMaxResolution"; import { useNSUserInfo } from "@/hooks/useNSUserInfo"; import { parseUserId } from "@/networks"; -import { - initializeArticleMd, - markdownTagStyles, -} from "@/screens/FeedNewArticle/components/ArticleContentEditor/utils"; +import { markdownTagStyles, articleMd as md } from "@/utils/feed/markdown"; import { zodTryParseJSON } from "@/utils/sanitize"; import { ARTICLE_MAX_WIDTH, @@ -102,7 +99,6 @@ export const FeedPostArticleMarkdownView: FC<{ ZodSocialFeedArticleMetadata, post.metadata, ); - const md = initializeArticleMd(); const message = articleMetadata?.message; const html = message ? md.render(message) : null; const title = articleMetadata?.title; diff --git a/packages/screens/FeedNewArticle/components/ArticleContentEditor/utils.ts b/packages/utils/feed/markdown.ts similarity index 92% rename from packages/screens/FeedNewArticle/components/ArticleContentEditor/utils.ts rename to packages/utils/feed/markdown.ts index 7a10e50aa7..5c6db6868e 100644 --- a/packages/screens/FeedNewArticle/components/ArticleContentEditor/utils.ts +++ b/packages/utils/feed/markdown.ts @@ -111,10 +111,8 @@ export const markdownTagStyles: MixedStyleRecord = { }, }; -// Used to get the same parameters at Article creation and consultation -export const initializeArticleMd = () => { - return markdownit({ - linkify: true, - breaks: true, - }).use(emoji); -}; +// The markdownit instance. Used to get the same parameters at Article creation and consultation. +export const articleMd = markdownit({ + linkify: true, + breaks: true, +}).use(emoji); From 9c4bf232a5186e5330be6eec6b20c7116010deb2 Mon Sep 17 00:00:00 2001 From: WaDadidou Date: Mon, 16 Dec 2024 17:59:11 -0500 Subject: [PATCH 11/22] fix(article-md): Use new cpt ScreenTitle --- packages/screens/FeedNewArticle/FeedNewArticleScreen.tsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/screens/FeedNewArticle/FeedNewArticleScreen.tsx b/packages/screens/FeedNewArticle/FeedNewArticleScreen.tsx index 8abfa7e6fe..fb5ad27ec1 100644 --- a/packages/screens/FeedNewArticle/FeedNewArticleScreen.tsx +++ b/packages/screens/FeedNewArticle/FeedNewArticleScreen.tsx @@ -1,5 +1,5 @@ import pluralize from "pluralize"; -import { useRef, useState } from "react"; +import React, { useRef, useState } from "react"; import { FormProvider, useForm } from "react-hook-form"; import { ScrollView, useWindowDimensions, View } from "react-native"; import { useSelector } from "react-redux"; @@ -11,6 +11,7 @@ import priceSVG from "@/assets/icons/price.svg"; import { BrandText } from "@/components/BrandText"; import { SVG } from "@/components/SVG"; import { ScreenContainer } from "@/components/ScreenContainer"; +import { ScreenTitle } from "@/components/ScreenContainer/ScreenTitle"; import { WalletStatusBox } from "@/components/WalletStatusBox"; import { TertiaryBox } from "@/components/boxes/TertiaryBox"; import { CustomPressable } from "@/components/buttons/CustomPressable"; @@ -45,7 +46,7 @@ import { neutralFF, secondaryColor, } from "@/utils/style/colors"; -import { fontSemibold13, fontSemibold20 } from "@/utils/style/fonts"; +import { fontSemibold13 } from "@/utils/style/fonts"; import { layout, RESPONSIVE_BREAKPOINT_S, @@ -211,7 +212,7 @@ export const FeedNewArticleScreen: ScreenFC<"FeedNewArticle"> = () => { responsive mobileTitle="NEW ARTICLE" fullWidth - headerChildren={New Article} + headerChildren={New Article} onBackPress={navigateBack} footerChildren noMargin From 9c42f452b7854b855dffac21b1c61a3212b74a0e Mon Sep 17 00:00:00 2001 From: WaDadidou Date: Wed, 18 Dec 2024 12:08:09 -0500 Subject: [PATCH 12/22] fix(articles): Style ul, style mode buttons, remove ipfs url hack --- assets/icons/eye.svg | 5 ++ assets/icons/splitted-square.svg | 7 ++ .../cards/SocialArticleMarkdownCard.tsx | 6 +- .../ArticleContentEditor.tsx | 2 +- .../Toolbar/ModeButtons.tsx | 74 +++++++++++++++++++ .../ArticleContentEditor/Toolbar/Toolbar.tsx | 56 +++----------- packages/utils/feed/markdown.ts | 9 +++ 7 files changed, 108 insertions(+), 51 deletions(-) create mode 100644 assets/icons/eye.svg create mode 100644 assets/icons/splitted-square.svg create mode 100644 packages/screens/FeedNewArticle/components/ArticleContentEditor/Toolbar/ModeButtons.tsx diff --git a/assets/icons/eye.svg b/assets/icons/eye.svg new file mode 100644 index 0000000000..6afc7667c6 --- /dev/null +++ b/assets/icons/eye.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/icons/splitted-square.svg b/assets/icons/splitted-square.svg new file mode 100644 index 0000000000..6634f661d9 --- /dev/null +++ b/assets/icons/splitted-square.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/packages/components/socialFeed/SocialCard/cards/SocialArticleMarkdownCard.tsx b/packages/components/socialFeed/SocialCard/cards/SocialArticleMarkdownCard.tsx index afab9d2fe2..cb19f2f43d 100644 --- a/packages/components/socialFeed/SocialCard/cards/SocialArticleMarkdownCard.tsx +++ b/packages/components/socialFeed/SocialCard/cards/SocialArticleMarkdownCard.tsx @@ -68,11 +68,7 @@ export const SocialArticleMarkdownCard: FC<{ setLocalPost(post); }, [post]); - const thumbnailURI = thumbnailImage?.url - ? thumbnailImage.url.includes("://") - ? thumbnailImage.url - : "ipfs://" + thumbnailImage.url // we need this hack because ipfs "urls" in feed are raw CIDs - : defaultThumbnailImage; + const thumbnailURI = thumbnailImage?.url || defaultThumbnailImage; return ( = ({ width }) => { }} > {/* ==== Toolbar */} - + {/* ==== Edition and preview */} diff --git a/packages/screens/FeedNewArticle/components/ArticleContentEditor/Toolbar/ModeButtons.tsx b/packages/screens/FeedNewArticle/components/ArticleContentEditor/Toolbar/ModeButtons.tsx new file mode 100644 index 0000000000..afaa209e60 --- /dev/null +++ b/packages/screens/FeedNewArticle/components/ArticleContentEditor/Toolbar/ModeButtons.tsx @@ -0,0 +1,74 @@ +import { Dispatch, FC, SetStateAction, useState } from "react"; + +import eyeSVG from "@/assets/icons/eye.svg"; +import penSVG from "@/assets/icons/pen.svg"; +import splittedSquareSVG from "@/assets/icons/splitted-square.svg"; +import { SVG } from "@/components/SVG"; +import { CustomPressable } from "@/components/buttons/CustomPressable"; +import { SpacerRow } from "@/components/spacer"; +import { ContentMode } from "@/utils/feed/markdown"; +import { neutral11, neutral33, neutralFF } from "@/utils/style/colors"; +import { layout } from "@/utils/style/layout"; + +interface Props { + setMode: Dispatch>; + mode: ContentMode; +} + +export const ModeButtons: FC = ({ setMode, mode }) => { + const [hoveredButton, setHoveredButton] = useState(null); + + return ( + <> + setMode("EDITION")} + style={{ + backgroundColor: mode === "EDITION" ? neutral33 : neutral11, + padding: layout.spacing_x0_5, + borderRadius: 6, + borderWidth: 1, + borderColor: hoveredButton === "EDITION" ? neutralFF : neutral11, + }} + onHoverIn={() => setHoveredButton("EDITION")} + onHoverOut={() => setHoveredButton(null)} + > + + + + setMode("BOTH")} + style={{ + backgroundColor: mode === "BOTH" ? neutral33 : neutral11, + padding: layout.spacing_x0_5, + borderRadius: 6, + borderWidth: 1, + borderColor: hoveredButton === "BOTH" ? neutralFF : neutral11, + }} + onHoverIn={() => setHoveredButton("BOTH")} + onHoverOut={() => setHoveredButton(null)} + > + + + + setMode("PREVIEW")} + style={{ + backgroundColor: mode === "PREVIEW" ? neutral33 : neutral11, + padding: layout.spacing_x0_5, + borderRadius: 6, + borderWidth: 1, + borderColor: hoveredButton === "PREVIEW" ? neutralFF : neutral11, + }} + onHoverIn={() => setHoveredButton("PREVIEW")} + onHoverOut={() => setHoveredButton(null)} + > + + + + ); +}; diff --git a/packages/screens/FeedNewArticle/components/ArticleContentEditor/Toolbar/Toolbar.tsx b/packages/screens/FeedNewArticle/components/ArticleContentEditor/Toolbar/Toolbar.tsx index 96e765fd26..fd13ac7245 100644 --- a/packages/screens/FeedNewArticle/components/ArticleContentEditor/Toolbar/Toolbar.tsx +++ b/packages/screens/FeedNewArticle/components/ArticleContentEditor/Toolbar/Toolbar.tsx @@ -1,61 +1,27 @@ import { Dispatch, FC, SetStateAction } from "react"; -import { View } from "react-native"; -import { BrandText } from "@/components/BrandText"; -import { CustomPressable } from "@/components/buttons/CustomPressable"; -import { SpacerRow } from "@/components/spacer"; +import { TertiaryBox } from "@/components/boxes/TertiaryBox"; +import { ModeButtons } from "@/screens/FeedNewArticle/components/ArticleContentEditor/Toolbar/ModeButtons"; import { ContentMode } from "@/utils/feed/markdown"; -import { neutral1A } from "@/utils/style/colors"; -import { fontSemibold12 } from "@/utils/style/fonts"; +import { neutral11 } from "@/utils/style/colors"; import { layout } from "@/utils/style/layout"; interface Props { setMode: Dispatch>; + mode: ContentMode; } -export const Toolbar: FC = ({ setMode }) => { +export const Toolbar: FC = ({ setMode, mode }) => { return ( - - - setMode("EDITION")} - style={{ - backgroundColor: neutral1A, - padding: layout.spacing_x1, - borderRadius: 8, - }} - > - Edition - - - setMode("BOTH")} - style={{ - backgroundColor: neutral1A, - padding: layout.spacing_x1, - borderRadius: 8, - }} - > - Edition | Preview - - - setMode("PREVIEW")} - style={{ - backgroundColor: neutral1A, - padding: layout.spacing_x1, - borderRadius: 8, - }} - > - Preview - - - + + ); }; diff --git a/packages/utils/feed/markdown.ts b/packages/utils/feed/markdown.ts index 5c6db6868e..f353a5c306 100644 --- a/packages/utils/feed/markdown.ts +++ b/packages/utils/feed/markdown.ts @@ -29,6 +29,15 @@ export const markdownTagStyles: MixedStyleRecord = { fontFamily: "Exo_500Medium", fontWeight: "500", }, + ul: { + marginVertical: 4, + color: neutralA3, + fontSize: 14, + letterSpacing: -(14 * 0.04), + lineHeight: 20, + fontFamily: "Exo_500Medium", + fontWeight: "500", + }, code: { color: neutralA3, fontSize: 13, From 1eff1788b70706bc253fdabe743677bc26d5ce48 Mon Sep 17 00:00:00 2001 From: WaDadidou Date: Wed, 18 Dec 2024 12:18:37 -0500 Subject: [PATCH 13/22] fix(articles): Show border on article content input --- .../ArticleContentEditor/ArticleContentEditor.tsx | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/screens/FeedNewArticle/components/ArticleContentEditor/ArticleContentEditor.tsx b/packages/screens/FeedNewArticle/components/ArticleContentEditor/ArticleContentEditor.tsx index 2ca5cc2941..931749db0c 100644 --- a/packages/screens/FeedNewArticle/components/ArticleContentEditor/ArticleContentEditor.tsx +++ b/packages/screens/FeedNewArticle/components/ArticleContentEditor/ArticleContentEditor.tsx @@ -20,7 +20,12 @@ import { articleMd as md, } from "@/utils/feed/markdown"; import { ARTICLE_MAX_WIDTH } from "@/utils/social-feed"; -import { neutral00, neutralA3, neutralFF } from "@/utils/style/colors"; +import { + neutral00, + neutral33, + neutralA3, + neutralFF, +} from "@/utils/style/colors"; import { layout, RESPONSIVE_BREAKPOINT_S } from "@/utils/style/layout"; import { NewArticleFormValues } from "@/utils/types/feed"; @@ -110,7 +115,7 @@ export const ArticleContentEditor: FC = ({ width }) => { }, windowWidth >= RESPONSIVE_BREAKPOINT_S && { borderWidth, - borderColor: isTextInputHovered ? neutralFF : neutral00, + borderColor: isTextInputHovered ? neutralFF : neutral33, }, ]} > From abae21592ccd40f2945c7fb421c9d196eabaa007 Mon Sep 17 00:00:00 2001 From: WaDadidou Date: Wed, 18 Dec 2024 12:21:42 -0500 Subject: [PATCH 14/22] chore(articles): Remove useless variable --- .../socialFeed/SocialCard/cards/SocialArticleMarkdownCard.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/components/socialFeed/SocialCard/cards/SocialArticleMarkdownCard.tsx b/packages/components/socialFeed/SocialCard/cards/SocialArticleMarkdownCard.tsx index cb19f2f43d..24c0660269 100644 --- a/packages/components/socialFeed/SocialCard/cards/SocialArticleMarkdownCard.tsx +++ b/packages/components/socialFeed/SocialCard/cards/SocialArticleMarkdownCard.tsx @@ -68,8 +68,6 @@ export const SocialArticleMarkdownCard: FC<{ setLocalPost(post); }, [post]); - const thumbnailURI = thumbnailImage?.url || defaultThumbnailImage; - return ( Date: Wed, 18 Dec 2024 12:57:58 -0500 Subject: [PATCH 15/22] fix(articles): Few design modifs --- .../ArticleContentEditor.tsx | 56 +++++++++++-------- .../Toolbar/ModeButtons.tsx | 4 +- .../ArticleContentEditor/Toolbar/Toolbar.tsx | 4 +- 3 files changed, 38 insertions(+), 26 deletions(-) diff --git a/packages/screens/FeedNewArticle/components/ArticleContentEditor/ArticleContentEditor.tsx b/packages/screens/FeedNewArticle/components/ArticleContentEditor/ArticleContentEditor.tsx index 931749db0c..24098f0dc1 100644 --- a/packages/screens/FeedNewArticle/components/ArticleContentEditor/ArticleContentEditor.tsx +++ b/packages/screens/FeedNewArticle/components/ArticleContentEditor/ArticleContentEditor.tsx @@ -11,7 +11,6 @@ import RenderHtml from "react-native-render-html"; import { CustomPressable } from "@/components/buttons/CustomPressable"; import { Label } from "@/components/inputs/TextInputCustom"; -import { SpacerColumn } from "@/components/spacer"; import { useMaxResolution } from "@/hooks/useMaxResolution"; import { Toolbar } from "@/screens/FeedNewArticle/components/ArticleContentEditor/Toolbar/Toolbar"; import { @@ -40,15 +39,21 @@ export const ArticleContentEditor: FC = ({ width }) => { const textInputRef = useRef(null); const [isTextInputHovered, setTextInputHovered] = useState(false); const borderWidth = 1; - const textInputMinHeight = height - 142; - const editionAndPreviewHeight = height - 80; + const textInputContainerPadding = layout.spacing_x2 - borderWidth * 2; + const responsiveTextInputContainerPadding = + windowWidth < RESPONSIVE_BREAKPOINT_S ? 0 : textInputContainerPadding; + const toolbarWrapperHeight = 68; + const labelsWrappersHeight = 32; + const editionAndPreviewHeight = + height - toolbarWrapperHeight - textInputContainerPadding * 2; + const textInputMinHeight = + editionAndPreviewHeight - + labelsWrappersHeight - + responsiveTextInputContainerPadding * 2; + const [textInputHeight, setTextInputHeight] = useState(textInputMinHeight); const [mode, setMode] = useState("BOTH"); const [renderHtmlWidth, setRenderHtmlWidth] = useState(0); - const textInputContainerPadding = - windowWidth < RESPONSIVE_BREAKPOINT_S - ? 0 - : layout.spacing_x2 - borderWidth * 2; // ========== Form const { watch, control } = useFormContext(); @@ -66,8 +71,9 @@ export const ArticleContentEditor: FC = ({ width }) => { }} > {/* ==== Toolbar */} - - + + + {/* ==== Edition and preview */} = ({ width }) => { onHoverIn={() => setTextInputHovered(true)} onHoverOut={() => setTextInputHovered(false)} > - - + + + name="message" @@ -110,7 +117,7 @@ export const ArticleContentEditor: FC = ({ width }) => { style={[ { flex: 1, - padding: textInputContainerPadding, + padding: responsiveTextInputContainerPadding, borderRadius: 12, }, windowWidth >= RESPONSIVE_BREAKPOINT_S && { @@ -153,8 +160,9 @@ export const ArticleContentEditor: FC = ({ width }) => { flex: 1, }} > - - + + + = ({ width }) => { borderRadius: 12, borderWidth, borderColor: neutral00, - paddingBottom: textInputContainerPadding, - paddingHorizontal: textInputContainerPadding, - marginTop: textInputContainerPadding, + paddingBottom: responsiveTextInputContainerPadding, + paddingHorizontal: responsiveTextInputContainerPadding, + marginTop: responsiveTextInputContainerPadding, }} onLayout={(e) => setRenderHtmlWidth(e.nativeEvent.layout.width)} > diff --git a/packages/screens/FeedNewArticle/components/ArticleContentEditor/Toolbar/ModeButtons.tsx b/packages/screens/FeedNewArticle/components/ArticleContentEditor/Toolbar/ModeButtons.tsx index afaa209e60..75173aff34 100644 --- a/packages/screens/FeedNewArticle/components/ArticleContentEditor/Toolbar/ModeButtons.tsx +++ b/packages/screens/FeedNewArticle/components/ArticleContentEditor/Toolbar/ModeButtons.tsx @@ -34,7 +34,7 @@ export const ModeButtons: FC = ({ setMode, mode }) => { > - + setMode("BOTH")} style={{ @@ -54,7 +54,7 @@ export const ModeButtons: FC = ({ setMode, mode }) => { color={neutralFF} /> - + setMode("PREVIEW")} style={{ diff --git a/packages/screens/FeedNewArticle/components/ArticleContentEditor/Toolbar/Toolbar.tsx b/packages/screens/FeedNewArticle/components/ArticleContentEditor/Toolbar/Toolbar.tsx index fd13ac7245..ff9ea8c4c8 100644 --- a/packages/screens/FeedNewArticle/components/ArticleContentEditor/Toolbar/Toolbar.tsx +++ b/packages/screens/FeedNewArticle/components/ArticleContentEditor/Toolbar/Toolbar.tsx @@ -17,7 +17,9 @@ export const Toolbar: FC = ({ setMode, mode }) => { style={{ flexDirection: "row", alignSelf: "center", - padding: layout.spacing_x1, + height: 48, + alignItems: "center", + paddingHorizontal: layout.spacing_x1, backgroundColor: neutral11, }} > From 197841952400db75555bcb7abf874de7c90f4950 Mon Sep 17 00:00:00 2001 From: WaDadidou Date: Thu, 19 Dec 2024 21:13:50 -0500 Subject: [PATCH 16/22] chore: yarn lint-fix --- .../FeedNewArticle/FeedNewArticleScreen.tsx | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/packages/screens/FeedNewArticle/FeedNewArticleScreen.tsx b/packages/screens/FeedNewArticle/FeedNewArticleScreen.tsx index 7ef3745af7..7e0648a870 100644 --- a/packages/screens/FeedNewArticle/FeedNewArticleScreen.tsx +++ b/packages/screens/FeedNewArticle/FeedNewArticleScreen.tsx @@ -13,9 +13,9 @@ import { SVG } from "@/components/SVG"; import { ScreenContainer } from "@/components/ScreenContainer"; import { ScreenTitle } from "@/components/ScreenContainer/ScreenTitle"; import { TertiaryBox } from "@/components/boxes/TertiaryBox"; -import { DAOSelector } from "@/components/dao/DAOSelector"; import { CustomPressable } from "@/components/buttons/CustomPressable"; import { PrimaryButton } from "@/components/buttons/PrimaryButton"; +import { DAOSelector } from "@/components/dao/DAOSelector"; import { Label, TextInputCustom } from "@/components/inputs/TextInputCustom"; import { FileUploader } from "@/components/inputs/fileUploader"; import { FeedPostingProgressBar } from "@/components/loaders/FeedPostingProgressBar"; @@ -206,7 +206,7 @@ export const FeedNewArticleScreen: ScreenFC<"FeedNewArticle"> = () => { }); } }; - + // Reset DAOSelector when the user selects another wallet const [daoSelectorKey, setDaoSelectorKey] = useState(0); useEffect(() => { @@ -244,13 +244,13 @@ export const FeedNewArticleScreen: ScreenFC<"FeedNewArticle"> = () => { width, }} > - - + + = () => { Date: Fri, 20 Dec 2024 18:01:33 -0500 Subject: [PATCH 17/22] chore(lauchpad): Use ArticleMarkdown types --- .../cards/SocialArticleMarkdownCard.tsx | 4 ++-- .../FeedNewArticle/FeedNewArticleScreen.tsx | 8 ++++---- .../components/FeedPostArticleMarkdownView.tsx | 4 ++-- packages/utils/feed/queries.ts | 12 ++++++------ packages/utils/types/feed.ts | 16 ++++++++++++++-- 5 files changed, 28 insertions(+), 16 deletions(-) diff --git a/packages/components/socialFeed/SocialCard/cards/SocialArticleMarkdownCard.tsx b/packages/components/socialFeed/SocialCard/cards/SocialArticleMarkdownCard.tsx index 24c0660269..f0879fe584 100644 --- a/packages/components/socialFeed/SocialCard/cards/SocialArticleMarkdownCard.tsx +++ b/packages/components/socialFeed/SocialCard/cards/SocialArticleMarkdownCard.tsx @@ -31,7 +31,7 @@ import { RESPONSIVE_BREAKPOINT_S, SOCIAL_FEED_BREAKPOINT_M, } from "@/utils/style/layout"; -import { ZodSocialFeedArticleMetadata } from "@/utils/types/feed"; +import { ZodSocialFeedArticleMarkdownMetadata } from "@/utils/types/feed"; const ARTICLE_CARD_PADDING_VERTICAL = layout.spacing_x2; const ARTICLE_CARD_PADDING_HORIZONTAL = layout.spacing_x2_5; @@ -57,7 +57,7 @@ export const SocialArticleMarkdownCard: FC<{ windowWidth < RESPONSIVE_BREAKPOINT_S ? 0 : SOCIAl_CARD_BORDER_RADIUS; const metadata = zodTryParseJSON( - ZodSocialFeedArticleMetadata, + ZodSocialFeedArticleMarkdownMetadata, localPost.metadata, ); const thumbnailImage = metadata?.thumbnailImage; diff --git a/packages/screens/FeedNewArticle/FeedNewArticleScreen.tsx b/packages/screens/FeedNewArticle/FeedNewArticleScreen.tsx index 7e0648a870..46a7309ba0 100644 --- a/packages/screens/FeedNewArticle/FeedNewArticleScreen.tsx +++ b/packages/screens/FeedNewArticle/FeedNewArticleScreen.tsx @@ -34,7 +34,7 @@ import { ArticleContentEditor } from "@/screens/FeedNewArticle/components/Articl import { NewArticleLocationButton } from "@/screens/FeedNewArticle/components/NewArticleLocationButton"; import { selectNFTStorageAPI } from "@/store/slices/settings"; import { feedPostingStep, FeedPostingStepId } from "@/utils/feed/posting"; -import { generateArticleMetadata } from "@/utils/feed/queries"; +import { generateArticleMarkdownMetadata } from "@/utils/feed/queries"; import { generateIpfsKey } from "@/utils/ipfs"; import { IMAGE_MIME_TYPES } from "@/utils/mime"; import { ScreenFC, useAppNavigation } from "@/utils/navigation"; @@ -56,7 +56,7 @@ import { CustomLatLngExpression, NewArticleFormValues, PostCategory, - SocialFeedArticleMetadata, + SocialFeedArticleMarkdownMetadata, } from "@/utils/types/feed"; export const FeedNewArticleScreen: ScreenFC<"FeedNewArticle"> = () => { @@ -123,7 +123,7 @@ export const FeedNewArticleScreen: ScreenFC<"FeedNewArticle"> = () => { }); const formValues = newArticleForm.watch(); - const previewMetadata: SocialFeedArticleMetadata = { + const previewMetadata: SocialFeedArticleMarkdownMetadata = { title: formValues.title, shortDescription: formValues.shortDescription || "", thumbnailImage: formValues.thumbnailImage, @@ -182,7 +182,7 @@ export const FeedNewArticleScreen: ScreenFC<"FeedNewArticle"> = () => { )[0] : undefined; - const metadata = generateArticleMetadata({ + const metadata = generateArticleMarkdownMetadata({ ...formValues, thumbnailImage: remoteThumbnail, gifs: [], diff --git a/packages/screens/FeedPostView/components/FeedPostArticleMarkdownView.tsx b/packages/screens/FeedPostView/components/FeedPostArticleMarkdownView.tsx index 6566a3d2b5..2c4b59d722 100644 --- a/packages/screens/FeedPostView/components/FeedPostArticleMarkdownView.tsx +++ b/packages/screens/FeedPostView/components/FeedPostArticleMarkdownView.tsx @@ -50,7 +50,7 @@ import { OnPressReplyType, PostCategory, ReplyToType, - ZodSocialFeedArticleMetadata, + ZodSocialFeedArticleMarkdownMetadata, } from "@/utils/types/feed"; const contentPaddingHorizontal = layout.spacing_x2; @@ -96,7 +96,7 @@ export const FeedPostArticleMarkdownView: FC<{ const isNextPageAvailable = useSharedValue(hasNextPage); const articleMetadata = zodTryParseJSON( - ZodSocialFeedArticleMetadata, + ZodSocialFeedArticleMarkdownMetadata, post.metadata, ); const message = articleMetadata?.message; diff --git a/packages/utils/feed/queries.ts b/packages/utils/feed/queries.ts index b9f6897f61..b7e2499640 100644 --- a/packages/utils/feed/queries.ts +++ b/packages/utils/feed/queries.ts @@ -14,8 +14,8 @@ import { NewArticleFormValues, NewPostFormValues, PostCategory, - SocialFeedArticleMetadata, - SocialFeedPostMetadata, + SocialFeedArticleMarkdownMetadata, + SocialFeedPostMetadata, ZodSocialFeedArticleMarkdownMetadata, ZodSocialFeedArticleMetadata, ZodSocialFeedPostMetadata, } from "../types/feed"; @@ -112,7 +112,7 @@ interface GeneratePostMetadataParams extends Omit { location?: CustomLatLngExpression; } -interface GenerateArticleMetadataParams +interface GenerateArticleMarkdownMetadataParams extends Omit< NewArticleFormValues, "files" | "thumbnailImage" | "coverImage" @@ -147,7 +147,7 @@ export const generatePostMetadata = ({ return m; }; -export const generateArticleMetadata = ({ +export const generateArticleMarkdownMetadata = ({ title, message, files, @@ -159,8 +159,8 @@ export const generateArticleMetadata = ({ coverImage, shortDescription, location, -}: GenerateArticleMetadataParams): SocialFeedArticleMetadata => { - const m = ZodSocialFeedArticleMetadata.parse({ +}: GenerateArticleMarkdownMetadataParams): SocialFeedArticleMarkdownMetadata => { + const m = ZodSocialFeedArticleMarkdownMetadata.parse({ title, message, files, diff --git a/packages/utils/types/feed.ts b/packages/utils/types/feed.ts index 7a61b457ba..68c4a026d7 100644 --- a/packages/utils/types/feed.ts +++ b/packages/utils/types/feed.ts @@ -107,8 +107,20 @@ export const ZodSocialFeedArticleMetadata = z.object({ mentions: z.array(z.string()), ...zodSocialFeedCommonMetadata.shape, }); -export type SocialFeedArticleMetadata = z.infer< - typeof ZodSocialFeedArticleMetadata + +export const ZodSocialFeedArticleMarkdownMetadata = z.object({ + shortDescription: z.string(), + thumbnailImage: ZodRemoteFileData.optional(), + coverImage: ZodRemoteFileData.optional(), + message: z.string(), + files: MaybeFiles.optional(), + gifs: z.array(z.string()).optional(), + hashtags: z.array(z.string()), + mentions: z.array(z.string()), + ...zodSocialFeedCommonMetadata.shape, +}); +export type SocialFeedArticleMarkdownMetadata = z.infer< + typeof ZodSocialFeedArticleMarkdownMetadata >; export const ZodSocialFeedTrackMetadata = z.object({ From d85401b074d551f970a9bed21a8f752d473b0619 Mon Sep 17 00:00:00 2001 From: WaDadidou Date: Mon, 23 Dec 2024 15:18:09 -0500 Subject: [PATCH 18/22] fix(article-md): customize styles --- package.json | 2 + .../ArticleContentEditor.tsx | 4 +- .../Toolbar/ModeButtons.tsx | 20 ++- .../ArticleContentEditor/Toolbar/Toolbar.tsx | 6 +- packages/utils/feed/markdown.ts | 159 +++++++++++++----- packages/utils/feed/queries.ts | 4 +- yarn.lock | 20 ++- 7 files changed, 157 insertions(+), 58 deletions(-) diff --git a/package.json b/package.json index a6d2c94066..a59540cd71 100644 --- a/package.json +++ b/package.json @@ -77,6 +77,7 @@ "@types/leaflet": "^1.9.12", "@types/leaflet.markercluster": "^1.5.4", "@types/markdown-it-emoji": "^3.0.1", + "@types/markdown-it-footnote": "^3.0.4", "@types/papaparse": "^5.3.14", "@types/pluralize": "^0.0.33", "assert": "^2.1.0", @@ -123,6 +124,7 @@ "lottie-react-native": "6.5.1", "markdown-it": "^14.1.0", "markdown-it-emoji": "^3.0.0", + "markdown-it-footnote": "^4.0.0", "merkletreejs": "^0.4.0", "metamask-react": "^2.4.1", "moment": "^2.29.4", diff --git a/packages/screens/FeedNewArticle/components/ArticleContentEditor/ArticleContentEditor.tsx b/packages/screens/FeedNewArticle/components/ArticleContentEditor/ArticleContentEditor.tsx index 24098f0dc1..55fe6830a3 100644 --- a/packages/screens/FeedNewArticle/components/ArticleContentEditor/ArticleContentEditor.tsx +++ b/packages/screens/FeedNewArticle/components/ArticleContentEditor/ArticleContentEditor.tsx @@ -15,8 +15,8 @@ import { useMaxResolution } from "@/hooks/useMaxResolution"; import { Toolbar } from "@/screens/FeedNewArticle/components/ArticleContentEditor/Toolbar/Toolbar"; import { ContentMode, - markdownTagStyles, articleMd as md, + renderHtmlTagStyles, } from "@/utils/feed/markdown"; import { ARTICLE_MAX_WIDTH } from "@/utils/social-feed"; import { @@ -178,7 +178,7 @@ export const ArticleContentEditor: FC = ({ width }) => { > = ({ setMode, mode }) => { setMode("EDITION")} style={{ - backgroundColor: mode === "EDITION" ? neutral33 : neutral11, + backgroundColor: + mode === "EDITION" ? neutral33 : toolbarBackgroundColor, padding: layout.spacing_x0_5, borderRadius: 6, borderWidth: 1, - borderColor: hoveredButton === "EDITION" ? neutralFF : neutral11, + borderColor: + hoveredButton === "EDITION" ? neutralFF : toolbarBackgroundColor, }} onHoverIn={() => setHoveredButton("EDITION")} onHoverOut={() => setHoveredButton(null)} @@ -38,11 +41,12 @@ export const ModeButtons: FC = ({ setMode, mode }) => { setMode("BOTH")} style={{ - backgroundColor: mode === "BOTH" ? neutral33 : neutral11, + backgroundColor: mode === "BOTH" ? neutral33 : toolbarBackgroundColor, padding: layout.spacing_x0_5, borderRadius: 6, borderWidth: 1, - borderColor: hoveredButton === "BOTH" ? neutralFF : neutral11, + borderColor: + hoveredButton === "BOTH" ? neutralFF : toolbarBackgroundColor, }} onHoverIn={() => setHoveredButton("BOTH")} onHoverOut={() => setHoveredButton(null)} @@ -58,11 +62,13 @@ export const ModeButtons: FC = ({ setMode, mode }) => { setMode("PREVIEW")} style={{ - backgroundColor: mode === "PREVIEW" ? neutral33 : neutral11, + backgroundColor: + mode === "PREVIEW" ? neutral33 : toolbarBackgroundColor, padding: layout.spacing_x0_5, borderRadius: 6, borderWidth: 1, - borderColor: hoveredButton === "PREVIEW" ? neutralFF : neutral11, + borderColor: + hoveredButton === "PREVIEW" ? neutralFF : toolbarBackgroundColor, }} onHoverIn={() => setHoveredButton("PREVIEW")} onHoverOut={() => setHoveredButton(null)} diff --git a/packages/screens/FeedNewArticle/components/ArticleContentEditor/Toolbar/Toolbar.tsx b/packages/screens/FeedNewArticle/components/ArticleContentEditor/Toolbar/Toolbar.tsx index ff9ea8c4c8..60fd47933e 100644 --- a/packages/screens/FeedNewArticle/components/ArticleContentEditor/Toolbar/Toolbar.tsx +++ b/packages/screens/FeedNewArticle/components/ArticleContentEditor/Toolbar/Toolbar.tsx @@ -3,7 +3,7 @@ import { Dispatch, FC, SetStateAction } from "react"; import { TertiaryBox } from "@/components/boxes/TertiaryBox"; import { ModeButtons } from "@/screens/FeedNewArticle/components/ArticleContentEditor/Toolbar/ModeButtons"; import { ContentMode } from "@/utils/feed/markdown"; -import { neutral11 } from "@/utils/style/colors"; +import { neutral17 } from "@/utils/style/colors"; import { layout } from "@/utils/style/layout"; interface Props { @@ -11,6 +11,8 @@ interface Props { mode: ContentMode; } +export const toolbarBackgroundColor = neutral17; + export const Toolbar: FC = ({ setMode, mode }) => { return ( = ({ setMode, mode }) => { height: 48, alignItems: "center", paddingHorizontal: layout.spacing_x1, - backgroundColor: neutral11, + backgroundColor: toolbarBackgroundColor, }} > diff --git a/packages/utils/feed/markdown.ts b/packages/utils/feed/markdown.ts index f353a5c306..b9b0c70d61 100644 --- a/packages/utils/feed/markdown.ts +++ b/packages/utils/feed/markdown.ts @@ -1,9 +1,12 @@ import markdownit from "markdown-it"; import { full as emoji } from "markdown-it-emoji/dist/index.cjs"; +import footnote_plugin from "markdown-it-footnote"; import { MixedStyleRecord } from "react-native-render-html"; import { neutral17, + neutral33, + neutral67, neutralA3, neutralFF, primaryColor, @@ -11,7 +14,16 @@ import { export type ContentMode = "EDITION" | "BOTH" | "PREVIEW"; -export const markdownTagStyles: MixedStyleRecord = { +// The markdownit instance. Used to get the same parameters at Article creation and consultation. +export const articleMd = markdownit({ + linkify: true, + breaks: true, +}) + .use(emoji) + .use(footnote_plugin); + +// HTML tags styles used by RenderHtml +export const renderHtmlTagStyles: MixedStyleRecord = { body: { color: neutralA3, fontSize: 14, @@ -21,49 +33,15 @@ export const markdownTagStyles: MixedStyleRecord = { fontWeight: "500", }, p: { - marginVertical: 4, - color: neutralA3, + marginTop: 0, + marginBottom: 16, + fontSize: 14, letterSpacing: -(14 * 0.04), lineHeight: 22, fontFamily: "Exo_500Medium", fontWeight: "500", }, - ul: { - marginVertical: 4, - color: neutralA3, - fontSize: 14, - letterSpacing: -(14 * 0.04), - lineHeight: 20, - fontFamily: "Exo_500Medium", - fontWeight: "500", - }, - code: { - color: neutralA3, - fontSize: 13, - letterSpacing: -(13 * 0.04), - lineHeight: 22, - fontFamily: "Exo_500Medium", - fontWeight: "500", - - backgroundColor: neutral17, - marginVertical: 4, - paddingHorizontal: 4, - paddingVertical: 2, - borderRadius: 4, - alignSelf: "flex-start", - }, - pre: { - fontSize: 13, - letterSpacing: -(13 * 0.04), - fontFamily: "Exo_500Medium", - fontWeight: "500", - - backgroundColor: neutral17, - paddingHorizontal: 8, - paddingVertical: 8, - borderRadius: 4, - }, strong: { fontWeight: "700" }, a: { color: primaryColor, @@ -118,10 +96,103 @@ export const markdownTagStyles: MixedStyleRecord = { fontFamily: "Exo_500Medium", fontWeight: "500", }, -}; + ul: { + marginTop: 0, + marginBottom: 16, -// The markdownit instance. Used to get the same parameters at Article creation and consultation. -export const articleMd = markdownit({ - linkify: true, - breaks: true, -}).use(emoji); + color: neutralA3, + fontSize: 14, + letterSpacing: -(14 * 0.04), + lineHeight: 20, + fontFamily: "Exo_500Medium", + fontWeight: "500", + }, + ol: { + marginTop: 0, + marginBottom: 16, + + color: neutralA3, + fontSize: 14, + letterSpacing: -(14 * 0.04), + lineHeight: 20, + fontFamily: "Exo_500Medium", + fontWeight: "500", + }, + blockquote: { + marginTop: 0, + marginBottom: 16, + paddingBottom: -16, + + color: neutral67, + fontSize: 14, + letterSpacing: -(14 * 0.04), + lineHeight: 20, + fontFamily: "Exo_500Medium", + fontWeight: "500", + + marginLeft: 0, + paddingLeft: 14, + borderLeftWidth: 3, + borderLeftColor: neutral67, + }, + code: { + color: neutralA3, + fontSize: 13, + letterSpacing: -(13 * 0.04), + lineHeight: 22, + fontFamily: "Exo_500Medium", + fontWeight: "500", + + backgroundColor: neutral17, + marginVertical: 4, + paddingHorizontal: 4, + paddingVertical: 2, + borderRadius: 4, + alignSelf: "flex-start", + }, + pre: { + fontSize: 13, + letterSpacing: -(13 * 0.04), + fontFamily: "Exo_500Medium", + fontWeight: "500", + + backgroundColor: neutral17, + paddingHorizontal: 8, + borderRadius: 4, + + marginTop: 0, + marginBottom: 16, + }, + table: { + marginBottom: 16, + }, + thead: { + borderTopLeftRadius: 4, + borderTopRightRadius: 4, + borderColor: neutral33, + borderLeftWidth: 1, + borderTopWidth: 1, + borderRightWidth: 1, + backgroundColor: neutral17, + }, + th: { + borderColor: neutral33, + padding: 8, + }, + tbody: { + borderBottomLeftRadius: 4, + borderBottomRightRadius: 4, + borderColor: neutral33, + borderLeftWidth: 1, + borderBottomWidth: 1, + borderRightWidth: 1, + }, + tr: { + borderColor: neutral33, + }, + td: { + borderTopWidth: 0.5, + borderColor: neutral33, + padding: 8, + }, +}; diff --git a/packages/utils/feed/queries.ts b/packages/utils/feed/queries.ts index b7e2499640..feb41f91de 100644 --- a/packages/utils/feed/queries.ts +++ b/packages/utils/feed/queries.ts @@ -15,8 +15,8 @@ import { NewPostFormValues, PostCategory, SocialFeedArticleMarkdownMetadata, - SocialFeedPostMetadata, ZodSocialFeedArticleMarkdownMetadata, - ZodSocialFeedArticleMetadata, + SocialFeedPostMetadata, + ZodSocialFeedArticleMarkdownMetadata, ZodSocialFeedPostMetadata, } from "../types/feed"; import { RemoteFileData } from "../types/files"; diff --git a/yarn.lock b/yarn.lock index d74bd2521c..2f0530e1f2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6865,7 +6865,16 @@ __metadata: languageName: node linkType: hard -"@types/markdown-it@npm:^14, @types/markdown-it@npm:^14.1.2": +"@types/markdown-it-footnote@npm:^3.0.4": + version: 3.0.4 + resolution: "@types/markdown-it-footnote@npm:3.0.4" + dependencies: + "@types/markdown-it": "*" + checksum: 84d38790e1911eaf94bd3418a8782de1dd2543963f282849fe3fde7089f3ed6c3f5d07defd2ba51ad8d1cf3b32eeddfb21262e230bf8baddce5154f6735ed9d6 + languageName: node + linkType: hard + +"@types/markdown-it@npm:*, @types/markdown-it@npm:^14, @types/markdown-it@npm:^14.1.2": version: 14.1.2 resolution: "@types/markdown-it@npm:14.1.2" dependencies: @@ -15354,6 +15363,13 @@ __metadata: languageName: node linkType: hard +"markdown-it-footnote@npm:^4.0.0": + version: 4.0.0 + resolution: "markdown-it-footnote@npm:4.0.0" + checksum: 75543f8c81d7ba9620f5b2bc3fcbb5130ad7b4e3afbec19da3bdf3417dfb885582c66ccb0b39e3847bdf2876a110378095260477084fe0e7c29a887b4404401e + languageName: node + linkType: hard + "markdown-it@npm:^14.1.0": version: 14.1.0 resolution: "markdown-it@npm:14.1.0" @@ -20396,6 +20412,7 @@ __metadata: "@types/leaflet.markercluster": ^1.5.4 "@types/markdown-it": ^14.1.2 "@types/markdown-it-emoji": ^3.0.1 + "@types/markdown-it-footnote": ^3.0.4 "@types/node": ^20.9.1 "@types/papaparse": ^5.3.14 "@types/pluralize": ^0.0.33 @@ -20458,6 +20475,7 @@ __metadata: lottie-react-native: 6.5.1 markdown-it: ^14.1.0 markdown-it-emoji: ^3.0.0 + markdown-it-footnote: ^4.0.0 merkletreejs: ^0.4.0 metamask-react: ^2.4.1 moment: ^2.29.4 From e19e7a8fd5a44ed4f56a0cfb56f683a5f36289a8 Mon Sep 17 00:00:00 2001 From: WaDadidou Date: Mon, 23 Dec 2024 15:23:09 -0500 Subject: [PATCH 19/22] fix(article-md): fix import --- .../FeedPostView/components/FeedPostArticleMarkdownView.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/screens/FeedPostView/components/FeedPostArticleMarkdownView.tsx b/packages/screens/FeedPostView/components/FeedPostArticleMarkdownView.tsx index 2c4b59d722..140cc4c742 100644 --- a/packages/screens/FeedPostView/components/FeedPostArticleMarkdownView.tsx +++ b/packages/screens/FeedPostView/components/FeedPostArticleMarkdownView.tsx @@ -31,7 +31,7 @@ import { useIsMobile } from "@/hooks/useIsMobile"; import { useMaxResolution } from "@/hooks/useMaxResolution"; import { useNSUserInfo } from "@/hooks/useNSUserInfo"; import { parseUserId } from "@/networks"; -import { markdownTagStyles, articleMd as md } from "@/utils/feed/markdown"; +import { renderHtmlTagStyles, articleMd as md } from "@/utils/feed/markdown"; import { zodTryParseJSON } from "@/utils/sanitize"; import { ARTICLE_MAX_WIDTH, @@ -249,7 +249,7 @@ export const FeedPostArticleMarkdownView: FC<{ > From c2b53ba07aa77e0ec08dc3759078bb747650900e Mon Sep 17 00:00:00 2001 From: WaDadidou Date: Fri, 27 Dec 2024 15:55:05 -0500 Subject: [PATCH 20/22] fix(article-md): remove marginBottom from blockquote > p:first-child using domVisitors prop --- .../ArticleContentEditor.tsx | 2 + .../FeedPostArticleMarkdownView.tsx | 7 +++- packages/utils/feed/markdown.ts | 37 +++++++++++++++++-- 3 files changed, 41 insertions(+), 5 deletions(-) diff --git a/packages/screens/FeedNewArticle/components/ArticleContentEditor/ArticleContentEditor.tsx b/packages/screens/FeedNewArticle/components/ArticleContentEditor/ArticleContentEditor.tsx index 55fe6830a3..fcaab32ef4 100644 --- a/packages/screens/FeedNewArticle/components/ArticleContentEditor/ArticleContentEditor.tsx +++ b/packages/screens/FeedNewArticle/components/ArticleContentEditor/ArticleContentEditor.tsx @@ -17,6 +17,7 @@ import { ContentMode, articleMd as md, renderHtmlTagStyles, + renderHtmlDomVisitors, } from "@/utils/feed/markdown"; import { ARTICLE_MAX_WIDTH } from "@/utils/social-feed"; import { @@ -179,6 +180,7 @@ export const ArticleContentEditor: FC = ({ width }) => { diff --git a/packages/utils/feed/markdown.ts b/packages/utils/feed/markdown.ts index b9b0c70d61..770d8b4467 100644 --- a/packages/utils/feed/markdown.ts +++ b/packages/utils/feed/markdown.ts @@ -1,3 +1,4 @@ +import { Element as DomHandlerElement } from "domhandler"; import markdownit from "markdown-it"; import { full as emoji } from "markdown-it-emoji/dist/index.cjs"; import footnote_plugin from "markdown-it-footnote"; @@ -22,7 +23,28 @@ export const articleMd = markdownit({ .use(emoji) .use(footnote_plugin); -// HTML tags styles used by RenderHtml +// DOM modifications on document, texts, or elements from react-native-render-html. +// Because react-native-render-html doesn't allow common CSS selectors, we need to style tags using domVisitors callbacks +export const renderHtmlDomVisitors = { + onElement: (element: DomHandlerElement) => { + // Removing marginBottom from the child p of blockquote + if ( + element.name === "blockquote" && + element.children && + element.children.length > 0 + ) { + const tagChild = element.children.find((child) => child.type === "tag"); + // tagChild is a domhandler Node. It doesn't have attribs, but it has attribs in fact (wtf ?) + if (tagChild && "attribs" in tagChild) { + tagChild.attribs = { + style: "margin-bottom: 0", + }; + } + } + }, +}; + +// HTML tags styles used by RenderHtml from react-native-render-html export const renderHtmlTagStyles: MixedStyleRecord = { body: { color: neutralA3, @@ -34,7 +56,7 @@ export const renderHtmlTagStyles: MixedStyleRecord = { }, p: { marginTop: 0, - marginBottom: 16, + // marginBottom: 16, fontSize: 14, letterSpacing: -(14 * 0.04), @@ -118,10 +140,10 @@ export const renderHtmlTagStyles: MixedStyleRecord = { fontFamily: "Exo_500Medium", fontWeight: "500", }, + blockquote: { marginTop: 0, - marginBottom: 16, - paddingBottom: -16, + paddingBottom: 0, color: neutral67, fontSize: 14, @@ -135,6 +157,13 @@ export const renderHtmlTagStyles: MixedStyleRecord = { borderLeftWidth: 3, borderLeftColor: neutral67, }, + "ol > :first-child": { + backgroundColor: "red", + }, + "blockquote > :first-child": { + marginBottom: 0, + }, + code: { color: neutralA3, fontSize: 13, From a58719baa67622b90a9287cd2225b4cdc13176b8 Mon Sep 17 00:00:00 2001 From: WaDadidou Date: Sat, 28 Dec 2024 13:01:03 -0500 Subject: [PATCH 21/22] fix(article-md): markdown styles in constants, TODOs added --- .../cards/SocialArticleMarkdownCard.tsx | 2 + .../FeedPostArticleMarkdownView.tsx | 2 + packages/utils/feed/markdown.ts | 133 +++++++----------- 3 files changed, 51 insertions(+), 86 deletions(-) diff --git a/packages/components/socialFeed/SocialCard/cards/SocialArticleMarkdownCard.tsx b/packages/components/socialFeed/SocialCard/cards/SocialArticleMarkdownCard.tsx index f0879fe584..e0d29b45f2 100644 --- a/packages/components/socialFeed/SocialCard/cards/SocialArticleMarkdownCard.tsx +++ b/packages/components/socialFeed/SocialCard/cards/SocialArticleMarkdownCard.tsx @@ -36,6 +36,8 @@ import { ZodSocialFeedArticleMarkdownMetadata } from "@/utils/types/feed"; const ARTICLE_CARD_PADDING_VERTICAL = layout.spacing_x2; const ARTICLE_CARD_PADDING_HORIZONTAL = layout.spacing_x2_5; +// TODO: It's a copy of SocialArticleCard.tsx, just made waiting for a posts UI (and data) refacto. => Merge them in the future + export const SocialArticleMarkdownCard: FC<{ post: Post; isPostConsultation?: boolean; diff --git a/packages/screens/FeedPostView/components/FeedPostArticleMarkdownView.tsx b/packages/screens/FeedPostView/components/FeedPostArticleMarkdownView.tsx index f30527e044..2df1c4f896 100644 --- a/packages/screens/FeedPostView/components/FeedPostArticleMarkdownView.tsx +++ b/packages/screens/FeedPostView/components/FeedPostArticleMarkdownView.tsx @@ -57,6 +57,8 @@ import { ZodSocialFeedArticleMarkdownMetadata, } from "@/utils/types/feed"; +// TODO: It's a copy of FeedPostArticleView.tsx, just made waiting for a posts UI (and data) refacto + const contentPaddingHorizontal = layout.spacing_x2; export const FeedPostArticleMarkdownView: FC<{ diff --git a/packages/utils/feed/markdown.ts b/packages/utils/feed/markdown.ts index 770d8b4467..f68d803bf9 100644 --- a/packages/utils/feed/markdown.ts +++ b/packages/utils/feed/markdown.ts @@ -45,24 +45,36 @@ export const renderHtmlDomVisitors = { }; // HTML tags styles used by RenderHtml from react-native-render-html +type HtmlTagStyle = Record; +const baseTextStyle: HtmlTagStyle = { + color: neutralA3, + fontSize: 14, + letterSpacing: -(14 * 0.04), + lineHeight: 22, + fontFamily: "Exo_500Medium", + fontWeight: "500", +}; +const baseBlockStyle: HtmlTagStyle = { + marginTop: 0, + marginBottom: 16, +}; +const baseCodeStyle: HtmlTagStyle = { + ...baseTextStyle, + fontSize: 13, + letterSpacing: -(13 * 0.04), + backgroundColor: neutral17, + borderRadius: 4, +}; +const baseTableChildrenStyle: HtmlTagStyle = { + borderColor: neutral33, +}; export const renderHtmlTagStyles: MixedStyleRecord = { body: { - color: neutralA3, - fontSize: 14, - letterSpacing: -(14 * 0.04), - lineHeight: 22, - fontFamily: "Exo_500Medium", - fontWeight: "500", + ...baseTextStyle, }, p: { - marginTop: 0, - // marginBottom: 16, - - fontSize: 14, - letterSpacing: -(14 * 0.04), - lineHeight: 22, - fontFamily: "Exo_500Medium", - fontWeight: "500", + ...baseBlockStyle, + ...baseTextStyle, }, strong: { fontWeight: "700" }, a: { @@ -71,157 +83,106 @@ export const renderHtmlTagStyles: MixedStyleRecord = { }, hr: { backgroundColor: neutralA3 }, h1: { + ...baseTextStyle, color: neutralFF, fontSize: 28, letterSpacing: -(28 * 0.02), lineHeight: 37, - fontFamily: "Exo_500Medium", - fontWeight: "500", }, h2: { + ...baseTextStyle, color: neutralFF, fontSize: 21, letterSpacing: -(21 * 0.02), lineHeight: 28, - fontFamily: "Exo_500Medium", - fontWeight: "500", }, h3: { + ...baseTextStyle, color: neutralFF, fontSize: 16, letterSpacing: -(16 * 0.02), lineHeight: 23, - fontFamily: "Exo_500Medium", - fontWeight: "500", }, h4: { + ...baseTextStyle, color: neutralFF, - fontSize: 14, - letterSpacing: -(14 * 0.04), lineHeight: 20, - fontFamily: "Exo_500Medium", - fontWeight: "500", }, h5: { - color: neutralA3, - fontSize: 14, - letterSpacing: -(14 * 0.04), + ...baseTextStyle, lineHeight: 20, - fontFamily: "Exo_500Medium", - fontWeight: "500", }, h6: { - color: neutralA3, + ...baseTextStyle, fontSize: 12, letterSpacing: -(12 * 0.04), lineHeight: 16, - fontFamily: "Exo_500Medium", - fontWeight: "500", }, ul: { - marginTop: 0, - marginBottom: 16, - - color: neutralA3, - fontSize: 14, - letterSpacing: -(14 * 0.04), + ...baseBlockStyle, + ...baseTextStyle, lineHeight: 20, - fontFamily: "Exo_500Medium", - fontWeight: "500", }, ol: { - marginTop: 0, - marginBottom: 16, - - color: neutralA3, - fontSize: 14, - letterSpacing: -(14 * 0.04), + ...baseBlockStyle, + ...baseTextStyle, lineHeight: 20, - fontFamily: "Exo_500Medium", - fontWeight: "500", }, blockquote: { - marginTop: 0, - paddingBottom: 0, - + ...baseBlockStyle, + ...baseTextStyle, color: neutral67, - fontSize: 14, - letterSpacing: -(14 * 0.04), lineHeight: 20, - fontFamily: "Exo_500Medium", - fontWeight: "500", - marginLeft: 0, paddingLeft: 14, borderLeftWidth: 3, borderLeftColor: neutral67, }, - "ol > :first-child": { - backgroundColor: "red", - }, - "blockquote > :first-child": { - marginBottom: 0, - }, code: { - color: neutralA3, - fontSize: 13, - letterSpacing: -(13 * 0.04), - lineHeight: 22, - fontFamily: "Exo_500Medium", - fontWeight: "500", - - backgroundColor: neutral17, + ...baseCodeStyle, marginVertical: 4, paddingHorizontal: 4, paddingVertical: 2, - borderRadius: 4, alignSelf: "flex-start", }, pre: { - fontSize: 13, - letterSpacing: -(13 * 0.04), - fontFamily: "Exo_500Medium", - fontWeight: "500", - - backgroundColor: neutral17, + ...baseBlockStyle, + ...baseCodeStyle, paddingHorizontal: 8, - borderRadius: 4, - - marginTop: 0, - marginBottom: 16, }, + table: { marginBottom: 16, }, thead: { + ...baseTableChildrenStyle, borderTopLeftRadius: 4, borderTopRightRadius: 4, - borderColor: neutral33, borderLeftWidth: 1, borderTopWidth: 1, borderRightWidth: 1, backgroundColor: neutral17, }, th: { - borderColor: neutral33, + ...baseTableChildrenStyle, padding: 8, }, tbody: { + ...baseTableChildrenStyle, borderBottomLeftRadius: 4, borderBottomRightRadius: 4, - borderColor: neutral33, borderLeftWidth: 1, borderBottomWidth: 1, borderRightWidth: 1, }, tr: { - borderColor: neutral33, + ...baseTableChildrenStyle, }, td: { + ...baseTableChildrenStyle, borderTopWidth: 0.5, - borderColor: neutral33, padding: 8, }, }; From a1632dd806faeda87914a7497495fa181815c290 Mon Sep 17 00:00:00 2001 From: WaDadidou Date: Sat, 28 Dec 2024 13:09:03 -0500 Subject: [PATCH 22/22] fix(article-md): Use Element from react-native-render-html --- packages/utils/feed/markdown.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/utils/feed/markdown.ts b/packages/utils/feed/markdown.ts index f68d803bf9..b8943a1968 100644 --- a/packages/utils/feed/markdown.ts +++ b/packages/utils/feed/markdown.ts @@ -1,8 +1,7 @@ -import { Element as DomHandlerElement } from "domhandler"; import markdownit from "markdown-it"; import { full as emoji } from "markdown-it-emoji/dist/index.cjs"; import footnote_plugin from "markdown-it-footnote"; -import { MixedStyleRecord } from "react-native-render-html"; +import { MixedStyleRecord, Element } from "react-native-render-html"; import { neutral17, @@ -26,7 +25,7 @@ export const articleMd = markdownit({ // DOM modifications on document, texts, or elements from react-native-render-html. // Because react-native-render-html doesn't allow common CSS selectors, we need to style tags using domVisitors callbacks export const renderHtmlDomVisitors = { - onElement: (element: DomHandlerElement) => { + onElement: (element: Element) => { // Removing marginBottom from the child p of blockquote if ( element.name === "blockquote" && @@ -34,7 +33,7 @@ export const renderHtmlDomVisitors = { element.children.length > 0 ) { const tagChild = element.children.find((child) => child.type === "tag"); - // tagChild is a domhandler Node. It doesn't have attribs, but it has attribs in fact (wtf ?) + // tagChild is a react-native-render-html Node. It doesn't have attribs, but it has attribs in fact (wtf ?) if (tagChild && "attribs" in tagChild) { tagChild.attribs = { style: "margin-bottom: 0",