refactor: fix conflicts view when conflicted copies are already in trash (#2339)
This commit is contained in:
@@ -17,6 +17,7 @@ export * from './Interfaces/DeletedItem'
|
|||||||
export * from './Interfaces/EncryptedItem'
|
export * from './Interfaces/EncryptedItem'
|
||||||
export * from './Interfaces/ItemInterface'
|
export * from './Interfaces/ItemInterface'
|
||||||
export * from './Interfaces/TypeCheck'
|
export * from './Interfaces/TypeCheck'
|
||||||
|
export * from './Interfaces/UnionTypes'
|
||||||
export * from './Mutator/DecryptedItemMutator'
|
export * from './Mutator/DecryptedItemMutator'
|
||||||
export * from './Mutator/DeleteMutator'
|
export * from './Mutator/DeleteMutator'
|
||||||
export * from './Mutator/ItemMutator'
|
export * from './Mutator/ItemMutator'
|
||||||
|
|||||||
@@ -185,10 +185,13 @@ export abstract class Collection<
|
|||||||
|
|
||||||
if (this.isDecryptedElement(element)) {
|
if (this.isDecryptedElement(element)) {
|
||||||
const conflictOf = element.content.conflict_of
|
const conflictOf = element.content.conflict_of
|
||||||
if (conflictOf) {
|
|
||||||
|
if (conflictOf && !element.content.trashed) {
|
||||||
this.conflictMap.establishRelationship(conflictOf, element.uuid)
|
this.conflictMap.establishRelationship(conflictOf, element.uuid)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isConflictOfButTrashed = conflictOf && element.content.trashed
|
||||||
|
|
||||||
const isInConflictMapButIsNotConflictOf =
|
const isInConflictMapButIsNotConflictOf =
|
||||||
!conflictOf && this.conflictMap.getInverseRelationships(element.uuid).length > 0
|
!conflictOf && this.conflictMap.getInverseRelationships(element.uuid).length > 0
|
||||||
|
|
||||||
@@ -196,7 +199,7 @@ export abstract class Collection<
|
|||||||
this.conflictMap.existsInDirectMap(element.uuid) &&
|
this.conflictMap.existsInDirectMap(element.uuid) &&
|
||||||
this.conflictMap.getDirectRelationships(element.uuid).length === 0
|
this.conflictMap.getDirectRelationships(element.uuid).length === 0
|
||||||
|
|
||||||
if (isInConflictMapButIsNotConflictOf || isInConflictMapButDoesNotHaveConflicts) {
|
if (isInConflictMapButIsNotConflictOf || isInConflictMapButDoesNotHaveConflicts || isConflictOfButTrashed) {
|
||||||
this.conflictMap.removeFromMap(element.uuid)
|
this.conflictMap.removeFromMap(element.uuid)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,12 +6,13 @@ import { ItemCollection } from './ItemCollection'
|
|||||||
import { FillItemContent, ItemContent } from '../../../Abstract/Content/ItemContent'
|
import { FillItemContent, ItemContent } from '../../../Abstract/Content/ItemContent'
|
||||||
|
|
||||||
describe('item collection', () => {
|
describe('item collection', () => {
|
||||||
const createDecryptedPayload = (uuid?: string): DecryptedPayload => {
|
const createDecryptedPayload = (uuid?: string, content?: Partial<NoteContent>): DecryptedPayload => {
|
||||||
return new DecryptedPayload({
|
return new DecryptedPayload({
|
||||||
uuid: uuid || String(Math.random()),
|
uuid: uuid || String(Math.random()),
|
||||||
content_type: ContentType.Note,
|
content_type: ContentType.Note,
|
||||||
content: FillItemContent<NoteContent>({
|
content: FillItemContent<NoteContent>({
|
||||||
title: 'foo',
|
title: 'foo',
|
||||||
|
...content,
|
||||||
}),
|
}),
|
||||||
...PayloadTimestampDefaults(),
|
...PayloadTimestampDefaults(),
|
||||||
})
|
})
|
||||||
@@ -33,4 +34,81 @@ describe('item collection', () => {
|
|||||||
|
|
||||||
expect(collection.all()).toHaveLength(1)
|
expect(collection.all()).toHaveLength(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should not add conflicted copy to conflictMap if trashed', () => {
|
||||||
|
const collection = new ItemCollection()
|
||||||
|
|
||||||
|
const mainItem = new DecryptedItem(createDecryptedPayload())
|
||||||
|
const nonTrashedConflictedItem = new DecryptedItem(
|
||||||
|
createDecryptedPayload(undefined, {
|
||||||
|
conflict_of: mainItem.uuid,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
const trashedConflictedItem = new DecryptedItem(
|
||||||
|
createDecryptedPayload(undefined, {
|
||||||
|
conflict_of: mainItem.uuid,
|
||||||
|
trashed: true,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
collection.set([mainItem, nonTrashedConflictedItem, trashedConflictedItem])
|
||||||
|
|
||||||
|
expect(collection.conflictMap.existsInDirectMap(mainItem.uuid)).toBe(true)
|
||||||
|
expect(collection.conflictMap.getDirectRelationships(mainItem.uuid).includes(nonTrashedConflictedItem.uuid)).toBe(
|
||||||
|
true,
|
||||||
|
)
|
||||||
|
expect(collection.conflictMap.getDirectRelationships(mainItem.uuid).includes(trashedConflictedItem.uuid)).toBe(
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should remove conflicted copy from conflictMap if not conflicted anymore', () => {
|
||||||
|
const collection = new ItemCollection()
|
||||||
|
|
||||||
|
const mainItem = new DecryptedItem(createDecryptedPayload())
|
||||||
|
const conflictedItem = new DecryptedItem(
|
||||||
|
createDecryptedPayload(undefined, {
|
||||||
|
conflict_of: mainItem.uuid,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
collection.set([mainItem, conflictedItem])
|
||||||
|
|
||||||
|
expect(collection.conflictMap.existsInInverseMap(conflictedItem.uuid)).toBe(true)
|
||||||
|
|
||||||
|
const updatedConflictedItem = new DecryptedItem(
|
||||||
|
conflictedItem.payload.copy({
|
||||||
|
content: { conflict_of: undefined } as unknown as jest.Mocked<ItemContent>,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
collection.set(updatedConflictedItem)
|
||||||
|
|
||||||
|
expect(collection.conflictMap.existsInInverseMap(conflictedItem.uuid)).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should remove conflict parent if it has no conflicts anymore', () => {
|
||||||
|
const collection = new ItemCollection()
|
||||||
|
|
||||||
|
const mainItem = new DecryptedItem(createDecryptedPayload())
|
||||||
|
const conflictedItem = new DecryptedItem(
|
||||||
|
createDecryptedPayload(undefined, {
|
||||||
|
conflict_of: mainItem.uuid,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
collection.set([mainItem, conflictedItem])
|
||||||
|
|
||||||
|
expect(collection.conflictMap.existsInDirectMap(mainItem.uuid)).toBe(true)
|
||||||
|
|
||||||
|
const updatedConflictedItem = new DecryptedItem(
|
||||||
|
conflictedItem.payload.copy({
|
||||||
|
content: { conflict_of: undefined } as unknown as jest.Mocked<ItemContent>,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
collection.set([updatedConflictedItem, mainItem])
|
||||||
|
|
||||||
|
expect(collection.conflictMap.existsInDirectMap(mainItem.uuid)).toBe(false)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { ItemWithTags } from './Search/ItemWithTags'
|
|||||||
import { itemMatchesQuery, itemPassesFilters } from './Search/SearchUtilities'
|
import { itemMatchesQuery, itemPassesFilters } from './Search/SearchUtilities'
|
||||||
import { ItemFilter, ReferenceLookupCollection, SearchableDecryptedItem } from './Search/Types'
|
import { ItemFilter, ReferenceLookupCollection, SearchableDecryptedItem } from './Search/Types'
|
||||||
import { FilterDisplayOptions } from './DisplayOptions'
|
import { FilterDisplayOptions } from './DisplayOptions'
|
||||||
|
import { SystemViewId } from '../../Syncable/SmartView'
|
||||||
|
|
||||||
export function computeUnifiedFilterForDisplayOptions(
|
export function computeUnifiedFilterForDisplayOptions(
|
||||||
options: FilterDisplayOptions,
|
options: FilterDisplayOptions,
|
||||||
@@ -75,7 +76,10 @@ export function computeFiltersForDisplayOptions(
|
|||||||
filters.push((item) => itemMatchesQuery(item, query, collection))
|
filters.push((item) => itemMatchesQuery(item, query, collection))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!viewsPredicate?.keypathIncludesString('conflict_of')) {
|
if (
|
||||||
|
!viewsPredicate?.keypathIncludesString('conflict_of') &&
|
||||||
|
!options.views?.some((v) => v.uuid === SystemViewId.TrashedNotes)
|
||||||
|
) {
|
||||||
filters.push((item) => !item.conflictOf)
|
filters.push((item) => !item.conflictOf)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import {
|
|||||||
ItemsKeyInterface,
|
ItemsKeyInterface,
|
||||||
ItemContent,
|
ItemContent,
|
||||||
DecryptedPayload,
|
DecryptedPayload,
|
||||||
|
AnyItemInterface,
|
||||||
} from '@standardnotes/models'
|
} from '@standardnotes/models'
|
||||||
|
|
||||||
export interface ItemsClientInterface {
|
export interface ItemsClientInterface {
|
||||||
@@ -168,5 +169,6 @@ export interface ItemsClientInterface {
|
|||||||
iconString?: string,
|
iconString?: string,
|
||||||
): Promise<SmartView>
|
): Promise<SmartView>
|
||||||
|
|
||||||
|
conflictsOf(uuid: string): AnyItemInterface[]
|
||||||
numberOfNotesWithConflicts(): number
|
numberOfNotesWithConflicts(): number
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1415,7 +1415,11 @@ export class ItemManager
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
numberOfNotesWithConflicts(): number {
|
public conflictsOf(uuid: string) {
|
||||||
|
return this.collection.conflictsOf(uuid)
|
||||||
|
}
|
||||||
|
|
||||||
|
public numberOfNotesWithConflicts(): number {
|
||||||
return this.collection.numberOfItemsWithConflicts()
|
return this.collection.numberOfItemsWithConflicts()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -432,36 +432,11 @@ class NoteView extends AbstractComponent<NoteViewProps, State> {
|
|||||||
this.debounceReloadEditorComponent()
|
this.debounceReloadEditorComponent()
|
||||||
})
|
})
|
||||||
|
|
||||||
this.removeNoteStreamObserver = this.application.streamItems<SNNote>(
|
this.removeNoteStreamObserver = this.application.streamItems<SNNote>(ContentType.Note, async () => {
|
||||||
ContentType.Note,
|
this.setState({
|
||||||
async ({ inserted, changed, removed }) => {
|
conflictedNotes: this.application.items.conflictsOf(this.note.uuid) as SNNote[],
|
||||||
const insertedOrChanged = inserted.concat(changed)
|
})
|
||||||
|
})
|
||||||
for (const note of insertedOrChanged) {
|
|
||||||
if (note.conflictOf === this.note.uuid && !note.trashed) {
|
|
||||||
this.setState((state) => ({
|
|
||||||
conflictedNotes: state.conflictedNotes
|
|
||||||
.filter((conflictedNote) => conflictedNote.uuid !== note.uuid)
|
|
||||||
.concat([note]),
|
|
||||||
}))
|
|
||||||
} else {
|
|
||||||
this.setState((state) => {
|
|
||||||
return {
|
|
||||||
conflictedNotes: state.conflictedNotes.filter((conflictedNote) => conflictedNote.uuid !== note.uuid),
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const note of removed) {
|
|
||||||
this.setState((state) => {
|
|
||||||
return {
|
|
||||||
conflictedNotes: state.conflictedNotes.filter((conflictedNote) => conflictedNote.uuid !== note.uuid),
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private createComponentViewer(component: SNComponent) {
|
private createComponentViewer(component: SNComponent) {
|
||||||
|
|||||||
Reference in New Issue
Block a user