Skip to content

Commit

Permalink
Copy latest links when appropriate
Browse files Browse the repository at this point in the history
  • Loading branch information
ericvicenti committed Dec 4, 2024
1 parent 467d75f commit 743f777
Show file tree
Hide file tree
Showing 8 changed files with 185 additions and 336 deletions.
168 changes: 168 additions & 0 deletions frontend/apps/desktop/src/components/copy-reference-button.tsx
Original file line number Diff line number Diff line change
@@ -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 = () => (
<CurrentIcon
size={12}
color="$color5"
opacity={shouldOpen ? 1 : showIconOnHover ? 0 : 1}
$group-item-hover={{opacity: 1, color: '$color6'}}
/>
)
return (
<>
<Tooltip
content={
shouldOpen
? `Open ${reference.label} Link in Web Browser`
: `Copy ${reference.label} Link`
}
>
<Button
flexShrink={0}
flexGrow={0}
onHoverOut={() => {
setShouldOpen(false)
}}
aria-label={`${shouldOpen ? 'Open' : 'Copy'} ${reference.label} Link`}
chromeless
size="$2"
group="item"
theme="brand"
bg="$colorTransparent"
borderColor="$colorTransparent"
onPress={(e) => {
e.stopPropagation()
e.preventDefault()
if (shouldOpen) {
setShouldOpen(false)
externalOpen(reference.url)
} else {
setShouldOpen(true)
// in theory we should save this timeout in a ref and deal with it upon unmount. in practice it doesn't matter
setTimeout(() => {
setShouldOpen(false)
}, 5000)
reference.onCopy()
}
}}
hoverStyle={{
backgroundColor: '$colorTransparent',
borderColor: '$colorTransparent',
...props.hoverStyle,
}}
{...props}
>
{iconPosition == 'before' ? <Icon /> : null}
{children}
{iconPosition == 'after' ? <Icon /> : null}
</Button>
</Tooltip>
{reference.content}
</>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -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') {
Expand Down
181 changes: 2 additions & 179 deletions frontend/apps/desktop/src/components/directory.tsx
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -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 (
<Button
group="item"
borderWidth={0}
hoverStyle={{
bg: itemHoverBgColor,
}}
w="100%"
paddingHorizontal={16}
paddingVertical="$1"
onPress={goToDraft}
h={60}
icon={
draft.data?.metadata.icon ? (
<HMIcon size={28} id={id} metadata={draft.data.metadata} />
) : undefined
}
>
<XStack gap="$2" ai="center" f={1} paddingVertical="$2">
<YStack f={1} gap="$1.5">
<XStack ai="center" gap="$2" paddingLeft={4} f={1} w="100%">
<SizableText
fontWeight="bold"
textOverflow="ellipsis"
whiteSpace="nowrap"
overflow="hidden"
>
{draft.data?.metadata.name || 'Untitled'}
</SizableText>
<DraftTag />
</XStack>
<PathButton
docId={id}
isDraft
path={
!!draft.data?.metadata?.name &&
id.path &&
id.path.at(-1)?.startsWith('_')
? `${pathNameify(draft.data.metadata.name)}`
: id.path
? `${id.path.at(-1)}`
: ''
}
/>
</YStack>
</XStack>
<XStack gap="$3" ai="center">
{/* <Button theme="yellow" icon={Pencil} size="$2">
Resume Editing
</Button> */}
{draft.data?.lastUpdateTime ? (
<Tooltip
content={`Last update: ${formattedDateLong(
new Date(draft.data.lastUpdateTime),
)}`}
>
<SizableText size="$1">
{formattedDateMedium(new Date(draft.data.lastUpdateTime))}
</SizableText>
</Tooltip>
) : null}

{/* <XStack>
<DocumentEditors entry={id} />
</XStack> */}
</XStack>
</Button>
)
}

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 (
<DirectoryItem
PathButtonComponent={PathButton}
entry={entry}
authorsMetadata={authorsMetadata}
/>
)
}

function PathButton({
path,
docId,
isDraft = false,
}: {
path: string
docId: UnpackedHypermediaId
isDraft?: boolean
}) {
const Comp = !isDraft ? CopyReferenceButton : XStack
return (
<Comp
isBlockFocused={false}
docId={docId}
alignSelf="flex-start"
ai="center"
group="item"
// gap="$2"
bg="$colorTransparent"
borderColor="$colorTransparent"
borderWidth={0}
size="$1"
maxWidth="100%"
overflow="hidden"
copyIcon={Copy}
iconPosition="after"
showIconOnHover
>
<SizableText
color="$brand5"
size="$1"
$group-item-hover={
isDraft
? undefined
: {
color: '$brand6',
textDecorationLine: 'underline',
}
}
textOverflow="ellipsis"
whiteSpace="nowrap"
overflow="hidden"
>
{path}
</SizableText>
</Comp>
)
}
Loading

0 comments on commit 743f777

Please sign in to comment.