refactor: note editor relationships (#1821)
This commit is contained in:
@@ -8,6 +8,6 @@ export enum AppDataField {
|
||||
NotAvailableOnMobile = 'notAvailableOnMobile',
|
||||
MobileActive = 'mobileActive',
|
||||
LastSize = 'lastSize',
|
||||
PrefersPlainEditor = 'prefersPlainEditor',
|
||||
LegacyPrefersPlainEditor = 'prefersPlainEditor',
|
||||
ComponentInstallError = 'installError',
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
24
packages/models/src/Domain/Syncable/Note/NoteMutator.spec.ts
Normal file
24
packages/models/src/Domain/Syncable/Note/NoteMutator.spec.ts
Normal 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)
|
||||
})
|
||||
})
|
||||
@@ -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
|
||||
|
||||
@@ -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>>
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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'
|
||||
|
||||
Reference in New Issue
Block a user