@crystallize/design-system 1.10.0 → 1.11.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/CHANGELOG.md +12 -0
- package/dist/index.css +15 -17
- package/dist/index.d.ts +8 -1
- package/dist/index.js +522 -444
- package/dist/index.mjs +377 -304
- package/package.json +1 -1
- package/src/iconography/info.tsx +2 -2
- package/src/rich-text-editor/i18n/i18n.test.ts +14 -0
- package/src/rich-text-editor/i18n/index.tsx +65 -0
- package/src/rich-text-editor/i18n/translations/en.ts +66 -0
- package/src/rich-text-editor/i18n/types.ts +62 -0
- package/src/rich-text-editor/plugins/ActionsPlugin/index.tsx +5 -22
- package/src/rich-text-editor/plugins/CodeActionMenuPlugin/components/CopyButton/index.tsx +4 -1
- package/src/rich-text-editor/plugins/CodeActionMenuPlugin/components/PrettierButton/index.tsx +11 -1
- package/src/rich-text-editor/plugins/CodeActionMenuPlugin/index.tsx +2 -1
- package/src/rich-text-editor/plugins/FloatingLinkEditorPlugin/index.tsx +23 -5
- package/src/rich-text-editor/plugins/FloatingTextFormatToolbarPlugin/index.tsx +21 -10
- package/src/rich-text-editor/plugins/TabFocusPlugin/index.tsx +4 -12
- package/src/rich-text-editor/plugins/TableActionMenuPlugin/index.tsx +23 -14
- package/src/rich-text-editor/plugins/ToolbarPlugin/index.tsx +33 -33
- package/src/rich-text-editor/plugins/ToolbarPlugin/insert-table.tsx +6 -4
- package/src/rich-text-editor/rich-text-editor.css +6 -0
- package/src/rich-text-editor/rich-text-editor.stories.tsx +10 -0
- package/src/rich-text-editor/rich-text-editor.tsx +15 -9
- package/src/rich-text-editor/ui/LinkPreview.tsx +3 -1
- package/src/rich-text-editor/ui/ContentEditable.css +0 -13
- package/src/rich-text-editor/ui/ContentEditable.tsx +0 -15
package/package.json
CHANGED
package/src/iconography/info.tsx
CHANGED
|
@@ -20,7 +20,7 @@ export const Info = forwardRef<InfoRef, InfoProps>((delegated, ref) => {
|
|
|
20
20
|
/>
|
|
21
21
|
<path
|
|
22
22
|
fillRule="evenodd"
|
|
23
|
-
|
|
23
|
+
clipRule="evenodd"
|
|
24
24
|
d="M18.253 3.85212C18.253 5.04383 17.2869 6.00991 16.0952 6.00991C14.9035 6.00991 13.9374 5.04383 13.9374 3.85212C13.9374 2.66041 14.9035 1.69434 16.0952 1.69434C17.2869 1.69434 18.253 2.66041 18.253 3.85212ZM16.0952 5.34999C16.9224 5.34999 17.5931 4.67937 17.5931 3.85212C17.5931 3.02487 16.9224 2.35425 16.0952 2.35425C15.2679 2.35425 14.5973 3.02487 14.5973 3.85212C14.5973 4.67937 15.2679 5.34999 16.0952 5.34999Z"
|
|
25
25
|
fill="#528693"
|
|
26
26
|
/>
|
|
@@ -30,7 +30,7 @@ export const Info = forwardRef<InfoRef, InfoProps>((delegated, ref) => {
|
|
|
30
30
|
/>
|
|
31
31
|
<path
|
|
32
32
|
fillRule="evenodd"
|
|
33
|
-
|
|
33
|
+
clipRule="evenodd"
|
|
34
34
|
d="M11.2842 18.976L11.3049 18.9785C11.5112 19.0031 11.7201 18.9658 11.9049 18.8714C12.0891 18.7772 12.2409 18.6306 12.3411 18.4504C12.3471 18.4394 12.352 18.4277 12.3558 18.4158C12.3317 18.4124 12.3048 18.4091 12.2731 18.4053C12.0384 18.3881 11.8084 18.33 11.5936 18.2335C11.2334 18.0797 10.9456 17.7933 10.7906 17.433C10.6341 17.0694 10.6252 16.6594 10.7658 16.2894C10.9266 15.8663 11.1487 15.4689 11.4246 15.1101L11.6291 14.8181C11.6293 14.8178 11.629 14.8184 11.6291 14.8181C12.0038 14.2802 12.4092 13.763 12.8423 13.2707L12.9418 13.1535C12.9894 13.097 13.0361 13.0418 13.0821 12.9874C13.2314 12.8108 13.3734 12.6428 13.5141 12.4665L13.5237 12.4544L13.5339 12.4428C14.107 11.7899 14.5194 11.0128 14.7386 10.1733C14.8642 9.60674 14.7724 9.1134 14.5125 8.69961C14.2452 8.27422 13.7678 7.88337 13.0466 7.60647L13.0376 7.60304C12.2034 7.26882 11.3198 7.07373 10.4221 7.02572C10.2586 7.02444 10.0952 7.03841 9.93422 7.06745L9.91342 7.0712L9.89242 7.07362C9.77981 7.08656 9.67209 7.12639 9.57839 7.18964C9.48729 7.25113 9.41209 7.33296 9.35867 7.42842C9.35249 7.44171 9.33941 7.47402 9.32659 7.52286C9.31959 7.54951 9.31383 7.57671 9.30949 7.60325C9.36308 7.61115 9.41754 7.61364 9.47206 7.61055L9.51828 7.60793L9.63538 7.61773C9.79024 7.6263 9.94419 7.64682 10.0959 7.67913L10.1211 7.68451L10.1459 7.69185C10.6213 7.83272 11.1176 8.10198 11.3044 8.65041L11.3058 8.6547C11.4603 9.11879 11.309 9.5616 11.1547 9.87197C10.9947 10.194 10.7584 10.5161 10.546 10.7961C10.1932 11.2626 9.79323 11.726 9.4222 12.1559L9.4167 12.1622L9.30686 12.2896L9.29607 12.301C8.50411 13.1358 7.82119 14.0671 7.26353 15.0728C6.95101 15.7053 6.92585 16.2828 7.10297 16.7755C7.28379 17.2785 7.70442 17.772 8.42421 18.1631L8.42623 18.1642C9.29923 18.6426 10.2687 18.9197 11.2633 18.9749L11.2842 18.976ZM6.67909 14.7659C7.26511 13.7065 7.9836 12.7256 8.81732 11.8468L8.91693 11.7313C9.29243 11.2963 9.68044 10.8467 10.0199 10.3976C10.4486 9.83253 10.8215 9.28929 10.6797 8.86312C10.5968 8.6196 10.3605 8.44373 9.9584 8.32457C9.83715 8.29874 9.71404 8.28256 9.59022 8.27617L9.50938 8.26941C9.26232 8.2834 9.01575 8.23452 8.79281 8.12735C8.54248 7.93066 8.64731 7.36817 8.77404 7.12204C8.87835 6.92966 9.02762 6.76522 9.2092 6.64266C9.39077 6.5201 9.59929 6.44304 9.81707 6.41802C10.0235 6.38077 10.2331 6.36335 10.4429 6.36598C11.4176 6.41652 12.3773 6.6275 13.2831 6.9904C14.9259 7.62106 15.7097 8.86885 15.3801 10.3284C15.1357 11.2718 14.6733 12.1451 14.0299 12.8781C13.8828 13.0625 13.7326 13.2401 13.582 13.4182C13.5366 13.4719 13.4912 13.5256 13.4458 13.5795L13.3415 13.7024C12.9232 14.1775 12.5323 14.6759 12.1707 15.1952L11.9564 15.5012C11.7156 15.8119 11.5223 16.1565 11.3827 16.5237C11.303 16.7335 11.308 16.966 11.3968 17.1721C11.4855 17.3783 11.651 17.5421 11.8583 17.6289C12.0086 17.6973 12.1701 17.7377 12.335 17.748C12.5957 17.7793 12.8648 17.8126 12.9712 18.0603C13.0139 18.1752 13.0313 18.298 13.0223 18.4203C13.0133 18.5425 12.9781 18.6615 12.9191 18.769C12.7552 19.0647 12.5066 19.305 12.2052 19.459C11.9037 19.6131 11.563 19.6739 11.2267 19.6338C10.1339 19.5731 9.0685 19.2687 8.1091 18.743C6.45171 17.8422 5.90412 16.3181 6.67909 14.7659Z"
|
|
35
35
|
fill="#528693"
|
|
36
36
|
/>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { replaceI18nVariablesInString } from '.';
|
|
2
|
+
|
|
3
|
+
describe('RichTextEditor i18n', () => {
|
|
4
|
+
it('replaces variable parts of a translation string correctly', async () => {
|
|
5
|
+
expect(replaceI18nVariablesInString('Hi {{name}}!', 'you')).toBe('Hi you!');
|
|
6
|
+
expect(replaceI18nVariablesInString('Hi {{whatever-goes here}}!', 'you')).toBe('Hi you!');
|
|
7
|
+
expect(replaceI18nVariablesInString('It can be replace {{name}} many times {{name}}', 'you')).toBe(
|
|
8
|
+
'It can be replace you many times you',
|
|
9
|
+
);
|
|
10
|
+
expect(replaceI18nVariablesInString('It needs correct formatting {name}}', 'you')).toBe(
|
|
11
|
+
'It needs correct formatting {name}}',
|
|
12
|
+
);
|
|
13
|
+
});
|
|
14
|
+
});
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { createContext, ReactNode, useContext } from 'react';
|
|
2
|
+
|
|
3
|
+
import labelTranslationEn from './translations/en';
|
|
4
|
+
import type { I18N } from './types';
|
|
5
|
+
|
|
6
|
+
const I18nContext = createContext<I18N | null>(null);
|
|
7
|
+
|
|
8
|
+
export type SupportedLanguages = 'en';
|
|
9
|
+
|
|
10
|
+
export { default as labelTranslationEn } from './translations/en';
|
|
11
|
+
|
|
12
|
+
export function I18nProvider({
|
|
13
|
+
labelTranslations,
|
|
14
|
+
children,
|
|
15
|
+
}: {
|
|
16
|
+
language: SupportedLanguages;
|
|
17
|
+
labelTranslations?: I18N;
|
|
18
|
+
children: ReactNode;
|
|
19
|
+
}) {
|
|
20
|
+
const translations = labelTranslations || labelTranslationEn;
|
|
21
|
+
// const [translations, setTranslations] = useState<I18N>(labelTranslations || labelTranslationEn);
|
|
22
|
+
|
|
23
|
+
// useEffect(() => {
|
|
24
|
+
// let unmounted = false;
|
|
25
|
+
// (async function load() {
|
|
26
|
+
// if (language !== 'en') {
|
|
27
|
+
// const resource = await import(`./translations/${language}.ts`);
|
|
28
|
+
// if (!unmounted) {
|
|
29
|
+
// setTranslations(resource.default);
|
|
30
|
+
// }
|
|
31
|
+
// }
|
|
32
|
+
// })();
|
|
33
|
+
|
|
34
|
+
// return () => {
|
|
35
|
+
// unmounted = true;
|
|
36
|
+
// };
|
|
37
|
+
// }, [language, labelTranslations]);
|
|
38
|
+
|
|
39
|
+
return <I18nContext.Provider value={translations}>{children}</I18nContext.Provider>;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function replaceI18nVariablesInString(str: string, replaceWith: string) {
|
|
43
|
+
return str.replace(/({{[^}]+}})/g, replaceWith);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function useTr() {
|
|
47
|
+
const context = useContext(I18nContext);
|
|
48
|
+
|
|
49
|
+
return (key: keyof I18N, units?: number) => {
|
|
50
|
+
const thereAreUnits = typeof units === 'number';
|
|
51
|
+
const keyToUse = thereAreUnits && units > 1 ? `${key}_plural` : key;
|
|
52
|
+
|
|
53
|
+
if (context && keyToUse in context) {
|
|
54
|
+
// @ts-expect-error
|
|
55
|
+
const tr: string = context[keyToUse];
|
|
56
|
+
|
|
57
|
+
if (thereAreUnits) {
|
|
58
|
+
return replaceI18nVariablesInString(tr, units.toString());
|
|
59
|
+
}
|
|
60
|
+
return tr;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return '';
|
|
64
|
+
};
|
|
65
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import type { I18N } from '../types';
|
|
2
|
+
|
|
3
|
+
const translations: I18N = {
|
|
4
|
+
actionClearTextFormatting: 'Clear text formatting',
|
|
5
|
+
actionTextFormattingOptions: 'Formatting options for additional text styles',
|
|
6
|
+
actionFormatAsStrongLabel: 'Format text as bold',
|
|
7
|
+
actionFormatAsStrongTitle: 'Bold (Ctrl+B)',
|
|
8
|
+
actionFormatAsStrongTitleApple: 'Bold (⌘B)',
|
|
9
|
+
actionFormatAsEmphasizedLabel: 'Format text as italics',
|
|
10
|
+
actionFormatAsEmphasizedTitle: 'Italic (Ctrl+I)',
|
|
11
|
+
actionFormatAsEmphasizedTitleApple: 'Italic (⌘I)',
|
|
12
|
+
actionFormatAsUnderlinedLabel: 'Underline text',
|
|
13
|
+
actionFormatAsUnderlinedTitle: 'Underlined (Ctrl+U)',
|
|
14
|
+
actionFormatAsUnderlinedTitleApple: 'Underlined (⌘U)',
|
|
15
|
+
actionFormatWithStrikethroughLabel: 'Format text with a strikethrough',
|
|
16
|
+
actionFormatWithStrikethroughTitle: 'Strikethrough',
|
|
17
|
+
actionFormatWithSubscriptLabel: 'Format text as subscript',
|
|
18
|
+
actionFormatWithSubscriptTitle: 'Subscript',
|
|
19
|
+
actionFormatWithSuperscriptLabel: 'Format text as superscript',
|
|
20
|
+
actionFormatWithSuperscriptTitle: 'Superscript',
|
|
21
|
+
actionInsertCodeBlock: 'Insert code block',
|
|
22
|
+
actionInsertlink: 'Insert link',
|
|
23
|
+
actionCopyJSON: 'Copy JSON',
|
|
24
|
+
actionCopyCode: 'Copy code',
|
|
25
|
+
actionClear: 'Clear editor',
|
|
26
|
+
actionFormatCode: 'Format code with Prettier',
|
|
27
|
+
actionTableInsertRowsAbove: 'Insert row above',
|
|
28
|
+
actionTableInsertRowsAbove_plural: 'Insert {{rows}} rows above',
|
|
29
|
+
actionTableInsertRowsBelow: 'Insert row above',
|
|
30
|
+
actionTableInsertRowsBelow_plural: 'Insert {{rows}} rows above',
|
|
31
|
+
actionTableInsertColumnsBefore: 'Insert column left',
|
|
32
|
+
actionTableInsertColumnsBefore_plural: 'Insert {{columns}} columns left',
|
|
33
|
+
actionTableInsertColumnsAfter: 'Insert column right',
|
|
34
|
+
actionTableInsertColumnsAfter_plural: 'Insert {{columns}} columns right',
|
|
35
|
+
actionTableAddRowHeader: 'Add row header',
|
|
36
|
+
actionTableRemoveRowHeader: 'Remove row header',
|
|
37
|
+
actionTableAddColumnHeader: 'Add column header',
|
|
38
|
+
actionTableRemoveColumnHeader: 'Remove column header',
|
|
39
|
+
actionTableDeleteColumn: 'Delete column',
|
|
40
|
+
actionTableDeleteRow: 'Delete row',
|
|
41
|
+
actionTableDeleteTable: 'Delete table',
|
|
42
|
+
actionTableOpenOptions: 'Open table options',
|
|
43
|
+
actionUndoLabel: 'Undo',
|
|
44
|
+
actionUndoTitle: 'Undo (Ctrl+Z)',
|
|
45
|
+
actionUndoTitleApple: 'Undo (⌘Z)',
|
|
46
|
+
actionRedoLabel: 'Undo',
|
|
47
|
+
actionRedoTitle: 'Undo (Ctrl+Y)',
|
|
48
|
+
actionRedoTitleApple: 'Redo (⌘Y)',
|
|
49
|
+
codeSelectLanguage: 'Select language',
|
|
50
|
+
linkEditorLink: 'Link',
|
|
51
|
+
linkEditorRel: 'Rel',
|
|
52
|
+
linkEditorTarget: 'Target',
|
|
53
|
+
linkEditorCommit: 'Done',
|
|
54
|
+
linkEditorEdit: 'Edit',
|
|
55
|
+
linkPreviewReplaceTextWithTitle: 'Replace link text with its title',
|
|
56
|
+
horizontalRule: 'Horizontal rule',
|
|
57
|
+
table: 'Table',
|
|
58
|
+
insertTableTitle: 'Insert table',
|
|
59
|
+
insertTableDescription:
|
|
60
|
+
'Define your starting point of a table, you can add and remove columns and rows after creation.',
|
|
61
|
+
insertTableRows: 'Rows',
|
|
62
|
+
insertTableColumns: 'Columns',
|
|
63
|
+
insertTableCommit: 'Insert table',
|
|
64
|
+
} as const;
|
|
65
|
+
|
|
66
|
+
export default translations;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
export type I18NKeys =
|
|
2
|
+
| 'actionClearTextFormatting'
|
|
3
|
+
| 'actionTextFormattingOptions'
|
|
4
|
+
| 'actionFormatAsStrongLabel'
|
|
5
|
+
| 'actionFormatAsStrongTitle'
|
|
6
|
+
| 'actionFormatAsStrongTitleApple'
|
|
7
|
+
| 'actionFormatAsEmphasizedLabel'
|
|
8
|
+
| 'actionFormatAsEmphasizedTitle'
|
|
9
|
+
| 'actionFormatAsEmphasizedTitleApple'
|
|
10
|
+
| 'actionFormatAsUnderlinedLabel'
|
|
11
|
+
| 'actionFormatAsUnderlinedTitle'
|
|
12
|
+
| 'actionFormatAsUnderlinedTitleApple'
|
|
13
|
+
| 'actionFormatWithStrikethroughLabel'
|
|
14
|
+
| 'actionFormatWithStrikethroughTitle'
|
|
15
|
+
| 'actionFormatWithSubscriptLabel'
|
|
16
|
+
| 'actionFormatWithSubscriptTitle'
|
|
17
|
+
| 'actionFormatWithSuperscriptLabel'
|
|
18
|
+
| 'actionFormatWithSuperscriptTitle'
|
|
19
|
+
| 'actionCopyJSON'
|
|
20
|
+
| 'actionClear'
|
|
21
|
+
| 'actionFormatCode'
|
|
22
|
+
| 'actionCopyCode'
|
|
23
|
+
| 'actionInsertCodeBlock'
|
|
24
|
+
| 'actionInsertlink'
|
|
25
|
+
| 'actionTableInsertRowsAbove'
|
|
26
|
+
| 'actionTableInsertRowsAbove_plural'
|
|
27
|
+
| 'actionTableInsertRowsBelow'
|
|
28
|
+
| 'actionTableInsertRowsBelow_plural'
|
|
29
|
+
| 'actionTableInsertColumnsBefore'
|
|
30
|
+
| 'actionTableInsertColumnsBefore_plural'
|
|
31
|
+
| 'actionTableInsertColumnsAfter'
|
|
32
|
+
| 'actionTableInsertColumnsAfter_plural'
|
|
33
|
+
| 'actionTableAddRowHeader'
|
|
34
|
+
| 'actionTableRemoveRowHeader'
|
|
35
|
+
| 'actionTableAddColumnHeader'
|
|
36
|
+
| 'actionTableRemoveColumnHeader'
|
|
37
|
+
| 'actionTableDeleteColumn'
|
|
38
|
+
| 'actionTableDeleteRow'
|
|
39
|
+
| 'actionTableDeleteTable'
|
|
40
|
+
| 'actionTableOpenOptions'
|
|
41
|
+
| 'actionUndoLabel'
|
|
42
|
+
| 'actionUndoTitle'
|
|
43
|
+
| 'actionUndoTitleApple'
|
|
44
|
+
| 'actionRedoLabel'
|
|
45
|
+
| 'actionRedoTitle'
|
|
46
|
+
| 'actionRedoTitleApple'
|
|
47
|
+
| 'codeSelectLanguage'
|
|
48
|
+
| 'linkEditorLink'
|
|
49
|
+
| 'linkEditorRel'
|
|
50
|
+
| 'linkEditorTarget'
|
|
51
|
+
| 'linkEditorCommit'
|
|
52
|
+
| 'linkEditorEdit'
|
|
53
|
+
| 'linkPreviewReplaceTextWithTitle'
|
|
54
|
+
| 'horizontalRule'
|
|
55
|
+
| 'table'
|
|
56
|
+
| 'insertTableTitle'
|
|
57
|
+
| 'insertTableDescription'
|
|
58
|
+
| 'insertTableRows'
|
|
59
|
+
| 'insertTableColumns'
|
|
60
|
+
| 'insertTableCommit';
|
|
61
|
+
|
|
62
|
+
export type I18N = Record<I18NKeys, string>;
|
|
@@ -11,6 +11,7 @@ import { CLEAR_EDITOR_COMMAND, LexicalEditor } from 'lexical';
|
|
|
11
11
|
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
|
|
12
12
|
|
|
13
13
|
import { ActionMenu } from '../../../action-menu';
|
|
14
|
+
import { useTr } from '../../i18n';
|
|
14
15
|
import { lexicalToCrystallizeRichText } from '../../model/lexical-to-crystallize';
|
|
15
16
|
import type { CrystallizeRichTextActionMenuItem } from '../../types/types';
|
|
16
17
|
|
|
@@ -24,25 +25,6 @@ async function copyJson(editor: LexicalEditor) {
|
|
|
24
25
|
}
|
|
25
26
|
}
|
|
26
27
|
|
|
27
|
-
async function exportJson(editor: LexicalEditor) {
|
|
28
|
-
const json = lexicalToCrystallizeRichText({ editorState: editor.getEditorState() });
|
|
29
|
-
|
|
30
|
-
const blob = new Blob([JSON.stringify(json, null, 1)], {
|
|
31
|
-
type: 'application/json',
|
|
32
|
-
});
|
|
33
|
-
const href = URL.createObjectURL(blob);
|
|
34
|
-
|
|
35
|
-
const link = document.createElement('a');
|
|
36
|
-
link.href = href;
|
|
37
|
-
link.download = 'crystallizeRichText.json';
|
|
38
|
-
|
|
39
|
-
document.body.appendChild(link);
|
|
40
|
-
link.click();
|
|
41
|
-
document.body.removeChild(link);
|
|
42
|
-
|
|
43
|
-
URL.revokeObjectURL(href);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
28
|
export default function ActionsPlugin({
|
|
47
29
|
append,
|
|
48
30
|
prepend,
|
|
@@ -51,6 +33,8 @@ export default function ActionsPlugin({
|
|
|
51
33
|
prepend?: CrystallizeRichTextActionMenuItem[];
|
|
52
34
|
}): JSX.Element {
|
|
53
35
|
const [editor] = useLexicalComposerContext();
|
|
36
|
+
const tr = useTr();
|
|
37
|
+
|
|
54
38
|
return (
|
|
55
39
|
<div className="z-50 flex items-center ">
|
|
56
40
|
<div></div>
|
|
@@ -66,8 +50,7 @@ export default function ActionsPlugin({
|
|
|
66
50
|
{actionItem.title}
|
|
67
51
|
</ActionMenu.Item>
|
|
68
52
|
))}
|
|
69
|
-
<ActionMenu.Item onSelect={() => copyJson(editor)}>
|
|
70
|
-
<ActionMenu.Item onSelect={() => exportJson(editor)}>Export JSON</ActionMenu.Item>
|
|
53
|
+
<ActionMenu.Item onSelect={() => copyJson(editor)}>{tr('actionCopyJSON')}</ActionMenu.Item>
|
|
71
54
|
<ActionMenu.Item
|
|
72
55
|
className="danger"
|
|
73
56
|
onSelect={() => {
|
|
@@ -75,7 +58,7 @@ export default function ActionsPlugin({
|
|
|
75
58
|
editor.focus();
|
|
76
59
|
}}
|
|
77
60
|
>
|
|
78
|
-
|
|
61
|
+
{tr('actionClear')}
|
|
79
62
|
</ActionMenu.Item>
|
|
80
63
|
{!append
|
|
81
64
|
? null
|
|
@@ -12,6 +12,8 @@ import { $getNearestNodeFromDOMNode, $getSelection, $setSelection, LexicalEditor
|
|
|
12
12
|
import { useDebouncedCallback } from 'use-debounce';
|
|
13
13
|
import { $isCodeNode } from '@lexical/code';
|
|
14
14
|
|
|
15
|
+
import { useTr } from '../../../../i18n';
|
|
16
|
+
|
|
15
17
|
interface Props {
|
|
16
18
|
editor: LexicalEditor;
|
|
17
19
|
getCodeDOMNode: () => HTMLElement | null;
|
|
@@ -19,6 +21,7 @@ interface Props {
|
|
|
19
21
|
|
|
20
22
|
export function CopyButton({ editor, getCodeDOMNode }: Props) {
|
|
21
23
|
const [isCopyCompleted, setCopyCompleted] = useState<boolean>(false);
|
|
24
|
+
const tr = useTr();
|
|
22
25
|
|
|
23
26
|
const removeSuccessIcon = useDebouncedCallback(() => {
|
|
24
27
|
setCopyCompleted(false);
|
|
@@ -54,7 +57,7 @@ export function CopyButton({ editor, getCodeDOMNode }: Props) {
|
|
|
54
57
|
}
|
|
55
58
|
|
|
56
59
|
return (
|
|
57
|
-
<button className="menu-item" onClick={handleClick} aria-label=
|
|
60
|
+
<button className="menu-item" onClick={handleClick} aria-label={tr('actionCopyCode')}>
|
|
58
61
|
{isCopyCompleted ? <i className="format success" /> : <i className="format copy" />}
|
|
59
62
|
</button>
|
|
60
63
|
);
|
package/src/rich-text-editor/plugins/CodeActionMenuPlugin/components/PrettierButton/index.tsx
CHANGED
|
@@ -12,6 +12,8 @@ import { $getNearestNodeFromDOMNode, LexicalEditor } from 'lexical';
|
|
|
12
12
|
import type { Options } from 'prettier';
|
|
13
13
|
import { $isCodeNode } from '@lexical/code';
|
|
14
14
|
|
|
15
|
+
import { useTr } from '../../../../i18n';
|
|
16
|
+
|
|
15
17
|
interface Props {
|
|
16
18
|
lang: string;
|
|
17
19
|
editor: LexicalEditor;
|
|
@@ -69,6 +71,7 @@ function getPrettierOptions(lang: string): Options {
|
|
|
69
71
|
export function PrettierButton({ lang, editor, getCodeDOMNode }: Props) {
|
|
70
72
|
const [syntaxError, setSyntaxError] = useState<string>('');
|
|
71
73
|
const [tipsVisible, setTipsVisible] = useState<boolean>(false);
|
|
74
|
+
const tr = useTr();
|
|
72
75
|
|
|
73
76
|
async function handleClick(): Promise<void> {
|
|
74
77
|
const codeDOMNode = getCodeDOMNode();
|
|
@@ -92,6 +95,13 @@ export function PrettierButton({ lang, editor, getCodeDOMNode }: Props) {
|
|
|
92
95
|
|
|
93
96
|
parsed = format(content, options);
|
|
94
97
|
|
|
98
|
+
/**
|
|
99
|
+
* Remove EOF from prettier output. This is useful when
|
|
100
|
+
* using prettier on files, but becomes weird when the code
|
|
101
|
+
* is embedded within a larger portion of text.
|
|
102
|
+
*/
|
|
103
|
+
parsed = parsed.replace(/[\r\n]+$/, '');
|
|
104
|
+
|
|
95
105
|
if (parsed !== '') {
|
|
96
106
|
const selection = codeNode.select(0);
|
|
97
107
|
selection.insertText(parsed);
|
|
@@ -129,7 +139,7 @@ export function PrettierButton({ lang, editor, getCodeDOMNode }: Props) {
|
|
|
129
139
|
onClick={handleClick}
|
|
130
140
|
onMouseEnter={handleMouseEnter}
|
|
131
141
|
onMouseLeave={handleMouseLeave}
|
|
132
|
-
aria-label=
|
|
142
|
+
aria-label={tr('actionFormatCode')}
|
|
133
143
|
>
|
|
134
144
|
{syntaxError ? <i className="format prettier-error" /> : <i className="format prettier" />}
|
|
135
145
|
</button>
|
|
@@ -29,7 +29,7 @@ function CodeActionMenuContainer({ anchorElem }: { anchorElem: HTMLElement }): J
|
|
|
29
29
|
|
|
30
30
|
const [lang, setLang] = useState('');
|
|
31
31
|
const [isShown, setShown] = useState<boolean>(false);
|
|
32
|
-
const [shouldListenMouseMove, setShouldListenMouseMove] = useState<boolean>(
|
|
32
|
+
const [shouldListenMouseMove, setShouldListenMouseMove] = useState<boolean>(true);
|
|
33
33
|
const [position, setPosition] = useState<Position>({
|
|
34
34
|
right: '0',
|
|
35
35
|
top: '0',
|
|
@@ -43,6 +43,7 @@ function CodeActionMenuContainer({ anchorElem }: { anchorElem: HTMLElement }): J
|
|
|
43
43
|
|
|
44
44
|
const debouncedOnMouseMove = useDebouncedCallback((event: MouseEvent) => {
|
|
45
45
|
const { codeDOMNode, isOutside } = getMouseInfo(event);
|
|
46
|
+
|
|
46
47
|
if (isOutside) {
|
|
47
48
|
setShown(false);
|
|
48
49
|
return;
|
|
@@ -30,6 +30,7 @@ import { Button } from '../../../button';
|
|
|
30
30
|
import { IconButton } from '../../../icon-button';
|
|
31
31
|
import { Icon } from '../../../iconography';
|
|
32
32
|
import { InputWithLabel } from '../../../input-with-label';
|
|
33
|
+
import { useTr } from '../../i18n';
|
|
33
34
|
import LinkPreview from '../../ui/LinkPreview';
|
|
34
35
|
import { getSelectedNode } from '../../utils/getSelectedNode';
|
|
35
36
|
import { setFloatingElemPosition } from '../../utils/setFloatingElemPosition';
|
|
@@ -53,6 +54,7 @@ function FloatingLinkEditor({
|
|
|
53
54
|
const [target, setTarget] = useState<string | null>(null);
|
|
54
55
|
const [isEditMode, setEditMode] = useState(false);
|
|
55
56
|
const [lastSelection, setLastSelection] = useState<RangeSelection | GridSelection | NodeSelection | null>(null);
|
|
57
|
+
const tr = useTr();
|
|
56
58
|
|
|
57
59
|
const updateLinkEditor = useCallback(() => {
|
|
58
60
|
const selection = $getSelection();
|
|
@@ -187,14 +189,29 @@ function FloatingLinkEditor({
|
|
|
187
189
|
{isEditMode ? (
|
|
188
190
|
<div>
|
|
189
191
|
<div className="border-0 border-b border-gray-100-800 border-solid px-3">
|
|
190
|
-
<InputWithLabel
|
|
192
|
+
<InputWithLabel
|
|
193
|
+
label={tr('linkEditorLink')}
|
|
194
|
+
type="text"
|
|
195
|
+
value={linkUrl}
|
|
196
|
+
onChange={e => setLinkUrl(e.target.value)}
|
|
197
|
+
/>
|
|
191
198
|
</div>
|
|
192
199
|
|
|
193
200
|
<div className="border-0 border-b border-gray-100-800 border-solid px-3">
|
|
194
|
-
<InputWithLabel
|
|
201
|
+
<InputWithLabel
|
|
202
|
+
label={tr('linkEditorRel')}
|
|
203
|
+
type="text"
|
|
204
|
+
value={rel ?? ''}
|
|
205
|
+
onChange={e => setRel(e.target.value)}
|
|
206
|
+
/>
|
|
195
207
|
</div>
|
|
196
208
|
<div className="border-0 border-b border-gray-100-800 border-solid px-3">
|
|
197
|
-
<InputWithLabel
|
|
209
|
+
<InputWithLabel
|
|
210
|
+
label={tr('linkEditorTarget')}
|
|
211
|
+
type="text"
|
|
212
|
+
value={target ?? ''}
|
|
213
|
+
onChange={e => setTarget(e.target.value)}
|
|
214
|
+
/>
|
|
198
215
|
</div>
|
|
199
216
|
<div className="flex px-6 py-2 justify-end">
|
|
200
217
|
<Button
|
|
@@ -211,14 +228,14 @@ function FloatingLinkEditor({
|
|
|
211
228
|
}
|
|
212
229
|
}}
|
|
213
230
|
>
|
|
214
|
-
|
|
231
|
+
{tr('linkEditorCommit')}
|
|
215
232
|
</Button>
|
|
216
233
|
</div>
|
|
217
234
|
</div>
|
|
218
235
|
) : (
|
|
219
236
|
<>
|
|
220
237
|
<div className="link-input !flex flex-nowrap justify-between items-center max-w-full ">
|
|
221
|
-
<div className="
|
|
238
|
+
<div className="grid">
|
|
222
239
|
<a href={linkUrl} target="_blank" rel="noopener noreferrer">
|
|
223
240
|
{linkUrl}
|
|
224
241
|
</a>
|
|
@@ -241,6 +258,7 @@ function FloatingLinkEditor({
|
|
|
241
258
|
tabIndex={0}
|
|
242
259
|
onMouseDown={event => event.preventDefault()}
|
|
243
260
|
onClick={() => setEditMode(true)}
|
|
261
|
+
aria-label={tr('linkEditorEdit')}
|
|
244
262
|
>
|
|
245
263
|
<Icon.Edit />
|
|
246
264
|
</IconButton>
|
|
@@ -25,6 +25,8 @@ import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext
|
|
|
25
25
|
import { mergeRegister } from '@lexical/utils';
|
|
26
26
|
|
|
27
27
|
import { IconButton } from '../../../icon-button';
|
|
28
|
+
import { useTr } from '../../i18n';
|
|
29
|
+
import { IS_APPLE } from '../../utils/environment';
|
|
28
30
|
import { getDOMRangeRect } from '../../utils/getDOMRangeRect';
|
|
29
31
|
import { getSelectedNode } from '../../utils/getSelectedNode';
|
|
30
32
|
import { setFloatingElemPosition } from '../../utils/setFloatingElemPosition';
|
|
@@ -53,6 +55,7 @@ function TextFormatFloatingToolbar({
|
|
|
53
55
|
isUnderline: boolean;
|
|
54
56
|
}): JSX.Element {
|
|
55
57
|
const popupCharStylesEditorRef = useRef<HTMLDivElement | null>(null);
|
|
58
|
+
const tr = useTr();
|
|
56
59
|
|
|
57
60
|
const insertLink = useCallback(() => {
|
|
58
61
|
if (!isLink) {
|
|
@@ -139,7 +142,8 @@ function TextFormatFloatingToolbar({
|
|
|
139
142
|
editor.dispatchCommand(FORMAT_TEXT_COMMAND, 'bold');
|
|
140
143
|
}}
|
|
141
144
|
style={{ padding: 0, overflow: 'hidden' }}
|
|
142
|
-
|
|
145
|
+
title={tr(IS_APPLE ? 'actionFormatAsStrongTitleApple' : 'actionFormatAsStrongTitle')}
|
|
146
|
+
aria-label={tr('actionFormatAsStrongLabel')}
|
|
143
147
|
>
|
|
144
148
|
<i
|
|
145
149
|
className={`format bold w-full h-full bg-[length:18px_18px] bg-no-repeat bg-center ${
|
|
@@ -152,7 +156,8 @@ function TextFormatFloatingToolbar({
|
|
|
152
156
|
onClick={() => {
|
|
153
157
|
editor.dispatchCommand(FORMAT_TEXT_COMMAND, 'italic');
|
|
154
158
|
}}
|
|
155
|
-
|
|
159
|
+
title={tr('actionFormatAsEmphasizedTitle')}
|
|
160
|
+
aria-label={tr('actionFormatAsEmphasizedLabel')}
|
|
156
161
|
>
|
|
157
162
|
<i
|
|
158
163
|
className={`format italic w-full h-full bg-[length:18px_18px] bg-no-repeat bg-center ${
|
|
@@ -165,7 +170,8 @@ function TextFormatFloatingToolbar({
|
|
|
165
170
|
onClick={() => {
|
|
166
171
|
editor.dispatchCommand(FORMAT_TEXT_COMMAND, 'underline');
|
|
167
172
|
}}
|
|
168
|
-
|
|
173
|
+
title={tr('actionFormatAsUnderlinedTitle')}
|
|
174
|
+
aria-label={tr('actionFormatAsUnderlinedLabel')}
|
|
169
175
|
>
|
|
170
176
|
<i
|
|
171
177
|
className={`format underline w-full h-full bg-[length:18px_18px] bg-no-repeat bg-center ${
|
|
@@ -178,7 +184,8 @@ function TextFormatFloatingToolbar({
|
|
|
178
184
|
onClick={() => {
|
|
179
185
|
editor.dispatchCommand(FORMAT_TEXT_COMMAND, 'strikethrough');
|
|
180
186
|
}}
|
|
181
|
-
|
|
187
|
+
title={tr('actionFormatWithStrikethroughTitle')}
|
|
188
|
+
aria-label={tr('actionFormatWithStrikethroughLabel')}
|
|
182
189
|
>
|
|
183
190
|
<i
|
|
184
191
|
className={`format strikethrough w-full h-full bg-[length:18px_18px] bg-no-repeat bg-center ${
|
|
@@ -191,8 +198,8 @@ function TextFormatFloatingToolbar({
|
|
|
191
198
|
onClick={() => {
|
|
192
199
|
editor.dispatchCommand(FORMAT_TEXT_COMMAND, 'subscript');
|
|
193
200
|
}}
|
|
194
|
-
title=
|
|
195
|
-
aria-label=
|
|
201
|
+
title={tr('actionFormatWithSubscriptTitle')}
|
|
202
|
+
aria-label={tr('actionFormatWithSubscriptLabel')}
|
|
196
203
|
>
|
|
197
204
|
<i
|
|
198
205
|
className={`format subscript w-full h-full bg-[length:18px_18px] bg-no-repeat bg-center ${
|
|
@@ -205,8 +212,8 @@ function TextFormatFloatingToolbar({
|
|
|
205
212
|
onClick={() => {
|
|
206
213
|
editor.dispatchCommand(FORMAT_TEXT_COMMAND, 'superscript');
|
|
207
214
|
}}
|
|
208
|
-
title=
|
|
209
|
-
aria-label=
|
|
215
|
+
title={tr('actionFormatWithSuperscriptTitle')}
|
|
216
|
+
aria-label={tr('actionFormatWithSuperscriptLabel')}
|
|
210
217
|
>
|
|
211
218
|
<i
|
|
212
219
|
className={`format superscript w-full h-full bg-[length:18px_18px] bg-no-repeat bg-center ${
|
|
@@ -219,7 +226,7 @@ function TextFormatFloatingToolbar({
|
|
|
219
226
|
onClick={() => {
|
|
220
227
|
editor.dispatchCommand(FORMAT_TEXT_COMMAND, 'code');
|
|
221
228
|
}}
|
|
222
|
-
aria-label=
|
|
229
|
+
aria-label={tr('actionInsertCodeBlock')}
|
|
223
230
|
>
|
|
224
231
|
<i
|
|
225
232
|
className={`format code w-full h-full bg-[length:18px_18px] bg-no-repeat bg-center ${
|
|
@@ -227,7 +234,11 @@ function TextFormatFloatingToolbar({
|
|
|
227
234
|
}`}
|
|
228
235
|
/>
|
|
229
236
|
</IconButton>
|
|
230
|
-
<IconButton
|
|
237
|
+
<IconButton
|
|
238
|
+
style={{ padding: 0, overflow: 'hidden' }}
|
|
239
|
+
onClick={insertLink}
|
|
240
|
+
aria-label={tr('actionInsertlink')}
|
|
241
|
+
>
|
|
231
242
|
<i
|
|
232
243
|
className={`format link w-full h-full bg-[length:18px_18px] bg-no-repeat bg-center ${
|
|
233
244
|
isLink ? 'bg-purple-50-900 opacity-100' : 'opacity-60'
|
|
@@ -6,14 +6,9 @@
|
|
|
6
6
|
*
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
|
|
12
|
-
$isRangeSelection,
|
|
13
|
-
$setSelection,
|
|
14
|
-
FOCUS_COMMAND,
|
|
15
|
-
} from 'lexical';
|
|
16
|
-
import {useEffect} from 'react';
|
|
9
|
+
import { useEffect } from 'react';
|
|
10
|
+
import { $getSelection, $isRangeSelection, $setSelection, FOCUS_COMMAND } from 'lexical';
|
|
11
|
+
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
|
|
17
12
|
|
|
18
13
|
const COMMAND_PRIORITY_LOW = 1;
|
|
19
14
|
const TAB_TO_FOCUS_INTERVAL = 100;
|
|
@@ -48,10 +43,7 @@ export default function TabFocusPlugin(): null {
|
|
|
48
43
|
(event: FocusEvent) => {
|
|
49
44
|
const selection = $getSelection();
|
|
50
45
|
if ($isRangeSelection(selection)) {
|
|
51
|
-
if (
|
|
52
|
-
lastTabKeyDownTimestamp + TAB_TO_FOCUS_INTERVAL >
|
|
53
|
-
event.timeStamp
|
|
54
|
-
) {
|
|
46
|
+
if (lastTabKeyDownTimestamp + TAB_TO_FOCUS_INTERVAL > event.timeStamp) {
|
|
55
47
|
$setSelection(selection.clone());
|
|
56
48
|
}
|
|
57
49
|
}
|