refactor: new snjs support (#967)

This commit is contained in:
Mo
2022-04-11 12:48:19 -05:00
committed by GitHub
parent 3126d97dca
commit 3a2ff2f440
44 changed files with 569 additions and 799 deletions

View File

@@ -6,7 +6,12 @@ import {
observable,
runInAction,
} from 'mobx';
import { ApplicationEvent, ContentType, SNItem } from '@standardnotes/snjs';
import {
ApplicationEvent,
ContentType,
SNNote,
SNTag,
} from '@standardnotes/snjs';
import { WebApplication } from '@/ui_models/application';
import { AccountMenuPane } from '@/components/AccountMenu';
@@ -23,7 +28,7 @@ export class AccountMenuState {
otherSessionsSignOut = false;
server: string | undefined = undefined;
enableServerOption = false;
notesAndTags: SNItem[] = [];
notesAndTags: (SNNote | SNTag)[] = [];
isEncryptionEnabled = false;
encryptionStatusString = '';
isBackupEncrypted = false;

View File

@@ -8,13 +8,14 @@ import {
ContentType,
DeinitSource,
NoteViewController,
PayloadSource,
PrefKey,
SNNote,
SmartView,
SNTag,
SystemViewId,
removeFromArray,
PayloadSource,
Uuid,
} from '@standardnotes/snjs';
import {
action,
@@ -276,9 +277,9 @@ export class AppState {
this.application.noteControllerGroup.closeAllNoteViews();
}
noteControllerForNote(note: SNNote) {
noteControllerForNote(uuid: Uuid) {
for (const controller of this.getNoteControllers()) {
if (controller.note.uuid === note.uuid) {
if (controller.note.uuid === uuid) {
return controller;
}
}
@@ -328,43 +329,61 @@ export class AppState {
}
streamNotesAndTags() {
this.application.streamItems(
this.application.streamItems<SNNote | SNTag>(
[ContentType.Note, ContentType.Tag],
async (items, source) => {
async ({ changed, inserted, removed, source }) => {
if (
![PayloadSource.PreSyncSave, PayloadSource.RemoteRetrieved].includes(
source
)
) {
return;
}
const removedNotes = removed.filter(
(i) => i.content_type === ContentType.Note
);
for (const removedNote of removedNotes) {
const noteController = this.noteControllerForNote(removedNote.uuid);
if (noteController) {
this.closeNoteController(noteController);
}
}
const changedOrInserted = [...changed, ...inserted].filter(
(i) => i.content_type === ContentType.Note
);
const selectedTag = this.tags.selected;
/** Close any note controllers for deleted/trashed/archived notes */
if (source === PayloadSource.PreSyncSave) {
const notes = items.filter(
(candidate) => candidate.content_type === ContentType.Note
) as SNNote[];
for (const note of notes) {
const noteController = this.noteControllerForNote(note);
if (!noteController) {
continue;
}
if (note.deleted) {
this.closeNoteController(noteController);
} else if (
note.trashed &&
!(
selectedTag instanceof SmartView &&
selectedTag.uuid === SystemViewId.TrashedNotes
) &&
!this.searchOptions.includeTrashed
) {
this.closeNoteController(noteController);
} else if (
note.archived &&
!(
selectedTag instanceof SmartView &&
selectedTag.uuid === SystemViewId.ArchivedNotes
) &&
!this.searchOptions.includeArchived &&
!this.application.getPreference(PrefKey.NotesShowArchived, false)
) {
this.closeNoteController(noteController);
}
for (const note of changedOrInserted) {
const noteController = this.noteControllerForNote(note.uuid);
if (!noteController) {
continue;
}
const isBrowswingTrashedNotes =
selectedTag instanceof SmartView &&
selectedTag.uuid === SystemViewId.TrashedNotes;
const isBrowsingArchivedNotes =
selectedTag instanceof SmartView &&
selectedTag.uuid === SystemViewId.ArchivedNotes;
if (
note.trashed &&
!isBrowswingTrashedNotes &&
!this.searchOptions.includeTrashed
) {
this.closeNoteController(noteController);
} else if (
note.archived &&
!isBrowsingArchivedNotes &&
!this.searchOptions.includeArchived &&
!this.application.getPreference(PrefKey.NotesShowArchived, false)
) {
this.closeNoteController(noteController);
}
}
}
@@ -436,11 +455,9 @@ export class AppState {
/** Returns the tags that are referncing this note */
public getNoteTags(note: SNNote) {
return this.application.items
.itemsReferencingItem(note.uuid)
.filter((ref) => {
return ref.content_type === ContentType.Tag;
}) as SNTag[];
return this.application.items.itemsReferencingItem(note).filter((ref) => {
return ref.content_type === ContentType.Tag;
}) as SNTag[];
}
panelDidResize(name: string, collapsed: boolean) {

View File

@@ -215,7 +215,7 @@ export class NoteTagsState {
async removeTagFromActiveNote(tag: SNTag): Promise<void> {
const { activeNote } = this;
if (activeNote) {
await this.application.mutator.changeItem(tag.uuid, (mutator) => {
await this.application.mutator.changeItem(tag, (mutator) => {
mutator.removeItemAsRelationship(activeNote);
});
this.application.sync.sync();

View File

@@ -60,15 +60,22 @@ export class NotesState {
});
appEventListeners.push(
application.streamItems(ContentType.Note, (notes) => {
runInAction(() => {
for (const note of notes) {
if (this.selectedNotes[note.uuid]) {
this.selectedNotes[note.uuid] = note as SNNote;
application.streamItems<SNNote>(
ContentType.Note,
({ changed, inserted, removed }) => {
runInAction(() => {
for (const removedNote of removed) {
delete this.selectedNotes[removedNote.uuid];
}
}
});
})
for (const note of [...changed, ...inserted]) {
if (this.selectedNotes[note.uuid]) {
this.selectedNotes[note.uuid] = note;
}
}
});
}
)
);
}
@@ -85,9 +92,8 @@ export class NotesState {
}
private async selectNotesRange(selectedNote: SNNote): Promise<void> {
const notes = this.application.items.getDisplayableItems(
ContentType.Note
) as SNNote[];
const notes = this.application.items.getDisplayableNotes();
const lastSelectedNoteIndex = notes.findIndex(
(note) => note.uuid == this.lastSelectedNote?.uuid
);
@@ -179,10 +185,6 @@ export class NotesState {
this.appState.noteTags.reloadTags();
await this.onActiveEditorChanged();
if (note.waitingForKey) {
this.application.presentKeyRecoveryWizard();
}
}
setContextMenuOpen(open: boolean): void {
@@ -263,7 +265,7 @@ export class NotesState {
mutate: (mutator: NoteMutator) => void
): Promise<void> {
await this.application.mutator.changeItems(
Object.keys(this.selectedNotes),
Object.values(this.selectedNotes),
mutate,
false
);
@@ -399,7 +401,7 @@ export class NotesState {
async toggleGlobalSpellcheckForNote(note: SNNote) {
await this.application.mutator.changeItem<NoteMutator>(
note.uuid,
note,
(mutator) => {
mutator.toggleSpellcheck();
},
@@ -410,11 +412,11 @@ export class NotesState {
async addTagToSelectedNotes(tag: SNTag): Promise<void> {
const selectedNotes = Object.values(this.selectedNotes);
const parentChainTags = this.application.items.getTagParentChain(tag.uuid);
const parentChainTags = this.application.items.getTagParentChain(tag);
const tagsToAdd = [...parentChainTags, tag];
await Promise.all(
tagsToAdd.map(async (tag) => {
await this.application.mutator.changeItem(tag.uuid, (mutator) => {
await this.application.mutator.changeItem(tag, (mutator) => {
for (const note of selectedNotes) {
mutator.addItemAsRelationship(note);
}
@@ -426,7 +428,7 @@ export class NotesState {
async removeTagFromSelectedNotes(tag: SNTag): Promise<void> {
const selectedNotes = Object.values(this.selectedNotes);
await this.application.mutator.changeItem(tag.uuid, (mutator) => {
await this.application.mutator.changeItem(tag, (mutator) => {
for (const note of selectedNotes) {
mutator.removeItemAsRelationship(note);
}

View File

@@ -1,6 +1,7 @@
import {
ApplicationEvent,
CollectionSort,
CollectionSortProperty,
ContentType,
findInArray,
NotesDisplayCriteria,
@@ -28,7 +29,7 @@ const ELEMENT_ID_SEARCH_BAR = 'search-bar';
const ELEMENT_ID_SCROLL_CONTAINER = 'notes-scrollable';
export type DisplayOptions = {
sortBy: CollectionSort;
sortBy: CollectionSortProperty;
sortReverse: boolean;
hidePinned: boolean;
showArchived: boolean;
@@ -73,18 +74,20 @@ export class NotesViewState {
this.resetPagination();
appObservers.push(
application.streamItems(ContentType.Note, () => {
application.streamItems<SNNote>(ContentType.Note, () => {
this.reloadNotes();
const activeNote = this.appState.notes.activeNoteController?.note;
if (this.application.getAppState().notes.selectedNotesCount < 2) {
if (activeNote) {
const discarded = activeNote.deleted || activeNote.trashed;
const browsingTrashedNotes =
this.appState.selectedTag instanceof SmartView &&
this.appState.selectedTag?.uuid === SystemViewId.TrashedNotes;
if (
discarded &&
!(
this.appState.selectedTag instanceof SmartView &&
this.appState.selectedTag?.uuid === SystemViewId.TrashedNotes
) &&
activeNote.trashed &&
!browsingTrashedNotes &&
!this.appState?.searchOptions.includeTrashed
) {
this.selectNextOrCreateNew();
@@ -96,19 +99,24 @@ export class NotesViewState {
}
}
}),
application.streamItems([ContentType.Tag], async (items) => {
const tags = items as SNTag[];
/** A tag could have changed its relationships, so we need to reload the filter */
this.reloadNotesDisplayOptions();
this.reloadNotes();
if (
this.appState.selectedTag &&
findInArray(tags, 'uuid', this.appState.selectedTag.uuid)
) {
/** Tag title could have changed */
this.reloadPanelTitle();
application.streamItems<SNTag>(
[ContentType.Tag],
async ({ changed, inserted }) => {
const tags = [...changed, ...inserted];
/** A tag could have changed its relationships, so we need to reload the filter */
this.reloadNotesDisplayOptions();
this.reloadNotes();
if (
this.appState.selectedTag &&
findInArray(tags, 'uuid', this.appState.selectedTag.uuid)
) {
/** Tag title could have changed */
this.reloadPanelTitle();
}
}
}),
),
application.addEventObserver(async () => {
this.reloadPreferences();
}, ApplicationEvent.PreferencesChanged),
@@ -223,9 +231,7 @@ export class NotesViewState {
if (!tag) {
return;
}
const notes = this.application.items.getDisplayableItems(
ContentType.Note
) as SNNote[];
const notes = this.application.items.getDisplayableNotes();
const renderedNotes = notes.slice(0, this.notesToDisplay);
this.notes = notes;
@@ -250,7 +256,7 @@ export class NotesViewState {
}
const criteria = NotesDisplayCriteria.Create({
sortProperty: this.displayOptions.sortBy as CollectionSort,
sortProperty: this.displayOptions.sortBy,
sortDirection: this.displayOptions.sortReverse ? 'asc' : 'dsc',
tags: tag instanceof SNTag ? [tag] : [],
views: tag instanceof SmartView ? [tag] : [],
@@ -498,7 +504,7 @@ export class NotesViewState {
handleEditorChange = async () => {
const activeNote = this.appState.getActiveNoteController()?.note;
if (activeNote && activeNote.conflictOf) {
this.application.mutator.changeAndSaveItem(activeNote.uuid, (mutator) => {
this.application.mutator.changeAndSaveItem(activeNote, (mutator) => {
mutator.conflictOf = undefined;
});
}

View File

@@ -14,6 +14,7 @@ import {
TagMutator,
UuidString,
isSystemView,
FindItem,
} from '@standardnotes/snjs';
import {
action,
@@ -29,11 +30,9 @@ import { FeaturesState, SMART_TAGS_FEATURE_NAME } from './features_state';
type AnyTag = SNTag | SmartView;
const rootTags = (application: SNApplication): SNTag[] => {
const hasNoParent = (tag: SNTag) => !application.items.getTagParent(tag.uuid);
const hasNoParent = (tag: SNTag) => !application.items.getTagParent(tag);
const allTags = application.items.getDisplayableItems(
ContentType.Tag
) as SNTag[];
const allTags = application.items.getDisplayableItems<SNTag>(ContentType.Tag);
const rootTags = allTags.filter(hasNoParent);
return rootTags;
@@ -44,10 +43,10 @@ const tagSiblings = (application: SNApplication, tag: SNTag): SNTag[] => {
tags.filter((other) => other.uuid !== tag.uuid);
const isTemplateTag = application.items.isTemplateItem(tag);
const parentTag = !isTemplateTag && application.items.getTagParent(tag.uuid);
const parentTag = !isTemplateTag && application.items.getTagParent(tag);
if (parentTag) {
const siblingsAndTag = application.items.getTagChildren(parentTag.uuid);
const siblingsAndTag = application.items.getTagChildren(parentTag);
return withoutCurrentTag(siblingsAndTag);
}
@@ -148,24 +147,24 @@ export class TagsState {
appEventListeners.push(
this.application.streamItems(
[ContentType.Tag, ContentType.SmartView],
(items) => {
({ changed, removed }) => {
runInAction(() => {
this.tags = this.application.items.getDisplayableItems<SNTag>(
ContentType.Tag
);
this.smartViews = this.application.items.getSmartViews();
const selectedTag = this.selected_;
if (selectedTag && !isSystemView(selectedTag as SmartView)) {
const matchingTag = items.find(
(candidate) => candidate.uuid === selectedTag.uuid
) as AnyTag;
if (matchingTag) {
if (matchingTag.deleted) {
this.selected_ = this.smartViews[0];
} else {
this.selected_ = matchingTag;
}
if (FindItem(removed, selectedTag.uuid)) {
this.selected_ = this.smartViews[0];
}
const updated = FindItem(changed, selectedTag.uuid);
if (updated) {
this.selected_ = updated as AnyTag;
}
} else {
this.selected_ = this.smartViews[0];
@@ -202,7 +201,7 @@ export class TagsState {
title
)) as SNTag;
const futureSiblings = this.application.items.getTagChildren(parent.uuid);
const futureSiblings = this.application.items.getTagChildren(parent);
if (!isValidFutureSiblings(this.application, futureSiblings, createdTag)) {
this.setAddingSubtagTo(undefined);
@@ -319,7 +318,7 @@ export class TagsState {
return [];
}
const children = this.application.items.getTagChildren(tag.uuid);
const children = this.application.items.getTagChildren(tag);
const childrenUuids = children.map((childTag) => childTag.uuid);
const childrenTags = this.tags.filter((tag) =>
@@ -328,8 +327,8 @@ export class TagsState {
return childrenTags;
}
isValidTagParent(parentUuid: UuidString, tagUuid: UuidString): boolean {
return this.application.items.isValidTagParent(parentUuid, tagUuid);
isValidTagParent(parent: SNTag, tag: SNTag): boolean {
return this.application.items.isValidTagParent(parent, tag);
}
public hasParent(tagUuid: UuidString): boolean {
@@ -343,7 +342,7 @@ export class TagsState {
): Promise<void> {
const tag = this.application.items.findItem(tagUuid) as SNTag;
const currentParent = this.application.items.getTagParent(tag.uuid);
const currentParent = this.application.items.getTagParent(tag);
const currentParentUuid = currentParent?.uuid;
if (currentParentUuid === futureParentUuid) {
@@ -361,9 +360,8 @@ export class TagsState {
}
await this.application.mutator.unsetTagParent(tag);
} else {
const futureSiblings = this.application.items.getTagChildren(
futureParent.uuid
);
const futureSiblings =
this.application.items.getTagChildren(futureParent);
if (!isValidFutureSiblings(this.application, futureSiblings, tag)) {
return;
}
@@ -374,9 +372,7 @@ export class TagsState {
}
get rootTags(): SNTag[] {
return this.tags.filter(
(tag) => !this.application.items.getTagParent(tag.uuid)
);
return this.tags.filter((tag) => !this.application.items.getTagParent(tag));
}
get tagsCount(): number {
@@ -401,7 +397,7 @@ export class TagsState {
public set selected(tag: AnyTag | undefined) {
if (tag && tag.conflictOf) {
this.application.mutator.changeAndSaveItem(tag.uuid, (mutator) => {
this.application.mutator.changeAndSaveItem(tag, (mutator) => {
mutator.conflictOf = undefined;
});
}
@@ -417,12 +413,9 @@ export class TagsState {
}
public setExpanded(tag: SNTag, expanded: boolean) {
this.application.mutator.changeAndSaveItem<TagMutator>(
tag.uuid,
(mutator) => {
mutator.expanded = expanded;
}
);
this.application.mutator.changeAndSaveItem<TagMutator>(tag, (mutator) => {
mutator.expanded = expanded;
});
}
public get selectedUuid(): UuidString | undefined {
@@ -527,7 +520,7 @@ export class TagsState {
});
} else {
await this.application.mutator.changeAndSaveItem<TagMutator>(
tag.uuid,
tag,
(mutator) => {
mutator.title = newTitle;
}
@@ -563,9 +556,7 @@ export class TagsState {
}
public get hasAtLeastOneFolder(): boolean {
return this.tags.some(
(tag) => !!this.application.items.getTagParent(tag.uuid)
);
return this.tags.some((tag) => !!this.application.items.getTagParent(tag));
}
}

View File

@@ -75,15 +75,16 @@ export class WebApplication extends SNApplication {
if (source === DeinitSource.AppGroupUnload) {
this.getThemeService().deactivateAllThemes();
}
for (const service of Object.values(this.webServices)) {
if ('deinit' in service) {
service.deinit?.(source);
}
(service as any).application = undefined;
}
this.webServices = {} as WebServices;
this.noteControllerGroup.deinit();
this.iconsController.deinit();
this.webEventObservers.length = 0;
if (source === DeinitSource.SignOut) {

View File

@@ -6,6 +6,7 @@ import {
DeviceInterface,
Platform,
Runtime,
InternalEventBus,
} from '@standardnotes/snjs';
import { AppState } from '@/ui_models/app_state';
import { Bridge } from '@/services/bridge';
@@ -16,7 +17,6 @@ import { IOService } from '@/services/ioService';
import { AutolockService } from '@/services/autolock_service';
import { StatusManager } from '@/services/statusManager';
import { ThemeManager } from '@/services/themeManager';
import { InternalEventBus } from '@standardnotes/services';
export class ApplicationGroup extends SNApplicationGroup {
constructor(