Merge branch 'develop' into feature/remove-batch-manager
This commit is contained in:
@@ -16,6 +16,7 @@ import { Bridge } from '@/services/bridge';
|
||||
import { storage, StorageKey } from '@/services/localStorage';
|
||||
import { AccountMenuState } from './account_menu_state';
|
||||
import { ActionsMenuState } from './actions_menu_state';
|
||||
import { NoteTagsState } from './note_tags_state';
|
||||
import { NoAccountWarningState } from './no_account_warning_state';
|
||||
import { SyncState } from './sync_state';
|
||||
import { SearchOptionsState } from './search_options_state';
|
||||
@@ -63,6 +64,7 @@ export class AppState {
|
||||
readonly accountMenu = new AccountMenuState();
|
||||
readonly actionsMenu = new ActionsMenuState();
|
||||
readonly noAccountWarning: NoAccountWarningState;
|
||||
readonly noteTags: NoteTagsState;
|
||||
readonly sync = new SyncState();
|
||||
readonly searchOptions: SearchOptionsState;
|
||||
readonly notes: NotesState;
|
||||
@@ -82,12 +84,18 @@ export class AppState {
|
||||
this.$rootScope = $rootScope;
|
||||
this.application = application;
|
||||
this.notes = new NotesState(
|
||||
this.application,
|
||||
application,
|
||||
this,
|
||||
async () => {
|
||||
await this.notifyEvent(AppStateEvent.ActiveEditorChanged);
|
||||
},
|
||||
this.appEventObserverRemovers,
|
||||
);
|
||||
this.noteTags = new NoteTagsState(
|
||||
application,
|
||||
this,
|
||||
this.appEventObserverRemovers
|
||||
);
|
||||
this.tags = new TagsState(
|
||||
application,
|
||||
this.appEventObserverRemovers,
|
||||
|
||||
208
app/assets/javascripts/ui_models/app_state/note_tags_state.ts
Normal file
208
app/assets/javascripts/ui_models/app_state/note_tags_state.ts
Normal file
@@ -0,0 +1,208 @@
|
||||
import { SNNote, ContentType, SNTag, UuidString } from '@standardnotes/snjs';
|
||||
import { action, computed, makeObservable, observable } from 'mobx';
|
||||
import { WebApplication } from '../application';
|
||||
import { AppState } from './app_state';
|
||||
|
||||
export class NoteTagsState {
|
||||
autocompleteInputFocused = false;
|
||||
autocompleteSearchQuery = '';
|
||||
autocompleteTagHintFocused = false;
|
||||
autocompleteTagResults: SNTag[] = [];
|
||||
focusedTagResultUuid: UuidString | undefined = undefined;
|
||||
focusedTagUuid: UuidString | undefined = undefined;
|
||||
tags: SNTag[] = [];
|
||||
tagsContainerMaxWidth: number | 'auto' = 0;
|
||||
|
||||
constructor(
|
||||
private application: WebApplication,
|
||||
private appState: AppState,
|
||||
appEventListeners: (() => void)[]
|
||||
) {
|
||||
makeObservable(this, {
|
||||
autocompleteInputFocused: observable,
|
||||
autocompleteSearchQuery: observable,
|
||||
autocompleteTagHintFocused: observable,
|
||||
autocompleteTagResults: observable,
|
||||
focusedTagUuid: observable,
|
||||
focusedTagResultUuid: observable,
|
||||
tags: observable,
|
||||
tagsContainerMaxWidth: observable,
|
||||
|
||||
autocompleteTagHintVisible: computed,
|
||||
|
||||
clearAutocompleteSearch: action,
|
||||
focusNextTag: action,
|
||||
focusPreviousTag: action,
|
||||
setAutocompleteInputFocused: action,
|
||||
setAutocompleteSearchQuery: action,
|
||||
setAutocompleteTagHintFocused: action,
|
||||
setAutocompleteTagResults: action,
|
||||
setFocusedTagResultUuid: action,
|
||||
setFocusedTagUuid: action,
|
||||
setTags: action,
|
||||
setTagsContainerMaxWidth: action,
|
||||
reloadTags: action,
|
||||
});
|
||||
|
||||
appEventListeners.push(
|
||||
application.streamItems(ContentType.Tag, () => {
|
||||
this.reloadTags();
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
get activeNote(): SNNote | undefined {
|
||||
return this.appState.notes.activeEditor?.note;
|
||||
}
|
||||
|
||||
get autocompleteTagHintVisible(): boolean {
|
||||
return (
|
||||
this.autocompleteSearchQuery !== '' &&
|
||||
!this.autocompleteTagResults.some(
|
||||
(tagResult) => tagResult.title === this.autocompleteSearchQuery
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
setAutocompleteInputFocused(focused: boolean): void {
|
||||
this.autocompleteInputFocused = focused;
|
||||
}
|
||||
|
||||
setAutocompleteSearchQuery(query: string): void {
|
||||
this.autocompleteSearchQuery = query;
|
||||
}
|
||||
|
||||
setAutocompleteTagHintFocused(focused: boolean): void {
|
||||
this.autocompleteTagHintFocused = focused;
|
||||
}
|
||||
|
||||
setAutocompleteTagResults(results: SNTag[]): void {
|
||||
this.autocompleteTagResults = results;
|
||||
}
|
||||
|
||||
setFocusedTagUuid(tagUuid: UuidString | undefined): void {
|
||||
this.focusedTagUuid = tagUuid;
|
||||
}
|
||||
|
||||
setFocusedTagResultUuid(tagUuid: UuidString | undefined): void {
|
||||
this.focusedTagResultUuid = tagUuid;
|
||||
}
|
||||
|
||||
setTags(tags: SNTag[]): void {
|
||||
this.tags = tags;
|
||||
}
|
||||
|
||||
setTagsContainerMaxWidth(width: number): void {
|
||||
this.tagsContainerMaxWidth = width;
|
||||
}
|
||||
|
||||
clearAutocompleteSearch(): void {
|
||||
this.setAutocompleteSearchQuery('');
|
||||
this.searchActiveNoteAutocompleteTags();
|
||||
}
|
||||
|
||||
async createAndAddNewTag(): Promise<void> {
|
||||
const newTag = await this.application.findOrCreateTag(
|
||||
this.autocompleteSearchQuery
|
||||
);
|
||||
await this.addTagToActiveNote(newTag);
|
||||
this.clearAutocompleteSearch();
|
||||
}
|
||||
|
||||
focusNextTag(tag: SNTag): void {
|
||||
const nextTagIndex = this.getTagIndex(tag, this.tags) + 1;
|
||||
if (nextTagIndex > -1 && this.tags.length > nextTagIndex) {
|
||||
const nextTag = this.tags[nextTagIndex];
|
||||
this.setFocusedTagUuid(nextTag.uuid);
|
||||
}
|
||||
}
|
||||
|
||||
focusNextTagResult(tagResult: SNTag): void {
|
||||
const nextTagResultIndex =
|
||||
this.getTagIndex(tagResult, this.autocompleteTagResults) + 1;
|
||||
if (
|
||||
nextTagResultIndex > -1 &&
|
||||
this.autocompleteTagResults.length > nextTagResultIndex
|
||||
) {
|
||||
const nextTagResult = this.autocompleteTagResults[nextTagResultIndex];
|
||||
this.setFocusedTagResultUuid(nextTagResult.uuid);
|
||||
}
|
||||
}
|
||||
|
||||
focusPreviousTag(tag: SNTag): void {
|
||||
const previousTagIndex = this.getTagIndex(tag, this.tags) - 1;
|
||||
if (previousTagIndex > -1 && this.tags.length > previousTagIndex) {
|
||||
const previousTag = this.tags[previousTagIndex];
|
||||
this.setFocusedTagUuid(previousTag.uuid);
|
||||
}
|
||||
}
|
||||
|
||||
focusPreviousTagResult(tagResult: SNTag): void {
|
||||
const previousTagResultIndex =
|
||||
this.getTagIndex(tagResult, this.autocompleteTagResults) - 1;
|
||||
if (
|
||||
previousTagResultIndex > -1 &&
|
||||
this.autocompleteTagResults.length > previousTagResultIndex
|
||||
) {
|
||||
const previousTagResult =
|
||||
this.autocompleteTagResults[previousTagResultIndex];
|
||||
this.setFocusedTagResultUuid(previousTagResult.uuid);
|
||||
}
|
||||
}
|
||||
|
||||
searchActiveNoteAutocompleteTags(): void {
|
||||
const newResults = this.application.searchTags(
|
||||
this.autocompleteSearchQuery,
|
||||
this.activeNote
|
||||
);
|
||||
this.setAutocompleteTagResults(newResults);
|
||||
}
|
||||
|
||||
getTagIndex(tag: SNTag, tagsArr: SNTag[]): number {
|
||||
return tagsArr.findIndex((t) => t.uuid === tag.uuid);
|
||||
}
|
||||
|
||||
reloadTags(): void {
|
||||
const { activeNote } = this;
|
||||
if (activeNote) {
|
||||
const tags = this.application.getSortedTagsForNote(activeNote);
|
||||
this.setTags(tags);
|
||||
}
|
||||
}
|
||||
|
||||
reloadTagsContainerMaxWidth(): void {
|
||||
const EDITOR_ELEMENT_ID = 'editor-column';
|
||||
const editorWidth = document.getElementById(EDITOR_ELEMENT_ID)?.clientWidth;
|
||||
if (editorWidth) {
|
||||
this.setTagsContainerMaxWidth(editorWidth);
|
||||
}
|
||||
}
|
||||
|
||||
async addTagToActiveNote(tag: SNTag): Promise<void> {
|
||||
const { activeNote } = this;
|
||||
if (activeNote) {
|
||||
const parentChainTags = this.application.getTagParentChain(tag);
|
||||
const tagsToAdd = [...parentChainTags, tag];
|
||||
await Promise.all(
|
||||
tagsToAdd.map(async (tag) => {
|
||||
await this.application.changeItem(tag.uuid, (mutator) => {
|
||||
mutator.addItemAsRelationship(activeNote);
|
||||
});
|
||||
})
|
||||
);
|
||||
this.application.sync();
|
||||
this.reloadTags();
|
||||
}
|
||||
}
|
||||
|
||||
async removeTagFromActiveNote(tag: SNTag): Promise<void> {
|
||||
const { activeNote } = this;
|
||||
if (activeNote) {
|
||||
await this.application.changeItem(tag.uuid, (mutator) => {
|
||||
mutator.removeItemAsRelationship(activeNote);
|
||||
});
|
||||
this.application.sync();
|
||||
this.reloadTags();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
} from 'mobx';
|
||||
import { WebApplication } from '../application';
|
||||
import { Editor } from '../editor';
|
||||
import { AppState } from './app_state';
|
||||
|
||||
export class NotesState {
|
||||
lastSelectedNote: SNNote | undefined;
|
||||
@@ -32,6 +33,7 @@ export class NotesState {
|
||||
|
||||
constructor(
|
||||
private application: WebApplication,
|
||||
private appState: AppState,
|
||||
private onActiveEditorChanged: () => Promise<void>,
|
||||
appEventListeners: (() => void)[]
|
||||
) {
|
||||
@@ -168,6 +170,8 @@ export class NotesState {
|
||||
} else {
|
||||
this.activeEditor.setNote(note);
|
||||
}
|
||||
|
||||
this.appState.noteTags.reloadTags();
|
||||
await this.onActiveEditorChanged();
|
||||
|
||||
if (note.waitingForKey) {
|
||||
@@ -326,11 +330,17 @@ export class NotesState {
|
||||
|
||||
async addTagToSelectedNotes(tag: SNTag): Promise<void> {
|
||||
const selectedNotes = Object.values(this.selectedNotes);
|
||||
await this.application.changeItem(tag.uuid, (mutator) => {
|
||||
for (const note of selectedNotes) {
|
||||
mutator.addItemAsRelationship(note);
|
||||
}
|
||||
});
|
||||
const parentChainTags = this.application.getTagParentChain(tag);
|
||||
const tagsToAdd = [...parentChainTags, tag];
|
||||
await Promise.all(
|
||||
tagsToAdd.map(async (tag) => {
|
||||
await this.application.changeItem(tag.uuid, (mutator) => {
|
||||
for (const note of selectedNotes) {
|
||||
mutator.addItemAsRelationship(note);
|
||||
}
|
||||
});
|
||||
})
|
||||
);
|
||||
this.application.sync();
|
||||
}
|
||||
|
||||
@@ -342,13 +352,13 @@ export class NotesState {
|
||||
}
|
||||
});
|
||||
this.application.sync();
|
||||
|
||||
}
|
||||
|
||||
isTagInSelectedNotes(tag: SNTag): boolean {
|
||||
const selectedNotes = Object.values(this.selectedNotes);
|
||||
return selectedNotes.every((note) =>
|
||||
this.application
|
||||
.getAppState()
|
||||
this.appState
|
||||
.getNoteTags(note)
|
||||
.find((noteTag) => noteTag.uuid === tag.uuid)
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user