From fbba76bbb3e1da5d416dc8858a7e5de5bc09609b Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Thu, 10 Apr 2025 11:49:52 +0300 Subject: [PATCH] refactor(components): split editor toolbar for mobile --- src/public/app/layouts/mobile_layout.ts | 4 +- .../ribbon_widgets/classic_editor_toolbar.ts | 89 +---------- .../ribbon_widgets/mobile_editor_toolbar.ts | 139 ++++++++++++++++++ 3 files changed, 142 insertions(+), 90 deletions(-) create mode 100644 src/public/app/widgets/ribbon_widgets/mobile_editor_toolbar.ts diff --git a/src/public/app/layouts/mobile_layout.ts b/src/public/app/layouts/mobile_layout.ts index 6032c14c5..e7cea7576 100644 --- a/src/public/app/layouts/mobile_layout.ts +++ b/src/public/app/layouts/mobile_layout.ts @@ -22,7 +22,6 @@ import LauncherContainer from "../widgets/containers/launcher_container.js"; import RootContainer from "../widgets/containers/root_container.js"; import SharedInfoWidget from "../widgets/shared_info.js"; import PromotedAttributesWidget from "../widgets/ribbon_widgets/promoted_attributes.js"; -import ClassicEditorToolbar from "../widgets/ribbon_widgets/classic_editor_toolbar.js"; import SidebarContainer from "../widgets/mobile_widgets/sidebar_container.js"; import AboutDialog from "../widgets/dialogs/about.js"; import HelpDialog from "../widgets/dialogs/help.js"; @@ -32,6 +31,7 @@ import JumpToNoteDialog from "../widgets/dialogs/jump_to_note.js"; import RecentChangesDialog from "../widgets/dialogs/recent_changes.js"; import PromptDialog from "../widgets/dialogs/prompt.js"; import RefreshButton from "../widgets/floating_buttons/refresh_button.js"; +import MobileEditorToolbar from "../widgets/ribbon_widgets/mobile_editor_toolbar.js"; const MOBILE_CSS = ` `; @@ -82,13 +35,6 @@ const TPL = /*html*/`\ */ export default class ClassicEditorToolbar extends NoteContextAwareWidget { - private observer: MutationObserver; - - constructor() { - super(); - this.observer = new MutationObserver((e) => this.#onDropdownStateChanged(e)); - } - get name() { return "classicEditor"; } @@ -100,33 +46,6 @@ export default class ClassicEditorToolbar extends NoteContextAwareWidget { doRender() { this.$widget = $(TPL); this.contentSized(); - - if (utils.isMobile()) { - // The virtual keyboard obscures the editing toolbar so we have to reposition by calculating the height of the keyboard. - window.visualViewport?.addEventListener("resize", () => this.#adjustPosition()); - window.addEventListener("scroll", () => this.#adjustPosition()); - - // Observe when a dropdown is expanded to apply a style that allows the dropdown to be visible, since we can't have the element both visible and the toolbar scrollable. - this.observer.disconnect(); - this.observer.observe(this.$widget[0], { - attributeFilter: ["aria-expanded"], - subtree: true - }); - } - } - - #onDropdownStateChanged(e: MutationRecord[]) { - const dropdownActive = e.map((e) => (e.target as any).ariaExpanded === "true").reduce((acc, e) => acc && e); - this.$widget[0].classList.toggle("dropdown-active", dropdownActive); - } - - #adjustPosition() { - let bottom = window.innerHeight - (window.visualViewport?.height || 0); - - // When the keyboard is not visible, align it to the launcher bar instead. - bottom = Math.max(bottom, document.getElementById("mobile-bottom-bar")?.offsetHeight || 0); - - this.$widget.css("bottom", `${bottom}px`); } async getTitle() { @@ -139,7 +58,7 @@ export default class ClassicEditorToolbar extends NoteContextAwareWidget { } async #shouldDisplay() { - if (utils.isDesktop() && options.get("textNoteEditorType") !== "ckeditor-classic") { + if (options.get("textNoteEditorType") !== "ckeditor-classic") { return false; } @@ -154,10 +73,4 @@ export default class ClassicEditorToolbar extends NoteContextAwareWidget { return true; } - async refreshWithNote() { - if (utils.isMobile()) { - this.toggleExt(await this.#shouldDisplay()); - } - } - } diff --git a/src/public/app/widgets/ribbon_widgets/mobile_editor_toolbar.ts b/src/public/app/widgets/ribbon_widgets/mobile_editor_toolbar.ts new file mode 100644 index 000000000..81b8a1d97 --- /dev/null +++ b/src/public/app/widgets/ribbon_widgets/mobile_editor_toolbar.ts @@ -0,0 +1,139 @@ +import NoteContextAwareWidget from "../note_context_aware_widget.js"; + +const TPL = /*html*/`\ +
+ + +`; + +/** + * Handles the editing toolbar when the CKEditor is in decoupled mode. + * + *

+ * This toolbar is only enabled if the user has selected the classic CKEditor. + * + *

+ * The ribbon item is active by default for text notes, as long as they are not in read-only mode. + */ +export default class MobileEditorToolbar extends NoteContextAwareWidget { + + private observer: MutationObserver; + + constructor() { + super(); + this.observer = new MutationObserver((e) => this.#onDropdownStateChanged(e)); + } + + get name() { + return "classicEditor"; + } + + doRender() { + this.$widget = $(TPL); + this.contentSized(); + + // The virtual keyboard obscures the editing toolbar so we have to reposition by calculating the height of the keyboard. + window.visualViewport?.addEventListener("resize", () => this.#adjustPosition()); + window.addEventListener("scroll", () => this.#adjustPosition()); + + // Observe when a dropdown is expanded to apply a style that allows the dropdown to be visible, since we can't have the element both visible and the toolbar scrollable. + this.observer.disconnect(); + this.observer.observe(this.$widget[0], { + attributeFilter: ["aria-expanded"], + subtree: true + }); + } + + #onDropdownStateChanged(e: MutationRecord[]) { + const dropdownActive = e.map((e) => (e.target as any).ariaExpanded === "true").reduce((acc, e) => acc && e); + this.$widget[0].classList.toggle("dropdown-active", dropdownActive); + } + + #adjustPosition() { + let bottom = window.innerHeight - (window.visualViewport?.height || 0); + + // When the keyboard is not visible, align it to the launcher bar instead. + bottom = Math.max(bottom, document.getElementById("mobile-bottom-bar")?.offsetHeight || 0); + + this.$widget.css("bottom", `${bottom}px`); + } + + async #shouldDisplay() { + if (!this.note || this.note.type !== "text") { + return false; + } + + if (await this.noteContext?.isReadOnly()) { + return false; + } + + return true; + } + + async refreshWithNote() { + this.toggleExt(await this.#shouldDisplay()); + } + +}