refactor: de-couple linking controller from active item (#2108)
This commit is contained in:
@@ -27,6 +27,7 @@ import ApplicationProvider from '../ApplicationProvider'
|
|||||||
import CommandProvider from '../CommandProvider'
|
import CommandProvider from '../CommandProvider'
|
||||||
import PanesSystemComponent from '../Panes/PanesSystemComponent'
|
import PanesSystemComponent from '../Panes/PanesSystemComponent'
|
||||||
import DotOrgNotice from './DotOrgNotice'
|
import DotOrgNotice from './DotOrgNotice'
|
||||||
|
import LinkingControllerProvider from '@/Controllers/LinkingControllerProvider'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
application: WebApplication
|
application: WebApplication
|
||||||
@@ -177,62 +178,61 @@ const ApplicationView: FunctionComponent<Props> = ({ application, mainApplicatio
|
|||||||
application={application}
|
application={application}
|
||||||
featuresController={viewControllerManager.featuresController}
|
featuresController={viewControllerManager.featuresController}
|
||||||
>
|
>
|
||||||
<div className={platformString + ' main-ui-view sn-component h-full'}>
|
<LinkingControllerProvider controller={viewControllerManager.linkingController}>
|
||||||
<FileDragNDropProvider
|
<div className={platformString + ' main-ui-view sn-component h-full'}>
|
||||||
application={application}
|
<FileDragNDropProvider
|
||||||
featuresController={viewControllerManager.featuresController}
|
|
||||||
filesController={viewControllerManager.filesController}
|
|
||||||
>
|
|
||||||
<PanesSystemComponent />
|
|
||||||
</FileDragNDropProvider>
|
|
||||||
|
|
||||||
<>
|
|
||||||
<Footer application={application} applicationGroup={mainApplicationGroup} />
|
|
||||||
<SessionsModal application={application} viewControllerManager={viewControllerManager} />
|
|
||||||
<PreferencesViewWrapper viewControllerManager={viewControllerManager} application={application} />
|
|
||||||
<RevisionHistoryModal
|
|
||||||
application={application}
|
application={application}
|
||||||
historyModalController={viewControllerManager.historyModalController}
|
|
||||||
notesController={viewControllerManager.notesController}
|
|
||||||
selectionController={viewControllerManager.selectionController}
|
|
||||||
subscriptionController={viewControllerManager.subscriptionController}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
|
|
||||||
{renderChallenges()}
|
|
||||||
|
|
||||||
<>
|
|
||||||
<NotesContextMenu
|
|
||||||
application={application}
|
|
||||||
navigationController={viewControllerManager.navigationController}
|
|
||||||
notesController={viewControllerManager.notesController}
|
|
||||||
linkingController={viewControllerManager.linkingController}
|
|
||||||
historyModalController={viewControllerManager.historyModalController}
|
|
||||||
/>
|
|
||||||
<TagContextMenuWrapper
|
|
||||||
navigationController={viewControllerManager.navigationController}
|
|
||||||
featuresController={viewControllerManager.featuresController}
|
featuresController={viewControllerManager.featuresController}
|
||||||
/>
|
|
||||||
<FileContextMenuWrapper
|
|
||||||
filesController={viewControllerManager.filesController}
|
filesController={viewControllerManager.filesController}
|
||||||
selectionController={viewControllerManager.selectionController}
|
>
|
||||||
/>
|
<PanesSystemComponent />
|
||||||
<PurchaseFlowWrapper application={application} viewControllerManager={viewControllerManager} />
|
</FileDragNDropProvider>
|
||||||
<ConfirmSignoutContainer
|
<>
|
||||||
applicationGroup={mainApplicationGroup}
|
<Footer application={application} applicationGroup={mainApplicationGroup} />
|
||||||
viewControllerManager={viewControllerManager}
|
<SessionsModal application={application} viewControllerManager={viewControllerManager} />
|
||||||
application={application}
|
<PreferencesViewWrapper viewControllerManager={viewControllerManager} application={application} />
|
||||||
/>
|
<RevisionHistoryModal
|
||||||
<ToastContainer />
|
application={application}
|
||||||
<FilePreviewModalWrapper application={application} viewControllerManager={viewControllerManager} />
|
historyModalController={viewControllerManager.historyModalController}
|
||||||
<PermissionsModalWrapper application={application} />
|
notesController={viewControllerManager.notesController}
|
||||||
<ConfirmDeleteAccountContainer
|
selectionController={viewControllerManager.selectionController}
|
||||||
application={application}
|
subscriptionController={viewControllerManager.subscriptionController}
|
||||||
viewControllerManager={viewControllerManager}
|
/>
|
||||||
/>
|
</>
|
||||||
</>
|
{renderChallenges()}
|
||||||
{application.routeService.isDotOrg && <DotOrgNotice />}
|
<>
|
||||||
</div>
|
<NotesContextMenu
|
||||||
|
application={application}
|
||||||
|
navigationController={viewControllerManager.navigationController}
|
||||||
|
notesController={viewControllerManager.notesController}
|
||||||
|
linkingController={viewControllerManager.linkingController}
|
||||||
|
historyModalController={viewControllerManager.historyModalController}
|
||||||
|
/>
|
||||||
|
<TagContextMenuWrapper
|
||||||
|
navigationController={viewControllerManager.navigationController}
|
||||||
|
featuresController={viewControllerManager.featuresController}
|
||||||
|
/>
|
||||||
|
<FileContextMenuWrapper
|
||||||
|
filesController={viewControllerManager.filesController}
|
||||||
|
selectionController={viewControllerManager.selectionController}
|
||||||
|
/>
|
||||||
|
<PurchaseFlowWrapper application={application} viewControllerManager={viewControllerManager} />
|
||||||
|
<ConfirmSignoutContainer
|
||||||
|
applicationGroup={mainApplicationGroup}
|
||||||
|
viewControllerManager={viewControllerManager}
|
||||||
|
application={application}
|
||||||
|
/>
|
||||||
|
<ToastContainer />
|
||||||
|
<FilePreviewModalWrapper application={application} viewControllerManager={viewControllerManager} />
|
||||||
|
<PermissionsModalWrapper application={application} />
|
||||||
|
<ConfirmDeleteAccountContainer
|
||||||
|
application={application}
|
||||||
|
viewControllerManager={viewControllerManager}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
{application.routeService.isDotOrg && <DotOrgNotice />}
|
||||||
|
</div>
|
||||||
|
</LinkingControllerProvider>
|
||||||
</PremiumModalProvider>
|
</PremiumModalProvider>
|
||||||
</ResponsivePaneProvider>
|
</ResponsivePaneProvider>
|
||||||
</AndroidBackHandlerProvider>
|
</AndroidBackHandlerProvider>
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import { ElementIds } from '@/Constants/ElementIDs'
|
|||||||
import Menu from '../Menu/Menu'
|
import Menu from '../Menu/Menu'
|
||||||
import { getLinkingSearchResults } from '@/Utils/Items/Search/getSearchResults'
|
import { getLinkingSearchResults } from '@/Utils/Items/Search/getSearchResults'
|
||||||
import { useApplication } from '../ApplicationProvider'
|
import { useApplication } from '../ApplicationProvider'
|
||||||
|
import { DecryptedItem } from '@standardnotes/snjs'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
linkingController: LinkingController
|
linkingController: LinkingController
|
||||||
@@ -27,6 +28,7 @@ type Props = {
|
|||||||
focusedId: string | undefined
|
focusedId: string | undefined
|
||||||
setFocusedId: (id: string) => void
|
setFocusedId: (id: string) => void
|
||||||
hoverLabel?: string
|
hoverLabel?: string
|
||||||
|
item: DecryptedItem
|
||||||
}
|
}
|
||||||
|
|
||||||
const ItemLinkAutocompleteInput = ({
|
const ItemLinkAutocompleteInput = ({
|
||||||
@@ -35,12 +37,16 @@ const ItemLinkAutocompleteInput = ({
|
|||||||
focusedId,
|
focusedId,
|
||||||
setFocusedId,
|
setFocusedId,
|
||||||
hoverLabel,
|
hoverLabel,
|
||||||
|
item,
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
const application = useApplication()
|
const application = useApplication()
|
||||||
const { tags, linkItemToSelectedItem, createAndAddNewTag, isEntitledToNoteLinking, activeItem } = linkingController
|
|
||||||
|
const { getLinkedTagsForItem, linkItems, createAndAddNewTag, isEntitledToNoteLinking } = linkingController
|
||||||
|
|
||||||
|
const tagsLinkedToItem = getLinkedTagsForItem(item) || []
|
||||||
|
|
||||||
const [searchQuery, setSearchQuery] = useState('')
|
const [searchQuery, setSearchQuery] = useState('')
|
||||||
const { unlinkedItems, shouldShowCreateTag } = getLinkingSearchResults(searchQuery, application, activeItem)
|
const { unlinkedItems, shouldShowCreateTag } = getLinkingSearchResults(searchQuery, application, item)
|
||||||
|
|
||||||
const [dropdownVisible, setDropdownVisible] = useState(false)
|
const [dropdownVisible, setDropdownVisible] = useState(false)
|
||||||
const [dropdownMaxHeight, setDropdownMaxHeight] = useState<number | 'auto'>('auto')
|
const [dropdownMaxHeight, setDropdownMaxHeight] = useState<number | 'auto'>('auto')
|
||||||
@@ -122,7 +128,7 @@ const ItemLinkAutocompleteInput = ({
|
|||||||
<input
|
<input
|
||||||
ref={inputRef}
|
ref={inputRef}
|
||||||
className={classNames(
|
className={classNames(
|
||||||
`${tags.length > 0 ? 'w-80' : 'mr-10 w-70'}`,
|
`${tagsLinkedToItem.length > 0 ? 'w-80' : 'mr-10 w-70'}`,
|
||||||
'bg-transparent text-sm text-text focus:border-b-2 focus:border-solid focus:border-info lg:text-xs',
|
'bg-transparent text-sm text-text focus:border-b-2 focus:border-solid focus:border-info lg:text-xs',
|
||||||
'no-border h-7 focus:shadow-none focus:outline-none',
|
'no-border h-7 focus:shadow-none focus:outline-none',
|
||||||
)}
|
)}
|
||||||
@@ -141,7 +147,7 @@ const ItemLinkAutocompleteInput = ({
|
|||||||
{areSearchResultsVisible && (
|
{areSearchResultsVisible && (
|
||||||
<DisclosurePanel
|
<DisclosurePanel
|
||||||
className={classNames(
|
className={classNames(
|
||||||
tags.length > 0 ? 'w-80' : 'mr-10 w-70',
|
tagsLinkedToItem.length > 0 ? 'w-80' : 'mr-10 w-70',
|
||||||
'absolute z-dropdown-menu flex flex-col overflow-y-auto rounded bg-default py-2 shadow-main',
|
'absolute z-dropdown-menu flex flex-col overflow-y-auto rounded bg-default py-2 shadow-main',
|
||||||
)}
|
)}
|
||||||
style={{
|
style={{
|
||||||
@@ -159,7 +165,8 @@ const ItemLinkAutocompleteInput = ({
|
|||||||
>
|
>
|
||||||
<LinkedItemSearchResults
|
<LinkedItemSearchResults
|
||||||
createAndAddNewTag={createAndAddNewTag}
|
createAndAddNewTag={createAndAddNewTag}
|
||||||
linkItemToSelectedItem={linkItemToSelectedItem}
|
linkItems={linkItems}
|
||||||
|
item={item}
|
||||||
results={unlinkedItems}
|
results={unlinkedItems}
|
||||||
searchQuery={searchQuery}
|
searchQuery={searchQuery}
|
||||||
shouldShowCreateTag={shouldShowCreateTag}
|
shouldShowCreateTag={shouldShowCreateTag}
|
||||||
|
|||||||
@@ -11,23 +11,30 @@ import { LinkableItem } from '@/Utils/Items/Search/LinkableItem'
|
|||||||
import { ItemLink } from '@/Utils/Items/Search/ItemLink'
|
import { ItemLink } from '@/Utils/Items/Search/ItemLink'
|
||||||
import { FOCUS_TAGS_INPUT_COMMAND, keyboardStringForShortcut } from '@standardnotes/ui-services'
|
import { FOCUS_TAGS_INPUT_COMMAND, keyboardStringForShortcut } from '@standardnotes/ui-services'
|
||||||
import { useCommandService } from '../CommandProvider'
|
import { useCommandService } from '../CommandProvider'
|
||||||
|
import { useApplication } from '../ApplicationProvider'
|
||||||
|
import { useItemLinks } from '@/Hooks/useItemLinks'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
linkingController: LinkingController
|
linkingController: LinkingController
|
||||||
}
|
}
|
||||||
|
|
||||||
const LinkedItemBubblesContainer = ({ linkingController }: Props) => {
|
const LinkedItemBubblesContainer = ({ linkingController }: Props) => {
|
||||||
|
const application = useApplication()
|
||||||
|
const activeItem = application.itemControllerGroup.activeItemViewController?.item
|
||||||
|
|
||||||
const { toggleAppPane } = useResponsiveAppPane()
|
const { toggleAppPane } = useResponsiveAppPane()
|
||||||
|
|
||||||
const commandService = useCommandService()
|
const commandService = useCommandService()
|
||||||
|
|
||||||
const {
|
const { unlinkItemFromSelectedItem: unlinkItem, activateItem } = linkingController
|
||||||
allItemLinks,
|
|
||||||
notesLinkingToActiveItem,
|
const { notesLinkedToItem, filesLinkedToItem, tagsLinkedToItem, notesLinkingToItem, filesLinkingToItem } =
|
||||||
filesLinkingToActiveItem,
|
useItemLinks(activeItem)
|
||||||
unlinkItemFromSelectedItem: unlinkItem,
|
|
||||||
activateItem,
|
const allItemsLinkedToItem: ItemLink[] = useMemo(
|
||||||
} = linkingController
|
() => new Array<ItemLink>().concat(notesLinkedToItem, filesLinkedToItem, tagsLinkedToItem),
|
||||||
|
[filesLinkedToItem, notesLinkedToItem, tagsLinkedToItem],
|
||||||
|
)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
return commandService.addCommandHandler({
|
return commandService.addCommandHandler({
|
||||||
@@ -47,11 +54,11 @@ const LinkedItemBubblesContainer = ({ linkingController }: Props) => {
|
|||||||
)
|
)
|
||||||
|
|
||||||
const [focusedId, setFocusedId] = useState<string>()
|
const [focusedId, setFocusedId] = useState<string>()
|
||||||
const focusableIds = allItemLinks
|
const focusableIds = allItemsLinkedToItem
|
||||||
.map((link) => link.id)
|
.map((link) => link.id)
|
||||||
.concat(
|
.concat(
|
||||||
notesLinkingToActiveItem.map((link) => link.id),
|
notesLinkingToItem.map((link) => link.id),
|
||||||
filesLinkingToActiveItem.map((link) => link.id),
|
filesLinkingToItem.map((link) => link.id),
|
||||||
[ElementIds.ItemLinkAutocompleteInput],
|
[ElementIds.ItemLinkAutocompleteInput],
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -84,9 +91,9 @@ const LinkedItemBubblesContainer = ({ linkingController }: Props) => {
|
|||||||
)
|
)
|
||||||
|
|
||||||
const isItemBidirectionallyLinked = (link: ItemLink) => {
|
const isItemBidirectionallyLinked = (link: ItemLink) => {
|
||||||
const existsInAllItemLinks = !!allItemLinks.find((item) => link.item.uuid === item.item.uuid)
|
const existsInAllItemLinks = !!allItemsLinkedToItem.find((item) => link.item.uuid === item.item.uuid)
|
||||||
const existsInNotesLinkingToItem = !!notesLinkingToActiveItem.find((item) => link.item.uuid === item.item.uuid)
|
const existsInNotesLinkingToItem = !!notesLinkingToItem.find((item) => link.item.uuid === item.item.uuid)
|
||||||
const existsInFilesLinkingToItem = !!filesLinkingToActiveItem.find((item) => link.item.uuid === item.item.uuid)
|
const existsInFilesLinkingToItem = !!filesLinkingToItem.find((item) => link.item.uuid === item.item.uuid)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
existsInAllItemLinks &&
|
existsInAllItemLinks &&
|
||||||
@@ -94,16 +101,20 @@ const LinkedItemBubblesContainer = ({ linkingController }: Props) => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!activeItem) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={classNames(
|
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',
|
'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',
|
allItemsLinkedToItem.length || notesLinkingToItem.length ? 'mt-1' : 'mt-0.5',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{allItemLinks
|
{allItemsLinkedToItem
|
||||||
.concat(notesLinkingToActiveItem)
|
.concat(notesLinkingToItem)
|
||||||
.concat(filesLinkingToActiveItem)
|
.concat(filesLinkingToItem)
|
||||||
.map((link) => (
|
.map((link) => (
|
||||||
<LinkedItemBubble
|
<LinkedItemBubble
|
||||||
link={link}
|
link={link}
|
||||||
@@ -123,6 +134,7 @@ const LinkedItemBubblesContainer = ({ linkingController }: Props) => {
|
|||||||
focusPreviousItem={focusPreviousItem}
|
focusPreviousItem={focusPreviousItem}
|
||||||
setFocusedId={setFocusedId}
|
setFocusedId={setFocusedId}
|
||||||
hoverLabel={`Focus input to add a link (${shortcut})`}
|
hoverLabel={`Focus input to add a link (${shortcut})`}
|
||||||
|
item={activeItem}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -10,22 +10,24 @@ import { useCallback } from 'react'
|
|||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
createAndAddNewTag: LinkingController['createAndAddNewTag']
|
createAndAddNewTag: LinkingController['createAndAddNewTag']
|
||||||
linkItemToSelectedItem: LinkingController['linkItemToSelectedItem']
|
linkItems: LinkingController['linkItems']
|
||||||
results: LinkableItem[]
|
results: LinkableItem[]
|
||||||
searchQuery: string
|
searchQuery: string
|
||||||
shouldShowCreateTag: boolean
|
shouldShowCreateTag: boolean
|
||||||
onClickCallback?: () => void
|
onClickCallback?: () => void
|
||||||
isEntitledToNoteLinking: boolean
|
isEntitledToNoteLinking: boolean
|
||||||
|
item: LinkableItem
|
||||||
}
|
}
|
||||||
|
|
||||||
const LinkedItemSearchResults = ({
|
const LinkedItemSearchResults = ({
|
||||||
createAndAddNewTag,
|
createAndAddNewTag,
|
||||||
linkItemToSelectedItem,
|
linkItems,
|
||||||
results,
|
results,
|
||||||
searchQuery,
|
searchQuery,
|
||||||
shouldShowCreateTag,
|
shouldShowCreateTag,
|
||||||
onClickCallback,
|
onClickCallback,
|
||||||
isEntitledToNoteLinking,
|
isEntitledToNoteLinking,
|
||||||
|
item,
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
const onClickAddNew = useCallback(
|
const onClickAddNew = useCallback(
|
||||||
(searchQuery: string) => {
|
(searchQuery: string) => {
|
||||||
@@ -44,7 +46,7 @@ const LinkedItemSearchResults = ({
|
|||||||
key={result.uuid}
|
key={result.uuid}
|
||||||
className="flex w-full items-center justify-between gap-4 overflow-hidden py-2 px-3 hover:bg-contrast hover:text-foreground focus:bg-info-backdrop"
|
className="flex w-full items-center justify-between gap-4 overflow-hidden py-2 px-3 hover:bg-contrast hover:text-foreground focus:bg-info-backdrop"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
void linkItemToSelectedItem(result)
|
void linkItems(item, result)
|
||||||
onClickCallback?.()
|
onClickCallback?.()
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { FilesController } from '@/Controllers/FilesController'
|
|||||||
import { LinkingController } from '@/Controllers/LinkingController'
|
import { LinkingController } from '@/Controllers/LinkingController'
|
||||||
import { observer } from 'mobx-react-lite'
|
import { observer } from 'mobx-react-lite'
|
||||||
import { useRef, useCallback } from 'react'
|
import { useRef, useCallback } from 'react'
|
||||||
|
import { useApplication } from '../ApplicationProvider'
|
||||||
import RoundIconButton from '../Button/RoundIconButton'
|
import RoundIconButton from '../Button/RoundIconButton'
|
||||||
import Popover from '../Popover/Popover'
|
import Popover from '../Popover/Popover'
|
||||||
import StyledTooltip from '../StyledTooltip/StyledTooltip'
|
import StyledTooltip from '../StyledTooltip/StyledTooltip'
|
||||||
@@ -16,6 +17,9 @@ type Props = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const LinkedItemsButton = ({ linkingController, filesController, onClickPreprocessing, featuresController }: Props) => {
|
const LinkedItemsButton = ({ linkingController, filesController, onClickPreprocessing, featuresController }: Props) => {
|
||||||
|
const application = useApplication()
|
||||||
|
const activeItem = application.itemControllerGroup.activeItemViewController?.item
|
||||||
|
|
||||||
const { isLinkingPanelOpen, setIsLinkingPanelOpen } = linkingController
|
const { isLinkingPanelOpen, setIsLinkingPanelOpen } = linkingController
|
||||||
const buttonRef = useRef<HTMLButtonElement>(null)
|
const buttonRef = useRef<HTMLButtonElement>(null)
|
||||||
|
|
||||||
@@ -27,6 +31,10 @@ const LinkedItemsButton = ({ linkingController, filesController, onClickPreproce
|
|||||||
setIsLinkingPanelOpen(willMenuOpen)
|
setIsLinkingPanelOpen(willMenuOpen)
|
||||||
}, [isLinkingPanelOpen, onClickPreprocessing, setIsLinkingPanelOpen])
|
}, [isLinkingPanelOpen, onClickPreprocessing, setIsLinkingPanelOpen])
|
||||||
|
|
||||||
|
if (!activeItem) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<StyledTooltip label="Linked items panel">
|
<StyledTooltip label="Linked items panel">
|
||||||
@@ -34,6 +42,7 @@ const LinkedItemsButton = ({ linkingController, filesController, onClickPreproce
|
|||||||
</StyledTooltip>
|
</StyledTooltip>
|
||||||
<Popover togglePopover={toggleMenu} anchorElement={buttonRef.current} open={isLinkingPanelOpen} className="pb-2">
|
<Popover togglePopover={toggleMenu} anchorElement={buttonRef.current} open={isLinkingPanelOpen} className="pb-2">
|
||||||
<LinkedItemsPanel
|
<LinkedItemsPanel
|
||||||
|
item={activeItem}
|
||||||
isOpen={isLinkingPanelOpen}
|
isOpen={isLinkingPanelOpen}
|
||||||
linkingController={linkingController}
|
linkingController={linkingController}
|
||||||
filesController={filesController}
|
filesController={filesController}
|
||||||
|
|||||||
@@ -12,44 +12,45 @@ import Icon from '../Icon/Icon'
|
|||||||
import DecoratedInput from '../Input/DecoratedInput'
|
import DecoratedInput from '../Input/DecoratedInput'
|
||||||
import LinkedItemSearchResults from './LinkedItemSearchResults'
|
import LinkedItemSearchResults from './LinkedItemSearchResults'
|
||||||
import { LinkedItemsSectionItem } from './LinkedItemsSectionItem'
|
import { LinkedItemsSectionItem } from './LinkedItemsSectionItem'
|
||||||
|
import { DecryptedItem } from '@standardnotes/snjs'
|
||||||
|
|
||||||
const LinkedItemsPanel = ({
|
const LinkedItemsPanel = ({
|
||||||
linkingController,
|
linkingController,
|
||||||
filesController,
|
filesController,
|
||||||
featuresController,
|
featuresController,
|
||||||
isOpen,
|
isOpen,
|
||||||
|
item,
|
||||||
}: {
|
}: {
|
||||||
linkingController: LinkingController
|
linkingController: LinkingController
|
||||||
filesController: FilesController
|
filesController: FilesController
|
||||||
featuresController: FeaturesController
|
featuresController: FeaturesController
|
||||||
isOpen: boolean
|
isOpen: boolean
|
||||||
|
item: DecryptedItem
|
||||||
}) => {
|
}) => {
|
||||||
const {
|
const {
|
||||||
tags,
|
getLinkedTagsForItem,
|
||||||
linkedFiles,
|
getFilesLinksForItem,
|
||||||
filesLinkingToActiveItem,
|
getLinkedNotesForItem,
|
||||||
notesLinkedToItem,
|
getNotesLinkingToItem,
|
||||||
notesLinkingToActiveItem,
|
linkItems,
|
||||||
allItemLinks: allLinkedItems,
|
|
||||||
linkItemToSelectedItem,
|
|
||||||
unlinkItemFromSelectedItem,
|
unlinkItemFromSelectedItem,
|
||||||
activateItem,
|
activateItem,
|
||||||
createAndAddNewTag,
|
createAndAddNewTag,
|
||||||
isEntitledToNoteLinking,
|
isEntitledToNoteLinking,
|
||||||
activeItem,
|
|
||||||
} = linkingController
|
} = linkingController
|
||||||
|
|
||||||
|
const tagsLinkedToItem = getLinkedTagsForItem(item) || []
|
||||||
|
const { filesLinkedToItem, filesLinkingToItem } = getFilesLinksForItem(item)
|
||||||
|
const notesLinkedToItem = getLinkedNotesForItem(item) || []
|
||||||
|
const notesLinkingToItem = getNotesLinkingToItem(item) || []
|
||||||
|
|
||||||
const { entitledToFiles } = featuresController
|
const { entitledToFiles } = featuresController
|
||||||
const application = useApplication()
|
const application = useApplication()
|
||||||
|
|
||||||
const searchInputRef = useRef<HTMLInputElement | null>(null)
|
const searchInputRef = useRef<HTMLInputElement | null>(null)
|
||||||
const [searchQuery, setSearchQuery] = useState('')
|
const [searchQuery, setSearchQuery] = useState('')
|
||||||
const isSearching = !!searchQuery.length
|
const isSearching = !!searchQuery.length
|
||||||
const { linkedResults, unlinkedItems, shouldShowCreateTag } = getLinkingSearchResults(
|
const { linkedResults, unlinkedItems, shouldShowCreateTag } = getLinkingSearchResults(searchQuery, application, item)
|
||||||
searchQuery,
|
|
||||||
application,
|
|
||||||
activeItem,
|
|
||||||
)
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isOpen && searchInputRef.current) {
|
if (isOpen && searchInputRef.current) {
|
||||||
@@ -64,7 +65,7 @@ const LinkedItemsPanel = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
void filesController.selectAndUploadNewFiles((file) => {
|
void filesController.selectAndUploadNewFiles((file) => {
|
||||||
void linkItemToSelectedItem(file)
|
void linkItems(item, file)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,7 +74,7 @@ const LinkedItemsPanel = ({
|
|||||||
<form
|
<form
|
||||||
className={classNames(
|
className={classNames(
|
||||||
'sticky top-0 z-10 bg-default px-2.5 pt-2.5',
|
'sticky top-0 z-10 bg-default px-2.5 pt-2.5',
|
||||||
allLinkedItems.length || linkedResults.length || unlinkedItems.length || notesLinkingToActiveItem.length
|
linkedResults.length || unlinkedItems.length || notesLinkingToItem.length
|
||||||
? 'border-b border-border pb-2.5'
|
? 'border-b border-border pb-2.5'
|
||||||
: 'pb-1',
|
: 'pb-1',
|
||||||
)}
|
)}
|
||||||
@@ -105,7 +106,7 @@ const LinkedItemsPanel = ({
|
|||||||
<div className="mt-3 mb-1 px-3 text-menu-item font-semibold uppercase text-passive-0">Unlinked</div>
|
<div className="mt-3 mb-1 px-3 text-menu-item font-semibold uppercase text-passive-0">Unlinked</div>
|
||||||
<LinkedItemSearchResults
|
<LinkedItemSearchResults
|
||||||
createAndAddNewTag={createAndAddNewTag}
|
createAndAddNewTag={createAndAddNewTag}
|
||||||
linkItemToSelectedItem={linkItemToSelectedItem}
|
linkItems={linkItems}
|
||||||
results={unlinkedItems}
|
results={unlinkedItems}
|
||||||
searchQuery={searchQuery}
|
searchQuery={searchQuery}
|
||||||
shouldShowCreateTag={shouldShowCreateTag}
|
shouldShowCreateTag={shouldShowCreateTag}
|
||||||
@@ -114,6 +115,7 @@ const LinkedItemsPanel = ({
|
|||||||
setSearchQuery('')
|
setSearchQuery('')
|
||||||
searchInputRef.current?.focus()
|
searchInputRef.current?.focus()
|
||||||
}}
|
}}
|
||||||
|
item={item}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -137,11 +139,11 @@ const LinkedItemsPanel = ({
|
|||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
{!!tags.length && (
|
{!!tagsLinkedToItem.length && (
|
||||||
<div>
|
<div>
|
||||||
<div className="mt-3 mb-1 px-3 text-menu-item font-semibold uppercase text-passive-0">Linked Tags</div>
|
<div className="mt-3 mb-1 px-3 text-menu-item font-semibold uppercase text-passive-0">Linked Tags</div>
|
||||||
<div className="my-1">
|
<div className="my-1">
|
||||||
{tags.map((link) => (
|
{tagsLinkedToItem.map((link) => (
|
||||||
<LinkedItemsSectionItem
|
<LinkedItemsSectionItem
|
||||||
key={link.id}
|
key={link.id}
|
||||||
item={link.item}
|
item={link.item}
|
||||||
@@ -165,7 +167,7 @@ const LinkedItemsPanel = ({
|
|||||||
<Icon type="add" />
|
<Icon type="add" />
|
||||||
Upload and link file(s)
|
Upload and link file(s)
|
||||||
</button>
|
</button>
|
||||||
{linkedFiles.map((link) => (
|
{filesLinkedToItem.map((link) => (
|
||||||
<LinkedItemsSectionItem
|
<LinkedItemsSectionItem
|
||||||
key={link.id}
|
key={link.id}
|
||||||
item={link.item}
|
item={link.item}
|
||||||
@@ -178,13 +180,13 @@ const LinkedItemsPanel = ({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{!!filesLinkingToActiveItem.length && (
|
{!!filesLinkingToItem.length && (
|
||||||
<div>
|
<div>
|
||||||
<div className="mt-3 mb-1 px-3 text-menu-item font-semibold uppercase text-passive-0">
|
<div className="mt-3 mb-1 px-3 text-menu-item font-semibold uppercase text-passive-0">
|
||||||
Files Linking To Current File
|
Files Linking To Current File
|
||||||
</div>
|
</div>
|
||||||
<div className="my-1">
|
<div className="my-1">
|
||||||
{filesLinkingToActiveItem.map((link) => (
|
{filesLinkingToItem.map((link) => (
|
||||||
<LinkedItemsSectionItem
|
<LinkedItemsSectionItem
|
||||||
key={link.id}
|
key={link.id}
|
||||||
item={link.item}
|
item={link.item}
|
||||||
@@ -214,13 +216,13 @@ const LinkedItemsPanel = ({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{!!notesLinkingToActiveItem.length && (
|
{!!notesLinkingToItem.length && (
|
||||||
<div>
|
<div>
|
||||||
<div className="mt-3 mb-1 px-3 text-menu-item font-semibold uppercase text-passive-0">
|
<div className="mt-3 mb-1 px-3 text-menu-item font-semibold uppercase text-passive-0">
|
||||||
Notes Linking To This Note
|
Notes Linking To This Note
|
||||||
</div>
|
</div>
|
||||||
<div className="my-1">
|
<div className="my-1">
|
||||||
{notesLinkingToActiveItem.map((link) => (
|
{notesLinkingToItem.map((link) => (
|
||||||
<LinkedItemsSectionItem
|
<LinkedItemsSectionItem
|
||||||
key={link.id}
|
key={link.id}
|
||||||
item={link.item}
|
item={link.item}
|
||||||
|
|||||||
@@ -245,8 +245,6 @@ export class ItemListController extends AbstractViewController implements Intern
|
|||||||
|
|
||||||
await this.application.itemControllerGroup.createItemController({ note })
|
await this.application.itemControllerGroup.createItemController({ note })
|
||||||
|
|
||||||
this.linkingController.reloadAllLinks()
|
|
||||||
|
|
||||||
await this.publishCrossControllerEventSync(CrossControllerEvent.ActiveEditorChanged)
|
await this.publishCrossControllerEventSync(CrossControllerEvent.ActiveEditorChanged)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -262,8 +260,6 @@ export class ItemListController extends AbstractViewController implements Intern
|
|||||||
}
|
}
|
||||||
|
|
||||||
await this.application.itemControllerGroup.createItemController({ file })
|
await this.application.itemControllerGroup.createItemController({ file })
|
||||||
|
|
||||||
this.linkingController.reloadAllLinks()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setCompletedFullSync = (completed: boolean) => {
|
setCompletedFullSync = (completed: boolean) => {
|
||||||
@@ -659,8 +655,6 @@ export class ItemListController extends AbstractViewController implements Intern
|
|||||||
|
|
||||||
const controller = await this.createNewNoteController(useTitle, createdAt, autofocusBehavior)
|
const controller = await this.createNewNoteController(useTitle, createdAt, autofocusBehavior)
|
||||||
|
|
||||||
this.linkingController.reloadAllLinks()
|
|
||||||
|
|
||||||
this.selectionController.scrollToItem(controller.item)
|
this.selectionController.scrollToItem(controller.item)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import { NoteViewController } from '@/Components/NoteView/Controller/NoteViewCon
|
|||||||
import { AppPaneId } from '@/Components/Panes/AppPaneMetadata'
|
import { AppPaneId } from '@/Components/Panes/AppPaneMetadata'
|
||||||
import { PrefDefaults } from '@/Constants/PrefDefaults'
|
import { PrefDefaults } from '@/Constants/PrefDefaults'
|
||||||
import { createLinkFromItem } from '@/Utils/Items/Search/createLinkFromItem'
|
import { createLinkFromItem } from '@/Utils/Items/Search/createLinkFromItem'
|
||||||
import { ItemLink } from '@/Utils/Items/Search/ItemLink'
|
|
||||||
import { LinkableItem } from '@/Utils/Items/Search/LinkableItem'
|
import { LinkableItem } from '@/Utils/Items/Search/LinkableItem'
|
||||||
import {
|
import {
|
||||||
ApplicationEvent,
|
ApplicationEvent,
|
||||||
@@ -28,11 +27,6 @@ import { SelectedItemsController } from './SelectedItemsController'
|
|||||||
import { SubscriptionController } from './Subscription/SubscriptionController'
|
import { SubscriptionController } from './Subscription/SubscriptionController'
|
||||||
|
|
||||||
export class LinkingController extends AbstractViewController {
|
export class LinkingController extends AbstractViewController {
|
||||||
tags: ItemLink<SNTag>[] = []
|
|
||||||
linkedFiles: ItemLink<FileItem>[] = []
|
|
||||||
filesLinkingToActiveItem: ItemLink<FileItem>[] = []
|
|
||||||
notesLinkedToItem: ItemLink<SNNote>[] = []
|
|
||||||
notesLinkingToActiveItem: ItemLink<SNNote>[] = []
|
|
||||||
shouldLinkToParentFolders: boolean
|
shouldLinkToParentFolders: boolean
|
||||||
isLinkingPanelOpen = false
|
isLinkingPanelOpen = false
|
||||||
private itemListController!: ItemListController
|
private itemListController!: ItemListController
|
||||||
@@ -48,22 +42,11 @@ export class LinkingController extends AbstractViewController {
|
|||||||
super(application, eventBus)
|
super(application, eventBus)
|
||||||
|
|
||||||
makeObservable(this, {
|
makeObservable(this, {
|
||||||
tags: observable,
|
|
||||||
linkedFiles: observable,
|
|
||||||
filesLinkingToActiveItem: observable,
|
|
||||||
notesLinkedToItem: observable,
|
|
||||||
notesLinkingToActiveItem: observable,
|
|
||||||
isLinkingPanelOpen: observable,
|
isLinkingPanelOpen: observable,
|
||||||
|
|
||||||
allItemLinks: computed,
|
|
||||||
isEntitledToNoteLinking: computed,
|
isEntitledToNoteLinking: computed,
|
||||||
selectedItemTitle: computed,
|
|
||||||
|
|
||||||
setIsLinkingPanelOpen: action,
|
setIsLinkingPanelOpen: action,
|
||||||
reloadLinkedFiles: action,
|
|
||||||
reloadLinkedTags: action,
|
|
||||||
reloadLinkedNotes: action,
|
|
||||||
reloadNotesLinkingToItem: action,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
this.shouldLinkToParentFolders = application.getPreference(
|
this.shouldLinkToParentFolders = application.getPreference(
|
||||||
@@ -89,19 +72,6 @@ export class LinkingController extends AbstractViewController {
|
|||||||
this.itemListController = itemListController
|
this.itemListController = itemListController
|
||||||
this.filesController = filesController
|
this.filesController = filesController
|
||||||
this.subscriptionController = subscriptionController
|
this.subscriptionController = subscriptionController
|
||||||
|
|
||||||
this.disposers.push(
|
|
||||||
this.application.streamItems(ContentType.File, () => {
|
|
||||||
this.reloadLinkedFiles()
|
|
||||||
}),
|
|
||||||
this.application.streamItems(ContentType.Tag, () => {
|
|
||||||
this.reloadLinkedTags()
|
|
||||||
}),
|
|
||||||
this.application.streamItems(ContentType.Note, () => {
|
|
||||||
this.reloadLinkedNotes()
|
|
||||||
this.reloadNotesLinkingToItem()
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get isEntitledToNoteLinking() {
|
get isEntitledToNoteLinking() {
|
||||||
@@ -112,87 +82,61 @@ export class LinkingController extends AbstractViewController {
|
|||||||
this.isLinkingPanelOpen = open
|
this.isLinkingPanelOpen = open
|
||||||
}
|
}
|
||||||
|
|
||||||
get allItemLinks() {
|
|
||||||
return [...this.tags, ...this.linkedFiles, ...this.notesLinkedToItem]
|
|
||||||
}
|
|
||||||
|
|
||||||
get activeItem() {
|
get activeItem() {
|
||||||
return this.application.itemControllerGroup.activeItemViewController?.item
|
return this.application.itemControllerGroup.activeItemViewController?.item
|
||||||
}
|
}
|
||||||
|
|
||||||
get selectedItemTitle() {
|
getFilesLinksForItem = (item: LinkableItem | undefined) => {
|
||||||
return this.selectionController.firstSelectedItem
|
if (!item || this.application.items.isTemplateItem(item)) {
|
||||||
? this.selectionController.firstSelectedItem.title
|
return {
|
||||||
: this.activeItem
|
filesLinkedToItem: [],
|
||||||
? this.activeItem.title
|
filesLinkingToItem: [],
|
||||||
: ''
|
}
|
||||||
}
|
|
||||||
|
|
||||||
reloadAllLinks() {
|
|
||||||
this.reloadLinkedFiles()
|
|
||||||
this.reloadLinkedTags()
|
|
||||||
this.reloadLinkedNotes()
|
|
||||||
this.reloadNotesLinkingToItem()
|
|
||||||
}
|
|
||||||
|
|
||||||
reloadLinkedFiles() {
|
|
||||||
if (!this.activeItem || this.application.items.isTemplateItem(this.activeItem)) {
|
|
||||||
this.linkedFiles = []
|
|
||||||
this.filesLinkingToActiveItem = []
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const referencesOfActiveItem = naturalSort(
|
const referencesOfItem = naturalSort(this.application.items.referencesForItem(item).filter(isFile), 'title')
|
||||||
this.application.items.referencesForItem(this.activeItem).filter(isFile),
|
|
||||||
'title',
|
|
||||||
)
|
|
||||||
|
|
||||||
const referencingActiveItem = naturalSort(
|
const referencingItem = naturalSort(this.application.items.itemsReferencingItem(item).filter(isFile), 'title')
|
||||||
this.application.items.itemsReferencingItem(this.activeItem).filter(isFile),
|
|
||||||
'title',
|
|
||||||
)
|
|
||||||
|
|
||||||
if (this.activeItem.content_type === ContentType.File) {
|
if (item.content_type === ContentType.File) {
|
||||||
this.linkedFiles = referencesOfActiveItem.map((item) => createLinkFromItem(item, 'linked'))
|
return {
|
||||||
this.filesLinkingToActiveItem = referencingActiveItem.map((item) => createLinkFromItem(item, 'linked-by'))
|
filesLinkedToItem: referencesOfItem.map((item) => createLinkFromItem(item, 'linked')),
|
||||||
|
filesLinkingToItem: referencingItem.map((item) => createLinkFromItem(item, 'linked-by')),
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
this.linkedFiles = referencingActiveItem.map((item) => createLinkFromItem(item, 'linked'))
|
return {
|
||||||
this.filesLinkingToActiveItem = referencesOfActiveItem.map((item) => createLinkFromItem(item, 'linked-by'))
|
filesLinkedToItem: referencingItem.map((item) => createLinkFromItem(item, 'linked')),
|
||||||
|
filesLinkingToItem: referencesOfItem.map((item) => createLinkFromItem(item, 'linked-by')),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
reloadLinkedTags() {
|
getLinkedTagsForItem = (item: LinkableItem | undefined) => {
|
||||||
if (!this.activeItem) {
|
if (!item) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
this.tags = this.application.items
|
return this.application.items.getSortedTagsForItem(item).map((tag) => createLinkFromItem(tag, 'linked'))
|
||||||
.getSortedTagsForItem(this.activeItem)
|
|
||||||
.map((item) => createLinkFromItem(item, 'linked'))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
reloadLinkedNotes() {
|
getLinkedNotesForItem = (item: LinkableItem | undefined) => {
|
||||||
if (!this.activeItem || this.application.items.isTemplateItem(this.activeItem)) {
|
if (!item || this.application.items.isTemplateItem(item)) {
|
||||||
this.notesLinkedToItem = []
|
return []
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.notesLinkedToItem = naturalSort(
|
return naturalSort(this.application.items.referencesForItem(item).filter(isNote), 'title').map((item) =>
|
||||||
this.application.items.referencesForItem(this.activeItem).filter(isNote),
|
createLinkFromItem(item, 'linked'),
|
||||||
'title',
|
)
|
||||||
).map((item) => createLinkFromItem(item, 'linked'))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
reloadNotesLinkingToItem() {
|
getNotesLinkingToItem = (item: LinkableItem | undefined) => {
|
||||||
if (!this.activeItem) {
|
if (!item) {
|
||||||
this.notesLinkingToActiveItem = []
|
return []
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.notesLinkingToActiveItem = naturalSort(
|
return naturalSort(this.application.items.itemsReferencingItem(item).filter(isNote), 'title').map((item) =>
|
||||||
this.application.items.itemsReferencingItem(this.activeItem).filter(isNote),
|
createLinkFromItem(item, 'linked-by'),
|
||||||
'title',
|
)
|
||||||
).map((item) => createLinkFromItem(item, 'linked-by'))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
activateItem = async (item: LinkableItem): Promise<AppPaneId | undefined> => {
|
activateItem = async (item: LinkableItem): Promise<AppPaneId | undefined> => {
|
||||||
@@ -230,7 +174,6 @@ export class LinkingController extends AbstractViewController {
|
|||||||
await this.application.items.unlinkItems(selectedItem, itemToUnlink)
|
await this.application.items.unlinkItems(selectedItem, itemToUnlink)
|
||||||
|
|
||||||
void this.application.sync.sync()
|
void this.application.sync.sync()
|
||||||
this.reloadAllLinks()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ensureActiveItemIsInserted = async () => {
|
ensureActiveItemIsInserted = async () => {
|
||||||
@@ -240,11 +183,22 @@ export class LinkingController extends AbstractViewController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
linkItems = async (item: SNNote | FileItem, itemToLink: LinkableItem) => {
|
linkItems = async (item: LinkableItem, itemToLink: LinkableItem) => {
|
||||||
if (item instanceof SNNote) {
|
if (item instanceof SNNote) {
|
||||||
|
if (itemToLink instanceof SNNote && !this.isEntitledToNoteLinking) {
|
||||||
|
void this.publishCrossControllerEventSync(CrossControllerEvent.DisplayPremiumModal, {
|
||||||
|
featureName: 'Note linking',
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.uuid === this.activeItem?.uuid) {
|
||||||
|
await this.ensureActiveItemIsInserted()
|
||||||
|
}
|
||||||
|
|
||||||
if (itemToLink instanceof FileItem) {
|
if (itemToLink instanceof FileItem) {
|
||||||
await this.application.items.associateFileWithNote(itemToLink, item)
|
await this.application.items.associateFileWithNote(itemToLink, item)
|
||||||
} else if (itemToLink instanceof SNNote && this.isEntitledToNoteLinking) {
|
} else if (itemToLink instanceof SNNote) {
|
||||||
await this.application.items.linkNoteToNote(item, itemToLink)
|
await this.application.items.linkNoteToNote(item, itemToLink)
|
||||||
} else if (itemToLink instanceof SNTag) {
|
} else if (itemToLink instanceof SNTag) {
|
||||||
await this.addTagToItem(itemToLink, item)
|
await this.addTagToItem(itemToLink, item)
|
||||||
@@ -266,7 +220,6 @@ export class LinkingController extends AbstractViewController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void this.application.sync.sync()
|
void this.application.sync.sync()
|
||||||
this.reloadAllLinks()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
linkItemToSelectedItem = async (itemToLink: LinkableItem): Promise<boolean> => {
|
linkItemToSelectedItem = async (itemToLink: LinkableItem): Promise<boolean> => {
|
||||||
@@ -307,7 +260,6 @@ export class LinkingController extends AbstractViewController {
|
|||||||
await this.application.items.addTagToFile(item, tag, this.shouldLinkToParentFolders)
|
await this.application.items.addTagToFile(item, tag, this.shouldLinkToParentFolders)
|
||||||
}
|
}
|
||||||
|
|
||||||
this.reloadLinkedTags()
|
|
||||||
this.application.sync.sync().catch(console.error)
|
this.application.sync.sync().catch(console.error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
33
packages/web/src/javascripts/Hooks/useItemLinks.ts
Normal file
33
packages/web/src/javascripts/Hooks/useItemLinks.ts
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import { useApplication } from '@/Components/ApplicationProvider'
|
||||||
|
import { useLinkingController } from '@/Controllers/LinkingControllerProvider'
|
||||||
|
import { ContentType, DecryptedItem } from '@standardnotes/snjs'
|
||||||
|
import { useEffect, useState } from 'react'
|
||||||
|
|
||||||
|
export const useItemLinks = (item: DecryptedItem | undefined) => {
|
||||||
|
const application = useApplication()
|
||||||
|
const linkingController = useLinkingController()
|
||||||
|
const { getLinkedNotesForItem, getNotesLinkingToItem, getFilesLinksForItem, getLinkedTagsForItem } = linkingController
|
||||||
|
|
||||||
|
const [, refresh] = useState(Date.now())
|
||||||
|
|
||||||
|
const notesLinkedToItem = getLinkedNotesForItem(item) || []
|
||||||
|
const notesLinkingToItem = getNotesLinkingToItem(item) || []
|
||||||
|
const { filesLinkedToItem, filesLinkingToItem } = getFilesLinksForItem(item)
|
||||||
|
const tagsLinkedToItem = getLinkedTagsForItem(item) || []
|
||||||
|
|
||||||
|
useEffect(
|
||||||
|
() =>
|
||||||
|
application.streamItems([ContentType.Note, ContentType.File, ContentType.Tag], () => {
|
||||||
|
refresh(Date.now())
|
||||||
|
}),
|
||||||
|
[application],
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
notesLinkedToItem,
|
||||||
|
notesLinkingToItem,
|
||||||
|
filesLinkedToItem,
|
||||||
|
filesLinkingToItem,
|
||||||
|
tagsLinkedToItem,
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user