feat(react/ribbon): display help tooltip in attribute editor

This commit is contained in:
Elian Doran 2025-08-23 12:31:54 +03:00
parent 1e00407864
commit befc5a9530
No known key found for this signature in database
4 changed files with 76 additions and 32 deletions

View File

@ -16,12 +16,7 @@ import type { default as FAttribute, AttributeType } from "../../entities/fattri
import type FNote from "../../entities/fnote.js";
import { escapeQuotes } from "../../services/utils.js";
const HELP_TEXT = `
<p>${t("attribute_editor.help_text_body1")}</p>
<p>${t("attribute_editor.help_text_body2")}</p>
<p>${t("attribute_editor.help_text_body3")}</p>`;
const TPL = /*html*/`
@ -229,9 +224,7 @@ export default class AttributeEditorWidget extends NoteContextAwareWidget implem
}
async handleEditorClick(e: JQuery.ClickEvent) {
const pos = this.textEditor.model.document.selection.getFirstPosition();
if (pos && pos.textNode && pos.textNode.data) {
if () {
const clickIndex = this.getClickIndex(pos);
let parsedAttrs;
@ -267,25 +260,9 @@ export default class AttributeEditorWidget extends NoteContextAwareWidget implem
this.showHelpTooltip();
}
}, 100);
} else {
this.showHelpTooltip();
}
}
showHelpTooltip() {
this.attributeDetailWidget.hide();
this.$editor.tooltip({
trigger: "focus",
html: true,
title: HELP_TEXT,
placement: "bottom",
offset: "0,30"
});
this.$editor.tooltip("show");
}
getClickIndex(pos: ModelPosition) {
let clickIndex = pos.offset - (pos.textNode?.startOffset ?? 0);

View File

@ -1,4 +1,4 @@
import type { AttributeEditor, EditorConfig } from "@triliumnext/ckeditor5";
import { CKTextEditor, type AttributeEditor, type EditorConfig, type ModelPosition } from "@triliumnext/ckeditor5";
import { useEffect, useRef } from "preact/compat";
interface CKEditorOpts {
@ -9,15 +9,19 @@ interface CKEditorOpts {
disableNewlines?: boolean;
disableSpellcheck?: boolean;
onChange?: () => void;
onClick?: (pos?: ModelPosition | null) => void;
}
export default function CKEditor({ className, tabIndex, editor, config, disableNewlines, disableSpellcheck, onChange }: CKEditorOpts) {
export default function CKEditor({ className, tabIndex, editor, config, disableNewlines, disableSpellcheck, onChange, onClick }: CKEditorOpts) {
const editorContainerRef = useRef<HTMLDivElement>(null);
const textEditorRef = useRef<CKTextEditor>(null);
useEffect(() => {
if (!editorContainerRef.current) return;
editor.create(editorContainerRef.current, config).then((textEditor) => {
textEditorRef.current = textEditor;
if (disableNewlines) {
textEditor.editing.view.document.on(
"enter",
@ -48,6 +52,12 @@ export default function CKEditor({ className, tabIndex, editor, config, disableN
ref={editorContainerRef}
className={className}
tabIndex={tabIndex}
onClick={() => {
if (onClick) {
const pos = textEditorRef.current?.model.document.selection.getFirstPosition();
onClick(pos);
}
}}
/>
)
}

View File

@ -12,7 +12,8 @@ import FNote from "../../entities/fnote";
import attributes from "../../services/attributes";
import FBlob from "../../entities/fblob";
import NoteContextAwareWidget from "../note_context_aware_widget";
import { RefObject, VNode } from "preact";
import { Ref, RefObject, VNode } from "preact";
import { Tooltip } from "bootstrap";
type TriliumEventHandler<T extends EventNames> = (data: EventData<T>) => void;
const registeredHandlers: Map<Component, Map<EventNames, TriliumEventHandler<any>[]>> = new Map();
@ -511,3 +512,28 @@ export function useWindowSize() {
return size;
}
export function useTooltip(elRef: RefObject<HTMLElement>, config: Partial<Tooltip.Options>) {
useEffect(() => {
if (!elRef?.current) return;
const $el = $(elRef.current);
$el.tooltip(config);
}, [ elRef, config ]);
const showTooltip = useCallback(() => {
if (!elRef?.current) return;
const $el = $(elRef.current);
$el.tooltip("show");
}, [ elRef ]);
const hideTooltip = useCallback(() => {
if (!elRef?.current) return;
const $el = $(elRef.current);
$el.tooltip("hide");
}, [ elRef ]);
return { showTooltip, hideTooltip };
}

View File

@ -1,9 +1,17 @@
import { useCallback, useEffect, useMemo, useRef, useState } from "preact/hooks"
import { AttributeEditor as CKEditorAttributeEditor, EditorConfig, MentionFeed } from "@triliumnext/ckeditor5";
import { useEffect, useRef, useState } from "preact/hooks"
import { AttributeEditor as CKEditorAttributeEditor, MentionFeed } from "@triliumnext/ckeditor5";
import { t } from "../../../services/i18n";
import server from "../../../services/server";
import note_autocomplete, { Suggestion } from "../../../services/note_autocomplete";
import CKEditor from "../../react/CKEditor";
import { useTooltip } from "../../react/hooks";
const HELP_TEXT = `
<p>${t("attribute_editor.help_text_body1")}</p>
<p>${t("attribute_editor.help_text_body2")}</p>
<p>${t("attribute_editor.help_text_body3")}</p>`;
const mentionSetup: MentionFeed[] = [
{
@ -52,10 +60,26 @@ const mentionSetup: MentionFeed[] = [
export default function AttributeEditor() {
const [ attributeDetailVisible, setAttributeDetailVisible ] = useState(false);
const [ state, setState ] = useState<"normal" | "showHelpTooltip" | "showAttributeDetail">();
const wrapperRef = useRef<HTMLDivElement>(null);
const { showTooltip, hideTooltip } = useTooltip(wrapperRef, {
trigger: "focus",
html: true,
title: HELP_TEXT,
placement: "bottom",
offset: "0,30"
});
useEffect(() => {
if (state === "showHelpTooltip") {
showTooltip();
} else {
hideTooltip();
}
}, [ state ]);
return (
<div style="position: relative; padding-top: 10px; padding-bottom: 10px">
<div ref={wrapperRef} style="position: relative; padding-top: 10px; padding-bottom: 10px">
<CKEditor
className="attribute-list-editor"
tabIndex={200}
@ -69,6 +93,13 @@ export default function AttributeEditor() {
onChange={() => {
console.log("Data changed!");
}}
onClick={(pos) => {
if (pos && pos.textNode && pos.textNode.data) {
setState("showAttributeDetail")
} else {
setState("showHelpTooltip");
}
}}
disableNewlines disableSpellcheck
/>
</div>