@pie-lib/editable-html-tip-tap 2.1.7 → 2.1.9
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/CHANGELOG.md +19 -0
- package/lib/components/EditableHtml.js +3 -1
- package/lib/components/EditableHtml.js.map +1 -1
- package/lib/components/TiptapContainer.js +8 -1
- package/lib/components/TiptapContainer.js.map +1 -1
- package/lib/components/respArea/InlineDropdown.js +6 -0
- package/lib/components/respArea/InlineDropdown.js.map +1 -1
- package/lib/extensions/css.js +17 -7
- package/lib/extensions/css.js.map +1 -1
- package/lib/extensions/math.js +83 -14
- package/lib/extensions/math.js.map +1 -1
- package/package.json +6 -6
- package/src/components/EditableHtml.jsx +1 -1
- package/src/components/TiptapContainer.jsx +17 -2
- package/src/components/__tests__/InlineDropdown.test.jsx +14 -0
- package/src/components/respArea/InlineDropdown.jsx +12 -0
- package/src/extensions/__tests__/math.test.js +146 -9
- package/src/extensions/css.js +16 -3
- package/src/extensions/math.js +92 -15
package/src/extensions/css.js
CHANGED
|
@@ -169,14 +169,27 @@ export const CSSMark = Mark.create({
|
|
|
169
169
|
},
|
|
170
170
|
|
|
171
171
|
parseHTML() {
|
|
172
|
-
// Any span with a class that matches one of allowed classes
|
|
172
|
+
// Any span with a class that matches one of allowed classes any span that carries a class attribute
|
|
173
|
+
// so that pre-existing spans are preserved when loading content
|
|
173
174
|
return [
|
|
174
175
|
{
|
|
175
176
|
tag: 'span[class]',
|
|
176
177
|
getAttrs: (el) => {
|
|
177
178
|
const cls = el.getAttribute('class') || '';
|
|
178
|
-
|
|
179
|
-
|
|
179
|
+
|
|
180
|
+
if (!cls) {
|
|
181
|
+
return false;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const allowedClasses = (this.options && this.options.classes) || [];
|
|
185
|
+
|
|
186
|
+
if (allowedClasses.length > 0) {
|
|
187
|
+
const match = this.options.classes.find((name) => cls.includes(name));
|
|
188
|
+
|
|
189
|
+
return match ? { class: match } : false;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return { class: cls };
|
|
180
193
|
},
|
|
181
194
|
},
|
|
182
195
|
];
|
package/src/extensions/math.js
CHANGED
|
@@ -23,7 +23,9 @@ export const EnsureTextAfterMathPlugin = (mathNodeName) =>
|
|
|
23
23
|
key: ensureTextAfterMathPluginKey,
|
|
24
24
|
appendTransaction: (transactions, oldState, newState) => {
|
|
25
25
|
// Only act when the doc actually changed
|
|
26
|
-
if (!transactions.some((tr) => tr.docChanged))
|
|
26
|
+
if (!transactions.some((tr) => tr.docChanged)) {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
27
29
|
|
|
28
30
|
const tr = newState.tr;
|
|
29
31
|
let changed = false;
|
|
@@ -202,6 +204,7 @@ export const MathNodeView = (props) => {
|
|
|
202
204
|
const { node, updateAttributes, editor, selected, options } = props;
|
|
203
205
|
const [showToolbar, setShowToolbar] = useState(selected);
|
|
204
206
|
const toolbarRef = useRef(null);
|
|
207
|
+
const nodeRef = useRef(null);
|
|
205
208
|
const timestamp = useRef(Date.now());
|
|
206
209
|
const [position, setPosition] = useState({ top: 0, left: 0 });
|
|
207
210
|
const { math: mathOptions = {} } = options || {};
|
|
@@ -232,28 +235,102 @@ export const MathNodeView = (props) => {
|
|
|
232
235
|
editor.commands.focus();
|
|
233
236
|
};
|
|
234
237
|
|
|
238
|
+
// Only open the toolbar when this node is *explicitly* selected
|
|
239
|
+
// via a NodeSelection — not when it's merely included in a broader
|
|
240
|
+
// TextSelection or AllSelection (e.g. click-drag across math, or Cmd+A).
|
|
235
241
|
useEffect(() => {
|
|
236
|
-
if (selected)
|
|
242
|
+
if (!selected) return;
|
|
243
|
+
|
|
244
|
+
const { selection } = editor.state;
|
|
245
|
+
const isNodeSelected = selection.node?.type?.name === 'math';
|
|
246
|
+
|
|
247
|
+
if (isNodeSelected) {
|
|
237
248
|
setShowToolbar(true);
|
|
238
249
|
}
|
|
239
|
-
}, [selected]);
|
|
250
|
+
}, [selected, editor]);
|
|
240
251
|
|
|
241
252
|
useEffect(() => {
|
|
242
253
|
setToolbarOpened(editor, selected || showToolbar);
|
|
243
254
|
}, [editor, showToolbar, selected]);
|
|
244
255
|
|
|
245
256
|
useEffect(() => {
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
257
|
+
if (!editor || !showToolbar) {
|
|
258
|
+
setPosition({ top: 0, left: 0 });
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Clamp in viewport coordinates, then convert to portal-container-relative values
|
|
263
|
+
// for position: absolute (toolbar is portaled into _tiptapContainerEl or document.body).
|
|
264
|
+
const updatePosition = () => {
|
|
265
|
+
if (!toolbarRef.current) {
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const { from } = editor.state.selection;
|
|
270
|
+
const start = editor.view.coordsAtPos(from);
|
|
271
|
+
const nodeRect = nodeRef.current?.getBoundingClientRect?.();
|
|
272
|
+
|
|
273
|
+
// Anchor to the math node element when available; fall back to selection coords.
|
|
274
|
+
const anchorTop = nodeRect?.height ? nodeRect.top : start.top;
|
|
275
|
+
const anchorLeft = nodeRect?.width ? nodeRect.left : start.left;
|
|
276
|
+
const anchorBottom = nodeRect?.height ? nodeRect.bottom : (start.bottom ?? start.top);
|
|
277
|
+
|
|
278
|
+
const toolbarHeight = toolbarRef.current.offsetHeight;
|
|
279
|
+
const toolbarWidth = toolbarRef.current.offsetWidth;
|
|
280
|
+
|
|
281
|
+
const gap = 0;
|
|
282
|
+
const spaceBelow = window.innerHeight - (anchorBottom + gap);
|
|
283
|
+
|
|
284
|
+
// Place the toolbar's top-left corner directly below the anchor; flip above when needed.
|
|
285
|
+
let top = spaceBelow >= toolbarHeight ? anchorBottom + gap : anchorTop - toolbarHeight - gap;
|
|
286
|
+
let left = anchorLeft;
|
|
287
|
+
|
|
288
|
+
const margin = 8;
|
|
289
|
+
top = Math.max(margin, Math.min(top, window.innerHeight - toolbarHeight - margin));
|
|
290
|
+
left = Math.max(margin, Math.min(left, window.innerWidth - toolbarWidth - margin));
|
|
291
|
+
|
|
292
|
+
const portalEl = editor._tiptapContainerEl || document.body;
|
|
293
|
+
const containerRect = portalEl.getBoundingClientRect();
|
|
294
|
+
|
|
295
|
+
setPosition({
|
|
296
|
+
top: top - containerRect.top,
|
|
297
|
+
left: left - containerRect.left,
|
|
298
|
+
});
|
|
299
|
+
};
|
|
300
|
+
|
|
301
|
+
updatePosition();
|
|
302
|
+
|
|
303
|
+
let frame = null;
|
|
304
|
+
const scheduleUpdate = () => {
|
|
305
|
+
if (frame !== null) {
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
frame = requestAnimationFrame(() => {
|
|
310
|
+
frame = null;
|
|
311
|
+
updatePosition();
|
|
312
|
+
});
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
frame = requestAnimationFrame(() => {
|
|
316
|
+
frame = null;
|
|
317
|
+
updatePosition();
|
|
255
318
|
});
|
|
256
319
|
|
|
320
|
+
window.addEventListener('scroll', scheduleUpdate, true);
|
|
321
|
+
window.addEventListener('resize', scheduleUpdate);
|
|
322
|
+
|
|
323
|
+
return () => {
|
|
324
|
+
if (frame !== null) {
|
|
325
|
+
cancelAnimationFrame(frame);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
window.removeEventListener('scroll', scheduleUpdate, true);
|
|
329
|
+
window.removeEventListener('resize', scheduleUpdate);
|
|
330
|
+
};
|
|
331
|
+
}, [editor, showToolbar]);
|
|
332
|
+
|
|
333
|
+
useEffect(() => {
|
|
257
334
|
const handleClickOutside = (event) => {
|
|
258
335
|
const target = event?.target;
|
|
259
336
|
|
|
@@ -298,7 +375,7 @@ export const MathNodeView = (props) => {
|
|
|
298
375
|
}
|
|
299
376
|
|
|
300
377
|
return () => document.removeEventListener('click', handleClickOutside);
|
|
301
|
-
}, [editor, showToolbar]);
|
|
378
|
+
}, [editor, showToolbar, node]);
|
|
302
379
|
|
|
303
380
|
return (
|
|
304
381
|
<NodeViewWrapper
|
|
@@ -310,7 +387,7 @@ export const MathNodeView = (props) => {
|
|
|
310
387
|
}}
|
|
311
388
|
data-selected={selected}
|
|
312
389
|
>
|
|
313
|
-
<div onClick={() => setShowToolbar(true)} contentEditable={false}>
|
|
390
|
+
<div ref={nodeRef} onClick={() => setShowToolbar(true)} contentEditable={false}>
|
|
314
391
|
<MathPreview latex={latex} />
|
|
315
392
|
</div>
|
|
316
393
|
{showToolbar &&
|
|
@@ -322,7 +399,7 @@ export const MathNodeView = (props) => {
|
|
|
322
399
|
position: 'absolute',
|
|
323
400
|
top: `${position.top}px`,
|
|
324
401
|
left: `${position.left}px`,
|
|
325
|
-
zIndex:
|
|
402
|
+
zIndex: 1000,
|
|
326
403
|
background: 'var(--editable-html-toolbar-bg, #efefef)',
|
|
327
404
|
boxShadow:
|
|
328
405
|
'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)',
|