@gcforms/editor 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +18 -0
- package/src/Editor.tsx +126 -0
- package/src/ToolTip.tsx +26 -0
- package/src/config.ts +16 -0
- package/src/hooks/useEditorFocus.tsx +38 -0
- package/src/icons/BoldIcon.tsx +15 -0
- package/src/icons/BulletListIcon.tsx +15 -0
- package/src/icons/EditIcon.tsx +14 -0
- package/src/icons/H2Icon.tsx +15 -0
- package/src/icons/H3Icon.tsx +15 -0
- package/src/icons/ItalicIcon.tsx +15 -0
- package/src/icons/LinkIcon.tsx +15 -0
- package/src/icons/NumberedListIcon.tsx +15 -0
- package/src/index.ts +1 -0
- package/src/plugins/FloatingLinkEditorPlugin/index.css +71 -0
- package/src/plugins/FloatingLinkEditorPlugin/index.tsx +326 -0
- package/src/plugins/ListMaxIndentPlugin/index.tsx +81 -0
- package/src/plugins/TabControlPlugin/index.tsx +28 -0
- package/src/plugins/ToolbarPlugin/index.tsx +395 -0
- package/src/plugins/ToolbarPlugin/styles.css +24 -0
- package/src/plugins/TreeViewPlugin/index.tsx +28 -0
- package/src/styles.css +11 -0
- package/src/tests/Editor.cy.tsx +84 -0
- package/src/tests/Editor.test.js +113 -0
- package/src/themes/BasicTheme.ts +69 -0
- package/src/ui/ContentEditable.css +44 -0
- package/src/ui/ContentEditable.tsx +39 -0
- package/src/utils/getSelectedNode.ts +25 -0
- package/src/utils/setFloatingElemPositionForLinkEditor.ts +46 -0
- package/src/utils/url.ts +32 -0
package/package.json
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@gcforms/editor",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"author": "Canadian Digital Service",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"publishConfig": {
|
|
7
|
+
"access": "public"
|
|
8
|
+
},
|
|
9
|
+
"packageManager": "yarn@4.6.0",
|
|
10
|
+
"dependencies": {
|
|
11
|
+
"@lexical/react": "^0.28.0",
|
|
12
|
+
"lexical": "^0.28.0"
|
|
13
|
+
},
|
|
14
|
+
"peerDependencies": {
|
|
15
|
+
"react": "^19.0.0",
|
|
16
|
+
"react-dom": "^19.0.0"
|
|
17
|
+
}
|
|
18
|
+
}
|
package/src/Editor.tsx
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import React, { useId, useState } from "react";
|
|
3
|
+
import { LexicalComposer } from "@lexical/react/LexicalComposer";
|
|
4
|
+
import { RichTextPlugin } from "@lexical/react/LexicalRichTextPlugin";
|
|
5
|
+
import { LexicalErrorBoundary } from "@lexical/react/LexicalErrorBoundary";
|
|
6
|
+
import { HistoryPlugin } from "@lexical/react/LexicalHistoryPlugin";
|
|
7
|
+
import { LinkPlugin } from "@lexical/react/LexicalLinkPlugin";
|
|
8
|
+
import { ListPlugin } from "@lexical/react/LexicalListPlugin";
|
|
9
|
+
import ContentEditable from "./ui/ContentEditable";
|
|
10
|
+
import TabControlPlugin from "./plugins/TabControlPlugin";
|
|
11
|
+
import ToolbarPlugin from "./plugins/ToolbarPlugin";
|
|
12
|
+
import TreeViewPlugin from "./plugins/TreeViewPlugin";
|
|
13
|
+
import ListMaxIndentLevelPlugin from "./plugins/ListMaxIndentPlugin";
|
|
14
|
+
import FloatingLinkEditorPlugin from "./plugins/FloatingLinkEditorPlugin";
|
|
15
|
+
import { editorConfig } from "./config";
|
|
16
|
+
import { $createParagraphNode, $getRoot } from "lexical";
|
|
17
|
+
import {
|
|
18
|
+
$convertFromMarkdownString,
|
|
19
|
+
$convertToMarkdownString,
|
|
20
|
+
TRANSFORMERS,
|
|
21
|
+
} from "@lexical/markdown";
|
|
22
|
+
import { OnChangePlugin } from "@lexical/react/LexicalOnChangePlugin";
|
|
23
|
+
import "./styles.css";
|
|
24
|
+
|
|
25
|
+
interface EditorProps {
|
|
26
|
+
id?: string;
|
|
27
|
+
content?: string;
|
|
28
|
+
showTreeview?: boolean;
|
|
29
|
+
ariaLabel?: string;
|
|
30
|
+
ariaDescribedBy?: string;
|
|
31
|
+
lang?: string;
|
|
32
|
+
|
|
33
|
+
onChange?(...args: unknown[]): unknown;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export const Editor = ({
|
|
37
|
+
id,
|
|
38
|
+
content = "",
|
|
39
|
+
showTreeview = false,
|
|
40
|
+
ariaLabel = "AriaLabel",
|
|
41
|
+
ariaDescribedBy = "AriaDescribedBy",
|
|
42
|
+
onChange,
|
|
43
|
+
}: EditorProps) => {
|
|
44
|
+
const [floatingAnchorElem, setFloatingAnchorElem] = useState<HTMLDivElement | null>(null);
|
|
45
|
+
const [isLinkEditMode, setIsLinkEditMode] = useState<boolean>(false);
|
|
46
|
+
|
|
47
|
+
const randomId = useId();
|
|
48
|
+
const editorId = id || `editor-${randomId}`;
|
|
49
|
+
|
|
50
|
+
const onRef = (_floatingAnchorElem: HTMLDivElement) => {
|
|
51
|
+
if (_floatingAnchorElem !== null) {
|
|
52
|
+
setFloatingAnchorElem(_floatingAnchorElem);
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
return (
|
|
57
|
+
<LexicalComposer
|
|
58
|
+
initialConfig={{
|
|
59
|
+
...editorConfig,
|
|
60
|
+
editorState: () => {
|
|
61
|
+
// @TODO: how to make this configurable
|
|
62
|
+
if (!content) {
|
|
63
|
+
const root = $getRoot();
|
|
64
|
+
const paragraphNode = $createParagraphNode();
|
|
65
|
+
root.append(paragraphNode);
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
$convertFromMarkdownString(content, TRANSFORMERS);
|
|
69
|
+
},
|
|
70
|
+
}}
|
|
71
|
+
>
|
|
72
|
+
<div>
|
|
73
|
+
<ToolbarPlugin editorId={id || ""} />
|
|
74
|
+
|
|
75
|
+
<RichTextPlugin
|
|
76
|
+
contentEditable={
|
|
77
|
+
<div className="gc-editor-container" ref={onRef}>
|
|
78
|
+
<ContentEditable
|
|
79
|
+
placeholder={""}
|
|
80
|
+
id={editorId}
|
|
81
|
+
ariaLabel={ariaLabel && ariaLabel}
|
|
82
|
+
ariaDescribedBy={ariaDescribedBy && ariaDescribedBy}
|
|
83
|
+
/>
|
|
84
|
+
</div>
|
|
85
|
+
}
|
|
86
|
+
ErrorBoundary={LexicalErrorBoundary}
|
|
87
|
+
/>
|
|
88
|
+
<HistoryPlugin />
|
|
89
|
+
{showTreeview && <TreeViewPlugin />}
|
|
90
|
+
<OnChangePlugin // @TODO: make the following configurable
|
|
91
|
+
onChange={(editorState) => {
|
|
92
|
+
editorState.read(() => {
|
|
93
|
+
// Read the contents of the EditorState here.
|
|
94
|
+
const markdown = $convertToMarkdownString(TRANSFORMERS);
|
|
95
|
+
|
|
96
|
+
// Add two spaces to previous line for linebreaks (this is not handled properly by $convertToMarkdownString)
|
|
97
|
+
const lines = markdown.split("\n");
|
|
98
|
+
lines.forEach((currentLine, i) => {
|
|
99
|
+
if (i > 0) {
|
|
100
|
+
const previousLine = lines[i - 1];
|
|
101
|
+
if (previousLine !== "" && currentLine !== "") {
|
|
102
|
+
lines[i - 1] = previousLine.trim() + " ";
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
onChange && onChange(lines.join("\n"));
|
|
107
|
+
});
|
|
108
|
+
}}
|
|
109
|
+
/>
|
|
110
|
+
<ListPlugin />
|
|
111
|
+
<LinkPlugin />
|
|
112
|
+
<TabControlPlugin />
|
|
113
|
+
<ListMaxIndentLevelPlugin maxDepth={5} />
|
|
114
|
+
{floatingAnchorElem && (
|
|
115
|
+
<>
|
|
116
|
+
<FloatingLinkEditorPlugin
|
|
117
|
+
anchorElem={floatingAnchorElem}
|
|
118
|
+
isLinkEditMode={isLinkEditMode}
|
|
119
|
+
setIsLinkEditMode={setIsLinkEditMode}
|
|
120
|
+
/>
|
|
121
|
+
</>
|
|
122
|
+
)}
|
|
123
|
+
</div>
|
|
124
|
+
</LexicalComposer>
|
|
125
|
+
);
|
|
126
|
+
};
|
package/src/ToolTip.tsx
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import React, { useId, Children } from "react";
|
|
3
|
+
|
|
4
|
+
export const ToolTip = ({ children, text }: { children: React.ReactElement; text: string }) => {
|
|
5
|
+
const id = `tooltip-${useId()}`;
|
|
6
|
+
|
|
7
|
+
return (
|
|
8
|
+
<span className="relative">
|
|
9
|
+
{Children.map(children, (child) => {
|
|
10
|
+
return React.cloneElement(child, {
|
|
11
|
+
// @ts-expect-error -- TODO: fix this
|
|
12
|
+
...child.props,
|
|
13
|
+
// @ts-expect-error -- TODO: fix this
|
|
14
|
+
className: child.props.className + " peer",
|
|
15
|
+
"aria-labelledby": id,
|
|
16
|
+
});
|
|
17
|
+
})}
|
|
18
|
+
<span
|
|
19
|
+
id={id}
|
|
20
|
+
className={`invisible absolute -left-14 -top-10 z-[1000] w-36 whitespace-nowrap rounded bg-gray-800 p-1 text-center text-sm text-white after:absolute after:left-1/2 after:top-full after:-translate-x-1/2 after:border-8 after:border-x-transparent after:border-b-transparent after:border-t-gray-700 after:content-[''] peer-hover:visible peer-focus:visible`}
|
|
21
|
+
>
|
|
22
|
+
{text}
|
|
23
|
+
</span>
|
|
24
|
+
</span>
|
|
25
|
+
);
|
|
26
|
+
};
|
package/src/config.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { HeadingNode } from "@lexical/rich-text";
|
|
2
|
+
import { LinkNode } from "@lexical/link";
|
|
3
|
+
import { ListItemNode, ListNode } from "@lexical/list";
|
|
4
|
+
import basicTheme from "./themes/BasicTheme";
|
|
5
|
+
|
|
6
|
+
export const editorConfig = {
|
|
7
|
+
namespace: "FormBuilder",
|
|
8
|
+
// The editor theme
|
|
9
|
+
theme: basicTheme,
|
|
10
|
+
// Handling of errors during update
|
|
11
|
+
onError(error: Error) {
|
|
12
|
+
throw error;
|
|
13
|
+
},
|
|
14
|
+
// Any custom nodes go here
|
|
15
|
+
nodes: [HeadingNode, LinkNode, ListItemNode, ListNode],
|
|
16
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext";
|
|
3
|
+
import { BLUR_COMMAND, COMMAND_PRIORITY_LOW, FOCUS_COMMAND } from "lexical";
|
|
4
|
+
import { useEffect, useState } from "react";
|
|
5
|
+
|
|
6
|
+
export const useEditorFocus = () => {
|
|
7
|
+
const [editor] = useLexicalComposerContext();
|
|
8
|
+
// Possibly use useRef for synchronous updates but no re-rendering effect
|
|
9
|
+
const [hasFocus, setFocus] = useState(false);
|
|
10
|
+
|
|
11
|
+
useEffect(
|
|
12
|
+
() =>
|
|
13
|
+
editor.registerCommand(
|
|
14
|
+
BLUR_COMMAND,
|
|
15
|
+
() => {
|
|
16
|
+
setFocus(false);
|
|
17
|
+
return false;
|
|
18
|
+
},
|
|
19
|
+
COMMAND_PRIORITY_LOW
|
|
20
|
+
),
|
|
21
|
+
[editor]
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
useEffect(
|
|
25
|
+
() =>
|
|
26
|
+
editor.registerCommand(
|
|
27
|
+
FOCUS_COMMAND,
|
|
28
|
+
() => {
|
|
29
|
+
setFocus(true);
|
|
30
|
+
return false;
|
|
31
|
+
},
|
|
32
|
+
COMMAND_PRIORITY_LOW
|
|
33
|
+
),
|
|
34
|
+
[editor]
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
return hasFocus;
|
|
38
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export const BoldIcon = ({ className, title }: { className?: string; title?: string }) => (
|
|
2
|
+
<svg
|
|
3
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
4
|
+
height="20"
|
|
5
|
+
width="20"
|
|
6
|
+
className={className}
|
|
7
|
+
viewBox="0 0 20 20"
|
|
8
|
+
focusable="false"
|
|
9
|
+
aria-hidden={title ? false : true}
|
|
10
|
+
role={title ? "img" : "presentation"}
|
|
11
|
+
>
|
|
12
|
+
{title && <title>{title}</title>}
|
|
13
|
+
<path d="M15.6 10.79c.97-.67 1.65-1.77 1.65-2.79 0-2.26-1.75-4-4-4H7v14h7.04c2.09 0 3.71-1.7 3.71-3.79 0-1.52-.86-2.82-2.15-3.42zM10 6.5h3c.83 0 1.5.67 1.5 1.5s-.67 1.5-1.5 1.5h-3v-3zm3.5 9H10v-3h3.5c.83 0 1.5.67 1.5 1.5s-.67 1.5-1.5 1.5z"></path>
|
|
14
|
+
</svg>
|
|
15
|
+
);
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export const BulletListIcon = ({ className, title }: { className?: string; title?: string }) => (
|
|
2
|
+
<svg
|
|
3
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
4
|
+
height="20"
|
|
5
|
+
width="20"
|
|
6
|
+
className={className}
|
|
7
|
+
viewBox="0 0 20 20"
|
|
8
|
+
focusable="false"
|
|
9
|
+
aria-hidden={title ? false : true}
|
|
10
|
+
role={title ? "img" : "presentation"}
|
|
11
|
+
>
|
|
12
|
+
{title && <title>{title}</title>}
|
|
13
|
+
<path d="M4 10.5c-.83 0-1.5.67-1.5 1.5s.67 1.5 1.5 1.5 1.5-.67 1.5-1.5-.67-1.5-1.5-1.5zm0-6c-.83 0-1.5.67-1.5 1.5S3.17 7.5 4 7.5 5.5 6.83 5.5 6 4.83 4.5 4 4.5zm0 12c-.83 0-1.5.68-1.5 1.5s.68 1.5 1.5 1.5 1.5-.68 1.5-1.5-.67-1.5-1.5-1.5zM7 19h14v-2H7v2zm0-6h14v-2H7v2zm0-8v2h14V5H7z"></path>
|
|
14
|
+
</svg>
|
|
15
|
+
);
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export const EditIcon = ({ className, title }: { className?: string; title?: string }) => (
|
|
2
|
+
<svg
|
|
3
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
4
|
+
height="21"
|
|
5
|
+
width="21"
|
|
6
|
+
className={className}
|
|
7
|
+
focusable="false"
|
|
8
|
+
aria-hidden={title ? false : true}
|
|
9
|
+
role={title ? "img" : "presentation"}
|
|
10
|
+
>
|
|
11
|
+
{title && <title>{title}</title>}
|
|
12
|
+
<path d="M.582 20.418v-4.256l5.293-5.294L.991 5.957a1.654 1.654 0 0 1-.368-.546c-.082-.2-.123-.4-.123-.6 0-.2.04-.4.123-.6.082-.2.204-.382.368-.546l2.456-2.483c.163-.164.345-.286.546-.368.2-.082.4-.123.6-.123a1.678 1.678 0 0 1 1.173.491l4.911 4.911L16.052.718a.667.667 0 0 1 .273-.177c.091-.027.191-.041.3-.041.11 0 .21.014.3.04a.667.667 0 0 1 .273.178l3.084 3.083c.09.091.15.182.177.273.027.091.041.191.041.3 0 .11-.014.21-.04.3a.667.667 0 0 1-.178.273l-5.376 5.376 4.912 4.911c.164.164.286.35.368.56.082.209.123.413.123.613s-.04.4-.123.6c-.082.2-.204.383-.368.546l-2.456 2.429a1.653 1.653 0 0 1-.545.368c-.2.082-.4.123-.6.123-.2 0-.4-.041-.601-.123a1.653 1.653 0 0 1-.546-.368l-4.911-4.884-5.32 5.32H.581ZM7.048 9.695 9.504 7.24 7.512 5.248l-1.31 1.31L5.058 5.41l1.31-1.31L4.62 2.356 2.164 4.811l4.884 4.884Zm9.114 9.14 2.455-2.455-1.746-1.746-1.31 1.31-1.146-1.147 1.31-1.31-1.992-1.991-2.455 2.456 4.884 4.884Zm-13.943-.054h1.91L15.452 7.458l-1.91-1.91L2.22 16.87v1.91Zm14.38-12.47 1.91-1.91-1.91-1.91-1.91 1.91 1.91 1.91Z" />
|
|
13
|
+
</svg>
|
|
14
|
+
);
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export const H2Icon = ({ className, title }: { className?: string; title?: string }) => (
|
|
2
|
+
<svg
|
|
3
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
4
|
+
height="20"
|
|
5
|
+
width="20"
|
|
6
|
+
className={className}
|
|
7
|
+
viewBox="0 0 20 20"
|
|
8
|
+
focusable="false"
|
|
9
|
+
aria-hidden={title ? false : true}
|
|
10
|
+
role={title ? "img" : "presentation"}
|
|
11
|
+
>
|
|
12
|
+
{title && <title>{title}</title>}
|
|
13
|
+
<path d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-4 8a2 2 0 0 1-2 2h-2v2h4v2H9v-4a2 2 0 0 1 2-2h2V9H9V7h4a2 2 0 0 1 2 2v2z"></path>
|
|
14
|
+
</svg>
|
|
15
|
+
);
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export const H3Icon = ({ className, title }: { className?: string; title?: string }) => (
|
|
2
|
+
<svg
|
|
3
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
4
|
+
height="20"
|
|
5
|
+
width="20"
|
|
6
|
+
className={className}
|
|
7
|
+
viewBox="0 0 20 20"
|
|
8
|
+
focusable="false"
|
|
9
|
+
aria-hidden={title ? false : true}
|
|
10
|
+
role={title ? "img" : "presentation"}
|
|
11
|
+
>
|
|
12
|
+
{title && <title>{title}</title>}
|
|
13
|
+
<path d="M19.01 3h-14c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-4 7.5c0 .83-.67 1.5-1.5 1.5.83 0 1.5.67 1.5 1.5V15a2 2 0 0 1-2 2h-4v-2h4v-2h-2v-2h2V9h-4V7h4a2 2 0 0 1 2 2v1.5z"></path>
|
|
14
|
+
</svg>
|
|
15
|
+
);
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export const ItalicIcon = ({ className, title }: { className?: string; title?: string }) => (
|
|
2
|
+
<svg
|
|
3
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
4
|
+
height="20"
|
|
5
|
+
width="20"
|
|
6
|
+
className={className}
|
|
7
|
+
viewBox="0 0 20 20"
|
|
8
|
+
focusable="false"
|
|
9
|
+
aria-hidden={title ? false : true}
|
|
10
|
+
role={title ? "img" : "presentation"}
|
|
11
|
+
>
|
|
12
|
+
{title && <title>{title}</title>}
|
|
13
|
+
<path d="M10 4v3h2.21l-3.42 8H6v3h8v-3h-2.21l3.42-8H18V4z"></path>
|
|
14
|
+
</svg>
|
|
15
|
+
);
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export const LinkIcon = ({ className, title }: { className?: string; title?: string }) => (
|
|
2
|
+
<svg
|
|
3
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
4
|
+
height="20"
|
|
5
|
+
width="20"
|
|
6
|
+
className={className}
|
|
7
|
+
viewBox="0 0 20 20"
|
|
8
|
+
focusable="false"
|
|
9
|
+
aria-hidden={title ? false : true}
|
|
10
|
+
role={title ? "img" : "presentation"}
|
|
11
|
+
>
|
|
12
|
+
{title && <title>{title}</title>}
|
|
13
|
+
<path d="M9 14H6q-1.667 0-2.833-1.167Q2 11.667 2 10q0-1.667 1.167-2.833Q4.333 6 6 6h3v1.5H6q-1.042 0-1.771.729Q3.5 8.958 3.5 10q0 1.042.729 1.771.729.729 1.771.729h3Zm-2-3.25v-1.5h6v1.5ZM11 14v-1.5h3q1.042 0 1.771-.729.729-.729.729-1.771 0-1.042-.729-1.771Q15.042 7.5 14 7.5h-3V6h3q1.667 0 2.833 1.167Q18 8.333 18 10q0 1.667-1.167 2.833Q15.667 14 14 14Z" />
|
|
14
|
+
</svg>
|
|
15
|
+
);
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export const NumberedListIcon = ({ className, title }: { className?: string; title?: string }) => (
|
|
2
|
+
<svg
|
|
3
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
4
|
+
height="20"
|
|
5
|
+
width="20"
|
|
6
|
+
className={className}
|
|
7
|
+
viewBox="0 0 20 20"
|
|
8
|
+
focusable="false"
|
|
9
|
+
aria-hidden={title ? false : true}
|
|
10
|
+
role={title ? "img" : "presentation"}
|
|
11
|
+
>
|
|
12
|
+
{title && <title>{title}</title>}
|
|
13
|
+
<path d="M3 20v-1h2v-.5H4v-1h1V17H3v-1h3v4Zm5-1v-2h13v2Zm-5-5v-.9L4.8 11H3v-1h3v.9L4.2 13H6v1Zm5-1v-2h13v2ZM4 8V5H3V4h2v4Zm4-1V5h13v2Z" />
|
|
14
|
+
</svg>
|
|
15
|
+
);
|
package/src/index.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { Editor } from "./Editor";
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/*********************
|
|
2
|
+
* link editor *
|
|
3
|
+
*********************/
|
|
4
|
+
.gc-link-editor {
|
|
5
|
+
position: absolute;
|
|
6
|
+
top: 0;
|
|
7
|
+
left: 0;
|
|
8
|
+
z-index: 10;
|
|
9
|
+
max-width: 400px;
|
|
10
|
+
width: 100%;
|
|
11
|
+
background-color: #fff;
|
|
12
|
+
border: 2px solid black;
|
|
13
|
+
border-radius: 8px;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.gc-link-editor button.active,
|
|
17
|
+
.toolbar button.active {
|
|
18
|
+
background-color: #fff;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.gc-link-editor .link-input {
|
|
22
|
+
display: inline-block;
|
|
23
|
+
width: calc(100% - 24px);
|
|
24
|
+
box-sizing: border-box;
|
|
25
|
+
margin: 8px 12px;
|
|
26
|
+
padding: 8px 12px;
|
|
27
|
+
border-radius: 8px;
|
|
28
|
+
background-color: #eee;
|
|
29
|
+
font-size: 15px;
|
|
30
|
+
color: #050505;
|
|
31
|
+
border: 0;
|
|
32
|
+
outline: 0;
|
|
33
|
+
position: relative;
|
|
34
|
+
font-family: inherit;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.gc-link-editor button.link-edit {
|
|
38
|
+
background-size: 16px;
|
|
39
|
+
background-position: center;
|
|
40
|
+
background-repeat: no-repeat;
|
|
41
|
+
width: 35px;
|
|
42
|
+
vertical-align: -0.25em;
|
|
43
|
+
position: absolute;
|
|
44
|
+
right: 0;
|
|
45
|
+
top: 0;
|
|
46
|
+
bottom: 0;
|
|
47
|
+
cursor: pointer;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.gc-link-editor .link-input span {
|
|
51
|
+
color: #216fdb;
|
|
52
|
+
text-decoration: none;
|
|
53
|
+
display: block;
|
|
54
|
+
white-space: nowrap;
|
|
55
|
+
overflow: hidden;
|
|
56
|
+
margin-right: 30px;
|
|
57
|
+
text-overflow: ellipsis;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.gc-link-editor .font-size-wrapper,
|
|
61
|
+
.gc-link-editor .font-family-wrapper {
|
|
62
|
+
display: flex;
|
|
63
|
+
margin: 0 4px;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.gc-link-editor select {
|
|
67
|
+
padding: 6px;
|
|
68
|
+
border: none;
|
|
69
|
+
background-color: #00000013;
|
|
70
|
+
border-radius: 4px;
|
|
71
|
+
}
|