From e104481bcb089f68f1955ce0dc0cda53e70a0b37 Mon Sep 17 00:00:00 2001 From: Horacio Herrera Date: Tue, 17 Oct 2023 14:20:09 +0200 Subject: [PATCH] frontend(renderer): remove ugly lines. new embed cards --- frontend/apps/site/publication-page.tsx | 4 +- .../packages/app/components/static-embeds.tsx | 90 +- frontend/packages/editor/src/editor.tsx | 8 +- frontend/packages/shared/src/index.ts | 1 - frontend/packages/shared/src/json-hm.ts | 75 -- .../packages/shared/src/static-renderer.tsx | 898 ++++++++++++++++++ .../packages/shared/src/static-styles.css | 11 + .../shared/src/static/static-renderer.tsx | 76 +- 8 files changed, 1005 insertions(+), 158 deletions(-) delete mode 100644 frontend/packages/shared/src/json-hm.ts create mode 100644 frontend/packages/shared/src/static-renderer.tsx create mode 100644 frontend/packages/shared/src/static-styles.css diff --git a/frontend/apps/site/publication-page.tsx b/frontend/apps/site/publication-page.tsx index 32657b98cc..4cc89ccbf9 100644 --- a/frontend/apps/site/publication-page.tsx +++ b/frontend/apps/site/publication-page.tsx @@ -5,6 +5,7 @@ import { HMGroup, HMPublication, Publication, + StaticBlockNode, UnpackedHypermediaId, createHmDocLink, formatBytes, @@ -13,7 +14,6 @@ import { unpackHmId, } from '@mintter/shared' -import {StaticBlockNode} from '@mintter/shared' import { ArrowRight, Button, @@ -30,6 +30,7 @@ import { useMedia, } from '@mintter/ui' import {DehydratedState} from '@tanstack/react-query' +import {OGImageMeta} from 'head' import {NextLink} from 'next-link' import Head from 'next/head' import {useMemo} from 'react' @@ -38,7 +39,6 @@ import Footer from './footer' import {PublicationMetadata} from './publication-metadata' import {SiteHead} from './site-head' import {trpc} from './trpc' -import {OGImageMeta} from 'head' export type PublicationPageProps = { // documentId: string diff --git a/frontend/packages/app/components/static-embeds.tsx b/frontend/packages/app/components/static-embeds.tsx index 3eac832fbc..7346eafb4f 100644 --- a/frontend/packages/app/components/static-embeds.tsx +++ b/frontend/packages/app/components/static-embeds.tsx @@ -67,6 +67,7 @@ export function StaticBlockPublication(props: StaticEmbedProps) { blockNode={bn} childrenType="group" index={idx} + embedDepth={1} /> ))} @@ -78,25 +79,80 @@ export function StaticBlockPublication(props: StaticEmbedProps) { } export function StaticBlockGroup(props: StaticEmbedProps) { - const groupId = props.type == 'g' ? createHmId('d', props.eid) : undefined + const groupId = props.type == 'g' ? createHmId('g', props.eid) : undefined const groupQuery = useGroup(groupId, props.version || undefined) - if (groupQuery.status == 'success') { - return - } - - return null + return groupQuery.status == 'success' ? ( + + + + + + + + Group + + + + {groupQuery.data?.title} + + + Some random group description... + + + + + + ) : null } export function StaticBlockAccount(props: StaticEmbedProps) { const accountId = props.type == 'a' ? props.eid : undefined const accountQuery = useAccount(accountId) - if (accountQuery.status == 'success') { - return - } - - return null + return accountQuery.status == 'success' ? ( + + + + + + + + Account + + + + {accountQuery.data?.profile?.alias} + + + {accountQuery.data.profile?.bio} + + + + + + ) : null } function EntityCard({ @@ -111,15 +167,9 @@ function EntityCard({ route: NavRoute }) { return ( - - {icon} - - - {title} - - {description} - - + + {JSON.stringify({title, description, route})} + ) } function GroupCard({group}: {group: Group}) { diff --git a/frontend/packages/editor/src/editor.tsx b/frontend/packages/editor/src/editor.tsx index 6098e0226a..8aeca32e52 100644 --- a/frontend/packages/editor/src/editor.tsx +++ b/frontend/packages/editor/src/editor.tsx @@ -1,4 +1,5 @@ import {HyperDocsEditor} from '@mintter/app/models/documents' +import {useOpenUrl} from '@mintter/app/open-url' import {YStack} from '@mintter/ui' import { BlockNoteView, @@ -9,13 +10,6 @@ import { } from './blocknote' import './blocknote/core/style.css' import './editor.css' -import {useOpenUrl} from '@mintter/app/open-url' -import { - DefaultStaticBlockAccount, - DefaultStaticBlockGroup, - DefaultStaticBlockPublication, - StaticPublicationProvider, -} from '@mintter/shared' export function HyperMediaEditorView({editor}: {editor: HyperDocsEditor}) { const openUrl = useOpenUrl() diff --git a/frontend/packages/shared/src/index.ts b/frontend/packages/shared/src/index.ts index 9e26505e77..8d0bb689d1 100644 --- a/frontend/packages/shared/src/index.ts +++ b/frontend/packages/shared/src/index.ts @@ -2,6 +2,5 @@ export * from './client' export * from './constants' export * from './grpc-client' export * from './hm-documents' -export * from './json-hm' export * from './utils' export * from './static' diff --git a/frontend/packages/shared/src/json-hm.ts b/frontend/packages/shared/src/json-hm.ts deleted file mode 100644 index 2cef64a3d3..0000000000 --- a/frontend/packages/shared/src/json-hm.ts +++ /dev/null @@ -1,75 +0,0 @@ -import type { - Account, - ChangeInfo, - Device, - Document, - Group, - HMTimestamp, - MttLink, - Profile, - Publication, - Group_SiteInfo, -} from '@mintter/shared' - -export type ServerChangeInfo = ChangeInfo -export type HMChangeInfo = { - id?: string - author?: string - createTime?: HMTimestamp - version?: string - deps?: string[] -} - -export type ServerDocument = Document -export type ServerPublication = Publication - -export type ServerGroupSiteInfo = Group_SiteInfo -export type HMGroupSiteInfo = { - baseUrl?: string - lastSyncTime?: HMTimestamp - lastOkSyncTime?: HMTimestamp - version?: string -} - -export type ServerDevice = Device -export type HMDevice = { - deviceId?: string -} - -export type ServerProfile = Profile -export type HMProfile = { - alias?: string - bio?: string - avatar?: string -} - -export type ServerAccount = Account -export type HMAccount = { - id?: string - profile?: HMProfile - devices?: {[key: string]: HMDevice} -} - -export type ServerGroup = Group -export type HMGroup = { - id?: string - title?: string - description?: string - ownerAccountId?: string - createTime?: HMTimestamp - version?: string -} - -export type ServerLink = MttLink -export type HMLink = { - target?: { - documentId?: string - version?: string - blockId?: string - } - source?: { - documentId?: string - version?: string - blockId?: string - } -} diff --git a/frontend/packages/shared/src/static-renderer.tsx b/frontend/packages/shared/src/static-renderer.tsx new file mode 100644 index 0000000000..02bc8658d2 --- /dev/null +++ b/frontend/packages/shared/src/static-renderer.tsx @@ -0,0 +1,898 @@ +import { + BACKEND_FILE_URL, + Block, + BlockNode, + HMBlock, + HMBlockChildrenType, + HMBlockEmbed, + HMBlockFile, + HMBlockImage, + HMBlockNode, + HMBlockVideo, + HMInlineContent, + HMPublication, + Publication, + formatBytes, + getCIDFromIPFSUrl, + idToUrl, + isHypermediaScheme, + toHMInlineContent, + unpackDocId, +} from '@mintter/shared' +import { + ExternalLink, + File, + FontSizeTokens, + SizableText, + SizableTextProps, + Spinner, + Text, + TextProps, + Tooltip, + XStack, + YStack, + YStackProps, +} from '@mintter/ui' + +import {useRouter} from 'next/router' +import {createContext, useMemo, useState} from 'react' +import './static-styles.css' + +let blockVerticalPadding: FontSizeTokens = '$4' +let blockHorizontalPadding: FontSizeTokens = '$4' +let blockBorderRadius = '$3' + +export const staticRendererContext = createContext(null) + +export function StaticPublicationProvider({children}) { + let value = useMemo(() => { + client: undefined + }, []) + + return ( + + {children} + + ) +} + +export function StaticPublication({publication}: {publication: Publication}) { + return ( + + + {publication.document?.children.map((bn, idx) => ( + + ))} + + + ) +} + +export function StaticGroup({ + children, + childrenType = 'group', + start, + ...props +}: YStackProps & { + childrenType?: HMBlockChildrenType + start?: any +}) { + return ( + + {children} + + ) +} + +function BlockNodeMarker({ + block, + childrenType, + index = 0, + start, + headingTextStyles, +}: { + block: Block + childrenType?: string + start?: string + index?: number + headingTextStyles: TextProps +}) { + let styles = useMemo( + () => + childrenType == 'ol' + ? ({ + position: 'absolute', + right: '27%', + marginTop: 6, + } satisfies SizableTextProps) + : {}, + [childrenType], + ) + let marker + + if (childrenType == 'ol') { + marker = `${index + Number(start)}.` + } + + if (childrenType == 'ul') { + marker = '•' + } + + if (!marker) return null + + return ( + + + {marker} + + + ) +} + +export function StaticBlockNode({ + blockNode, + depth = 1, + childrenType = 'group', + ...props +}: { + blockNode: BlockNode + index: number + copyBlock?: { + docId: string + version: string + } + depth?: number + start?: string | number + childrenType?: HMBlockChildrenType | string +}) { + const headingMarginStyles = useHeadingMarginStyles(depth) + const [isHovering, setIsHovering] = useState(false) + + let bnChildren = blockNode.children?.length + ? blockNode.children.map((bn, index) => ( + + )) + : null + + const isList = useMemo( + () => childrenType == 'ol' || childrenType == 'ul', + [childrenType], + ) + + const headingStyles = useMemo(() => { + if (blockNode.block.type == 'heading') { + return headingMarginStyles + } + + return {} + }, [blockNode.block]) + + return ( + setIsHovering(true)} + onHoverOut={() => setIsHovering(false)} + backgroundColor={isHovering ? '$color5' : 'transparent'} + className="blocknode-static" + > + + + + + {bnChildren ? ( + setIsHovering(false)} + onHoverOut={() => setIsHovering(true)} + childrenType={childrenType as HMBlockChildrenType} + start={props.start} + display="block" + > + {bnChildren} + + ) : null} + + ) +} + +let blockStyles: YStackProps = { + width: '100%', + alignSelf: 'center', + overflow: 'hidden', + borderRadius: '$2', + borderWidth: 1, + borderColor: 'blue', + flex: 1, +} + +let inlineContentProps: SizableTextProps = { + className: 'static-inlinecontent', + fontFamily: '$editorBody', + size: '$4', + $gtMd: { + size: '$5', + }, + $gtLg: { + size: '$6', + }, +} + +type StaticBlockProps = { + block: Block + depth: number +} + +function StaticBlock(props: StaticBlockProps) { + if (props.block.type == 'paragraph') { + return + } + + if (props.block.type == 'heading') { + return + } + + if (props.block.type == 'image') { + return + } +} + +function StaticBlockParagraph({block, depth}: StaticBlockProps) { + let inline = useMemo(() => toHMInlineContent(new Block(block)), []) + + return ( + + + + + + ) +} + +function StaticBlockHeading({block, depth}: StaticBlockProps) { + let inline = useMemo(() => toHMInlineContent(new Block(block)), []) + let headingTextStyles = useHeadingTextStyles(depth) + let tag = `h${depth}` + + return ( + + + + + + ) +} + +function useHeadingTextStyles(depth: number) { + function headingFontValues(value: number) { + return { + fontSize: value, + lineHeight: value * 1.25, + } + } + + return useMemo(() => { + if (depth == 1) { + return { + ...headingFontValues(30), + $gtMd: headingFontValues(36), + $gtLg: headingFontValues(42), + } satisfies TextProps + } + + if (depth == 2) { + return { + ...headingFontValues(24), + $gtMd: headingFontValues(30), + $gtLg: headingFontValues(36), + } satisfies TextProps + } + + if (depth == 3) { + return { + ...headingFontValues(20), + $gtMd: headingFontValues(24), + $gtLg: headingFontValues(30), + } satisfies TextProps + } + + return { + ...headingFontValues(18), + $gtMd: headingFontValues(20), + $gtLg: headingFontValues(24), + } satisfies TextProps + }, [depth]) +} + +function useHeadingMarginStyles(depth: number) { + function headingFontValues(value: number) { + return { + marginTop: value, + marginBottom: value / 2, + } + } + + return useMemo(() => { + if (depth == 1) { + return { + ...headingFontValues(30), + $gtMd: headingFontValues(36), + $gtLg: headingFontValues(42), + } satisfies TextProps + } + + if (depth == 2) { + return { + ...headingFontValues(24), + $gtMd: headingFontValues(30), + $gtLg: headingFontValues(36), + } satisfies TextProps + } + + if (depth == 3) { + return { + ...headingFontValues(20), + $gtMd: headingFontValues(24), + $gtLg: headingFontValues(30), + } satisfies TextProps + } + + return { + ...headingFontValues(18), + $gtMd: headingFontValues(20), + $gtLg: headingFontValues(24), + } satisfies TextProps + }, [depth]) +} + +function StaticBlockImage({block, depth}: StaticBlockProps) { + let inline = useMemo(() => toHMInlineContent(new Block(block)), []) + const cid = getCIDFromIPFSUrl(block?.ref) + if (!cid) return null + + return ( + + {block.attributes.alt} + {inline.length ? ( + + + + ) : null} + + ) +} + +function InlineContentView({ + inline, + style, + ...props +}: SizableTextProps & { + inline: HMInlineContent[] +}) { + return ( + + {inline.map((content, index) => { + if (content.type === 'text') { + let textDecorationLine: + | 'none' + | 'line-through' + | 'underline' + | 'underline line-through' + | undefined + if (content.styles.underline) { + if (content.styles.strike) { + textDecorationLine = 'underline line-through' + } else { + textDecorationLine = 'underline' + } + } else if (content.styles.strike) { + textDecorationLine = 'line-through' + } + + let children: any = content.text + + if (content.styles.bold) { + children = {children} + } + + if (content.styles.italic) { + children = {children} + } + + if (content.styles.underline) { + children = {children} + } + + if (content.styles.code) { + children = ( + + {children} + + ) + } + + if (content.styles.backgroundColor) { + children = ( + + {children} + + ) + } + + if (content.styles.strike) { + children = {children} + } + + if (content.styles.textColor) { + children = ( + {children} + ) + } + + return ( + + {children} + + ) + } + if (content.type === 'link') { + const href = isHypermediaScheme(content.href) + ? idToUrl(content.href, null) + : content.href + + const isExternal = isHypermediaScheme(content.href) + return ( + href && ( + + + {isExternal ? : null} + + // + // + // + // + // {isExternal ? : null} + // + // + // + ) + ) + } + return null + })} + + ) +} + +// ==================================================================================================== + +function StaticImageBlock({ + block, + isList, +}: { + block: HMBlockImage + isList?: boolean +}) { + const cid = getCIDFromIPFSUrl(block?.ref) + if (!cid) return null + return ( + + {block.attributes?.alt} { + console.error('image errored', e) + }} + /> + + ) + // return +} + +function stripHMLinkPrefix(link: string) { + return link.replace(/^hm:\//, '') +} + +function StaticEmbedBlock({ + block, + isList, +}: { + block: HMBlockEmbed + isList?: boolean +}) { + const reference = block.ref + const docId = unpackDocId(reference) + const router = useRouter() + let embed = trpc.publication.get.useQuery( + { + documentId: docId?.docId, + versionId: docId?.version || undefined, + }, + {enabled: !!docId}, + ) + let content = + if (embed.data?.publication?.document?.children) { + if (docId?.blockRef) { + const blockNode = getBlockNodeById( + embed.data?.publication?.document?.children, + docId.blockRef, + ) + content = blockNode ? ( + + ) : ( + Block not found. + ) + } else { + content = ( + <> + {embed.data?.publication?.document?.children?.map( + (block: HMBlockNode) => ( + + ), + )} + + ) + } + } + return ( + + { + const ref = stripHMLinkPrefix(block.ref) + router.push(ref) + }} + href={stripHMLinkPrefix(block.ref)} + > + {content} + {/* */} + + + ) +} + +function StaticBlock_({block, isList}: {block: HMBlock; isList?: boolean}) { + let niceBlock = block + + if (niceBlock.type == 'paragraph' || niceBlock.type == 'heading') { + return + } + + if (niceBlock.type == 'image') { + return + } + if (niceBlock.type == 'embed') { + return + } + if (niceBlock.type == 'code') { + let _content = ( +
+        {block.text}
+      
+ ) + + return isList ?
  • {_content}
  • : _content + } + + if (niceBlock.type == 'file') { + return + } + + if (niceBlock.type == 'video') { + return + } + // fallback for unknown block types + // return {JSON.stringify(block)} + return ( + + ) +} + +function ErrorMessageBlock({message, id}: {message: string; id: string}) { + return ( + + + {message} + + + ) +} + +export function PublicationContent({ + publication, +}: { + publication: HMPublication | undefined +}) { + // This removes the first block from the document content if it's not a media element (embed, image, video...) + let blocks = useMemo(() => { + let _b = publication?.document?.children + + if ( + !_b?.length || + (_b.length == 1 && + !['embed', 'image', 'video'].includes(_b[0].block?.type)) + ) + return [] + + // check if the first block has content or not. + if ( + _b[0].block?.type && + ['embed', 'image', 'video'].includes(_b[0].block?.type) + ) + return _b + + let [_firstBlock, ...restBlocks] = _b + + if (_firstBlock.children?.length) { + restBlocks = [..._firstBlock.children, ...restBlocks] + } + + return restBlocks + }, [publication?.document?.children]) + return ( + + {blocks.map((block, index) => ( + + ))} + + ) +} + +function getBlockNodeById( + blocks: Array, + blockId: string, +): HMBlockNode | null { + if (!blockId) return null + + let res: HMBlockNode | undefined + blocks.find((bn) => { + if (bn.block?.id == blockId) { + res = bn + return true + } else if (bn.children?.length) { + const foundChild = getBlockNodeById(bn.children, blockId) + if (foundChild) { + res = foundChild + return true + } + } + return false + }) + return res || null +} + +export function StaticFileBlock({block}: {block: HMBlockFile}) { + let cid = useMemo(() => getCIDFromIPFSUrl(block.ref), [block.ref]) + return ( + + console.log('OPEN FILE', cid)} + > + + + + + {block.attributes.name} + + {block.attributes.size && ( + + {formatBytes(parseInt(block.attributes.size))} + + )} + + + + ) +} + +function StaticVideoBlock({ + block, + isList, +}: { + block: HMBlockVideo + isList?: boolean +}) { + const ref = block.ref || '' + return ref ? ( + + {ref.startsWith('ipfs://') ? ( + + + Something is wrong with the video file. + + ) : ( + + )} + + ) : null +} + +function getSourceType(name?: string) { + if (!name) return + const nameArray = name.split('.') + return `video/${nameArray[nameArray.length - 1]}` +} diff --git a/frontend/packages/shared/src/static-styles.css b/frontend/packages/shared/src/static-styles.css new file mode 100644 index 0000000000..4945dae207 --- /dev/null +++ b/frontend/packages/shared/src/static-styles.css @@ -0,0 +1,11 @@ +li.blocknode-static { + font-family: 'Courier New', Courier, monospace; + position: absolute; +} + +.static-inlinecontent code { + background-color: red; + padding: 0.18rem 0.3rem; + border-radius: 6px; + font-size: 85%; +} diff --git a/frontend/packages/shared/src/static/static-renderer.tsx b/frontend/packages/shared/src/static/static-renderer.tsx index ac2854c029..21366abc9c 100644 --- a/frontend/packages/shared/src/static/static-renderer.tsx +++ b/frontend/packages/shared/src/static/static-renderer.tsx @@ -6,7 +6,6 @@ import { Group, HMBlockChildrenType, HMBlockFile, - HMBlockNode, HMInlineContent, Publication, formatBytes, @@ -33,7 +32,6 @@ import { import { PropsWithChildren, - ReactNode, createContext, useContext, useMemo, @@ -52,9 +50,9 @@ export const staticPublicationContext = export type StaticEmbedProps = StaticBlockProps & ReturnType export type EntityComponentsRecord = { - StaticAccount: (props: StaticEmbedProps) => JSX.Element - StaticGroup: (props: StaticEmbedProps) => JSX.Element - StaticPublication: (props: StaticEmbedProps) => JSX.Element + StaticAccount: React.FC + StaticGroup: React.FC + StaticPublication: React.FC } export function StaticPublicationProvider({ @@ -182,6 +180,7 @@ export function StaticBlockNode({ depth?: number start?: string | number childrenType?: HMBlockChildrenType | string + embedDepth?: number }) { const headingMarginStyles = useHeadingMarginStyles(depth) const [isHovering, setIsHovering] = useState(false) @@ -195,6 +194,9 @@ export function StaticBlockNode({ childrenType={blockNode.block!.attributes?.childrenType} start={blockNode.block?.attributes.start} index={index} + embedDepth={ + props.embedDepth ? props.embedDepth + 1 : props.embedDepth + } /> )) : null @@ -207,16 +209,22 @@ export function StaticBlockNode({ return {} }, [blockNode.block, headingMarginStyles]) - const isEmbed = blockNode.block?.type == 'embed' + const isEmbed = blockNode.block.type == 'embed' return ( setIsHovering(true)} - onHoverOut={() => setIsHovering(false)} - backgroundColor={isHovering ? '$color5' : 'transparent'} + onHoverIn={() => (props.embedDepth ? setIsHovering(true) : undefined)} + onHoverOut={() => (props.embedDepth ? setIsHovering(false) : undefined)} + backgroundColor={ + props.embedDepth + ? 'transparent' + : isHovering + ? '$color5' + : 'transparent' + } className="static-blocknode" > {bnChildren ? ( setIsHovering(false)} - onHoverOut={() => setIsHovering(true)} + onHoverIn={() => + props.embedDepth ? setIsHovering(false) : undefined + } + onHoverOut={() => + props.embedDepth ? setIsHovering(true) : undefined + } childrenType={childrenType as HMBlockChildrenType} start={props.start} display="block" @@ -645,48 +657,6 @@ export function StaticBlockEmbed(props: StaticBlockProps & {blockRef: string}) { return } -export type StaticBlockAccountProps = { - account?: Account -} - -export function DefaultStaticBlockAccount( - props: StaticBlockProps & StaticBlockAccountProps, -) { - return ( - - - Account Embed here: {props.block.ref} - - - ) -} - -export type StaticBlockGroupProps = { - group?: Group -} - -export function DefaultStaticBlockGroup( - props: StaticBlockProps & StaticBlockGroupProps, -) { - return ( - - - Group Embed here: {props.block.ref} - - - ) -} - -export function DefaultStaticBlockPublication(props: StaticBlockProps) { - return ( - - - Publication Embed here: {props.block.ref} - - - ) -} - export function DefaultStaticBlockUnknown(props: StaticBlockProps) { return (