@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.
Files changed (191) hide show
  1. package/lib/components/DynamicContentEditor/DynamicContentEditor.d.ts +4 -2
  2. package/lib/components/DynamicContentEditor/DynamicContentEditor.d.ts.map +1 -1
  3. package/lib/components/DynamicContentEditor/DynamicContentEditor.js +64 -59
  4. package/lib/components/DynamicContentEditor/DynamicContentEditor.js.map +1 -1
  5. package/lib/components/Editor/Editor.context.d.ts +6 -6
  6. package/lib/components/Editor/Editor.context.d.ts.map +1 -1
  7. package/lib/components/Editor/Editor.context.js +1 -1
  8. package/lib/components/Editor/Editor.context.js.map +1 -1
  9. package/lib/components/Editor/Editor.d.ts +1 -10
  10. package/lib/components/Editor/Editor.d.ts.map +1 -1
  11. package/lib/components/Editor/Editor.js +301 -490
  12. package/lib/components/Editor/Editor.js.map +1 -1
  13. package/lib/components/Editor/Editor.styles.d.ts +37 -7
  14. package/lib/components/Editor/Editor.styles.d.ts.map +1 -1
  15. package/lib/components/Editor/Editor.styles.js +60 -30
  16. package/lib/components/Editor/Editor.styles.js.map +1 -1
  17. package/lib/components/Editor/Editor.test-ids.d.ts +2 -1
  18. package/lib/components/Editor/Editor.test-ids.d.ts.map +1 -1
  19. package/lib/components/Editor/Editor.test-ids.js +2 -0
  20. package/lib/components/Editor/Editor.test-ids.js.map +1 -1
  21. package/lib/components/Editor/Editor.types.d.ts +34 -14
  22. package/lib/components/Editor/Editor.types.d.ts.map +1 -1
  23. package/lib/components/Editor/Editor.types.js.map +1 -1
  24. package/lib/components/Editor/IframeTiptapEditor.d.ts +30 -0
  25. package/lib/components/Editor/IframeTiptapEditor.d.ts.map +1 -0
  26. package/lib/components/Editor/IframeTiptapEditor.js +695 -0
  27. package/lib/components/Editor/IframeTiptapEditor.js.map +1 -0
  28. package/lib/components/Editor/ImageActionButtons.d.ts +20 -0
  29. package/lib/components/Editor/ImageActionButtons.d.ts.map +1 -0
  30. package/lib/components/Editor/ImageActionButtons.js +84 -0
  31. package/lib/components/Editor/ImageActionButtons.js.map +1 -0
  32. package/lib/components/Editor/ImageEditDialog.d.ts +17 -0
  33. package/lib/components/Editor/ImageEditDialog.d.ts.map +1 -0
  34. package/lib/components/Editor/ImageEditDialog.js +90 -0
  35. package/lib/components/Editor/ImageEditDialog.js.map +1 -0
  36. package/lib/components/Editor/TableCellMenu.d.ts +35 -0
  37. package/lib/components/Editor/TableCellMenu.d.ts.map +1 -0
  38. package/lib/components/Editor/TableCellMenu.js +120 -0
  39. package/lib/components/Editor/TableCellMenu.js.map +1 -0
  40. package/lib/components/Editor/Toolbar/AIRewriteButton.d.ts +17 -0
  41. package/lib/components/Editor/Toolbar/AIRewriteButton.d.ts.map +1 -0
  42. package/lib/components/Editor/Toolbar/AIRewriteButton.js +79 -0
  43. package/lib/components/Editor/Toolbar/AIRewriteButton.js.map +1 -0
  44. package/lib/components/Editor/Toolbar/AlignmentSelect.d.ts +8 -0
  45. package/lib/components/Editor/Toolbar/AlignmentSelect.d.ts.map +1 -0
  46. package/lib/components/Editor/Toolbar/AlignmentSelect.js +137 -0
  47. package/lib/components/Editor/Toolbar/AlignmentSelect.js.map +1 -0
  48. package/lib/components/Editor/Toolbar/AnchorButton.d.ts +3 -4
  49. package/lib/components/Editor/Toolbar/AnchorButton.d.ts.map +1 -1
  50. package/lib/components/Editor/Toolbar/AnchorButton.js +156 -82
  51. package/lib/components/Editor/Toolbar/AnchorButton.js.map +1 -1
  52. package/lib/components/Editor/Toolbar/ColorPickerButton.d.ts +9 -0
  53. package/lib/components/Editor/Toolbar/ColorPickerButton.d.ts.map +1 -0
  54. package/lib/components/Editor/Toolbar/ColorPickerButton.js +190 -0
  55. package/lib/components/Editor/Toolbar/ColorPickerButton.js.map +1 -0
  56. package/lib/components/Editor/Toolbar/FontFamilySelect.d.ts +8 -0
  57. package/lib/components/Editor/Toolbar/FontFamilySelect.d.ts.map +1 -0
  58. package/lib/components/Editor/Toolbar/FontFamilySelect.js +150 -0
  59. package/lib/components/Editor/Toolbar/FontFamilySelect.js.map +1 -0
  60. package/lib/components/Editor/Toolbar/FontSizeSelect.d.ts +8 -0
  61. package/lib/components/Editor/Toolbar/FontSizeSelect.d.ts.map +1 -0
  62. package/lib/components/Editor/Toolbar/FontSizeSelect.js +145 -0
  63. package/lib/components/Editor/Toolbar/FontSizeSelect.js.map +1 -0
  64. package/lib/components/Editor/Toolbar/ImageButton.d.ts +5 -5
  65. package/lib/components/Editor/Toolbar/ImageButton.d.ts.map +1 -1
  66. package/lib/components/Editor/Toolbar/ImageButton.js +131 -18
  67. package/lib/components/Editor/Toolbar/ImageButton.js.map +1 -1
  68. package/lib/components/Editor/Toolbar/SourceCodeButton.d.ts +8 -0
  69. package/lib/components/Editor/Toolbar/SourceCodeButton.d.ts.map +1 -0
  70. package/lib/components/Editor/Toolbar/SourceCodeButton.js +49 -0
  71. package/lib/components/Editor/Toolbar/SourceCodeButton.js.map +1 -0
  72. package/lib/components/Editor/Toolbar/TableButton.d.ts +8 -0
  73. package/lib/components/Editor/Toolbar/TableButton.d.ts.map +1 -0
  74. package/lib/components/Editor/Toolbar/TableButton.js +291 -0
  75. package/lib/components/Editor/Toolbar/TableButton.js.map +1 -0
  76. package/lib/components/Editor/Toolbar/TextSelect.d.ts +4 -5
  77. package/lib/components/Editor/Toolbar/TextSelect.d.ts.map +1 -1
  78. package/lib/components/Editor/Toolbar/TextSelect.js +61 -30
  79. package/lib/components/Editor/Toolbar/TextSelect.js.map +1 -1
  80. package/lib/components/Editor/Toolbar/Toolbar.d.ts +17 -6
  81. package/lib/components/Editor/Toolbar/Toolbar.d.ts.map +1 -1
  82. package/lib/components/Editor/Toolbar/Toolbar.js +169 -47
  83. package/lib/components/Editor/Toolbar/Toolbar.js.map +1 -1
  84. package/lib/components/Editor/Toolbar/Toolbar.test-ids.d.ts +2 -2
  85. package/lib/components/Editor/Toolbar/Toolbar.test-ids.d.ts.map +1 -1
  86. package/lib/components/Editor/Toolbar/Toolbar.test-ids.js +17 -1
  87. package/lib/components/Editor/Toolbar/Toolbar.test-ids.js.map +1 -1
  88. package/lib/components/Editor/Toolbar/WordCount.d.ts +8 -0
  89. package/lib/components/Editor/Toolbar/WordCount.d.ts.map +1 -0
  90. package/lib/components/Editor/Toolbar/WordCount.js +31 -0
  91. package/lib/components/Editor/Toolbar/WordCount.js.map +1 -0
  92. package/lib/components/Editor/extensions/FontSize.d.ts +21 -0
  93. package/lib/components/Editor/extensions/FontSize.d.ts.map +1 -0
  94. package/lib/components/Editor/extensions/FontSize.js +42 -0
  95. package/lib/components/Editor/extensions/FontSize.js.map +1 -0
  96. package/lib/components/Editor/extensions/PreserveDiv.d.ts +13 -0
  97. package/lib/components/Editor/extensions/PreserveDiv.d.ts.map +1 -0
  98. package/lib/components/Editor/extensions/PreserveDiv.js +73 -0
  99. package/lib/components/Editor/extensions/PreserveDiv.js.map +1 -0
  100. package/lib/components/Editor/extensions/TableCellSelection.d.ts +4 -0
  101. package/lib/components/Editor/extensions/TableCellSelection.d.ts.map +1 -0
  102. package/lib/components/Editor/extensions/TableCellSelection.js +53 -0
  103. package/lib/components/Editor/extensions/TableCellSelection.js.map +1 -0
  104. package/lib/components/Editor/extensions/TextIndent.d.ts +22 -0
  105. package/lib/components/Editor/extensions/TextIndent.d.ts.map +1 -0
  106. package/lib/components/Editor/extensions/TextIndent.js +137 -0
  107. package/lib/components/Editor/extensions/TextIndent.js.map +1 -0
  108. package/lib/components/Editor/hooks/useCloseOnEditorClick.d.ts +5 -0
  109. package/lib/components/Editor/hooks/useCloseOnEditorClick.d.ts.map +1 -0
  110. package/lib/components/Editor/hooks/useCloseOnEditorClick.js +18 -0
  111. package/lib/components/Editor/hooks/useCloseOnEditorClick.js.map +1 -0
  112. package/lib/components/Editor/hooks/useEscapeKey.d.ts +4 -0
  113. package/lib/components/Editor/hooks/useEscapeKey.d.ts.map +1 -0
  114. package/lib/components/Editor/hooks/useEscapeKey.js +24 -0
  115. package/lib/components/Editor/hooks/useEscapeKey.js.map +1 -0
  116. package/lib/components/Editor/hooks/useIframeSetup.d.ts +54 -0
  117. package/lib/components/Editor/hooks/useIframeSetup.d.ts.map +1 -0
  118. package/lib/components/Editor/hooks/useIframeSetup.js +284 -0
  119. package/lib/components/Editor/hooks/useIframeSetup.js.map +1 -0
  120. package/lib/components/Editor/hooks/useImageActions.d.ts +19 -0
  121. package/lib/components/Editor/hooks/useImageActions.d.ts.map +1 -0
  122. package/lib/components/Editor/hooks/useImageActions.js +198 -0
  123. package/lib/components/Editor/hooks/useImageActions.js.map +1 -0
  124. package/lib/components/Editor/hooks/useTableCellMenu.d.ts +22 -0
  125. package/lib/components/Editor/hooks/useTableCellMenu.d.ts.map +1 -0
  126. package/lib/components/Editor/hooks/useTableCellMenu.js +120 -0
  127. package/lib/components/Editor/hooks/useTableCellMenu.js.map +1 -0
  128. package/lib/components/Editor/iframeContentStyles.d.ts +10 -0
  129. package/lib/components/Editor/iframeContentStyles.d.ts.map +1 -0
  130. package/lib/components/Editor/iframeContentStyles.js +162 -0
  131. package/lib/components/Editor/iframeContentStyles.js.map +1 -0
  132. package/lib/components/Editor/index.d.ts +2 -0
  133. package/lib/components/Editor/index.d.ts.map +1 -1
  134. package/lib/components/Editor/index.js +1 -0
  135. package/lib/components/Editor/index.js.map +1 -1
  136. package/lib/components/Editor/sanitize.d.ts +3 -0
  137. package/lib/components/Editor/sanitize.d.ts.map +1 -0
  138. package/lib/components/Editor/sanitize.js +11 -0
  139. package/lib/components/Editor/sanitize.js.map +1 -0
  140. package/lib/components/Editor/utils/htmlPlaceholder.d.ts +69 -0
  141. package/lib/components/Editor/utils/htmlPlaceholder.d.ts.map +1 -0
  142. package/lib/components/Editor/utils/htmlPlaceholder.js +154 -0
  143. package/lib/components/Editor/utils/htmlPlaceholder.js.map +1 -0
  144. package/lib/components/RichTextEditor/DecoratorComponents/Table.d.ts +6 -4
  145. package/lib/components/RichTextEditor/DecoratorComponents/Table.d.ts.map +1 -1
  146. package/lib/components/RichTextEditor/DecoratorComponents/Table.js +10 -8
  147. package/lib/components/RichTextEditor/DecoratorComponents/Table.js.map +1 -1
  148. package/lib/components/RichTextEditor/RichTextEditor.d.ts.map +1 -1
  149. package/lib/components/RichTextEditor/RichTextEditor.js +15 -2
  150. package/lib/components/RichTextEditor/RichTextEditor.js.map +1 -1
  151. package/lib/components/RichTextEditor/RichTextEditor.styles.d.ts +5 -5
  152. package/lib/components/RichTextEditor/RichTextEditor.styles.d.ts.map +1 -1
  153. package/lib/components/RichTextEditor/RichTextEditor.styles.js +3 -5
  154. package/lib/components/RichTextEditor/RichTextEditor.styles.js.map +1 -1
  155. package/lib/components/RichTextEditor/RichTextEditor.types.d.ts +5 -0
  156. package/lib/components/RichTextEditor/RichTextEditor.types.d.ts.map +1 -1
  157. package/lib/components/RichTextEditor/RichTextEditor.types.js.map +1 -1
  158. package/lib/components/RichTextEditor/RichTextViewer.d.ts.map +1 -1
  159. package/lib/components/RichTextEditor/RichTextViewer.js +9 -2
  160. package/lib/components/RichTextEditor/RichTextViewer.js.map +1 -1
  161. package/lib/components/RichTextEditor/Toolbar/Toolbar.js +1 -1
  162. package/lib/components/RichTextEditor/Toolbar/Toolbar.js.map +1 -1
  163. package/lib/components/RichTextEditor/Toolbar/Toolbar.types.d.ts +4 -4
  164. package/lib/components/RichTextEditor/Toolbar/Toolbar.types.d.ts.map +1 -1
  165. package/lib/components/RichTextEditor/Toolbar/Toolbar.types.js.map +1 -1
  166. package/lib/components/RichTextEditor/Toolbar/ToolbarButton.d.ts.map +1 -1
  167. package/lib/components/RichTextEditor/Toolbar/ToolbarButton.js +41 -26
  168. package/lib/components/RichTextEditor/Toolbar/ToolbarButton.js.map +1 -1
  169. package/lib/components/RichTextEditor/utils/htmlConverter.d.ts +2 -0
  170. package/lib/components/RichTextEditor/utils/htmlConverter.d.ts.map +1 -1
  171. package/lib/components/RichTextEditor/utils/htmlConverter.js +12 -0
  172. package/lib/components/RichTextEditor/utils/htmlConverter.js.map +1 -1
  173. package/lib/components/RichTextEditor/utils/interactionRenderer.d.ts.map +1 -1
  174. package/lib/components/RichTextEditor/utils/interactionRenderer.js +20 -19
  175. package/lib/components/RichTextEditor/utils/interactionRenderer.js.map +1 -1
  176. package/lib/components/RichTextEditor/utils/markdownConverter.d.ts.map +1 -1
  177. package/lib/components/RichTextEditor/utils/markdownConverter.js +131 -30
  178. package/lib/components/RichTextEditor/utils/markdownConverter.js.map +1 -1
  179. package/lib/components/RichTextEditor/utils/renderers.d.ts +5 -3
  180. package/lib/components/RichTextEditor/utils/renderers.d.ts.map +1 -1
  181. package/lib/components/RichTextEditor/utils/renderers.js +62 -34
  182. package/lib/components/RichTextEditor/utils/renderers.js.map +1 -1
  183. package/lib/components/RichTextEditor/utils/slateConverter.d.ts +4 -3
  184. package/lib/components/RichTextEditor/utils/slateConverter.d.ts.map +1 -1
  185. package/lib/components/RichTextEditor/utils/slateConverter.js +86 -38
  186. package/lib/components/RichTextEditor/utils/slateConverter.js.map +1 -1
  187. package/package.json +30 -8
  188. package/lib/components/Editor/ImageEditor.d.ts +0 -10
  189. package/lib/components/Editor/ImageEditor.d.ts.map +0 -1
  190. package/lib/components/Editor/ImageEditor.js +0 -292
  191. 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":"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","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 { FC } from 'react';
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: Editor;
5
+ editor: TiptapEditor;
7
6
  }
