From 743f7772cdad8aa77aabb74ff516160a430db74a Mon Sep 17 00:00:00 2001 From: Eric Vicenti Date: Wed, 4 Dec 2024 11:46:56 -0800 Subject: [PATCH] Copy latest links when appropriate --- .../src/components/copy-reference-button.tsx | 168 ++++++++++++++++ .../src/components/copy-reference-url.tsx | 1 + .../apps/desktop/src/components/directory.tsx | 181 +----------------- .../src/components/document-head-items.tsx | 8 +- .../src/components/titlebar-common.tsx | 153 +-------------- .../src/pages/document-content-provider.tsx | 2 +- frontend/apps/desktop/src/pages/document.tsx | 6 +- frontend/packages/ui/src/document-content.tsx | 2 +- 8 files changed, 185 insertions(+), 336 deletions(-) create mode 100644 frontend/apps/desktop/src/components/copy-reference-button.tsx diff --git a/frontend/apps/desktop/src/components/copy-reference-button.tsx b/frontend/apps/desktop/src/components/copy-reference-button.tsx new file mode 100644 index 000000000..5dacf555c --- /dev/null +++ b/frontend/apps/desktop/src/components/copy-reference-button.tsx @@ -0,0 +1,168 @@ +import {useAppContext} from '@/app-context' +import {useCopyReferenceUrl} from '@/components/copy-reference-url' +import {useEntity} from '@/models/entities' +import {useGatewayUrl} from '@/models/gateway-settings' +import { + BlockRange, + DEFAULT_GATEWAY_URL, + ExpandedBlockRange, + UnpackedHypermediaId, + createSiteUrl, + createWebHMUrl, + hmId, +} from '@shm/shared' +import {Button, ButtonProps, Tooltip} from '@shm/ui' +import {ExternalLink, Link} from '@tamagui/lucide-icons' +import {PropsWithChildren, ReactNode, useState} from 'react' + +export function useDocumentUrl({ + docId, + isBlockFocused, + latest, +}: { + docId?: UnpackedHypermediaId + isBlockFocused: boolean + latest?: boolean +}): { + label: string + url: string + onCopy: ( + blockId?: string | undefined, + blockRange?: BlockRange | ExpandedBlockRange, + ) => void + content: ReactNode +} | null { + const docEntity = useEntity(docId) + if (!docId?.uid) return null + const accountEntity = useEntity(hmId('d', docId?.uid!)) + const gwUrl = useGatewayUrl().data || DEFAULT_GATEWAY_URL + const siteHostname = accountEntity.data?.document?.metadata?.siteUrl + const [copyDialogContent, onCopyReference] = useCopyReferenceUrl( + siteHostname || gwUrl, + ) + if (!docId) return null + const url = siteHostname + ? createSiteUrl({ + hostname: siteHostname, + path: docId.path, + version: docEntity.data?.document?.version, + latest, + }) + : createWebHMUrl('d', docId.uid, { + version: docEntity.data?.document?.version, + hostname: gwUrl, + path: docId.path, + latest, + }) + return { + url, + label: siteHostname + ? 'Site' + : 'Public' + (latest ? ' Latest' : ' Exact Version'), + content: copyDialogContent, + onCopy: ( + blockId: string | undefined, + blockRange?: BlockRange | ExpandedBlockRange | null, + ) => { + const focusBlockId = isBlockFocused ? docId.blockRef : null + onCopyReference({ + ...docId, + hostname: siteHostname || gwUrl, + version: docEntity.data?.document?.version || null, + blockRef: blockId || focusBlockId || null, + blockRange: blockRange || null, + path: docId.path, + latest, + }) + }, + } +} + +export function CopyReferenceButton({ + children, + docId, + isBlockFocused, + latest, + copyIcon = Link, + openIcon = ExternalLink, + iconPosition = 'before', + showIconOnHover = false, + ...props +}: PropsWithChildren< + ButtonProps & { + docId: UnpackedHypermediaId + isBlockFocused: boolean + latest?: boolean + isIconAfter?: boolean + showIconOnHover?: boolean + copyIcon?: React.ElementType + openIcon?: React.ElementType + iconPosition?: 'before' | 'after' + } +>) { + const [shouldOpen, setShouldOpen] = useState(false) + const reference = useDocumentUrl({docId, isBlockFocused, latest}) + const {externalOpen} = useAppContext() + if (!reference) return null + const CurrentIcon = shouldOpen ? openIcon : copyIcon + const Icon = () => ( + + ) + return ( + <> + + + + {reference.content} + + ) +} diff --git a/frontend/apps/desktop/src/components/copy-reference-url.tsx b/frontend/apps/desktop/src/components/copy-reference-url.tsx index 6cc3f44aa..f211a029c 100644 --- a/frontend/apps/desktop/src/components/copy-reference-url.tsx +++ b/frontend/apps/desktop/src/components/copy-reference-url.tsx @@ -42,6 +42,7 @@ export function useCopyReferenceUrl(hostname: string) { blockRange: input.blockRange, hostname, path: input.path, + latest: input.latest, }) copyTextToClipboard(url) if (pushOnCopy.data === 'never') { diff --git a/frontend/apps/desktop/src/components/directory.tsx b/frontend/apps/desktop/src/components/directory.tsx index d69cfed5d..51c24759d 100644 --- a/frontend/apps/desktop/src/components/directory.tsx +++ b/frontend/apps/desktop/src/components/directory.tsx @@ -1,31 +1,15 @@ import {useDraft} from '@/models/accounts' import {useDraftList, useListDirectory} from '@/models/documents' -import {useEntities, useSubscribedEntity} from '@/models/entities' -import {pathNameify} from '@/utils/path' +import {useSubscribedEntity} from '@/models/entities' import {useNavigate} from '@/utils/useNavigate' import { - formattedDateLong, - formattedDateMedium, getMetadataName, hmId, - HMMetadata, UnpackedHypermediaId, unpackHmId, } from '@shm/shared' -import { - Button, - DirectoryItem, - HMIcon, - itemHoverBgColor, - SizableText, - SmallListItem, - Tooltip, - XStack, - YStack, -} from '@shm/ui' -import {Copy} from '@tamagui/lucide-icons' +import {HMIcon, SizableText, SmallListItem} from '@shm/ui' import {useMemo} from 'react' -import {CopyReferenceButton} from './titlebar-common' export function Directory({ docId, @@ -156,164 +140,3 @@ function DraftItem({ /> ) } -function DraftItemLarge({id}: {id: UnpackedHypermediaId}) { - const navigate = useNavigate() - - const draft = useDraft(id) - function goToDraft() { - navigate({key: 'draft', id}) - } - - return ( - */} - {draft.data?.lastUpdateTime ? ( - - - {formattedDateMedium(new Date(draft.data.lastUpdateTime))} - - - ) : null} - - {/* - - */} - - - ) -} - -function DirectoryItemWithAuthors({ - entry, -}: { - entry: { - id: UnpackedHypermediaId - hasDraft?: boolean - authors: string[] - path: string - metadata: HMMetadata - } -}) { - const editorIds = useMemo( - () => - entry.authors.length > 3 ? entry.authors.slice(0, 2) : entry.authors, - [entry.authors], - ) - const editors = useEntities(editorIds.map((id) => hmId('d', id))) - const authorsMetadata = editors - .map((query) => query.data) - .filter((author) => !!author) - .map((data) => { - return { - id: data!.id!, - metadata: data?.document?.metadata, - } - }) - return ( - - ) -} - -function PathButton({ - path, - docId, - isDraft = false, -}: { - path: string - docId: UnpackedHypermediaId - isDraft?: boolean -}) { - const Comp = !isDraft ? CopyReferenceButton : XStack - return ( - - - {path} - - - ) -} diff --git a/frontend/apps/desktop/src/components/document-head-items.tsx b/frontend/apps/desktop/src/components/document-head-items.tsx index bff44b2d4..d11d61332 100644 --- a/frontend/apps/desktop/src/components/document-head-items.tsx +++ b/frontend/apps/desktop/src/components/document-head-items.tsx @@ -2,10 +2,10 @@ import {useMyAccountIds} from '@/models/daemon' import {HMDocument, hmId, UnpackedHypermediaId} from '@shm/shared' import {Check, SizableText, XStack} from '@shm/ui' -import {useEntities} from '@/models/entities' +import {useEntities, useSubscribedEntity} from '@/models/entities' import {DonateButton} from '@shm/ui' +import {CopyReferenceButton} from './copy-reference-button' import {SubscriptionButton} from './subscription' -import {CopyReferenceButton} from './titlebar-common' export function DocumentHeadItems({ docId, @@ -21,6 +21,9 @@ export function DocumentHeadItems({ const authors = useEntities( document.authors.map((author) => hmId('d', author)) || [], ) + const latestDoc = useSubscribedEntity({...docId, version: null, latest: true}) + const isLatest = + docId.latest || document.version === latestDoc.data?.document?.version return ( <> {docIsInMyAccount ? ( @@ -44,6 +47,7 @@ export function DocumentHeadItems({ /> diff --git a/frontend/apps/desktop/src/components/titlebar-common.tsx b/frontend/apps/desktop/src/components/titlebar-common.tsx index e96a830e0..dc8e63caf 100644 --- a/frontend/apps/desktop/src/components/titlebar-common.tsx +++ b/frontend/apps/desktop/src/components/titlebar-common.tsx @@ -14,13 +14,8 @@ import { } from '@/utils/navigation' import {useNavigate} from '@/utils/useNavigate' import { - BlockRange, DEFAULT_GATEWAY_URL, - ExpandedBlockRange, HMBlockNode, - UnpackedHypermediaId, - createSiteUrl, - createWebHMUrl, displayHostname, hmBlocksToEditorContent, hmId, @@ -28,7 +23,6 @@ import { import { Back, Button, - ButtonProps, ColorProp, Forward, Menu, @@ -50,14 +44,13 @@ import { ArrowRightFromLine, CloudOff, Download, - ExternalLink, Link, Pencil, Trash, UploadCloud, UserPlus, } from '@tamagui/lucide-icons' -import {PropsWithChildren, ReactNode, useContext, useState} from 'react' +import {ReactNode, useContext} from 'react' import {AddConnectionDialog} from './contacts-prompt' import {useAppDialog} from './dialog' import DiscardDraftButton from './discard-draft-button' @@ -241,150 +234,6 @@ function EditDocButton() { ) } -export function useDocumentUrl({ - docId, - isBlockFocused, -}: { - docId?: UnpackedHypermediaId - isBlockFocused: boolean -}): { - label: string - url: string - onCopy: ( - blockId?: string | undefined, - blockRange?: BlockRange | ExpandedBlockRange, - ) => void - content: ReactNode -} | null { - const docEntity = useEntity(docId) - if (!docId?.uid) return null - const accountEntity = useEntity(hmId('d', docId?.uid!)) - const gwUrl = useGatewayUrl().data || DEFAULT_GATEWAY_URL - const siteHostname = accountEntity.data?.document?.metadata?.siteUrl - const [copyDialogContent, onCopyReference] = useCopyReferenceUrl( - siteHostname || gwUrl, - ) - if (!docId) return null - const url = siteHostname - ? createSiteUrl({ - hostname: siteHostname, - path: docId.path, - version: docEntity.data?.document?.version, - latest: true, - }) - : createWebHMUrl('d', docId.uid, { - version: docEntity.data?.document?.version, - hostname: gwUrl, - path: docId.path, - }) - return { - url, - label: siteHostname ? 'Site' : 'Public', - content: copyDialogContent, - onCopy: ( - blockId: string | undefined, - blockRange?: BlockRange | ExpandedBlockRange | null, - ) => { - const focusBlockId = isBlockFocused ? docId.blockRef : null - onCopyReference({ - ...docId, - hostname: siteHostname || gwUrl, - version: docEntity.data?.document?.version || null, - blockRef: blockId || focusBlockId || null, - blockRange: blockRange || null, - path: docId.path, - }) - }, - } -} - -export function CopyReferenceButton({ - children, - docId, - isBlockFocused, - copyIcon = Link, - openIcon = ExternalLink, - iconPosition = 'before', - showIconOnHover = false, - ...props -}: PropsWithChildren< - ButtonProps & { - docId: UnpackedHypermediaId - isBlockFocused: boolean - isIconAfter?: boolean - showIconOnHover?: boolean - copyIcon?: React.ElementType - openIcon?: React.ElementType - iconPosition?: 'before' | 'after' - } ->) { - const [shouldOpen, setShouldOpen] = useState(false) - const reference = useDocumentUrl({docId, isBlockFocused}) - const {externalOpen} = useAppContext() - if (!reference) return null - const CurrentIcon = shouldOpen ? openIcon : copyIcon - const Icon = () => ( - - ) - return ( - <> - - - - {reference.content} - - ) -} - export function PageActionButtons(props: TitleBarProps) { const route = useNavRoute() const connectDialog = useAppDialog(AddConnectionDialog) diff --git a/frontend/apps/desktop/src/pages/document-content-provider.tsx b/frontend/apps/desktop/src/pages/document-content-provider.tsx index f313aeea8..6a09ac6a6 100644 --- a/frontend/apps/desktop/src/pages/document-content-provider.tsx +++ b/frontend/apps/desktop/src/pages/document-content-provider.tsx @@ -15,7 +15,7 @@ import { contentLayoutUnit, contentTextUnit, } from '@shm/ui' -import {useDocumentUrl} from '../components/titlebar-common' +import {useDocumentUrl} from '../components/copy-reference-button' export function AppDocContentProvider({ children, diff --git a/frontend/apps/desktop/src/pages/document.tsx b/frontend/apps/desktop/src/pages/document.tsx index 0286f361d..cf504cf97 100644 --- a/frontend/apps/desktop/src/pages/document.tsx +++ b/frontend/apps/desktop/src/pages/document.tsx @@ -202,7 +202,11 @@ function _MainDocumentPage({ }, []) const entity = useSubscribedEntity(id) - const siteHomeEntity = useSubscribedEntity(hmId('d', id.uid)) + const siteHomeEntity = useSubscribedEntity( + // if the route document ID matches the home document, then use it because it may be referring to a specific version + id.path?.length ? hmId('d', id.uid) : id, + // otherwise, create an ID with the latest version of the home document + ) if (entity.isInitialLoading) return if (!entity.data?.document) return null diff --git a/frontend/packages/ui/src/document-content.tsx b/frontend/packages/ui/src/document-content.tsx index 2b964efad..9f7ab49d0 100644 --- a/frontend/packages/ui/src/document-content.tsx +++ b/frontend/packages/ui/src/document-content.tsx @@ -726,7 +726,7 @@ export function BlockNodeContent({ {!props.embedDepth && !renderOnly ? ( <> {onCopyBlock ? ( - +