@pie-lib/editable-html-tip-tap 0.1.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/dist/components/CharacterPicker.d.ts +31 -0
- package/dist/components/CharacterPicker.d.ts.map +1 -0
- package/dist/components/CharacterPicker.js +129 -0
- package/dist/components/EditableHtml.d.ts +11 -0
- package/dist/components/EditableHtml.d.ts.map +1 -0
- package/dist/components/EditableHtml.js +270 -0
- package/dist/components/MenuBar.d.ts +11 -0
- package/dist/components/MenuBar.d.ts.map +1 -0
- package/dist/components/MenuBar.js +460 -0
- package/dist/components/TiptapContainer.d.ts +11 -0
- package/dist/components/TiptapContainer.d.ts.map +1 -0
- package/dist/components/TiptapContainer.js +157 -0
- package/dist/components/characters/characterUtils.d.ts +36 -0
- package/dist/components/characters/characterUtils.d.ts.map +1 -0
- package/dist/components/characters/characterUtils.js +465 -0
- package/dist/components/characters/custom-popper.d.ts +14 -0
- package/dist/components/characters/custom-popper.d.ts.map +1 -0
- package/dist/components/characters/custom-popper.js +32 -0
- package/dist/components/common/done-button.d.ts +30 -0
- package/dist/components/common/done-button.d.ts.map +1 -0
- package/dist/components/common/done-button.js +26 -0
- package/dist/components/common/toolbar-buttons.d.ts +39 -0
- package/dist/components/common/toolbar-buttons.d.ts.map +1 -0
- package/dist/components/common/toolbar-buttons.js +91 -0
- package/dist/components/icons/CssIcon.d.ts +11 -0
- package/dist/components/icons/CssIcon.d.ts.map +1 -0
- package/dist/components/icons/CssIcon.js +14 -0
- package/dist/components/icons/RespArea.d.ts +26 -0
- package/dist/components/icons/RespArea.d.ts.map +1 -0
- package/dist/components/icons/RespArea.js +42 -0
- package/dist/components/icons/TableIcons.d.ts +14 -0
- package/dist/components/icons/TableIcons.d.ts.map +1 -0
- package/dist/components/icons/TableIcons.js +32 -0
- package/dist/components/icons/TextAlign.d.ts +18 -0
- package/dist/components/icons/TextAlign.d.ts.map +1 -0
- package/dist/components/icons/TextAlign.js +134 -0
- package/dist/components/image/AltDialog.d.ts +23 -0
- package/dist/components/image/AltDialog.d.ts.map +1 -0
- package/dist/components/image/AltDialog.js +61 -0
- package/dist/components/image/ImageToolbar.d.ts +25 -0
- package/dist/components/image/ImageToolbar.d.ts.map +1 -0
- package/dist/components/image/ImageToolbar.js +80 -0
- package/dist/components/image/InsertImageHandler.d.ts +33 -0
- package/dist/components/image/InsertImageHandler.d.ts.map +1 -0
- package/dist/components/image/InsertImageHandler.js +55 -0
- package/dist/components/media/MediaDialog.d.ts +44 -0
- package/dist/components/media/MediaDialog.d.ts.map +1 -0
- package/dist/components/media/MediaDialog.js +389 -0
- package/dist/components/media/MediaToolbar.d.ts +20 -0
- package/dist/components/media/MediaToolbar.d.ts.map +1 -0
- package/dist/components/media/MediaToolbar.js +41 -0
- package/dist/components/media/MediaWrapper.d.ts +20 -0
- package/dist/components/media/MediaWrapper.d.ts.map +1 -0
- package/dist/components/respArea/DragInTheBlank/DragInTheBlank.d.ts +23 -0
- package/dist/components/respArea/DragInTheBlank/DragInTheBlank.d.ts.map +1 -0
- package/dist/components/respArea/DragInTheBlank/DragInTheBlank.js +58 -0
- package/dist/components/respArea/DragInTheBlank/choice.d.ts +56 -0
- package/dist/components/respArea/DragInTheBlank/choice.d.ts.map +1 -0
- package/dist/components/respArea/DragInTheBlank/choice.js +156 -0
- package/dist/components/respArea/ExplicitConstructedResponse.d.ts +20 -0
- package/dist/components/respArea/ExplicitConstructedResponse.d.ts.map +1 -0
- package/dist/components/respArea/ExplicitConstructedResponse.js +67 -0
- package/dist/components/respArea/InlineDropdown.d.ts +18 -0
- package/dist/components/respArea/InlineDropdown.d.ts.map +1 -0
- package/dist/components/respArea/InlineDropdown.js +91 -0
- package/dist/components/respArea/MathTemplated.d.ts +19 -0
- package/dist/components/respArea/MathTemplated.d.ts.map +1 -0
- package/dist/components/respArea/MathTemplated.js +97 -0
- package/dist/components/respArea/ToolbarIcon.d.ts +14 -0
- package/dist/components/respArea/ToolbarIcon.d.ts.map +1 -0
- package/dist/components/respArea/ToolbarIcon.js +17 -0
- package/dist/constants.d.ts +14 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +4 -0
- package/dist/extensions/css.d.ts +12 -0
- package/dist/extensions/css.d.ts.map +1 -0
- package/dist/extensions/css.js +115 -0
- package/dist/extensions/custom-toolbar-wrapper.d.ts +11 -0
- package/dist/extensions/custom-toolbar-wrapper.d.ts.map +1 -0
- package/dist/extensions/custom-toolbar-wrapper.js +58 -0
- package/dist/extensions/div-node.d.ts +11 -0
- package/dist/extensions/div-node.d.ts.map +1 -0
- package/dist/extensions/div-node.js +25 -0
- package/dist/extensions/extended-table.d.ts +11 -0
- package/dist/extensions/extended-table.d.ts.map +1 -0
- package/dist/extensions/extended-table.js +15 -0
- package/dist/extensions/image-component.d.ts +22 -0
- package/dist/extensions/image-component.d.ts.map +1 -0
- package/dist/extensions/image-component.js +200 -0
- package/dist/extensions/image.d.ts +11 -0
- package/dist/extensions/image.d.ts.map +1 -0
- package/dist/extensions/image.js +42 -0
- package/dist/extensions/index.d.ts +17 -0
- package/dist/extensions/index.d.ts.map +1 -0
- package/dist/extensions/index.js +65 -0
- package/dist/extensions/math.d.ts +15 -0
- package/dist/extensions/math.d.ts.map +1 -0
- package/dist/extensions/math.js +150 -0
- package/dist/extensions/media.d.ts +19 -0
- package/dist/extensions/media.d.ts.map +1 -0
- package/dist/extensions/media.js +147 -0
- package/dist/extensions/responseArea.d.ts +28 -0
- package/dist/extensions/responseArea.d.ts.map +1 -0
- package/dist/extensions/responseArea.js +259 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +6 -0
- package/dist/styles/editorContainerStyles.d.ts +135 -0
- package/dist/styles/editorContainerStyles.d.ts.map +1 -0
- package/dist/theme.d.ts +10 -0
- package/dist/theme.d.ts.map +1 -0
- package/dist/utils/helper.d.ts +10 -0
- package/dist/utils/helper.d.ts.map +1 -0
- package/dist/utils/helper.js +7 -0
- package/dist/utils/size.d.ts +10 -0
- package/dist/utils/size.d.ts.map +1 -0
- package/dist/utils/size.js +14 -0
- package/package.json +71 -0
- package/src/components/CharacterPicker.tsx +210 -0
- package/src/components/EditableHtml.tsx +416 -0
- package/src/components/MenuBar.tsx +558 -0
- package/src/components/TiptapContainer.tsx +228 -0
- package/src/components/characters/characterUtils.ts +457 -0
- package/src/components/characters/custom-popper.tsx +48 -0
- package/src/components/common/done-button.tsx +37 -0
- package/src/components/common/toolbar-buttons.tsx +132 -0
- package/src/components/icons/CssIcon.tsx +25 -0
- package/src/components/icons/RespArea.tsx +81 -0
- package/src/components/icons/TableIcons.tsx +62 -0
- package/src/components/icons/TextAlign.tsx +124 -0
- package/src/components/image/AltDialog.tsx +92 -0
- package/src/components/image/ImageToolbar.tsx +109 -0
- package/src/components/image/InsertImageHandler.ts +121 -0
- package/src/components/media/MediaDialog.tsx +606 -0
- package/src/components/media/MediaToolbar.tsx +59 -0
- package/src/components/media/MediaWrapper.tsx +49 -0
- package/src/components/respArea/DragInTheBlank/DragInTheBlank.tsx +86 -0
- package/src/components/respArea/DragInTheBlank/choice.tsx +266 -0
- package/src/components/respArea/ExplicitConstructedResponse.tsx +122 -0
- package/src/components/respArea/InlineDropdown.tsx +152 -0
- package/src/components/respArea/MathTemplated.tsx +134 -0
- package/src/components/respArea/ToolbarIcon.tsx +76 -0
- package/src/constants.ts +15 -0
- package/src/extensions/css.tsx +230 -0
- package/src/extensions/custom-toolbar-wrapper.tsx +88 -0
- package/src/extensions/div-node.tsx +46 -0
- package/src/extensions/extended-table.ts +34 -0
- package/src/extensions/image-component.tsx +303 -0
- package/src/extensions/image.tsx +64 -0
- package/src/extensions/index.tsx +91 -0
- package/src/extensions/math.tsx +285 -0
- package/src/extensions/media.tsx +198 -0
- package/src/extensions/responseArea.tsx +404 -0
- package/src/index.tsx +15 -0
- package/src/styles/editorContainerStyles.ts +155 -0
- package/src/theme.ts +11 -0
- package/src/utils/helper.tsx +27 -0
- package/src/utils/size.ts +42 -0
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
/**
|
|
3
|
+
* @synced-from pie-lib/packages/editable-html-tip-tap/src/extensions/math.js
|
|
4
|
+
* @auto-generated
|
|
5
|
+
*
|
|
6
|
+
* This file is automatically synced from pie-elements and converted to TypeScript.
|
|
7
|
+
* Manual edits will be overwritten on next sync.
|
|
8
|
+
* To make changes, edit the upstream JavaScript file and run sync again.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import React, { useEffect, useRef, useState } from 'react';
|
|
12
|
+
import ReactDOM from 'react-dom';
|
|
13
|
+
import { Node } from '@tiptap/core';
|
|
14
|
+
import { NodeViewWrapper, ReactNodeViewRenderer } from '@tiptap/react';
|
|
15
|
+
import { NodeSelection, Plugin, PluginKey, TextSelection } from 'prosemirror-state';
|
|
16
|
+
import { MathPreview, MathToolbar } from '@pie-lib/math-toolbar';
|
|
17
|
+
import { wrapMath } from '@pie-element/shared-math-rendering-mathjax';
|
|
18
|
+
|
|
19
|
+
const ensureTextAfterMathPluginKey = new PluginKey('ensureTextAfterMath');
|
|
20
|
+
|
|
21
|
+
export const EnsureTextAfterMathPlugin = (mathNodeName) =>
|
|
22
|
+
new Plugin({
|
|
23
|
+
key: ensureTextAfterMathPluginKey,
|
|
24
|
+
appendTransaction: (transactions, oldState, newState) => {
|
|
25
|
+
// Only act when the doc actually changed
|
|
26
|
+
if (!transactions.some((tr) => tr.docChanged)) return null;
|
|
27
|
+
|
|
28
|
+
const tr = newState.tr;
|
|
29
|
+
let changed = false;
|
|
30
|
+
|
|
31
|
+
newState.doc.descendants((node, pos) => {
|
|
32
|
+
if (node.type.name === mathNodeName) {
|
|
33
|
+
const nextPos = pos + node.nodeSize;
|
|
34
|
+
const nextNode = newState.doc.nodeAt(nextPos);
|
|
35
|
+
|
|
36
|
+
// If there's no node after, or the next node isn't text, insert a space
|
|
37
|
+
if (!nextNode || nextNode.type.name !== 'text') {
|
|
38
|
+
tr.insert(nextPos, newState.schema.text('\u200b'));
|
|
39
|
+
changed = true;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
return changed ? tr : null;
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
export const ZeroWidthSpaceHandlingPlugin = new Plugin({
|
|
49
|
+
key: new PluginKey('zeroWidthSpaceHandling'),
|
|
50
|
+
props: {
|
|
51
|
+
handleKeyDown(view, event) {
|
|
52
|
+
const { state, dispatch } = view;
|
|
53
|
+
const { selection, doc } = state;
|
|
54
|
+
const { from, empty } = selection;
|
|
55
|
+
|
|
56
|
+
if (empty && event.key === 'Backspace' && from > 0) {
|
|
57
|
+
const prevChar = doc.textBetween(from - 1, from, '\uFFFC', '\uFFFC');
|
|
58
|
+
if (prevChar === '\u200b') {
|
|
59
|
+
const tr = state.tr.delete(from - 2, from);
|
|
60
|
+
dispatch(tr);
|
|
61
|
+
return true; // handled
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (empty && event.key === 'ArrowLeft' && from > 0) {
|
|
66
|
+
const prevChar = doc.textBetween(from - 1, from, '\uFFFC', '\uFFFC');
|
|
67
|
+
// If the previous character is the zero-width space...
|
|
68
|
+
if (prevChar === '\u200b') {
|
|
69
|
+
const posBefore = from - 1;
|
|
70
|
+
const resolved = state.doc.resolve(posBefore - 1); // look just before the zwsp
|
|
71
|
+
const maybeNode = resolved.nodeAfter || resolved.nodeBefore;
|
|
72
|
+
|
|
73
|
+
// Check if there's an inline selectable node (e.g., your math node)
|
|
74
|
+
if (maybeNode) {
|
|
75
|
+
const nodePos = posBefore - maybeNode.nodeSize;
|
|
76
|
+
const nodeResolved = state.doc.resolve(nodePos);
|
|
77
|
+
const tr = state.tr.setSelection(NodeSelection.create(state.doc, nodeResolved.pos));
|
|
78
|
+
dispatch(tr);
|
|
79
|
+
return true;
|
|
80
|
+
} else {
|
|
81
|
+
// Just move the text cursor before the zwsp
|
|
82
|
+
const tr = state.tr.setSelection(TextSelection.create(state.doc, from - 2));
|
|
83
|
+
dispatch(tr);
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return false;
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
export const MathNode = Node.create({
|
|
95
|
+
name: 'math',
|
|
96
|
+
group: 'inline',
|
|
97
|
+
inline: true,
|
|
98
|
+
atom: true,
|
|
99
|
+
|
|
100
|
+
addAttributes() {
|
|
101
|
+
return {
|
|
102
|
+
latex: { default: '' },
|
|
103
|
+
wrapper: { default: null },
|
|
104
|
+
html: { default: null },
|
|
105
|
+
};
|
|
106
|
+
},
|
|
107
|
+
|
|
108
|
+
addProseMirrorPlugins() {
|
|
109
|
+
return [EnsureTextAfterMathPlugin(this.name), ZeroWidthSpaceHandlingPlugin];
|
|
110
|
+
},
|
|
111
|
+
|
|
112
|
+
parseHTML() {
|
|
113
|
+
return [
|
|
114
|
+
{
|
|
115
|
+
tag: 'span[data-latex]',
|
|
116
|
+
getAttrs: (el) => ({
|
|
117
|
+
latex: el.getAttribute('data-raw') || el.textContent,
|
|
118
|
+
}),
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
tag: 'span[data-type="mathml"]',
|
|
122
|
+
getAttrs: (el) => ({
|
|
123
|
+
html: el.innerHTML,
|
|
124
|
+
}),
|
|
125
|
+
},
|
|
126
|
+
];
|
|
127
|
+
},
|
|
128
|
+
|
|
129
|
+
addCommands() {
|
|
130
|
+
return {
|
|
131
|
+
insertMath:
|
|
132
|
+
(latex = '') =>
|
|
133
|
+
({ tr, editor, dispatch }) => {
|
|
134
|
+
const { state } = editor.view;
|
|
135
|
+
const node = state.schema.nodes.math.create({
|
|
136
|
+
latex,
|
|
137
|
+
});
|
|
138
|
+
const { selection } = state;
|
|
139
|
+
|
|
140
|
+
// The inserted node is typically just before the cursor
|
|
141
|
+
const pos = selection.$from.pos;
|
|
142
|
+
|
|
143
|
+
tr.insert(pos, node);
|
|
144
|
+
|
|
145
|
+
if (node?.type?.name === this.name) {
|
|
146
|
+
// Create a NodeSelection from the current doc
|
|
147
|
+
const sel = NodeSelection.create(tr.doc, selection.$from.pos);
|
|
148
|
+
|
|
149
|
+
// Build a fresh transaction from the current state and set the selection
|
|
150
|
+
tr.setSelection(sel);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
dispatch(tr);
|
|
154
|
+
|
|
155
|
+
return true;
|
|
156
|
+
},
|
|
157
|
+
// insertMath: (latex = '') => ({ commands }) => {
|
|
158
|
+
// return commands.insertContent({
|
|
159
|
+
// type: this.name,
|
|
160
|
+
// attrs: { latex },
|
|
161
|
+
// });
|
|
162
|
+
// },
|
|
163
|
+
};
|
|
164
|
+
},
|
|
165
|
+
|
|
166
|
+
renderHTML({ HTMLAttributes }) {
|
|
167
|
+
if (HTMLAttributes.html) {
|
|
168
|
+
return ['span', { 'data-type': 'mathml', dangerouslySetInnerHTML: { __html: HTMLAttributes.html } }];
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return [
|
|
172
|
+
'span',
|
|
173
|
+
{ 'data-latex': '', 'data-raw': HTMLAttributes.latex },
|
|
174
|
+
wrapMath(HTMLAttributes.latex, HTMLAttributes.wrapper),
|
|
175
|
+
];
|
|
176
|
+
},
|
|
177
|
+
|
|
178
|
+
addNodeView() {
|
|
179
|
+
return ReactNodeViewRenderer((props) => <MathNodeView {...{ ...props, options: this.options }} />);
|
|
180
|
+
},
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
export const MathNodeView = (props) => {
|
|
184
|
+
const { node, updateAttributes, editor, selected, options } = props;
|
|
185
|
+
const [showToolbar, setShowToolbar] = useState(selected);
|
|
186
|
+
const toolbarRef = useRef(null);
|
|
187
|
+
const [position, setPosition] = useState({ top: 0, left: 0 });
|
|
188
|
+
|
|
189
|
+
const latex = node.attrs.latex || '';
|
|
190
|
+
|
|
191
|
+
useEffect(() => {
|
|
192
|
+
if (selected) {
|
|
193
|
+
setShowToolbar(true);
|
|
194
|
+
}
|
|
195
|
+
}, [selected]);
|
|
196
|
+
|
|
197
|
+
useEffect(() => {
|
|
198
|
+
editor._toolbarOpened = !!showToolbar;
|
|
199
|
+
}, [showToolbar]);
|
|
200
|
+
|
|
201
|
+
useEffect(() => {
|
|
202
|
+
// Calculate position relative to selection
|
|
203
|
+
const bodyRect = document.body.getBoundingClientRect();
|
|
204
|
+
const { from } = editor.state.selection;
|
|
205
|
+
const start = editor.view.coordsAtPos(from);
|
|
206
|
+
setPosition({
|
|
207
|
+
top: start.top + Math.abs(bodyRect.top) + 40, // shift above
|
|
208
|
+
left: start.left,
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
const handleClickOutside = (event) => {
|
|
212
|
+
if (
|
|
213
|
+
toolbarRef.current &&
|
|
214
|
+
!toolbarRef.current.contains(event.target) &&
|
|
215
|
+
!event.target.closest('[data-inline-node]')
|
|
216
|
+
) {
|
|
217
|
+
setShowToolbar(false);
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
if (showToolbar) {
|
|
222
|
+
// Use `click` (not `mousedown`) so interacting with browser UI like the scrollbar
|
|
223
|
+
// doesn't automatically dismiss the math toolbar.
|
|
224
|
+
document.addEventListener('click', handleClickOutside);
|
|
225
|
+
} else {
|
|
226
|
+
document.removeEventListener('click', handleClickOutside);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
return () => document.removeEventListener('click', handleClickOutside);
|
|
230
|
+
}, [editor, showToolbar]);
|
|
231
|
+
|
|
232
|
+
const handleChange = (newLatex) => {
|
|
233
|
+
updateAttributes({ latex: newLatex });
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
const handleDone = (newLatex) => {
|
|
237
|
+
updateAttributes({ latex: newLatex });
|
|
238
|
+
setShowToolbar(false);
|
|
239
|
+
|
|
240
|
+
editor._toolbarOpened = false;
|
|
241
|
+
|
|
242
|
+
const { selection, tr, doc } = editor.state;
|
|
243
|
+
const sel = TextSelection.create(doc, selection.from + 1);
|
|
244
|
+
|
|
245
|
+
// Build a fresh transaction from the current state and set the selection
|
|
246
|
+
tr.setSelection(sel);
|
|
247
|
+
editor.view.dispatch(tr);
|
|
248
|
+
editor.commands.focus();
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
return (
|
|
252
|
+
<NodeViewWrapper
|
|
253
|
+
className="math-node"
|
|
254
|
+
style={{
|
|
255
|
+
display: 'inline-flex',
|
|
256
|
+
cursor: 'pointer',
|
|
257
|
+
margin: '0 4px',
|
|
258
|
+
}}
|
|
259
|
+
data-selected={selected}
|
|
260
|
+
>
|
|
261
|
+
<div onClick={() => setShowToolbar(true)} contentEditable={false}>
|
|
262
|
+
<MathPreview latex={latex} />
|
|
263
|
+
</div>
|
|
264
|
+
{showToolbar &&
|
|
265
|
+
ReactDOM.createPortal(
|
|
266
|
+
<div
|
|
267
|
+
ref={toolbarRef}
|
|
268
|
+
data-toolbar-for={editor.instanceId}
|
|
269
|
+
style={{
|
|
270
|
+
position: 'absolute',
|
|
271
|
+
top: `${position.top}px`,
|
|
272
|
+
left: `${position.left}px`,
|
|
273
|
+
zIndex: 20,
|
|
274
|
+
background: 'var(--editable-html-toolbar-bg, #efefef)',
|
|
275
|
+
boxShadow:
|
|
276
|
+
'0px 1px 5px 0px rgba(0, 0, 0, 0.2), 0px 2px 2px 0px rgba(0, 0, 0, 0.14), 0px 3px 1px -2px rgba(0, 0, 0, 0.12)',
|
|
277
|
+
}}
|
|
278
|
+
>
|
|
279
|
+
<MathToolbar latex={latex} autoFocus onChange={handleChange} onDone={handleDone} keypadMode="basic" />
|
|
280
|
+
</div>,
|
|
281
|
+
document.body,
|
|
282
|
+
)}
|
|
283
|
+
</NodeViewWrapper>
|
|
284
|
+
);
|
|
285
|
+
};
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
/**
|
|
3
|
+
* @synced-from pie-lib/packages/editable-html-tip-tap/src/extensions/media.js
|
|
4
|
+
* @auto-generated
|
|
5
|
+
*
|
|
6
|
+
* This file is automatically synced from pie-elements and converted to TypeScript.
|
|
7
|
+
* Manual edits will be overwritten on next sync.
|
|
8
|
+
* To make changes, edit the upstream JavaScript file and run sync again.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import React, { useEffect } from 'react';
|
|
12
|
+
import ReactDOM from 'react-dom';
|
|
13
|
+
import { mergeAttributes, Node } from '@tiptap/core';
|
|
14
|
+
import { NodeViewWrapper, ReactNodeViewRenderer } from '@tiptap/react';
|
|
15
|
+
import MediaDialog from '../components/media/MediaDialog.js';
|
|
16
|
+
import MediaToolbar from '../components/media/MediaToolbar.js';
|
|
17
|
+
|
|
18
|
+
export const Media = Node.create({
|
|
19
|
+
name: 'media',
|
|
20
|
+
group: 'inline',
|
|
21
|
+
inline: true,
|
|
22
|
+
atom: true,
|
|
23
|
+
|
|
24
|
+
addAttributes() {
|
|
25
|
+
return {
|
|
26
|
+
type: { default: 'video' },
|
|
27
|
+
src: { default: null },
|
|
28
|
+
width: { default: null },
|
|
29
|
+
height: { default: null },
|
|
30
|
+
title: { default: null },
|
|
31
|
+
starts: { default: null },
|
|
32
|
+
ends: { default: null },
|
|
33
|
+
editing: { default: false },
|
|
34
|
+
tag: { default: 'iframe' }, // 'iframe' or 'audio'
|
|
35
|
+
url: { default: null },
|
|
36
|
+
};
|
|
37
|
+
},
|
|
38
|
+
|
|
39
|
+
parseHTML() {
|
|
40
|
+
return [
|
|
41
|
+
{
|
|
42
|
+
tag: 'iframe[data-type="video"]',
|
|
43
|
+
getAttrs: (el) => ({
|
|
44
|
+
type: 'video',
|
|
45
|
+
tag: 'iframe',
|
|
46
|
+
src: el.getAttribute('src'),
|
|
47
|
+
width: el.getAttribute('width'),
|
|
48
|
+
height: el.getAttribute('height'),
|
|
49
|
+
title: el.dataset.title,
|
|
50
|
+
starts: el.dataset.starts,
|
|
51
|
+
ends: el.dataset.ends,
|
|
52
|
+
url: el.dataset.url,
|
|
53
|
+
}),
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
tag: 'audio',
|
|
57
|
+
getAttrs: (el) => ({
|
|
58
|
+
type: 'audio',
|
|
59
|
+
tag: 'audio',
|
|
60
|
+
src: el.querySelector('source')?.getAttribute('src'),
|
|
61
|
+
}),
|
|
62
|
+
},
|
|
63
|
+
];
|
|
64
|
+
},
|
|
65
|
+
|
|
66
|
+
renderHTML({ HTMLAttributes }) {
|
|
67
|
+
const { tag, src, width, height } = HTMLAttributes;
|
|
68
|
+
|
|
69
|
+
if (tag === 'audio') {
|
|
70
|
+
return ['audio', { controls: 'controls', controlsList: 'nodownload' }, ['source', { src, type: 'audio/mp3' }]];
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return [
|
|
74
|
+
'iframe',
|
|
75
|
+
mergeAttributes(
|
|
76
|
+
{
|
|
77
|
+
'data-type': 'video',
|
|
78
|
+
frameborder: '0',
|
|
79
|
+
allow: 'accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture',
|
|
80
|
+
allowfullscreen: '',
|
|
81
|
+
src,
|
|
82
|
+
},
|
|
83
|
+
width ? { width } : {},
|
|
84
|
+
height ? { height } : {},
|
|
85
|
+
),
|
|
86
|
+
];
|
|
87
|
+
},
|
|
88
|
+
|
|
89
|
+
addCommands() {
|
|
90
|
+
return {
|
|
91
|
+
insertMedia:
|
|
92
|
+
(attrs) =>
|
|
93
|
+
({ commands }) => {
|
|
94
|
+
return commands.insertContent({ type: this.name, attrs });
|
|
95
|
+
},
|
|
96
|
+
updateMedia:
|
|
97
|
+
(attrs) =>
|
|
98
|
+
({ commands }) => {
|
|
99
|
+
return commands.updateAttributes(this.name, attrs);
|
|
100
|
+
},
|
|
101
|
+
};
|
|
102
|
+
},
|
|
103
|
+
|
|
104
|
+
addNodeView() {
|
|
105
|
+
return ReactNodeViewRenderer((props) => <MediaNodeView {...{ ...props, options: this.options }} />);
|
|
106
|
+
},
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
const removeDialogs = () => {
|
|
110
|
+
const prevDialogs = document.querySelectorAll('.insert-media-dialog');
|
|
111
|
+
|
|
112
|
+
prevDialogs.forEach((s) => s.remove());
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
export const insertDialog = (props) => {
|
|
116
|
+
const newEl = document.createElement('div');
|
|
117
|
+
const { type, callback, options, ...rest } = props;
|
|
118
|
+
const initialBodyOverflow = document.body.style.overflow;
|
|
119
|
+
|
|
120
|
+
removeDialogs();
|
|
121
|
+
|
|
122
|
+
newEl.className = 'insert-media-dialog';
|
|
123
|
+
document.body.style.overflow = 'hidden';
|
|
124
|
+
|
|
125
|
+
const handleClose = (val, data) => {
|
|
126
|
+
callback(val, data);
|
|
127
|
+
newEl.remove();
|
|
128
|
+
document.body.style.overflow = initialBodyOverflow;
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
const el = (
|
|
132
|
+
<MediaDialog
|
|
133
|
+
{...rest}
|
|
134
|
+
uploadSoundSupport={options.uploadSoundSupport}
|
|
135
|
+
type={type}
|
|
136
|
+
disablePortal={true}
|
|
137
|
+
open={true}
|
|
138
|
+
handleClose={handleClose}
|
|
139
|
+
/>
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
ReactDOM.render(el, newEl);
|
|
143
|
+
|
|
144
|
+
document.body.appendChild(newEl);
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
export default function MediaNodeView({ editor, node, updateAttributes, deleteNode, options }) {
|
|
148
|
+
const { type, src, width, height, tag } = node.attrs;
|
|
149
|
+
|
|
150
|
+
const handleEdit = () => {
|
|
151
|
+
insertDialog({
|
|
152
|
+
...node.attrs,
|
|
153
|
+
options: options,
|
|
154
|
+
edit: true,
|
|
155
|
+
callback: (val, data) => {
|
|
156
|
+
if (val) {
|
|
157
|
+
updateAttributes(data);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
editor.chain().focus().run();
|
|
161
|
+
},
|
|
162
|
+
});
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
useEffect(() => {
|
|
166
|
+
// Only open dialog for newly inserted media without a src
|
|
167
|
+
if (!src) {
|
|
168
|
+
insertDialog({
|
|
169
|
+
...node.attrs,
|
|
170
|
+
options: options,
|
|
171
|
+
edit: true,
|
|
172
|
+
callback: (val, data) => {
|
|
173
|
+
if (val) {
|
|
174
|
+
updateAttributes(data);
|
|
175
|
+
} else {
|
|
176
|
+
deleteNode();
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
editor.chain().focus().run();
|
|
180
|
+
},
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
}, []);
|
|
184
|
+
|
|
185
|
+
return (
|
|
186
|
+
<NodeViewWrapper data-type={type} style={{ width, height }}>
|
|
187
|
+
{tag === 'audio' ? (
|
|
188
|
+
<audio controls controlsList="nodownload">
|
|
189
|
+
<source type="audio/mp3" src={src} />
|
|
190
|
+
</audio>
|
|
191
|
+
) : (
|
|
192
|
+
<iframe src={src} allowFullScreen frameBorder="0" />
|
|
193
|
+
)}
|
|
194
|
+
|
|
195
|
+
<MediaToolbar onEdit={handleEdit} onRemove={deleteNode} />
|
|
196
|
+
</NodeViewWrapper>
|
|
197
|
+
);
|
|
198
|
+
}
|