diff --git a/app/blog/[identifier]/index.tsx b/app/blog/[identifier]/index.tsx index 1281ed6..74700dc 100644 --- a/app/blog/[identifier]/index.tsx +++ b/app/blog/[identifier]/index.tsx @@ -47,7 +47,7 @@ export default function BlogPage() { const profile = await resolveProfile(blogId!); setProfile(profile); const fetchedThreads = await fetchBlogPosts(profile, authCtx); - console.log(fetchedThreads); + // console.log(fetchedThreads); setThreads(fetchedThreads); setLoading(false); })(); diff --git a/src/components/primitive/Link.tsx b/src/components/primitive/Link.tsx new file mode 100644 index 0000000..fa3c13f --- /dev/null +++ b/src/components/primitive/Link.tsx @@ -0,0 +1,28 @@ +import { Linking, Platform, Text as RNText, TextProps } from "react-native"; + +export function Link(props: TextProps & { uri?: string }) { + const { uri } = props; + + const openLink = () => { + if (Platform.OS === "web") { + window.open(uri!, "_blank"); + } else { + Linking.openURL(uri!); + } + }; + + return ( + + ); +} diff --git a/src/lib/richtext/render.tsx b/src/lib/richtext/render.tsx index dc087ab..9472ade 100644 --- a/src/lib/richtext/render.tsx +++ b/src/lib/richtext/render.tsx @@ -1,12 +1,13 @@ -import { ReactElement, useCallback } from "react"; -import { RichTextBlocks, RichTextFacet } from "./richtext"; -import { dev } from "@/src/lexicons"; +import { Link } from "@/src/components/primitive/Link"; import { Text } from "@/src/components/primitive/Text"; -import { StyleSheet } from "react-native"; +import { dev } from "@/src/lexicons"; +import { RichText as AtpRichTextAPI } from "@atproto/api"; +import { ReactElement, useMemo } from "react"; +import { StyleProp, StyleSheet, TextStyle } from "react-native"; +import { RichTextBlocks, RichTextFacet } from "./richtext"; const formatStyles = StyleSheet.create({ - regular: { - }, + regular: {}, h2: { fontSize: 20, }, @@ -27,28 +28,99 @@ const formatStyles = StyleSheet.create({ borderLeftColor: "#7e7e7e", color: "#9c9c9c", paddingLeft: 15, - paddingVertical: 6 + paddingVertical: 6, + }, +}); + +const formatStyleMap = new Map(Object.entries(formatStyles)); + +function renderText(text: string, facets?: RichTextFacet[]) { + const rtApi = new AtpRichTextAPI({ + text: text, + facets: (facets as any) ?? [], + }); + + const elements: ReactElement[] = []; + + let i = 0; + for (const segment of rtApi.segments()) { + const segmentStyles: StyleProp[] = []; + let linkHref: string | undefined; + + for (const feature of segment.facet?.features ?? []) { + if (feature.$type === "dev.maelstrom.bramblr.richtext.facet#color") { + const colorFeature = + feature as dev.maelstrom.bramblr.richtext.facet.Color; + + segmentStyles.push({ + color: /(#[a-fA-F\d]{3,6})/.exec(colorFeature.color ?? "")?.[1] ?? "", + }); + } else if ( + feature.$type === "dev.maelstrom.bramblr.richtext.facet#link" + ) { + const linkFeature = + feature as dev.maelstrom.bramblr.richtext.facet.Link; + + linkHref = /(^(?:https|http):\/\/.*)/.exec(linkFeature.uri ?? "")?.[1]; + segmentStyles.push({ textDecorationLine: "underline" }); + } + } + + if (linkHref) { + // If link + elements.push( + + {segment.text} + , + ); + } else { + // If normal text + elements.push( + + {segment.text} + , + ); + } + i++; } -}) + return elements; +} + +function TextBlock({ + text, + format, + facets, +}: { + text: string; + format?: string; + facets?: RichTextFacet[]; +}) { + const formatStyle = + (format ? formatStyleMap.get(format) : undefined) ?? formatStyles.regular; -const formatStyleMap = new Map(Object.entries(formatStyles)) + const renderedText = useMemo(() => renderText(text, facets), [text, facets]); -function TextBlock({ text, format, facets }: { text: string, format?: string, facets?: RichTextFacet[] }) { - const formatStyle = (format ? formatStyleMap.get(format) : undefined) ?? formatStyles.regular - return {text} + return {renderedText}; } function renderRichText(richTextBlocks: RichTextBlocks) { - const paragraphs: ReactElement[] = [] + const paragraphs: ReactElement[] = []; for (const [i, block] of richTextBlocks.entries()) { if (block.$type === "dev.maelstrom.bramblr.richtext.block#text") { - const textBlock = block as dev.maelstrom.bramblr.richtext.block.Text - paragraphs.push() + const textBlock = block as dev.maelstrom.bramblr.richtext.block.Text; + paragraphs.push( + , + ); } } - return paragraphs + return paragraphs; } export default function RichTextBody({ @@ -56,7 +128,7 @@ export default function RichTextBody({ }: { richText: RichTextBlocks; }) { - const render = useCallback(() => renderRichText(richText), [richText]); + const renderedBlocks = useMemo(() => renderRichText(richText), [richText]); - return <>{render()}; + return <>{renderedBlocks}; }