fix: issue with not being able to unlink a file from a note (#1836)

This commit is contained in:
Mo
2022-10-19 14:36:30 -05:00
committed by GitHub
parent ba9a9f8d01
commit 4030953b00
10 changed files with 122 additions and 243 deletions

View File

@@ -4,7 +4,8 @@ import { DecryptedPayloadInterface } from '../../Abstract/Payload/Interfaces/Dec
import { FileMetadata } from './FileMetadata' import { FileMetadata } from './FileMetadata'
import { FileProtocolV1 } from './FileProtocolV1' import { FileProtocolV1 } from './FileProtocolV1'
import { SortableItem } from '../../Runtime/Collection/CollectionSort' import { SortableItem } from '../../Runtime/Collection/CollectionSort'
import { ConflictStrategy } from '../../Abstract/Item' import { ConflictStrategy, ItemInterface } from '../../Abstract/Item'
import { ContentType } from '@standardnotes/common'
type EncryptedBytesLength = number type EncryptedBytesLength = number
type DecryptedBytesLength = number type DecryptedBytesLength = number
@@ -31,6 +32,8 @@ export type FileContentSpecialized = FileContentWithoutSize & FileMetadata & Siz
export type FileContent = FileContentSpecialized & ItemContent export type FileContent = FileContentSpecialized & ItemContent
export const isFile = (x: ItemInterface): x is FileItem => x.content_type === ContentType.File
export class FileItem export class FileItem
extends DecryptedItem<FileContent> extends DecryptedItem<FileContent>
implements FileContentWithoutSize, Sizes, FileProtocolV1, FileMetadata, SortableItem implements FileContentWithoutSize, Sizes, FileProtocolV1, FileMetadata, SortableItem

View File

@@ -0,0 +1,7 @@
/* istanbul ignore file */
export enum ItemRelationshipDirection {
AReferencesB,
BReferencesA,
NoRelationship,
}

View File

@@ -1,3 +1,5 @@
/* istanbul ignore file */
import { ContentType, Uuid } from '@standardnotes/common' import { ContentType, Uuid } from '@standardnotes/common'
import { import {
SNNote, SNNote,
@@ -76,9 +78,9 @@ export interface ItemsClientInterface {
linkNoteToNote(note: SNNote, otherNote: SNNote): Promise<SNNote> linkNoteToNote(note: SNNote, otherNote: SNNote): Promise<SNNote>
linkFileToFile(file: FileItem, otherFile: FileItem): Promise<FileItem> linkFileToFile(file: FileItem, otherFile: FileItem): Promise<FileItem>
unlinkItem( unlinkItems(
item: DecryptedItemInterface<ItemContent>, itemOne: DecryptedItemInterface<ItemContent>,
itemToUnlink: DecryptedItemInterface<ItemContent>, itemTwo: DecryptedItemInterface<ItemContent>,
): Promise<DecryptedItemInterface<ItemContent>> ): Promise<DecryptedItemInterface<ItemContent>>
/** /**
@@ -115,12 +117,6 @@ export interface ItemsClientInterface {
*/ */
getSortedTagsForItem(item: DecryptedItemInterface<ItemContent>): SNTag[] getSortedTagsForItem(item: DecryptedItemInterface<ItemContent>): SNTag[]
getSortedLinkedFilesForItem(item: DecryptedItemInterface<ItemContent>): FileItem[]
getSortedFilesLinkingToItem(item: DecryptedItemInterface<ItemContent>): FileItem[]
getSortedLinkedNotesForItem(item: DecryptedItemInterface<ItemContent>): SNNote[]
getSortedNotesLinkingToItem(item: DecryptedItemInterface<ItemContent>): SNNote[]
isSmartViewTitle(title: string): boolean isSmartViewTitle(title: string): boolean
getSmartViews(): SmartView[] getSmartViews(): SmartView[]
@@ -152,12 +148,4 @@ export interface ItemsClientInterface {
* @returns Whether the item is a template (unmanaged) * @returns Whether the item is a template (unmanaged)
*/ */
isTemplateItem(item: DecryptedItemInterface): boolean isTemplateItem(item: DecryptedItemInterface): boolean
/**
* @returns `'direct'` if `itemOne` has the reference to `itemTwo`, `'indirect'` if `itemTwo` has the reference to `itemOne`, `'unlinked'` if neither reference each other
*/
relationshipTypeForItems(
itemOne: DecryptedItemInterface,
itemTwo: DecryptedItemInterface,
): 'direct' | 'indirect' | 'unlinked'
} }

View File

@@ -54,6 +54,7 @@ export * from './Item/ItemCounterInterface'
export * from './Item/ItemManagerInterface' export * from './Item/ItemManagerInterface'
export * from './Item/ItemsClientInterface' export * from './Item/ItemsClientInterface'
export * from './Item/ItemsServerInterface' export * from './Item/ItemsServerInterface'
export * from './Item/ItemRelationshipDirection'
export * from './Mutator/MutatorClientInterface' export * from './Mutator/MutatorClientInterface'
export * from './Payloads/PayloadManagerInterface' export * from './Payloads/PayloadManagerInterface'
export * from './Preferences/PreferenceServiceInterface' export * from './Preferences/PreferenceServiceInterface'

View File

@@ -1,5 +1,5 @@
import { ContentType } from '@standardnotes/common' import { ContentType } from '@standardnotes/common'
import { InternalEventBusInterface } from '@standardnotes/services' import { InternalEventBusInterface, ItemRelationshipDirection } from '@standardnotes/services'
import { ItemManager } from './ItemManager' import { ItemManager } from './ItemManager'
import { PayloadManager } from '../Payloads/PayloadManager' import { PayloadManager } from '../Payloads/PayloadManager'
import { UuidGenerator } from '@standardnotes/utils' import { UuidGenerator } from '@standardnotes/utils'
@@ -784,21 +784,6 @@ describe('itemManager', () => {
expect(references).toHaveLength(0) expect(references).toHaveLength(0)
}) })
it('should get files linked with note', async () => {
itemManager = createService()
const note = createNoteWithTitle('invoices')
const file = createFile('invoice_1.pdf')
const secondFile = createFile('unrelated-file.xlsx')
await itemManager.insertItems([note, file, secondFile])
await itemManager.associateFileWithNote(file, note)
const filesAssociatedWithNote = itemManager.getSortedFilesLinkingToItem(note)
expect(filesAssociatedWithNote).toHaveLength(1)
expect(filesAssociatedWithNote[0].uuid).toBe(file.uuid)
})
it('should link note to note', async () => { it('should link note to note', async () => {
itemManager = createService() itemManager = createService()
const note = createNoteWithTitle('research') const note = createNoteWithTitle('research')
@@ -834,101 +819,49 @@ describe('itemManager', () => {
const firstNoteLinkedToSecond = await itemManager.linkNoteToNote(firstNote, secondNote) const firstNoteLinkedToSecond = await itemManager.linkNoteToNote(firstNote, secondNote)
const relationshipOfFirstNoteToSecond = itemManager.relationshipTypeForItems(firstNoteLinkedToSecond, secondNote) const relationshipOfFirstNoteToSecond = itemManager.relationshipDirectionBetweenItems(
const relationshipOfSecondNoteToFirst = itemManager.relationshipTypeForItems(secondNote, firstNoteLinkedToSecond) firstNoteLinkedToSecond,
const relationshipOfFirstNoteToUnlinked = itemManager.relationshipTypeForItems( secondNote,
)
const relationshipOfSecondNoteToFirst = itemManager.relationshipDirectionBetweenItems(
secondNote,
firstNoteLinkedToSecond,
)
const relationshipOfFirstNoteToUnlinked = itemManager.relationshipDirectionBetweenItems(
firstNoteLinkedToSecond, firstNoteLinkedToSecond,
unlinkedNote, unlinkedNote,
) )
expect(relationshipOfFirstNoteToSecond).toBe('direct') expect(relationshipOfFirstNoteToSecond).toBe(ItemRelationshipDirection.AReferencesB)
expect(relationshipOfSecondNoteToFirst).toBe('indirect') expect(relationshipOfSecondNoteToFirst).toBe(ItemRelationshipDirection.BReferencesA)
expect(relationshipOfFirstNoteToUnlinked).toBe('unlinked') expect(relationshipOfFirstNoteToUnlinked).toBe(ItemRelationshipDirection.NoRelationship)
}) })
it('should unlink itemToUnlink from item', async () => { it('should unlink itemOne from itemTwo if relation is direct', async () => {
itemManager = createService() itemManager = createService()
const note = createNoteWithTitle('Note 1') const note = createNoteWithTitle('Note 1')
const note2 = createNoteWithTitle('Note 2') const note2 = createNoteWithTitle('Note 2')
await itemManager.insertItems([note, note2]) await itemManager.insertItems([note, note2])
const linkedItem = await itemManager.linkNoteToNote(note, note2) const linkedItem = await itemManager.linkNoteToNote(note, note2)
const unlinkedItem = await itemManager.unlinkItem(linkedItem, note2) const unlinkedItem = await itemManager.unlinkItems(linkedItem, note2)
const references = unlinkedItem.references const references = unlinkedItem.references
expect(unlinkedItem.uuid).toBe(note.uuid)
expect(references).toHaveLength(0) expect(references).toHaveLength(0)
}) })
it('should get all linked files for item', async () => { it('should unlink itemTwo from itemOne if relation is indirect', async () => {
itemManager = createService() itemManager = createService()
const file = createFile('A1') const note = createNoteWithTitle('Note 1')
const file2 = createFile('B2') const note2 = createNoteWithTitle('Note 2')
const file3 = createFile('C3') await itemManager.insertItems([note, note2])
await itemManager.insertItems([file, file2, file3]) const linkedItem = await itemManager.linkNoteToNote(note, note2)
const changedItem = await itemManager.unlinkItems(linkedItem, note2)
await itemManager.linkFileToFile(file, file3) expect(changedItem.uuid).toBe(note.uuid)
await itemManager.linkFileToFile(file, file2) expect(changedItem.references).toHaveLength(0)
const sortedFilesForItem = itemManager.getSortedLinkedFilesForItem(file)
expect(sortedFilesForItem).toHaveLength(2)
expect(sortedFilesForItem[0].uuid).toEqual(file2.uuid)
expect(sortedFilesForItem[1].uuid).toEqual(file3.uuid)
})
it('should get all files linking to item', async () => {
itemManager = createService()
const baseFile = createFile('file')
const fileToLink1 = createFile('A1')
const fileToLink2 = createFile('B2')
await itemManager.insertItems([baseFile, fileToLink1, fileToLink2])
await itemManager.linkFileToFile(fileToLink2, baseFile)
await itemManager.linkFileToFile(fileToLink1, baseFile)
const sortedFilesForItem = itemManager.getSortedFilesLinkingToItem(baseFile)
expect(sortedFilesForItem).toHaveLength(2)
expect(sortedFilesForItem[0].uuid).toEqual(fileToLink1.uuid)
expect(sortedFilesForItem[1].uuid).toEqual(fileToLink2.uuid)
})
it('should get all linked notes for item', async () => {
itemManager = createService()
const baseNote = createNoteWithTitle('note')
const noteToLink1 = createNoteWithTitle('A1')
const noteToLink2 = createNoteWithTitle('B2')
await itemManager.insertItems([baseNote, noteToLink1, noteToLink2])
await itemManager.linkNoteToNote(baseNote, noteToLink2)
await itemManager.linkNoteToNote(baseNote, noteToLink1)
const sortedFilesForItem = itemManager.getSortedLinkedNotesForItem(baseNote)
expect(sortedFilesForItem).toHaveLength(2)
expect(sortedFilesForItem[0].uuid).toEqual(noteToLink1.uuid)
expect(sortedFilesForItem[1].uuid).toEqual(noteToLink2.uuid)
})
it('should get all notes linking to item', async () => {
itemManager = createService()
const baseNote = createNoteWithTitle('note')
const noteToLink1 = createNoteWithTitle('A1')
const noteToLink2 = createNoteWithTitle('B2')
await itemManager.insertItems([baseNote, noteToLink1, noteToLink2])
await itemManager.linkNoteToNote(noteToLink2, baseNote)
await itemManager.linkNoteToNote(noteToLink1, baseNote)
const sortedFilesForItem = itemManager.getSortedNotesLinkingToItem(baseNote)
expect(sortedFilesForItem).toHaveLength(2)
expect(sortedFilesForItem[0].uuid).toEqual(noteToLink1.uuid)
expect(sortedFilesForItem[1].uuid).toEqual(noteToLink2.uuid)
}) })
}) })
}) })

