@pega/cosmos-react-rte 9.0.0-build.9.9 → 9.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/lib/components/DynamicContentEditor/DynamicContentEditor.d.ts +4 -2
- package/lib/components/DynamicContentEditor/DynamicContentEditor.d.ts.map +1 -1
- package/lib/components/DynamicContentEditor/DynamicContentEditor.js +64 -59
- package/lib/components/DynamicContentEditor/DynamicContentEditor.js.map +1 -1
- package/lib/components/Editor/Editor.context.d.ts +6 -6
- package/lib/components/Editor/Editor.context.d.ts.map +1 -1
- package/lib/components/Editor/Editor.context.js +1 -1
- package/lib/components/Editor/Editor.context.js.map +1 -1
- package/lib/components/Editor/Editor.d.ts +1 -10
- package/lib/components/Editor/Editor.d.ts.map +1 -1
- package/lib/components/Editor/Editor.js +301 -490
- package/lib/components/Editor/Editor.js.map +1 -1
- package/lib/components/Editor/Editor.styles.d.ts +37 -7
- package/lib/components/Editor/Editor.styles.d.ts.map +1 -1
- package/lib/components/Editor/Editor.styles.js +60 -30
- package/lib/components/Editor/Editor.styles.js.map +1 -1
- package/lib/components/Editor/Editor.test-ids.d.ts +2 -1
- package/lib/components/Editor/Editor.test-ids.d.ts.map +1 -1
- package/lib/components/Editor/Editor.test-ids.js +2 -0
- package/lib/components/Editor/Editor.test-ids.js.map +1 -1
- package/lib/components/Editor/Editor.types.d.ts +34 -14
- package/lib/components/Editor/Editor.types.d.ts.map +1 -1
- package/lib/components/Editor/Editor.types.js.map +1 -1
- package/lib/components/Editor/IframeTiptapEditor.d.ts +30 -0
- package/lib/components/Editor/IframeTiptapEditor.d.ts.map +1 -0
- package/lib/components/Editor/IframeTiptapEditor.js +695 -0
- package/lib/components/Editor/IframeTiptapEditor.js.map +1 -0
- package/lib/components/Editor/ImageActionButtons.d.ts +20 -0
- package/lib/components/Editor/ImageActionButtons.d.ts.map +1 -0
- package/lib/components/Editor/ImageActionButtons.js +84 -0
- package/lib/components/Editor/ImageActionButtons.js.map +1 -0
- package/lib/components/Editor/ImageEditDialog.d.ts +17 -0
- package/lib/components/Editor/ImageEditDialog.d.ts.map +1 -0
- package/lib/components/Editor/ImageEditDialog.js +90 -0
- package/lib/components/Editor/ImageEditDialog.js.map +1 -0
- package/lib/components/Editor/TableCellMenu.d.ts +35 -0
- package/lib/components/Editor/TableCellMenu.d.ts.map +1 -0
- package/lib/components/Editor/TableCellMenu.js +120 -0
- package/lib/components/Editor/TableCellMenu.js.map +1 -0
- package/lib/components/Editor/Toolbar/AIRewriteButton.d.ts +17 -0
- package/lib/components/Editor/Toolbar/AIRewriteButton.d.ts.map +1 -0
- package/lib/components/Editor/Toolbar/AIRewriteButton.js +79 -0
- package/lib/components/Editor/Toolbar/AIRewriteButton.js.map +1 -0
- package/lib/components/Editor/Toolbar/AlignmentSelect.d.ts +8 -0
- package/lib/components/Editor/Toolbar/AlignmentSelect.d.ts.map +1 -0
- package/lib/components/Editor/Toolbar/AlignmentSelect.js +137 -0
- package/lib/components/Editor/Toolbar/AlignmentSelect.js.map +1 -0
- package/lib/components/Editor/Toolbar/AnchorButton.d.ts +3 -4
- package/lib/components/Editor/Toolbar/AnchorButton.d.ts.map +1 -1
- package/lib/components/Editor/Toolbar/AnchorButton.js +156 -82
- package/lib/components/Editor/Toolbar/AnchorButton.js.map +1 -1
- package/lib/components/Editor/Toolbar/ColorPickerButton.d.ts +9 -0
- package/lib/components/Editor/Toolbar/ColorPickerButton.d.ts.map +1 -0
- package/lib/components/Editor/Toolbar/ColorPickerButton.js +190 -0
- package/lib/components/Editor/Toolbar/ColorPickerButton.js.map +1 -0
- package/lib/components/Editor/Toolbar/FontFamilySelect.d.ts +8 -0
- package/lib/components/Editor/Toolbar/FontFamilySelect.d.ts.map +1 -0
- package/lib/components/Editor/Toolbar/FontFamilySelect.js +150 -0
- package/lib/components/Editor/Toolbar/FontFamilySelect.js.map +1 -0
- package/lib/components/Editor/Toolbar/FontSizeSelect.d.ts +8 -0
- package/lib/components/Editor/Toolbar/FontSizeSelect.d.ts.map +1 -0
- package/lib/components/Editor/Toolbar/FontSizeSelect.js +145 -0
- package/lib/components/Editor/Toolbar/FontSizeSelect.js.map +1 -0
- package/lib/components/Editor/Toolbar/ImageButton.d.ts +5 -5
- package/lib/components/Editor/Toolbar/ImageButton.d.ts.map +1 -1
- package/lib/components/Editor/Toolbar/ImageButton.js +131 -18
- package/lib/components/Editor/Toolbar/ImageButton.js.map +1 -1
- package/lib/components/Editor/Toolbar/SourceCodeButton.d.ts +8 -0
- package/lib/components/Editor/Toolbar/SourceCodeButton.d.ts.map +1 -0
- package/lib/components/Editor/Toolbar/SourceCodeButton.js +49 -0
- package/lib/components/Editor/Toolbar/SourceCodeButton.js.map +1 -0
- package/lib/components/Editor/Toolbar/TableButton.d.ts +8 -0
- package/lib/components/Editor/Toolbar/TableButton.d.ts.map +1 -0
- package/lib/components/Editor/Toolbar/TableButton.js +291 -0
- package/lib/components/Editor/Toolbar/TableButton.js.map +1 -0
- package/lib/components/Editor/Toolbar/TextSelect.d.ts +4 -5
- package/lib/components/Editor/Toolbar/TextSelect.d.ts.map +1 -1
- package/lib/components/Editor/Toolbar/TextSelect.js +61 -30
- package/lib/components/Editor/Toolbar/TextSelect.js.map +1 -1
- package/lib/components/Editor/Toolbar/Toolbar.d.ts +17 -6
- package/lib/components/Editor/Toolbar/Toolbar.d.ts.map +1 -1
- package/lib/components/Editor/Toolbar/Toolbar.js +169 -47
- package/lib/components/Editor/Toolbar/Toolbar.js.map +1 -1
- package/lib/components/Editor/Toolbar/Toolbar.test-ids.d.ts +2 -2
- package/lib/components/Editor/Toolbar/Toolbar.test-ids.d.ts.map +1 -1
- package/lib/components/Editor/Toolbar/Toolbar.test-ids.js +17 -1
- package/lib/components/Editor/Toolbar/Toolbar.test-ids.js.map +1 -1
- package/lib/components/Editor/Toolbar/WordCount.d.ts +8 -0
- package/lib/components/Editor/Toolbar/WordCount.d.ts.map +1 -0
- package/lib/components/Editor/Toolbar/WordCount.js +31 -0
- package/lib/components/Editor/Toolbar/WordCount.js.map +1 -0
- package/lib/components/Editor/extensions/FontSize.d.ts +21 -0
- package/lib/components/Editor/extensions/FontSize.d.ts.map +1 -0
- package/lib/components/Editor/extensions/FontSize.js +42 -0
- package/lib/components/Editor/extensions/FontSize.js.map +1 -0
- package/lib/components/Editor/extensions/PreserveDiv.d.ts +13 -0
- package/lib/components/Editor/extensions/PreserveDiv.d.ts.map +1 -0
- package/lib/components/Editor/extensions/PreserveDiv.js +73 -0
- package/lib/components/Editor/extensions/PreserveDiv.js.map +1 -0
- package/lib/components/Editor/extensions/TableCellSelection.d.ts +4 -0
- package/lib/components/Editor/extensions/TableCellSelection.d.ts.map +1 -0
- package/lib/components/Editor/extensions/TableCellSelection.js +53 -0
- package/lib/components/Editor/extensions/TableCellSelection.js.map +1 -0
- package/lib/components/Editor/extensions/TextIndent.d.ts +22 -0
- package/lib/components/Editor/extensions/TextIndent.d.ts.map +1 -0
- package/lib/components/Editor/extensions/TextIndent.js +137 -0
- package/lib/components/Editor/extensions/TextIndent.js.map +1 -0
- package/lib/components/Editor/hooks/useCloseOnEditorClick.d.ts +5 -0
- package/lib/components/Editor/hooks/useCloseOnEditorClick.d.ts.map +1 -0
- package/lib/components/Editor/hooks/useCloseOnEditorClick.js +18 -0
- package/lib/components/Editor/hooks/useCloseOnEditorClick.js.map +1 -0
- package/lib/components/Editor/hooks/useEscapeKey.d.ts +4 -0
- package/lib/components/Editor/hooks/useEscapeKey.d.ts.map +1 -0
- package/lib/components/Editor/hooks/useEscapeKey.js +24 -0
- package/lib/components/Editor/hooks/useEscapeKey.js.map +1 -0
- package/lib/components/Editor/hooks/useIframeSetup.d.ts +54 -0
- package/lib/components/Editor/hooks/useIframeSetup.d.ts.map +1 -0
- package/lib/components/Editor/hooks/useIframeSetup.js +284 -0
- package/lib/components/Editor/hooks/useIframeSetup.js.map +1 -0
- package/lib/components/Editor/hooks/useImageActions.d.ts +19 -0
- package/lib/components/Editor/hooks/useImageActions.d.ts.map +1 -0
- package/lib/components/Editor/hooks/useImageActions.js +198 -0
- package/lib/components/Editor/hooks/useImageActions.js.map +1 -0
- package/lib/components/Editor/hooks/useTableCellMenu.d.ts +22 -0
- package/lib/components/Editor/hooks/useTableCellMenu.d.ts.map +1 -0
- package/lib/components/Editor/hooks/useTableCellMenu.js +120 -0
- package/lib/components/Editor/hooks/useTableCellMenu.js.map +1 -0
- package/lib/components/Editor/iframeContentStyles.d.ts +10 -0
- package/lib/components/Editor/iframeContentStyles.d.ts.map +1 -0
- package/lib/components/Editor/iframeContentStyles.js +162 -0
- package/lib/components/Editor/iframeContentStyles.js.map +1 -0
- package/lib/components/Editor/index.d.ts +2 -0
- package/lib/components/Editor/index.d.ts.map +1 -1
- package/lib/components/Editor/index.js +1 -0
- package/lib/components/Editor/index.js.map +1 -1
- package/lib/components/Editor/sanitize.d.ts +3 -0
- package/lib/components/Editor/sanitize.d.ts.map +1 -0
- package/lib/components/Editor/sanitize.js +11 -0
- package/lib/components/Editor/sanitize.js.map +1 -0
- package/lib/components/Editor/utils/htmlPlaceholder.d.ts +69 -0
- package/lib/components/Editor/utils/htmlPlaceholder.d.ts.map +1 -0
- package/lib/components/Editor/utils/htmlPlaceholder.js +154 -0
- package/lib/components/Editor/utils/htmlPlaceholder.js.map +1 -0
- package/lib/components/RichTextEditor/DecoratorComponents/Table.d.ts +6 -4
- package/lib/components/RichTextEditor/DecoratorComponents/Table.d.ts.map +1 -1
- package/lib/components/RichTextEditor/DecoratorComponents/Table.js +10 -8
- package/lib/components/RichTextEditor/DecoratorComponents/Table.js.map +1 -1
- package/lib/components/RichTextEditor/RichTextEditor.d.ts.map +1 -1
- package/lib/components/RichTextEditor/RichTextEditor.js +15 -2
- package/lib/components/RichTextEditor/RichTextEditor.js.map +1 -1
- package/lib/components/RichTextEditor/RichTextEditor.styles.d.ts +5 -5
- package/lib/components/RichTextEditor/RichTextEditor.styles.d.ts.map +1 -1
- package/lib/components/RichTextEditor/RichTextEditor.styles.js +3 -5
- package/lib/components/RichTextEditor/RichTextEditor.styles.js.map +1 -1
- package/lib/components/RichTextEditor/RichTextEditor.types.d.ts +5 -0
- package/lib/components/RichTextEditor/RichTextEditor.types.d.ts.map +1 -1
- package/lib/components/RichTextEditor/RichTextEditor.types.js.map +1 -1
- package/lib/components/RichTextEditor/RichTextViewer.d.ts.map +1 -1
- package/lib/components/RichTextEditor/RichTextViewer.js +9 -2
- package/lib/components/RichTextEditor/RichTextViewer.js.map +1 -1
- package/lib/components/RichTextEditor/Toolbar/Toolbar.js +1 -1
- package/lib/components/RichTextEditor/Toolbar/Toolbar.js.map +1 -1
- package/lib/components/RichTextEditor/Toolbar/Toolbar.types.d.ts +4 -4
- package/lib/components/RichTextEditor/Toolbar/Toolbar.types.d.ts.map +1 -1
- package/lib/components/RichTextEditor/Toolbar/Toolbar.types.js.map +1 -1
- package/lib/components/RichTextEditor/Toolbar/ToolbarButton.d.ts.map +1 -1
- package/lib/components/RichTextEditor/Toolbar/ToolbarButton.js +41 -26
- package/lib/components/RichTextEditor/Toolbar/ToolbarButton.js.map +1 -1
- package/lib/components/RichTextEditor/utils/htmlConverter.d.ts +2 -0
- package/lib/components/RichTextEditor/utils/htmlConverter.d.ts.map +1 -1
- package/lib/components/RichTextEditor/utils/htmlConverter.js +12 -0
- package/lib/components/RichTextEditor/utils/htmlConverter.js.map +1 -1
- package/lib/components/RichTextEditor/utils/interactionRenderer.d.ts.map +1 -1
- package/lib/components/RichTextEditor/utils/interactionRenderer.js +20 -19
- package/lib/components/RichTextEditor/utils/interactionRenderer.js.map +1 -1
- package/lib/components/RichTextEditor/utils/markdownConverter.d.ts.map +1 -1
- package/lib/components/RichTextEditor/utils/markdownConverter.js +131 -30
- package/lib/components/RichTextEditor/utils/markdownConverter.js.map +1 -1
- package/lib/components/RichTextEditor/utils/renderers.d.ts +5 -3
- package/lib/components/RichTextEditor/utils/renderers.d.ts.map +1 -1
- package/lib/components/RichTextEditor/utils/renderers.js +62 -34
- package/lib/components/RichTextEditor/utils/renderers.js.map +1 -1
- package/lib/components/RichTextEditor/utils/slateConverter.d.ts +4 -3
- package/lib/components/RichTextEditor/utils/slateConverter.d.ts.map +1 -1
- package/lib/components/RichTextEditor/utils/slateConverter.js +86 -38
- package/lib/components/RichTextEditor/utils/slateConverter.js.map +1 -1
- package/package.json +30 -8
- package/lib/components/Editor/ImageEditor.d.ts +0 -10
- package/lib/components/Editor/ImageEditor.d.ts.map +0 -1
- package/lib/components/Editor/ImageEditor.js +0 -292
- package/lib/components/Editor/ImageEditor.js.map +0 -1
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useRef, useState } from 'react';
|
|
3
|
+
import { AIRewrite, Icon, registerIcon, useElement, useI18n } from '@pega/cosmos-react-core';
|
|
4
|
+
import * as polarisIcon from '@pega/cosmos-react-core/lib/components/Icon/icons/polaris.icon';
|
|
5
|
+
import ToolbarButton from '../../RichTextEditor/Toolbar/ToolbarButton';
|
|
6
|
+
import { extractLinkAndImg, restorePlaceholders } from '../utils/htmlPlaceholder';
|
|
7
|
+
registerIcon(polarisIcon);
|
|
8
|
+
const AIRewriteButton = ({ editor, onRewriteClick, renderSuggestionEditor }) => {
|
|
9
|
+
const t = useI18n();
|
|
10
|
+
const [buttonEl, setButtonEl] = useElement();
|
|
11
|
+
const [showPopover, setShowPopover] = useState(false);
|
|
12
|
+
const [isGenerating, setIsGenerating] = useState(false);
|
|
13
|
+
const [suggestion, setSuggestion] = useState(undefined);
|
|
14
|
+
const [hasSuggestionContent, setHasSuggestionContent] = useState(false);
|
|
15
|
+
const getContentRef = useRef(undefined);
|
|
16
|
+
const generationTokenRef = useRef(0);
|
|
17
|
+
const placeholderMapRef = useRef({});
|
|
18
|
+
const userPlaceholdersRef = useRef([]);
|
|
19
|
+
const getHtmlContent = () => {
|
|
20
|
+
return editor ? editor.getHTML().trim() : '';
|
|
21
|
+
};
|
|
22
|
+
const hasContent = () => {
|
|
23
|
+
return editor ? editor.getText().trim() !== '' : false;
|
|
24
|
+
};
|
|
25
|
+
const handleGenerate = (rewriteAction, additionalInstructions) => {
|
|
26
|
+
const originalContent = getHtmlContent();
|
|
27
|
+
const { placeholderHtml, placeholderMap, userPlaceholders } = extractLinkAndImg(originalContent);
|
|
28
|
+
placeholderMapRef.current = placeholderMap;
|
|
29
|
+
userPlaceholdersRef.current = userPlaceholders;
|
|
30
|
+
generationTokenRef.current += 1;
|
|
31
|
+
const token = generationTokenRef.current;
|
|
32
|
+
setIsGenerating(true);
|
|
33
|
+
setSuggestion(undefined);
|
|
34
|
+
setHasSuggestionContent(false);
|
|
35
|
+
onRewriteClick(placeholderHtml, rewriteAction, additionalInstructions || '', (rewrittenText) => {
|
|
36
|
+
if (token !== generationTokenRef.current)
|
|
37
|
+
return;
|
|
38
|
+
setIsGenerating(false);
|
|
39
|
+
const trimmedText = rewrittenText.trim();
|
|
40
|
+
setSuggestion(trimmedText || undefined);
|
|
41
|
+
setHasSuggestionContent(trimmedText.length > 0);
|
|
42
|
+
});
|
|
43
|
+
};
|
|
44
|
+
const handleSubmit = () => {
|
|
45
|
+
const editedContent = getContentRef.current?.() ?? suggestion ?? '';
|
|
46
|
+
const restoredContent = restorePlaceholders(editedContent, placeholderMapRef.current, userPlaceholdersRef.current);
|
|
47
|
+
editor.chain().setContent(restoredContent).focus('end').run();
|
|
48
|
+
setShowPopover(false);
|
|
49
|
+
setSuggestion(undefined);
|
|
50
|
+
setHasSuggestionContent(false);
|
|
51
|
+
placeholderMapRef.current = {};
|
|
52
|
+
userPlaceholdersRef.current = [];
|
|
53
|
+
};
|
|
54
|
+
const handleCancel = () => {
|
|
55
|
+
generationTokenRef.current += 1;
|
|
56
|
+
setShowPopover(false);
|
|
57
|
+
setSuggestion(undefined);
|
|
58
|
+
setIsGenerating(false);
|
|
59
|
+
setHasSuggestionContent(false);
|
|
60
|
+
placeholderMapRef.current = {};
|
|
61
|
+
userPlaceholdersRef.current = [];
|
|
62
|
+
buttonEl?.focus();
|
|
63
|
+
};
|
|
64
|
+
return (_jsxs(_Fragment, { children: [_jsx(ToolbarButton, { ref: setButtonEl, onClick: () => {
|
|
65
|
+
setShowPopover(true);
|
|
66
|
+
}, disabled: !hasContent(), tooltip: t('ai_rewrite_title'), label: t('ai_rewrite_title'), children: _jsx(Icon, { name: 'polaris' }) }), showPopover && buttonEl && (_jsx(AIRewrite, { target: buttonEl, isGenerating: isGenerating, suggestion: hasSuggestionContent ? suggestion : undefined, onGenerate: handleGenerate, onSubmit: handleSubmit, onCancel: handleCancel, suggestionEditor: renderSuggestionEditor
|
|
67
|
+
? renderSuggestionEditor({
|
|
68
|
+
defaultValue: suggestion,
|
|
69
|
+
disabled: isGenerating || !suggestion,
|
|
70
|
+
readOnly: false,
|
|
71
|
+
onGetContent: getContent => {
|
|
72
|
+
getContentRef.current = getContent;
|
|
73
|
+
},
|
|
74
|
+
onContentChange: setHasSuggestionContent
|
|
75
|
+
})
|
|
76
|
+
: undefined }))] }));
|
|
77
|
+
};
|
|
78
|
+
export default AIRewriteButton;
|
|
79
|
+
//# sourceMappingURL=AIRewriteButton.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AIRewriteButton.js","sourceRoot":"","sources":["../../../../src/components/Editor/Toolbar/AIRewriteButton.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useRef, useState } from 'react';\nimport type { ReactNode } from 'react';\nimport type { Editor as TiptapEditor } from '@tiptap/core';\n\nimport { AIRewrite, Icon, registerIcon, useElement, useI18n } from '@pega/cosmos-react-core';\nimport type { AIAction } from '@pega/cosmos-react-core';\nimport * as polarisIcon from '@pega/cosmos-react-core/lib/components/Icon/icons/polaris.icon';\n\nimport ToolbarButton from '../../RichTextEditor/Toolbar/ToolbarButton';\nimport { extractLinkAndImg, restorePlaceholders } from '../utils/htmlPlaceholder';\nimport type { PlaceholderMap } from '../utils/htmlPlaceholder';\n\nregisterIcon(polarisIcon);\n\nexport interface AIRewriteButtonProps {\n editor: TiptapEditor;\n onRewriteClick: (\n originalText: string,\n rewriteAction: AIAction,\n additionalInstructions: string,\n onSuccess: (rewrittenText: string) => void\n ) => void;\n renderSuggestionEditor?: (props: {\n defaultValue?: string;\n disabled: boolean;\n readOnly: boolean;\n onGetContent: (getContent: () => string) => void;\n onContentChange: (hasContent: boolean) => void;\n }) => ReactNode;\n}\n\nconst AIRewriteButton = ({\n editor,\n onRewriteClick,\n renderSuggestionEditor\n}: AIRewriteButtonProps) => {\n const t = useI18n();\n const [buttonEl, setButtonEl] = useElement<HTMLElement>();\n const [showPopover, setShowPopover] = useState(false);\n const [isGenerating, setIsGenerating] = useState(false);\n const [suggestion, setSuggestion] = useState<string | undefined>(undefined);\n const [hasSuggestionContent, setHasSuggestionContent] = useState(false);\n const getContentRef = useRef<(() => string) | undefined>(undefined);\n const generationTokenRef = useRef(0);\n const placeholderMapRef = useRef<PlaceholderMap>({});\n const userPlaceholdersRef = useRef<string[]>([]);\n\n const getHtmlContent = (): string => {\n return editor ? editor.getHTML().trim() : '';\n };\n\n const hasContent = (): boolean => {\n return editor ? editor.getText().trim() !== '' : false;\n };\n\n const handleGenerate = (rewriteAction: AIAction, additionalInstructions: string) => {\n const originalContent = getHtmlContent();\n const { placeholderHtml, placeholderMap, userPlaceholders } =\n extractLinkAndImg(originalContent);\n placeholderMapRef.current = placeholderMap;\n userPlaceholdersRef.current = userPlaceholders;\n generationTokenRef.current += 1;\n const token = generationTokenRef.current;\n setIsGenerating(true);\n setSuggestion(undefined);\n setHasSuggestionContent(false);\n\n onRewriteClick(\n placeholderHtml,\n rewriteAction,\n additionalInstructions || '',\n (rewrittenText: string) => {\n if (token !== generationTokenRef.current) return;\n setIsGenerating(false);\n const trimmedText = rewrittenText.trim();\n setSuggestion(trimmedText || undefined);\n setHasSuggestionContent(trimmedText.length > 0);\n }\n );\n };\n\n const handleSubmit = () => {\n const editedContent = getContentRef.current?.() ?? suggestion ?? '';\n const restoredContent = restorePlaceholders(\n editedContent,\n placeholderMapRef.current,\n userPlaceholdersRef.current\n );\n editor.chain().setContent(restoredContent).focus('end').run();\n setShowPopover(false);\n setSuggestion(undefined);\n setHasSuggestionContent(false);\n placeholderMapRef.current = {};\n userPlaceholdersRef.current = [];\n };\n\n const handleCancel = () => {\n generationTokenRef.current += 1;\n setShowPopover(false);\n setSuggestion(undefined);\n setIsGenerating(false);\n setHasSuggestionContent(false);\n placeholderMapRef.current = {};\n userPlaceholdersRef.current = [];\n buttonEl?.focus();\n };\n\n return (\n <>\n <ToolbarButton\n ref={setButtonEl}\n onClick={() => {\n setShowPopover(true);\n }}\n disabled={!hasContent()}\n tooltip={t('ai_rewrite_title')}\n label={t('ai_rewrite_title')}\n >\n <Icon name='polaris' />\n </ToolbarButton>\n {showPopover && buttonEl && (\n <AIRewrite\n target={buttonEl}\n isGenerating={isGenerating}\n suggestion={hasSuggestionContent ? suggestion : undefined}\n onGenerate={handleGenerate}\n onSubmit={handleSubmit}\n onCancel={handleCancel}\n suggestionEditor={\n renderSuggestionEditor\n ? renderSuggestionEditor({\n defaultValue: suggestion,\n disabled: isGenerating || !suggestion,\n readOnly: false,\n onGetContent: getContent => {\n getContentRef.current = getContent;\n },\n onContentChange: setHasSuggestionContent\n })\n : undefined\n }\n />\n )}\n </>\n );\n};\n\nexport default AIRewriteButton;\n"]}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Editor as TiptapEditor } from '@tiptap/core';
|
|
2
|
+
interface AlignmentSelectProps {
|
|
3
|
+
editor: TiptapEditor;
|
|
4
|
+
'data-testid'?: string;
|
|
5
|
+
}
|
|
6
|
+
declare const AlignmentSelect: ({ editor, ...restProps }: AlignmentSelectProps) => import("react/jsx-runtime").JSX.Element;
|
|
7
|
+
export default AlignmentSelect;
|
|
8
|
+
//# sourceMappingURL=AlignmentSelect.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AlignmentSelect.d.ts","sourceRoot":"","sources":["../../../../src/components/Editor/Toolbar/AlignmentSelect.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,MAAM,IAAI,YAAY,EAAE,MAAM,cAAc,CAAC;AAsD3D,UAAU,oBAAoB;IAC5B,MAAM,EAAE,YAAY,CAAC;IACrB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,QAAA,MAAM,eAAe,GAAI,0BAA0B,oBAAoB,4CAwJtE,CAAC;AAEF,eAAe,eAAe,CAAC"}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useRef, useState, useEffect, useCallback } from 'react';
|
|
3
|
+
import styled, { css } from 'styled-components';
|
|
4
|
+
import { Popover, Icon, registerIcon, useOuterEvent, Menu, Button, defaultThemeProp, useI18n, Tooltip, isInstance } from '@pega/cosmos-react-core';
|
|
5
|
+
import * as arrowMicroDownIcon from '@pega/cosmos-react-core/lib/components/Icon/icons/arrow-micro-down.icon';
|
|
6
|
+
import * as alignLeftIcon from '@pega/cosmos-react-core/lib/components/Icon/icons/align-left.icon';
|
|
7
|
+
import * as alignCenterIcon from '@pega/cosmos-react-core/lib/components/Icon/icons/align-center.icon';
|
|
8
|
+
import * as alignRightIcon from '@pega/cosmos-react-core/lib/components/Icon/icons/align-right.icon';
|
|
9
|
+
import useEscapeKey from '../hooks/useEscapeKey';
|
|
10
|
+
import useCloseOnEditorClick from '../hooks/useCloseOnEditorClick';
|
|
11
|
+
registerIcon(arrowMicroDownIcon, alignLeftIcon, alignCenterIcon, alignRightIcon);
|
|
12
|
+
const StyledAlignmentSelect = styled(Button)(({ theme }) => {
|
|
13
|
+
return css `
|
|
14
|
+
color: ${theme.base.palette['foreground-color']};
|
|
15
|
+
display: inline-flex;
|
|
16
|
+
align-items: center;
|
|
17
|
+
height: calc(3.5 * ${theme.base.spacing});
|
|
18
|
+
padding: 0 calc(0.5 * ${theme.base.spacing});
|
|
19
|
+
`;
|
|
20
|
+
});
|
|
21
|
+
StyledAlignmentSelect.defaultProps = defaultThemeProp;
|
|
22
|
+
const StyledSelectMenu = styled.div(({ theme }) => {
|
|
23
|
+
return css `
|
|
24
|
+
min-width: calc(20 * ${theme.base.spacing});
|
|
25
|
+
`;
|
|
26
|
+
});
|
|
27
|
+
StyledSelectMenu.defaultProps = defaultThemeProp;
|
|
28
|
+
const ALIGNMENTS = [
|
|
29
|
+
{ id: 'left', icon: 'align-left', labelKey: 'rte_align_left' },
|
|
30
|
+
{ id: 'center', icon: 'align-center', labelKey: 'rte_align_center' },
|
|
31
|
+
{ id: 'right', icon: 'align-right', labelKey: 'rte_align_right' }
|
|
32
|
+
];
|
|
33
|
+
const AlignmentSelect = ({ editor, ...restProps }) => {
|
|
34
|
+
const t = useI18n();
|
|
35
|
+
const [open, setOpen] = useState(false);
|
|
36
|
+
const [activeAlignment, setActiveAlignment] = useState('left');
|
|
37
|
+
const selecting = useRef(false);
|
|
38
|
+
const openedByClick = useRef(false);
|
|
39
|
+
const buttonRef = useRef(null);
|
|
40
|
+
const popoverRef = useRef(null);
|
|
41
|
+
const menuRef = useRef(null);
|
|
42
|
+
useOuterEvent('mousedown', [buttonRef, popoverRef, menuRef], () => {
|
|
43
|
+
setOpen(false);
|
|
44
|
+
});
|
|
45
|
+
const handleEscapeClose = useCallback(() => {
|
|
46
|
+
setOpen(false);
|
|
47
|
+
buttonRef.current?.focus();
|
|
48
|
+
}, []);
|
|
49
|
+
useEscapeKey(open, handleEscapeClose, editor);
|
|
50
|
+
useCloseOnEditorClick(open, handleEscapeClose);
|
|
51
|
+
const updateActiveAlignment = useCallback(() => {
|
|
52
|
+
if (editor.isActive({ textAlign: 'center' })) {
|
|
53
|
+
setActiveAlignment('center');
|
|
54
|
+
}
|
|
55
|
+
else if (editor.isActive({ textAlign: 'right' })) {
|
|
56
|
+
setActiveAlignment('right');
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
setActiveAlignment('left');
|
|
60
|
+
}
|
|
61
|
+
}, [editor]);
|
|
62
|
+
useEffect(() => {
|
|
63
|
+
updateActiveAlignment();
|
|
64
|
+
editor.on('selectionUpdate', updateActiveAlignment);
|
|
65
|
+
editor.on('transaction', updateActiveAlignment);
|
|
66
|
+
return () => {
|
|
67
|
+
editor.off('selectionUpdate', updateActiveAlignment);
|
|
68
|
+
editor.off('transaction', updateActiveAlignment);
|
|
69
|
+
};
|
|
70
|
+
}, [editor, updateActiveAlignment]);
|
|
71
|
+
const onAlignmentSelect = (alignmentId, e) => {
|
|
72
|
+
e.stopPropagation();
|
|
73
|
+
e.preventDefault();
|
|
74
|
+
editor.chain().focus().setTextAlign(alignmentId).run();
|
|
75
|
+
if (!openedByClick.current) {
|
|
76
|
+
buttonRef.current?.focus();
|
|
77
|
+
}
|
|
78
|
+
setOpen(false);
|
|
79
|
+
selecting.current = true;
|
|
80
|
+
setTimeout(() => {
|
|
81
|
+
selecting.current = false;
|
|
82
|
+
if (!editor.isFocused && openedByClick.current) {
|
|
83
|
+
editor.commands.focus();
|
|
84
|
+
}
|
|
85
|
+
}, 0);
|
|
86
|
+
};
|
|
87
|
+
const getActiveIcon = () => {
|
|
88
|
+
const alignment = ALIGNMENTS.find(a => a.id === activeAlignment);
|
|
89
|
+
return alignment?.icon || 'align-left';
|
|
90
|
+
};
|
|
91
|
+
const getActiveLabel = () => {
|
|
92
|
+
const alignment = ALIGNMENTS.find(a => a.id === activeAlignment);
|
|
93
|
+
return alignment ? t(alignment.labelKey) : t('rte_align_left');
|
|
94
|
+
};
|
|
95
|
+
return (_jsxs(_Fragment, { children: [_jsxs(StyledAlignmentSelect, { ...restProps, variant: 'simple', type: 'button', icon: false, ref: buttonRef, "data-toolbar-item": 'true', onMouseDown: (e) => {
|
|
96
|
+
e.preventDefault();
|
|
97
|
+
if (!open) {
|
|
98
|
+
openedByClick.current = true;
|
|
99
|
+
}
|
|
100
|
+
setOpen(!open);
|
|
101
|
+
}, onKeyDown: (e) => {
|
|
102
|
+
if (e.key === 'Enter' && !selecting.current) {
|
|
103
|
+
e.preventDefault();
|
|
104
|
+
setOpen(true);
|
|
105
|
+
openedByClick.current = false;
|
|
106
|
+
}
|
|
107
|
+
else if (e.key === 'Enter') {
|
|
108
|
+
e.preventDefault();
|
|
109
|
+
selecting.current = false;
|
|
110
|
+
}
|
|
111
|
+
else if (e.key === 'ArrowLeft' || e.key === 'ArrowRight') {
|
|
112
|
+
setOpen(false);
|
|
113
|
+
}
|
|
114
|
+
else if (e.key === 'Escape' && open) {
|
|
115
|
+
e.preventDefault();
|
|
116
|
+
setOpen(false);
|
|
117
|
+
}
|
|
118
|
+
}, onFocus: (e) => {
|
|
119
|
+
e.preventDefault();
|
|
120
|
+
e.stopPropagation();
|
|
121
|
+
}, onBlur: (e) => {
|
|
122
|
+
const { relatedTarget } = e;
|
|
123
|
+
if (isInstance(relatedTarget, Node) &&
|
|
124
|
+
!buttonRef.current?.contains(relatedTarget) &&
|
|
125
|
+
!popoverRef.current?.contains(relatedTarget) &&
|
|
126
|
+
!menuRef.current?.contains(relatedTarget)) {
|
|
127
|
+
setOpen(false);
|
|
128
|
+
}
|
|
129
|
+
}, onClick: (e) => e.stopPropagation(), "aria-expanded": open, "aria-label": `${t('rte_alignment')}. ${getActiveLabel()} ${t('selected').toLocaleLowerCase()}`, "aria-haspopup": true, children: [_jsx(Icon, { name: getActiveIcon() }), _jsx(Icon, { name: 'arrow-micro-down' })] }), buttonRef.current && (_jsx(Tooltip, { target: buttonRef.current, showDelay: 'none', hideDelay: 'none', children: t('rte_alignment') })), _jsx(Popover, { show: open, as: StyledSelectMenu, target: buttonRef.current, placement: 'bottom-start', ref: popoverRef, children: _jsx(Menu, { items: ALIGNMENTS.map(({ id, icon, labelKey }) => ({
|
|
130
|
+
id,
|
|
131
|
+
primary: t(labelKey),
|
|
132
|
+
visual: _jsx(Icon, { name: icon }),
|
|
133
|
+
selected: id === activeAlignment
|
|
134
|
+
})), focusControlEl: buttonRef.current ?? undefined, mode: 'single-select', ref: menuRef, onItemClick: onAlignmentSelect }) })] }));
|
|
135
|
+
};
|
|
136
|
+
export default AlignmentSelect;
|
|
137
|
+
//# sourceMappingURL=AlignmentSelect.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AlignmentSelect.js","sourceRoot":"","sources":["../../../../src/components/Editor/Toolbar/AlignmentSelect.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,OAAO,CAAC;AAGjE,OAAO,MAAM,EAAE,EAAE,GAAG,EAAE,MAAM,mBAAmB,CAAC;AAEhD,OAAO,EACL,OAAO,EACP,IAAI,EACJ,YAAY,EACZ,aAAa,EACb,IAAI,EACJ,MAAM,EACN,gBAAgB,EAChB,OAAO,EACP,OAAO,EACP,UAAU,EACX,MAAM,yBAAyB,CAAC;AAEjC,OAAO,KAAK,kBAAkB,MAAM,yEAAyE,CAAC;AAC9G,OAAO,KAAK,aAAa,MAAM,mEAAmE,CAAC;AACnG,OAAO,KAAK,eAAe,MAAM,qEAAqE,CAAC;AACvG,OAAO,KAAK,cAAc,MAAM,oEAAoE,CAAC;AAErG,OAAO,YAAY,MAAM,uBAAuB,CAAC;AACjD,OAAO,qBAAqB,MAAM,gCAAgC,CAAC;AAEnE,YAAY,CAAC,kBAAkB,EAAE,aAAa,EAAE,eAAe,EAAE,cAAc,CAAC,CAAC;AAEjF,MAAM,qBAAqB,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE;IACzD,OAAO,GAAG,CAAA;aACC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,kBAAkB,CAAC;;;yBAG1B,KAAK,CAAC,IAAI,CAAC,OAAO;4BACf,KAAK,CAAC,IAAI,CAAC,OAAO;GAC3C,CAAC;AACJ,CAAC,CAAC,CAAC;AAEH,qBAAqB,CAAC,YAAY,GAAG,gBAAgB,CAAC;AAEtD,MAAM,gBAAgB,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE;IAChD,OAAO,GAAG,CAAA;2BACe,KAAK,CAAC,IAAI,CAAC,OAAO;GAC1C,CAAC;AACJ,CAAC,CAAC,CAAC;AAEH,gBAAgB,CAAC,YAAY,GAAG,gBAAgB,CAAC;AAIjD,MAAM,UAAU,GAA6E;IAC3F,EAAE,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,YAAY,EAAE,QAAQ,EAAE,gBAAgB,EAAE;IAC9D,EAAE,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,cAAc,EAAE,QAAQ,EAAE,kBAAkB,EAAE;IACpE,EAAE,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,aAAa,EAAE,QAAQ,EAAE,iBAAiB,EAAE;CAClE,CAAC;AAOF,MAAM,eAAe,GAAG,CAAC,EAAE,MAAM,EAAE,GAAG,SAAS,EAAwB,EAAE,EAAE;IACzE,MAAM,CAAC,GAAG,OAAO,EAAE,CAAC;IACpB,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;IACxC,MAAM,CAAC,eAAe,EAAE,kBAAkB,CAAC,GAAG,QAAQ,CAAkB,MAAM,CAAC,CAAC;IAChF,MAAM,SAAS,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;IAChC,MAAM,aAAa,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;IACpC,MAAM,SAAS,GAAG,MAAM,CAAoB,IAAI,CAAC,CAAC;IAClD,MAAM,UAAU,GAAG,MAAM,CAAiB,IAAI,CAAC,CAAC;IAChD,MAAM,OAAO,GAAG,MAAM,CAAiB,IAAI,CAAC,CAAC;IAE7C,aAAa,CAAC,WAAW,EAAE,CAAC,SAAS,EAAE,UAAU,EAAE,OAAO,CAAC,EAAE,GAAG,EAAE;QAChE,OAAO,CAAC,KAAK,CAAC,CAAC;IACjB,CAAC,CAAC,CAAC;IAEH,MAAM,iBAAiB,GAAG,WAAW,CAAC,GAAG,EAAE;QACzC,OAAO,CAAC,KAAK,CAAC,CAAC;QACf,SAAS,CAAC,OAAO,EAAE,KAAK,EAAE,CAAC;IAC7B,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,YAAY,CAAC,IAAI,EAAE,iBAAiB,EAAE,MAAM,CAAC,CAAC;IAC9C,qBAAqB,CAAC,IAAI,EAAE,iBAAiB,CAAC,CAAC;IAE/C,MAAM,qBAAqB,GAAG,WAAW,CAAC,GAAG,EAAE;QAC7C,IAAI,MAAM,CAAC,QAAQ,CAAC,EAAE,SAAS,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC;YAC7C,kBAAkB,CAAC,QAAQ,CAAC,CAAC;QAC/B,CAAC;aAAM,IAAI,MAAM,CAAC,QAAQ,CAAC,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC;YACnD,kBAAkB,CAAC,OAAO,CAAC,CAAC;QAC9B,CAAC;aAAM,CAAC;YACN,kBAAkB,CAAC,MAAM,CAAC,CAAC;QAC7B,CAAC;IACH,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC;IAEb,SAAS,CAAC,GAAG,EAAE;QACb,qBAAqB,EAAE,CAAC;QACxB,MAAM,CAAC,EAAE,CAAC,iBAAiB,EAAE,qBAAqB,CAAC,CAAC;QACpD,MAAM,CAAC,EAAE,CAAC,aAAa,EAAE,qBAAqB,CAAC,CAAC;QAChD,OAAO,GAAG,EAAE;YACV,MAAM,CAAC,GAAG,CAAC,iBAAiB,EAAE,qBAAqB,CAAC,CAAC;YACrD,MAAM,CAAC,GAAG,CAAC,aAAa,EAAE,qBAAqB,CAAC,CAAC;QACnD,CAAC,CAAC;IACJ,CAAC,EAAE,CAAC,MAAM,EAAE,qBAAqB,CAAC,CAAC,CAAC;IAEpC,MAAM,iBAAiB,GAAG,CAAC,WAAmB,EAAE,CAAsB,EAAE,EAAE;QACxE,CAAC,CAAC,eAAe,EAAE,CAAC;QACpB,CAAC,CAAC,cAAc,EAAE,CAAC;QAEnB,MAAM,CAAC,KAAK,EAAE,CAAC,KAAK,EAAE,CAAC,YAAY,CAAC,WAAW,CAAC,CAAC,GAAG,EAAE,CAAC;QAEvD,IAAI,CAAC,aAAa,CAAC,OAAO,EAAE,CAAC;YAC3B,SAAS,CAAC,OAAO,EAAE,KAAK,EAAE,CAAC;QAC7B,CAAC;QACD,OAAO,CAAC,KAAK,CAAC,CAAC;QACf,SAAS,CAAC,OAAO,GAAG,IAAI,CAAC;QACzB,UAAU,CAAC,GAAG,EAAE;YACd,SAAS,CAAC,OAAO,GAAG,KAAK,CAAC;YAC1B,IAAI,CAAC,MAAM,CAAC,SAAS,IAAI,aAAa,CAAC,OAAO,EAAE,CAAC;gBAC/C,MAAM,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC;YAC1B,CAAC;QACH,CAAC,EAAE,CAAC,CAAC,CAAC;IACR,CAAC,CAAC;IAEF,MAAM,aAAa,GAAG,GAAG,EAAE;QACzB,MAAM,SAAS,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,eAAe,CAAC,CAAC;QACjE,OAAO,SAAS,EAAE,IAAI,IAAI,YAAY,CAAC;IACzC,CAAC,CAAC;IAEF,MAAM,cAAc,GAAG,GAAG,EAAE;QAC1B,MAAM,SAAS,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,eAAe,CAAC,CAAC;QACjE,OAAO,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,gBAAgB,CAAC,CAAC;IACjE,CAAC,CAAC;IAEF,OAAO,CACL,8BACE,MAAC,qBAAqB,OAChB,SAAS,EACb,OAAO,EAAC,QAAQ,EAChB,IAAI,EAAC,QAAQ,EACb,IAAI,EAAE,KAAK,EACX,GAAG,EAAE,SAAS,uBACI,MAAM,EACxB,WAAW,EAAE,CAAC,CAAa,EAAE,EAAE;oBAC7B,CAAC,CAAC,cAAc,EAAE,CAAC;oBACnB,IAAI,CAAC,IAAI,EAAE,CAAC;wBACV,aAAa,CAAC,OAAO,GAAG,IAAI,CAAC;oBAC/B,CAAC;oBACD,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC;gBACjB,CAAC,EACD,SAAS,EAAE,CAAC,CAAgB,EAAE,EAAE;oBAC9B,IAAI,CAAC,CAAC,GAAG,KAAK,OAAO,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,CAAC;wBAC5C,CAAC,CAAC,cAAc,EAAE,CAAC;wBACnB,OAAO,CAAC,IAAI,CAAC,CAAC;wBACd,aAAa,CAAC,OAAO,GAAG,KAAK,CAAC;oBAChC,CAAC;yBAAM,IAAI,CAAC,CAAC,GAAG,KAAK,OAAO,EAAE,CAAC;wBAC7B,CAAC,CAAC,cAAc,EAAE,CAAC;wBACnB,SAAS,CAAC,OAAO,GAAG,KAAK,CAAC;oBAC5B,CAAC;yBAAM,IAAI,CAAC,CAAC,GAAG,KAAK,WAAW,IAAI,CAAC,CAAC,GAAG,KAAK,YAAY,EAAE,CAAC;wBAC3D,OAAO,CAAC,KAAK,CAAC,CAAC;oBACjB,CAAC;yBAAM,IAAI,CAAC,CAAC,GAAG,KAAK,QAAQ,IAAI,IAAI,EAAE,CAAC;wBACtC,CAAC,CAAC,cAAc,EAAE,CAAC;wBACnB,OAAO,CAAC,KAAK,CAAC,CAAC;oBACjB,CAAC;gBACH,CAAC,EACD,OAAO,EAAE,CAAC,CAAa,EAAE,EAAE;oBACzB,CAAC,CAAC,cAAc,EAAE,CAAC;oBACnB,CAAC,CAAC,eAAe,EAAE,CAAC;gBACtB,CAAC,EACD,MAAM,EAAE,CAAC,CAAa,EAAE,EAAE;oBACxB,MAAM,EAAE,aAAa,EAAE,GAAG,CAAC,CAAC;oBAC5B,IACE,UAAU,CAAC,aAAa,EAAE,IAAI,CAAC;wBAC/B,CAAC,SAAS,CAAC,OAAO,EAAE,QAAQ,CAAC,aAAa,CAAC;wBAC3C,CAAC,UAAU,CAAC,OAAO,EAAE,QAAQ,CAAC,aAAa,CAAC;wBAC5C,CAAC,OAAO,CAAC,OAAO,EAAE,QAAQ,CAAC,aAAa,CAAC,EACzC,CAAC;wBACD,OAAO,CAAC,KAAK,CAAC,CAAC;oBACjB,CAAC;gBACH,CAAC,EACD,OAAO,EAAE,CAAC,CAAa,EAAE,EAAE,CAAC,CAAC,CAAC,eAAe,EAAE,mBAChC,IAAI,gBACP,GAAG,CAAC,CAAC,eAAe,CAAC,KAAK,cAAc,EAAE,IAAI,CAAC,CAAC,UAAU,CAAC,CAAC,iBAAiB,EAAE,EAAE,oCAG7F,KAAC,IAAI,IAAC,IAAI,EAAE,aAAa,EAAE,GAAI,EAC/B,KAAC,IAAI,IAAC,IAAI,EAAC,kBAAkB,GAAG,IACV,EACvB,SAAS,CAAC,OAAO,IAAI,CACpB,KAAC,OAAO,IAAC,MAAM,EAAE,SAAS,CAAC,OAAO,EAAE,SAAS,EAAC,MAAM,EAAC,SAAS,EAAC,MAAM,YAClE,CAAC,CAAC,eAAe,CAAC,GACX,CACX,EACD,KAAC,OAAO,IACN,IAAI,EAAE,IAAI,EACV,EAAE,EAAE,gBAAgB,EACpB,MAAM,EAAE,SAAS,CAAC,OAAO,EACzB,SAAS,EAAC,cAAc,EACxB,GAAG,EAAE,UAAU,YAEf,KAAC,IAAI,IACH,KAAK,EAAE,UAAU,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC,CAAC;wBACjD,EAAE;wBACF,OAAO,EAAE,CAAC,CAAC,QAAQ,CAAC;wBACpB,MAAM,EAAE,KAAC,IAAI,IAAC,IAAI,EAAE,IAAI,GAAI;wBAC5B,QAAQ,EAAE,EAAE,KAAK,eAAe;qBACjC,CAAC,CAAC,EACH,cAAc,EAAE,SAAS,CAAC,OAAO,IAAI,SAAS,EAC9C,IAAI,EAAC,eAAe,EACpB,GAAG,EAAE,OAAO,EACZ,WAAW,EAAE,iBAAiB,GAC9B,GACM,IACT,CACJ,CAAC;AACJ,CAAC,CAAC;AAEF,eAAe,eAAe,CAAC","sourcesContent":["import { useRef, useState, useEffect, useCallback } from 'react';\nimport type { MouseEvent, KeyboardEvent, FocusEvent } from 'react';\nimport type { Editor as TiptapEditor } from '@tiptap/core';\nimport styled, { css } from 'styled-components';\n\nimport {\n Popover,\n Icon,\n registerIcon,\n useOuterEvent,\n Menu,\n Button,\n defaultThemeProp,\n useI18n,\n Tooltip,\n isInstance\n} from '@pega/cosmos-react-core';\nimport type { TranslationPack } from '@pega/cosmos-react-core';\nimport * as arrowMicroDownIcon from '@pega/cosmos-react-core/lib/components/Icon/icons/arrow-micro-down.icon';\nimport * as alignLeftIcon from '@pega/cosmos-react-core/lib/components/Icon/icons/align-left.icon';\nimport * as alignCenterIcon from '@pega/cosmos-react-core/lib/components/Icon/icons/align-center.icon';\nimport * as alignRightIcon from '@pega/cosmos-react-core/lib/components/Icon/icons/align-right.icon';\n\nimport useEscapeKey from '../hooks/useEscapeKey';\nimport useCloseOnEditorClick from '../hooks/useCloseOnEditorClick';\n\nregisterIcon(arrowMicroDownIcon, alignLeftIcon, alignCenterIcon, alignRightIcon);\n\nconst StyledAlignmentSelect = styled(Button)(({ theme }) => {\n return css`\n color: ${theme.base.palette['foreground-color']};\n display: inline-flex;\n align-items: center;\n height: calc(3.5 * ${theme.base.spacing});\n padding: 0 calc(0.5 * ${theme.base.spacing});\n `;\n});\n\nStyledAlignmentSelect.defaultProps = defaultThemeProp;\n\nconst StyledSelectMenu = styled.div(({ theme }) => {\n return css`\n min-width: calc(20 * ${theme.base.spacing});\n `;\n});\n\nStyledSelectMenu.defaultProps = defaultThemeProp;\n\ntype AlignmentOption = 'left' | 'center' | 'right';\n\nconst ALIGNMENTS: { id: AlignmentOption; icon: string; labelKey: keyof TranslationPack }[] = [\n { id: 'left', icon: 'align-left', labelKey: 'rte_align_left' },\n { id: 'center', icon: 'align-center', labelKey: 'rte_align_center' },\n { id: 'right', icon: 'align-right', labelKey: 'rte_align_right' }\n];\n\ninterface AlignmentSelectProps {\n editor: TiptapEditor;\n 'data-testid'?: string;\n}\n\nconst AlignmentSelect = ({ editor, ...restProps }: AlignmentSelectProps) => {\n const t = useI18n();\n const [open, setOpen] = useState(false);\n const [activeAlignment, setActiveAlignment] = useState<AlignmentOption>('left');\n const selecting = useRef(false);\n const openedByClick = useRef(false);\n const buttonRef = useRef<HTMLButtonElement>(null);\n const popoverRef = useRef<HTMLDivElement>(null);\n const menuRef = useRef<HTMLDivElement>(null);\n\n useOuterEvent('mousedown', [buttonRef, popoverRef, menuRef], () => {\n setOpen(false);\n });\n\n const handleEscapeClose = useCallback(() => {\n setOpen(false);\n buttonRef.current?.focus();\n }, []);\n\n useEscapeKey(open, handleEscapeClose, editor);\n useCloseOnEditorClick(open, handleEscapeClose);\n\n const updateActiveAlignment = useCallback(() => {\n if (editor.isActive({ textAlign: 'center' })) {\n setActiveAlignment('center');\n } else if (editor.isActive({ textAlign: 'right' })) {\n setActiveAlignment('right');\n } else {\n setActiveAlignment('left');\n }\n }, [editor]);\n\n useEffect(() => {\n updateActiveAlignment();\n editor.on('selectionUpdate', updateActiveAlignment);\n editor.on('transaction', updateActiveAlignment);\n return () => {\n editor.off('selectionUpdate', updateActiveAlignment);\n editor.off('transaction', updateActiveAlignment);\n };\n }, [editor, updateActiveAlignment]);\n\n const onAlignmentSelect = (alignmentId: string, e: MouseEvent<Element>) => {\n e.stopPropagation();\n e.preventDefault();\n\n editor.chain().focus().setTextAlign(alignmentId).run();\n\n if (!openedByClick.current) {\n buttonRef.current?.focus();\n }\n setOpen(false);\n selecting.current = true;\n setTimeout(() => {\n selecting.current = false;\n if (!editor.isFocused && openedByClick.current) {\n editor.commands.focus();\n }\n }, 0);\n };\n\n const getActiveIcon = () => {\n const alignment = ALIGNMENTS.find(a => a.id === activeAlignment);\n return alignment?.icon || 'align-left';\n };\n\n const getActiveLabel = () => {\n const alignment = ALIGNMENTS.find(a => a.id === activeAlignment);\n return alignment ? t(alignment.labelKey) : t('rte_align_left');\n };\n\n return (\n <>\n <StyledAlignmentSelect\n {...restProps}\n variant='simple'\n type='button'\n icon={false}\n ref={buttonRef}\n data-toolbar-item='true'\n onMouseDown={(e: MouseEvent) => {\n e.preventDefault();\n if (!open) {\n openedByClick.current = true;\n }\n setOpen(!open);\n }}\n onKeyDown={(e: KeyboardEvent) => {\n if (e.key === 'Enter' && !selecting.current) {\n e.preventDefault();\n setOpen(true);\n openedByClick.current = false;\n } else if (e.key === 'Enter') {\n e.preventDefault();\n selecting.current = false;\n } else if (e.key === 'ArrowLeft' || e.key === 'ArrowRight') {\n setOpen(false);\n } else if (e.key === 'Escape' && open) {\n e.preventDefault();\n setOpen(false);\n }\n }}\n onFocus={(e: FocusEvent) => {\n e.preventDefault();\n e.stopPropagation();\n }}\n onBlur={(e: FocusEvent) => {\n const { relatedTarget } = e;\n if (\n isInstance(relatedTarget, Node) &&\n !buttonRef.current?.contains(relatedTarget) &&\n !popoverRef.current?.contains(relatedTarget) &&\n !menuRef.current?.contains(relatedTarget)\n ) {\n setOpen(false);\n }\n }}\n onClick={(e: MouseEvent) => e.stopPropagation()}\n aria-expanded={open}\n aria-label={`${t('rte_alignment')}. ${getActiveLabel()} ${t('selected').toLocaleLowerCase()}`}\n aria-haspopup\n >\n <Icon name={getActiveIcon()} />\n <Icon name='arrow-micro-down' />\n </StyledAlignmentSelect>\n {buttonRef.current && (\n <Tooltip target={buttonRef.current} showDelay='none' hideDelay='none'>\n {t('rte_alignment')}\n </Tooltip>\n )}\n <Popover\n show={open}\n as={StyledSelectMenu}\n target={buttonRef.current}\n placement='bottom-start'\n ref={popoverRef}\n >\n <Menu\n items={ALIGNMENTS.map(({ id, icon, labelKey }) => ({\n id,\n primary: t(labelKey),\n visual: <Icon name={icon} />,\n selected: id === activeAlignment\n }))}\n focusControlEl={buttonRef.current ?? undefined}\n mode='single-select'\n ref={menuRef}\n onItemClick={onAlignmentSelect}\n />\n </Popover>\n </>\n );\n};\n\nexport default AlignmentSelect;\n"]}
|
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import type { Editor } from 'tinymce';
|
|
1
|
+
import type { Editor as TiptapEditor } from '@tiptap/core';
|
|
3
2
|
import type { ForwardProps } from '@pega/cosmos-react-core';
|
|
4
3
|
interface AnchorButtonProps {
|
|
5
4
|
osx: boolean;
|
|
6
|
-
editor:
|
|
5
|
+
editor: TiptapEditor;
|
|
7
6
|
}
|
|
8
|
-
declare const AnchorButton:
|
|
7
|
+
declare const AnchorButton: ({ osx, editor, ...restProps }: AnchorButtonProps & ForwardProps) => import("react/jsx-runtime").JSX.Element;
|
|
9
8
|
export default AnchorButton;
|
|
10
9
|
//# sourceMappingURL=AnchorButton.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AnchorButton.d.ts","sourceRoot":"","sources":["../../../../src/components/Editor/Toolbar/AnchorButton.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"AnchorButton.d.ts","sourceRoot":"","sources":["../../../../src/components/Editor/Toolbar/AnchorButton.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,MAAM,IAAI,YAAY,EAAE,MAAM,cAAc,CAAC;AAiB3D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAU5D,UAAU,iBAAiB;IACzB,GAAG,EAAE,OAAO,CAAC;IACb,MAAM,EAAE,YAAY,CAAC;CACtB;AAED,QAAA,MAAM,YAAY,GAAI,+BAA+B,iBAAiB,GAAG,YAAY,4CAwTpF,CAAC;AAEF,eAAe,YAAY,CAAC"}
|
|
@@ -1,127 +1,207 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import { useRef, useState, useEffect, useLayoutEffect } from 'react';
|
|
3
|
-
import { Button, CardContent, Grid, Icon, registerIcon, Input, useOuterEvent, Form, useI18n } from '@pega/cosmos-react-core';
|
|
2
|
+
import { useRef, useState, useEffect, useLayoutEffect, useCallback } from 'react';
|
|
3
|
+
import { Button, CardContent, Grid, Icon, registerIcon, Input, useOuterEvent, Form, useI18n, Text, useElement, useEscape, isInstance } from '@pega/cosmos-react-core';
|
|
4
4
|
import * as chainIcon from '@pega/cosmos-react-core/lib/components/Icon/icons/chain.icon';
|
|
5
5
|
import ToolbarButton from '../../RichTextEditor/Toolbar/ToolbarButton';
|
|
6
6
|
import { getKeyCommand } from '../../RichTextEditor/Toolbar/utils';
|
|
7
7
|
import { StyledEditPopover } from '../Editor.styles';
|
|
8
|
+
import useCloseOnEditorClick from '../hooks/useCloseOnEditorClick';
|
|
8
9
|
registerIcon(chainIcon);
|
|
9
10
|
const AnchorButton = ({ osx, editor, ...restProps }) => {
|
|
10
11
|
const t = useI18n();
|
|
11
12
|
const buttonRef = useRef(null);
|
|
12
|
-
const
|
|
13
|
-
const
|
|
14
|
-
const
|
|
15
|
-
const [
|
|
16
|
-
const [selection, setSelection] = useState();
|
|
13
|
+
const [textInputEl, setTextInputEl] = useElement();
|
|
14
|
+
const [urlInputEl, setUrlInputEl] = useElement();
|
|
15
|
+
const [popoverEl, setPopoverEl] = useElement();
|
|
16
|
+
const [linkText, setLinkText] = useState('');
|
|
17
17
|
const [url, setUrl] = useState('');
|
|
18
|
-
const [urlMatch, setUrlMatch] = useState(
|
|
18
|
+
const [urlMatch, setUrlMatch] = useState(true);
|
|
19
19
|
const [anchorMenu, setAnchorMenu] = useState(false);
|
|
20
20
|
const [shouldFocusInput, setShouldFocusInput] = useState(false);
|
|
21
|
+
// Store the original selection range when menu opens so we can restore it when creating the link
|
|
22
|
+
const originalSelectionRef = useRef(null);
|
|
21
23
|
const tooltip = getKeyCommand(osx, ({ ctrl }) => `${t('rte_link')} (${ctrl}K)`);
|
|
22
24
|
const openMenu = (opts = {}) => {
|
|
25
|
+
// Save the original selection before opening the menu
|
|
26
|
+
const { from, to } = editor.state.selection;
|
|
27
|
+
originalSelectionRef.current = { from, to };
|
|
23
28
|
setAnchorMenu(true);
|
|
24
29
|
if (opts.focusInput) {
|
|
25
30
|
setShouldFocusInput(true);
|
|
26
31
|
}
|
|
27
32
|
};
|
|
28
|
-
const resetMenu = () => {
|
|
29
|
-
|
|
30
|
-
setSelectedText('');
|
|
33
|
+
const resetMenu = useCallback(() => {
|
|
34
|
+
setLinkText('');
|
|
31
35
|
setUrl('');
|
|
32
36
|
setUrlMatch(true);
|
|
33
37
|
setAnchorMenu(false);
|
|
34
|
-
|
|
35
|
-
|
|
38
|
+
originalSelectionRef.current = null;
|
|
39
|
+
}, []);
|
|
40
|
+
useOuterEvent('mousedown', [popoverEl, buttonRef], () => {
|
|
36
41
|
if (anchorMenu) {
|
|
37
42
|
resetMenu();
|
|
38
43
|
}
|
|
39
44
|
});
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
editor.selection = selection;
|
|
44
|
-
if (selection.getNode().tagName === 'A') {
|
|
45
|
-
const anchorEl = editor.selection.getNode();
|
|
46
|
-
anchorEl.setAttribute('href', url);
|
|
47
|
-
anchorEl.setAttribute('data-mce-href', url);
|
|
48
|
-
anchorEl.textContent = selectedText;
|
|
49
|
-
editor.execCommand('mceAddUndoLevel');
|
|
50
|
-
}
|
|
51
|
-
else {
|
|
52
|
-
editor.insertContent(`<a href='${new URL(/^[a-z][a-z0-9.+-]*:/i.test(url) ? url : `https:${url}`).href}'>${selectedText}</a>`);
|
|
53
|
-
}
|
|
45
|
+
// Close menu when clicking inside the editor iframe
|
|
46
|
+
const handleEditorClick = useCallback(() => {
|
|
47
|
+
if (anchorMenu) {
|
|
54
48
|
resetMenu();
|
|
55
49
|
}
|
|
50
|
+
}, [anchorMenu, resetMenu]);
|
|
51
|
+
useCloseOnEditorClick(anchorMenu, handleEditorClick);
|
|
52
|
+
const isValidUrl = (urlString) => {
|
|
53
|
+
if (!urlString.trim())
|
|
54
|
+
return false;
|
|
55
|
+
const allowedProtocols = /^(https?|mailto|tel):/i;
|
|
56
|
+
const hasProtocol = /^[a-z][a-z0-9.+-]*:/i.test(urlString);
|
|
57
|
+
try {
|
|
58
|
+
const testUrl = hasProtocol ? urlString : `https://${urlString}`;
|
|
59
|
+
if (hasProtocol && !allowedProtocols.test(urlString)) {
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
const urlObj = new URL(testUrl);
|
|
63
|
+
return URL.canParse?.(testUrl) ?? Boolean(urlObj.href);
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
56
68
|
};
|
|
57
|
-
|
|
58
|
-
if (
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
+
const createLink = useCallback(() => {
|
|
70
|
+
if (!url || !isValidUrl(url)) {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
// Only allow safe protocols to prevent XSS (e.g., javascript: URLs)
|
|
74
|
+
const allowedProtocols = /^(https?|mailto|tel):/i;
|
|
75
|
+
const hasProtocol = /^[a-z][a-z0-9.+-]*:/i.test(url);
|
|
76
|
+
let normalizedUrl;
|
|
77
|
+
if (hasProtocol) {
|
|
78
|
+
// Reject URLs with disallowed protocols
|
|
79
|
+
if (!allowedProtocols.test(url)) {
|
|
80
|
+
return;
|
|
69
81
|
}
|
|
82
|
+
normalizedUrl = url;
|
|
70
83
|
}
|
|
71
84
|
else {
|
|
85
|
+
normalizedUrl = `https://${url}`;
|
|
86
|
+
}
|
|
87
|
+
// Use the original selection to determine if text was selected in the editor
|
|
88
|
+
const originalSelection = originalSelectionRef.current;
|
|
89
|
+
if (originalSelection && originalSelection.from !== originalSelection.to) {
|
|
90
|
+
// Text was originally selected - restore selection and apply link
|
|
91
|
+
// Then collapse selection to end and clear stored mark so subsequent typing isn't linked
|
|
92
|
+
editor
|
|
93
|
+
.chain()
|
|
94
|
+
.focus()
|
|
95
|
+
.setTextSelection({ from: originalSelection.from, to: originalSelection.to })
|
|
96
|
+
.setLink({ href: normalizedUrl })
|
|
97
|
+
.setTextSelection(originalSelection.to)
|
|
98
|
+
.unsetMark('link')
|
|
99
|
+
.run();
|
|
100
|
+
}
|
|
101
|
+
else if (linkText) {
|
|
102
|
+
// No text was originally selected, insert new link with the text entered in modal
|
|
103
|
+
editor
|
|
104
|
+
.chain()
|
|
105
|
+
.focus()
|
|
106
|
+
.insertContent({
|
|
107
|
+
type: 'text',
|
|
108
|
+
text: linkText,
|
|
109
|
+
marks: [{ type: 'link', attrs: { href: normalizedUrl } }]
|
|
110
|
+
})
|
|
111
|
+
.unsetMark('link')
|
|
112
|
+
.insertContent(' ')
|
|
113
|
+
.run();
|
|
114
|
+
}
|
|
115
|
+
resetMenu();
|
|
116
|
+
}, [url, linkText, editor, resetMenu]);
|
|
117
|
+
useEffect(() => {
|
|
118
|
+
if (anchorMenu && originalSelectionRef.current) {
|
|
119
|
+
// Use the stored original selection to populate the modal
|
|
120
|
+
// This avoids issues where focus shift may collapse the editor selection
|
|
121
|
+
const { from, to } = originalSelectionRef.current;
|
|
122
|
+
const text = editor.state.doc.textBetween(from, to);
|
|
123
|
+
const linkMark = editor.getAttributes('link');
|
|
124
|
+
if (linkMark.href) {
|
|
125
|
+
setUrl(linkMark.href);
|
|
126
|
+
setLinkText(text || '');
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
setLinkText(text || '');
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
else if (!anchorMenu) {
|
|
72
133
|
resetMenu();
|
|
73
134
|
}
|
|
74
|
-
}, [anchorMenu]);
|
|
135
|
+
}, [anchorMenu, editor, resetMenu]);
|
|
75
136
|
const preventDef = (e) => {
|
|
76
137
|
e.preventDefault();
|
|
77
138
|
e.stopPropagation();
|
|
78
139
|
};
|
|
79
140
|
const isLinkActive = () => {
|
|
80
|
-
return editor.
|
|
141
|
+
return editor.isActive('link') && editor.isFocused;
|
|
81
142
|
};
|
|
82
|
-
const cancelAnchorCreation = (event) => {
|
|
83
|
-
if (((event
|
|
84
|
-
event?.type === '
|
|
143
|
+
const cancelAnchorCreation = useCallback((event) => {
|
|
144
|
+
if (((event && 'key' in event && event.key === 'Escape') ||
|
|
145
|
+
event?.type === 'click' ||
|
|
85
146
|
!event) &&
|
|
86
147
|
anchorMenu) {
|
|
87
|
-
event
|
|
148
|
+
if (event && 'key' in event && event.key === 'Escape') {
|
|
149
|
+
event.stopPropagation();
|
|
150
|
+
}
|
|
88
151
|
resetMenu();
|
|
89
152
|
buttonRef.current?.focus();
|
|
90
153
|
}
|
|
91
|
-
};
|
|
154
|
+
}, [anchorMenu, resetMenu]);
|
|
155
|
+
// Ctrl+K shortcut to open link dialog from within the editor iframe
|
|
92
156
|
useEffect(() => {
|
|
93
|
-
const
|
|
94
|
-
if (e.key === 'k' && (e.metaKey || e.ctrlKey)
|
|
157
|
+
const handleCtrlK = (e) => {
|
|
158
|
+
if (e.key === 'k' && (e.metaKey || e.ctrlKey)) {
|
|
95
159
|
e.preventDefault();
|
|
96
160
|
openMenu({ focusInput: true });
|
|
97
161
|
}
|
|
98
|
-
|
|
162
|
+
};
|
|
163
|
+
const iframeWindow = editor.view.dom.ownerDocument.defaultView;
|
|
164
|
+
iframeWindow?.addEventListener('keydown', handleCtrlK);
|
|
165
|
+
return () => iframeWindow?.removeEventListener('keydown', handleCtrlK);
|
|
166
|
+
}, [editor]);
|
|
167
|
+
// Escape to close popover - listen on both iframe and parent document
|
|
168
|
+
// since focus may be in either location depending on how the popover was opened
|
|
169
|
+
useEffect(() => {
|
|
170
|
+
const handleEscape = (e) => {
|
|
171
|
+
if (e.key === 'Escape' && anchorMenu) {
|
|
172
|
+
e.preventDefault();
|
|
99
173
|
cancelAnchorCreation();
|
|
100
174
|
}
|
|
101
175
|
};
|
|
102
|
-
editor.
|
|
176
|
+
const iframeWindow = editor.view.dom.ownerDocument.defaultView;
|
|
177
|
+
iframeWindow?.addEventListener('keydown', handleEscape);
|
|
178
|
+
document.addEventListener('keydown', handleEscape);
|
|
103
179
|
return () => {
|
|
104
|
-
|
|
180
|
+
iframeWindow?.removeEventListener('keydown', handleEscape);
|
|
181
|
+
document.removeEventListener('keydown', handleEscape);
|
|
105
182
|
};
|
|
106
|
-
}, []);
|
|
183
|
+
}, [editor, anchorMenu, cancelAnchorCreation]);
|
|
184
|
+
useEscape(cancelAnchorCreation, popoverEl);
|
|
107
185
|
useLayoutEffect(() => {
|
|
108
|
-
if (
|
|
109
|
-
|
|
186
|
+
if (textInputEl && shouldFocusInput) {
|
|
187
|
+
textInputEl.focus();
|
|
110
188
|
setShouldFocusInput(false);
|
|
111
189
|
}
|
|
112
|
-
}, [
|
|
190
|
+
}, [textInputEl, shouldFocusInput]);
|
|
113
191
|
useEffect(() => {
|
|
114
192
|
// These events must be added here so they run before the native event in useArrows (used in the toolbar).
|
|
115
193
|
const onKeyDown = (e) => {
|
|
116
|
-
e.
|
|
194
|
+
if (e.key !== 'Escape') {
|
|
195
|
+
e.stopPropagation();
|
|
196
|
+
}
|
|
117
197
|
};
|
|
118
|
-
|
|
119
|
-
|
|
198
|
+
textInputEl?.addEventListener('keydown', onKeyDown);
|
|
199
|
+
urlInputEl?.addEventListener('keydown', onKeyDown);
|
|
120
200
|
return () => {
|
|
121
|
-
|
|
122
|
-
|
|
201
|
+
textInputEl?.removeEventListener('keydown', onKeyDown);
|
|
202
|
+
urlInputEl?.removeEventListener('keydown', onKeyDown);
|
|
123
203
|
};
|
|
124
|
-
}, [
|
|
204
|
+
}, [textInputEl, urlInputEl]);
|
|
125
205
|
return (_jsxs(_Fragment, { children: [_jsx(ToolbarButton, { ref: buttonRef, onMouseDown: e => {
|
|
126
206
|
e.preventDefault();
|
|
127
207
|
openMenu();
|
|
@@ -130,34 +210,28 @@ const AnchorButton = ({ osx, editor, ...restProps }) => {
|
|
|
130
210
|
e.preventDefault();
|
|
131
211
|
openMenu({ focusInput: true });
|
|
132
212
|
}
|
|
133
|
-
},
|
|
213
|
+
}, onBlur: (e) => {
|
|
214
|
+
const { relatedTarget } = e;
|
|
215
|
+
if (isInstance(relatedTarget, Node) &&
|
|
216
|
+
!buttonRef.current?.contains(relatedTarget) &&
|
|
217
|
+
!popoverEl?.contains(relatedTarget)) {
|
|
218
|
+
resetMenu();
|
|
219
|
+
}
|
|
220
|
+
}, active: isLinkActive(), tooltip: tooltip, label: t('rte_link'), ...restProps, children: _jsx(Icon, { name: 'chain' }) }), _jsx(StyledEditPopover, { show: anchorMenu, target: buttonRef.current, ref: setPopoverEl, placement: 'bottom', children: _jsx(CardContent, { children: _jsx(Form, { as: 'div', actions: _jsxs(_Fragment, { children: [_jsx(Button, { variant: 'secondary', onClick: cancelAnchorCreation, type: 'button', children: t('cancel') }), _jsx(Button, { variant: 'primary', disabled: !url || !urlMatch, onClick: (e) => {
|
|
134
221
|
e.preventDefault();
|
|
135
222
|
createLink();
|
|
136
|
-
}, children: t('apply') })] }), children: _jsxs(Grid, { container: { rowGap: 2 }, children: [_jsx(Input, { label: t('rte_link_text'), value:
|
|
137
|
-
|
|
138
|
-
}, ref:
|
|
223
|
+
}, children: t('apply') })] }), children: _jsxs(Grid, { container: { rowGap: 2 }, children: [_jsx(Text, { variant: 'h2', children: t('rte_add_link') }), _jsx(Input, { label: t('rte_link_text'), value: linkText, onClick: preventDef, onChange: (e) => {
|
|
224
|
+
setLinkText(e.target.value);
|
|
225
|
+
}, ref: setTextInputEl }), _jsx(Input, { label: t('rte_link_url'), value: url, onClick: preventDef, onChange: (e) => {
|
|
139
226
|
const urlInput = e.target.value;
|
|
140
227
|
setUrl(urlInput);
|
|
228
|
+
// Clear error while typing, validate on blur
|
|
141
229
|
if (!urlMatch) {
|
|
142
|
-
try {
|
|
143
|
-
// eslint-disable-next-line no-new
|
|
144
|
-
new URL(/^[a-z][a-z0-9+.-]*:/i.test(urlInput) ? urlInput : `https:${urlInput}`);
|
|
145
|
-
setUrlMatch(true);
|
|
146
|
-
}
|
|
147
|
-
catch {
|
|
148
|
-
setUrlMatch(false);
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
}, onBlur: () => {
|
|
152
|
-
try {
|
|
153
|
-
// eslint-disable-next-line no-new
|
|
154
|
-
new URL(/^[a-z][a-z0-9+.-]*:/i.test(url) ? url : `https:${url}`);
|
|
155
230
|
setUrlMatch(true);
|
|
156
231
|
}
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
}, info: !urlMatch ? t('rte_invalid_url') : '', status: !urlMatch ? 'error' : undefined, ref: urlInputRef })] }) }) }) })] }));
|
|
232
|
+
}, onBlur: () => {
|
|
233
|
+
setUrlMatch(!url || isValidUrl(url));
|
|
234
|
+
}, info: url && !urlMatch ? t('rte_invalid_url') : '', status: url && !urlMatch ? 'error' : undefined, ref: setUrlInputEl })] }) }) }) })] }));
|
|
161
235
|
};
|
|
162
236
|
export default AnchorButton;
|
|
163
237
|
//# sourceMappingURL=AnchorButton.js.map
|