@pega/cosmos-react-rte 9.0.0-build.9.8 → 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
|
@@ -1,246 +1,81 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import tinymce from 'tinymce/tinymce';
|
|
4
|
-
import 'tinymce/icons/default';
|
|
5
|
-
import 'tinymce/themes/silver';
|
|
6
|
-
import 'tinymce/plugins/advlist';
|
|
7
|
-
import 'tinymce/plugins/lists';
|
|
8
|
-
import 'tinymce/plugins/autolink';
|
|
9
|
-
import 'tinymce/models/dom';
|
|
10
|
-
import { forwardRef, useEffect, useRef, useState, useLayoutEffect, useImperativeHandle, useCallback, useMemo } from 'react';
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { forwardRef, useEffect, useRef, useState, useImperativeHandle, useMemo, useCallback } from 'react';
|
|
11
3
|
import { css } from 'styled-components';
|
|
12
|
-
import {
|
|
13
|
-
import { mix, stripUnit } from 'polished';
|
|
14
|
-
import { createUID, FormControl, useAfterInitialEffect, useTheme, useUID, useI18n, useConsolidatedRef, useAutoResize, useConfiguration, useTestIds, withTestIds, documentIsAvailable, getActiveElement } from '@pega/cosmos-react-core';
|
|
15
|
-
import { getHtmlStyles } from '@pega/cosmos-react-core/lib/components/HTML/HTML';
|
|
16
|
-
import { createGlobalBodyStyles, createGlobalRootStyles, globalSpacingStyles } from '@pega/cosmos-react-core/lib/styles/GlobalStyle';
|
|
4
|
+
import { FormControl, Progress, HiddenText, useAfterInitialEffect, useTheme, useUID, useConsolidatedRef, useTestIds, withTestIds, useConfiguration, useI18n } from '@pega/cosmos-react-core';
|
|
17
5
|
import StyledRichTextEditor from '../RichTextEditor/RichTextEditor.styles';
|
|
18
|
-
import Toolbar from './Toolbar/Toolbar';
|
|
19
6
|
import EditorContext from './Editor.context';
|
|
20
|
-
import {
|
|
21
|
-
import ImageEditor, { imgHoverClass } from './ImageEditor';
|
|
22
|
-
import { offscreenSelectionStyles, mceContentBodyStyles, StyledEditorContainer, StyledEditorRoot } from './Editor.styles';
|
|
7
|
+
import { StyledEditorContainer, StyledEditorIframe, StyledEditorRoot, StyledSourceTextarea } from './Editor.styles';
|
|
23
8
|
import { getEditorTestIds } from './Editor.test-ids';
|
|
9
|
+
import Toolbar from './Toolbar/Toolbar';
|
|
10
|
+
import WordCount from './Toolbar/WordCount';
|
|
11
|
+
import { ImageEditDialog } from './ImageEditDialog';
|
|
12
|
+
import ImageActionButtons from './ImageActionButtons';
|
|
13
|
+
import TableCellMenu from './TableCellMenu';
|
|
24
14
|
import createFileItemElement from './FileItemElement';
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
15
|
+
import useIframeSetup from './hooks/useIframeSetup';
|
|
16
|
+
import useImageActions from './hooks/useImageActions';
|
|
17
|
+
import useTableCellMenu from './hooks/useTableCellMenu';
|
|
18
|
+
import { dispatchEditorClickEvent } from './hooks/useCloseOnEditorClick';
|
|
19
|
+
import sanitizeHtml from './sanitize';
|
|
20
|
+
// Suggestion editor has no toolbar — formatting controls are intentionally omitted
|
|
21
|
+
const SUGGESTION_TOOLBAR = [];
|
|
22
|
+
const SuggestionEditor = ({ defaultValue, onGetContent, onContentChange, renderEditor }) => {
|
|
23
|
+
const editorRef = useRef(null);
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
onGetContent(() => editorRef.current?.getEditor()?.getHTML() ?? '');
|
|
26
|
+
}, [onGetContent]);
|
|
27
|
+
useAfterInitialEffect(() => {
|
|
28
|
+
editorRef.current?.getEditor()?.commands.setContent(defaultValue ?? '');
|
|
29
|
+
}, [defaultValue]);
|
|
30
|
+
return renderEditor({
|
|
31
|
+
ref: editorRef,
|
|
32
|
+
onInit: tiptapEditor => {
|
|
33
|
+
// Signal initial content state so submit button enables correctly
|
|
34
|
+
onContentChange(tiptapEditor.getText().trim() !== '');
|
|
35
|
+
},
|
|
36
|
+
onChange: editor => {
|
|
37
|
+
onContentChange((editor?.getText() ?? '').trim() !== '');
|
|
38
|
+
}
|
|
30
39
|
});
|
|
31
40
|
};
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
img:hover,
|
|
35
|
-
img.${imgHoverClass} {
|
|
36
|
-
box-shadow: 0 0 0 0.06125rem ${theme.base.palette['primary-background']},
|
|
37
|
-
0 0 0 0.125rem ${theme.base.palette.interactive}, 0 0 0 0.25rem rgba(0, 118, 209, 0.3);
|
|
38
|
-
}
|
|
39
|
-
`;
|
|
40
|
-
};
|
|
41
|
-
export const getPlaceholderStyles = (theme) => {
|
|
42
|
-
return `
|
|
43
|
-
.mce-content-body[data-mce-placeholder] {
|
|
44
|
-
position: relative;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
.mce-content-body[data-mce-placeholder]:not(.mce-visualblocks)::before {
|
|
48
|
-
cursor: text;
|
|
49
|
-
color: ${mix(theme.base.transparency['transparent-3'], theme.base.palette['foreground-color'], theme.components['form-control']['background-color'])};
|
|
50
|
-
content: attr(data-mce-placeholder);
|
|
51
|
-
position: absolute;
|
|
52
|
-
}
|
|
53
|
-
`;
|
|
54
|
-
};
|
|
55
|
-
const Editor = forwardRef(function Editor(props, ref) {
|
|
41
|
+
const Editor = forwardRef(function ForwardedEditor(props, ref) {
|
|
42
|
+
const { testId, id, label, labelHidden, info, status, required, disabled, readOnly, onChange, onKeyDown, onFocus, onBlur, onImageAdded, onResolveSuggestion, onInit, onUnload, defaultValue, placeholder, children, additionalInfo, toolbar, customActions, customComponents = [], spellcheck = true, imageInsertionMode = 'all', initOptions, onRewriteClick, progress, autoFocus, height, autoResize = true, secure = false, ...restProps } = props;
|
|
56
43
|
const theme = useTheme();
|
|
57
|
-
const { components: { 'text-area': { 'min-height': textAreaMinHeight } } } = theme;
|
|
58
44
|
const { styleSheetTarget } = useConfiguration();
|
|
59
|
-
const [editor, setEditor] = useState();
|
|
60
|
-
const [focused, setFocused] = useState(false);
|
|
61
45
|
const t = useI18n();
|
|
62
|
-
const
|
|
46
|
+
const [focused, setFocused] = useState(false);
|
|
47
|
+
const [tiptapEditor, setTiptapEditor] = useState(null);
|
|
48
|
+
const [imageEditDialog, setImageEditDialog] = useState({ isOpen: false, attributes: null });
|
|
49
|
+
const [sourceMode, setSourceMode] = useState(false);
|
|
50
|
+
const [sourceCode, setSourceCode] = useState('');
|
|
51
|
+
const editButtonRef = useRef(null);
|
|
52
|
+
const tableCellMenuButtonRef = useRef(null);
|
|
53
|
+
const editorContainerRef = useRef(null);
|
|
54
|
+
const generatedId = useUID();
|
|
55
|
+
const editorId = id ?? generatedId;
|
|
63
56
|
const labelId = useUID();
|
|
57
|
+
const requiredDescriptionId = `${editorId}-required`;
|
|
64
58
|
const editorRef = useConsolidatedRef(ref);
|
|
65
|
-
const
|
|
66
|
-
const tinyMceContainerRef = useRef(null);
|
|
67
|
-
const initialized = useRef(false);
|
|
59
|
+
const formFieldRef = useRef(null);
|
|
68
60
|
const pastedImages = useRef([]);
|
|
69
|
-
const
|
|
61
|
+
const iframeRef = useRef(null);
|
|
70
62
|
const testIds = useTestIds(testId, getEditorTestIds);
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
if (baseHeight[type]) {
|
|
80
|
-
return stripUnit(baseHeight[type]);
|
|
81
|
-
}
|
|
82
|
-
return undefined;
|
|
83
|
-
};
|
|
84
|
-
const imagesEnabled = toolbar.includes('images');
|
|
85
|
-
const maxHeight = autoResize ? getHeight('max') : undefined;
|
|
86
|
-
const minHeight = autoResize ? getHeight('min') : undefined;
|
|
87
|
-
const [autoResizeRef, resizeEditor] = useAutoResize(maxHeight, minHeight);
|
|
88
|
-
const formFieldRef = useRef(null);
|
|
89
|
-
const value = useRef(defaultValue);
|
|
90
|
-
const customActionsLength = customActions?.length ?? 0;
|
|
91
|
-
const renderToolbar = editor && (toolbar.length > 0 || customActionsLength > 0) && !readOnly && !disabled;
|
|
92
|
-
const processPastedImgUrls = async (infoArr) => {
|
|
93
|
-
const files = await Promise.all(infoArr.map(({ id: imgId, url }) => getFileFromUrl(url, imgId)));
|
|
94
|
-
files.forEach((file, i) => {
|
|
95
|
-
onImageAdded?.(file, infoArr[i].id);
|
|
96
|
-
});
|
|
97
|
-
};
|
|
98
|
-
const pastePreprocess = (plugin, args) => {
|
|
99
|
-
const isImg = args.content.includes('<img');
|
|
100
|
-
if (!imagesEnabled && isImg) {
|
|
101
|
-
args.content = args.content.replace(/<img(.*)\/>/g, '');
|
|
102
|
-
}
|
|
103
|
-
if (imagesEnabled && isImg) {
|
|
104
|
-
const pastedHtml = new DOMParser().parseFromString(args.content, 'text/html').body;
|
|
105
|
-
const imgInfo = [];
|
|
106
|
-
pastedHtml.querySelectorAll('img').forEach(imgEl => {
|
|
107
|
-
const imgId = createUID();
|
|
108
|
-
const url = imgEl.src;
|
|
109
|
-
const name = pastedImages.current.shift()?.name || id;
|
|
110
|
-
imgInfo.push({ id: imgId, url, name });
|
|
111
|
-
const uploadEl = document.createElement('pega-file');
|
|
112
|
-
uploadEl.setAttribute('data-id', imgId);
|
|
113
|
-
uploadEl.setAttribute('data-name', name);
|
|
114
|
-
uploadEl.setAttribute('data-url', url);
|
|
115
|
-
uploadEl.setAttribute('data-progress', '0');
|
|
116
|
-
uploadEl.setAttribute('contenteditable', 'false');
|
|
117
|
-
const uploadElString = `<br/>${uploadEl.outerHTML}<br/>`;
|
|
118
|
-
const uploadElHtml = new DOMParser().parseFromString(uploadElString, 'text/html').body;
|
|
119
|
-
imgEl.replaceWith(uploadElHtml);
|
|
120
|
-
});
|
|
121
|
-
args.content = pastedHtml.innerHTML;
|
|
122
|
-
processPastedImgUrls(imgInfo);
|
|
123
|
-
}
|
|
124
|
-
};
|
|
125
|
-
useImperativeHandle(editorRef, () => ({
|
|
126
|
-
focus: () => {
|
|
127
|
-
editor?.focus();
|
|
128
|
-
},
|
|
129
|
-
getPlainText: () => {
|
|
130
|
-
return editor ? editor.getContent({ format: 'text' }) : '';
|
|
131
|
-
},
|
|
132
|
-
getRichText: () => {
|
|
133
|
-
return editor ? JSON.stringify(editor.getContent({ format: 'raw' })) : '';
|
|
134
|
-
},
|
|
135
|
-
getHtml: () => {
|
|
136
|
-
return editor ? editor.getContent({ format: 'html' }) : '';
|
|
137
|
-
},
|
|
138
|
-
clear: () => {
|
|
139
|
-
editor?.setContent('');
|
|
140
|
-
},
|
|
141
|
-
updateAttachmentAttributes: (imageId, progress, error) => {
|
|
142
|
-
if (editor) {
|
|
143
|
-
const editorEl = editor.getDoc();
|
|
144
|
-
const imageCustomEl = editorEl.querySelector(`pega-file[data-id="${imageId}"]`);
|
|
145
|
-
if (imageCustomEl) {
|
|
146
|
-
if (error)
|
|
147
|
-
imageCustomEl.setAttribute('data-error', error);
|
|
148
|
-
else if (progress)
|
|
149
|
-
imageCustomEl.setAttribute('data-progress', `${progress}`);
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
},
|
|
153
|
-
appendImage: ({ src, alt, attachmentId }, imageId) => {
|
|
154
|
-
if (editor) {
|
|
155
|
-
const editorEl = editor.getDoc();
|
|
156
|
-
const imageUploadEl = editorEl.querySelector(`pega-file[data-id="${imageId}"]`);
|
|
157
|
-
if (imageUploadEl) {
|
|
158
|
-
const imgEl = editorEl.createElement('img');
|
|
159
|
-
imgEl.setAttribute('src', src);
|
|
160
|
-
imgEl.setAttribute('alt', alt);
|
|
161
|
-
if (attachmentId) {
|
|
162
|
-
imgEl.setAttribute('data-attachment-id', attachmentId);
|
|
163
|
-
}
|
|
164
|
-
imageUploadEl.replaceWith(imgEl);
|
|
165
|
-
imageUploadEl?.remove();
|
|
166
|
-
if (autoResize)
|
|
167
|
-
resizeEditor();
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
},
|
|
171
|
-
insertText: (text) => {
|
|
172
|
-
let activeElement = null;
|
|
173
|
-
if (documentIsAvailable) {
|
|
174
|
-
activeElement = getActiveElement();
|
|
175
|
-
}
|
|
176
|
-
editor?.insertContent(text);
|
|
177
|
-
if (activeElement)
|
|
178
|
-
activeElement.focus();
|
|
179
|
-
},
|
|
180
|
-
setCursorLocationToStart: () => {
|
|
181
|
-
editor?.selection.setCursorLocation();
|
|
182
|
-
editor?.selection.getNode().scrollIntoView();
|
|
183
|
-
},
|
|
184
|
-
insertHtml: (html, overwrite = false) => {
|
|
185
|
-
if (overwrite && html === editor?.getContent({ format: 'html' }))
|
|
186
|
-
return;
|
|
187
|
-
let activeElement = null;
|
|
188
|
-
if (documentIsAvailable) {
|
|
189
|
-
activeElement = getActiveElement();
|
|
190
|
-
}
|
|
191
|
-
if (overwrite) {
|
|
192
|
-
editor?.setContent('');
|
|
193
|
-
}
|
|
194
|
-
if (readOnly || disabled) {
|
|
195
|
-
editor?.setContent(html);
|
|
196
|
-
}
|
|
197
|
-
else {
|
|
198
|
-
editor?.insertContent(html);
|
|
199
|
-
}
|
|
200
|
-
if (activeElement)
|
|
201
|
-
activeElement.focus();
|
|
202
|
-
},
|
|
203
|
-
getEditor: () => {
|
|
204
|
-
return editor;
|
|
205
|
-
},
|
|
206
|
-
element: formFieldRef.current || undefined
|
|
207
|
-
}), [editor]);
|
|
208
|
-
useLayoutEffect(() => {
|
|
209
|
-
if (!tinyMceRef.current || !tinyMceContainerRef.current)
|
|
210
|
-
return;
|
|
211
|
-
const styles = serialize(compile(getHtmlStyles(theme)), stringify);
|
|
212
|
-
const imageStyles = serialize(compile(getImageStyles(theme)), stringify);
|
|
213
|
-
const placeholderStyles = serialize(compile(getPlaceholderStyles(theme)), stringify);
|
|
63
|
+
// Check if images feature is enabled
|
|
64
|
+
const imagesEnabled = toolbar?.includes('images') ?? false;
|
|
65
|
+
// Check if tables feature is enabled
|
|
66
|
+
const tablesEnabled = toolbar?.includes('tables') ?? false;
|
|
67
|
+
// Check if links feature is enabled (controls autolink behavior)
|
|
68
|
+
const linksEnabled = toolbar?.includes('links') ?? false;
|
|
69
|
+
// Define built-in custom components and merge with user-provided ones
|
|
70
|
+
const customElements = useMemo(() => {
|
|
214
71
|
const FileItemStyle = css `
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
}
|
|
220
|
-
`;
|
|
221
|
-
const contentStyle = `
|
|
222
|
-
${createGlobalRootStyles(theme)}
|
|
223
|
-
${globalSpacingStyles}
|
|
224
|
-
${createGlobalBodyStyles(theme)}
|
|
225
|
-
|
|
226
|
-
html {
|
|
227
|
-
overflow: hidden;
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
body {
|
|
231
|
-
min-height: 3rem;
|
|
232
|
-
padding: ${theme.base.spacing};
|
|
233
|
-
background: unset;
|
|
72
|
+
/* stylelint-disable-next-line selector-type-no-unknown */
|
|
73
|
+
pega-file {
|
|
74
|
+
display: inline-block;
|
|
75
|
+
width: ${theme.base['content-width'].md};
|
|
234
76
|
}
|
|
235
|
-
|
|
236
|
-
${offscreenSelectionStyles}
|
|
237
|
-
${mceContentBodyStyles}
|
|
238
|
-
${placeholderStyles}
|
|
239
|
-
${imageStyles}
|
|
240
|
-
${styles}
|
|
241
|
-
${initOptions?.contentStyle}
|
|
242
77
|
`;
|
|
243
|
-
|
|
78
|
+
return [
|
|
244
79
|
{
|
|
245
80
|
createCustomElement: createFileItemElement,
|
|
246
81
|
name: 'pega-file',
|
|
@@ -249,285 +84,261 @@ const Editor = forwardRef(function Editor(props, ref) {
|
|
|
249
84
|
},
|
|
250
85
|
...customComponents
|
|
251
86
|
];
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
plugins: 'lists advlist autolink',
|
|
262
|
-
paste_block_drop: true,
|
|
263
|
-
paste_data_images: initOptions?.pasteDataImages ?? true,
|
|
264
|
-
lists_indent_on_tab: true,
|
|
265
|
-
text_patterns: initOptions?.textPatterns,
|
|
266
|
-
icons: '',
|
|
267
|
-
branding: false,
|
|
268
|
-
elementpath: false,
|
|
269
|
-
placeholder,
|
|
270
|
-
content_css: false,
|
|
271
|
-
browser_spellcheck: spellcheck,
|
|
272
|
-
relative_urls: false,
|
|
273
|
-
remove_script_host: false,
|
|
274
|
-
paste_preprocess: pastePreprocess,
|
|
275
|
-
convert_unsafe_embeds: true,
|
|
276
|
-
sandbox_iframes: true,
|
|
277
|
-
paste_as_text: initOptions?.pasteAsText,
|
|
278
|
-
init_instance_callback: initializedEditor => {
|
|
279
|
-
if (initOptions?.initInstanceCallback) {
|
|
280
|
-
initOptions.initInstanceCallback(initializedEditor);
|
|
281
|
-
}
|
|
282
|
-
if (typeof label === 'string') {
|
|
283
|
-
initializedEditor.getBody().setAttribute('aria-label', label);
|
|
284
|
-
}
|
|
285
|
-
const iframeWindow = (tinyMceContainerRef.current?.querySelector('iframe')).contentWindow;
|
|
286
|
-
const editorCustomElements = iframeWindow?.customElements;
|
|
287
|
-
// Manually add content style to allow setting nonce attribute
|
|
288
|
-
const contentStyleEl = document.createElement('style');
|
|
289
|
-
contentStyleEl.innerText = contentStyle;
|
|
290
|
-
contentStyleEl.nonce = window.__webpack_nonce__;
|
|
291
|
-
initializedEditor.getDoc().head.appendChild(contentStyleEl);
|
|
292
|
-
if (editorCustomElements) {
|
|
293
|
-
customElements.forEach(({ name, createCustomElement: customElementCreator, style }) => {
|
|
294
|
-
if (!editorCustomElements.get(name)) {
|
|
295
|
-
editorCustomElements.define(name, customElementCreator(iframeWindow));
|
|
296
|
-
}
|
|
297
|
-
if (style) {
|
|
298
|
-
const customElementStyleEl = document.createElement('style');
|
|
299
|
-
customElementStyleEl.innerText = style;
|
|
300
|
-
customElementStyleEl.nonce = window.__webpack_nonce__;
|
|
301
|
-
initializedEditor.getDoc().head.appendChild(customElementStyleEl);
|
|
302
|
-
}
|
|
303
|
-
});
|
|
304
|
-
}
|
|
305
|
-
},
|
|
306
|
-
extended_valid_elements: `${customElements
|
|
307
|
-
.map(comp => `${comp.name}${comp.extensionAttributes ? `[${comp.extensionAttributes.join('|')}]` : ''}`)
|
|
308
|
-
.join(',')}`,
|
|
309
|
-
custom_elements: customElements.map(comp => `~${comp.name}`).join('~,'),
|
|
310
|
-
invalid_elements: 'iframe',
|
|
311
|
-
setup: editorSettings => {
|
|
312
|
-
editorSettings.on('keydown', e => {
|
|
313
|
-
const activeElement = editorSettings?.getDoc().activeElement;
|
|
314
|
-
if (e.key === 'Enter' && activeElement?.nodeName === 'PEGA-FILE') {
|
|
315
|
-
e.preventDefault();
|
|
316
|
-
const bookmark = editorSettings.selection.getBookmark();
|
|
317
|
-
activeElement.shadowRoot?.querySelector('button')?.click();
|
|
318
|
-
editorSettings?.selection.moveToBookmark(bookmark);
|
|
319
|
-
}
|
|
320
|
-
else
|
|
321
|
-
onKeyDown?.(e);
|
|
322
|
-
});
|
|
323
|
-
getTextFormats(t).forEach((format, i) => {
|
|
324
|
-
editorSettings.addShortcut(`meta+alt+${i}`, `${t('rte_change_text_format')} ${format.text}`, () => {
|
|
325
|
-
editorSettings.execCommand('FormatBlock', false, format.type);
|
|
326
|
-
});
|
|
327
|
-
});
|
|
328
|
-
editorSettings.addShortcut('meta+shift+b', t('rte_bold'), () => {
|
|
329
|
-
editorSettings.execCommand('Bold');
|
|
330
|
-
});
|
|
331
|
-
editorSettings.addShortcut('meta+shift+i', t('rte_italic'), () => {
|
|
332
|
-
editorSettings.execCommand('Italic');
|
|
333
|
-
});
|
|
334
|
-
editorSettings.addShortcut('meta+shift+x', t('rte_strike_through'), () => {
|
|
335
|
-
editorSettings.execCommand('Strikethrough');
|
|
336
|
-
});
|
|
337
|
-
editorSettings.addShortcut('meta+shift+l', t('rte_toggle_unordered_list'), () => {
|
|
338
|
-
editorSettings.execCommand('InsertUnorderedList');
|
|
339
|
-
});
|
|
340
|
-
editorSettings.addShortcut('alt+m', t('rte_indent_selection'), () => {
|
|
341
|
-
editorSettings.execCommand('Indent');
|
|
342
|
-
});
|
|
343
|
-
editorSettings.addShortcut('alt+shift+m', t('rte_unindent_selection'), () => {
|
|
344
|
-
editorSettings.execCommand('Outdent');
|
|
345
|
-
});
|
|
346
|
-
customActions?.forEach(action => {
|
|
347
|
-
if (action.shortcut) {
|
|
348
|
-
editorSettings.addShortcut(action.shortcut.pattern, action.shortcut.description, () => {
|
|
349
|
-
action.shortcut?.command(editorSettings);
|
|
350
|
-
});
|
|
351
|
-
}
|
|
352
|
-
});
|
|
353
|
-
}
|
|
354
|
-
})
|
|
355
|
-
.then(tinymceEditors => {
|
|
356
|
-
setEditor(tinymceEditors[0]);
|
|
357
|
-
tinymceEditors[0]?.editorContainer?.removeAttribute('style');
|
|
358
|
-
const iframe = tinyMceContainerRef.current?.querySelector('iframe');
|
|
359
|
-
if (iframe) {
|
|
360
|
-
const globalStyles = styleSheetTarget?.querySelectorAll('[data-cosmos-global-style]') ??
|
|
361
|
-
[];
|
|
362
|
-
const extraStyles = document.querySelectorAll('[data-cosmos-global-style]') ?? [];
|
|
363
|
-
const iframeContentHead = iframe?.contentDocument?.querySelector('head');
|
|
364
|
-
[...globalStyles, ...extraStyles].forEach(sheet => {
|
|
365
|
-
iframeContentHead?.appendChild(sheet.cloneNode(true));
|
|
366
|
-
});
|
|
367
|
-
autoResizeRef.current = iframe;
|
|
368
|
-
if (autoResize)
|
|
369
|
-
resizeEditor();
|
|
87
|
+
}, [customComponents, theme]);
|
|
88
|
+
// Register custom elements in the main window (needed for Tiptap to recognize them)
|
|
89
|
+
useEffect(() => {
|
|
90
|
+
customElements.forEach((component) => {
|
|
91
|
+
if (component.name &&
|
|
92
|
+
component.createCustomElement &&
|
|
93
|
+
!window.customElements.get(component.name)) {
|
|
94
|
+
const CustomElement = component.createCustomElement(window);
|
|
95
|
+
window.customElements.define(component.name, CustomElement);
|
|
370
96
|
}
|
|
371
97
|
});
|
|
372
|
-
}, []);
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
98
|
+
}, [customElements]);
|
|
99
|
+
// Sanitize defaultValue if secure mode is enabled
|
|
100
|
+
const sanitizedDefaultValue = useMemo(() => (secure && defaultValue ? sanitizeHtml(defaultValue) : defaultValue), [secure, defaultValue]);
|
|
101
|
+
// Mount Tiptap inside iframe using custom hook
|
|
102
|
+
const { iframeEditorRef } = useIframeSetup({
|
|
103
|
+
iframeRef,
|
|
104
|
+
theme,
|
|
105
|
+
styleSheetTarget,
|
|
106
|
+
customElements,
|
|
107
|
+
placeholder,
|
|
108
|
+
defaultValue: sanitizedDefaultValue,
|
|
109
|
+
disabled,
|
|
110
|
+
readOnly,
|
|
111
|
+
onChange,
|
|
112
|
+
onKeyDown,
|
|
113
|
+
onFocus,
|
|
114
|
+
onBlur,
|
|
115
|
+
onInit,
|
|
116
|
+
spellcheck,
|
|
117
|
+
initOptions,
|
|
118
|
+
onImageAdded,
|
|
119
|
+
imagesEnabled,
|
|
120
|
+
linksEnabled,
|
|
121
|
+
imageInsertionMode,
|
|
122
|
+
pastedImages,
|
|
123
|
+
editorId,
|
|
124
|
+
required,
|
|
125
|
+
setTiptapEditor,
|
|
126
|
+
setFocused,
|
|
127
|
+
autoResize,
|
|
128
|
+
secure,
|
|
129
|
+
onTabOut: () => {
|
|
130
|
+
// Focus first toolbar button when Tab is pressed in editor
|
|
131
|
+
const toolbarEl = editorContainerRef.current?.querySelector('[data-testid$="toolbar"]');
|
|
132
|
+
const firstButton = toolbarEl?.querySelector('button:not([disabled])');
|
|
133
|
+
if (firstButton) {
|
|
134
|
+
firstButton.focus();
|
|
135
|
+
}
|
|
136
|
+
},
|
|
137
|
+
onFocusTableMenu: () => {
|
|
138
|
+
tableCellMenuButtonRef.current?.focus();
|
|
139
|
+
},
|
|
140
|
+
onFocusPreviousCellMenu: () => {
|
|
141
|
+
// Go to previous cell and then focus its menu
|
|
142
|
+
if (tiptapEditor) {
|
|
143
|
+
tiptapEditor.commands.goToPreviousCell();
|
|
144
|
+
setTimeout(() => {
|
|
145
|
+
tableCellMenuButtonRef.current?.focus();
|
|
146
|
+
}, 0);
|
|
147
|
+
}
|
|
376
148
|
}
|
|
377
|
-
}
|
|
149
|
+
});
|
|
150
|
+
// Set up unload event listener to save content before iframe is destroyed
|
|
378
151
|
useEffect(() => {
|
|
379
|
-
if (!
|
|
152
|
+
if (!iframeRef.current?.contentWindow || !onUnload)
|
|
380
153
|
return;
|
|
381
|
-
const
|
|
382
|
-
const
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
if (autoResize)
|
|
388
|
-
resizeEditor();
|
|
389
|
-
};
|
|
390
|
-
const onEditorChange = (event) => {
|
|
391
|
-
if (event.type === 'paste' && event.clipboardData?.files) {
|
|
392
|
-
pastedImages.current = Array.from(event.clipboardData?.files);
|
|
393
|
-
}
|
|
394
|
-
if (event.type !== 'execcommand' || event.command !== 'mceFocus') {
|
|
395
|
-
onChange?.(editor);
|
|
396
|
-
}
|
|
397
|
-
};
|
|
398
|
-
const onEditorFocus = () => {
|
|
399
|
-
setFocused(true);
|
|
400
|
-
onFocus?.();
|
|
401
|
-
};
|
|
402
|
-
const onEditorBlur = () => {
|
|
403
|
-
setFocused(false);
|
|
404
|
-
onBlur?.();
|
|
405
|
-
};
|
|
406
|
-
onChangeEvents.forEach(event => {
|
|
407
|
-
editor.on(event, onEditorChange);
|
|
408
|
-
});
|
|
409
|
-
editor.on('keydown', onEditorKeyDown);
|
|
410
|
-
if (autoResize)
|
|
411
|
-
editor.on('paste', resizeEditor);
|
|
412
|
-
editor.on('focus', onEditorFocus);
|
|
413
|
-
editor.on('blur', onEditorBlur);
|
|
154
|
+
const iframeWindow = iframeRef.current.contentWindow;
|
|
155
|
+
const abortController = new AbortController();
|
|
156
|
+
iframeWindow.addEventListener('unload', () => {
|
|
157
|
+
const html = iframeEditorRef.current?.getHtml() ?? '';
|
|
158
|
+
onUnload(html);
|
|
159
|
+
}, { signal: abortController.signal });
|
|
414
160
|
return () => {
|
|
415
|
-
|
|
416
|
-
editor.off(event, onEditorChange);
|
|
417
|
-
});
|
|
418
|
-
editor.off('keydown', onEditorKeyDown);
|
|
419
|
-
if (autoResize)
|
|
420
|
-
editor.off('paste', resizeEditor);
|
|
421
|
-
editor.off('focus', onEditorFocus);
|
|
422
|
-
editor.off('blur', onEditorBlur);
|
|
161
|
+
abortController.abort();
|
|
423
162
|
};
|
|
424
|
-
}, [
|
|
163
|
+
}, [onUnload]);
|
|
164
|
+
// Dispatch event on iframe mousedown to close all open popovers/menus
|
|
425
165
|
useEffect(() => {
|
|
426
|
-
|
|
166
|
+
const iframeDoc = iframeRef.current?.contentDocument;
|
|
167
|
+
if (!iframeDoc)
|
|
427
168
|
return;
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
}, [editor, onInit]);
|
|
431
|
-
useAfterInitialEffect(() => {
|
|
432
|
-
editor?.mode.set(readOnly || disabled ? 'readonly' : 'design');
|
|
433
|
-
}, [readOnly, disabled]);
|
|
434
|
-
const addImage = useCallback((image, imageId) => {
|
|
435
|
-
if (editor) {
|
|
436
|
-
const imageUid = imageId ?? createUID();
|
|
437
|
-
editor.insertContent(`<br/><pega-file data-id='${imageUid}' data-progress='0' data-name='${image.name}' > </pega-file><br/>`);
|
|
438
|
-
onImageAdded?.(image, imageUid);
|
|
439
|
-
}
|
|
440
|
-
}, [editor]);
|
|
441
|
-
const ctx = useMemo(() => ({ addImage, editor: editor || {} }), [editor, addImage]);
|
|
442
|
-
useEffect(() => {
|
|
443
|
-
const onEditorDrop = (e) => {
|
|
444
|
-
e.preventDefault();
|
|
445
|
-
if (e.dataTransfer) {
|
|
446
|
-
Array.from(e.dataTransfer.files).forEach(file => {
|
|
447
|
-
if (file.type.includes('image') && imagesEnabled) {
|
|
448
|
-
addImage(file);
|
|
449
|
-
}
|
|
450
|
-
});
|
|
451
|
-
}
|
|
169
|
+
const handleMouseDown = () => {
|
|
170
|
+
dispatchEditorClickEvent();
|
|
452
171
|
};
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
172
|
+
iframeDoc.addEventListener('mousedown', handleMouseDown);
|
|
173
|
+
return () => iframeDoc.removeEventListener('mousedown', handleMouseDown);
|
|
174
|
+
}, [tiptapEditor]);
|
|
175
|
+
// Set up image action buttons (edit and delete) using custom hook
|
|
176
|
+
const { hoveredImagePosition, currentImageAttrs, currentImageElement, handleDelete: handleImageDelete, setIsOverButtons } = useImageActions({
|
|
177
|
+
tiptapEditor,
|
|
178
|
+
iframeRef,
|
|
179
|
+
isDialogOpen: imageEditDialog.isOpen
|
|
180
|
+
});
|
|
181
|
+
// Set up table cell menu using custom hook (only when tables are active)
|
|
182
|
+
const tableMenuEnabled = tablesEnabled && !readOnly && !disabled;
|
|
183
|
+
const { cellPosition, setIsOverMenu: setIsOverTableMenu, addRowBelow, addRowAbove, addColumnAfter, addColumnBefore, deleteRow, deleteColumn, deleteTable } = useTableCellMenu({
|
|
184
|
+
tiptapEditor: tableMenuEnabled ? tiptapEditor : null,
|
|
185
|
+
iframeRef
|
|
186
|
+
});
|
|
187
|
+
// Handle keyboard navigation from table menu to next cell (start of content)
|
|
188
|
+
const goToNextCell = useCallback(() => {
|
|
189
|
+
if (tiptapEditor) {
|
|
190
|
+
tiptapEditor.commands.focus();
|
|
191
|
+
tiptapEditor.commands.goToNextCell();
|
|
192
|
+
}
|
|
193
|
+
}, [tiptapEditor]);
|
|
194
|
+
// Handle keyboard navigation from table menu back to current cell
|
|
195
|
+
const returnToCell = useCallback(() => {
|
|
196
|
+
tiptapEditor?.commands.focus();
|
|
197
|
+
}, [tiptapEditor]);
|
|
198
|
+
// Store the image element when opening the dialog to target the correct node on save
|
|
199
|
+
const editingImageElementRef = useRef(null);
|
|
200
|
+
// Handle edit button click from ImageActionButtons
|
|
201
|
+
const onEditButtonClick = () => {
|
|
202
|
+
if (currentImageAttrs && currentImageElement) {
|
|
203
|
+
// Store the element reference to target the correct node on save
|
|
204
|
+
editingImageElementRef.current = currentImageElement;
|
|
205
|
+
setImageEditDialog({
|
|
206
|
+
isOpen: true,
|
|
207
|
+
attributes: currentImageAttrs
|
|
459
208
|
});
|
|
460
|
-
|
|
461
|
-
resizeObserver.observe(body);
|
|
462
|
-
}
|
|
209
|
+
// Don't call handleImageEdit() here - keep buttons visible while dialog is open
|
|
463
210
|
}
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
}, [editor, resizeEditor]);
|
|
469
|
-
useEffect(() => {
|
|
470
|
-
if (!editor || !autoResize)
|
|
211
|
+
};
|
|
212
|
+
// Handle image edit save
|
|
213
|
+
const handleImageEditSave = (attrs) => {
|
|
214
|
+
if (!tiptapEditor || !editingImageElementRef.current)
|
|
471
215
|
return;
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
216
|
+
// Get exact position from DOM element to handle duplicate images correctly
|
|
217
|
+
const pos = tiptapEditor.view.posAtDOM(editingImageElementRef.current, 0);
|
|
218
|
+
if (pos >= 0) {
|
|
219
|
+
const { state } = tiptapEditor;
|
|
220
|
+
const node = state.doc.nodeAt(pos);
|
|
221
|
+
if (!node || node.type.name !== 'image')
|
|
222
|
+
return;
|
|
223
|
+
const nodeEnd = pos + node.nodeSize;
|
|
224
|
+
const tr = state.tr;
|
|
225
|
+
// Update image attributes
|
|
226
|
+
tr.setNodeMarkup(pos, undefined, {
|
|
227
|
+
src: attrs.src,
|
|
228
|
+
alt: attrs.alt,
|
|
229
|
+
...(attrs.width && { width: attrs.width }),
|
|
230
|
+
...(attrs.height && { height: attrs.height })
|
|
481
231
|
});
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
232
|
+
// Handle action URL (links are marks in ProseMirror, not nodes)
|
|
233
|
+
// Always remove existing link mark first, then add new one if needed
|
|
234
|
+
tr.removeMark(pos, nodeEnd, state.schema.marks.link);
|
|
235
|
+
if (attrs.actionUrl) {
|
|
236
|
+
const linkMark = state.schema.marks.link.create({
|
|
237
|
+
href: attrs.actionUrl,
|
|
238
|
+
target: '_blank'
|
|
239
|
+
});
|
|
240
|
+
tr.addMark(pos, nodeEnd, linkMark);
|
|
491
241
|
}
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
242
|
+
tiptapEditor.view.dispatch(tr);
|
|
243
|
+
}
|
|
244
|
+
// Clear the ref after save
|
|
245
|
+
editingImageElementRef.current = null;
|
|
246
|
+
};
|
|
247
|
+
// Update editor when props change
|
|
248
|
+
useAfterInitialEffect(() => {
|
|
249
|
+
iframeEditorRef.current?.setEditable(!readOnly && !disabled);
|
|
250
|
+
}, [readOnly, disabled]);
|
|
251
|
+
// Handle autoFocus
|
|
498
252
|
useEffect(() => {
|
|
499
|
-
if (
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
253
|
+
if (autoFocus && tiptapEditor) {
|
|
254
|
+
tiptapEditor.commands.focus();
|
|
255
|
+
}
|
|
256
|
+
}, [autoFocus, tiptapEditor]);
|
|
257
|
+
// Expose methods via ref
|
|
258
|
+
useImperativeHandle(editorRef, () => ({
|
|
259
|
+
focus: () => iframeEditorRef.current?.focus(),
|
|
260
|
+
getPlainText: () => iframeEditorRef.current?.getPlainText() ?? '',
|
|
261
|
+
getRichText: () => iframeEditorRef.current?.getRichText() ?? '',
|
|
262
|
+
getHtml: () => iframeEditorRef.current?.getHtml() ?? '',
|
|
263
|
+
clear: () => iframeEditorRef.current?.clear(),
|
|
264
|
+
updateAttachmentAttributes: (imageId, uploadProgress, error) => {
|
|
265
|
+
if (!iframeRef.current?.contentDocument)
|
|
266
|
+
return;
|
|
267
|
+
const iframeDoc = iframeRef.current.contentDocument;
|
|
268
|
+
const imageCustomEl = iframeDoc.querySelector(`pega-file[data-id="${imageId}"]`);
|
|
269
|
+
if (imageCustomEl) {
|
|
270
|
+
if (error) {
|
|
271
|
+
imageCustomEl.setAttribute('data-error', error);
|
|
272
|
+
}
|
|
273
|
+
else if (uploadProgress !== undefined) {
|
|
274
|
+
imageCustomEl.setAttribute('data-progress', `${uploadProgress}`);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
},
|
|
278
|
+
appendImage: (imageData, imageId) => {
|
|
279
|
+
if (!iframeRef.current?.contentDocument || !tiptapEditor)
|
|
280
|
+
return;
|
|
281
|
+
const iframeDoc = iframeRef.current.contentDocument;
|
|
282
|
+
const imageUploadEl = iframeDoc.querySelector(`pega-file[data-id="${imageId}"]`);
|
|
283
|
+
if (imageUploadEl) {
|
|
284
|
+
// Create img element
|
|
285
|
+
const imgEl = iframeDoc.createElement('img');
|
|
286
|
+
imgEl.setAttribute('src', imageData.src);
|
|
287
|
+
imgEl.setAttribute('alt', imageData.alt);
|
|
288
|
+
if (imageData.attachmentId) {
|
|
289
|
+
imgEl.setAttribute('data-attachment-id', imageData.attachmentId);
|
|
290
|
+
}
|
|
291
|
+
// Replace pega-file with img in the DOM
|
|
292
|
+
imageUploadEl.replaceWith(imgEl);
|
|
293
|
+
// Also update the Tiptap document
|
|
294
|
+
const html = tiptapEditor.getHTML();
|
|
295
|
+
const updatedHtml = html.replace(new RegExp(`<pega-file[^>]*data-id=["']${imageId}["'][^>]*>.*?</pega-file>`, 'g'), imgEl.outerHTML);
|
|
296
|
+
if (updatedHtml !== html) {
|
|
297
|
+
tiptapEditor.commands.setContent(updatedHtml, { emitUpdate: false });
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
},
|
|
301
|
+
insertText: (text) => iframeEditorRef.current?.insertText(text),
|
|
302
|
+
setCursorLocationToStart: () => iframeEditorRef.current?.setCursorLocationToStart(),
|
|
303
|
+
insertHtml: (html, overwrite) => iframeEditorRef.current?.insertHtml(html, overwrite),
|
|
304
|
+
getEditor: () => iframeEditorRef.current?.getEditor() ?? null,
|
|
305
|
+
element: formFieldRef.current || undefined
|
|
306
|
+
}), [tiptapEditor]);
|
|
307
|
+
const ctx = useMemo(() => ({
|
|
308
|
+
editor: tiptapEditor,
|
|
309
|
+
addImage: (image, imageId, alt) => {
|
|
310
|
+
if (!tiptapEditor || !image)
|
|
311
|
+
return;
|
|
312
|
+
const imgId = imageId || `img-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;
|
|
313
|
+
// Create pega-file element to show upload progress
|
|
314
|
+
const pegaFileHtml = `<br/><pega-file data-id='${imgId}' data-progress='0' data-name='${image.name}' data-alt='${alt || image.name}' contenteditable='false'></pega-file><br/>`;
|
|
315
|
+
// Insert the pega-file element
|
|
316
|
+
tiptapEditor.chain().focus().insertContent(pegaFileHtml).run();
|
|
317
|
+
// Trigger the onImageAdded callback so consumer can upload and call appendImage
|
|
318
|
+
onImageAdded?.(image, imgId, alt);
|
|
319
|
+
}
|
|
320
|
+
}), [tiptapEditor, onImageAdded]);
|
|
321
|
+
const renderSuggestionEditor = useCallback((suggestionProps) => (_jsx(SuggestionEditor, { defaultValue: suggestionProps.defaultValue, disabled: suggestionProps.disabled, readOnly: suggestionProps.readOnly, onGetContent: suggestionProps.onGetContent, onContentChange: suggestionProps.onContentChange, renderEditor: ({ ref: suggestionEditorRef, onInit: suggestionOnInit, onChange: suggestionOnChange }) => (_jsx(Editor, { ref: suggestionEditorRef, label: t('ai_rewrite_result_label'), labelHidden: true, toolbar: SUGGESTION_TOOLBAR, defaultValue: suggestionProps.defaultValue, disabled: suggestionProps.disabled, readOnly: suggestionProps.readOnly, height: height, onInit: suggestionOnInit, onChange: suggestionOnChange })) })), [height, t]);
|
|
322
|
+
return (_jsxs(EditorContext.Provider, { value: ctx, children: [_jsx(StyledEditorRoot, { testId: testIds, label: label, labelFor: '', labelId: labelId, labelHidden: labelHidden, id: editorId, info: info, status: status, required: required, disabled: disabled, readOnly: readOnly, onResolveSuggestion: onResolveSuggestion, ref: formFieldRef, additionalInfo: additionalInfo, children: _jsxs(_Fragment, { children: [_jsx(FormControl, { ...restProps, ref: editorRef, required: required, disabled: disabled, readOnly: readOnly, status: status, as: StyledRichTextEditor, focused: focused, hasSuggestion: status === 'pending', children: _jsxs(StyledEditorContainer, { ref: editorContainerRef, children: [progress ? (_jsx(Progress, { placement: 'local', message: typeof progress === 'string' ? progress : undefined })) : null, _jsx(StyledEditorIframe, { "data-testid": testIds.iframe, title: typeof label === 'string' ? label : t('rte_text_formatting_toolbar'), role: 'application', "aria-label": typeof label === 'string' ? label : t('rte_text_formatting_toolbar'), "aria-describedby": required ? requiredDescriptionId : undefined, ref: iframeRef, tabIndex: sourceMode ? -1 : 0, "$minHeight": height?.min, "$maxHeight": height?.max, "$hidden": sourceMode }), required ? (_jsx(HiddenText, { id: requiredDescriptionId, children: t('required') })) : null, sourceMode ? (_jsx(StyledSourceTextarea, { value: sourceCode, onChange: e => setSourceCode(e.target.value), spellCheck: false, "aria-label": t('rte_source_code'), "$minHeight": height?.min, "$maxHeight": height?.max })) : null, (toolbar?.length || customActions?.length) && tiptapEditor ? (_jsx(Toolbar, { testId: testIds, features: toolbar || [], editor: tiptapEditor, customActions: customActions, imageInsertionMode: imageInsertionMode, onRewriteClick: onRewriteClick, renderSuggestionEditor: renderSuggestionEditor, sourceMode: sourceMode, onSourceModeToggle: (isSourceMode) => {
|
|
323
|
+
if (isSourceMode) {
|
|
324
|
+
setSourceCode(tiptapEditor.getHTML());
|
|
325
|
+
}
|
|
326
|
+
else {
|
|
327
|
+
tiptapEditor.commands.setContent(sourceCode);
|
|
328
|
+
}
|
|
329
|
+
setSourceMode(isSourceMode);
|
|
330
|
+
} })) : null, imagesEnabled && !readOnly && !disabled ? (_jsx(ImageActionButtons, { ref: editButtonRef, position: hoveredImagePosition, containerRef: editorContainerRef, onEdit: onEditButtonClick, onDelete: handleImageDelete, onMouseEnter: () => setIsOverButtons(true), onMouseLeave: () => setIsOverButtons(false), editLabel: t('rte_edit_image', [currentImageAttrs?.alt || '']), deleteLabel: t('rte_delete_image', [currentImageAttrs?.alt || '']) })) : null, tablesEnabled && !readOnly && !disabled && !sourceMode ? (_jsx(TableCellMenu, { ref: tableCellMenuButtonRef, position: cellPosition, containerRef: editorContainerRef, onAddRowBelow: addRowBelow, onAddRowAbove: addRowAbove, onAddColumnAfter: addColumnAfter, onAddColumnBefore: addColumnBefore, onDeleteRow: deleteRow, onDeleteColumn: deleteColumn, onDeleteTable: deleteTable, onGoToNextCell: goToNextCell, onReturnToCell: returnToCell, onMouseEnter: () => setIsOverTableMenu(true), onMouseLeave: () => setIsOverTableMenu(false), labels: {
|
|
331
|
+
tableOptions: t('rte_table_options'),
|
|
332
|
+
addRowBelow: t('rte_add_row_below'),
|
|
333
|
+
addRowAbove: t('rte_add_row_above'),
|
|
334
|
+
addColumnAfter: t('rte_add_column_after'),
|
|
335
|
+
addColumnBefore: t('rte_add_column_before'),
|
|
336
|
+
deleteRow: t('rte_delete_row'),
|
|
337
|
+
deleteColumn: t('rte_delete_column'),
|
|
338
|
+
deleteTable: t('rte_delete_table')
|
|
339
|
+
} })) : null] }) }), toolbar?.includes('word-count') && tiptapEditor ? (_jsx(WordCount, { "data-testid": testIds.wordCount, editor: tiptapEditor })) : null] }) }), children, imageEditDialog.attributes && (_jsx(ImageEditDialog, { isOpen: imageEditDialog.isOpen, onClose: () => {
|
|
340
|
+
setImageEditDialog({ isOpen: false, attributes: null });
|
|
341
|
+
}, onSave: handleImageEditSave, initialAttributes: imageEditDialog.attributes, target: editButtonRef.current }))] }));
|
|
531
342
|
});
|
|
532
|
-
export default withTestIds(
|
|
343
|
+
export default withTestIds(Editor, getEditorTestIds);
|
|
533
344
|
//# sourceMappingURL=Editor.js.map
|