View File

@@ -7,7 +7,7 @@ import { UuidString } from '../../Types/UuidString'
import * as Models from '@standardnotes/models' import * as Models from '@standardnotes/models'
import * as Services from '@standardnotes/services' import * as Services from '@standardnotes/services'
import { PayloadManagerChangeData } from '../Payloads' import { PayloadManagerChangeData } from '../Payloads'
import { DiagnosticInfo, ItemsClientInterface } from '@standardnotes/services' import { DiagnosticInfo, ItemsClientInterface, ItemRelationshipDirection } from '@standardnotes/services'
import { ApplicationDisplayOptions } from '@Lib/Application/Options/OptionalOptions' import { ApplicationDisplayOptions } from '@Lib/Application/Options/OptionalOptions'
import { CollectionSort, DecryptedItemInterface, ItemContent } from '@standardnotes/models' import { CollectionSort, DecryptedItemInterface, ItemContent } from '@standardnotes/models'
@@ -1169,12 +1169,18 @@ export class ItemManager
}) })
} }
public async unlinkItem( public async unlinkItems(itemA: DecryptedItemInterface<ItemContent>, itemB: DecryptedItemInterface<ItemContent>) {
item: DecryptedItemInterface<ItemContent>, const relationshipDirection = this.relationshipDirectionBetweenItems(itemA, itemB)
itemToUnlink: DecryptedItemInterface<ItemContent>,
) { if (relationshipDirection === ItemRelationshipDirection.NoRelationship) {
return this.changeItem(item, (mutator) => { throw new Error('Trying to unlink already unlinked items')
mutator.removeItemAsRelationship(itemToUnlink) }
const itemToChange = relationshipDirection === ItemRelationshipDirection.AReferencesB ? itemA : itemB
const itemToRemove = itemToChange === itemA ? itemB : itemA
return this.changeItem(itemToChange, (mutator) => {
mutator.removeItemAsRelationship(itemToRemove)
}) })
} }
@@ -1192,54 +1198,6 @@ export class ItemManager
) )
} }
public getSortedLinkedFilesForItem(item: DecryptedItemInterface<ItemContent>): Models.FileItem[] {
if (this.isTemplateItem(item)) {
return []
}
const filesReferencedByItem = this.referencesForItem(item).filter(
(ref) => ref.content_type === ContentType.File,
) as Models.FileItem[]
return naturalSort(filesReferencedByItem, 'title')
}
public getSortedFilesLinkingToItem(item: DecryptedItemInterface<ItemContent>): Models.FileItem[] {
if (this.isTemplateItem(item)) {
return []
}
const filesReferencingItem = this.itemsReferencingItem(item).filter(
(ref) => ref.content_type === ContentType.File,
) as Models.FileItem[]
return naturalSort(filesReferencingItem, 'title')
}
public getSortedLinkedNotesForItem(item: DecryptedItemInterface<ItemContent>): Models.SNNote[] {
if (this.isTemplateItem(item)) {
return []
}
const notesReferencedByItem = this.referencesForItem(item).filter(
(ref) => ref.content_type === ContentType.Note,
) as Models.SNNote[]
return naturalSort(notesReferencedByItem, 'title')
}
public getSortedNotesLinkingToItem(item: Models.DecryptedItemInterface<Models.ItemContent>): Models.SNNote[] {
if (this.isTemplateItem(item)) {
return []
}
const notesReferencingItem = this.itemsReferencingItem(item).filter(
(ref) => ref.content_type === ContentType.Note,
) as Models.SNNote[]
return naturalSort(notesReferencingItem, 'title')
}
public async createTag(title: string, parentItemToLookupUuidFor?: Models.SNTag): Promise<Models.SNTag> { public async createTag(title: string, parentItemToLookupUuidFor?: Models.SNTag): Promise<Models.SNTag> {
const newTag = await this.createItem<Models.SNTag>( const newTag = await this.createItem<Models.SNTag>(
ContentType.Tag, ContentType.Tag,
@@ -1433,21 +1391,18 @@ export class ItemManager
return this.findAnyItems(uuids) as (Models.DecryptedItemInterface | Models.DeletedItemInterface)[] return this.findAnyItems(uuids) as (Models.DecryptedItemInterface | Models.DeletedItemInterface)[]
} }
/** public relationshipDirectionBetweenItems(
* @returns `'direct'` if `itemOne` has the reference to `itemTwo`, `'indirect'` if `itemTwo` has the reference to `itemOne`, `'unlinked'` if neither reference each other itemA: Models.DecryptedItemInterface<Models.ItemContent>,
*/ itemB: Models.DecryptedItemInterface<Models.ItemContent>,
public relationshipTypeForItems( ): ItemRelationshipDirection {
itemOne: Models.DecryptedItemInterface<Models.ItemContent>, const itemAReferencesItemB = !!itemA.references.find((reference) => reference.uuid === itemB.uuid)
itemTwo: Models.DecryptedItemInterface<Models.ItemContent>, const itemBReferencesItemA = !!itemB.references.find((reference) => reference.uuid === itemA.uuid)
): 'direct' | 'indirect' | 'unlinked' {
const itemOneReferencesItemTwo = this.isTemplateItem(itemOne)
? false
: !!this.referencesForItem(itemOne).find((reference) => reference.uuid === itemTwo.uuid)
const itemTwoReferencesItemOne = this.isTemplateItem(itemTwo)
? false
: !!this.referencesForItem(itemTwo).find((reference) => reference.uuid === itemOne.uuid)
return itemOneReferencesItemTwo ? 'direct' : itemTwoReferencesItemOne ? 'indirect' : 'unlinked' return itemAReferencesItemB
? ItemRelationshipDirection.AReferencesB
: itemBReferencesItemA
? ItemRelationshipDirection.BReferencesA
: ItemRelationshipDirection.NoRelationship
} }
override getDiagnostics(): Promise<DiagnosticInfo | undefined> { override getDiagnostics(): Promise<DiagnosticInfo | undefined> {

View File

@@ -1,5 +1,5 @@
import { PLAIN_EDITOR_NAME } from '@/Constants/Constants' import { PLAIN_EDITOR_NAME } from '@/Constants/Constants'
import { sanitizeHtmlString, SNNote } from '@standardnotes/snjs' import { isFile, sanitizeHtmlString, SNNote } from '@standardnotes/snjs'
import { observer } from 'mobx-react-lite' import { observer } from 'mobx-react-lite'
import { FunctionComponent, useCallback, useRef } from 'react' import { FunctionComponent, useCallback, useRef } from 'react'
import Icon from '@/Components/Icon/Icon' import Icon from '@/Components/Icon/Icon'
@@ -32,7 +32,7 @@ const NoteListItem: FunctionComponent<DisplayableListItemProps> = ({
const editorForNote = application.componentManager.editorForNote(item as SNNote) const editorForNote = application.componentManager.editorForNote(item as SNNote)
const editorName = editorForNote?.name ?? PLAIN_EDITOR_NAME const editorName = editorForNote?.name ?? PLAIN_EDITOR_NAME
const [icon, tint] = application.iconsController.getIconAndTintForNoteType(editorForNote?.package_info.note_type) const [icon, tint] = application.iconsController.getIconAndTintForNoteType(editorForNote?.package_info.note_type)
const hasFiles = application.items.getSortedFilesLinkingToItem(item).length > 0 const hasFiles = application.items.itemsReferencingItem(item).filter(isFile).length > 0
const openNoteContextMenu = (posX: number, posY: number) => { const openNoteContextMenu = (posX: number, posY: number) => {
notesController.setContextMenuOpen(false) notesController.setContextMenuOpen(false)

View File

@@ -103,7 +103,7 @@ const LinkedItemBubble = ({
<span className="max-w-290px flex items-center overflow-hidden overflow-ellipsis whitespace-nowrap"> <span className="max-w-290px flex items-center overflow-hidden overflow-ellipsis whitespace-nowrap">
{tagTitle && <span className="text-passive-1">{tagTitle.titlePrefix}</span>} {tagTitle && <span className="text-passive-1">{tagTitle.titlePrefix}</span>}
<span className="flex items-center gap-1"> <span className="flex items-center gap-1">
{link.relationWithSelectedItem === 'indirect' && link.item.content_type !== ContentType.Tag && ( {link.type === 'linked-by' && link.item.content_type !== ContentType.Tag && (
<span className={!isBidirectional ? 'hidden group-focus:block' : ''}>Linked By:</span> <span className={!isBidirectional ? 'hidden group-focus:block' : ''}>Linked By:</span>
)} )}
{link.item.title} {link.item.title}

View File

@@ -14,7 +14,14 @@ import {
ClassicFileSaver, ClassicFileSaver,
parseFileName, parseFileName,
} from '@standardnotes/filepicker' } from '@standardnotes/filepicker'
import { ChallengeReason, ClientDisplayableError, ContentType, FileItem, InternalEventBus } from '@standardnotes/snjs' import {
ChallengeReason,
ClientDisplayableError,
ContentType,
FileItem,
InternalEventBus,
isFile,
} from '@standardnotes/snjs'
import { addToast, dismissToast, ToastType, updateToast } from '@standardnotes/toast' import { addToast, dismissToast, ToastType, updateToast } from '@standardnotes/toast'
import { action, makeObservable, observable, reaction } from 'mobx' import { action, makeObservable, observable, reaction } from 'mobx'
import { WebApplication } from '../Application/Application' import { WebApplication } from '../Application/Application'
@@ -99,7 +106,7 @@ export class FilesController extends AbstractViewController {
reloadAttachedFiles = () => { reloadAttachedFiles = () => {
const note = this.notesController.firstSelectedNote const note = this.notesController.firstSelectedNote
if (note) { if (note) {
this.attachedFiles = this.application.items.getSortedFilesLinkingToItem(note) this.attachedFiles = this.application.items.itemsReferencingItem(note).filter(isFile)
} }
} }

View File

@@ -10,12 +10,13 @@ import {
IconType, IconType,
InternalEventBus, InternalEventBus,
ItemContent, ItemContent,
ItemsClientInterface,
naturalSort, naturalSort,
NoteViewController, NoteViewController,
PrefKey, PrefKey,
SNNote, SNNote,
SNTag, SNTag,
isFile,
isNote,
} from '@standardnotes/snjs' } from '@standardnotes/snjs'
import { action, computed, makeObservable, observable } from 'mobx' import { action, computed, makeObservable, observable } from 'mobx'
import { AbstractViewController } from './Abstract/AbstractViewController' import { AbstractViewController } from './Abstract/AbstractViewController'
@@ -27,12 +28,10 @@ import { SubscriptionController } from './Subscription/SubscriptionController'
export type LinkableItem = DecryptedItemInterface<ItemContent> export type LinkableItem = DecryptedItemInterface<ItemContent>
type RelationWithSelectedItem = ReturnType<ItemsClientInterface['relationshipTypeForItems']>
export type ItemLink<ItemType extends LinkableItem = LinkableItem> = { export type ItemLink<ItemType extends LinkableItem = LinkableItem> = {
id: string id: string
item: ItemType item: ItemType
relationWithSelectedItem: RelationWithSelectedItem type: 'linked' | 'linked-by'
} }
export class LinkingController extends AbstractViewController { export class LinkingController extends AbstractViewController {
@@ -143,68 +142,68 @@ export class LinkingController extends AbstractViewController {
this.reloadNotesLinkingToItem() this.reloadNotesLinkingToItem()
} }
createLinkFromItem = <ItemType extends LinkableItem = LinkableItem>( createLinkFromItem = <I extends LinkableItem = LinkableItem>(itemA: I, type: 'linked' | 'linked-by'): ItemLink<I> => {
item: ItemType,
relation?: RelationWithSelectedItem,
): ItemLink<ItemType> => {
const relationWithSelectedItem = relation ? relation : this.itemRelationshipWithSelectedItem(item)
return { return {
id: `${item.uuid}-${relationWithSelectedItem}`, id: `${itemA.uuid}-${type}`,
item, item: itemA,
relationWithSelectedItem, type,
} }
} }
reloadLinkedFiles() { reloadLinkedFiles() {
if (!this.activeItem) { if (!this.activeItem || this.application.items.isTemplateItem(this.activeItem)) {
return return
} }
const isActiveItemAFile = this.activeItem instanceof FileItem const referencesOfActiveItem = naturalSort(
this.application.items.referencesForItem(this.activeItem).filter(isFile),
'title',
)
const linkedFiles = this.application.items const referencingActiveItem = naturalSort(
.getSortedLinkedFilesForItem(this.activeItem) this.application.items.itemsReferencingItem(this.activeItem).filter(isFile),
.map((item) => this.createLinkFromItem(item, isActiveItemAFile ? 'direct' : 'indirect')) 'title',
)
const filesLinkingToActiveItem = this.application.items if (this.activeItem.content_type === ContentType.File) {
.getSortedFilesLinkingToItem(this.activeItem) this.linkedFiles = referencesOfActiveItem.map((item) => this.createLinkFromItem(item, 'linked'))
.map((item) => this.createLinkFromItem(item, isActiveItemAFile ? 'indirect' : 'direct')) this.filesLinkingToActiveItem = referencingActiveItem.map((item) => this.createLinkFromItem(item, 'linked-by'))
if (isActiveItemAFile) {
this.linkedFiles = linkedFiles
this.filesLinkingToActiveItem = filesLinkingToActiveItem
} else { } else {
this.linkedFiles = filesLinkingToActiveItem this.linkedFiles = referencingActiveItem.map((item) => this.createLinkFromItem(item, 'linked'))
this.filesLinkingToActiveItem = linkedFiles this.filesLinkingToActiveItem = referencesOfActiveItem.map((item) => this.createLinkFromItem(item, 'linked-by'))
} }
} }
reloadLinkedTags() { reloadLinkedTags() {
if (this.activeItem) { if (!this.activeItem || this.application.items.isTemplateItem(this.activeItem)) {
const tags = this.application.items return
.getSortedTagsForItem(this.activeItem)
.map((item) => this.createLinkFromItem(item))
this.tags = tags
} }
this.tags = this.application.items
.getSortedTagsForItem(this.activeItem)
.map((item) => this.createLinkFromItem(item, 'linked'))
} }
reloadLinkedNotes() { reloadLinkedNotes() {
if (this.activeItem) { if (!this.activeItem || this.application.items.isTemplateItem(this.activeItem)) {
const notes = this.application.items return
.getSortedLinkedNotesForItem(this.activeItem)
.map((item) => this.createLinkFromItem(item, 'direct'))
this.notesLinkedToItem = notes
} }
this.notesLinkedToItem = naturalSort(
this.application.items.referencesForItem(this.activeItem).filter(isNote),
'title',
).map((item) => this.createLinkFromItem(item, 'linked'))
} }
reloadNotesLinkingToItem() { reloadNotesLinkingToItem() {
if (this.activeItem) { if (!this.activeItem) {
const notes = this.application.items return
.getSortedNotesLinkingToItem(this.activeItem)
.map((item) => this.createLinkFromItem(item, 'indirect'))
this.notesLinkingToActiveItem = notes
} }
this.notesLinkingToActiveItem = naturalSort(
this.application.items.itemsReferencingItem(this.activeItem).filter(isNote),
'title',
).map((item) => this.createLinkFromItem(item, 'linked-by'))
} }
getTitleForLinkedTag = (item: LinkableItem) => { getTitleForLinkedTag = (item: LinkableItem) => {
@@ -263,16 +262,6 @@ export class LinkingController extends AbstractViewController {
return undefined return undefined
} }
itemRelationshipWithSelectedItem = (item: LinkableItem) => {
const activeItem = this.activeItem
if (!activeItem) {
throw new Error('No active item available')
}
return this.application.items.relationshipTypeForItems(activeItem, item)
}
unlinkItemFromSelectedItem = async (itemToUnlink: ItemLink) => { unlinkItemFromSelectedItem = async (itemToUnlink: ItemLink) => {
const selectedItem = this.selectionController.firstSelectedItem const selectedItem = this.selectionController.firstSelectedItem
@@ -280,13 +269,7 @@ export class LinkingController extends AbstractViewController {
return return
} }
const selectedItemReferencesItemToUnlink = itemToUnlink.relationWithSelectedItem === 'direct' await this.application.items.unlinkItems(selectedItem, itemToUnlink.item)
if (selectedItemReferencesItemToUnlink) {
await this.application.items.unlinkItem(selectedItem, itemToUnlink.item)
} else {
await this.application.items.unlinkItem(itemToUnlink.item, selectedItem)
}
void this.application.sync.sync() void this.application.sync.sync()
this.reloadAllLinks() this.reloadAllLinks()
@@ -404,9 +387,11 @@ export class LinkingController extends AbstractViewController {
const linkedResults = searchResults const linkedResults = searchResults
.filter(isAlreadyLinked) .filter(isAlreadyLinked)
.slice(0, 20) .slice(0, 20)
.map((item) => this.createLinkFromItem(item)) .map((item) => this.createLinkFromItem(item, 'linked'))
const isResultExistingTag = (result: DecryptedItemInterface<ItemContent>) => const isResultExistingTag = (result: DecryptedItemInterface<ItemContent>) =>
result.content_type === ContentType.Tag && result.title === searchQuery result.content_type === ContentType.Tag && result.title === searchQuery
const shouldShowCreateTag = const shouldShowCreateTag =
!linkedResults.find((link) => isResultExistingTag(link.item)) && !unlinkedResults.find(isResultExistingTag) !linkedResults.find((link) => isResultExistingTag(link.item)) && !unlinkedResults.find(isResultExistingTag)