@gcforms/editor 0.0.1 → 1.0.0
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/CHANGELOG.md +15 -0
- package/package.json +5 -5
- package/src/Editor.tsx +93 -74
- package/src/config.ts +6 -3
- package/src/context/LocaleContext.tsx +31 -0
- package/src/context/ToolbarContext.tsx +86 -0
- package/src/hooks/useTranslation.tsx +19 -0
- package/src/i18n/en.json +22 -0
- package/src/i18n/fr.json +22 -0
- package/src/i18n/index.tsx +12 -0
- package/src/icons/CancelIcon.tsx +15 -0
- package/src/icons/CheckIcon.tsx +15 -0
- package/src/icons/DeleteIcon.tsx +15 -0
- package/src/icons/EditIcon.tsx +4 -3
- package/src/icons/LinkIcon.tsx +2 -2
- package/src/plugins/FloatingLinkEditorPlugin/index.css +50 -54
- package/src/plugins/FloatingLinkEditorPlugin/index.tsx +305 -138
- package/src/plugins/MaxLengthPlugin/index.tsx +56 -0
- package/src/plugins/ShortcutsPlugin/index.tsx +78 -0
- package/src/plugins/ShortcutsPlugin/shortcuts.ts +76 -0
- package/src/plugins/ToolbarPlugin/index.tsx +121 -149
- package/src/plugins/ToolbarPlugin/styles.css +4 -6
- package/src/plugins/ToolbarPlugin/utils.ts +172 -0
- package/src/styles.css +8 -8
- package/src/tests/Editor.cy.tsx +5 -3
- package/src/transformers/index.ts +18 -0
- package/src/ui/ContentEditable.css +2 -16
- package/src/ui/ContentEditable.tsx +2 -2
- package/src/ui/ToolTip.css +35 -0
- package/src/{ToolTip.tsx → ui/ToolTip.tsx} +4 -6
- package/src/utils/url.ts +7 -9
- package/src/themes/BasicTheme.ts +0 -69
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [1.0.0] - 2025-04-07
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
|
|
12
|
+
- Refactored and moved wysiwyg editor to internal package
|
|
13
|
+
- Updated to latest Lexical version
|
|
14
|
+
- Replaced Link Editor with latest from Lexical examples
|
|
15
|
+
- Moved Markdown transforms to Transformer
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gcforms/editor",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"author": "Canadian Digital Service",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"publishConfig": {
|
|
@@ -8,11 +8,11 @@
|
|
|
8
8
|
},
|
|
9
9
|
"packageManager": "yarn@4.6.0",
|
|
10
10
|
"dependencies": {
|
|
11
|
-
"@lexical/react": "^0.
|
|
12
|
-
"lexical": "^0.
|
|
11
|
+
"@lexical/react": "^0.30.0",
|
|
12
|
+
"lexical": "^0.30.0"
|
|
13
13
|
},
|
|
14
14
|
"peerDependencies": {
|
|
15
|
-
"react": "^19.
|
|
16
|
-
"react-dom": "^19.
|
|
15
|
+
"react": "^19.1.0",
|
|
16
|
+
"react-dom": "^19.1.0"
|
|
17
17
|
}
|
|
18
18
|
}
|
package/src/Editor.tsx
CHANGED
|
@@ -1,11 +1,18 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import React, { useId, useState } from "react";
|
|
3
|
+
import {
|
|
4
|
+
$convertFromMarkdownString,
|
|
5
|
+
$convertToMarkdownString,
|
|
6
|
+
TRANSFORMERS,
|
|
7
|
+
} from "@lexical/markdown";
|
|
3
8
|
import { LexicalComposer } from "@lexical/react/LexicalComposer";
|
|
4
9
|
import { RichTextPlugin } from "@lexical/react/LexicalRichTextPlugin";
|
|
5
10
|
import { LexicalErrorBoundary } from "@lexical/react/LexicalErrorBoundary";
|
|
6
11
|
import { HistoryPlugin } from "@lexical/react/LexicalHistoryPlugin";
|
|
7
12
|
import { LinkPlugin } from "@lexical/react/LexicalLinkPlugin";
|
|
8
13
|
import { ListPlugin } from "@lexical/react/LexicalListPlugin";
|
|
14
|
+
import { OnChangePlugin } from "@lexical/react/LexicalOnChangePlugin";
|
|
15
|
+
|
|
9
16
|
import ContentEditable from "./ui/ContentEditable";
|
|
10
17
|
import TabControlPlugin from "./plugins/TabControlPlugin";
|
|
11
18
|
import ToolbarPlugin from "./plugins/ToolbarPlugin";
|
|
@@ -13,14 +20,14 @@ import TreeViewPlugin from "./plugins/TreeViewPlugin";
|
|
|
13
20
|
import ListMaxIndentLevelPlugin from "./plugins/ListMaxIndentPlugin";
|
|
14
21
|
import FloatingLinkEditorPlugin from "./plugins/FloatingLinkEditorPlugin";
|
|
15
22
|
import { editorConfig } from "./config";
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
TRANSFORMERS,
|
|
21
|
-
} from "@lexical/markdown";
|
|
22
|
-
import { OnChangePlugin } from "@lexical/react/LexicalOnChangePlugin";
|
|
23
|
+
import { LINE_BREAK_FIX } from "./transformers";
|
|
24
|
+
import { Language } from "./i18n";
|
|
25
|
+
import { LocaleContext } from "./context/LocaleContext";
|
|
26
|
+
|
|
23
27
|
import "./styles.css";
|
|
28
|
+
import { ToolbarContext } from "./context/ToolbarContext";
|
|
29
|
+
import ShortcutsPlugin from "./plugins/ShortcutsPlugin";
|
|
30
|
+
import { MaxLengthPlugin } from "./plugins/MaxLengthPlugin";
|
|
24
31
|
|
|
25
32
|
interface EditorProps {
|
|
26
33
|
id?: string;
|
|
@@ -28,7 +35,10 @@ interface EditorProps {
|
|
|
28
35
|
showTreeview?: boolean;
|
|
29
36
|
ariaLabel?: string;
|
|
30
37
|
ariaDescribedBy?: string;
|
|
31
|
-
|
|
38
|
+
locale?: string;
|
|
39
|
+
contentLocale?: string;
|
|
40
|
+
className?: string;
|
|
41
|
+
maxLength?: number;
|
|
32
42
|
|
|
33
43
|
onChange?(...args: unknown[]): unknown;
|
|
34
44
|
}
|
|
@@ -40,9 +50,16 @@ export const Editor = ({
|
|
|
40
50
|
ariaLabel = "AriaLabel",
|
|
41
51
|
ariaDescribedBy = "AriaDescribedBy",
|
|
42
52
|
onChange,
|
|
53
|
+
locale = "en",
|
|
54
|
+
contentLocale = "en",
|
|
55
|
+
className,
|
|
56
|
+
maxLength,
|
|
43
57
|
}: EditorProps) => {
|
|
58
|
+
contentLocale = contentLocale || locale;
|
|
59
|
+
|
|
44
60
|
const [floatingAnchorElem, setFloatingAnchorElem] = useState<HTMLDivElement | null>(null);
|
|
45
61
|
const [isLinkEditMode, setIsLinkEditMode] = useState<boolean>(false);
|
|
62
|
+
const [contentLength, setContentLength] = useState<number>(0);
|
|
46
63
|
|
|
47
64
|
const randomId = useId();
|
|
48
65
|
const editorId = id || `editor-${randomId}`;
|
|
@@ -54,73 +71,75 @@ export const Editor = ({
|
|
|
54
71
|
};
|
|
55
72
|
|
|
56
73
|
return (
|
|
57
|
-
<
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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
|
-
});
|
|
74
|
+
<LocaleContext initialLocale={locale as Language}>
|
|
75
|
+
<ToolbarContext>
|
|
76
|
+
<LexicalComposer
|
|
77
|
+
initialConfig={{
|
|
78
|
+
...editorConfig,
|
|
79
|
+
editorState: () => {
|
|
80
|
+
$convertFromMarkdownString(content, [...TRANSFORMERS]);
|
|
81
|
+
},
|
|
108
82
|
}}
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
83
|
+
>
|
|
84
|
+
<div className="gc-editor-container">
|
|
85
|
+
<ToolbarPlugin editorId={editorId} setIsLinkEditMode={setIsLinkEditMode} />
|
|
86
|
+
<ShortcutsPlugin setIsLinkEditMode={setIsLinkEditMode} />
|
|
87
|
+
<RichTextPlugin
|
|
88
|
+
contentEditable={
|
|
89
|
+
<div
|
|
90
|
+
className="gc-editor-container"
|
|
91
|
+
ref={onRef}
|
|
92
|
+
{...(contentLocale && { lang: contentLocale })}
|
|
93
|
+
>
|
|
94
|
+
<ContentEditable
|
|
95
|
+
className={className || ""}
|
|
96
|
+
placeholder={""}
|
|
97
|
+
id={editorId}
|
|
98
|
+
ariaLabel={ariaLabel}
|
|
99
|
+
ariaDescribedBy={ariaDescribedBy}
|
|
100
|
+
/>
|
|
101
|
+
</div>
|
|
102
|
+
}
|
|
103
|
+
ErrorBoundary={LexicalErrorBoundary}
|
|
104
|
+
/>
|
|
105
|
+
<HistoryPlugin />
|
|
106
|
+
{showTreeview && <TreeViewPlugin />}
|
|
107
|
+
<OnChangePlugin
|
|
108
|
+
onChange={(editorState) => {
|
|
109
|
+
editorState.read(() => {
|
|
110
|
+
const markdown = $convertToMarkdownString([...TRANSFORMERS, LINE_BREAK_FIX]);
|
|
111
|
+
onChange && onChange(markdown);
|
|
112
|
+
});
|
|
113
|
+
}}
|
|
120
114
|
/>
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
115
|
+
<ListPlugin />
|
|
116
|
+
<LinkPlugin />
|
|
117
|
+
<TabControlPlugin />
|
|
118
|
+
<ListMaxIndentLevelPlugin maxDepth={5} />
|
|
119
|
+
{maxLength && (
|
|
120
|
+
<MaxLengthPlugin maxLength={maxLength} setContentLength={setContentLength} />
|
|
121
|
+
)}
|
|
122
|
+
{floatingAnchorElem && (
|
|
123
|
+
<>
|
|
124
|
+
<FloatingLinkEditorPlugin
|
|
125
|
+
anchorElem={floatingAnchorElem}
|
|
126
|
+
isLinkEditMode={isLinkEditMode}
|
|
127
|
+
setIsLinkEditMode={setIsLinkEditMode}
|
|
128
|
+
/>
|
|
129
|
+
</>
|
|
130
|
+
)}
|
|
131
|
+
{maxLength && (
|
|
132
|
+
<div className="gc-editor-max-length">
|
|
133
|
+
{maxLength && (
|
|
134
|
+
<>
|
|
135
|
+
{contentLength}/{maxLength}
|
|
136
|
+
</>
|
|
137
|
+
)}
|
|
138
|
+
</div>
|
|
139
|
+
)}
|
|
140
|
+
</div>
|
|
141
|
+
</LexicalComposer>
|
|
142
|
+
</ToolbarContext>
|
|
143
|
+
</LocaleContext>
|
|
125
144
|
);
|
|
126
145
|
};
|
package/src/config.ts
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import { HeadingNode } from "@lexical/rich-text";
|
|
2
2
|
import { LinkNode } from "@lexical/link";
|
|
3
3
|
import { ListItemNode, ListNode } from "@lexical/list";
|
|
4
|
-
import basicTheme from "./themes/BasicTheme";
|
|
5
4
|
|
|
6
5
|
export const editorConfig = {
|
|
7
6
|
namespace: "FormBuilder",
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
theme: {
|
|
8
|
+
text: {
|
|
9
|
+
bold: "font-bold",
|
|
10
|
+
italic: "italic",
|
|
11
|
+
},
|
|
12
|
+
},
|
|
10
13
|
// Handling of errors during update
|
|
11
14
|
onError(error: Error) {
|
|
12
15
|
throw error;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import React, { createContext, useContext } from "react";
|
|
2
|
+
import translations, { Language } from "../i18n";
|
|
3
|
+
|
|
4
|
+
interface LocaleContextProps {
|
|
5
|
+
locale: Language;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const Context = createContext<LocaleContextProps | undefined>(undefined);
|
|
9
|
+
|
|
10
|
+
export const LocaleContext: React.FC<{
|
|
11
|
+
initialLocale: Language;
|
|
12
|
+
children: React.ReactNode;
|
|
13
|
+
}> = ({ initialLocale, children }) => {
|
|
14
|
+
let locale = initialLocale;
|
|
15
|
+
|
|
16
|
+
if (!translations[initialLocale as Language]) {
|
|
17
|
+
// eslint-disable-next-line no-console
|
|
18
|
+
console.warn(`The locale "${initialLocale}" is not supported. Defaulting to "en".`);
|
|
19
|
+
locale = "en";
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return <Context.Provider value={{ locale }}>{children}</Context.Provider>;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export const useLocale = (): LocaleContextProps => {
|
|
26
|
+
const context = useContext(Context);
|
|
27
|
+
if (!context) {
|
|
28
|
+
throw new Error("useLocale must be used within a LocaleProvider");
|
|
29
|
+
}
|
|
30
|
+
return context;
|
|
31
|
+
};
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { JSX } from "react";
|
|
10
|
+
|
|
11
|
+
import React, { createContext, ReactNode, useCallback, useContext, useMemo, useState } from "react";
|
|
12
|
+
|
|
13
|
+
export const MIN_ALLOWED_FONT_SIZE = 8;
|
|
14
|
+
export const MAX_ALLOWED_FONT_SIZE = 72;
|
|
15
|
+
export const DEFAULT_FONT_SIZE = 15;
|
|
16
|
+
|
|
17
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
18
|
+
const rootTypeToRootName = {
|
|
19
|
+
root: "Root",
|
|
20
|
+
table: "Table",
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export const blockTypeToBlockName = {
|
|
24
|
+
bullet: "Bulleted List",
|
|
25
|
+
check: "Check List",
|
|
26
|
+
h2: "Heading 2",
|
|
27
|
+
h3: "Heading 3",
|
|
28
|
+
number: "Numbered List",
|
|
29
|
+
paragraph: "Normal",
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const INITIAL_TOOLBAR_STATE = {
|
|
33
|
+
blockType: "paragraph" as keyof typeof blockTypeToBlockName,
|
|
34
|
+
canRedo: false,
|
|
35
|
+
canUndo: false,
|
|
36
|
+
isBold: false,
|
|
37
|
+
isItalic: false,
|
|
38
|
+
isLink: false,
|
|
39
|
+
rootType: "root" as keyof typeof rootTypeToRootName,
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
type ToolbarState = typeof INITIAL_TOOLBAR_STATE;
|
|
43
|
+
|
|
44
|
+
// Utility type to get keys and infer value types
|
|
45
|
+
type ToolbarStateKey = keyof ToolbarState;
|
|
46
|
+
type ToolbarStateValue<Key extends ToolbarStateKey> = ToolbarState[Key];
|
|
47
|
+
|
|
48
|
+
type ContextShape = {
|
|
49
|
+
toolbarState: ToolbarState;
|
|
50
|
+
updateToolbarState<Key extends ToolbarStateKey>(key: Key, value: ToolbarStateValue<Key>): void;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const Context = createContext<ContextShape | undefined>(undefined);
|
|
54
|
+
|
|
55
|
+
export const ToolbarContext = ({ children }: { children: ReactNode }): JSX.Element => {
|
|
56
|
+
const [toolbarState, setToolbarState] = useState(INITIAL_TOOLBAR_STATE);
|
|
57
|
+
|
|
58
|
+
const updateToolbarState = useCallback(
|
|
59
|
+
<Key extends ToolbarStateKey>(key: Key, value: ToolbarStateValue<Key>) => {
|
|
60
|
+
setToolbarState((prev) => ({
|
|
61
|
+
...prev,
|
|
62
|
+
[key]: value,
|
|
63
|
+
}));
|
|
64
|
+
},
|
|
65
|
+
[]
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
const contextValue = useMemo(() => {
|
|
69
|
+
return {
|
|
70
|
+
toolbarState,
|
|
71
|
+
updateToolbarState,
|
|
72
|
+
};
|
|
73
|
+
}, [toolbarState, updateToolbarState]);
|
|
74
|
+
|
|
75
|
+
return <Context.Provider value={contextValue}>{children}</Context.Provider>;
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
export const useToolbarState = () => {
|
|
79
|
+
const context = useContext(Context);
|
|
80
|
+
|
|
81
|
+
if (context === undefined) {
|
|
82
|
+
throw new Error("useToolbarState must be used within a ToolbarProvider");
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return context;
|
|
86
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { useCallback } from "react";
|
|
2
|
+
import translations from "../i18n";
|
|
3
|
+
import type { Translations } from "../i18n";
|
|
4
|
+
import type { TranslationKey } from "../i18n";
|
|
5
|
+
import { useLocale } from "../context/LocaleContext";
|
|
6
|
+
|
|
7
|
+
export const useTranslation = () => {
|
|
8
|
+
const { locale } = useLocale();
|
|
9
|
+
|
|
10
|
+
const t = useCallback(
|
|
11
|
+
(key: TranslationKey): string => {
|
|
12
|
+
const langTranslations = translations[locale] as Translations;
|
|
13
|
+
return langTranslations[key] || key;
|
|
14
|
+
},
|
|
15
|
+
[locale]
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
return { t, locale };
|
|
19
|
+
};
|
package/src/i18n/en.json
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"textFormatting": "Text formatting",
|
|
3
|
+
"tooltipFormatBold": "Bold",
|
|
4
|
+
"tooltipFormatBulletList": "Bulleted list",
|
|
5
|
+
"tooltipFormatH2": "Heading 2",
|
|
6
|
+
"tooltipFormatH3": "Heading 3",
|
|
7
|
+
"tooltipFormatItalic": "Italic",
|
|
8
|
+
"tooltipFormatNumberedList": "Numbered list",
|
|
9
|
+
"tooltipInsertLink": "Insert link",
|
|
10
|
+
"formatBold": "Format bold",
|
|
11
|
+
"formatBulletList": "Format bulleted list",
|
|
12
|
+
"formatH2": "Format Heading 2",
|
|
13
|
+
"formatH3": "Format Heading 3",
|
|
14
|
+
"formatItalic": "Format italic",
|
|
15
|
+
"formatNumberedList": "Format numbered list",
|
|
16
|
+
"insertLink": "Insert link",
|
|
17
|
+
"cancelEditLink": "Cancel edit link",
|
|
18
|
+
"saveLink": "Save link",
|
|
19
|
+
"editLink": "Edit link",
|
|
20
|
+
"removeLink": "Remove link",
|
|
21
|
+
"pressEnterToEditLink": "Press Enter to edit link"
|
|
22
|
+
}
|
package/src/i18n/fr.json
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"textFormatting": "Mise en forme du texte",
|
|
3
|
+
"tooltipFormatBold": "En gras",
|
|
4
|
+
"tooltipFormatBulletList": "Liste à puces",
|
|
5
|
+
"tooltipFormatH2": "En-tête niveau 2",
|
|
6
|
+
"tooltipFormatH3": "En-tête niveau 3",
|
|
7
|
+
"tooltipFormatItalic": "Italique",
|
|
8
|
+
"tooltipFormatNumberedList": "Liste numérotée",
|
|
9
|
+
"tooltipInsertLink": "Insérer un lien",
|
|
10
|
+
"formatBold": "Mettre en gras",
|
|
11
|
+
"formatBulletList": "Mettre en liste à puces",
|
|
12
|
+
"formatH2": "Mettre en en-tête niveau 2",
|
|
13
|
+
"formatH3": "Mettre en en-tête niveau 3",
|
|
14
|
+
"formatItalic": "Mettre en italique",
|
|
15
|
+
"formatNumberedList": "Mettre en liste numérotée",
|
|
16
|
+
"insertLink": "Insérer un lien",
|
|
17
|
+
"cancelEditLink": "Annuler la modification du lien",
|
|
18
|
+
"saveLink": "Enregistrer le lien",
|
|
19
|
+
"editLink": "Modifier le lien",
|
|
20
|
+
"removeLink": "Supprimer le lien",
|
|
21
|
+
"pressEnterToEditLink": "Appuyez sur Entrée pour modifier le lien"
|
|
22
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import en from "./en.json";
|
|
2
|
+
import fr from "./fr.json";
|
|
3
|
+
|
|
4
|
+
const translations = {
|
|
5
|
+
en,
|
|
6
|
+
fr,
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export default translations;
|
|
10
|
+
export type Language = keyof typeof translations;
|
|
11
|
+
export type TranslationKey = keyof typeof translations.en;
|
|
12
|
+
export type Translations = typeof translations.en;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export const CancelIcon = ({ className, title }: { className?: string; title?: string }) => (
|
|
2
|
+
<svg
|
|
3
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
4
|
+
height="24"
|
|
5
|
+
width="24"
|
|
6
|
+
className={className}
|
|
7
|
+
viewBox="0 0 24 24"
|
|
8
|
+
focusable="false"
|
|
9
|
+
aria-hidden={title ? false : true}
|
|
10
|
+
role={title ? "img" : "presentation"}
|
|
11
|
+
>
|
|
12
|
+
{title && <title>{title}</title>}
|
|
13
|
+
<path d="m8.4 17 3.6-3.6 3.6 3.6 1.4-1.4-3.6-3.6L17 8.4 15.6 7 12 10.6 8.4 7 7 8.4l3.6 3.6L7 15.6Zm3.6 5q-2.075 0-3.9-.788-1.825-.787-3.175-2.137-1.35-1.35-2.137-3.175Q2 14.075 2 12t.788-3.9q.787-1.825 2.137-3.175 1.35-1.35 3.175-2.138Q9.925 2 12 2t3.9.787q1.825.788 3.175 2.138 1.35 1.35 2.137 3.175Q22 9.925 22 12t-.788 3.9q-.787 1.825-2.137 3.175-1.35 1.35-3.175 2.137Q14.075 22 12 22Zm0-2q3.35 0 5.675-2.325Q20 15.35 20 12q0-3.35-2.325-5.675Q15.35 4 12 4 8.65 4 6.325 6.325 4 8.65 4 12q0 3.35 2.325 5.675Q8.65 20 12 20Zm0-8Z" />
|
|
14
|
+
</svg>
|
|
15
|
+
);
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export const CheckIcon = ({ className, title }: { className?: string; title?: string }) => (
|
|
2
|
+
<svg
|
|
3
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
4
|
+
height="24px"
|
|
5
|
+
width="24px"
|
|
6
|
+
viewBox="0 -960 960 960"
|
|
7
|
+
className={className}
|
|
8
|
+
focusable="false"
|
|
9
|
+
aria-hidden={title ? false : true}
|
|
10
|
+
role={title ? "img" : "presentation"}
|
|
11
|
+
>
|
|
12
|
+
{title && <title>{title}</title>}
|
|
13
|
+
<path d="m424-296 282-282-56-56-226 226-114-114-56 56 170 170Zm56 216q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm0-80q134 0 227-93t93-227q0-134-93-227t-227-93q-134 0-227 93t-93 227q0 134 93 227t227 93Zm0-320Z" />
|
|
14
|
+
</svg>
|
|
15
|
+
);
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export const DeleteIcon = ({ className, title }: { className?: string; title?: string }) => (
|
|
2
|
+
<svg
|
|
3
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
4
|
+
height="48"
|
|
5
|
+
width="48"
|
|
6
|
+
className={className}
|
|
7
|
+
viewBox="0 -960 960 960"
|
|
8
|
+
focusable="false"
|
|
9
|
+
aria-hidden={title ? false : true}
|
|
10
|
+
role={title ? "img" : "presentation"}
|
|
11
|
+
>
|
|
12
|
+
{title && <title>{title}</title>}
|
|
13
|
+
<path d="M261-120q-24.75 0-42.375-17.625T201-180v-570h-41v-60h188v-30h264v30h188v60h-41v570q0 24-18 42t-42 18H261Zm438-630H261v570h438v-570ZM367-266h60v-399h-60v399Zm166 0h60v-399h-60v399ZM261-750v570-570Z" />
|
|
14
|
+
</svg>
|
|
15
|
+
);
|
package/src/icons/EditIcon.tsx
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
export const EditIcon = ({ className, title }: { className?: string; title?: string }) => (
|
|
2
2
|
<svg
|
|
3
3
|
xmlns="http://www.w3.org/2000/svg"
|
|
4
|
-
height="
|
|
5
|
-
width="
|
|
4
|
+
height="24px"
|
|
5
|
+
width="24px"
|
|
6
|
+
viewBox="0 -960 960 960"
|
|
6
7
|
className={className}
|
|
7
8
|
focusable="false"
|
|
8
9
|
aria-hidden={title ? false : true}
|
|
9
10
|
role={title ? "img" : "presentation"}
|
|
10
11
|
>
|
|
11
12
|
{title && <title>{title}</title>}
|
|
12
|
-
<path d="
|
|
13
|
+
<path d="M200-120q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h357l-80 80H200v560h560v-278l80-80v358q0 33-23.5 56.5T760-120H200Zm280-360ZM360-360v-170l367-367q12-12 27-18t30-6q16 0 30.5 6t26.5 18l56 57q11 12 17 26.5t6 29.5q0 15-5.5 29.5T897-728L530-360H360Zm481-424-56-56 56 56ZM440-440h56l232-232-28-28-29-28-231 231v57Zm260-260-29-28 29 28 28 28-28-28Z" />
|
|
13
14
|
</svg>
|
|
14
15
|
);
|
package/src/icons/LinkIcon.tsx
CHANGED
|
@@ -4,12 +4,12 @@ export const LinkIcon = ({ className, title }: { className?: string; title?: str
|
|
|
4
4
|
height="20"
|
|
5
5
|
width="20"
|
|
6
6
|
className={className}
|
|
7
|
-
viewBox="0
|
|
7
|
+
viewBox="0 -960 960 960"
|
|
8
8
|
focusable="false"
|
|
9
9
|
aria-hidden={title ? false : true}
|
|
10
10
|
role={title ? "img" : "presentation"}
|
|
11
11
|
>
|
|
12
12
|
{title && <title>{title}</title>}
|
|
13
|
-
<path d="
|
|
13
|
+
<path d="M440-280H280q-83 0-141.5-58.5T80-480q0-83 58.5-141.5T280-680h160v80H280q-50 0-85 35t-35 85q0 50 35 85t85 35h160v80ZM320-440v-80h320v80H320Zm200 160v-80h160q50 0 85-35t35-85q0-50-35-85t-85-35H520v-80h160q83 0 141.5 58.5T880-480q0 83-58.5 141.5T680-280H520Z" />
|
|
14
14
|
</svg>
|
|
15
15
|
);
|