Files
standardnotes-app-web/packages/web/src/javascripts/Components/LinkedItems/LinkedItemBubblesContainer.tsx

132 lines
4.4 KiB
TypeScript

import { observer } from 'mobx-react-lite'
import ItemLinkAutocompleteInput from './ItemLinkAutocompleteInput'
import { LinkingController } from '@/Controllers/LinkingController'
import LinkedItemBubble from './LinkedItemBubble'
import { useCallback, useEffect, useMemo, useState } from 'react'
import { useResponsiveAppPane } from '../ResponsivePane/ResponsivePaneProvider'
import { ElementIds } from '@/Constants/ElementIDs'
import { classNames } from '@/Utils/ConcatenateClassNames'
import { ContentType } from '@standardnotes/snjs'
import { LinkableItem } from '@/Utils/Items/Search/LinkableItem'
import { ItemLink } from '@/Utils/Items/Search/ItemLink'
import { FOCUS_TAGS_INPUT_COMMAND, keyboardStringForShortcut } from '@standardnotes/ui-services'
import { useCommandService } from '../ApplicationView/CommandProvider'
type Props = {
linkingController: LinkingController
}
const LinkedItemBubblesContainer = ({ linkingController }: Props) => {
const { toggleAppPane } = useResponsiveAppPane()
const commandService = useCommandService()
const {
allItemLinks,
notesLinkingToActiveItem,
filesLinkingToActiveItem,
unlinkItemFromSelectedItem: unlinkItem,
activateItem,
} = linkingController
useEffect(() => {
return commandService.addCommandHandler({
command: FOCUS_TAGS_INPUT_COMMAND,
onKeyDown: () => {
const input = document.getElementById(ElementIds.ItemLinkAutocompleteInput)
if (input) {
input.focus()
}
},
})
}, [commandService])
const shortcut = useMemo(
() => keyboardStringForShortcut(commandService.keyboardShortcutForCommand(FOCUS_TAGS_INPUT_COMMAND)),
[commandService],
)
const [focusedId, setFocusedId] = useState<string>()
const focusableIds = allItemLinks
.map((link) => link.id)
.concat(
notesLinkingToActiveItem.map((link) => link.id),
filesLinkingToActiveItem.map((link) => link.id),
[ElementIds.ItemLinkAutocompleteInput],
)
const focusPreviousItem = useCallback(() => {
const currentFocusedIndex = focusableIds.findIndex((id) => id === focusedId)
const previousIndex = currentFocusedIndex - 1
if (previousIndex > -1) {
setFocusedId(focusableIds[previousIndex])
}
}, [focusableIds, focusedId])
const focusNextItem = useCallback(() => {
const currentFocusedIndex = focusableIds.findIndex((id) => id === focusedId)
const nextIndex = currentFocusedIndex + 1
if (nextIndex < focusableIds.length) {
setFocusedId(focusableIds[nextIndex])
}
}, [focusableIds, focusedId])
const activateItemAndTogglePane = useCallback(
async (item: LinkableItem) => {
const paneId = await activateItem(item)
if (paneId) {
toggleAppPane(paneId)
}
},
[activateItem, toggleAppPane],
)
const isItemBidirectionallyLinked = (link: ItemLink) => {
const existsInAllItemLinks = !!allItemLinks.find((item) => link.item.uuid === item.item.uuid)
const existsInNotesLinkingToItem = !!notesLinkingToActiveItem.find((item) => link.item.uuid === item.item.uuid)
const existsInFilesLinkingToItem = !!filesLinkingToActiveItem.find((item) => link.item.uuid === item.item.uuid)
return (
existsInAllItemLinks &&
(link.item.content_type === ContentType.Note ? existsInNotesLinkingToItem : existsInFilesLinkingToItem)
)
}
return (
<div
className={classNames(
'note-view-linking-container hidden min-w-80 max-w-full flex-wrap items-center gap-2 bg-transparent md:-mr-2 md:flex',
allItemLinks.length || notesLinkingToActiveItem.length ? 'mt-1' : 'mt-0.5',
)}
>
{allItemLinks
.concat(notesLinkingToActiveItem)
.concat(filesLinkingToActiveItem)
.map((link) => (
<LinkedItemBubble
link={link}
key={link.id}
activateItem={activateItemAndTogglePane}
unlinkItem={unlinkItem}
focusPreviousItem={focusPreviousItem}
focusNextItem={focusNextItem}
focusedId={focusedId}
setFocusedId={setFocusedId}
isBidirectional={isItemBidirectionallyLinked(link)}
/>
))}
<ItemLinkAutocompleteInput
focusedId={focusedId}
linkingController={linkingController}
focusPreviousItem={focusPreviousItem}
setFocusedId={setFocusedId}
hoverLabel={`Focus input to add a link (${shortcut})`}
/>
</div>
)
}
export default observer(LinkedItemBubblesContainer)