127 lines
3.7 KiB
TypeScript
127 lines
3.7 KiB
TypeScript
import { ItemLink, LinkableItem, LinkingController } from '@/Controllers/LinkingController'
|
|
import { classNames } from '@/Utils/ConcatenateClassNames'
|
|
import { KeyboardKey } from '@standardnotes/ui-services'
|
|
import { observer } from 'mobx-react-lite'
|
|
import { KeyboardEventHandler, MouseEventHandler, useEffect, useRef, useState } from 'react'
|
|
import { ContentType } from '@standardnotes/snjs'
|
|
import Icon from '../Icon/Icon'
|
|
|
|
type Props = {
|
|
link: ItemLink
|
|
getItemIcon: LinkingController['getLinkedItemIcon']
|
|
getTitleForLinkedTag: LinkingController['getTitleForLinkedTag']
|
|
activateItem: (item: LinkableItem) => Promise<void>
|
|
unlinkItem: LinkingController['unlinkItemFromSelectedItem']
|
|
focusPreviousItem: () => void
|
|
focusNextItem: () => void
|
|
focusedId: string | undefined
|
|
setFocusedId: (id: string) => void
|
|
isBidirectional: boolean
|
|
}
|
|
|
|
const LinkedItemBubble = ({
|
|
link,
|
|
getItemIcon,
|
|
getTitleForLinkedTag,
|
|
activateItem,
|
|
unlinkItem,
|
|
focusPreviousItem,
|
|
focusNextItem,
|
|
focusedId,
|
|
setFocusedId,
|
|
isBidirectional,
|
|
}: Props) => {
|
|
const ref = useRef<HTMLButtonElement>(null)
|
|
|
|
const [showUnlinkButton, setShowUnlinkButton] = useState(false)
|
|
const unlinkButtonRef = useRef<HTMLAnchorElement | null>(null)
|
|
|
|
const [wasClicked, setWasClicked] = useState(false)
|
|
|
|
const handleFocus = () => {
|
|
if (focusedId !== link.id) {
|
|
setFocusedId(link.id)
|
|
}
|
|
setShowUnlinkButton(true)
|
|
}
|
|
|
|
const onBlur = () => {
|
|
setShowUnlinkButton(false)
|
|
setWasClicked(false)
|
|
}
|
|
|
|
const onClick: MouseEventHandler = (event) => {
|
|
if (wasClicked && event.target !== unlinkButtonRef.current) {
|
|
setWasClicked(false)
|
|
void activateItem(link.item)
|
|
} else {
|
|
setWasClicked(true)
|
|
}
|
|
}
|
|
|
|
const onUnlinkClick: MouseEventHandler = (event) => {
|
|
event.stopPropagation()
|
|
unlinkItem(link)
|
|
}
|
|
|
|
const onKeyDown: KeyboardEventHandler = (event) => {
|
|
switch (event.key) {
|
|
case KeyboardKey.Backspace: {
|
|
focusPreviousItem()
|
|
unlinkItem(link)
|
|
break
|
|
}
|
|
case KeyboardKey.Left:
|
|
focusPreviousItem()
|
|
break
|
|
case KeyboardKey.Right:
|
|
focusNextItem()
|
|
break
|
|
}
|
|
}
|
|
|
|
const [icon, iconClassName] = getItemIcon(link.item)
|
|
const tagTitle = getTitleForLinkedTag(link.item)
|
|
|
|
useEffect(() => {
|
|
if (link.id === focusedId) {
|
|
ref.current?.focus()
|
|
}
|
|
}, [focusedId, link.id])
|
|
|
|
return (
|
|
<button
|
|
ref={ref}
|
|
className="group flex h-6 cursor-pointer items-center rounded border-0 bg-passive-4-opacity-variant py-2 pl-1 pr-2 text-xs text-text hover:bg-contrast focus:bg-contrast"
|
|
onFocus={handleFocus}
|
|
onBlur={onBlur}
|
|
onClick={onClick}
|
|
title={tagTitle ? tagTitle.longTitle : link.item.title}
|
|
onKeyDown={onKeyDown}
|
|
>
|
|
<Icon type={icon} className={classNames('mr-1 flex-shrink-0', iconClassName)} size="small" />
|
|
<span className="max-w-290px flex items-center overflow-hidden overflow-ellipsis whitespace-nowrap">
|
|
{tagTitle && <span className="text-passive-1">{tagTitle.titlePrefix}</span>}
|
|
<span className="flex items-center gap-1">
|
|
{link.relationWithSelectedItem === 'indirect' && link.item.content_type !== ContentType.Tag && (
|
|
<span className={!isBidirectional ? 'hidden group-focus:block' : ''}>Linked By:</span>
|
|
)}
|
|
{link.item.title}
|
|
</span>
|
|
</span>
|
|
{showUnlinkButton && (
|
|
<a
|
|
ref={unlinkButtonRef}
|
|
role="button"
|
|
className="ml-2 -mr-1 flex cursor-pointer border-0 bg-transparent p-0"
|
|
onClick={onUnlinkClick}
|
|
>
|
|
<Icon type="close" className="text-neutral hover:text-info" size="small" />
|
|
</a>
|
|
)}
|
|
</button>
|
|
)
|
|
}
|
|
|
|
export default observer(LinkedItemBubble)
|