refactor: note editor relationships (#1821)
This commit is contained in:
@@ -66,7 +66,7 @@
|
|||||||
"css-loader": "^6.7.1",
|
"css-loader": "^6.7.1",
|
||||||
"eslint": "^8.17.0",
|
"eslint": "^8.17.0",
|
||||||
"eslint-plugin-prettier": "^4.2.1",
|
"eslint-plugin-prettier": "^4.2.1",
|
||||||
"husky": "^8.0.0",
|
"husky": "^8.0.1",
|
||||||
"lint-staged": "^13.0.1",
|
"lint-staged": "^13.0.1",
|
||||||
"npm-check-updates": "^14.1.1",
|
"npm-check-updates": "^14.1.1",
|
||||||
"prettier": "^2.6.2",
|
"prettier": "^2.6.2",
|
||||||
|
|||||||
@@ -5,4 +5,5 @@ export enum NoteType {
|
|||||||
RichText = 'rich-text',
|
RichText = 'rich-text',
|
||||||
Spreadsheet = 'spreadsheet',
|
Spreadsheet = 'spreadsheet',
|
||||||
Task = 'task',
|
Task = 'task',
|
||||||
|
Plain = 'plain-text',
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,6 @@ export enum AppDataField {
|
|||||||
NotAvailableOnMobile = 'notAvailableOnMobile',
|
NotAvailableOnMobile = 'notAvailableOnMobile',
|
||||||
MobileActive = 'mobileActive',
|
MobileActive = 'mobileActive',
|
||||||
LastSize = 'lastSize',
|
LastSize = 'lastSize',
|
||||||
PrefersPlainEditor = 'prefersPlainEditor',
|
LegacyPrefersPlainEditor = 'prefersPlainEditor',
|
||||||
ComponentInstallError = 'installError',
|
ComponentInstallError = 'installError',
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { FillItemContent } from '../../Abstract/Content/ItemContent'
|
|||||||
import { SNComponent } from './Component'
|
import { SNComponent } from './Component'
|
||||||
import { ComponentContent } from './ComponentContent'
|
import { ComponentContent } from './ComponentContent'
|
||||||
import { PayloadTimestampDefaults } from '../../Abstract/Payload'
|
import { PayloadTimestampDefaults } from '../../Abstract/Payload'
|
||||||
|
import { NoteType } from '@standardnotes/features'
|
||||||
|
|
||||||
describe('component model', () => {
|
describe('component model', () => {
|
||||||
it('valid hosted url should ignore url', () => {
|
it('valid hosted url should ignore url', () => {
|
||||||
@@ -46,4 +47,42 @@ describe('component model', () => {
|
|||||||
expect(component.hasValidHostedUrl()).toBe(true)
|
expect(component.hasValidHostedUrl()).toBe(true)
|
||||||
expect(component.hosted_url).toBe('http://foo.com')
|
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,
|
ComponentFlag,
|
||||||
ComponentPermission,
|
ComponentPermission,
|
||||||
FindNativeFeature,
|
FindNativeFeature,
|
||||||
|
NoteType,
|
||||||
} from '@standardnotes/features'
|
} from '@standardnotes/features'
|
||||||
import { AppDataField } from '../../Abstract/Item/Types/AppDataField'
|
import { AppDataField } from '../../Abstract/Item/Types/AppDataField'
|
||||||
import { ComponentContent, ComponentInterface } from './ComponentContent'
|
import { ComponentContent, ComponentInterface } from './ComponentContent'
|
||||||
@@ -177,6 +178,10 @@ export class SNComponent extends DecryptedItem<ComponentContent> implements Comp
|
|||||||
return this.package_info as ThirdPartyFeatureDescription
|
return this.package_info as ThirdPartyFeatureDescription
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public get noteType(): NoteType {
|
||||||
|
return this.package_info.note_type || NoteType.Plain
|
||||||
|
}
|
||||||
|
|
||||||
public get isDeprecated(): boolean {
|
public get isDeprecated(): boolean {
|
||||||
let flags: string[] = this.package_info.flags ?? []
|
let flags: string[] = this.package_info.flags ?? []
|
||||||
flags = flags.map((flag: string) => flag.toLowerCase())
|
flags = flags.map((flag: string) => flag.toLowerCase())
|
||||||
|
|||||||
@@ -29,14 +29,9 @@ describe('SNNote Tests', () => {
|
|||||||
expect(note.preview_html).toBeFalsy()
|
expect(note.preview_html).toBeFalsy()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should set mobilePrefersPlainEditor when given a valid choice', () => {
|
it('should not set default value for note type if none is provided', () => {
|
||||||
const selected = createNote({
|
const note = createNote({})
|
||||||
mobilePrefersPlainEditor: true,
|
|
||||||
})
|
|
||||||
|
|
||||||
const unselected = createNote()
|
expect(note.noteType).toBe(undefined)
|
||||||
|
|
||||||
expect(selected.mobilePrefersPlainEditor).toBeTruthy()
|
|
||||||
expect(unselected.mobilePrefersPlainEditor).toBe(undefined)
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
|
import { AppDataField } from './../../Abstract/Item/Types/AppDataField'
|
||||||
import { ContentType } from '@standardnotes/common'
|
import { ContentType } from '@standardnotes/common'
|
||||||
|
import { FeatureIdentifier, NoteType } from '@standardnotes/features'
|
||||||
import { DecryptedItem } from '../../Abstract/Item/Implementations/DecryptedItem'
|
import { DecryptedItem } from '../../Abstract/Item/Implementations/DecryptedItem'
|
||||||
import { ItemInterface } from '../../Abstract/Item/Interfaces/ItemInterface'
|
import { ItemInterface } from '../../Abstract/Item/Interfaces/ItemInterface'
|
||||||
import { AppDataField } from '../../Abstract/Item/Types/AppDataField'
|
|
||||||
import { DecryptedPayloadInterface } from '../../Abstract/Payload/Interfaces/DecryptedPayload'
|
import { DecryptedPayloadInterface } from '../../Abstract/Payload/Interfaces/DecryptedPayload'
|
||||||
import { NoteContent, NoteContentSpecialized } from './NoteContent'
|
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 {
|
export class SNNote extends DecryptedItem<NoteContent> implements NoteContentSpecialized {
|
||||||
public readonly title: string
|
public readonly title: string
|
||||||
public readonly text: string
|
public readonly text: string
|
||||||
public readonly mobilePrefersPlainEditor?: boolean
|
|
||||||
public readonly hidePreview: boolean = false
|
public readonly hidePreview: boolean = false
|
||||||
public readonly preview_plain: string
|
public readonly preview_plain: string
|
||||||
public readonly preview_html: string
|
public readonly preview_html: string
|
||||||
public readonly prefersPlainEditor: boolean
|
|
||||||
public readonly spellcheck?: 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>) {
|
constructor(payload: DecryptedPayloadInterface<NoteContent>) {
|
||||||
super(payload)
|
super(payload)
|
||||||
@@ -26,9 +29,14 @@ export class SNNote extends DecryptedItem<NoteContent> implements NoteContentSpe
|
|||||||
this.preview_html = String(this.payload.content.preview_html || '')
|
this.preview_html = String(this.payload.content.preview_html || '')
|
||||||
this.hidePreview = Boolean(this.payload.content.hidePreview)
|
this.hidePreview = Boolean(this.payload.content.hidePreview)
|
||||||
this.spellcheck = this.payload.content.spellcheck
|
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)
|
if (!this.noteType) {
|
||||||
|
const prefersPlain = this.getAppDomainValueWithDefault(AppDataField.LegacyPrefersPlainEditor, false)
|
||||||
this.mobilePrefersPlainEditor = this.payload.content.mobilePrefersPlainEditor
|
if (prefersPlain) {
|
||||||
|
this.noteType = NoteType.Plain
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,15 @@
|
|||||||
|
import { FeatureIdentifier, NoteType } from '@standardnotes/features'
|
||||||
import { ItemContent } from '../../Abstract/Content/ItemContent'
|
import { ItemContent } from '../../Abstract/Content/ItemContent'
|
||||||
|
|
||||||
export interface NoteContentSpecialized {
|
export interface NoteContentSpecialized {
|
||||||
title: string
|
title: string
|
||||||
text: string
|
text: string
|
||||||
mobilePrefersPlainEditor?: boolean
|
|
||||||
hidePreview?: boolean
|
hidePreview?: boolean
|
||||||
preview_plain?: string
|
preview_plain?: string
|
||||||
preview_html?: string
|
preview_html?: string
|
||||||
spellcheck?: boolean
|
spellcheck?: boolean
|
||||||
|
noteType?: NoteType
|
||||||
|
editorIdentifier?: FeatureIdentifier | string
|
||||||
}
|
}
|
||||||
|
|
||||||
export type NoteContent = NoteContentSpecialized & ItemContent
|
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 { NoteContent } from './NoteContent'
|
||||||
import { DecryptedItemMutator } from '../../Abstract/Item/Mutator/DecryptedItemMutator'
|
import { DecryptedItemMutator } from '../../Abstract/Item/Mutator/DecryptedItemMutator'
|
||||||
import { SNNote } from './Note'
|
import { SNNote } from './Note'
|
||||||
import { NoteToNoteReference } from '../../Abstract/Reference/NoteToNoteReference'
|
import { NoteToNoteReference } from '../../Abstract/Reference/NoteToNoteReference'
|
||||||
import { ContentType } from '@standardnotes/common'
|
import { ContentType } from '@standardnotes/common'
|
||||||
import { ContentReferenceType } from '../../Abstract/Item'
|
import { ContentReferenceType } from '../../Abstract/Item'
|
||||||
|
import { FeatureIdentifier, NoteType } from '@standardnotes/features'
|
||||||
|
|
||||||
export class NoteMutator extends DecryptedItemMutator<NoteContent> {
|
export class NoteMutator extends DecryptedItemMutator<NoteContent> {
|
||||||
set title(title: string) {
|
set title(title: string) {
|
||||||
@@ -27,14 +27,18 @@ export class NoteMutator extends DecryptedItemMutator<NoteContent> {
|
|||||||
this.mutableContent.preview_html = preview_html
|
this.mutableContent.preview_html = preview_html
|
||||||
}
|
}
|
||||||
|
|
||||||
set prefersPlainEditor(prefersPlainEditor: boolean) {
|
|
||||||
this.setAppDataItem(AppDataField.PrefersPlainEditor, prefersPlainEditor)
|
|
||||||
}
|
|
||||||
|
|
||||||
set spellcheck(spellcheck: boolean) {
|
set spellcheck(spellcheck: boolean) {
|
||||||
this.mutableContent.spellcheck = spellcheck
|
this.mutableContent.spellcheck = spellcheck
|
||||||
}
|
}
|
||||||
|
|
||||||
|
set noteType(noteType: NoteType) {
|
||||||
|
this.mutableContent.noteType = noteType
|
||||||
|
}
|
||||||
|
|
||||||
|
set editorIdentifier(identifier: FeatureIdentifier | string | undefined) {
|
||||||
|
this.mutableContent.editorIdentifier = identifier
|
||||||
|
}
|
||||||
|
|
||||||
toggleSpellcheck(): void {
|
toggleSpellcheck(): void {
|
||||||
if (this.mutableContent.spellcheck == undefined) {
|
if (this.mutableContent.spellcheck == undefined) {
|
||||||
this.mutableContent.spellcheck = false
|
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 { extendArray, UuidGenerator } from '@standardnotes/utils'
|
||||||
import { ImmutablePayloadCollection } from '../../Runtime/Collection/Payload/ImmutablePayloadCollection'
|
import { ImmutablePayloadCollection } from '../../Runtime/Collection/Payload/ImmutablePayloadCollection'
|
||||||
import { ItemContent } from '../../Abstract/Content/ItemContent'
|
import { ItemContent } from '../../Abstract/Content/ItemContent'
|
||||||
import { AffectorMapping } from './AffectorFunction'
|
|
||||||
import { PayloadsByUpdatingReferencingPayloadReferences } from './PayloadsByUpdatingReferencingPayloadReferences'
|
import { PayloadsByUpdatingReferencingPayloadReferences } from './PayloadsByUpdatingReferencingPayloadReferences'
|
||||||
import { isDecryptedPayload } from '../../Abstract/Payload/Interfaces/TypeCheck'
|
import { isDecryptedPayload } from '../../Abstract/Payload/Interfaces/TypeCheck'
|
||||||
import { FullyFormedPayloadInterface } from '../../Abstract/Payload/Interfaces/UnionTypes'
|
import { FullyFormedPayloadInterface } from '../../Abstract/Payload/Interfaces/UnionTypes'
|
||||||
@@ -69,13 +68,5 @@ export function PayloadsByDuplicating<C extends ItemContent = ItemContent>(dto:
|
|||||||
extendArray(results, updatedReferencing)
|
extendArray(results, updatedReferencing)
|
||||||
}
|
}
|
||||||
|
|
||||||
const affector = AffectorMapping[payload.content_type]
|
|
||||||
if (affector) {
|
|
||||||
const affected = affector(payload, copy, baseCollection)
|
|
||||||
if (affected) {
|
|
||||||
extendArray(results, affected)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return results
|
return results
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -74,7 +74,6 @@ export * from './Utilities/Item/FindItem'
|
|||||||
export * from './Utilities/Item/ItemContentsDiffer'
|
export * from './Utilities/Item/ItemContentsDiffer'
|
||||||
export * from './Utilities/Item/ItemContentsEqual'
|
export * from './Utilities/Item/ItemContentsEqual'
|
||||||
export * from './Utilities/Item/ItemGenerator'
|
export * from './Utilities/Item/ItemGenerator'
|
||||||
export * from './Utilities/Payload/AffectorFunction'
|
|
||||||
export * from './Utilities/Payload/CopyPayloadWithContentOverride'
|
export * from './Utilities/Payload/CopyPayloadWithContentOverride'
|
||||||
export * from './Utilities/Payload/CreatePayload'
|
export * from './Utilities/Payload/CreatePayload'
|
||||||
export * from './Utilities/Payload/FindPayload'
|
export * from './Utilities/Payload/FindPayload'
|
||||||
|
|||||||
@@ -20,5 +20,5 @@ export interface ComponentManagerInterface {
|
|||||||
urlOverride?: string,
|
urlOverride?: string,
|
||||||
): ComponentViewerInterface
|
): ComponentViewerInterface
|
||||||
presentPermissionsDialog(_dialog: PermissionDialog): void
|
presentPermissionsDialog(_dialog: PermissionDialog): void
|
||||||
getDefaultEditor(): SNComponent
|
getDefaultEditor(): SNComponent | undefined
|
||||||
}
|
}
|
||||||
|
|||||||
33
packages/snjs/lib/Client/NoteViewController.spec.ts
Normal file
33
packages/snjs/lib/Client/NoteViewController.spec.ts
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import { SNApplication } from './../Application/Application'
|
||||||
|
import { ContentType } from '@standardnotes/common'
|
||||||
|
import { MutatorService } from './../Services/Mutator/MutatorService'
|
||||||
|
import { SNComponentManager } from './../Services/ComponentManager/ComponentManager'
|
||||||
|
import { NoteType } from '@standardnotes/features'
|
||||||
|
import { NoteViewController } from './NoteViewController'
|
||||||
|
|
||||||
|
describe('note view controller', () => {
|
||||||
|
let application: SNApplication
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
application = {} as jest.Mocked<SNApplication>
|
||||||
|
application.streamItems = jest.fn()
|
||||||
|
|
||||||
|
const componentManager = {} as jest.Mocked<SNComponentManager>
|
||||||
|
componentManager.getDefaultEditor = jest.fn()
|
||||||
|
Object.defineProperty(application, 'componentManager', { value: componentManager })
|
||||||
|
|
||||||
|
const mutator = {} as jest.Mocked<MutatorService>
|
||||||
|
mutator.createTemplateItem = jest.fn()
|
||||||
|
Object.defineProperty(application, 'mutator', { value: mutator })
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should create notes with plaintext note type', async () => {
|
||||||
|
const controller = new NoteViewController(application)
|
||||||
|
await controller.initialize(false)
|
||||||
|
|
||||||
|
expect(application.mutator.createTemplateItem).toHaveBeenCalledWith(
|
||||||
|
ContentType.Note,
|
||||||
|
expect.objectContaining({ noteType: NoteType.Plain }),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { NoteType } from '@standardnotes/features'
|
||||||
import {
|
import {
|
||||||
NoteMutator,
|
NoteMutator,
|
||||||
SNNote,
|
SNNote,
|
||||||
@@ -66,9 +67,12 @@ export class NoteViewController implements ItemViewControllerInterface {
|
|||||||
|
|
||||||
async initialize(addTagHierarchy: boolean): Promise<void> {
|
async initialize(addTagHierarchy: boolean): Promise<void> {
|
||||||
if (!this.item) {
|
if (!this.item) {
|
||||||
|
const editor = this.application.componentManager.getDefaultEditor()
|
||||||
const note = this.application.mutator.createTemplateItem<NoteContent, SNNote>(ContentType.Note, {
|
const note = this.application.mutator.createTemplateItem<NoteContent, SNNote>(ContentType.Note, {
|
||||||
text: '',
|
text: '',
|
||||||
title: this.defaultTitle || '',
|
title: this.defaultTitle || '',
|
||||||
|
noteType: editor?.noteType || NoteType.Plain,
|
||||||
|
editorIdentifier: editor?.identifier,
|
||||||
references: [],
|
references: [],
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -3,12 +3,14 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { SNPreferencesService } from '../Preferences/PreferencesService'
|
import { SNPreferencesService } from '../Preferences/PreferencesService'
|
||||||
|
import { createNote } from './../../Spec/SpecUtils'
|
||||||
import {
|
import {
|
||||||
ComponentAction,
|
ComponentAction,
|
||||||
ComponentPermission,
|
ComponentPermission,
|
||||||
FeatureDescription,
|
FeatureDescription,
|
||||||
FindNativeFeature,
|
FindNativeFeature,
|
||||||
FeatureIdentifier,
|
FeatureIdentifier,
|
||||||
|
NoteType,
|
||||||
} from '@standardnotes/features'
|
} from '@standardnotes/features'
|
||||||
import { ContentType } from '@standardnotes/common'
|
import { ContentType } from '@standardnotes/common'
|
||||||
import { GenericItem, SNComponent, Environment, Platform } from '@standardnotes/models'
|
import { GenericItem, SNComponent, Environment, Platform } from '@standardnotes/models'
|
||||||
@@ -306,6 +308,26 @@ describe('featuresService', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('editors', () => {
|
||||||
|
it('getEditorForNote should return undefined is note type is plain', () => {
|
||||||
|
const note = createNote({
|
||||||
|
noteType: NoteType.Plain,
|
||||||
|
})
|
||||||
|
const manager = createManager(Environment.Web, Platform.MacWeb)
|
||||||
|
|
||||||
|
expect(manager.editorForNote(note)).toBe(undefined)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('getEditorForNote should call legacy function if no note editorIdentifier or noteType', () => {
|
||||||
|
const note = createNote({})
|
||||||
|
const manager = createManager(Environment.Web, Platform.MacWeb)
|
||||||
|
manager['legacyGetEditorForNote'] = jest.fn()
|
||||||
|
manager.editorForNote(note)
|
||||||
|
|
||||||
|
expect(manager['legacyGetEditorForNote']).toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
describe('editor change alert', () => {
|
describe('editor change alert', () => {
|
||||||
it('should not require alert switching from plain editor', () => {
|
it('should not require alert switching from plain editor', () => {
|
||||||
const manager = createManager(Environment.Web, Platform.MacWeb)
|
const manager = createManager(Environment.Web, Platform.MacWeb)
|
||||||
|
|||||||
@@ -17,7 +17,14 @@ import {
|
|||||||
import { SNSyncService } from '@Lib/Services/Sync/SyncService'
|
import { SNSyncService } from '@Lib/Services/Sync/SyncService'
|
||||||
import find from 'lodash/find'
|
import find from 'lodash/find'
|
||||||
import uniq from 'lodash/uniq'
|
import uniq from 'lodash/uniq'
|
||||||
import { ComponentArea, ComponentAction, ComponentPermission, FindNativeFeature } from '@standardnotes/features'
|
import {
|
||||||
|
ComponentArea,
|
||||||
|
ComponentAction,
|
||||||
|
ComponentPermission,
|
||||||
|
FindNativeFeature,
|
||||||
|
NoteType,
|
||||||
|
FeatureIdentifier,
|
||||||
|
} from '@standardnotes/features'
|
||||||
import { Copy, filterFromArray, removeFromArray, sleep, assert } from '@standardnotes/utils'
|
import { Copy, filterFromArray, removeFromArray, sleep, assert } from '@standardnotes/utils'
|
||||||
import { UuidString } from '@Lib/Types/UuidString'
|
import { UuidString } from '@Lib/Types/UuidString'
|
||||||
import { AllowedBatchContentTypes } from '@Lib/Services/ComponentManager/Types'
|
import { AllowedBatchContentTypes } from '@Lib/Services/ComponentManager/Types'
|
||||||
@@ -112,6 +119,10 @@ export class SNComponentManager
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentWithIdentifier(identifier: FeatureIdentifier | string): SNComponent | undefined {
|
||||||
|
return this.components.find((component) => component.identifier === identifier)
|
||||||
|
}
|
||||||
|
|
||||||
override deinit(): void {
|
override deinit(): void {
|
||||||
super.deinit()
|
super.deinit()
|
||||||
|
|
||||||
@@ -569,13 +580,6 @@ export class SNComponentManager
|
|||||||
}
|
}
|
||||||
|
|
||||||
allComponentIframes(): HTMLIFrameElement[] {
|
allComponentIframes(): HTMLIFrameElement[] {
|
||||||
if (this.isMobile) {
|
|
||||||
/**
|
|
||||||
* Retrieving all iframes is typically related to lifecycle management of
|
|
||||||
* non-editor components. So this function is not useful to mobile.
|
|
||||||
*/
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
return Array.from(document.getElementsByTagName('iframe'))
|
return Array.from(document.getElementsByTagName('iframe'))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -584,23 +588,29 @@ export class SNComponentManager
|
|||||||
}
|
}
|
||||||
|
|
||||||
editorForNote(note: SNNote): SNComponent | undefined {
|
editorForNote(note: SNNote): SNComponent | undefined {
|
||||||
|
if (note.editorIdentifier) {
|
||||||
|
return this.componentWithIdentifier(note.editorIdentifier)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (note.noteType === NoteType.Plain) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.legacyGetEditorForNote(note)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Uses legacy approach of note/editor association. New method uses note.editorIdentifier and note.noteType directly.
|
||||||
|
*/
|
||||||
|
private legacyGetEditorForNote(note: SNNote): SNComponent | undefined {
|
||||||
const editors = this.componentsForArea(ComponentArea.Editor)
|
const editors = this.componentsForArea(ComponentArea.Editor)
|
||||||
for (const editor of editors) {
|
for (const editor of editors) {
|
||||||
if (editor.isExplicitlyEnabledForItem(note.uuid)) {
|
if (editor.isExplicitlyEnabledForItem(note.uuid)) {
|
||||||
return editor
|
return editor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let defaultEditor
|
const defaultEditor = this.getDefaultEditor()
|
||||||
/* No editor found for note. Use default editor, if note does not prefer system editor */
|
|
||||||
if (this.isMobile) {
|
|
||||||
if (!note.mobilePrefersPlainEditor) {
|
|
||||||
defaultEditor = this.getDefaultEditor()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (!note.prefersPlainEditor) {
|
|
||||||
defaultEditor = this.getDefaultEditor()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (defaultEditor && !defaultEditor.isExplicitlyDisabledForItem(note.uuid)) {
|
if (defaultEditor && !defaultEditor.isExplicitlyDisabledForItem(note.uuid)) {
|
||||||
return defaultEditor
|
return defaultEditor
|
||||||
} else {
|
} else {
|
||||||
@@ -608,15 +618,9 @@ export class SNComponentManager
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getDefaultEditor(): SNComponent {
|
getDefaultEditor(): SNComponent | undefined {
|
||||||
const editors = this.componentsForArea(ComponentArea.Editor)
|
const editors = this.componentsForArea(ComponentArea.Editor)
|
||||||
if (this.isMobile) {
|
return editors.filter((e) => e.isDefaultEditor())[0]
|
||||||
return editors.filter((e) => {
|
|
||||||
return e.isMobileDefault
|
|
||||||
})[0]
|
|
||||||
} else {
|
|
||||||
return editors.filter((e) => e.isDefaultEditor())[0]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
permissionsStringForPermissions(permissions: ComponentPermission[], component: SNComponent): string {
|
permissionsStringForPermissions(permissions: ComponentPermission[], component: SNComponent): string {
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import {
|
|||||||
SmartView,
|
SmartView,
|
||||||
SystemViewId,
|
SystemViewId,
|
||||||
} from '@standardnotes/models'
|
} from '@standardnotes/models'
|
||||||
|
import { createNoteWithTitle } from '../../Spec/SpecUtils'
|
||||||
|
|
||||||
const setupRandomUuid = () => {
|
const setupRandomUuid = () => {
|
||||||
UuidGenerator.SetGenerator(() => String(Math.random()))
|
UuidGenerator.SetGenerator(() => String(Math.random()))
|
||||||
@@ -83,19 +84,6 @@ describe('itemManager', () => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const createNote = (title: string) => {
|
|
||||||
return new Models.SNNote(
|
|
||||||
new Models.DecryptedPayload({
|
|
||||||
uuid: String(Math.random()),
|
|
||||||
content_type: ContentType.Note,
|
|
||||||
content: Models.FillItemContent<Models.NoteContent>({
|
|
||||||
title: title,
|
|
||||||
}),
|
|
||||||
...PayloadTimestampDefaults(),
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const createFile = (name: string) => {
|
const createFile = (name: string) => {
|
||||||
return new Models.FileItem(
|
return new Models.FileItem(
|
||||||
new Models.DecryptedPayload({
|
new Models.DecryptedPayload({
|
||||||
@@ -168,7 +156,7 @@ describe('itemManager', () => {
|
|||||||
it('viewing notes with tag', async () => {
|
it('viewing notes with tag', async () => {
|
||||||
itemManager = createService()
|
itemManager = createService()
|
||||||
const tag = createTag('parent')
|
const tag = createTag('parent')
|
||||||
const note = createNote('note')
|
const note = createNoteWithTitle('note')
|
||||||
await itemManager.insertItems([tag, note])
|
await itemManager.insertItems([tag, note])
|
||||||
await itemManager.addTagToNote(note, tag, false)
|
await itemManager.addTagToNote(note, tag, false)
|
||||||
|
|
||||||
@@ -185,9 +173,9 @@ describe('itemManager', () => {
|
|||||||
it('viewing trashed notes smart view should include archived notes', async () => {
|
it('viewing trashed notes smart view should include archived notes', async () => {
|
||||||
itemManager = createService()
|
itemManager = createService()
|
||||||
|
|
||||||
const archivedNote = createNote('archived')
|
const archivedNote = createNoteWithTitle('archived')
|
||||||
const trashedNote = createNote('trashed')
|
const trashedNote = createNoteWithTitle('trashed')
|
||||||
const archivedAndTrashedNote = createNote('archived&trashed')
|
const archivedAndTrashedNote = createNoteWithTitle('archived&trashed')
|
||||||
|
|
||||||
await itemManager.insertItems([archivedNote, trashedNote, archivedAndTrashedNote])
|
await itemManager.insertItems([archivedNote, trashedNote, archivedAndTrashedNote])
|
||||||
|
|
||||||
@@ -389,8 +377,8 @@ describe('itemManager', () => {
|
|||||||
await itemManager.insertItems([parentTag, childTag])
|
await itemManager.insertItems([parentTag, childTag])
|
||||||
await itemManager.setTagParent(parentTag, childTag)
|
await itemManager.setTagParent(parentTag, childTag)
|
||||||
|
|
||||||
const parentNote = createNote('parentNote')
|
const parentNote = createNoteWithTitle('parentNote')
|
||||||
const childNote = createNote('childNote')
|
const childNote = createNoteWithTitle('childNote')
|
||||||
await itemManager.insertItems([parentNote, childNote])
|
await itemManager.insertItems([parentNote, childNote])
|
||||||
|
|
||||||
await itemManager.addTagToNote(parentNote, parentTag, false)
|
await itemManager.addTagToNote(parentNote, parentTag, false)
|
||||||
@@ -477,7 +465,7 @@ describe('itemManager', () => {
|
|||||||
const fooDelimiter = await itemManager.createTag('bar.foo')
|
const fooDelimiter = await itemManager.createTag('bar.foo')
|
||||||
const barFooDelimiter = await itemManager.createTag('baz.bar.foo')
|
const barFooDelimiter = await itemManager.createTag('baz.bar.foo')
|
||||||
const fooAttached = await itemManager.createTag('Foo')
|
const fooAttached = await itemManager.createTag('Foo')
|
||||||
const note = createNote('note')
|
const note = createNoteWithTitle('note')
|
||||||
await itemManager.insertItems([foo, foobar, bar, barfoo, fooDelimiter, barFooDelimiter, fooAttached, note])
|
await itemManager.insertItems([foo, foobar, bar, barfoo, fooDelimiter, barFooDelimiter, fooAttached, note])
|
||||||
await itemManager.addTagToNote(note, fooAttached, false)
|
await itemManager.addTagToNote(note, fooAttached, false)
|
||||||
|
|
||||||
@@ -501,8 +489,8 @@ describe('itemManager', () => {
|
|||||||
await itemManager.insertItems([parentTag, childTag])
|
await itemManager.insertItems([parentTag, childTag])
|
||||||
await itemManager.setTagParent(parentTag, childTag)
|
await itemManager.setTagParent(parentTag, childTag)
|
||||||
|
|
||||||
const parentNote = createNote('parentNote')
|
const parentNote = createNoteWithTitle('parentNote')
|
||||||
const childNote = createNote('childNote')
|
const childNote = createNoteWithTitle('childNote')
|
||||||
await itemManager.insertItems([parentNote, childNote])
|
await itemManager.insertItems([parentNote, childNote])
|
||||||
|
|
||||||
await itemManager.addTagToNote(parentNote, parentTag, false)
|
await itemManager.addTagToNote(parentNote, parentTag, false)
|
||||||
@@ -519,8 +507,8 @@ describe('itemManager', () => {
|
|||||||
const tag1 = createTag('tag 1')
|
const tag1 = createTag('tag 1')
|
||||||
await itemManager.insertItems([tag1])
|
await itemManager.insertItems([tag1])
|
||||||
|
|
||||||
const note1 = createNote('note 1')
|
const note1 = createNoteWithTitle('note 1')
|
||||||
const note2 = createNote('note 2')
|
const note2 = createNoteWithTitle('note 2')
|
||||||
await itemManager.insertItems([note1, note2])
|
await itemManager.insertItems([note1, note2])
|
||||||
|
|
||||||
await itemManager.addTagToNote(note1, tag1, false)
|
await itemManager.addTagToNote(note1, tag1, false)
|
||||||
@@ -654,8 +642,8 @@ describe('itemManager', () => {
|
|||||||
const view = itemManager.untaggedNotesSmartView
|
const view = itemManager.untaggedNotesSmartView
|
||||||
|
|
||||||
const tag = createTag('tag')
|
const tag = createTag('tag')
|
||||||
const untaggedNote = createNote('note')
|
const untaggedNote = createNoteWithTitle('note')
|
||||||
const taggedNote = createNote('taggedNote')
|
const taggedNote = createNoteWithTitle('taggedNote')
|
||||||
await itemManager.insertItems([tag, untaggedNote, taggedNote])
|
await itemManager.insertItems([tag, untaggedNote, taggedNote])
|
||||||
|
|
||||||
expect(itemManager.notesMatchingSmartView(view)).toHaveLength(2)
|
expect(itemManager.notesMatchingSmartView(view)).toHaveLength(2)
|
||||||
@@ -704,7 +692,7 @@ describe('itemManager', () => {
|
|||||||
itemManager = createService()
|
itemManager = createService()
|
||||||
const parentTag = createTag('parent')
|
const parentTag = createTag('parent')
|
||||||
const childTag = createTag('child')
|
const childTag = createTag('child')
|
||||||
const note = createNote('note')
|
const note = createNoteWithTitle('note')
|
||||||
|
|
||||||
await itemManager.insertItems([parentTag, childTag, note])
|
await itemManager.insertItems([parentTag, childTag, note])
|
||||||
await itemManager.setTagParent(parentTag, childTag)
|
await itemManager.setTagParent(parentTag, childTag)
|
||||||
@@ -722,7 +710,7 @@ describe('itemManager', () => {
|
|||||||
itemManager = createService()
|
itemManager = createService()
|
||||||
const parentTag = createTag('parent')
|
const parentTag = createTag('parent')
|
||||||
const childTag = createTag('child')
|
const childTag = createTag('child')
|
||||||
const note = createNote('note')
|
const note = createNoteWithTitle('note')
|
||||||
|
|
||||||
await itemManager.insertItems([parentTag, childTag, note])
|
await itemManager.insertItems([parentTag, childTag, note])
|
||||||
await itemManager.setTagParent(parentTag, childTag)
|
await itemManager.setTagParent(parentTag, childTag)
|
||||||
@@ -772,7 +760,7 @@ describe('itemManager', () => {
|
|||||||
|
|
||||||
it('should link file with note', async () => {
|
it('should link file with note', async () => {
|
||||||
itemManager = createService()
|
itemManager = createService()
|
||||||
const note = createNote('invoices')
|
const note = createNoteWithTitle('invoices')
|
||||||
const file = createFile('invoice_1.pdf')
|
const file = createFile('invoice_1.pdf')
|
||||||
await itemManager.insertItems([note, file])
|
await itemManager.insertItems([note, file])
|
||||||
|
|
||||||
@@ -785,7 +773,7 @@ describe('itemManager', () => {
|
|||||||
|
|
||||||
it('should unlink file from note', async () => {
|
it('should unlink file from note', async () => {
|
||||||
itemManager = createService()
|
itemManager = createService()
|
||||||
const note = createNote('invoices')
|
const note = createNoteWithTitle('invoices')
|
||||||
const file = createFile('invoice_1.pdf')
|
const file = createFile('invoice_1.pdf')
|
||||||
await itemManager.insertItems([note, file])
|
await itemManager.insertItems([note, file])
|
||||||
|
|
||||||
@@ -798,7 +786,7 @@ describe('itemManager', () => {
|
|||||||
|
|
||||||
it('should get files linked with note', async () => {
|
it('should get files linked with note', async () => {
|
||||||
itemManager = createService()
|
itemManager = createService()
|
||||||
const note = createNote('invoices')
|
const note = createNoteWithTitle('invoices')
|
||||||
const file = createFile('invoice_1.pdf')
|
const file = createFile('invoice_1.pdf')
|
||||||
const secondFile = createFile('unrelated-file.xlsx')
|
const secondFile = createFile('unrelated-file.xlsx')
|
||||||
await itemManager.insertItems([note, file, secondFile])
|
await itemManager.insertItems([note, file, secondFile])
|
||||||
@@ -813,8 +801,8 @@ describe('itemManager', () => {
|
|||||||
|
|
||||||
it('should link note to note', async () => {
|
it('should link note to note', async () => {
|
||||||
itemManager = createService()
|
itemManager = createService()
|
||||||
const note = createNote('research')
|
const note = createNoteWithTitle('research')
|
||||||
const note2 = createNote('citation')
|
const note2 = createNoteWithTitle('citation')
|
||||||
await itemManager.insertItems([note, note2])
|
await itemManager.insertItems([note, note2])
|
||||||
|
|
||||||
const resultingNote = await itemManager.linkNoteToNote(note, note2)
|
const resultingNote = await itemManager.linkNoteToNote(note, note2)
|
||||||
@@ -839,9 +827,9 @@ describe('itemManager', () => {
|
|||||||
|
|
||||||
it('should get the relationship type for two items', async () => {
|
it('should get the relationship type for two items', async () => {
|
||||||
itemManager = createService()
|
itemManager = createService()
|
||||||
const firstNote = createNote('First note')
|
const firstNote = createNoteWithTitle('First note')
|
||||||
const secondNote = createNote('Second note')
|
const secondNote = createNoteWithTitle('Second note')
|
||||||
const unlinkedNote = createNote('Unlinked note')
|
const unlinkedNote = createNoteWithTitle('Unlinked note')
|
||||||
await itemManager.insertItems([firstNote, secondNote, unlinkedNote])
|
await itemManager.insertItems([firstNote, secondNote, unlinkedNote])
|
||||||
|
|
||||||
const firstNoteLinkedToSecond = await itemManager.linkNoteToNote(firstNote, secondNote)
|
const firstNoteLinkedToSecond = await itemManager.linkNoteToNote(firstNote, secondNote)
|
||||||
@@ -860,8 +848,8 @@ describe('itemManager', () => {
|
|||||||
|
|
||||||
it('should unlink itemToUnlink from item', async () => {
|
it('should unlink itemToUnlink from item', async () => {
|
||||||
itemManager = createService()
|
itemManager = createService()
|
||||||
const note = createNote('Note 1')
|
const note = createNoteWithTitle('Note 1')
|
||||||
const note2 = createNote('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)
|
||||||
@@ -909,9 +897,9 @@ describe('itemManager', () => {
|
|||||||
|
|
||||||
it('should get all linked notes for item', async () => {
|
it('should get all linked notes for item', async () => {
|
||||||
itemManager = createService()
|
itemManager = createService()
|
||||||
const baseNote = createNote('note')
|
const baseNote = createNoteWithTitle('note')
|
||||||
const noteToLink1 = createNote('A1')
|
const noteToLink1 = createNoteWithTitle('A1')
|
||||||
const noteToLink2 = createNote('B2')
|
const noteToLink2 = createNoteWithTitle('B2')
|
||||||
|
|
||||||
await itemManager.insertItems([baseNote, noteToLink1, noteToLink2])
|
await itemManager.insertItems([baseNote, noteToLink1, noteToLink2])
|
||||||
|
|
||||||
@@ -927,9 +915,9 @@ describe('itemManager', () => {
|
|||||||
|
|
||||||
it('should get all notes linking to item', async () => {
|
it('should get all notes linking to item', async () => {
|
||||||
itemManager = createService()
|
itemManager = createService()
|
||||||
const baseNote = createNote('note')
|
const baseNote = createNoteWithTitle('note')
|
||||||
const noteToLink1 = createNote('A1')
|
const noteToLink1 = createNoteWithTitle('A1')
|
||||||
const noteToLink2 = createNote('B2')
|
const noteToLink2 = createNoteWithTitle('B2')
|
||||||
|
|
||||||
await itemManager.insertItems([baseNote, noteToLink1, noteToLink2])
|
await itemManager.insertItems([baseNote, noteToLink1, noteToLink2])
|
||||||
|
|
||||||
|
|||||||
29
packages/snjs/lib/Spec/SpecUtils.ts
Normal file
29
packages/snjs/lib/Spec/SpecUtils.ts
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import { ContentType } from '@standardnotes/common'
|
||||||
|
import * as Models from '@standardnotes/models'
|
||||||
|
|
||||||
|
export const createNote = (payload?: Partial<Models.NoteContent>): Models.SNNote => {
|
||||||
|
return new Models.SNNote(
|
||||||
|
new Models.DecryptedPayload(
|
||||||
|
{
|
||||||
|
uuid: String(Math.random()),
|
||||||
|
content_type: ContentType.Note,
|
||||||
|
content: Models.FillItemContent({ ...payload }),
|
||||||
|
...Models.PayloadTimestampDefaults(),
|
||||||
|
},
|
||||||
|
Models.PayloadSource.Constructor,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const createNoteWithTitle = (title: string) => {
|
||||||
|
return new Models.SNNote(
|
||||||
|
new Models.DecryptedPayload({
|
||||||
|
uuid: String(Math.random()),
|
||||||
|
content_type: ContentType.Note,
|
||||||
|
content: Models.FillItemContent<Models.NoteContent>({
|
||||||
|
title: title,
|
||||||
|
}),
|
||||||
|
...Models.PayloadTimestampDefaults(),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -356,14 +356,13 @@ describe('app models', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('maintains editor reference when duplicating note', async function () {
|
it('maintains editor reference when duplicating note', async function () {
|
||||||
const note = await Factory.createMappedNote(this.application)
|
|
||||||
const editor = await this.application.itemManager.createItem(
|
const editor = await this.application.itemManager.createItem(
|
||||||
ContentType.Component,
|
ContentType.Component,
|
||||||
{ area: ComponentArea.Editor },
|
{ area: ComponentArea.Editor, package_info: { identifier: 'foo-editor' } },
|
||||||
true,
|
true,
|
||||||
)
|
)
|
||||||
await this.application.itemManager.changeComponent(editor, (mutator) => {
|
const note = await Factory.insertItemWithOverride(this.application, ContentType.Note, {
|
||||||
mutator.associateWithItem(note.uuid)
|
editorIdentifier: 'foo-editor',
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(this.application.componentManager.editorForNote(note).uuid).to.equal(editor.uuid)
|
expect(this.application.componentManager.editorForNote(note).uuid).to.equal(editor.uuid)
|
||||||
|
|||||||
@@ -114,59 +114,6 @@ describe('online conflict handling', function () {
|
|||||||
await this.sharedFinalAssertions()
|
await this.sharedFinalAssertions()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('duplicating note should maintain editor ref', async function () {
|
|
||||||
const note = await Factory.createSyncedNote(this.application)
|
|
||||||
this.expectedItemCount++
|
|
||||||
const basePayload = createDirtyPayload(ContentType.Component)
|
|
||||||
const payload = basePayload.copy({
|
|
||||||
content: {
|
|
||||||
...basePayload.content,
|
|
||||||
area: ComponentArea.Editor,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
const editor = await this.application.itemManager.emitItemFromPayload(payload, PayloadEmitSource.LocalChanged)
|
|
||||||
this.expectedItemCount++
|
|
||||||
await this.application.syncService.sync(syncOptions)
|
|
||||||
|
|
||||||
await this.application.mutator.changeAndSaveItem(
|
|
||||||
editor,
|
|
||||||
(mutator) => {
|
|
||||||
mutator.associateWithItem(note.uuid)
|
|
||||||
},
|
|
||||||
undefined,
|
|
||||||
undefined,
|
|
||||||
syncOptions,
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(this.application.componentManager.editorForNote(note)).to.be.ok
|
|
||||||
|
|
||||||
/** Conflict the note */
|
|
||||||
/** First modify the item without saving so that
|
|
||||||
* our local contents digress from the server's */
|
|
||||||
await this.application.mutator.changeItem(note, (mutator) => {
|
|
||||||
mutator.title = `${Math.random()}`
|
|
||||||
})
|
|
||||||
|
|
||||||
await Factory.changePayloadTimeStampAndSync(
|
|
||||||
this.application,
|
|
||||||
note.payload,
|
|
||||||
Factory.dateToMicroseconds(Factory.yesterday()),
|
|
||||||
{
|
|
||||||
title: 'zar',
|
|
||||||
},
|
|
||||||
syncOptions,
|
|
||||||
)
|
|
||||||
|
|
||||||
this.expectedItemCount++
|
|
||||||
|
|
||||||
const duplicate = this.application.itemManager.getDisplayableNotes().find((n) => {
|
|
||||||
return n.uuid !== note.uuid
|
|
||||||
})
|
|
||||||
expect(duplicate).to.be.ok
|
|
||||||
expect(this.application.componentManager.editorForNote(duplicate)).to.be.ok
|
|
||||||
await this.sharedFinalAssertions()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should create conflicted copy if incoming server item attempts to overwrite local dirty item', async function () {
|
it('should create conflicted copy if incoming server item attempts to overwrite local dirty item', async function () {
|
||||||
// create an item and sync it
|
// create an item and sync it
|
||||||
const note = await Factory.createMappedNote(this.application)
|
const note = await Factory.createMappedNote(this.application)
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
ComponentArea,
|
ComponentArea,
|
||||||
ItemMutator,
|
ItemMutator,
|
||||||
NoteMutator,
|
NoteMutator,
|
||||||
|
NoteType,
|
||||||
PrefKey,
|
PrefKey,
|
||||||
SNComponent,
|
SNComponent,
|
||||||
SNNote,
|
SNNote,
|
||||||
@@ -19,10 +20,6 @@ import { EditorMenuGroup } from '@/Components/NotesOptions/EditorMenuGroup'
|
|||||||
import { EditorMenuItem } from '@/Components/NotesOptions/EditorMenuItem'
|
import { EditorMenuItem } from '@/Components/NotesOptions/EditorMenuItem'
|
||||||
import { createEditorMenuGroups } from './createEditorMenuGroups'
|
import { createEditorMenuGroups } from './createEditorMenuGroups'
|
||||||
import { PLAIN_EDITOR_NAME } from '@/Constants/Constants'
|
import { PLAIN_EDITOR_NAME } from '@/Constants/Constants'
|
||||||
import {
|
|
||||||
transactionForAssociateComponentWithCurrentNote,
|
|
||||||
transactionForDisassociateComponentWithCurrentNote,
|
|
||||||
} from '../NoteView/TransactionFunctions'
|
|
||||||
import { reloadFont } from '../NoteView/FontFunctions'
|
import { reloadFont } from '../NoteView/FontFunctions'
|
||||||
import { PremiumFeatureIconClass, PremiumFeatureIconName } from '../Icon/PremiumFeatureIcon'
|
import { PremiumFeatureIconClass, PremiumFeatureIconName } from '../Icon/PremiumFeatureIcon'
|
||||||
|
|
||||||
@@ -97,42 +94,28 @@ const ChangeEditorMenu: FunctionComponent<ChangeEditorMenuProps> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!component) {
|
if (!component) {
|
||||||
if (!note.prefersPlainEditor) {
|
transactions.push({
|
||||||
transactions.push({
|
itemUuid: note.uuid,
|
||||||
itemUuid: note.uuid,
|
mutate: (m: ItemMutator) => {
|
||||||
mutate: (m: ItemMutator) => {
|
const noteMutator = m as NoteMutator
|
||||||
const noteMutator = m as NoteMutator
|
noteMutator.noteType = NoteType.Plain
|
||||||
noteMutator.prefersPlainEditor = true
|
noteMutator.editorIdentifier = undefined
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
|
||||||
const currentEditor = application.componentManager.editorForNote(note)
|
|
||||||
if (currentEditor?.isExplicitlyEnabledForItem(note.uuid)) {
|
|
||||||
transactions.push(transactionForDisassociateComponentWithCurrentNote(currentEditor, note))
|
|
||||||
}
|
|
||||||
reloadFont(application.getPreference(PrefKey.EditorMonospaceEnabled))
|
reloadFont(application.getPreference(PrefKey.EditorMonospaceEnabled))
|
||||||
} else if (component.area === ComponentArea.Editor) {
|
} else {
|
||||||
const currentEditor = application.componentManager.editorForNote(note)
|
transactions.push({
|
||||||
if (currentEditor && component.uuid !== currentEditor.uuid) {
|
itemUuid: note.uuid,
|
||||||
transactions.push(transactionForDisassociateComponentWithCurrentNote(currentEditor, note))
|
mutate: (m: ItemMutator) => {
|
||||||
}
|
const noteMutator = m as NoteMutator
|
||||||
const prefersPlain = note.prefersPlainEditor
|
noteMutator.noteType = component.noteType
|
||||||
if (prefersPlain) {
|
noteMutator.editorIdentifier = component.identifier
|
||||||
transactions.push({
|
},
|
||||||
itemUuid: note.uuid,
|
})
|
||||||
mutate: (m: ItemMutator) => {
|
|
||||||
const noteMutator = m as NoteMutator
|
|
||||||
noteMutator.prefersPlainEditor = false
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
transactions.push(transactionForAssociateComponentWithCurrentNote(component, note))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await application.mutator.runTransactionalMutations(transactions)
|
await application.mutator.runTransactionalMutations(transactions)
|
||||||
/** Dirtying can happen above */
|
|
||||||
application.sync.sync().catch(console.error)
|
application.sync.sync().catch(console.error)
|
||||||
|
|
||||||
setCurrentEditor(application.componentManager.editorForNote(note))
|
setCurrentEditor(application.componentManager.editorForNote(note))
|
||||||
},
|
},
|
||||||
[application],
|
[application],
|
||||||
|
|||||||
@@ -12,15 +12,13 @@ import { EditorMenuGroup } from '@/Components/NotesOptions/EditorMenuGroup'
|
|||||||
import { EditorMenuItem } from '@/Components/NotesOptions/EditorMenuItem'
|
import { EditorMenuItem } from '@/Components/NotesOptions/EditorMenuItem'
|
||||||
import { PLAIN_EDITOR_NAME } from '@/Constants/Constants'
|
import { PLAIN_EDITOR_NAME } from '@/Constants/Constants'
|
||||||
|
|
||||||
type EditorGroup = NoteType | 'plain' | 'others'
|
type EditorGroup = NoteType | 'others'
|
||||||
|
|
||||||
const getEditorGroup = (featureDescription: FeatureDescription): EditorGroup => {
|
const getEditorGroup = (featureDescription: FeatureDescription): EditorGroup => {
|
||||||
if (featureDescription.note_type) {
|
if (featureDescription.note_type) {
|
||||||
return featureDescription.note_type
|
return featureDescription.note_type
|
||||||
} else if (featureDescription.file_type) {
|
} else if (featureDescription.file_type) {
|
||||||
switch (featureDescription.file_type) {
|
switch (featureDescription.file_type) {
|
||||||
case 'txt':
|
|
||||||
return 'plain'
|
|
||||||
case 'html':
|
case 'html':
|
||||||
return NoteType.RichText
|
return NoteType.RichText
|
||||||
case 'md':
|
case 'md':
|
||||||
@@ -34,7 +32,7 @@ const getEditorGroup = (featureDescription: FeatureDescription): EditorGroup =>
|
|||||||
|
|
||||||
export const createEditorMenuGroups = (application: WebApplication, editors: SNComponent[]) => {
|
export const createEditorMenuGroups = (application: WebApplication, editors: SNComponent[]) => {
|
||||||
const editorItems: Record<EditorGroup, EditorMenuItem[]> = {
|
const editorItems: Record<EditorGroup, EditorMenuItem[]> = {
|
||||||
plain: [
|
'plain-text': [
|
||||||
{
|
{
|
||||||
name: PLAIN_EDITOR_NAME,
|
name: PLAIN_EDITOR_NAME,
|
||||||
isEntitled: true,
|
isEntitled: true,
|
||||||
@@ -79,7 +77,7 @@ export const createEditorMenuGroups = (application: WebApplication, editors: SNC
|
|||||||
icon: 'plain-text',
|
icon: 'plain-text',
|
||||||
iconClassName: 'text-accessory-tint-1',
|
iconClassName: 'text-accessory-tint-1',
|
||||||
title: 'Plain text',
|
title: 'Plain text',
|
||||||
items: editorItems.plain,
|
items: editorItems['plain-text'],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: 'rich-text',
|
icon: 'rich-text',
|
||||||
|
|||||||
@@ -10,15 +10,16 @@ import {
|
|||||||
ProposedSecondsToDeferUILevelSessionExpirationDuringActiveInteraction,
|
ProposedSecondsToDeferUILevelSessionExpirationDuringActiveInteraction,
|
||||||
NoteViewController,
|
NoteViewController,
|
||||||
SNNote,
|
SNNote,
|
||||||
|
NoteType,
|
||||||
|
PayloadEmitSource,
|
||||||
} from '@standardnotes/snjs'
|
} from '@standardnotes/snjs'
|
||||||
|
|
||||||
import NoteView from './NoteView'
|
import NoteView from './NoteView'
|
||||||
|
|
||||||
describe('NoteView', () => {
|
describe('NoteView', () => {
|
||||||
let noteViewController: NoteViewController
|
let noteViewController: NoteViewController
|
||||||
let application: WebApplication
|
let application: WebApplication
|
||||||
let viewControllerManager: ViewControllerManager
|
let viewControllerManager: ViewControllerManager
|
||||||
let notesState: NotesController
|
let notesController: NotesController
|
||||||
|
|
||||||
const createNoteView = () =>
|
const createNoteView = () =>
|
||||||
new NoteView({
|
new NoteView({
|
||||||
@@ -31,11 +32,12 @@ describe('NoteView', () => {
|
|||||||
|
|
||||||
noteViewController = {} as jest.Mocked<NoteViewController>
|
noteViewController = {} as jest.Mocked<NoteViewController>
|
||||||
|
|
||||||
notesState = {} as jest.Mocked<NotesController>
|
notesController = {} as jest.Mocked<NotesController>
|
||||||
notesState.setShowProtectedWarning = jest.fn()
|
notesController.setShowProtectedWarning = jest.fn()
|
||||||
|
notesController.getSpellcheckStateForNote = jest.fn()
|
||||||
|
|
||||||
viewControllerManager = {
|
viewControllerManager = {
|
||||||
notesController: notesState,
|
notesController: notesController,
|
||||||
} as jest.Mocked<ViewControllerManager>
|
} as jest.Mocked<ViewControllerManager>
|
||||||
|
|
||||||
application = {} as jest.Mocked<WebApplication>
|
application = {} as jest.Mocked<WebApplication>
|
||||||
@@ -59,7 +61,7 @@ describe('NoteView', () => {
|
|||||||
|
|
||||||
await createNoteView().onAppEvent(ApplicationEvent.UnprotectedSessionExpired)
|
await createNoteView().onAppEvent(ApplicationEvent.UnprotectedSessionExpired)
|
||||||
|
|
||||||
expect(notesState.setShowProtectedWarning).toHaveBeenCalledWith(true)
|
expect(notesController.setShowProtectedWarning).toHaveBeenCalledWith(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should postpone the note hiding by correct time if the time passed after its last modification is less than the allowed idle time', async () => {
|
it('should postpone the note hiding by correct time if the time passed after its last modification is less than the allowed idle time', async () => {
|
||||||
@@ -77,11 +79,11 @@ describe('NoteView', () => {
|
|||||||
|
|
||||||
jest.advanceTimersByTime((secondsAfterWhichTheNoteShouldHide - 1) * 1000)
|
jest.advanceTimersByTime((secondsAfterWhichTheNoteShouldHide - 1) * 1000)
|
||||||
|
|
||||||
expect(notesState.setShowProtectedWarning).not.toHaveBeenCalled()
|
expect(notesController.setShowProtectedWarning).not.toHaveBeenCalled()
|
||||||
|
|
||||||
jest.advanceTimersByTime(1 * 1000)
|
jest.advanceTimersByTime(1 * 1000)
|
||||||
|
|
||||||
expect(notesState.setShowProtectedWarning).toHaveBeenCalledWith(true)
|
expect(notesController.setShowProtectedWarning).toHaveBeenCalledWith(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should postpone the note hiding by correct time if the user continued editing it even after the protection session has expired', async () => {
|
it('should postpone the note hiding by correct time if the user continued editing it even after the protection session has expired', async () => {
|
||||||
@@ -105,10 +107,10 @@ describe('NoteView', () => {
|
|||||||
|
|
||||||
secondsAfterWhichTheNoteShouldHide = ProposedSecondsToDeferUILevelSessionExpirationDuringActiveInteraction
|
secondsAfterWhichTheNoteShouldHide = ProposedSecondsToDeferUILevelSessionExpirationDuringActiveInteraction
|
||||||
jest.advanceTimersByTime((secondsAfterWhichTheNoteShouldHide - 1) * 1000)
|
jest.advanceTimersByTime((secondsAfterWhichTheNoteShouldHide - 1) * 1000)
|
||||||
expect(notesState.setShowProtectedWarning).not.toHaveBeenCalled()
|
expect(notesController.setShowProtectedWarning).not.toHaveBeenCalled()
|
||||||
|
|
||||||
jest.advanceTimersByTime(1 * 1000)
|
jest.advanceTimersByTime(1 * 1000)
|
||||||
expect(notesState.setShowProtectedWarning).toHaveBeenCalledWith(true)
|
expect(notesController.setShowProtectedWarning).toHaveBeenCalledWith(true)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -120,7 +122,43 @@ describe('NoteView', () => {
|
|||||||
|
|
||||||
await createNoteView().onAppEvent(ApplicationEvent.UnprotectedSessionExpired)
|
await createNoteView().onAppEvent(ApplicationEvent.UnprotectedSessionExpired)
|
||||||
|
|
||||||
expect(notesState.setShowProtectedWarning).not.toHaveBeenCalled()
|
expect(notesController.setShowProtectedWarning).not.toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('editors', () => {
|
||||||
|
it('should reload editor if noteType changes', async () => {
|
||||||
|
noteViewController.item = {
|
||||||
|
noteType: NoteType.Code,
|
||||||
|
} as jest.Mocked<SNNote>
|
||||||
|
|
||||||
|
const view = createNoteView()
|
||||||
|
view.reloadEditorComponent = jest.fn()
|
||||||
|
view.setState = jest.fn()
|
||||||
|
|
||||||
|
const changedItem = {
|
||||||
|
noteType: NoteType.Plain,
|
||||||
|
} as jest.Mocked<SNNote>
|
||||||
|
view.onNoteInnerChange(changedItem, PayloadEmitSource.LocalChanged)
|
||||||
|
|
||||||
|
expect(view.reloadEditorComponent).toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should reload editor if editorIdentifier changes', async () => {
|
||||||
|
noteViewController.item = {
|
||||||
|
editorIdentifier: 'foo',
|
||||||
|
} as jest.Mocked<SNNote>
|
||||||
|
|
||||||
|
const view = createNoteView()
|
||||||
|
view.reloadEditorComponent = jest.fn()
|
||||||
|
view.setState = jest.fn()
|
||||||
|
|
||||||
|
const changedItem = {
|
||||||
|
editorIdentifier: 'bar',
|
||||||
|
} as jest.Mocked<SNNote>
|
||||||
|
view.onNoteInnerChange(changedItem, PayloadEmitSource.LocalChanged)
|
||||||
|
|
||||||
|
expect(view.reloadEditorComponent).toHaveBeenCalled()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -142,7 +180,7 @@ describe('NoteView', () => {
|
|||||||
|
|
||||||
await noteView.dismissProtectedWarning()
|
await noteView.dismissProtectedWarning()
|
||||||
|
|
||||||
expect(notesState.setShowProtectedWarning).toHaveBeenCalledWith(false)
|
expect(notesController.setShowProtectedWarning).toHaveBeenCalledWith(false)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should not reveal note contents if the authorization has not been passed', async () => {
|
it('should not reveal note contents if the authorization has not been passed', async () => {
|
||||||
@@ -155,7 +193,7 @@ describe('NoteView', () => {
|
|||||||
|
|
||||||
await noteView.dismissProtectedWarning()
|
await noteView.dismissProtectedWarning()
|
||||||
|
|
||||||
expect(notesState.setShowProtectedWarning).not.toHaveBeenCalled()
|
expect(notesController.setShowProtectedWarning).not.toHaveBeenCalled()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -170,7 +208,7 @@ describe('NoteView', () => {
|
|||||||
|
|
||||||
await noteView.dismissProtectedWarning()
|
await noteView.dismissProtectedWarning()
|
||||||
|
|
||||||
expect(notesState.setShowProtectedWarning).toHaveBeenCalledWith(false)
|
expect(notesController.setShowProtectedWarning).toHaveBeenCalledWith(false)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import {
|
|||||||
Platform,
|
Platform,
|
||||||
EditorLineHeight,
|
EditorLineHeight,
|
||||||
EditorFontSize,
|
EditorFontSize,
|
||||||
|
NoteType,
|
||||||
} from '@standardnotes/snjs'
|
} from '@standardnotes/snjs'
|
||||||
import { debounce, isDesktopApplication, isIOS } from '@/Utils'
|
import { debounce, isDesktopApplication, isIOS } from '@/Utils'
|
||||||
import { EditorEventSource } from '../../Types/EditorEventSource'
|
import { EditorEventSource } from '../../Types/EditorEventSource'
|
||||||
@@ -88,6 +89,9 @@ type State = {
|
|||||||
lineHeight?: EditorLineHeight
|
lineHeight?: EditorLineHeight
|
||||||
fontSize?: EditorFontSize
|
fontSize?: EditorFontSize
|
||||||
updateSavingIndicator?: boolean
|
updateSavingIndicator?: boolean
|
||||||
|
|
||||||
|
editorFeatureIdentifier?: string
|
||||||
|
noteType?: NoteType
|
||||||
}
|
}
|
||||||
|
|
||||||
const PlaintextFontSizeMapping: Record<EditorFontSize, string> = {
|
const PlaintextFontSizeMapping: Record<EditorFontSize, string> = {
|
||||||
@@ -151,6 +155,8 @@ class NoteView extends PureComponent<NoteViewProps, State> {
|
|||||||
rightResizerWidth: 0,
|
rightResizerWidth: 0,
|
||||||
rightResizerOffset: 0,
|
rightResizerOffset: 0,
|
||||||
shouldStickyHeader: false,
|
shouldStickyHeader: false,
|
||||||
|
editorFeatureIdentifier: this.controller.item.editorIdentifier,
|
||||||
|
noteType: this.controller.item.noteType,
|
||||||
}
|
}
|
||||||
|
|
||||||
this.editorContentRef = createRef<HTMLDivElement>()
|
this.editorContentRef = createRef<HTMLDivElement>()
|
||||||
@@ -249,7 +255,7 @@ class NoteView extends PureComponent<NoteViewProps, State> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private onNoteInnerChange(note: SNNote, source: PayloadEmitSource): void {
|
onNoteInnerChange(note: SNNote, source: PayloadEmitSource): void {
|
||||||
if (note.uuid !== this.note.uuid) {
|
if (note.uuid !== this.note.uuid) {
|
||||||
throw Error('Editor received changes for non-current note')
|
throw Error('Editor received changes for non-current note')
|
||||||
}
|
}
|
||||||
@@ -288,6 +294,15 @@ class NoteView extends PureComponent<NoteViewProps, State> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (note.editorIdentifier !== this.state.editorFeatureIdentifier || note.noteType !== this.state.noteType) {
|
||||||
|
this.setState({
|
||||||
|
editorFeatureIdentifier: note.editorIdentifier,
|
||||||
|
noteType: note.noteType,
|
||||||
|
})
|
||||||
|
|
||||||
|
void this.reloadEditorComponent()
|
||||||
|
}
|
||||||
|
|
||||||
this.reloadSpellcheck().catch(console.error)
|
this.reloadSpellcheck().catch(console.error)
|
||||||
|
|
||||||
const isTemplateNoteInsertedToBeInteractableWithEditor = source === PayloadEmitSource.LocalInserted && note.dirty
|
const isTemplateNoteInsertedToBeInteractableWithEditor = source === PayloadEmitSource.LocalInserted && note.dirty
|
||||||
@@ -476,18 +491,19 @@ class NoteView extends PureComponent<NoteViewProps, State> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async reloadEditorComponent() {
|
async reloadEditorComponent() {
|
||||||
if (this.state.showProtectedWarning) {
|
if (this.state.showProtectedWarning) {
|
||||||
this.destroyCurrentEditorComponent()
|
this.destroyCurrentEditorComponent()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const newEditor = this.application.componentManager.editorForNote(this.note)
|
const newEditor = this.application.componentManager.editorForNote(this.note)
|
||||||
|
|
||||||
/** Editors cannot interact with template notes so the note must be inserted */
|
/** Editors cannot interact with template notes so the note must be inserted */
|
||||||
if (newEditor && this.controller.isTemplateNote) {
|
if (newEditor && this.controller.isTemplateNote) {
|
||||||
await this.controller.insertTemplatedNote()
|
await this.controller.insertTemplatedNote()
|
||||||
this.associateComponentWithCurrentNote(newEditor).catch(console.error)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentComponentViewer = this.state.editorComponentViewer
|
const currentComponentViewer = this.state.editorComponentViewer
|
||||||
|
|
||||||
if (currentComponentViewer?.componentUuid !== newEditor?.uuid) {
|
if (currentComponentViewer?.componentUuid !== newEditor?.uuid) {
|
||||||
@@ -800,25 +816,17 @@ class NoteView extends PureComponent<NoteViewProps, State> {
|
|||||||
|
|
||||||
toggleStackComponent = async (component: SNComponent) => {
|
toggleStackComponent = async (component: SNComponent) => {
|
||||||
if (!component.isExplicitlyEnabledForItem(this.note.uuid)) {
|
if (!component.isExplicitlyEnabledForItem(this.note.uuid)) {
|
||||||
await this.associateComponentWithCurrentNote(component)
|
await this.application.mutator.runTransactionalMutation(
|
||||||
|
transactionForAssociateComponentWithCurrentNote(component, this.note),
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
await this.disassociateComponentWithCurrentNote(component)
|
await this.application.mutator.runTransactionalMutation(
|
||||||
|
transactionForDisassociateComponentWithCurrentNote(component, this.note),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
this.application.sync.sync().catch(console.error)
|
this.application.sync.sync().catch(console.error)
|
||||||
}
|
}
|
||||||
|
|
||||||
async disassociateComponentWithCurrentNote(component: SNComponent) {
|
|
||||||
return this.application.mutator.runTransactionalMutation(
|
|
||||||
transactionForDisassociateComponentWithCurrentNote(component, this.note),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
async associateComponentWithCurrentNote(component: SNComponent) {
|
|
||||||
return this.application.mutator.runTransactionalMutation(
|
|
||||||
transactionForAssociateComponentWithCurrentNote(component, this.note),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
registerKeyboardShortcuts() {
|
registerKeyboardShortcuts() {
|
||||||
this.removeTrashKeyObserver = this.application.io.addKeyObserver({
|
this.removeTrashKeyObserver = this.application.io.addKeyObserver({
|
||||||
key: KeyboardKey.Backspace,
|
key: KeyboardKey.Backspace,
|
||||||
|
|||||||
@@ -1,11 +1,3 @@
|
|||||||
const {
|
const snjs = require('@standardnotes/snjs')
|
||||||
ApplicationEvent,
|
|
||||||
ProtectionSessionDurations,
|
|
||||||
ProposedSecondsToDeferUILevelSessionExpirationDuringActiveInteraction,
|
|
||||||
} = require('@standardnotes/snjs')
|
|
||||||
|
|
||||||
module.exports = {
|
module.exports = snjs
|
||||||
ApplicationEvent: ApplicationEvent,
|
|
||||||
ProtectionSessionDurations: ProtectionSessionDurations,
|
|
||||||
ProposedSecondsToDeferUILevelSessionExpirationDuringActiveInteraction,
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -5925,7 +5925,7 @@ __metadata:
|
|||||||
css-loader: ^6.7.1
|
css-loader: ^6.7.1
|
||||||
eslint: ^8.17.0
|
eslint: ^8.17.0
|
||||||
eslint-plugin-prettier: ^4.2.1
|
eslint-plugin-prettier: ^4.2.1
|
||||||
husky: ^8.0.0
|
husky: ^8.0.1
|
||||||
lint-staged: ^13.0.1
|
lint-staged: ^13.0.1
|
||||||
npm-check-updates: ^14.1.1
|
npm-check-updates: ^14.1.1
|
||||||
prettier: ^2.6.2
|
prettier: ^2.6.2
|
||||||
@@ -20622,7 +20622,7 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"husky@npm:^8.0.0":
|
"husky@npm:^8.0.1":
|
||||||
version: 8.0.1
|
version: 8.0.1
|
||||||
resolution: "husky@npm:8.0.1"
|
resolution: "husky@npm:8.0.1"
|
||||||
bin:
|
bin:
|
||||||
|
|||||||
Reference in New Issue
Block a user