]> BookStack Code Mirror - bookstack/blob - resources/js/wysiwyg/ui/buttons.ts
Lexical: Added a range of format buttons
[bookstack] / resources / js / wysiwyg / ui / buttons.ts
1 import {EditorButtonDefinition} from "./editor-button";
2 import {
3     $createParagraphNode,
4     $isParagraphNode,
5     BaseSelection, FORMAT_TEXT_COMMAND,
6     LexicalEditor,
7     LexicalNode,
8     REDO_COMMAND, TextFormatType,
9     UNDO_COMMAND
10 } from "lexical";
11 import {selectionContainsNodeType, selectionContainsTextFormat, toggleSelectionBlockNodeType} from "../helpers";
12 import {$createCalloutNode, $isCalloutNodeOfCategory, CalloutCategory} from "../nodes/callout";
13 import {
14     $createHeadingNode,
15     $createQuoteNode,
16     $isHeadingNode,
17     $isQuoteNode,
18     HeadingNode,
19     HeadingTagType
20 } from "@lexical/rich-text";
21
22 export const undoButton: EditorButtonDefinition = {
23     label: 'Undo',
24     action(editor: LexicalEditor) {
25         editor.dispatchCommand(UNDO_COMMAND);
26     },
27     isActive(selection: BaseSelection|null): boolean {
28         return false;
29     }
30 }
31
32 export const redoButton: EditorButtonDefinition = {
33     label: 'Redo',
34     action(editor: LexicalEditor) {
35         editor.dispatchCommand(REDO_COMMAND);
36     },
37     isActive(selection: BaseSelection|null): boolean {
38         return false;
39     }
40 }
41
42 function buildCalloutButton(category: CalloutCategory, name: string): EditorButtonDefinition {
43     return {
44         label: `${name} Callout`,
45         action(editor: LexicalEditor) {
46             toggleSelectionBlockNodeType(
47                 editor,
48                 (node) => $isCalloutNodeOfCategory(node, category),
49                 () => $createCalloutNode(category),
50             )
51         },
52         isActive(selection: BaseSelection|null): boolean {
53             return selectionContainsNodeType(selection, (node) => $isCalloutNodeOfCategory(node, category));
54         }
55     };
56 }
57
58 export const infoCalloutButton: EditorButtonDefinition = buildCalloutButton('info', 'Info');
59 export const dangerCalloutButton: EditorButtonDefinition = buildCalloutButton('danger', 'Danger');
60 export const warningCalloutButton: EditorButtonDefinition = buildCalloutButton('warning', 'Warning');
61 export const successCalloutButton: EditorButtonDefinition = buildCalloutButton('success', 'Success');
62
63 const isHeaderNodeOfTag = (node: LexicalNode | null | undefined, tag: HeadingTagType) => {
64       return $isHeadingNode(node) && (node as HeadingNode).getTag() === tag;
65 };
66
67 function buildHeaderButton(tag: HeadingTagType, name: string): EditorButtonDefinition {
68     return {
69         label: name,
70         action(editor: LexicalEditor) {
71             toggleSelectionBlockNodeType(
72                 editor,
73                 (node) => isHeaderNodeOfTag(node, tag),
74                 () => $createHeadingNode(tag),
75             )
76         },
77         isActive(selection: BaseSelection|null): boolean {
78             return selectionContainsNodeType(selection, (node) => isHeaderNodeOfTag(node, tag));
79         }
80     };
81 }
82
83 export const h2Button: EditorButtonDefinition = buildHeaderButton('h2', 'Large Header');
84 export const h3Button: EditorButtonDefinition = buildHeaderButton('h3', 'Medium Header');
85 export const h4Button: EditorButtonDefinition = buildHeaderButton('h4', 'Small Header');
86 export const h5Button: EditorButtonDefinition = buildHeaderButton('h5', 'Tiny Header');
87
88 export const blockquoteButton: EditorButtonDefinition = {
89     label: 'Blockquote',
90     action(editor: LexicalEditor) {
91         toggleSelectionBlockNodeType(editor, $isQuoteNode, $createQuoteNode);
92     },
93     isActive(selection: BaseSelection|null): boolean {
94         return selectionContainsNodeType(selection, $isQuoteNode);
95     }
96 };
97
98 export const paragraphButton: EditorButtonDefinition = {
99     label: 'Paragraph',
100     action(editor: LexicalEditor) {
101         toggleSelectionBlockNodeType(editor, $isParagraphNode, $createParagraphNode);
102     },
103     isActive(selection: BaseSelection|null): boolean {
104         return selectionContainsNodeType(selection, $isParagraphNode);
105     }
106 }
107
108 function buildFormatButton(label: string, format: TextFormatType): EditorButtonDefinition {
109     return {
110         label: label,
111         action(editor: LexicalEditor) {
112             editor.dispatchCommand(FORMAT_TEXT_COMMAND, format);
113         },
114         isActive(selection: BaseSelection|null): boolean {
115             return selectionContainsTextFormat(selection, format);
116         }
117     };
118 }
119
120 export const boldButton: EditorButtonDefinition = buildFormatButton('Bold', 'bold');
121 export const italicButton: EditorButtonDefinition = buildFormatButton('Italic', 'italic');
122 export const underlineButton: EditorButtonDefinition = buildFormatButton('Underline', 'underline');
123 // Todo - Text color
124 // Todo - Highlight color
125 export const strikethroughButton: EditorButtonDefinition = buildFormatButton('Strikethrough', 'strikethrough');
126 export const superscriptButton: EditorButtonDefinition = buildFormatButton('Superscript', 'superscript');
127 export const subscriptButton: EditorButtonDefinition = buildFormatButton('Subscript', 'subscript');
128 export const codeButton: EditorButtonDefinition = buildFormatButton('Inline Code', 'code');
129 // Todo - Clear formatting