8
- declare const AnchorButton: FC<AnchorButtonProps & ForwardProps>;
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":"AACA,OAAO,KAAK,EAAE,EAAE,EAAgE,MAAM,OAAO,CAAC;AAC9F,OAAO,KAAK,EAAE,MAAM,EAAmB,MAAM,SAAS,CAAC;AAavD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAS5D,UAAU,iBAAiB;IACzB,GAAG,EAAE,OAAO,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,QAAA,MAAM,YAAY,EAAE,EAAE,CAAC,iBAAiB,GAAG,YAAY,CA4OtD,CAAC;AAEF,eAAe,YAAY,CAAC"}
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 textInputRef = useRef(null);
13
- const urlInputRef = useRef(null);
14
- const popoverRef = useRef(null);
15
- const [selectedText, setSelectedText] = useState('');
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(false);
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
- setSelection(undefined);
30
- setSelectedText('');
33
+ const resetMenu = useCallback(() => {
34
+ setLinkText('');
31
35
  setUrl('');
32
36
  setUrlMatch(true);
33
37
  setAnchorMenu(false);
34
- };
35
- useOuterEvent('mousedown', [popoverRef, buttonRef], () => {
38
+ originalSelectionRef.current = null;
39
+ }, []);
40
+ useOuterEvent('mousedown', [popoverEl, buttonRef], () => {
36
41
  if (anchorMenu) {
37
42
  resetMenu();
38
43
  }
39
44
  });
40
- const createLink = () => {
41
- if (url && selection) {
42
- editor.focus();
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
- useEffect(() => {
58
- if (anchorMenu) {
59
- if (editor.selection) {
60
- const selectedEl = editor.selection.getNode();
61
- if (selectedEl?.tagName === 'A') {
62
- setSelectedText(selectedEl.textContent || '');
63
- setUrl(selectedEl.getAttribute('href') || '');
64
- }
65
- else {
66
- setSelectedText(editor.selection.getContent({ format: 'text' }));
67
- }
68
- setSelection({ ...editor.selection });
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.selection.getNode().tagName === 'A' && editor.hasFocus();
141
+ return editor.isActive('link') && editor.isFocused;
81
142
  };
82
- const cancelAnchorCreation = (event) => {
83
- if (((event?.type === 'keydown' && event?.key === 'Enter') ||
84
- event?.type === 'mousedown' ||
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?.preventDefault();
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 keyCommandListener = (e) => {
94
- if (e.key === 'k' && (e.metaKey || e.ctrlKey) && editor.hasFocus()) {
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
- if (e.key === 'Escape') {
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.getDoc().addEventListener('keydown', keyCommandListener);
176
+ const iframeWindow = editor.view.dom.ownerDocument.defaultView;
177
+ iframeWindow?.addEventListener('keydown', handleEscape);
178
+ document.addEventListener('keydown', handleEscape);
103
179
  return () => {
104
- editor.getDoc().removeEventListener('keydown', keyCommandListener);
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 (anchorMenu && shouldFocusInput) {
109
- textInputRef.current?.focus();
186
+ if (textInputEl && shouldFocusInput) {
187
+ textInputEl.focus();
110
188
  setShouldFocusInput(false);
111
189
  }
112
- }, [textInputRef.current]);
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.stopPropagation();
194
+ if (e.key !== 'Escape') {
195
+ e.stopPropagation();
196
+ }
117
197
  };
118
- textInputRef.current?.addEventListener('keydown', onKeyDown);
119
- urlInputRef.current?.addEventListener('keydown', onKeyDown);
198
+ textInputEl?.addEventListener('keydown', onKeyDown);
199
+ urlInputEl?.addEventListener('keydown', onKeyDown);
120
200
  return () => {
121
- textInputRef.current?.removeEventListener('keydown', onKeyDown);
122
- urlInputRef.current?.removeEventListener('keydown', onKeyDown);
201
+ textInputEl?.removeEventListener('keydown', onKeyDown);
202
+ urlInputEl?.removeEventListener('keydown', onKeyDown);
123
203
  };
124
- }, [textInputRef.current, urlInputRef.current]);
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
- }, active: isLinkActive(), tooltip: tooltip, label: t('rte_link'), ...restProps, children: _jsx(Icon, { name: 'chain' }) }), _jsx(StyledEditPopover, { show: anchorMenu, target: buttonRef.current, ref: popoverRef, placement: 'bottom', children: _jsx(CardContent, { children: _jsx(Form, { as: 'div', actions: _jsxs(_Fragment, { children: [_jsx(Button, { variant: 'secondary', onKeyDown: cancelAnchorCreation, onMouseDown: cancelAnchorCreation, type: 'button', children: t('cancel') }), _jsx(Button, { disabled: !url || !urlMatch, name: 'apply', variant: 'primary', onClick: (e) => {
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: selectedText, onClick: preventDef, onChange: (e) => {
137
- setSelectedText(e.target.value);
138
- }, ref: textInputRef }), _jsx(Input, { label: t('rte_link_url'), value: url, onClick: preventDef, onChange: (e) => {
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
- catch {
158
- setUrlMatch(false);
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