refactor: note editor relationships (#1821)

This commit is contained in:
Mo
2022-10-18 08:59:24 -05:00
committed by GitHub
parent c83dc48d3f
commit 2b66ff82ee
28 changed files with 357 additions and 299 deletions

View File

@@ -8,6 +8,6 @@ export enum AppDataField {
NotAvailableOnMobile = 'notAvailableOnMobile',
MobileActive = 'mobileActive',
LastSize = 'lastSize',
PrefersPlainEditor = 'prefersPlainEditor',
LegacyPrefersPlainEditor = 'prefersPlainEditor',
ComponentInstallError = 'installError',
}

View File

@@ -5,6 +5,7 @@ import { FillItemContent } from '../../Abstract/Content/ItemContent'
import { SNComponent } from './Component'
import { ComponentContent } from './ComponentContent'
import { PayloadTimestampDefaults } from '../../Abstract/Payload'
import { NoteType } from '@standardnotes/features'
describe('component model', () => {
it('valid hosted url should ignore url', () => {
@@ -46,4 +47,42 @@ describe('component model', () => {
expect(component.hasValidHostedUrl()).toBe(true)
expect(component.hosted_url).toBe('http://foo.com')
})
it('should return noteType as specified in package_info', () => {
const component = new SNComponent(
new DecryptedPayload(
{
uuid: String(Math.random()),
content_type: ContentType.Component,
content: FillItemContent({
package_info: {
note_type: NoteType.Authentication,
},
} as ComponentContent),
...PayloadTimestampDefaults(),
},
PayloadSource.Constructor,
),
)
expect(component.noteType).toEqual(NoteType.Authentication)
})
it('should return plain as noteType if no note type defined in package_info', () => {
const component = new SNComponent(
new DecryptedPayload(
{
uuid: String(Math.random()),
content_type: ContentType.Component,
content: FillItemContent({
package_info: {},
} as ComponentContent),
...PayloadTimestampDefaults(),
},
PayloadSource.Constructor,
),
)
expect(component.noteType).toEqual(NoteType.Plain)
})
})

View File

@@ -7,6 +7,7 @@ import {
ComponentFlag,
ComponentPermission,
FindNativeFeature,
NoteType,
} from '@standardnotes/features'
import { AppDataField } from '../../Abstract/Item/Types/AppDataField'
import { ComponentContent, ComponentInterface } from './ComponentContent'
@@ -177,6 +178,10 @@ export class SNComponent extends DecryptedItem<ComponentContent> implements Comp
return this.package_info as ThirdPartyFeatureDescription
}
public get noteType(): NoteType {
return this.package_info.note_type || NoteType.Plain
}
public get isDeprecated(): boolean {
let flags: string[] = this.package_info.flags ?? []
flags = flags.map((flag: string) => flag.toLowerCase())

View File

@@ -29,14 +29,9 @@ describe('SNNote Tests', () => {
expect(note.preview_html).toBeFalsy()
})
it('should set mobilePrefersPlainEditor when given a valid choice', () => {
const selected = createNote({
mobilePrefersPlainEditor: true,
})
it('should not set default value for note type if none is provided', () => {
const note = createNote({})
const unselected = createNote()
expect(selected.mobilePrefersPlainEditor).toBeTruthy()
expect(unselected.mobilePrefersPlainEditor).toBe(undefined)
expect(note.noteType).toBe(undefined)
})
})

View File

@@ -1,7 +1,8 @@
import { AppDataField } from './../../Abstract/Item/Types/AppDataField'
import { ContentType } from '@standardnotes/common'
import { FeatureIdentifier, NoteType } from '@standardnotes/features'
import { DecryptedItem } from '../../Abstract/Item/Implementations/DecryptedItem'
import { ItemInterface } from '../../Abstract/Item/Interfaces/ItemInterface'
import { AppDataField } from '../../Abstract/Item/Types/AppDataField'
import { DecryptedPayloadInterface } from '../../Abstract/Payload/Interfaces/DecryptedPayload'
import { NoteContent, NoteContentSpecialized } from './NoteContent'
@@ -10,12 +11,14 @@ export const isNote = (x: ItemInterface): x is SNNote => x.content_type === Cont
export class SNNote extends DecryptedItem<NoteContent> implements NoteContentSpecialized {
public readonly title: string
public readonly text: string
public readonly mobilePrefersPlainEditor?: boolean
public readonly hidePreview: boolean = false
public readonly preview_plain: string
public readonly preview_html: string
public readonly prefersPlainEditor: boolean
public readonly spellcheck?: boolean
public readonly noteType?: NoteType
/** The package_info.identifier of the editor (not its uuid), such as org.standardnotes.advanced-markdown */
public readonly editorIdentifier?: FeatureIdentifier | string
constructor(payload: DecryptedPayloadInterface<NoteContent>) {
super(payload)
@@ -26,9 +29,14 @@ export class SNNote extends DecryptedItem<NoteContent> implements NoteContentSpe
this.preview_html = String(this.payload.content.preview_html || '')
this.hidePreview = Boolean(this.payload.content.hidePreview)
this.spellcheck = this.payload.content.spellcheck
this.noteType = this.payload.content.noteType
this.editorIdentifier = this.payload.content.editorIdentifier
this.prefersPlainEditor = this.getAppDomainValueWithDefault(AppDataField.PrefersPlainEditor, false)
this.mobilePrefersPlainEditor = this.payload.content.mobilePrefersPlainEditor
if (!this.noteType) {
const prefersPlain = this.getAppDomainValueWithDefault(AppDataField.LegacyPrefersPlainEditor, false)
if (prefersPlain) {
this.noteType = NoteType.Plain
}
}
}
}

View File

@@ -1,13 +1,15 @@
import { FeatureIdentifier, NoteType } from '@standardnotes/features'
import { ItemContent } from '../../Abstract/Content/ItemContent'
export interface NoteContentSpecialized {
title: string
text: string
mobilePrefersPlainEditor?: boolean
hidePreview?: boolean
preview_plain?: string
preview_html?: string
spellcheck?: boolean
noteType?: NoteType
editorIdentifier?: FeatureIdentifier | string
}
export type NoteContent = NoteContentSpecialized & ItemContent

View File

@@ -0,0 +1,24 @@
import { NoteMutator } from './NoteMutator'
import { createNote } from './../../Utilities/Test/SpecUtils'
import { MutationType } from '../../Abstract/Item'
import { FeatureIdentifier, NoteType } from '@standardnotes/features'
describe('note mutator', () => {
it('sets noteType', () => {
const note = createNote({})
const mutator = new NoteMutator(note, MutationType.NoUpdateUserTimestamps)
mutator.noteType = NoteType.Authentication
const result = mutator.getResult()
expect(result.content.noteType).toEqual(NoteType.Authentication)
})
it('sets editorIdentifier', () => {
const note = createNote({})
const mutator = new NoteMutator(note, MutationType.NoUpdateUserTimestamps)
mutator.editorIdentifier = FeatureIdentifier.MarkdownProEditor
const result = mutator.getResult()
expect(result.content.editorIdentifier).toEqual(FeatureIdentifier.MarkdownProEditor)
})
})

View File

@@ -1,10 +1,10 @@
import { AppDataField } from '../../Abstract/Item/Types/AppDataField'
import { NoteContent } from './NoteContent'
import { DecryptedItemMutator } from '../../Abstract/Item/Mutator/DecryptedItemMutator'
import { SNNote } from './Note'
import { NoteToNoteReference } from '../../Abstract/Reference/NoteToNoteReference'
import { ContentType } from '@standardnotes/common'
import { ContentReferenceType } from '../../Abstract/Item'
import { FeatureIdentifier, NoteType } from '@standardnotes/features'
export class NoteMutator extends DecryptedItemMutator<NoteContent> {
set title(title: string) {
@@ -27,14 +27,18 @@ export class NoteMutator extends DecryptedItemMutator<NoteContent> {
this.mutableContent.preview_html = preview_html
}
set prefersPlainEditor(prefersPlainEditor: boolean) {
this.setAppDataItem(AppDataField.PrefersPlainEditor, prefersPlainEditor)
}
set spellcheck(spellcheck: boolean) {
this.mutableContent.spellcheck = spellcheck
}
set noteType(noteType: NoteType) {
this.mutableContent.noteType = noteType
}
set editorIdentifier(identifier: FeatureIdentifier | string | undefined) {
this.mutableContent.editorIdentifier = identifier
}
toggleSpellcheck(): void {
if (this.mutableContent.spellcheck == undefined) {
this.mutableContent.spellcheck = false

View File

@@ -1,55 +0,0 @@
import { DecryptedPayloadInterface } from './../../Abstract/Payload/Interfaces/DecryptedPayload'
import { ComponentContent } from '../../Syncable/Component/ComponentContent'
import { ComponentArea } from '@standardnotes/features'
import { ContentType } from '@standardnotes/common'
import { ComponentMutator, SNComponent } from '../../Syncable/Component'
import { CreateDecryptedItemFromPayload } from '../Item/ItemGenerator'
import { ImmutablePayloadCollection } from '../../Runtime/Collection/Payload/ImmutablePayloadCollection'
import { MutationType } from '../../Abstract/Item/Types/MutationType'
import { FullyFormedPayloadInterface } from '../../Abstract/Payload/Interfaces/UnionTypes'
import { isDecryptedPayload } from '../../Abstract/Payload'
import { SyncResolvedPayload } from '../../Runtime/Deltas/Utilities/SyncResolvedPayload'
export type AffectorFunction = (
basePayload: FullyFormedPayloadInterface,
duplicatePayload: FullyFormedPayloadInterface,
baseCollection: ImmutablePayloadCollection<FullyFormedPayloadInterface>,
) => SyncResolvedPayload[]
const NoteDuplicationAffectedPayloads: AffectorFunction = (
basePayload: FullyFormedPayloadInterface,
duplicatePayload: FullyFormedPayloadInterface,
baseCollection: ImmutablePayloadCollection<FullyFormedPayloadInterface>,
) => {
/** If note has editor, maintain editor relationship in duplicate note */
const components = baseCollection
.all(ContentType.Component)
.filter(isDecryptedPayload)
.map((payload) => {
return CreateDecryptedItemFromPayload<ComponentContent, SNComponent>(
payload as DecryptedPayloadInterface<ComponentContent>,
)
})
const editor = components
.filter((c) => c.area === ComponentArea.Editor)
.find((e) => {
return e.isExplicitlyEnabledForItem(basePayload.uuid)
})
if (!editor) {
return []
}
/** Modify the editor to include new note */
const mutator = new ComponentMutator(editor, MutationType.NoUpdateUserTimestamps)
mutator.associateWithItem(duplicatePayload.uuid)
const result = mutator.getResult() as SyncResolvedPayload
return [result]
}
export const AffectorMapping = {
[ContentType.Note]: NoteDuplicationAffectedPayloads,
} as Partial<Record<ContentType, AffectorFunction>>

View File

@@ -2,7 +2,6 @@ import { PayloadSource } from './../../Abstract/Payload/Types/PayloadSource'
import { extendArray, UuidGenerator } from '@standardnotes/utils'
import { ImmutablePayloadCollection } from '../../Runtime/Collection/Payload/ImmutablePayloadCollection'
import { ItemContent } from '../../Abstract/Content/ItemContent'
import { AffectorMapping } from './AffectorFunction'
import { PayloadsByUpdatingReferencingPayloadReferences } from './PayloadsByUpdatingReferencingPayloadReferences'
import { isDecryptedPayload } from '../../Abstract/Payload/Interfaces/TypeCheck'
import { FullyFormedPayloadInterface } from '../../Abstract/Payload/Interfaces/UnionTypes'
@@ -69,13 +68,5 @@ export function PayloadsByDuplicating<C extends ItemContent = ItemContent>(dto:
extendArray(results, updatedReferencing)
}
const affector = AffectorMapping[payload.content_type]
if (affector) {
const affected = affector(payload, copy, baseCollection)
if (affected) {
extendArray(results, affected)
}
}
return results
}

View File

@@ -74,7 +74,6 @@ export * from './Utilities/Item/FindItem'
export * from './Utilities/Item/ItemContentsDiffer'
export * from './Utilities/Item/ItemContentsEqual'
export * from './Utilities/Item/ItemGenerator'
export * from './Utilities/Payload/AffectorFunction'
export * from './Utilities/Payload/CopyPayloadWithContentOverride'
export * from './Utilities/Payload/CreatePayload'
export * from './Utilities/Payload/FindPayload'