]> BookStack Code Mirror - bookstack/blob - resources/js/wysiwyg/utils/formats.ts
Lexical: Started adding editor shortcuts
[bookstack] / resources / js / wysiwyg / utils / formats.ts
1 import {$isQuoteNode, HeadingNode, HeadingTagType} from "@lexical/rich-text";
2 import {$getSelection, LexicalEditor, LexicalNode} from "lexical";
3 import {
4     $getBlockElementNodesInSelection,
5     $getNodeFromSelection,
6     $insertNewBlockNodeAtSelection,
7     $toggleSelectionBlockNodeType,
8     getLastSelection
9 } from "./selection";
10 import {$createCustomHeadingNode, $isCustomHeadingNode} from "../nodes/custom-heading";
11 import {$createCustomParagraphNode, $isCustomParagraphNode} from "../nodes/custom-paragraph";
12 import {$createCustomQuoteNode} from "../nodes/custom-quote";
13 import {$createCodeBlockNode, $isCodeBlockNode, $openCodeEditorForNode, CodeBlockNode} from "../nodes/code-block";
14 import {$createCalloutNode, $isCalloutNode, CalloutCategory} from "../nodes/callout";
15
16 const $isHeaderNodeOfTag = (node: LexicalNode | null | undefined, tag: HeadingTagType) => {
17     return $isCustomHeadingNode(node) && (node as HeadingNode).getTag() === tag;
18 };
19
20 export function toggleSelectionAsHeading(editor: LexicalEditor, tag: HeadingTagType) {
21     editor.update(() => {
22         $toggleSelectionBlockNodeType(
23             (node) => $isHeaderNodeOfTag(node, tag),
24             () => $createCustomHeadingNode(tag),
25         )
26     });
27 }
28
29 export function toggleSelectionAsParagraph(editor: LexicalEditor) {
30     editor.update(() => {
31         $toggleSelectionBlockNodeType($isCustomParagraphNode, $createCustomParagraphNode);
32     });
33 }
34
35 export function toggleSelectionAsBlockquote(editor: LexicalEditor) {
36     editor.update(() => {
37         $toggleSelectionBlockNodeType($isQuoteNode, $createCustomQuoteNode);
38     });
39 }
40
41 export function formatCodeBlock(editor: LexicalEditor) {
42     editor.getEditorState().read(() => {
43         const selection = $getSelection();
44         const lastSelection = getLastSelection(editor);
45         const codeBlock = $getNodeFromSelection(lastSelection, $isCodeBlockNode) as (CodeBlockNode | null);
46         if (codeBlock === null) {
47             editor.update(() => {
48                 const codeBlock = $createCodeBlockNode();
49                 codeBlock.setCode(selection?.getTextContent() || '');
50                 $insertNewBlockNodeAtSelection(codeBlock, true);
51                 $openCodeEditorForNode(editor, codeBlock);
52                 codeBlock.selectStart();
53             });
54         } else {
55             $openCodeEditorForNode(editor, codeBlock);
56         }
57     });
58 }
59
60 export function cycleSelectionCalloutFormats(editor: LexicalEditor) {
61     editor.update(() => {
62         const selection = $getSelection();
63         const blocks = $getBlockElementNodesInSelection(selection);
64
65         let created = false;
66         for (const block of blocks) {
67             if (!$isCalloutNode(block)) {
68                 block.replace($createCalloutNode('info'), true);
69                 created = true;
70             }
71         }
72
73         if (created) {
74             return;
75         }
76
77         const types: CalloutCategory[] = ['info', 'warning', 'danger', 'success'];
78         for (const block of blocks) {
79             if ($isCalloutNode(block)) {
80                 const type = block.getCategory();
81                 const typeIndex = types.indexOf(type);
82                 const newIndex = (typeIndex + 1) % types.length;
83                 const newType = types[newIndex];
84                 block.setCategory(newType);
85             }
86         }
87     });
88 }