feat: Add new "Change Editor" option to note context menu (#823)

* feat: add editor icon

* refactor: remove 'any' type and format

* refactor: move NotesOptions and add ChangeEditorOption

* refactor: fix type for using regular RefObject<T>

* feat: add hide-if-last-child util class

* feat: add Change Editor option

* feat: make radio btn gray if not checked

* fix: accordion menu header and item sizing/spacing

* feat: add Escape key to KeyboardKey enum

* refactor: Remove Editor Menu

* feat: add editor select functionality

* refactor: move plain editor name to constant

* feat: add premium editors with modal if no subscription

refactor: simplify menu group creation

* feat: show alert when switching to non-interchangeable editor

* fix: change editor menu going out of bounds

* feat: increase group header & editor item size

* fix: change editor menu close on blur

* refactor: Use KeyboardKey enum & remove else statement

* feat: add keyboard navigation to change editor menu

* fix: editor menu separators

* feat: improve change editor menu sizing & spacing

* feat: show alert only if editor is not interchangeable

* feat: don't show alert when switching to/from plain editor

* chore: bump snjs version

* feat: temporarily remove change editor alert

* feat: dynamically get footer height

* refactor: move magic number to const

* refactor: move constants to constants file

* feat: use const instead of magic number
This commit is contained in:
Aman Harwara
2022-01-29 01:53:39 +05:30
committed by GitHub
parent 6aa926c2b0
commit b932e2a45e
21 changed files with 932 additions and 384 deletions

View File

@@ -2,3 +2,5 @@ export const PANEL_NAME_NOTES = 'notes';
export const PANEL_NAME_NAVIGATION = 'navigation';
export const EMAIL_REGEX =
/^([a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?\.)+[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?)$/;
export const MENU_MARGIN_FROM_APP_BORDER = 5;
export const MAX_MENU_SIZE_MULTIPLIER = 30;

View File

@@ -63,20 +63,6 @@
.sn-component(ng-if='self.note')
#editor-menu-bar.sk-app-bar.no-edges
.left
.sk-app-bar-item(
click-outside=`self.setMenuState('showEditorMenu', false)`
is-open='self.state.showEditorMenu',
ng-class="{'selected' : self.state.showEditorMenu}",
ng-click="self.toggleMenu('showEditorMenu')"
)
.sk-label Editor
editor-menu(
callback='self.editorMenuOnSelect',
current-item='self.note',
ng-if='self.state.showEditorMenu',
selected-editor-uuid='self.state.editorComponentViewer && self.state.editorComponentViewer.component.uuid',
application='self.application'
)
.sk-app-bar-item(
click-outside=`self.setMenuState('showActionsMenu', false)`,
is-open='self.state.showActionsMenu',

View File

@@ -8,7 +8,6 @@ import {
ContentType,
SNComponent,
SNNote,
NoteMutator,
ComponentArea,
PrefKey,
ComponentMutator,
@@ -28,7 +27,6 @@ import { EventSource } from '@/ui_models/app_state';
import {
STRING_DELETE_PLACEHOLDER_ATTEMPT,
STRING_DELETE_LOCKED_ATTEMPT,
STRING_EDIT_LOCKED_ATTEMPT,
StringDeleteNote,
} from '@/strings';
import { confirmDialog } from '@/services/alertService';
@@ -59,7 +57,6 @@ type EditorState = {
isDesktop?: boolean;
syncTakingTooLong: boolean;
showActionsMenu: boolean;
showEditorMenu: boolean;
showHistoryMenu: boolean;
spellcheck: boolean;
/** Setting to true then false will allow the main content textarea to be destroyed
@@ -83,6 +80,46 @@ function sortAlphabetically(array: SNComponent[]): SNComponent[] {
);
}
export const transactionForAssociateComponentWithCurrentNote = (
component: SNComponent,
note: SNNote
) => {
const transaction: TransactionalMutation = {
itemUuid: component.uuid,
mutate: (m: ItemMutator) => {
const mutator = m as ComponentMutator;
mutator.removeDisassociatedItemId(note.uuid);
mutator.associateWithItem(note.uuid);
},
};
return transaction;
};
export const transactionForDisassociateComponentWithCurrentNote = (
component: SNComponent,
note: SNNote
) => {
const transaction: TransactionalMutation = {
itemUuid: component.uuid,
mutate: (m: ItemMutator) => {
const mutator = m as ComponentMutator;
mutator.removeAssociatedItemId(note.uuid);
mutator.disassociateWithItem(note.uuid);
},
};
return transaction;
};
export const reloadFont = (monospaceFont?: boolean) => {
const root = document.querySelector(':root') as HTMLElement;
const propertyName = '--sn-stylekit-editor-font-family';
if (monospaceFont) {
root.style.setProperty(propertyName, 'var(--sn-stylekit-monospace-font)');
} else {
root.style.setProperty(propertyName, 'var(--sn-stylekit-sans-serif-font)');
}
};
export class NoteView extends PureViewCtrl<unknown, EditorState> {
/** Passed through template */
readonly application!: WebApplication;
@@ -114,7 +151,6 @@ export class NoteView extends PureViewCtrl<unknown, EditorState> {
onReady: () => this.reloadPreferences(),
};
this.editorMenuOnSelect = this.editorMenuOnSelect.bind(this);
this.onPanelResizeFinish = this.onPanelResizeFinish.bind(this);
this.setScrollPosition = this.setScrollPosition.bind(this);
this.resetScrollPosition = this.resetScrollPosition.bind(this);
@@ -146,7 +182,6 @@ export class NoteView extends PureViewCtrl<unknown, EditorState> {
this.onEditorComponentLoad = undefined;
this.statusTimeout = undefined;
(this.onPanelResizeFinish as unknown) = undefined;
(this.editorMenuOnSelect as unknown) = undefined;
super.deinit();
}
@@ -248,7 +283,6 @@ export class NoteView extends PureViewCtrl<unknown, EditorState> {
spellcheck: true,
syncTakingTooLong: false,
showActionsMenu: false,
showEditorMenu: false,
showHistoryMenu: false,
noteStatus: undefined,
textareaUnloading: false,
@@ -441,7 +475,7 @@ export class NoteView extends PureViewCtrl<unknown, EditorState> {
editorStateDidLoad: true,
});
}
this.reloadFont();
reloadFont(this.state.monospaceFont);
} else {
await this.setState({
editorStateDidLoad: true,
@@ -462,7 +496,7 @@ export class NoteView extends PureViewCtrl<unknown, EditorState> {
}
closeAllMenus(exclude?: string) {
const allMenus = ['showEditorMenu', 'showActionsMenu', 'showHistoryMenu'];
const allMenus = ['showActionsMenu', 'showHistoryMenu'];
const menuState: any = {};
for (const candidate of allMenus) {
if (candidate !== exclude) {
@@ -472,69 +506,6 @@ export class NoteView extends PureViewCtrl<unknown, EditorState> {
this.setState(menuState);
}
async editorMenuOnSelect(component?: SNComponent) {
const transactions: TransactionalMutation[] = [];
this.setMenuState('showEditorMenu', false);
if (this.appState.getActiveNoteController()?.isTemplateNote) {
await this.appState.getActiveNoteController().insertTemplatedNote();
}
if (this.note.locked) {
this.application.alertService.alert(STRING_EDIT_LOCKED_ATTEMPT);
return;
}
if (!component) {
if (!this.note.prefersPlainEditor) {
transactions.push({
itemUuid: this.note.uuid,
mutate: (m: ItemMutator) => {
const noteMutator = m as NoteMutator;
noteMutator.prefersPlainEditor = true;
},
});
}
if (
this.state.editorComponentViewer?.component.isExplicitlyEnabledForItem(
this.note.uuid
)
) {
transactions.push(
this.transactionForDisassociateComponentWithCurrentNote(
this.state.editorComponentViewer.component
)
);
}
this.reloadFont();
} else if (component.area === ComponentArea.Editor) {
const currentEditor = this.state.editorComponentViewer?.component;
if (currentEditor && component.uuid !== currentEditor.uuid) {
transactions.push(
this.transactionForDisassociateComponentWithCurrentNote(currentEditor)
);
}
const prefersPlain = this.note.prefersPlainEditor;
if (prefersPlain) {
transactions.push({
itemUuid: this.note.uuid,
mutate: (m: ItemMutator) => {
const noteMutator = m as NoteMutator;
noteMutator.prefersPlainEditor = false;
},
});
}
transactions.push(
this.transactionForAssociateComponentWithCurrentNote(component)
);
}
await this.application.runTransactionalMutations(transactions);
/** Dirtying can happen above */
this.application.sync();
}
hasAvailableExtensions() {
return (
this.application.actionsManager.extensionsInContextOfItem(this.note)
@@ -702,7 +673,7 @@ export class NoteView extends PureViewCtrl<unknown, EditorState> {
if (spellcheck !== this.state.spellcheck) {
await this.setState({ textareaUnloading: true });
await this.setState({ textareaUnloading: false });
this.reloadFont();
reloadFont(this.state.monospaceFont);
await this.setState({
spellcheck,
@@ -733,7 +704,7 @@ export class NoteView extends PureViewCtrl<unknown, EditorState> {
return;
}
this.reloadFont();
reloadFont(this.state.monospaceFont);
if (
this.state.marginResizersEnabled &&
@@ -753,19 +724,6 @@ export class NoteView extends PureViewCtrl<unknown, EditorState> {
}
}
reloadFont() {
const root = document.querySelector(':root') as HTMLElement;
const propertyName = '--sn-stylekit-editor-font-family';
if (this.state.monospaceFont) {
root.style.setProperty(propertyName, 'var(--sn-stylekit-monospace-font)');
} else {
root.style.setProperty(
propertyName,
'var(--sn-stylekit-sans-serif-font)'
);
}
}
/** @components */
registerComponentManagerEventObserver() {
@@ -844,42 +802,16 @@ export class NoteView extends PureViewCtrl<unknown, EditorState> {
async disassociateComponentWithCurrentNote(component: SNComponent) {
return this.application.runTransactionalMutation(
this.transactionForDisassociateComponentWithCurrentNote(component)
transactionForDisassociateComponentWithCurrentNote(component, this.note)
);
}
transactionForDisassociateComponentWithCurrentNote(component: SNComponent) {
const note = this.note;
const transaction: TransactionalMutation = {
itemUuid: component.uuid,
mutate: (m: ItemMutator) => {
const mutator = m as ComponentMutator;
mutator.removeAssociatedItemId(note.uuid);
mutator.disassociateWithItem(note.uuid);
},
};
return transaction;
}
async associateComponentWithCurrentNote(component: SNComponent) {
return this.application.runTransactionalMutation(
this.transactionForAssociateComponentWithCurrentNote(component)
transactionForAssociateComponentWithCurrentNote(component, this.note)
);
}
transactionForAssociateComponentWithCurrentNote(component: SNComponent) {
const note = this.note;
const transaction: TransactionalMutation = {
itemUuid: component.uuid,
mutate: (m: ItemMutator) => {
const mutator = m as ComponentMutator;
mutator.removeDisassociatedItemId(note.uuid);
mutator.associateWithItem(note.uuid);
},
};
return transaction;
}
registerKeyboardShortcuts() {
this.removeTrashKeyObserver = this.application.io.addKeyObserver({
key: KeyboardKey.Backspace,