feat(notebook): support text direction controls (#6293)

This commit is contained in:
Artur authored and GitHub committed 2026-07-12 07:58:02 +02:00
1 parent c56b91570b
commit d44bc06a93
5 files changed
+124 -20

No files matched your search

@@ -39,6 +39,8 @@ import {
IconRowRemove,
IconTableOff,
IconTablePlus,
IconTextDirectionLtr,
IconTextDirectionRtl,
IconX,
} from "@tabler/icons-react";
import { Color } from "@tiptap/extension-color";
@@ -66,6 +68,7 @@ import { useI18n, useScopedI18n } from "@homarr/translation/client";
import type { TablerIcon } from "@homarr/ui";
import type { WidgetComponentProps } from "../definition";
import { NotebookTextDirection, setTextDirection } from "./text-direction";
import "@mantine/tiptap/styles.css";
import "./notebook.css";
@@ -171,6 +174,7 @@ export function Notebook({ options, setOptions, isEditMode, boardId, itemId }: W
}),
TaskList.configure({ itemTypeName: "taskItem" }),
TextAlign.configure({ types: ["heading", "paragraph"] }),
NotebookTextDirection,
TextStyle,
],
shouldRerenderOnTransaction: true,
@@ -347,6 +351,22 @@ export function Notebook({ options, setOptions, isEditMode, boardId, itemId }: W
position: t("widget.notebook.align.right"),
})}
/>
<RichTextEditor.Control
title={tControls("directionLtr")}
aria-label={tControls("directionLtr")}
active={editor?.isActive({ dir: "ltr" })}
onClick={() => editor && setTextDirection(editor, "ltr").run()}
>
<IconTextDirectionLtr {...controlIconProps} />
</RichTextEditor.Control>
<RichTextEditor.Control
title={tControls("directionRtl")}
aria-label={tControls("directionRtl")}
active={editor?.isActive({ dir: "rtl" })}
onClick={() => editor && setTextDirection(editor, "rtl").run()}
>
<IconTextDirectionRtl {...controlIconProps} />
</RichTextEditor.Control>
</RichTextEditor.ControlsGroup>
<RichTextEditor.ControlsGroup>
@@ -0,0 +1,34 @@
import { Editor } from "@tiptap/react";
import { StarterKit } from "@tiptap/starter-kit";
import { describe, expect, it } from "vitest";
import { NotebookTextDirection, setTextDirection } from "./text-direction";
describe("notebook text direction", () => {
it("persists right-to-left direction in the notebook HTML", () => {
const editor = new Editor({
content: "<p>יש לי 16GB</p>",
extensions: [StarterKit, NotebookTextDirection],
});
setTextDirection(editor, "rtl").run();
expect(editor.getHTML()).toBe('<p dir="rtl">יש לי 16GB</p>');
editor.destroy();
});
it("reads and changes an existing direction attribute", () => {
const editor = new Editor({
content: '<h2 dir="rtl">Heading</h2>',
extensions: [StarterKit, NotebookTextDirection],
});
expect(editor.getHTML()).toBe('<h2 dir="rtl">Heading</h2>');
editor.commands.setTextSelection(1);
setTextDirection(editor, "ltr").run();
expect(editor.getHTML()).toContain('<h2 dir="ltr">Heading</h2>');
editor.destroy();
});
});
@@ -0,0 +1,28 @@
import { Extension } from "@tiptap/react";
import type { Editor } from "@tiptap/react";
export type TextDirection = "ltr" | "rtl";
export const NotebookTextDirection = Extension.create({
name: "notebookTextDirection",
addGlobalAttributes() {
return [
{
types: ["heading", "paragraph"],
attributes: {
dir: {
default: null,
parseHTML: (element) => element.getAttribute("dir"),
renderHTML: ({ dir }: { dir?: TextDirection }) => (dir ? { dir } : {}),
},
},
},
];
},
});
export function setTextDirection(editor: Editor, direction: TextDirection) {
const nodeType = editor.isActive("heading") ? "heading" : "paragraph";
return editor.chain().focus().updateAttributes(nodeType, { dir: direction });
}