]> BookStack Code Mirror - bookstack/blob - resources/js/wysiwyg/services/auto-links.ts
Images: Added testing to cover animated avif handling
[bookstack] / resources / js / wysiwyg / services / auto-links.ts
1 import {
2     $getSelection, BaseSelection,
3     COMMAND_PRIORITY_NORMAL,
4     KEY_ENTER_COMMAND,
5     KEY_SPACE_COMMAND,
6     LexicalEditor,
7     TextNode
8 } from "lexical";
9 import {$getTextNodeFromSelection} from "../utils/selection";
10 import {$createLinkNode, LinkNode} from "@lexical/link";
11
12
13 function isLinkText(text: string): boolean {
14     const lower = text.toLowerCase();
15     if (!lower.startsWith('http')) {
16         return false;
17     }
18
19     const linkRegex = /(http|https):\/\/(\S+)\.\S+$/;
20     return linkRegex.test(text);
21 }
22
23
24 function handlePotentialLinkEvent(node: TextNode, selection: BaseSelection, editor: LexicalEditor) {
25     const selectionRange = selection.getStartEndPoints();
26     if (!selectionRange) {
27         return;
28     }
29
30     const cursorPoint = selectionRange[0].offset;
31     const nodeText = node.getTextContent();
32     const rTrimText = nodeText.slice(0, cursorPoint);
33     const priorSpaceIndex = rTrimText.lastIndexOf(' ');
34     const startIndex = priorSpaceIndex + 1;
35     const textSegment = nodeText.slice(startIndex, cursorPoint);
36
37     if (!isLinkText(textSegment)) {
38         return;
39     }
40
41     editor.update(() => {
42         const linkNode: LinkNode = $createLinkNode(textSegment);
43         linkNode.append(new TextNode(textSegment));
44
45         const splits = node.splitText(startIndex, cursorPoint);
46         const targetIndex = startIndex > 0 ? 1 : 0;
47         const targetText = splits[targetIndex];
48         if (targetText) {
49             targetText.replace(linkNode);
50         }
51     });
52 }
53
54
55 export function registerAutoLinks(editor: LexicalEditor): () => void {
56
57     const handler = (payload: KeyboardEvent): boolean => {
58         const selection = $getSelection();
59         const textNode = $getTextNodeFromSelection(selection);
60         if (textNode && selection) {
61             handlePotentialLinkEvent(textNode, selection, editor);
62         }
63
64         return false;
65     };
66
67     const unregisterSpace = editor.registerCommand(KEY_SPACE_COMMAND, handler, COMMAND_PRIORITY_NORMAL);
68     const unregisterEnter = editor.registerCommand(KEY_ENTER_COMMAND, handler, COMMAND_PRIORITY_NORMAL);
69
70     return (): void => {
71         unregisterSpace();
72         unregisterEnter();
73     };
74 }