diff --git a/frontend/apps/desktop/src/app-account.tsx b/frontend/apps/desktop/src/app-account.tsx index bbdc8b469..533a64c2d 100644 --- a/frontend/apps/desktop/src/app-account.tsx +++ b/frontend/apps/desktop/src/app-account.tsx @@ -225,7 +225,7 @@ export function AccountWizardDialog() { if (isSaveWords) { saveWords.mutate({key: 'main', value: words}) } - invalidate([queryKeys.KEYS_LIST]) + invalidate([queryKeys.LOCAL_ACCOUNT_ID_LIST]) setCreatedAccount(res.accountId) setStep('complete') }) @@ -291,7 +291,7 @@ export function AccountWizardDialog() { disabled={!isExistingWordsSave} onPress={() => { addExistingAccount.mutateAsync().then((res) => { - invalidate([queryKeys.KEYS_LIST]) + invalidate([queryKeys.LOCAL_ACCOUNT_ID_LIST]) setCreatedAccount(res.accountId) setStep('complete') }) diff --git a/frontend/apps/desktop/src/components/app-embeds.tsx b/frontend/apps/desktop/src/components/app-embeds.tsx index c845128d5..0c9b879c5 100644 --- a/frontend/apps/desktop/src/components/app-embeds.tsx +++ b/frontend/apps/desktop/src/components/app-embeds.tsx @@ -1,4 +1,5 @@ -import {useAccount_deprecated, useProfile} from '@/models/accounts' +import {useAccount_deprecated} from '@/models/accounts' +import {useEntity} from '@/models/entities' import { API_FILE_URL, BlockContentUnknown, @@ -40,7 +41,6 @@ import { import {YStackProps} from 'tamagui' import {useAccounts} from '../models/accounts' import {useComment} from '../models/comments' -import {useDocument} from '../models/documents' import {getAvatarUrl} from '../utils/account-url' import {useNavRoute} from '../utils/navigation' import {getRouteContext, useOpenInContext} from '../utils/route-context' @@ -251,8 +251,8 @@ const EmbedSideAnnotation = forwardRef< /> ) if (unpacked && unpacked.type != 'd') return null - const doc = useDocument(unpacked?.qid, unpacked?.version || undefined) - const editors = useAccounts(doc.data?.authors || []) + const entity = useEntity(unpacked) + const editors = useAccounts(entity.data?.document?.authors || []) return ( {/* */} - {doc?.data?.metadata?.name} + {getDocumentTitle(entity?.data?.document)} {/* {formattedDateMedium(pub.data?.document?.publishTime)} */} {/* */} - {formattedDateMedium(doc.data?.updateTime)} + {formattedDateMedium(entity.data?.document?.updateTime)} comment on{' '} - {pubTarget?.data?.publication?.document?.title} + {getDocumentTitle(pubTarget?.data?.document)} {/* @@ -367,9 +362,7 @@ const CommentSideAnnotation = forwardRef(function CommentSideAnnotation( */} {/* */} - {formattedDateMedium( - pubTarget.data?.publication?.document?.updateTime, - )} + {formattedDateMedium(pubTarget.data?.document?.updateTime)} - documentId && ( - - ) - } + renderOpenButton={() => ( + + )} /> ) } export function EmbedDocumentCard(props: EntityComponentProps) { const docId = props.type == 'd' ? createHmId('d', props.eid) : undefined - const doc = useDocument( - docId, - props.latest ? undefined : props.version || undefined, - { - enabled: !!docId, - }, - ) + const doc = useEntity(props) let textContent = useMemo(() => { - if (doc.data?.content) { + if (doc.data?.document?.content) { let content = '' - doc.data?.content.forEach((bn) => { + doc.data?.document?.content.forEach((bn) => { content += bn.block?.text + ' ' }) return content @@ -503,11 +474,11 @@ export function EmbedDocumentCard(props: EntityComponentProps) { viewType={props.block.attributes?.view == 'card' ? 'card' : 'content'} > ) @@ -517,13 +488,14 @@ export function EmbedAccount( props: EntityComponentProps, parentBlockId: string | null, ) { - console.log(`== ~ props EmbedAccount:`, props) - const accountId = props.type == 'a' ? props.eid : undefined - const profile = useProfile(accountId) + const profile = useEntity(props) if (profile.status == 'success') { + const account = + profile.data?.type === 'a' ? profile.data?.account : undefined + if (!account) return null if (props.block?.attributes?.view == 'content' && profile.data) { - return + return } else if (props.block?.attributes?.view == 'card') { return ( - + ) } @@ -542,7 +514,7 @@ export function EmbedAccount( parentBlockId={parentBlockId} viewType="card" > - + @@ -659,7 +631,7 @@ function AccountInlineEmbed(props: InlineEmbedComponentProps) { function PublicationInlineEmbed(props: InlineEmbedComponentProps) { const pubId = props?.type == 'd' ? props.qid : undefined if (!pubId) throw new Error('Invalid props at PublicationInlineEmbed (pubId)') - const doc = useDocument(pubId, props?.version || undefined) + const doc = useEntity(props) const navigate = useNavigate() return ( - {getDocumentTitle(doc.data)} + {getDocumentTitle(doc.data?.document)} ) } diff --git a/frontend/apps/desktop/src/components/citations.tsx b/frontend/apps/desktop/src/components/citations.tsx index b006ff954..4a2eebda3 100644 --- a/frontend/apps/desktop/src/components/citations.tsx +++ b/frontend/apps/desktop/src/components/citations.tsx @@ -3,7 +3,8 @@ import {AccountLinkAvatar} from '@/components/account-link-avatar' import {useAccount_deprecated} from '@/models/accounts' import {useComment} from '@/models/comments' import {useEntityMentions} from '@/models/content-graph' -import {useDocTextContent, useDocument} from '@/models/documents' +import {useDocTextContent} from '@/models/documents' +import {useEntity} from '@/models/entities' import {DocumentRoute} from '@/utils/routes' import {useNavigate} from '@/utils/useNavigate' import { @@ -46,12 +47,20 @@ function CitationItem({mention}: {mention: Mention}) { function PublicationCitationItem({mention}: {mention: Mention}) { const spawn = useNavigate('spawn') const unpackedSource = unpackHmId(mention.source) - const doc = useDocument(mention.source, mention.sourceBlob?.cid, { - enabled: !!mention.source, - }) - let {data: account} = useAccount_deprecated(doc.data?.author) + const doc = useEntity( + unpackedSource + ? { + ...unpackedSource, + version: mention.sourceBlob?.cid || null, + } + : undefined, + { + enabled: !!unpackedSource, + }, + ) + let {data: account} = useAccount_deprecated(doc.data?.document?.owner) - const docTextContent = useDocTextContent(doc.data) + const docTextContent = useDocTextContent(doc.data?.document) const destRoute: DocumentRoute = { key: 'document', documentId: unpackedSource!.qid, @@ -60,16 +69,18 @@ function PublicationCitationItem({mention}: {mention: Mention}) { } return ( { if (unpackedSource) { spawn(destRoute) } }} - avatar={} + avatar={ + + } /> ) } @@ -88,13 +99,7 @@ function CommentCitationItem({mention}: {mention: Mention}) { return null }, [comment]) - const doc = useDocument( - commentTarget?.qid, - commentTarget?.version || undefined, - { - enabled: !!commentTarget, - }, - ) + const doc = useEntity(commentTarget) let {data: account} = useAccount_deprecated(comment?.author) diff --git a/frontend/apps/desktop/src/components/comments.tsx b/frontend/apps/desktop/src/components/comments.tsx index a7ae45b28..b1c266c02 100644 --- a/frontend/apps/desktop/src/components/comments.tsx +++ b/frontend/apps/desktop/src/components/comments.tsx @@ -1,5 +1,5 @@ import {useAccount_deprecated} from '@/models/accounts' -import {useDocument} from '@/models/documents' +import {useEntity} from '@/models/entities' import {AppDocContentProvider} from '@/pages/document-content-provider' import {trpc} from '@/trpc' import {useNavigate} from '@/utils/useNavigate' @@ -350,10 +350,10 @@ export function CommentPageTitlebarWithDocId({ }) { const docId = useStream(targetDocIdStream) const usableDocId = targetDocId || docId || undefined - const doc = useDocument(usableDocId) + const doc = useEntity(unpackHmId(usableDocId)) const spawn = useNavigate('spawn') - const author = doc.data?.author - const title = getDocumentTitle(doc.data) + const author = doc.data?.document?.owner + const title = getDocumentTitle(doc.data?.document) if (!doc || !author || !title || !usableDocId) return ( @@ -377,7 +377,7 @@ export function CommentPageTitlebarWithDocId({ spawn({ key: 'document', documentId: usableDocId, - versionId: doc.data?.version, + versionId: doc.data?.document?.version, }) }} > diff --git a/frontend/apps/desktop/src/components/commit-draft-button.tsx b/frontend/apps/desktop/src/components/commit-draft-button.tsx index 5a21e0c0d..4ee381d25 100644 --- a/frontend/apps/desktop/src/components/commit-draft-button.tsx +++ b/frontend/apps/desktop/src/components/commit-draft-button.tsx @@ -4,7 +4,7 @@ import {useNavRoute} from '@/utils/navigation' import {DraftRoute} from '@/utils/routes' import {useNavigate} from '@/utils/useNavigate' import {PlainMessage} from '@bufbuild/protobuf' -import {Document} from '@shm/shared' +import {Document, unpackHmId} from '@shm/shared' import { AlertCircle, Button, @@ -30,28 +30,28 @@ export default function CommitDraftButton() { const draftRoute: DraftRoute | null = route.key === 'draft' ? route : null if (!draftRoute) throw new Error('DraftPublicationButtons requires draft route') - const prevProfile = useProfileWithDraft(draftRoute.id) - - console.log(`== ~ CommitDraftButton ~ draft prevProfile:`, prevProfile.draft) + const unpackedDraftId = unpackHmId(draftRoute.id) + const prevProfile = useProfileWithDraft( + unpackedDraftId?.type === 'a' ? unpackedDraftId.eid : undefined, + ) // TODO: add also previous document here const deleteDraft = trpc.drafts.delete.useMutation() const publish = usePublishDraft(grpcClient, draftRoute.id) const invalidate = useQueryInvalidator() function handlePublish() { - console.log('=== PUBLISHING...', prevProfile) - if (prevProfile?.draft) { + if (prevProfile.draft) { publish .mutateAsync({ draft: prevProfile?.draft, previous: prevProfile.profile as PlainMessage, }) .then((res) => { - console.log('== PUBLISHED', res) deleteDraft.mutateAsync(res.id).finally(() => { if (draftRoute?.id) { invalidate(['trpc.drafts.get']) if (draftRoute?.id.startsWith('hm://a/')) { - navigate({key: 'account', accountId: draftRoute.id}) + const accountId = unpackHmId(draftRoute.id)?.eid + accountId && navigate({key: 'account', accountId}) } else { navigate({key: 'document', documentId: res.id}) } diff --git a/frontend/apps/desktop/src/components/contacts-prompt.tsx b/frontend/apps/desktop/src/components/contacts-prompt.tsx index 5ead974f6..04637ae4e 100644 --- a/frontend/apps/desktop/src/components/contacts-prompt.tsx +++ b/frontend/apps/desktop/src/components/contacts-prompt.tsx @@ -1,14 +1,16 @@ -import { AccessURLRow } from '@/url' -import { HYPERMEDIA_PUBLIC_WEB_GATEWAY } from '@shm/shared' -import { Button, Spinner, TextArea, XStack, toast } from '@shm/ui' -import { UserPlus } from '@tamagui/lucide-icons' -import { compressToEncodedURIComponent } from 'lz-string' -import { ComponentProps, useMemo, useState } from 'react' +import {useEntity} from '@/models/entities' +import {getProfileName} from '@/pages/account-page' +import {AccessURLRow} from '@/url' +import {hmId, HYPERMEDIA_PUBLIC_WEB_GATEWAY} from '@shm/shared' +import {Button, Spinner, TextArea, toast, XStack} from '@shm/ui' +import {UserPlus} from '@tamagui/lucide-icons' +import {compressToEncodedURIComponent} from 'lz-string' +import {ComponentProps, useMemo, useState} from 'react' import appError from '../errors' -import { useMyAccount_deprecated } from '../models/accounts' -import { useConnectPeer } from '../models/contacts' -import { useDaemonInfo } from '../models/daemon' -import { usePeerInfo } from '../models/networking' +import {useMyAccount_deprecated} from '../models/accounts' +import {useConnectPeer} from '../models/contacts' +import {useDaemonInfo} from '../models/daemon' +import {usePeerInfo} from '../models/networking' import { AppDialog, DialogCloseButton, @@ -29,13 +31,15 @@ function AddConnectionForm({ onClose, }: { onClose: () => void - input: true | { connectionString?: string; name?: string | undefined } + input: true | {connectionString?: string; name?: string | undefined} }) { const [peerText, setPeer] = useState('') const daemonInfo = useDaemonInfo() const account = useMyAccount_deprecated() + const profile = useEntity(account ? hmId('a', account) : undefined) const deviceId = daemonInfo.data?.peerId const peerInfo = usePeerInfo(deviceId) + console.log('peerInfo', peerInfo.data, deviceId) const connectionString = typeof input === 'object' ? input.connectionString : undefined @@ -47,10 +51,10 @@ function AddConnectionForm({ toast.success('Connection Added') }, onError: (error) => { - appError(`Connect to peer error: ${error?.rawMessage}`, { error }) + appError(`Connect to peer error: ${error?.rawMessage}`, {error}) }, }) - + const myName = getProfileName(profile.data?.document) const connectInfo = useMemo(() => { if (!deviceId || !peerInfo.data?.addrs?.length) return null return compressToEncodedURIComponent( @@ -58,14 +62,14 @@ function AddConnectionForm({ a: peerInfo.data?.addrs.map((addr) => { return addr.split('/p2p/').slice(0, -1).join('/p2p/') }), - n: account.data?.profile?.alias, + n: myName, d: deviceId, }), ) }, [ deviceId, peerInfo.data?.addrs?.length, // explicitly using addrs length because the address list is being polled and frequently changes order, which does not affect connecivity - account.data?.profile?.alias, + myName, ]) return ( <> diff --git a/frontend/apps/desktop/src/components/document-list.tsx b/frontend/apps/desktop/src/components/document-list.tsx index c53cf71c4..2454b446e 100644 --- a/frontend/apps/desktop/src/components/document-list.tsx +++ b/frontend/apps/desktop/src/components/document-list.tsx @@ -1,24 +1,17 @@ -import { useDocumentList } from '@/models/documents' -import { Delete, List, Spinner } from '@shm/ui' +import {useDocumentList} from '@/models/documents' +import {Delete, List, Spinner} from '@shm/ui' -import { useAppContext } from '@/app-context' -import { useCopyGatewayReference } from '@/components/copy-gateway-reference' -import { DocumentListItem } from '@/components/document-list-item' -import { copyLinkMenuItem } from '@/components/list-item' -import { - queryDocument -} from '@/models/documents' -import { getDocumentTitle, unpackHmId } from '@shm/shared' -import { ReactNode } from 'react' -import { useDeleteDialog } from './delete-dialog' +import {useAppContext} from '@/app-context' +import {useCopyGatewayReference} from '@/components/copy-gateway-reference' +import {DocumentListItem} from '@/components/document-list-item' +import {copyLinkMenuItem} from '@/components/list-item' +import {getDocumentTitle, unpackHmId} from '@shm/shared' +import {ReactNode} from 'react' +import {useDeleteDialog} from './delete-dialog' -export function DocumentsFullList({ - header, -}: { - header: ReactNode -}) { +export function DocumentsFullList({header}: {header: ReactNode}) { const documents = useDocumentList({}) - const { queryClient, grpcClient } = useAppContext() + const {queryClient, grpcClient} = useAppContext() const deleteDialog = useDeleteDialog() const items = documents.data.documents @@ -33,8 +26,8 @@ export function DocumentsFullList({ onEndReached={() => { documents.fetchNextPage() }} - renderItem={({ item: document }) => { - const { authors, author } = document + renderItem={({item: document}) => { + const {authors, author} = document if (!document) return null const docId = document.id const id = unpackHmId(docId) @@ -49,15 +42,7 @@ export function DocumentsFullList({ (d) => d.id == document?.id, )} onPointerEnter={() => { - if (document?.id) { - queryClient.client.prefetchQuery( - queryDocument({ - grpcClient, - docId: document.id, - version: document.version, - }), - ) - } + // todo: prefetch here for improved perf }} document={document} author={author} diff --git a/frontend/apps/desktop/src/components/edit-profile-dialog.tsx b/frontend/apps/desktop/src/components/edit-profile-dialog.tsx index 671464296..234d2e277 100644 --- a/frontend/apps/desktop/src/components/edit-profile-dialog.tsx +++ b/frontend/apps/desktop/src/components/edit-profile-dialog.tsx @@ -1,13 +1,5 @@ import {zodResolver} from '@hookform/resolvers/zod' -import { - Button, - DialogTitle, - Form, - Label, - Spinner, - XStack, - YStack, -} from '@shm/ui' +import {Button, Form, Label, XStack, YStack} from '@shm/ui' import {useEffect} from 'react' import {Control, useController, useForm} from 'react-hook-form' import {z} from 'zod' @@ -20,22 +12,11 @@ import {FormError, FormInput} from './form-input' export function useEditProfileDialog() { // for some reason the dialog doesn't work if the input is falsy // input is not needed for this dialog, so we just use "true", lol - return useAppDialog(EditProfileDialog) + return useAppDialog(EditProfileDialog) } function EditProfileDialog({onClose}: {onClose: () => void}) { return null - // const profile = useProfile() - return ( - <> - Edit Profile - {profile ? ( - - ) : ( - - )} - - ) } const profileSchema = z.object({ diff --git a/frontend/apps/desktop/src/components/sidebar-neo.tsx b/frontend/apps/desktop/src/components/sidebar-neo.tsx index 74116ba23..337108857 100644 --- a/frontend/apps/desktop/src/components/sidebar-neo.tsx +++ b/frontend/apps/desktop/src/components/sidebar-neo.tsx @@ -1,10 +1,9 @@ import {focusDraftBlock} from '@/draft-focusing' -import {useMyAccount_deprecated, useProfile} from '@/models/accounts' -import {useDocument} from '@/models/documents' +import {useMyAccount_deprecated} from '@/models/accounts' import { - useEntitiesContent, - useEntityContent, - useEntityRoutes, + useEntity, + useRouteBreadcrumbRoutes, + useRouteEntities, } from '@/models/entities' import {useFavorites} from '@/models/favorites' import {getProfileName} from '@/pages/account-page' @@ -55,10 +54,10 @@ function _SidebarNeo() { const replace = useNavigate('replace') let myAccountSection: ReactNode = null let standaloneSection: ReactNode = null - const entityRoutes = useEntityRoutes(route) + const entityRoutes = useRouteBreadcrumbRoutes(route) const firstEntityRoute = entityRoutes[0] const isMyAccountDraftActive = route.key === 'draft' && route.id === myAccount - const accountEntities = useEntitiesContent( + const accountEntities = useRouteEntities( myAccountRoute ? [myAccountRoute] : [], ) const isMyAccountActive = @@ -67,7 +66,7 @@ function _SidebarNeo() { firstEntityRoute.key === 'account' && firstEntityRoute.accountId === myAccount) // const [collapseMe, setCollapseMe] = useState(!isMyAccountActive) - // const entityContents = useEntitiesContent( + // const entityContents = useRouteEntities( // myAccountRoute ? [myAccountRoute, ...entityRoutes] : entityRoutes, // ) const handleNavigate = useCallback(function handleNavigate( @@ -76,7 +75,6 @@ function _SidebarNeo() { ) { if (doReplace) replace(route) else navigate(route) - // const destEntityRoutes = getEntityRoutes(route) // const firstEntityRoute = destEntityRoutes[0] // const isMyAccountActive = // firstEntityRoute && @@ -141,6 +139,7 @@ function _SidebarNeo() { /> ) : null} + function ResumeDraftButton({info}: {info: ItemDetails}) { if (!info) throw new Error('ItemDetails required for ResumeDraftButton') - const {docId} = info + const {id} = info const navigate = useNavigate() const draft = false @@ -275,7 +272,7 @@ function ContextItems({ return ( <> } /> @@ -727,7 +724,7 @@ function FavoriteAccountItem({ const id = unpackHmId(url) const route = useNavRoute() const accountId = id?.eid - const {data} = useProfile(accountId) + const {data} = useEntity(id) if (!accountId) return null return ( { onNavigate({key: 'account', accountId}) }} - title={getProfileName(data)} + title={getProfileName(data?.document)} /> ) } @@ -750,7 +747,7 @@ function FavoritePublicationItem({ }) { const id = unpackHmId(url) const route = useNavRoute() - const doc = useDocument(id?.qid, id?.version || undefined) + const doc = useEntity(id) const documentId = id?.qid if (!documentId) return null return ( @@ -764,7 +761,7 @@ function FavoritePublicationItem({ versionId: id?.version || undefined, }) }} - title={getDocumentTitle(doc)} + title={getDocumentTitle(doc.data?.document)} /> ) } diff --git a/frontend/apps/desktop/src/components/sidebar.tsx b/frontend/apps/desktop/src/components/sidebar.tsx index 711b712f1..ab95fd085 100644 --- a/frontend/apps/desktop/src/components/sidebar.tsx +++ b/frontend/apps/desktop/src/components/sidebar.tsx @@ -1,40 +1,9 @@ -import {useAccount_deprecated, useProfile} from '@/models/accounts' -import {useDocument, useDocumentEmbeds} from '@/models/documents' -import {useFavorites} from '@/models/favorites' -import {appRouteOfId, getRouteKey, useNavRoute} from '@/utils/navigation' -import {getRouteContext, getRouteParentContext} from '@/utils/route-context' -import { - AccountRoute, - BaseEntityRoute, - DocumentRoute, - NavRoute, -} from '@/utils/routes' +import {useNavRoute} from '@/utils/navigation' import {useNavigate} from '@/utils/useNavigate' -import { - HMBlockNode, - HMDocument, - UnpackedHypermediaId, - getDocumentTitle, - unpackHmId, -} from '@shm/shared' -import {Button, Home, SizableText, XStack, YStack} from '@shm/ui' -import { - Contact, - File, - FileText, - Hash, - Sparkles, - Star, -} from '@tamagui/lucide-icons' -import {PropsWithChildren, ReactNode, memo, useMemo} from 'react' -import { - GenericSidebarContainer, - SidebarDivider, - SidebarGroupItem, - SidebarItem, - activeDocOutline, - getDocOutline, -} from './sidebar-base' +import {Home} from '@shm/ui' +import {Contact, File, Sparkles} from '@tamagui/lucide-icons' +import {memo} from 'react' +import {GenericSidebarContainer, SidebarItem} from './sidebar-base' import {SidebarNeo} from './sidebar-neo' export const AppSidebar = memo(MainAppSidebar) @@ -92,512 +61,7 @@ export function MainAppSidebar() { title="Contacts" bold /> - - {/* - */} - {/* {account.data && ( - - )} */} - {/* {myAccountOutlineContent} */} - {/* {myAccount.data?.id && ( - - )} - - {myAccountRoute ? null : ( - - )} */} ) } - -function SidebarFavorites() { - const navigate = useNavigate() - const favorites = useFavorites() - const route = useNavRoute() - return ( - { - navigate({key: 'favorites'}) - }} - title="Favorites" - bold - icon={Star} - rightHover={ - [ - // {}, - // }, - // ]} - // />, - ] - } - items={favorites.map((fav) => { - const {key, url} = fav - if (key === 'account') { - return - } - if (key === 'document') { - return - } - return null - })} - /> - ) -} - -function FavoriteAccountItem({url}: {url: string}) { - const id = unpackHmId(url) - const route = useNavRoute() - const accountId = id?.eid - const account = useAccount_deprecated(accountId) - const navigate = useNavigate() - if (!accountId) return null - return ( - { - navigate({key: 'account', accountId}) - }} - title={account.data?.profile?.alias || 'Unknown Account'} - /> - ) -} - -function FavoritePublicationItem({url}: {url: string}) { - const id = unpackHmId(url) - const route = useNavRoute() - const navigate = useNavigate() - const doc = useDocument(id?.qid, id?.version || undefined) - const documentId = id?.qid - if (!documentId) return null - return ( - { - navigate({ - key: 'document', - documentId, - versionId: id?.version || undefined, - }) - }} - title={getDocumentTitle(doc.data)} - /> - ) -} - -function RouteOutline({ - route, - myAccountId, -}: { - route: NavRoute - myAccountId: string | undefined -}) { - if (route.key === 'draft') { - if (route.contextRoute?.key === 'document') { - return ( - <> - - - - ) - } - if (route.contextRoute?.key === 'account') { - return ( - <> - - - - ) - } - } - if (route.key === 'account') { - if (route.accountId === myAccountId) return null - return ( - <> - - - - ) - } - if (route.key === 'document') { - return ( - <> - - - - ) - } - return null -} - -function useNavigateBlock(fromRoute: NavRoute) { - const replace = useNavigate('replace') - const thisRoute = useNavRoute() - const navigate = useNavigate() - function navigateBlock( - blockId: string, - entityId?: UnpackedHypermediaId, - parentBlockId?: string, - ) { - if (entityId || getRouteKey(fromRoute) !== getRouteKey(thisRoute)) { - const destRoute = entityId ? appRouteOfId(entityId) : fromRoute - // uh, I'm sure this code is buggy: - const context = entityId ? getRouteContext(fromRoute, parentBlockId) : [] - if (destRoute?.key === 'document') { - navigate({ - ...destRoute, - context, - blockId, - }) - } else if (destRoute?.key === 'account') { - navigate({ - ...destRoute, - tab: 'profile', - context, - blockId, - }) - } else if (destRoute) { - navigate(destRoute) - } - } else if (fromRoute.key === 'document') { - replace({ - ...fromRoute, - blockId, - }) - } else if (fromRoute.key === 'account') { - replace({ - ...fromRoute, - tab: 'profile', - blockId, - }) - } - } - function focusBlock( - blockId: string, - entityId?: UnpackedHypermediaId, - parentBlockId?: string, - ) { - const context = getRouteParentContext(fromRoute) - const destRoute = entityId ? appRouteOfId(entityId) : fromRoute - if (destRoute?.key === 'document') { - navigate({ - ...destRoute, - context, - blockId: blockId, - isBlockFocused: true, - }) - } else if (destRoute?.key === 'account') { - navigate({ - ...destRoute, - context, - blockId: blockId, - isBlockFocused: true, - }) - } else if (destRoute) { - navigate(destRoute) - } - } - return {navigateBlock, focusBlock} -} - -function useContextItems(context: BaseEntityRoute[] | undefined) { - const items = - context?.map((contextRoute, index) => { - if (contextRoute.key === 'account') - return ( - context.slice(0, index)} - /> - ) - if (contextRoute.key === 'document') - return ( - context.slice(0, index)} - /> - ) - return null - }) || null - return {items} -} - -function useIntermediateContext( - route: BaseEntityRoute, - document: HMDocument | undefined | null, - blockId: string | undefined, -) { - const headings = useMemo(() => { - if (!blockId || !document) return null - let blockHeadings: null | {id: string; text: string}[] = null - if (!blockId) return [] - function findBlock( - nodes: HMBlockNode[] | undefined, - parentHeadings: {id: string; text: string}[], - ) { - return nodes?.find((blockNode) => { - if (!blockId) return null - if (blockNode.block.id === blockId) { - blockHeadings = parentHeadings - return true - } - if (blockNode.children?.length) { - return findBlock(blockNode.children, [ - ...parentHeadings, - {id: blockNode.block.id, text: blockNode.block.text}, - ]) - } - return false - }) - } - findBlock(document?.content, []) - return blockHeadings as null | {id: string; text: string}[] - }, [document, blockId]) - const navigate = useNavigate() - return headings?.map((heading) => { - return ( - { - navigate({...route, blockId: heading.id}) - }} - /> - ) - }) -} - -function AccountContextItem({ - route, - getContext, -}: { - route: AccountRoute - getContext: () => BaseEntityRoute[] -}) { - const unpacked = unpackHmId(route.accountId) - const {data} = useProfile(unpacked?.eid) - const navigate = useNavigate() - return ( - <> - { - navigate({...route, blockId: undefined, isBlockFocused: undefined}) - }} - /> - {useIntermediateContext(route, data?.profile, route.blockId)} - - ) -} - -function PublicationContextItem({ - route, - getContext, -}: { - route: DocumentRoute - getContext: () => BaseEntityRoute[] -}) { - const doc = useDocument(route.documentId, route.versionId) - const navigate = useNavigate() - return ( - <> - { - navigate({...route, blockId: undefined}) - }} - /> - {useIntermediateContext(route, doc.data, route.blockId)} - - ) -} - -function isDraftActive(route: NavRoute, documentId: string | undefined) { - return route.key === 'draft' && route.draftId === documentId -} - -function AccountRouteOutline({route}: {route: AccountRoute}) { - const activeRoute = useNavRoute() - const isActive = - activeRoute.key === 'account' && activeRoute.accountId === route.accountId - const account = useAccount_deprecated(route.accountId) - const profilePub = useProfilePublicationWithDraft(route.accountId) - const pubEmbeds = useDocumentEmbeds( - profilePub.data?.document, - !!profilePub.data, - { - skipCards: true, - }, - ) - const docOutline = getDocOutline( - profilePub?.data?.document?.children || [], - pubEmbeds, - ) - const navigate = useNavigate() - const replace = useNavigate('replace') - const {navigateBlock, focusBlock} = useNavigateBlock(route) - const {outlineContent, isBlockActive, isBlockFocused} = activeDocOutline( - docOutline, - route.isBlockFocused ? undefined : route.blockId, - route.isBlockFocused ? route.blockId : undefined, - pubEmbeds, - navigateBlock, - focusBlock, - navigate, - ) - const {items: parentContext} = useContextItems(route.context) - const isRootActive = isActive && !isBlockFocused - return ( - <> - {parentContext} - { - if (!isRootActive) { - replace({ - ...route, - blockId: undefined, - isBlockFocused: undefined, - }) - } - }} - title={account.data?.profile?.alias} - icon={Contact} - items={outlineContent} - defaultExpanded - /> - } - isDraft={profilePub.data?.isDocumentDraft} - onPressDraft={ - isDraftActive(activeRoute, profilePub?.data?.document?.id) - ? null - : () => { - navigate({ - key: 'draft', - draftId: profilePub.data?.drafts?.[0]?.id, - contextRoute: route, - }) - } - } - > - - ) -} - -function DocumentRouteOutline({route}: {route: DocumentRoute}) { - const activeRoute = useNavRoute() - const doc = useDocument(route.documentId, route.versionId) - const pubEmbeds = useDocumentEmbeds(doc.data, !!doc.data, { - skipCards: true, - }) - const outline = getDocOutline(doc.data?.content || [], pubEmbeds) - const navigate = useNavigate() - const replace = useNavigate('replace') - const {navigateBlock, focusBlock} = useNavigateBlock(route) - const {outlineContent, isBlockActive} = activeDocOutline( - outline, - route.isBlockFocused ? undefined : route.blockId, - route.isBlockFocused ? route.blockId : undefined, - pubEmbeds, - navigateBlock, - focusBlock, - navigate, - ) - const {items: parentContext} = useContextItems(route.context) - return ( - <> - {parentContext} - { - if (route.blockId) { - replace({...route, blockId: undefined}) - } - }} - title={pub.data?.document?.title} - icon={FileText} - items={outlineContent} - defaultExpanded - /> - } - isDraft={pub.data?.isDocumentDraft} - onPressDraft={() => { - isDraftActive(activeRoute, pub?.data?.document?.id) - ? null - : navigate({ - key: 'draft', - draftId: pub.data?.drafts?.[0]?.id, - contextRoute: route, - variant: null, - }) - }} - > - - ) -} - -function DraftItems({ - titleItem, - children, - onPressDraft, - isDraft, -}: PropsWithChildren<{ - titleItem: ReactNode - onPressDraft: null | (() => void) - isDraft?: boolean -}>) { - return ( - - {titleItem} - {isDraft ? ( - - - Unsaved Document - - {onPressDraft ? ( - - ) : null} - - ) : null} - {children} - - ) -} diff --git a/frontend/apps/desktop/src/components/titlebar-common.tsx b/frontend/apps/desktop/src/components/titlebar-common.tsx index 91850ac71..79c458ef0 100644 --- a/frontend/apps/desktop/src/components/titlebar-common.tsx +++ b/frontend/apps/desktop/src/components/titlebar-common.tsx @@ -6,12 +6,10 @@ import {useEditProfileDialog} from '@/components/edit-profile-dialog' import {useFavoriteMenuItem} from '@/components/favoriting' import {MenuItemType, OptionsDropdown} from '@/components/options-dropdown' import {DraftPublicationButtons, VersionContext} from '@/components/variants' -import { - useAccount_deprecated, - useMyAccountIds, - useProfileWithDraft, -} from '@/models/accounts' -import {useDocument, usePushPublication} from '@/models/documents' +import {useAccount_deprecated, useProfileWithDraft} from '@/models/accounts' +import {useMyAccountIds} from '@/models/daemon' +import {usePushPublication} from '@/models/documents' +import {useEntity} from '@/models/entities' import {useGatewayHost, useGatewayUrl} from '@/models/gateway-settings' import {SidebarWidth, useSidebarContext} from '@/sidebar-context' import { @@ -67,12 +65,13 @@ export function DocOptionsButton() { throw new Error( 'DocOptionsButton can only be rendered on publication route', ) - const docId = route.documentId + const docId = unpackHmId(route.documentId) + if (!docId) throw new Error('Invalid document ID') const gwHost = useGatewayHost() const push = usePushPublication() const deleteEntity = useDeleteDialog() const [copyContent, onCopy, host] = useCopyGatewayReference() - const doc = useDocument(route.documentId, route.versionId) + const doc = useEntity({...docId, version: route.versionId || null}) const menuItems: MenuItemType[] = [ { key: 'link', @@ -108,7 +107,7 @@ export function DocOptionsButton() { onPress: () => { deleteEntity.open({ id: route.documentId, - title: getDocumentTitle(doc.data), + title: getDocumentTitle(doc.data?.document), onSuccess: () => { dispatch({type: 'pop'}) }, @@ -116,9 +115,8 @@ export function DocOptionsButton() { }, }, ] - const id = unpackHmId(docId) - const docUrl = id - ? createHmId('d', id.eid, { + const docUrl = docId + ? createHmId('d', docId.eid, { version: route.versionId, }) : null @@ -148,7 +146,7 @@ export function AccountOptionsButton() { const editProfileDialog = useEditProfileDialog() const myAccountIds = useMyAccountIds() const {profile} = useProfileWithDraft(route.accountId) - const isMyAccount = myAccountIds.includes(route.accountId) + const isMyAccount = myAccountIds.data?.includes(route.accountId) if (isMyAccount) { menuItems.push({ key: 'edit-account', @@ -192,21 +190,21 @@ function EditAccountButton() { const myAccountIds = useMyAccountIds() const navigate = useNavigate() const {draft} = useProfileWithDraft(route.accountId) - if (!myAccountIds.includes(route.accountId)) { + if (!myAccountIds.data?.includes(route.accountId)) { return null } if (route.tab !== 'profile' && route.tab) return null const hasExistingDraft = !!draft return ( <> - + - )} -
- - - - - -
- - -
- + <> + + +

home page

+ {keys.data?.length ? ( + + {keys.data.map((key) => ( + + ))} + + ) : ( + + )} +
+ + + + + +
+ + +
+
+