2025-08-23 12:31:54 +03:00
|
|
|
import { CKTextEditor, type AttributeEditor, type EditorConfig, type ModelPosition } from "@triliumnext/ckeditor5";
|
2025-08-23 12:05:03 +03:00
|
|
|
import { useEffect, useRef } from "preact/compat";
|
|
|
|
|
|
|
|
|
|
interface CKEditorOpts {
|
2025-08-23 12:39:49 +03:00
|
|
|
currentValue?: string;
|
2025-08-23 12:05:03 +03:00
|
|
|
className: string;
|
|
|
|
|
tabIndex?: number;
|
|
|
|
|
config: EditorConfig;
|
|
|
|
|
editor: typeof AttributeEditor;
|
|
|
|
|
disableNewlines?: boolean;
|
|
|
|
|
disableSpellcheck?: boolean;
|
2025-08-23 12:55:48 +03:00
|
|
|
onChange?: (newValue?: string) => void;
|
|
|
|
|
onClick?: (e, pos?: ModelPosition | null) => void;
|
2025-08-23 12:05:03 +03:00
|
|
|
}
|
|
|
|
|
|
2025-08-23 12:39:49 +03:00
|
|
|
export default function CKEditor({ currentValue, className, tabIndex, editor, config, disableNewlines, disableSpellcheck, onChange, onClick }: CKEditorOpts) {
|
2025-08-23 12:05:03 +03:00
|
|
|
const editorContainerRef = useRef<HTMLDivElement>(null);
|
2025-08-23 12:31:54 +03:00
|
|
|
const textEditorRef = useRef<CKTextEditor>(null);
|
2025-08-23 12:05:03 +03:00
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
if (!editorContainerRef.current) return;
|
|
|
|
|
|
|
|
|
|
editor.create(editorContainerRef.current, config).then((textEditor) => {
|
2025-08-23 12:31:54 +03:00
|
|
|
textEditorRef.current = textEditor;
|
|
|
|
|
|
2025-08-23 12:05:03 +03:00
|
|
|
if (disableNewlines) {
|
|
|
|
|
textEditor.editing.view.document.on(
|
|
|
|
|
"enter",
|
|
|
|
|
(event, data) => {
|
|
|
|
|
// disable entering new line - see https://github.com/ckeditor/ckeditor5/issues/9422
|
|
|
|
|
data.preventDefault();
|
|
|
|
|
event.stop();
|
|
|
|
|
},
|
|
|
|
|
{ priority: "high" }
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (disableSpellcheck) {
|
|
|
|
|
const documentRoot = textEditor.editing.view.document.getRoot();
|
|
|
|
|
if (documentRoot) {
|
|
|
|
|
textEditor.editing.view.change((writer) => writer.setAttribute("spellcheck", "false", documentRoot));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (onChange) {
|
2025-08-23 12:55:48 +03:00
|
|
|
textEditor.model.document.on("change:data", () => {
|
|
|
|
|
onChange(textEditor.getData())
|
|
|
|
|
});
|
2025-08-23 12:05:03 +03:00
|
|
|
}
|
2025-08-23 12:39:49 +03:00
|
|
|
|
|
|
|
|
if (currentValue) {
|
|
|
|
|
textEditor.setData(currentValue);
|
|
|
|
|
}
|
2025-08-23 12:05:03 +03:00
|
|
|
});
|
|
|
|
|
}, []);
|
|
|
|
|
|
2025-08-23 12:39:49 +03:00
|
|
|
useEffect(() => {
|
|
|
|
|
if (!textEditorRef.current) return;
|
|
|
|
|
textEditorRef.current.setData(currentValue ?? "");
|
|
|
|
|
}, [ currentValue ]);
|
|
|
|
|
|
2025-08-23 12:05:03 +03:00
|
|
|
return (
|
|
|
|
|
<div
|
|
|
|
|
ref={editorContainerRef}
|
|
|
|
|
className={className}
|
|
|
|
|
tabIndex={tabIndex}
|
2025-08-23 12:55:48 +03:00
|
|
|
onClick={(e) => {
|
2025-08-23 12:31:54 +03:00
|
|
|
if (onClick) {
|
|
|
|
|
const pos = textEditorRef.current?.model.document.selection.getFirstPosition();
|
2025-08-23 12:55:48 +03:00
|
|
|
onClick(e, pos);
|
2025-08-23 12:31:54 +03:00
|
|
|
}
|
|
|
|
|
}}
|
2025-08-23 12:05:03 +03:00
|
|
|
/>
|
|
|
|
|
)
|
|
|
|
|
}
|