fix(text): classic toolbar broken on mobile due to prior change

This commit is contained in:
Elian Doran 2025-11-22 11:37:34 +02:00
parent c76f368fa0
commit 9b3ca65492
No known key found for this signature in database
3 changed files with 48 additions and 36 deletions

View File

@ -10,16 +10,21 @@ import { TabContext } from "./ribbon-interface";
* The ribbon item is active by default for text notes, as long as they are not in read-only mode. * The ribbon item is active by default for text notes, as long as they are not in read-only mode.
* *
* ! The toolbar is not only used in the ribbon, but also in the quick edit feature. * ! The toolbar is not only used in the ribbon, but also in the quick edit feature.
* * The mobile toolbar is handled separately (see `MobileEditorToolbar`).
*/ */
export default function FormattingToolbar({ hidden, ntxId }: TabContext) { export default function FormattingToolbar({ hidden, ntxId }: TabContext) {
const containerRef = useRef<HTMLDivElement>(null); const containerRef = useRef<HTMLDivElement>(null);
const [ textNoteEditorType ] = useTriliumOption("textNoteEditorType"); const [ textNoteEditorType ] = useTriliumOption("textNoteEditorType");
// Attach the toolbar from the CKEditor.
useTriliumEvent("textEditorRefreshed", ({ ntxId: eventNtxId, editor }) => { useTriliumEvent("textEditorRefreshed", ({ ntxId: eventNtxId, editor }) => {
if (eventNtxId !== ntxId) return; if (eventNtxId !== ntxId || !containerRef.current) return;
const toolbar = editor.ui.view.toolbar?.element; const toolbar = editor.ui.view.toolbar?.element;
if (toolbar && containerRef.current) {
if (toolbar) {
containerRef.current.replaceChildren(toolbar); containerRef.current.replaceChildren(toolbar);
} else {
containerRef.current.replaceChildren();
} }
}); });

View File

@ -233,12 +233,6 @@ export default function EditableText({ note, parentComponent, ntxId, noteContext
onWatchdogStateChange={onWatchdogStateChange} onWatchdogStateChange={onWatchdogStateChange}
onChange={() => spacedUpdate.scheduleUpdate()} onChange={() => spacedUpdate.scheduleUpdate()}
onEditorInitialized={(editor) => { onEditorInitialized={(editor) => {
console.log("Editor has been initialized!", parentComponent, editor);
if (isClassicEditor) {
setupClassicEditor(editor, parentComponent);
}
if (hasTouchBar) { if (hasTouchBar) {
const handler = () => refreshTouchBarRef.current?.(); const handler = () => refreshTouchBarRef.current?.();
for (const event of [ "bold", "italic", "underline", "paragraph", "heading" ]) { for (const event of [ "bold", "italic", "underline", "paragraph", "heading" ]) {
@ -303,26 +297,6 @@ function onNotificationWarning(data, evt) {
evt.stop(); evt.stop();
} }
function setupClassicEditor(editor: CKTextEditor, parentComponent: Component | undefined) {
if (!parentComponent) return;
if (utils.isMobile()) {
// Reposition all dropdowns to point upwards instead of downwards.
// See https://ckeditor.com/docs/ckeditor5/latest/examples/framework/bottom-toolbar-editor.html for more info.
const toolbarView = (editor as ClassicEditor).ui.view.toolbar;
for (const item of toolbarView.items) {
if (!("panelView" in item)) continue;
item.on("change:isOpen", () => {
if (!("isOpen" in item) || !item.isOpen) return;
// @ts-ignore
item.panelView.position = item.panelView.position.replace("s", "n");
});
}
}
}
function EditableTextTouchBar({ watchdogRef, refreshTouchBarRef }: { watchdogRef: RefObject<EditorWatchdog | null>, refreshTouchBarRef: RefObject<() => void> }) { function EditableTextTouchBar({ watchdogRef, refreshTouchBarRef }: { watchdogRef: RefObject<EditorWatchdog | null>, refreshTouchBarRef: RefObject<() => void> }) {
const [ headingSelectedIndex, setHeadingSelectedIndex ] = useState<number>(); const [ headingSelectedIndex, setHeadingSelectedIndex ] = useState<number>();

View File

@ -1,7 +1,8 @@
import { MutableRef, useCallback, useEffect, useRef, useState } from "preact/hooks"; import { MutableRef, useCallback, useEffect, useRef, useState } from "preact/hooks";
import { useNoteContext } from "../../react/hooks"; import { useNoteContext, useTriliumEvent } from "../../react/hooks";
import "./mobile_editor_toolbar.css"; import "./mobile_editor_toolbar.css";
import { isIOS } from "../../../services/utils"; import { isIOS } from "../../../services/utils";
import { CKTextEditor, ClassicEditor } from "@triliumnext/ckeditor5";
/** /**
* Handles the editing toolbar for CKEditor in mobile mode. The toolbar acts as a floating bar, with two different mechanism: * Handles the editing toolbar for CKEditor in mobile mode. The toolbar acts as a floating bar, with two different mechanism:
@ -10,12 +11,12 @@ import { isIOS } from "../../../services/utils";
* - On Android, the viewport change makes the keyboard resize the content area, all we have to do is to hide the tab bar and global menu (handled in the global style). * - On Android, the viewport change makes the keyboard resize the content area, all we have to do is to hide the tab bar and global menu (handled in the global style).
*/ */
export default function MobileEditorToolbar() { export default function MobileEditorToolbar() {
const wrapperRef = useRef<HTMLDivElement>(null); const containerRef = useRef<HTMLDivElement>(null);
const { note, noteContext } = useNoteContext(); const { note, noteContext, ntxId } = useNoteContext();
const [ shouldDisplay, setShouldDisplay ] = useState(false); const [ shouldDisplay, setShouldDisplay ] = useState(false);
const [ dropdownActive, setDropdownActive ] = useState(false); const [ dropdownActive, setDropdownActive ] = useState(false);
usePositioningOniOS(wrapperRef); usePositioningOniOS(containerRef);
useEffect(() => { useEffect(() => {
noteContext?.isReadOnly().then(isReadOnly => { noteContext?.isReadOnly().then(isReadOnly => {
@ -23,15 +24,28 @@ export default function MobileEditorToolbar() {
}); });
}, [ note ]); }, [ note ]);
// Attach the toolbar from the CKEditor.
useTriliumEvent("textEditorRefreshed", ({ ntxId: eventNtxId, editor }) => {
if (eventNtxId !== ntxId || !containerRef.current) return;
const toolbar = editor.ui.view.toolbar?.element;
repositionDropdowns(editor);
if (toolbar) {
containerRef.current.replaceChildren(toolbar);
} else {
containerRef.current.replaceChildren();
}
});
// 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. // 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.
useEffect(() => { useEffect(() => {
if (!wrapperRef.current) return; if (!containerRef.current) return;
const observer = new MutationObserver(e => { const observer = new MutationObserver(e => {
setDropdownActive(e.map((e) => (e.target as any).ariaExpanded === "true").reduce((acc, e) => acc && e)); setDropdownActive(e.map((e) => (e.target as any).ariaExpanded === "true").reduce((acc, e) => acc && e));
}); });
observer.observe(wrapperRef.current, { observer.observe(containerRef.current, {
attributeFilter: ["aria-expanded"], attributeFilter: ["aria-expanded"],
subtree: true subtree: true
}); });
@ -41,7 +55,7 @@ export default function MobileEditorToolbar() {
return ( return (
<div className={`classic-toolbar-outer-container ${!shouldDisplay ? "hidden-ext" : "visible"} ${isIOS() ? "ios" : ""}`}> <div className={`classic-toolbar-outer-container ${!shouldDisplay ? "hidden-ext" : "visible"} ${isIOS() ? "ios" : ""}`}>
<div ref={wrapperRef} className={`classic-toolbar-widget ${dropdownActive ? "dropdown-active" : ""}`}></div> <div ref={containerRef} className={`classic-toolbar-widget ${dropdownActive ? "dropdown-active" : ""}`}></div>
</div> </div>
) )
} }
@ -65,3 +79,22 @@ function usePositioningOniOS(wrapperRef: MutableRef<HTMLDivElement | null>) {
}; };
}, []); }, []);
} }
/**
* Reposition all dropdowns to point upwards instead of downwards.
* See https://ckeditor.com/docs/ckeditor5/latest/examples/framework/bottom-toolbar-editor.html for more info.
* @param editor
*/
function repositionDropdowns(editor: CKTextEditor) {
const toolbarView = (editor as ClassicEditor).ui.view.toolbar;
for (const item of toolbarView.items) {
if (!("panelView" in item)) continue;
item.on("change:isOpen", () => {
if (!("isOpen" in item) || !item.isOpen) return;
// @ts-ignore
item.panelView.position = item.panelView.position.replace("s", "n");
});
}
}