@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,120 @@
|
|
|
1
|
+
import { useState, useEffect, useCallback } from 'react';
|
|
2
|
+
export default function useTableCellMenu({ tiptapEditor, iframeRef }) {
|
|
3
|
+
const [cellPosition, setCellPosition] = useState(null);
|
|
4
|
+
const [isOverMenu, setIsOverMenu] = useState(false);
|
|
5
|
+
const updateCellPosition = useCallback(() => {
|
|
6
|
+
if (!tiptapEditor || !iframeRef.current) {
|
|
7
|
+
setCellPosition(null);
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
const isInTable = tiptapEditor.isActive('table') ||
|
|
11
|
+
tiptapEditor.isActive('tableCell') ||
|
|
12
|
+
tiptapEditor.isActive('tableHeader');
|
|
13
|
+
if (!isInTable) {
|
|
14
|
+
if (!isOverMenu) {
|
|
15
|
+
setCellPosition(null);
|
|
16
|
+
}
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
const iframeDoc = iframeRef.current.contentDocument;
|
|
20
|
+
if (!iframeDoc) {
|
|
21
|
+
setCellPosition(null);
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
const selection = iframeDoc.getSelection();
|
|
25
|
+
if (!selection || selection.rangeCount === 0) {
|
|
26
|
+
setCellPosition(null);
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
const anchorNode = selection.anchorNode;
|
|
30
|
+
if (!anchorNode) {
|
|
31
|
+
setCellPosition(null);
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
// anchorNode can be a Text node; get the parent element if so
|
|
35
|
+
const element = anchorNode.nodeType === Node.ELEMENT_NODE
|
|
36
|
+
? anchorNode
|
|
37
|
+
: anchorNode.parentElement;
|
|
38
|
+
const cellEl = element?.closest('td') ?? element?.closest('th') ?? null;
|
|
39
|
+
if (!cellEl) {
|
|
40
|
+
setCellPosition(null);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
const cellRect = cellEl.getBoundingClientRect();
|
|
44
|
+
const iframeRect = iframeRef.current.getBoundingClientRect();
|
|
45
|
+
setCellPosition({
|
|
46
|
+
top: iframeRect.top + cellRect.top,
|
|
47
|
+
left: iframeRect.left + cellRect.left,
|
|
48
|
+
width: cellRect.width,
|
|
49
|
+
height: cellRect.height
|
|
50
|
+
});
|
|
51
|
+
}, [tiptapEditor, iframeRef, isOverMenu]);
|
|
52
|
+
useEffect(() => {
|
|
53
|
+
if (!tiptapEditor)
|
|
54
|
+
return;
|
|
55
|
+
updateCellPosition();
|
|
56
|
+
tiptapEditor.on('selectionUpdate', updateCellPosition);
|
|
57
|
+
tiptapEditor.on('transaction', updateCellPosition);
|
|
58
|
+
// Update position on resize and scroll so menu follows the cell
|
|
59
|
+
window.addEventListener('resize', updateCellPosition);
|
|
60
|
+
window.addEventListener('scroll', updateCellPosition, true);
|
|
61
|
+
// Also listen for scroll within the iframe
|
|
62
|
+
const iframeWindow = iframeRef.current?.contentWindow;
|
|
63
|
+
iframeWindow?.addEventListener('scroll', updateCellPosition, true);
|
|
64
|
+
return () => {
|
|
65
|
+
tiptapEditor.off('selectionUpdate', updateCellPosition);
|
|
66
|
+
tiptapEditor.off('transaction', updateCellPosition);
|
|
67
|
+
window.removeEventListener('resize', updateCellPosition);
|
|
68
|
+
window.removeEventListener('scroll', updateCellPosition, true);
|
|
69
|
+
iframeWindow?.removeEventListener('scroll', updateCellPosition, true);
|
|
70
|
+
};
|
|
71
|
+
}, [tiptapEditor, iframeRef, updateCellPosition]);
|
|
72
|
+
const addRowBelow = useCallback(() => {
|
|
73
|
+
if (!tiptapEditor)
|
|
74
|
+
return;
|
|
75
|
+
tiptapEditor.chain().focus().addRowAfter().run();
|
|
76
|
+
}, [tiptapEditor]);
|
|
77
|
+
const addRowAbove = useCallback(() => {
|
|
78
|
+
if (!tiptapEditor)
|
|
79
|
+
return;
|
|
80
|
+
tiptapEditor.chain().focus().addRowBefore().run();
|
|
81
|
+
}, [tiptapEditor]);
|
|
82
|
+
const addColumnAfter = useCallback(() => {
|
|
83
|
+
if (!tiptapEditor)
|
|
84
|
+
return;
|
|
85
|
+
tiptapEditor.chain().focus().addColumnAfter().run();
|
|
86
|
+
}, [tiptapEditor]);
|
|
87
|
+
const addColumnBefore = useCallback(() => {
|
|
88
|
+
if (!tiptapEditor)
|
|
89
|
+
return;
|
|
90
|
+
tiptapEditor.chain().focus().addColumnBefore().run();
|
|
91
|
+
}, [tiptapEditor]);
|
|
92
|
+
const deleteRow = useCallback(() => {
|
|
93
|
+
if (!tiptapEditor)
|
|
94
|
+
return;
|
|
95
|
+
tiptapEditor.chain().focus().deleteRow().run();
|
|
96
|
+
}, [tiptapEditor]);
|
|
97
|
+
const deleteColumn = useCallback(() => {
|
|
98
|
+
if (!tiptapEditor)
|
|
99
|
+
return;
|
|
100
|
+
tiptapEditor.chain().focus().deleteColumn().run();
|
|
101
|
+
}, [tiptapEditor]);
|
|
102
|
+
const deleteTable = useCallback(() => {
|
|
103
|
+
if (!tiptapEditor)
|
|
104
|
+
return;
|
|
105
|
+
tiptapEditor.chain().focus().deleteTable().run();
|
|
106
|
+
}, [tiptapEditor]);
|
|
107
|
+
return {
|
|
108
|
+
cellPosition,
|
|
109
|
+
isOverMenu,
|
|
110
|
+
setIsOverMenu,
|
|
111
|
+
addRowBelow,
|
|
112
|
+
addRowAbove,
|
|
113
|
+
addColumnAfter,
|
|
114
|
+
addColumnBefore,
|
|
115
|
+
deleteRow,
|
|
116
|
+
deleteColumn,
|
|
117
|
+
deleteTable
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
//# sourceMappingURL=useTableCellMenu.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useTableCellMenu.js","sourceRoot":"","sources":["../../../../src/components/Editor/hooks/useTableCellMenu.ts"],"names":[],"mappings":"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","sourcesContent":["import { useState, useEffect, useCallback } from 'react';\nimport type { RefObject } from 'react';\nimport type { Editor as TiptapEditor } from '@tiptap/core';\n\nimport type { CellPosition } from '../TableCellMenu';\n\ninterface UseTableCellMenuParams {\n tiptapEditor: TiptapEditor | null;\n iframeRef: RefObject<HTMLIFrameElement>;\n}\n\ninterface UseTableCellMenuReturn {\n cellPosition: CellPosition | null;\n isOverMenu: boolean;\n setIsOverMenu: (value: boolean) => void;\n addRowBelow: () => void;\n addRowAbove: () => void;\n addColumnAfter: () => void;\n addColumnBefore: () => void;\n deleteRow: () => void;\n deleteColumn: () => void;\n deleteTable: () => void;\n}\n\nexport default function useTableCellMenu({\n tiptapEditor,\n iframeRef\n}: UseTableCellMenuParams): UseTableCellMenuReturn {\n const [cellPosition, setCellPosition] = useState<CellPosition | null>(null);\n const [isOverMenu, setIsOverMenu] = useState(false);\n\n const updateCellPosition = useCallback(() => {\n if (!tiptapEditor || !iframeRef.current) {\n setCellPosition(null);\n return;\n }\n\n const isInTable =\n tiptapEditor.isActive('table') ||\n tiptapEditor.isActive('tableCell') ||\n tiptapEditor.isActive('tableHeader');\n\n if (!isInTable) {\n if (!isOverMenu) {\n setCellPosition(null);\n }\n return;\n }\n\n const iframeDoc = iframeRef.current.contentDocument;\n if (!iframeDoc) {\n setCellPosition(null);\n return;\n }\n\n const selection = iframeDoc.getSelection();\n if (!selection || selection.rangeCount === 0) {\n setCellPosition(null);\n return;\n }\n\n const anchorNode = selection.anchorNode;\n if (!anchorNode) {\n setCellPosition(null);\n return;\n }\n\n // anchorNode can be a Text node; get the parent element if so\n const element =\n anchorNode.nodeType === Node.ELEMENT_NODE\n ? (anchorNode as HTMLElement)\n : anchorNode.parentElement;\n\n const cellEl = element?.closest('td') ?? element?.closest('th') ?? null;\n\n if (!cellEl) {\n setCellPosition(null);\n return;\n }\n\n const cellRect = cellEl.getBoundingClientRect();\n const iframeRect = iframeRef.current.getBoundingClientRect();\n\n setCellPosition({\n top: iframeRect.top + cellRect.top,\n left: iframeRect.left + cellRect.left,\n width: cellRect.width,\n height: cellRect.height\n });\n }, [tiptapEditor, iframeRef, isOverMenu]);\n\n useEffect(() => {\n if (!tiptapEditor) return;\n\n updateCellPosition();\n tiptapEditor.on('selectionUpdate', updateCellPosition);\n tiptapEditor.on('transaction', updateCellPosition);\n\n // Update position on resize and scroll so menu follows the cell\n window.addEventListener('resize', updateCellPosition);\n window.addEventListener('scroll', updateCellPosition, true);\n\n // Also listen for scroll within the iframe\n const iframeWindow = iframeRef.current?.contentWindow;\n iframeWindow?.addEventListener('scroll', updateCellPosition, true);\n\n return () => {\n tiptapEditor.off('selectionUpdate', updateCellPosition);\n tiptapEditor.off('transaction', updateCellPosition);\n window.removeEventListener('resize', updateCellPosition);\n window.removeEventListener('scroll', updateCellPosition, true);\n iframeWindow?.removeEventListener('scroll', updateCellPosition, true);\n };\n }, [tiptapEditor, iframeRef, updateCellPosition]);\n\n const addRowBelow = useCallback(() => {\n if (!tiptapEditor) return;\n tiptapEditor.chain().focus().addRowAfter().run();\n }, [tiptapEditor]);\n\n const addRowAbove = useCallback(() => {\n if (!tiptapEditor) return;\n tiptapEditor.chain().focus().addRowBefore().run();\n }, [tiptapEditor]);\n\n const addColumnAfter = useCallback(() => {\n if (!tiptapEditor) return;\n tiptapEditor.chain().focus().addColumnAfter().run();\n }, [tiptapEditor]);\n\n const addColumnBefore = useCallback(() => {\n if (!tiptapEditor) return;\n tiptapEditor.chain().focus().addColumnBefore().run();\n }, [tiptapEditor]);\n\n const deleteRow = useCallback(() => {\n if (!tiptapEditor) return;\n tiptapEditor.chain().focus().deleteRow().run();\n }, [tiptapEditor]);\n\n const deleteColumn = useCallback(() => {\n if (!tiptapEditor) return;\n tiptapEditor.chain().focus().deleteColumn().run();\n }, [tiptapEditor]);\n\n const deleteTable = useCallback(() => {\n if (!tiptapEditor) return;\n tiptapEditor.chain().focus().deleteTable().run();\n }, [tiptapEditor]);\n\n return {\n cellPosition,\n isOverMenu,\n setIsOverMenu,\n addRowBelow,\n addRowAbove,\n addColumnAfter,\n addColumnBefore,\n deleteRow,\n deleteColumn,\n deleteTable\n };\n}\n"]}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { useTheme } from '@pega/cosmos-react-core';
|
|
2
|
+
type Theme = ReturnType<typeof useTheme>;
|
|
3
|
+
export declare const getPlaceholderStyles: (theme: Theme) => string;
|
|
4
|
+
export declare const getEditorStyles: (theme: Theme) => string;
|
|
5
|
+
export declare const getImageStyles: (theme: Theme) => string;
|
|
6
|
+
export declare const getTableStyles: (theme: Theme) => string;
|
|
7
|
+
export declare const getListStyles: () => string;
|
|
8
|
+
export declare const getIframeContentStyles: (theme: Theme) => string;
|
|
9
|
+
export {};
|
|
10
|
+
//# sourceMappingURL=iframeContentStyles.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"iframeContentStyles.d.ts","sourceRoot":"","sources":["../../../src/components/Editor/iframeContentStyles.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAC;AAEnD,KAAK,KAAK,GAAG,UAAU,CAAC,OAAO,QAAQ,CAAC,CAAC;AAEzC,eAAO,MAAM,oBAAoB,GAAI,OAAO,KAAK,KAAG,MAYnD,CAAC;AAEF,eAAO,MAAM,eAAe,GAAI,OAAO,KAAK,KAAG,MAwB9C,CAAC;AAEF,eAAO,MAAM,cAAc,GAAI,OAAO,KAAK,KAAG,MAU7C,CAAC;AAEF,eAAO,MAAM,cAAc,GAAI,OAAO,KAAK,KAAG,MA6E7C,CAAC;AAEF,eAAO,MAAM,aAAa,QAAO,MA4BhC,CAAC;AAEF,eAAO,MAAM,sBAAsB,GAAI,OAAO,KAAK,KAAG,MAMrD,CAAC"}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { mix } from 'polished';
|
|
2
|
+
import { useTheme } from '@pega/cosmos-react-core';
|
|
3
|
+
export const getPlaceholderStyles = (theme) => `
|
|
4
|
+
.ProseMirror p.is-editor-empty:first-child::before {
|
|
5
|
+
color: ${mix(theme.base.transparency['transparent-3'], theme.base.palette['foreground-color'], theme.components['form-control']['background-color'])};
|
|
6
|
+
content: attr(data-placeholder);
|
|
7
|
+
float: left;
|
|
8
|
+
height: 0;
|
|
9
|
+
pointer-events: none;
|
|
10
|
+
}
|
|
11
|
+
`;
|
|
12
|
+
export const getEditorStyles = (theme) => `
|
|
13
|
+
html {
|
|
14
|
+
overflow: hidden;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
body {
|
|
18
|
+
min-height: 3rem;
|
|
19
|
+
padding: ${theme.base.spacing};
|
|
20
|
+
background: unset;
|
|
21
|
+
cursor: text;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
#tiptap-root {
|
|
25
|
+
min-height: 100%;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.ProseMirror {
|
|
29
|
+
outline: none;
|
|
30
|
+
overflow-wrap: break-word;
|
|
31
|
+
word-wrap: break-word;
|
|
32
|
+
word-break: break-word;
|
|
33
|
+
min-height: 100%;
|
|
34
|
+
cursor: text;
|
|
35
|
+
}
|
|
36
|
+
`;
|
|
37
|
+
export const getImageStyles = (theme) => `
|
|
38
|
+
.ProseMirror img {
|
|
39
|
+
max-width: 100%;
|
|
40
|
+
height: auto;
|
|
41
|
+
cursor: pointer;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.ProseMirror img.ProseMirror-selectednode {
|
|
45
|
+
outline: calc(0.125 * ${theme.base.spacing}) solid ${theme.base.palette.interactive};
|
|
46
|
+
}
|
|
47
|
+
`;
|
|
48
|
+
export const getTableStyles = (theme) => `
|
|
49
|
+
.ProseMirror table,
|
|
50
|
+
.ProseMirror table thead,
|
|
51
|
+
.ProseMirror table tbody,
|
|
52
|
+
.ProseMirror table colgroup,
|
|
53
|
+
.ProseMirror table col,
|
|
54
|
+
.ProseMirror table tr,
|
|
55
|
+
.ProseMirror table td,
|
|
56
|
+
.ProseMirror table th {
|
|
57
|
+
border: none;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.ProseMirror table {
|
|
61
|
+
border-collapse: separate;
|
|
62
|
+
border-spacing: 0;
|
|
63
|
+
table-layout: fixed;
|
|
64
|
+
width: 100%;
|
|
65
|
+
margin: 0;
|
|
66
|
+
border-top: calc(0.0625 * ${theme.base.spacing}) solid ${theme.components['form-control']['border-color']};
|
|
67
|
+
border-left: calc(0.0625 * ${theme.base.spacing}) solid ${theme.components['form-control']['border-color']};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.ProseMirror table td,
|
|
71
|
+
.ProseMirror table th {
|
|
72
|
+
min-width: 1em;
|
|
73
|
+
border-right: calc(0.0625 * ${theme.base.spacing}) solid ${theme.components['form-control']['border-color']};
|
|
74
|
+
border-bottom: calc(0.0625 * ${theme.base.spacing}) solid ${theme.components['form-control']['border-color']};
|
|
75
|
+
padding: calc(0.5 * ${theme.base.spacing});
|
|
76
|
+
vertical-align: top;
|
|
77
|
+
position: relative;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.ProseMirror table th {
|
|
81
|
+
font-weight: bold;
|
|
82
|
+
background-color: ${theme.base.palette['primary-background']};
|
|
83
|
+
text-align: left;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.ProseMirror table .selectedCell:after {
|
|
87
|
+
content: "";
|
|
88
|
+
position: absolute;
|
|
89
|
+
left: 0;
|
|
90
|
+
right: 0;
|
|
91
|
+
top: 0;
|
|
92
|
+
bottom: 0;
|
|
93
|
+
background-color: ${theme.base.palette.interactive};
|
|
94
|
+
opacity: 0.15;
|
|
95
|
+
pointer-events: none;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
.ProseMirror table td,
|
|
99
|
+
.ProseMirror table th {
|
|
100
|
+
position: relative;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
.ProseMirror table .selectedCell::selection,
|
|
104
|
+
.ProseMirror table .selectedCell *::selection {
|
|
105
|
+
background: transparent;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
.ProseMirror .column-resize-handle {
|
|
109
|
+
position: absolute;
|
|
110
|
+
right: 0;
|
|
111
|
+
top: 0;
|
|
112
|
+
bottom: 0;
|
|
113
|
+
width: calc(0.25 * ${theme.base.spacing});
|
|
114
|
+
background-color: ${theme.base.palette.interactive};
|
|
115
|
+
pointer-events: none;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
.ProseMirror.resize-cursor {
|
|
119
|
+
cursor: col-resize;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
.ProseMirror .tableWrapper {
|
|
123
|
+
overflow-x: auto;
|
|
124
|
+
}
|
|
125
|
+
`;
|
|
126
|
+
export const getListStyles = () => `
|
|
127
|
+
/* Hide default marker for aligned list items and use inline marker instead */
|
|
128
|
+
.ProseMirror ul li:has(> p[style*="text-align: center"]),
|
|
129
|
+
.ProseMirror ul li:has(> p[style*="text-align: right"]),
|
|
130
|
+
.ProseMirror ol li:has(> p[style*="text-align: center"]),
|
|
131
|
+
.ProseMirror ol li:has(> p[style*="text-align: right"]) {
|
|
132
|
+
list-style: none;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/* Add bullet marker inline with centered/right-aligned text */
|
|
136
|
+
.ProseMirror ul li:has(> p[style*="text-align: center"]) > p::before,
|
|
137
|
+
.ProseMirror ul li:has(> p[style*="text-align: right"]) > p::before {
|
|
138
|
+
content: "• ";
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/* Add number marker inline with centered/right-aligned text in ordered lists */
|
|
142
|
+
.ProseMirror ol {
|
|
143
|
+
counter-reset: list-counter;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
.ProseMirror ol li {
|
|
147
|
+
counter-increment: list-counter;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
.ProseMirror ol li:has(> p[style*="text-align: center"]) > p::before,
|
|
151
|
+
.ProseMirror ol li:has(> p[style*="text-align: right"]) > p::before {
|
|
152
|
+
content: counter(list-counter) ". ";
|
|
153
|
+
}
|
|
154
|
+
`;
|
|
155
|
+
export const getIframeContentStyles = (theme) => `
|
|
156
|
+
${getEditorStyles(theme)}
|
|
157
|
+
${getImageStyles(theme)}
|
|
158
|
+
${getTableStyles(theme)}
|
|
159
|
+
${getPlaceholderStyles(theme)}
|
|
160
|
+
${getListStyles()}
|
|
161
|
+
`;
|
|
162
|
+
//# sourceMappingURL=iframeContentStyles.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"iframeContentStyles.js","sourceRoot":"","sources":["../../../src/components/Editor/iframeContentStyles.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AAE/B,OAAO,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAC;AAInD,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC,KAAY,EAAU,EAAE,CAAC;;aAEjD,GAAG,CACV,KAAK,CAAC,IAAI,CAAC,YAAY,CAAC,eAAe,CAAC,EACxC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,kBAAkB,CAAC,EACtC,KAAK,CAAC,UAAU,CAAC,cAAc,CAAC,CAAC,kBAAkB,CAAC,CACrD;;;;;;CAMJ,CAAC;AAEF,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,KAAY,EAAU,EAAE,CAAC;;;;;;;eAO1C,KAAK,CAAC,IAAI,CAAC,OAAO;;;;;;;;;;;;;;;;;CAiBhC,CAAC;AAEF,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,KAAY,EAAU,EAAE,CAAC;;;;;;;;4BAQ5B,KAAK,CAAC,IAAI,CAAC,OAAO,WAAW,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,WAAW;;CAEtF,CAAC;AAEF,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,KAAY,EAAU,EAAE,CAAC;;;;;;;;;;;;;;;;;;gCAkBxB,KAAK,CAAC,IAAI,CAAC,OAAO,WAAW,KAAK,CAAC,UAAU,CAAC,cAAc,CAAC,CAAC,cAAc,CAAC;iCAC5E,KAAK,CAAC,IAAI,CAAC,OAAO,WAAW,KAAK,CAAC,UAAU,CAAC,cAAc,CAAC,CAAC,cAAc,CAAC;;;;;;kCAM5E,KAAK,CAAC,IAAI,CAAC,OAAO,WAAW,KAAK,CAAC,UAAU,CAAC,cAAc,CAAC,CAAC,cAAc,CAAC;mCAC5E,KAAK,CAAC,IAAI,CAAC,OAAO,WAAW,KAAK,CAAC,UAAU,CAAC,cAAc,CAAC,CAAC,cAAc,CAAC;0BACtF,KAAK,CAAC,IAAI,CAAC,OAAO;;;;;;;wBAOpB,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,oBAAoB,CAAC;;;;;;;;;;;wBAWxC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,WAAW;;;;;;;;;;;;;;;;;;;;yBAoB7B,KAAK,CAAC,IAAI,CAAC,OAAO;wBACnB,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,WAAW;;;;;;;;;;;CAWrD,CAAC;AAEF,MAAM,CAAC,MAAM,aAAa,GAAG,GAAW,EAAE,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA4B1C,CAAC;AAEF,MAAM,CAAC,MAAM,sBAAsB,GAAG,CAAC,KAAY,EAAU,EAAE,CAAC;IAC5D,eAAe,CAAC,KAAK,CAAC;IACtB,cAAc,CAAC,KAAK,CAAC;IACrB,cAAc,CAAC,KAAK,CAAC;IACrB,oBAAoB,CAAC,KAAK,CAAC;IAC3B,aAAa,EAAE;CAClB,CAAC","sourcesContent":["import { mix } from 'polished';\n\nimport { useTheme } from '@pega/cosmos-react-core';\n\ntype Theme = ReturnType<typeof useTheme>;\n\nexport const getPlaceholderStyles = (theme: Theme): string => `\n .ProseMirror p.is-editor-empty:first-child::before {\n color: ${mix(\n theme.base.transparency['transparent-3'],\n theme.base.palette['foreground-color'],\n theme.components['form-control']['background-color']\n )};\n content: attr(data-placeholder);\n float: left;\n height: 0;\n pointer-events: none;\n }\n`;\n\nexport const getEditorStyles = (theme: Theme): string => `\n html {\n overflow: hidden;\n }\n\n body {\n min-height: 3rem;\n padding: ${theme.base.spacing};\n background: unset;\n cursor: text;\n }\n\n #tiptap-root {\n min-height: 100%;\n }\n\n .ProseMirror {\n outline: none;\n overflow-wrap: break-word;\n word-wrap: break-word;\n word-break: break-word;\n min-height: 100%;\n cursor: text;\n }\n`;\n\nexport const getImageStyles = (theme: Theme): string => `\n .ProseMirror img {\n max-width: 100%;\n height: auto;\n cursor: pointer;\n }\n\n .ProseMirror img.ProseMirror-selectednode {\n outline: calc(0.125 * ${theme.base.spacing}) solid ${theme.base.palette.interactive};\n }\n`;\n\nexport const getTableStyles = (theme: Theme): string => `\n .ProseMirror table,\n .ProseMirror table thead,\n .ProseMirror table tbody,\n .ProseMirror table colgroup,\n .ProseMirror table col,\n .ProseMirror table tr,\n .ProseMirror table td,\n .ProseMirror table th {\n border: none;\n }\n\n .ProseMirror table {\n border-collapse: separate;\n border-spacing: 0;\n table-layout: fixed;\n width: 100%;\n margin: 0;\n border-top: calc(0.0625 * ${theme.base.spacing}) solid ${theme.components['form-control']['border-color']};\n border-left: calc(0.0625 * ${theme.base.spacing}) solid ${theme.components['form-control']['border-color']};\n }\n\n .ProseMirror table td,\n .ProseMirror table th {\n min-width: 1em;\n border-right: calc(0.0625 * ${theme.base.spacing}) solid ${theme.components['form-control']['border-color']};\n border-bottom: calc(0.0625 * ${theme.base.spacing}) solid ${theme.components['form-control']['border-color']};\n padding: calc(0.5 * ${theme.base.spacing});\n vertical-align: top;\n position: relative;\n }\n\n .ProseMirror table th {\n font-weight: bold;\n background-color: ${theme.base.palette['primary-background']};\n text-align: left;\n }\n\n .ProseMirror table .selectedCell:after {\n content: \"\";\n position: absolute;\n left: 0;\n right: 0;\n top: 0;\n bottom: 0;\n background-color: ${theme.base.palette.interactive};\n opacity: 0.15;\n pointer-events: none;\n }\n\n .ProseMirror table td,\n .ProseMirror table th {\n position: relative;\n }\n\n .ProseMirror table .selectedCell::selection,\n .ProseMirror table .selectedCell *::selection {\n background: transparent;\n }\n\n .ProseMirror .column-resize-handle {\n position: absolute;\n right: 0;\n top: 0;\n bottom: 0;\n width: calc(0.25 * ${theme.base.spacing});\n background-color: ${theme.base.palette.interactive};\n pointer-events: none;\n }\n\n .ProseMirror.resize-cursor {\n cursor: col-resize;\n }\n\n .ProseMirror .tableWrapper {\n overflow-x: auto;\n }\n`;\n\nexport const getListStyles = (): string => `\n /* Hide default marker for aligned list items and use inline marker instead */\n .ProseMirror ul li:has(> p[style*=\"text-align: center\"]),\n .ProseMirror ul li:has(> p[style*=\"text-align: right\"]),\n .ProseMirror ol li:has(> p[style*=\"text-align: center\"]),\n .ProseMirror ol li:has(> p[style*=\"text-align: right\"]) {\n list-style: none;\n }\n\n /* Add bullet marker inline with centered/right-aligned text */\n .ProseMirror ul li:has(> p[style*=\"text-align: center\"]) > p::before,\n .ProseMirror ul li:has(> p[style*=\"text-align: right\"]) > p::before {\n content: \"• \";\n }\n\n /* Add number marker inline with centered/right-aligned text in ordered lists */\n .ProseMirror ol {\n counter-reset: list-counter;\n }\n\n .ProseMirror ol li {\n counter-increment: list-counter;\n }\n\n .ProseMirror ol li:has(> p[style*=\"text-align: center\"]) > p::before,\n .ProseMirror ol li:has(> p[style*=\"text-align: right\"]) > p::before {\n content: counter(list-counter) \". \";\n }\n`;\n\nexport const getIframeContentStyles = (theme: Theme): string => `\n ${getEditorStyles(theme)}\n ${getImageStyles(theme)}\n ${getTableStyles(theme)}\n ${getPlaceholderStyles(theme)}\n ${getListStyles()}\n`;\n"]}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
export { default as Editor } from './Editor';
|
|
2
2
|
export type { EditorProps, EditorState } from './Editor.types';
|
|
3
3
|
export { useEditorContext } from './Editor.context';
|
|
4
|
+
export { extractMentions, restoreMentionPlaceholders } from './utils/htmlPlaceholder';
|
|
5
|
+
export type { PlaceholderMap } from './utils/htmlPlaceholder';
|
|
4
6
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/Editor/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,MAAM,EAAE,MAAM,UAAU,CAAC;AAC7C,YAAY,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC/D,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/Editor/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,MAAM,EAAE,MAAM,UAAU,CAAC;AAC7C,YAAY,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC/D,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAAE,eAAe,EAAE,0BAA0B,EAAE,MAAM,yBAAyB,CAAC;AACtF,YAAY,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/components/Editor/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,MAAM,EAAE,MAAM,UAAU,CAAC;AAE7C,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC","sourcesContent":["export { default as Editor } from './Editor';\nexport type { EditorProps, EditorState } from './Editor.types';\nexport { useEditorContext } from './Editor.context';\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/components/Editor/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,MAAM,EAAE,MAAM,UAAU,CAAC;AAE7C,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAAE,eAAe,EAAE,0BAA0B,EAAE,MAAM,yBAAyB,CAAC","sourcesContent":["export { default as Editor } from './Editor';\nexport type { EditorProps, EditorState } from './Editor.types';\nexport { useEditorContext } from './Editor.context';\nexport { extractMentions, restoreMentionPlaceholders } from './utils/htmlPlaceholder';\nexport type { PlaceholderMap } from './utils/htmlPlaceholder';\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sanitize.d.ts","sourceRoot":"","sources":["../../../src/components/Editor/sanitize.ts"],"names":[],"mappings":"AASA,QAAA,MAAM,YAAY,GAAI,MAAM,MAAM,KAAG,MAEpC,CAAC;AAEF,eAAe,YAAY,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import DOMPurify from 'dompurify';
|
|
2
|
+
const SANITIZE_CONFIG = {
|
|
3
|
+
FORBID_TAGS: ['iframe', 'object', 'embed', 'form', 'input', 'button'],
|
|
4
|
+
FORBID_ATTR: ['srcset'],
|
|
5
|
+
ALLOWED_URI_REGEXP: /^(?:https?|mailto|tel|ftp):/i
|
|
6
|
+
};
|
|
7
|
+
const sanitizeHtml = (html) => {
|
|
8
|
+
return DOMPurify.sanitize(html, SANITIZE_CONFIG);
|
|
9
|
+
};
|
|
10
|
+
export default sanitizeHtml;
|
|
11
|
+
//# sourceMappingURL=sanitize.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sanitize.js","sourceRoot":"","sources":["../../../src/components/Editor/sanitize.ts"],"names":[],"mappings":"AAAA,OAAO,SAAS,MAAM,WAAW,CAAC;AAGlC,MAAM,eAAe,GAAW;IAC9B,WAAW,EAAE,CAAC,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,CAAC;IACrE,WAAW,EAAE,CAAC,QAAQ,CAAC;IACvB,kBAAkB,EAAE,8BAA8B;CACnD,CAAC;AAEF,MAAM,YAAY,GAAG,CAAC,IAAY,EAAU,EAAE;IAC5C,OAAO,SAAS,CAAC,QAAQ,CAAC,IAAI,EAAE,eAAe,CAAC,CAAC;AACnD,CAAC,CAAC;AAEF,eAAe,YAAY,CAAC","sourcesContent":["import DOMPurify from 'dompurify';\nimport type { Config } from 'dompurify';\n\nconst SANITIZE_CONFIG: Config = {\n FORBID_TAGS: ['iframe', 'object', 'embed', 'form', 'input', 'button'],\n FORBID_ATTR: ['srcset'],\n ALLOWED_URI_REGEXP: /^(?:https?|mailto|tel|ftp):/i\n};\n\nconst sanitizeHtml = (html: string): string => {\n return DOMPurify.sanitize(html, SANITIZE_CONFIG);\n};\n\nexport default sanitizeHtml;\n"]}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Placeholder utilities for AI rewrite flows.
|
|
3
|
+
*
|
|
4
|
+
* Both the RTE (Tiptap/HTML) and Feed (Slate/markdown) paths need to protect
|
|
5
|
+
* certain tokens — links, images, user mentions — before sending text to the
|
|
6
|
+
* AI, and restore them afterwards. This file provides both sets of helpers.
|
|
7
|
+
*
|
|
8
|
+
* ## RTE (HTML) — `extractLinkAndImg` / `restorePlaceholders`
|
|
9
|
+
* Replaces `<a>` and `<img>` elements with `[PEGA-IMG-XXXX]` / `[PEGA-LINK-XXXX]`
|
|
10
|
+
* placeholders using DOMParser. Reduces payload size and prevents the AI from
|
|
11
|
+
* stripping or mangling anchor/image tags. Images wrapped in an anchor are
|
|
12
|
+
* captured as a single LINK placeholder so the full `<a><img/></a>` structure
|
|
13
|
+
* is stored intact.
|
|
14
|
+
*
|
|
15
|
+
* ## Feed (markdown) — `extractMentions` / `restoreMentionPlaceholders`
|
|
16
|
+
* Replaces `<pega-mention .../>` XML elements with `[PEGA-MENTION-XXXX]`
|
|
17
|
+
* placeholders using regex. Prevents the AI from dropping or mangling user
|
|
18
|
+
* @mention markup that is stored inline in Slate markdown.
|
|
19
|
+
*
|
|
20
|
+
* In both cases: placeholders the AI dropped are appended at the end;
|
|
21
|
+
* hallucinated placeholder strings not in the map are stripped;
|
|
22
|
+
* placeholder-like strings the user literally typed are preserved.
|
|
23
|
+
*/
|
|
24
|
+
export interface PlaceholderMap {
|
|
25
|
+
[placeholder: string]: string;
|
|
26
|
+
}
|
|
27
|
+
interface ExtractResult {
|
|
28
|
+
placeholderHtml: string;
|
|
29
|
+
placeholderMap: PlaceholderMap;
|
|
30
|
+
userPlaceholders: string[];
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Replaces all `<a>` and standalone `<img>` elements in the HTML with unique
|
|
34
|
+
* placeholders. Uses DOMParser to reliably locate elements rather than regex.
|
|
35
|
+
* Anchors are processed first so that linked images (`<a><img/></a>`) are
|
|
36
|
+
* captured as a single LINK placeholder.
|
|
37
|
+
*/
|
|
38
|
+
export declare function extractLinkAndImg(html: string): ExtractResult;
|
|
39
|
+
/**
|
|
40
|
+
* Restores placeholders in the rewritten HTML with the original HTML elements.
|
|
41
|
+
* Placeholders that were dropped by the AI are appended at the end, each
|
|
42
|
+
* wrapped in a `<p>` tag (TipTap's default block element).
|
|
43
|
+
* Any hallucinated placeholder strings (not in the map) are stripped.
|
|
44
|
+
*/
|
|
45
|
+
export declare function restorePlaceholders(html: string, placeholderMap: PlaceholderMap, userPlaceholders?: string[]): string;
|
|
46
|
+
interface ExtractMentionsResult {
|
|
47
|
+
cleanText: string;
|
|
48
|
+
mentionMap: PlaceholderMap;
|
|
49
|
+
userPlaceholders: string[];
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Extracts all `<pega-mention .../>` elements from a markdown string,
|
|
53
|
+
* replacing them with unique `[PEGA-MENTION-XXXX]` placeholders.
|
|
54
|
+
*
|
|
55
|
+
* Call this before sending Feed text to the AI for rewriting. After the AI
|
|
56
|
+
* responds, call `restoreMentionPlaceholders` to put the original mention XML back.
|
|
57
|
+
*/
|
|
58
|
+
export declare function extractMentions(markdown: string): ExtractMentionsResult;
|
|
59
|
+
/**
|
|
60
|
+
* Restores `<pega-mention .../>` XML elements in AI-rewritten Feed text.
|
|
61
|
+
*
|
|
62
|
+
* - Placeholders present in the AI response are replaced with the original mention XML.
|
|
63
|
+
* - Placeholders dropped by the AI are appended at the end (preserving all mentions).
|
|
64
|
+
* - Hallucinated placeholder strings (not in the map) are stripped, unless they were
|
|
65
|
+
* typed literally by the user.
|
|
66
|
+
*/
|
|
67
|
+
export declare function restoreMentionPlaceholders(text: string, mentionMap: PlaceholderMap, userPlaceholders?: string[]): string;
|
|
68
|
+
export {};
|
|
69
|
+
//# sourceMappingURL=htmlPlaceholder.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"htmlPlaceholder.d.ts","sourceRoot":"","sources":["../../../../src/components/Editor/utils/htmlPlaceholder.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAIH,MAAM,WAAW,cAAc;IAC7B,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAAC;CAC/B;AAED,UAAU,aAAa;IACrB,eAAe,EAAE,MAAM,CAAC;IACxB,cAAc,EAAE,cAAc,CAAC;IAC/B,gBAAgB,EAAE,MAAM,EAAE,CAAC;CAC5B;AAgBD;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,aAAa,CA0B7D;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,MAAM,EACZ,cAAc,EAAE,cAAc,EAC9B,gBAAgB,GAAE,MAAM,EAAO,GAC9B,MAAM,CAsBR;AAYD,UAAU,qBAAqB;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,cAAc,CAAC;IAC3B,gBAAgB,EAAE,MAAM,EAAE,CAAC;CAC5B;AAUD;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,qBAAqB,CAgBvE;AAED;;;;;;;GAOG;AACH,wBAAgB,0BAA0B,CACxC,IAAI,EAAE,MAAM,EACZ,UAAU,EAAE,cAAc,EAC1B,gBAAgB,GAAE,MAAM,EAAO,GAC9B,MAAM,CA4BR"}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Placeholder utilities for AI rewrite flows.
|
|
3
|
+
*
|
|
4
|
+
* Both the RTE (Tiptap/HTML) and Feed (Slate/markdown) paths need to protect
|
|
5
|
+
* certain tokens — links, images, user mentions — before sending text to the
|
|
6
|
+
* AI, and restore them afterwards. This file provides both sets of helpers.
|
|
7
|
+
*
|
|
8
|
+
* ## RTE (HTML) — `extractLinkAndImg` / `restorePlaceholders`
|
|
9
|
+
* Replaces `<a>` and `<img>` elements with `[PEGA-IMG-XXXX]` / `[PEGA-LINK-XXXX]`
|
|
10
|
+
* placeholders using DOMParser. Reduces payload size and prevents the AI from
|
|
11
|
+
* stripping or mangling anchor/image tags. Images wrapped in an anchor are
|
|
12
|
+
* captured as a single LINK placeholder so the full `<a><img/></a>` structure
|
|
13
|
+
* is stored intact.
|
|
14
|
+
*
|
|
15
|
+
* ## Feed (markdown) — `extractMentions` / `restoreMentionPlaceholders`
|
|
16
|
+
* Replaces `<pega-mention .../>` XML elements with `[PEGA-MENTION-XXXX]`
|
|
17
|
+
* placeholders using regex. Prevents the AI from dropping or mangling user
|
|
18
|
+
* @mention markup that is stored inline in Slate markdown.
|
|
19
|
+
*
|
|
20
|
+
* In both cases: placeholders the AI dropped are appended at the end;
|
|
21
|
+
* hallucinated placeholder strings not in the map are stripped;
|
|
22
|
+
* placeholder-like strings the user literally typed are preserved.
|
|
23
|
+
*/
|
|
24
|
+
import { createUID } from '@pega/cosmos-react-core';
|
|
25
|
+
const PLACEHOLDER_PATTERN = /\[PEGA-(?:IMG|LINK)-[^\]]+\]/g;
|
|
26
|
+
function createUniquePlaceholder(prefix, existingMap, html) {
|
|
27
|
+
let placeholder;
|
|
28
|
+
do {
|
|
29
|
+
placeholder = `[PEGA-${prefix}-${createUID()}]`;
|
|
30
|
+
} while (placeholder in existingMap || html.includes(placeholder));
|
|
31
|
+
return placeholder;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Replaces all `<a>` and standalone `<img>` elements in the HTML with unique
|
|
35
|
+
* placeholders. Uses DOMParser to reliably locate elements rather than regex.
|
|
36
|
+
* Anchors are processed first so that linked images (`<a><img/></a>`) are
|
|
37
|
+
* captured as a single LINK placeholder.
|
|
38
|
+
*/
|
|
39
|
+
export function extractLinkAndImg(html) {
|
|
40
|
+
const placeholderMap = {};
|
|
41
|
+
const parser = new DOMParser();
|
|
42
|
+
const doc = parser.parseFromString(html, 'text/html');
|
|
43
|
+
// Record placeholder-like strings the user literally typed so they are
|
|
44
|
+
// not mistakenly stripped during restoration (they are NOT hallucinations).
|
|
45
|
+
const userPlaceholders = (html.match(PLACEHOLDER_PATTERN) ?? []).filter((match, i, arr) => arr.indexOf(match) === i);
|
|
46
|
+
// Process anchor tags first — captures linked images as one unit
|
|
47
|
+
for (const anchor of Array.from(doc.body.querySelectorAll('a'))) {
|
|
48
|
+
const placeholder = createUniquePlaceholder('LINK', placeholderMap, html);
|
|
49
|
+
placeholderMap[placeholder] = anchor.outerHTML;
|
|
50
|
+
anchor.replaceWith(doc.createTextNode(placeholder));
|
|
51
|
+
}
|
|
52
|
+
// Process remaining standalone img tags (not inside an <a> already replaced)
|
|
53
|
+
for (const img of Array.from(doc.body.querySelectorAll('img'))) {
|
|
54
|
+
const placeholder = createUniquePlaceholder('IMG', placeholderMap, html);
|
|
55
|
+
placeholderMap[placeholder] = img.outerHTML;
|
|
56
|
+
img.replaceWith(doc.createTextNode(placeholder));
|
|
57
|
+
}
|
|
58
|
+
return { placeholderHtml: doc.body.innerHTML, placeholderMap, userPlaceholders };
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Restores placeholders in the rewritten HTML with the original HTML elements.
|
|
62
|
+
* Placeholders that were dropped by the AI are appended at the end, each
|
|
63
|
+
* wrapped in a `<p>` tag (TipTap's default block element).
|
|
64
|
+
* Any hallucinated placeholder strings (not in the map) are stripped.
|
|
65
|
+
*/
|
|
66
|
+
export function restorePlaceholders(html, placeholderMap, userPlaceholders = []) {
|
|
67
|
+
let result = html;
|
|
68
|
+
const missingElements = [];
|
|
69
|
+
for (const [placeholder, originalHtml] of Object.entries(placeholderMap)) {
|
|
70
|
+
if (result.includes(placeholder)) {
|
|
71
|
+
result = result.replaceAll(placeholder, originalHtml);
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
missingElements.push(originalHtml);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
// Strip any hallucinated placeholder strings the AI may have invented,
|
|
78
|
+
// but preserve placeholder-like strings the user literally typed.
|
|
79
|
+
const userSet = new Set(userPlaceholders);
|
|
80
|
+
result = result.replace(PLACEHOLDER_PATTERN, match => (userSet.has(match) ? match : ''));
|
|
81
|
+
if (missingElements.length > 0) {
|
|
82
|
+
result = `${result}${missingElements.map(el => `<p>${el}</p>`).join('')}`;
|
|
83
|
+
}
|
|
84
|
+
return result;
|
|
85
|
+
}
|
|
86
|
+
// ---------------------------------------------------------------------------
|
|
87
|
+
// Feed (markdown) — pega-mention placeholder helpers
|
|
88
|
+
// ---------------------------------------------------------------------------
|
|
89
|
+
/** Regex matching a <pega-mention .../> self-closing XML element inline in markdown. */
|
|
90
|
+
const PEGA_MENTION_REGEX = /<pega-mention (?:[^\n/]|\/(?!>))+\/>/g;
|
|
91
|
+
/** Matches [PEGA-MENTION-XXXX] bracket placeholders injected by extractMentions. */
|
|
92
|
+
const MENTION_PLACEHOLDER_PATTERN = /\[PEGA-MENTION-[^\]]+\]/g;
|
|
93
|
+
function createUniqueMentionPlaceholder(existingMap, text) {
|
|
94
|
+
let placeholder;
|
|
95
|
+
do {
|
|
96
|
+
placeholder = `[PEGA-MENTION-${createUID()}]`;
|
|
97
|
+
} while (placeholder in existingMap || text.includes(placeholder));
|
|
98
|
+
return placeholder;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Extracts all `<pega-mention .../>` elements from a markdown string,
|
|
102
|
+
* replacing them with unique `[PEGA-MENTION-XXXX]` placeholders.
|
|
103
|
+
*
|
|
104
|
+
* Call this before sending Feed text to the AI for rewriting. After the AI
|
|
105
|
+
* responds, call `restoreMentionPlaceholders` to put the original mention XML back.
|
|
106
|
+
*/
|
|
107
|
+
export function extractMentions(markdown) {
|
|
108
|
+
const mentionMap = {};
|
|
109
|
+
// Record placeholder-like strings the user literally typed so they are
|
|
110
|
+
// not mistakenly stripped during restoration.
|
|
111
|
+
const userPlaceholders = (markdown.match(MENTION_PLACEHOLDER_PATTERN) ?? []).filter((match, i, arr) => arr.indexOf(match) === i);
|
|
112
|
+
const cleanText = markdown.replace(PEGA_MENTION_REGEX, match => {
|
|
113
|
+
const placeholder = createUniqueMentionPlaceholder(mentionMap, markdown);
|
|
114
|
+
mentionMap[placeholder] = match;
|
|
115
|
+
return placeholder;
|
|
116
|
+
});
|
|
117
|
+
return { cleanText, mentionMap, userPlaceholders };
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Restores `<pega-mention .../>` XML elements in AI-rewritten Feed text.
|
|
121
|
+
*
|
|
122
|
+
* - Placeholders present in the AI response are replaced with the original mention XML.
|
|
123
|
+
* - Placeholders dropped by the AI are appended at the end (preserving all mentions).
|
|
124
|
+
* - Hallucinated placeholder strings (not in the map) are stripped, unless they were
|
|
125
|
+
* typed literally by the user.
|
|
126
|
+
*/
|
|
127
|
+
export function restoreMentionPlaceholders(text, mentionMap, userPlaceholders = []) {
|
|
128
|
+
let result = text;
|
|
129
|
+
const missingMentions = [];
|
|
130
|
+
for (const [placeholder, originalXml] of Object.entries(mentionMap)) {
|
|
131
|
+
if (result.includes(placeholder)) {
|
|
132
|
+
result = result.replaceAll(placeholder, originalXml);
|
|
133
|
+
}
|
|
134
|
+
else {
|
|
135
|
+
missingMentions.push(originalXml);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
const userSet = new Set(userPlaceholders);
|
|
139
|
+
result = result.replace(MENTION_PLACEHOLDER_PATTERN, match => (userSet.has(match) ? match : ''));
|
|
140
|
+
if (missingMentions.length > 0) {
|
|
141
|
+
const appendedMentions = missingMentions.join(' ');
|
|
142
|
+
if (!result) {
|
|
143
|
+
result = appendedMentions;
|
|
144
|
+
}
|
|
145
|
+
else if (/\s$/.test(result)) {
|
|
146
|
+
result = `${result}${appendedMentions}`;
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
result = `${result} ${appendedMentions}`;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return result;
|
|
153
|
+
}
|
|
154
|
+
//# sourceMappingURL=htmlPlaceholder.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"htmlPlaceholder.js","sourceRoot":"","sources":["../../../../src/components/Editor/utils/htmlPlaceholder.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Placeholder utilities for AI rewrite flows.\n *\n * Both the RTE (Tiptap/HTML) and Feed (Slate/markdown) paths need to protect\n * certain tokens — links, images, user mentions — before sending text to the\n * AI, and restore them afterwards. This file provides both sets of helpers.\n *\n * ## RTE (HTML) — `extractLinkAndImg` / `restorePlaceholders`\n * Replaces `<a>` and `<img>` elements with `[PEGA-IMG-XXXX]` / `[PEGA-LINK-XXXX]`\n * placeholders using DOMParser. Reduces payload size and prevents the AI from\n * stripping or mangling anchor/image tags. Images wrapped in an anchor are\n * captured as a single LINK placeholder so the full `<a><img/></a>` structure\n * is stored intact.\n *\n * ## Feed (markdown) — `extractMentions` / `restoreMentionPlaceholders`\n * Replaces `<pega-mention .../>` XML elements with `[PEGA-MENTION-XXXX]`\n * placeholders using regex. Prevents the AI from dropping or mangling user\n * @mention markup that is stored inline in Slate markdown.\n *\n * In both cases: placeholders the AI dropped are appended at the end;\n * hallucinated placeholder strings not in the map are stripped;\n * placeholder-like strings the user literally typed are preserved.\n */\n\nimport { createUID } from '@pega/cosmos-react-core';\n\nexport interface PlaceholderMap {\n [placeholder: string]: string;\n}\n\ninterface ExtractResult {\n placeholderHtml: string;\n placeholderMap: PlaceholderMap;\n userPlaceholders: string[];\n}\n\nconst PLACEHOLDER_PATTERN = /\\[PEGA-(?:IMG|LINK)-[^\\]]+\\]/g;\n\nfunction createUniquePlaceholder(\n prefix: string,\n existingMap: PlaceholderMap,\n html: string\n): string {\n let placeholder: string;\n do {\n placeholder = `[PEGA-${prefix}-${createUID()}]`;\n } while (placeholder in existingMap || html.includes(placeholder));\n return placeholder;\n}\n\n/**\n * Replaces all `<a>` and standalone `<img>` elements in the HTML with unique\n * placeholders. Uses DOMParser to reliably locate elements rather than regex.\n * Anchors are processed first so that linked images (`<a><img/></a>`) are\n * captured as a single LINK placeholder.\n */\nexport function extractLinkAndImg(html: string): ExtractResult {\n const placeholderMap: PlaceholderMap = {};\n const parser = new DOMParser();\n const doc = parser.parseFromString(html, 'text/html');\n\n // Record placeholder-like strings the user literally typed so they are\n // not mistakenly stripped during restoration (they are NOT hallucinations).\n const userPlaceholders = (html.match(PLACEHOLDER_PATTERN) ?? []).filter(\n (match, i, arr) => arr.indexOf(match) === i\n );\n\n // Process anchor tags first — captures linked images as one unit\n for (const anchor of Array.from(doc.body.querySelectorAll('a'))) {\n const placeholder = createUniquePlaceholder('LINK', placeholderMap, html);\n placeholderMap[placeholder] = anchor.outerHTML;\n anchor.replaceWith(doc.createTextNode(placeholder));\n }\n\n // Process remaining standalone img tags (not inside an <a> already replaced)\n for (const img of Array.from(doc.body.querySelectorAll('img'))) {\n const placeholder = createUniquePlaceholder('IMG', placeholderMap, html);\n placeholderMap[placeholder] = img.outerHTML;\n img.replaceWith(doc.createTextNode(placeholder));\n }\n\n return { placeholderHtml: doc.body.innerHTML, placeholderMap, userPlaceholders };\n}\n\n/**\n * Restores placeholders in the rewritten HTML with the original HTML elements.\n * Placeholders that were dropped by the AI are appended at the end, each\n * wrapped in a `<p>` tag (TipTap's default block element).\n * Any hallucinated placeholder strings (not in the map) are stripped.\n */\nexport function restorePlaceholders(\n html: string,\n placeholderMap: PlaceholderMap,\n userPlaceholders: string[] = []\n): string {\n let result = html;\n const missingElements: string[] = [];\n\n for (const [placeholder, originalHtml] of Object.entries(placeholderMap)) {\n if (result.includes(placeholder)) {\n result = result.replaceAll(placeholder, originalHtml);\n } else {\n missingElements.push(originalHtml);\n }\n }\n\n // Strip any hallucinated placeholder strings the AI may have invented,\n // but preserve placeholder-like strings the user literally typed.\n const userSet = new Set(userPlaceholders);\n result = result.replace(PLACEHOLDER_PATTERN, match => (userSet.has(match) ? match : ''));\n\n if (missingElements.length > 0) {\n result = `${result}${missingElements.map(el => `<p>${el}</p>`).join('')}`;\n }\n\n return result;\n}\n\n// ---------------------------------------------------------------------------\n// Feed (markdown) — pega-mention placeholder helpers\n// ---------------------------------------------------------------------------\n\n/** Regex matching a <pega-mention .../> self-closing XML element inline in markdown. */\nconst PEGA_MENTION_REGEX = /<pega-mention (?:[^\\n/]|\\/(?!>))+\\/>/g;\n\n/** Matches [PEGA-MENTION-XXXX] bracket placeholders injected by extractMentions. */\nconst MENTION_PLACEHOLDER_PATTERN = /\\[PEGA-MENTION-[^\\]]+\\]/g;\n\ninterface ExtractMentionsResult {\n cleanText: string;\n mentionMap: PlaceholderMap;\n userPlaceholders: string[];\n}\n\nfunction createUniqueMentionPlaceholder(existingMap: PlaceholderMap, text: string): string {\n let placeholder: string;\n do {\n placeholder = `[PEGA-MENTION-${createUID()}]`;\n } while (placeholder in existingMap || text.includes(placeholder));\n return placeholder;\n}\n\n/**\n * Extracts all `<pega-mention .../>` elements from a markdown string,\n * replacing them with unique `[PEGA-MENTION-XXXX]` placeholders.\n *\n * Call this before sending Feed text to the AI for rewriting. After the AI\n * responds, call `restoreMentionPlaceholders` to put the original mention XML back.\n */\nexport function extractMentions(markdown: string): ExtractMentionsResult {\n const mentionMap: PlaceholderMap = {};\n\n // Record placeholder-like strings the user literally typed so they are\n // not mistakenly stripped during restoration.\n const userPlaceholders = (markdown.match(MENTION_PLACEHOLDER_PATTERN) ?? []).filter(\n (match, i, arr) => arr.indexOf(match) === i\n );\n\n const cleanText = markdown.replace(PEGA_MENTION_REGEX, match => {\n const placeholder = createUniqueMentionPlaceholder(mentionMap, markdown);\n mentionMap[placeholder] = match;\n return placeholder;\n });\n\n return { cleanText, mentionMap, userPlaceholders };\n}\n\n/**\n * Restores `<pega-mention .../>` XML elements in AI-rewritten Feed text.\n *\n * - Placeholders present in the AI response are replaced with the original mention XML.\n * - Placeholders dropped by the AI are appended at the end (preserving all mentions).\n * - Hallucinated placeholder strings (not in the map) are stripped, unless they were\n * typed literally by the user.\n */\nexport function restoreMentionPlaceholders(\n text: string,\n mentionMap: PlaceholderMap,\n userPlaceholders: string[] = []\n): string {\n let result = text;\n const missingMentions: string[] = [];\n\n for (const [placeholder, originalXml] of Object.entries(mentionMap)) {\n if (result.includes(placeholder)) {\n result = result.replaceAll(placeholder, originalXml);\n } else {\n missingMentions.push(originalXml);\n }\n }\n\n const userSet = new Set(userPlaceholders);\n result = result.replace(MENTION_PLACEHOLDER_PATTERN, match => (userSet.has(match) ? match : ''));\n\n if (missingMentions.length > 0) {\n const appendedMentions = missingMentions.join(' ');\n\n if (!result) {\n result = appendedMentions;\n } else if (/\\s$/.test(result)) {\n result = `${result}${appendedMentions}`;\n } else {\n result = `${result} ${appendedMentions}`;\n }\n }\n\n return result;\n}\n"]}
|