@pixerate/editor 0.13.0 → 0.14.1
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/grammar/index.cjs +45 -58
- package/dist/grammar/index.cjs.map +1 -1
- package/dist/grammar/index.d.cts +13 -3
- package/dist/grammar/index.d.ts +13 -3
- package/dist/grammar/index.js +42 -58
- package/dist/grammar/index.js.map +1 -1
- package/dist/index.cjs +162 -101
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +9 -1
- package/dist/index.d.ts +9 -1
- package/dist/index.js +164 -102
- package/dist/index.js.map +1 -1
- package/dist/serializers/index.cjs +83 -25
- package/dist/serializers/index.cjs.map +1 -1
- package/dist/serializers/index.d.cts +1 -0
- package/dist/serializers/index.d.ts +1 -0
- package/dist/serializers/index.js +83 -25
- package/dist/serializers/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -66,6 +66,8 @@ __export(src_exports, {
|
|
|
66
66
|
SmilieReplacer: () => SmilieReplacer,
|
|
67
67
|
SpreadsheetController: () => SpreadsheetController,
|
|
68
68
|
StreamingLayoutManager: () => StreamingLayoutManager,
|
|
69
|
+
TEMPLATE_DEPTH_ERROR: () => TEMPLATE_DEPTH_ERROR,
|
|
70
|
+
TEMPLATE_LOOP_ERROR: () => TEMPLATE_LOOP_ERROR,
|
|
69
71
|
TEMPLATE_REGEX: () => TEMPLATE_REGEX,
|
|
70
72
|
TemplateSuggestions: () => TemplateSuggestions,
|
|
71
73
|
VARIABLE_REGEX: () => VARIABLE_REGEX,
|
|
@@ -111,6 +113,7 @@ __export(src_exports, {
|
|
|
111
113
|
getEditorText: () => getEditorText,
|
|
112
114
|
getLatestVersion: () => getLatestVersion,
|
|
113
115
|
getLayoutedNodes: () => getLayoutedNodes,
|
|
116
|
+
getTemplateBody: () => getTemplateBody,
|
|
114
117
|
getViewportMetrics: () => getViewportMetrics,
|
|
115
118
|
globalImageCache: () => globalImageCache,
|
|
116
119
|
handleImageInsertion: () => handleImageInsertion,
|
|
@@ -280,6 +283,18 @@ function getLatestVersion(template) {
|
|
|
280
283
|
}
|
|
281
284
|
return template.versions[template.versions.length - 1];
|
|
282
285
|
}
|
|
286
|
+
var TEMPLATE_LOOP_ERROR = "[Template loop detected]";
|
|
287
|
+
var TEMPLATE_DEPTH_ERROR = "[Template max depth exceeded]";
|
|
288
|
+
function getTemplateBody(template, bindings) {
|
|
289
|
+
const boundVersionId = bindings?.[template.name];
|
|
290
|
+
if (boundVersionId && template.versions) {
|
|
291
|
+
const boundVersion = template.versions.find(
|
|
292
|
+
(v) => v.id === boundVersionId || v.version === boundVersionId
|
|
293
|
+
);
|
|
294
|
+
return boundVersion?.body;
|
|
295
|
+
}
|
|
296
|
+
return getLatestVersion(template)?.body ?? template.body;
|
|
297
|
+
}
|
|
283
298
|
function resolveTemplates(text, templates = [], options) {
|
|
284
299
|
const templateMap = /* @__PURE__ */ new Map();
|
|
285
300
|
for (const t of templates) {
|
|
@@ -287,50 +302,27 @@ function resolveTemplates(text, templates = [], options) {
|
|
|
287
302
|
}
|
|
288
303
|
const maxDepth = options?.maxDepth ?? 10;
|
|
289
304
|
function resolveRecursive(currentText, visited, depth) {
|
|
290
|
-
if (depth > maxDepth) {
|
|
291
|
-
return "[Template max depth exceeded]";
|
|
292
|
-
}
|
|
293
|
-
let result = currentText;
|
|
294
305
|
const regex = new RegExp(TEMPLATE_REGEX.source, TEMPLATE_REGEX.flags);
|
|
295
|
-
|
|
296
|
-
while ((match = regex.exec(result)) !== null) {
|
|
297
|
-
const [fullMatch, rawTemplateName] = match;
|
|
306
|
+
return currentText.replace(regex, (fullMatch, rawTemplateName) => {
|
|
298
307
|
const templateName = rawTemplateName.trim();
|
|
299
308
|
if (visited.has(templateName)) {
|
|
300
|
-
return
|
|
309
|
+
return TEMPLATE_LOOP_ERROR;
|
|
301
310
|
}
|
|
302
311
|
const template = templateMap.get(templateName);
|
|
303
312
|
if (!template) {
|
|
304
|
-
|
|
305
|
-
}
|
|
306
|
-
let versionBody;
|
|
307
|
-
const boundVersionId = options?.bindings?.[templateName];
|
|
308
|
-
if (boundVersionId && template.versions) {
|
|
309
|
-
const boundVersion = template.versions.find(
|
|
310
|
-
(v) => v.id === boundVersionId || v.version === boundVersionId
|
|
311
|
-
);
|
|
312
|
-
versionBody = boundVersion?.body;
|
|
313
|
-
} else {
|
|
314
|
-
const latest = getLatestVersion(template);
|
|
315
|
-
versionBody = latest?.body ?? template.body;
|
|
313
|
+
return fullMatch;
|
|
316
314
|
}
|
|
315
|
+
const versionBody = getTemplateBody(template, options?.bindings);
|
|
317
316
|
if (versionBody === void 0) {
|
|
318
|
-
|
|
317
|
+
return fullMatch;
|
|
318
|
+
}
|
|
319
|
+
if (depth + 1 > maxDepth) {
|
|
320
|
+
return TEMPLATE_DEPTH_ERROR;
|
|
319
321
|
}
|
|
320
322
|
const nextVisited = new Set(visited);
|
|
321
323
|
nextVisited.add(templateName);
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
nextVisited,
|
|
325
|
-
depth + 1
|
|
326
|
-
);
|
|
327
|
-
if (resolvedBody === "[Template loop detected]" || resolvedBody === "[Template max depth exceeded]") {
|
|
328
|
-
return resolvedBody;
|
|
329
|
-
}
|
|
330
|
-
result = result.replace(fullMatch, resolvedBody);
|
|
331
|
-
regex.lastIndex = 0;
|
|
332
|
-
}
|
|
333
|
-
return result;
|
|
324
|
+
return resolveRecursive(versionBody, nextVisited, depth + 1);
|
|
325
|
+
});
|
|
334
326
|
}
|
|
335
327
|
return resolveRecursive(text, /* @__PURE__ */ new Set(), 0);
|
|
336
328
|
}
|
|
@@ -379,7 +371,7 @@ function extractInstructionNames(text) {
|
|
|
379
371
|
}
|
|
380
372
|
|
|
381
373
|
// src/grammar/sourcemap.ts
|
|
382
|
-
function resolveTemplatesWithMapping(text, templates = [], templateBindings) {
|
|
374
|
+
function resolveTemplatesWithMapping(text, templates = [], templateBindings, options) {
|
|
383
375
|
if (!templates || templates.length === 0 || !text.includes("{{")) {
|
|
384
376
|
return {
|
|
385
377
|
text,
|
|
@@ -395,7 +387,7 @@ function resolveTemplatesWithMapping(text, templates = [], templateBindings) {
|
|
|
395
387
|
};
|
|
396
388
|
}
|
|
397
389
|
const templateMap = new Map(templates.map((t) => [t.name, t]));
|
|
398
|
-
const regex =
|
|
390
|
+
const regex = new RegExp(TEMPLATE_REGEX.source, TEMPLATE_REGEX.flags);
|
|
399
391
|
let match;
|
|
400
392
|
let lastIndex = 0;
|
|
401
393
|
let resolvedText = "";
|
|
@@ -417,25 +409,13 @@ function resolveTemplatesWithMapping(text, templates = [], templateBindings) {
|
|
|
417
409
|
}
|
|
418
410
|
let resolvedBody = fullMatch;
|
|
419
411
|
let isTemplate = false;
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
);
|
|
428
|
-
versionBody = boundVersion?.body;
|
|
429
|
-
} else {
|
|
430
|
-
const latestVersion = getLatestVersion(template);
|
|
431
|
-
versionBody = latestVersion?.body ?? template.body;
|
|
432
|
-
}
|
|
433
|
-
if (versionBody !== void 0) {
|
|
434
|
-
resolvedBody = resolveTemplates(versionBody, templates, {
|
|
435
|
-
bindings: templateBindings
|
|
436
|
-
});
|
|
437
|
-
isTemplate = true;
|
|
438
|
-
}
|
|
412
|
+
const template = templateMap.get(templateName);
|
|
413
|
+
if (template && getTemplateBody(template, templateBindings) !== void 0) {
|
|
414
|
+
resolvedBody = resolveTemplates(fullMatch, templates, {
|
|
415
|
+
bindings: templateBindings,
|
|
416
|
+
maxDepth: options?.maxDepth
|
|
417
|
+
});
|
|
418
|
+
isTemplate = true;
|
|
439
419
|
}
|
|
440
420
|
if (isTemplate) {
|
|
441
421
|
map.push({
|
|
@@ -480,9 +460,11 @@ function mapResolvedOffsetToRaw(resolvedOffset, map) {
|
|
|
480
460
|
if (inRange) {
|
|
481
461
|
if (seg.source === "raw") {
|
|
482
462
|
return seg.rawStart + (resolvedOffset - seg.resolvedStart);
|
|
483
|
-
} else {
|
|
484
|
-
return seg.rawStart;
|
|
485
463
|
}
|
|
464
|
+
if (resolvedOffset === seg.resolvedEnd) {
|
|
465
|
+
return seg.rawEnd;
|
|
466
|
+
}
|
|
467
|
+
return seg.rawStart;
|
|
486
468
|
}
|
|
487
469
|
}
|
|
488
470
|
return resolvedOffset;
|
|
@@ -495,9 +477,11 @@ function mapRawOffsetToResolved(rawOffset, map) {
|
|
|
495
477
|
if (inRange) {
|
|
496
478
|
if (seg.source === "raw") {
|
|
497
479
|
return seg.resolvedStart + (rawOffset - seg.rawStart);
|
|
498
|
-
} else {
|
|
499
|
-
return seg.resolvedStart;
|
|
500
480
|
}
|
|
481
|
+
if (rawOffset === seg.rawEnd) {
|
|
482
|
+
return seg.resolvedEnd;
|
|
483
|
+
}
|
|
484
|
+
return seg.resolvedStart;
|
|
501
485
|
}
|
|
502
486
|
}
|
|
503
487
|
return rawOffset;
|
|
@@ -2508,38 +2492,98 @@ function escapeHtml(text) {
|
|
|
2508
2492
|
if (!text) return "";
|
|
2509
2493
|
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
2510
2494
|
}
|
|
2495
|
+
var PARAGRAPH_HTML_REGEX = /^\s*<p[\s>][\s\S]*<\/p>\s*$/i;
|
|
2511
2496
|
function plainTextToTipTapHtml(text) {
|
|
2512
2497
|
if (!text) return "<p></p>";
|
|
2513
|
-
if (
|
|
2498
|
+
if (PARAGRAPH_HTML_REGEX.test(text)) {
|
|
2514
2499
|
return text;
|
|
2515
2500
|
}
|
|
2516
2501
|
return text.replace(/\r\n/g, "\n").replace(/\r/g, "\n").split("\n").map((line) => `<p>${escapeHtml(line)}</p>`).join("");
|
|
2517
2502
|
}
|
|
2503
|
+
var BLOCK_TAGS = /* @__PURE__ */ new Set([
|
|
2504
|
+
"P",
|
|
2505
|
+
"DIV",
|
|
2506
|
+
"LI",
|
|
2507
|
+
"UL",
|
|
2508
|
+
"OL",
|
|
2509
|
+
"PRE",
|
|
2510
|
+
"BLOCKQUOTE",
|
|
2511
|
+
"TABLE",
|
|
2512
|
+
"TR",
|
|
2513
|
+
"H1",
|
|
2514
|
+
"H2",
|
|
2515
|
+
"H3",
|
|
2516
|
+
"H4",
|
|
2517
|
+
"H5",
|
|
2518
|
+
"H6",
|
|
2519
|
+
"HR"
|
|
2520
|
+
]);
|
|
2521
|
+
var MENTION_SPAN_REGEX = /<span\b(?=[^>]*\bdata-type=["']mention["'])([^>]*)>[\s\S]*?<\/span>/gi;
|
|
2522
|
+
function getHtmlAttribute(attrs, name) {
|
|
2523
|
+
const match = new RegExp(`\\b${name}=(?:"([^"]*)"|'([^']*)')`, "i").exec(
|
|
2524
|
+
attrs
|
|
2525
|
+
);
|
|
2526
|
+
return match ? match[1] ?? match[2] : void 0;
|
|
2527
|
+
}
|
|
2528
|
+
function parseHtmlFragment(html) {
|
|
2529
|
+
const template = document.createElement("template");
|
|
2530
|
+
template.innerHTML = html;
|
|
2531
|
+
return template.content;
|
|
2532
|
+
}
|
|
2533
|
+
function decodeHtmlEntities(text) {
|
|
2534
|
+
return text.replace(/</g, "<").replace(/>/g, ">").replace(/"/g, '"').replace(/�*39;/g, "'").replace(/ /g, "\xA0").replace(/&/g, "&");
|
|
2535
|
+
}
|
|
2518
2536
|
function htmlToPlainText(html) {
|
|
2519
2537
|
if (!html) return "";
|
|
2520
|
-
const mentionNormalized = html.replace(
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
)
|
|
2524
|
-
/<span[^>]*data-type=["']mention["'][^>]*data-id=["']([^"']+)["'][^>]*>.*?<\/span>/gi,
|
|
2525
|
-
"@$1"
|
|
2526
|
-
);
|
|
2538
|
+
const mentionNormalized = html.replace(MENTION_SPAN_REGEX, (match, attrs) => {
|
|
2539
|
+
const label = getHtmlAttribute(attrs, "data-label") || getHtmlAttribute(attrs, "data-id");
|
|
2540
|
+
return label ? `@${label}` : match;
|
|
2541
|
+
});
|
|
2527
2542
|
if (typeof document !== "undefined") {
|
|
2528
|
-
const
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
const
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
+
const fragment = parseHtmlFragment(mentionNormalized);
|
|
2544
|
+
fragment.querySelectorAll("br.ProseMirror-trailingBreak").forEach((br) => br.remove());
|
|
2545
|
+
const lines = [];
|
|
2546
|
+
let current = null;
|
|
2547
|
+
const flush = () => {
|
|
2548
|
+
if (current !== null) {
|
|
2549
|
+
lines.push(current);
|
|
2550
|
+
current = null;
|
|
2551
|
+
}
|
|
2552
|
+
};
|
|
2553
|
+
const visit = (node) => {
|
|
2554
|
+
if (node.nodeType === 3) {
|
|
2555
|
+
const value = node.nodeValue ?? "";
|
|
2556
|
+
if (current === null && value.trim() === "") return;
|
|
2557
|
+
current = (current ?? "") + value;
|
|
2558
|
+
return;
|
|
2559
|
+
}
|
|
2560
|
+
if (node.nodeType !== 1) return;
|
|
2561
|
+
const tag = node.tagName.toUpperCase();
|
|
2562
|
+
if (tag === "BR") {
|
|
2563
|
+
current = (current ?? "") + "\n";
|
|
2564
|
+
return;
|
|
2565
|
+
}
|
|
2566
|
+
if (!BLOCK_TAGS.has(tag)) {
|
|
2567
|
+
node.childNodes.forEach(visit);
|
|
2568
|
+
return;
|
|
2569
|
+
}
|
|
2570
|
+
flush();
|
|
2571
|
+
const linesBefore = lines.length;
|
|
2572
|
+
node.childNodes.forEach(visit);
|
|
2573
|
+
flush();
|
|
2574
|
+
if (lines.length === linesBefore && tag !== "HR") {
|
|
2575
|
+
lines.push("");
|
|
2576
|
+
}
|
|
2577
|
+
};
|
|
2578
|
+
fragment.childNodes.forEach(visit);
|
|
2579
|
+
flush();
|
|
2580
|
+
return lines.join("\n");
|
|
2581
|
+
}
|
|
2582
|
+
const text = mentionNormalized.replace(/<br\s*\/?>/gi, "\n").replace(
|
|
2583
|
+
/<\/(?:p|div|li|pre|blockquote|h[1-6])>(?:\s*<\/(?:li|ul|ol|blockquote|div)>)*/gi,
|
|
2584
|
+
"\n"
|
|
2585
|
+
).replace(/<[^>]+>/g, "");
|
|
2586
|
+
return decodeHtmlEntities(text).replace(/\n$/, "");
|
|
2543
2587
|
}
|
|
2544
2588
|
function getEditorText(editor) {
|
|
2545
2589
|
if (!editor) return "";
|
|
@@ -2598,9 +2642,7 @@ function extractMentionsFromDoc(docOrEditor) {
|
|
|
2598
2642
|
}
|
|
2599
2643
|
if (typeof docOrEditor === "string") {
|
|
2600
2644
|
if (typeof document !== "undefined") {
|
|
2601
|
-
|
|
2602
|
-
div.innerHTML = docOrEditor;
|
|
2603
|
-
div.querySelectorAll('span[data-type="mention"]').forEach((el) => {
|
|
2645
|
+
parseHtmlFragment(docOrEditor).querySelectorAll('span[data-type="mention"]').forEach((el) => {
|
|
2604
2646
|
mentions.push({
|
|
2605
2647
|
id: el.getAttribute("data-id") || "",
|
|
2606
2648
|
label: el.getAttribute("data-label") || "",
|
|
@@ -2693,6 +2735,20 @@ function getEditorMarkdown(editor) {
|
|
|
2693
2735
|
// src/controller.ts
|
|
2694
2736
|
var import_core10 = require("@tiptap/core");
|
|
2695
2737
|
var import_model2 = require("@tiptap/pm/model");
|
|
2738
|
+
function plainTextToSlice(text, schema) {
|
|
2739
|
+
const nodes = [];
|
|
2740
|
+
text.replace(/\r\n?/g, "\n").split("\n").forEach((line) => {
|
|
2741
|
+
const nodeJson = { type: "paragraph" };
|
|
2742
|
+
if (line.length > 0) {
|
|
2743
|
+
nodeJson.content = [{ type: "text", text: line }];
|
|
2744
|
+
}
|
|
2745
|
+
try {
|
|
2746
|
+
nodes.push(import_model2.Node.fromJSON(schema, nodeJson));
|
|
2747
|
+
} catch {
|
|
2748
|
+
}
|
|
2749
|
+
});
|
|
2750
|
+
return import_model2.Slice.maxOpen(import_model2.Fragment.fromArray(nodes));
|
|
2751
|
+
}
|
|
2696
2752
|
var EditorController = class {
|
|
2697
2753
|
editor;
|
|
2698
2754
|
plainTextMode;
|
|
@@ -2748,20 +2804,7 @@ var EditorController = class {
|
|
|
2748
2804
|
clipboardTextParser: (text, context, _plain, view) => {
|
|
2749
2805
|
const schema = context?.doc?.type?.schema || context?.schema || view?.state?.schema || this.editor.schema;
|
|
2750
2806
|
if (!schema) return import_model2.Slice.empty;
|
|
2751
|
-
|
|
2752
|
-
const nodes = [];
|
|
2753
|
-
blocks.forEach((line) => {
|
|
2754
|
-
const nodeJson = { type: "paragraph" };
|
|
2755
|
-
if (line.length > 0) {
|
|
2756
|
-
nodeJson.content = [{ type: "text", text: line }];
|
|
2757
|
-
}
|
|
2758
|
-
try {
|
|
2759
|
-
const node = import_model2.Node.fromJSON(schema, nodeJson);
|
|
2760
|
-
nodes.push(node);
|
|
2761
|
-
} catch {
|
|
2762
|
-
}
|
|
2763
|
-
});
|
|
2764
|
-
return import_model2.Slice.maxOpen(import_model2.Fragment.fromArray(nodes));
|
|
2807
|
+
return plainTextToSlice(text, schema);
|
|
2765
2808
|
}
|
|
2766
2809
|
}
|
|
2767
2810
|
});
|
|
@@ -2804,14 +2847,29 @@ var EditorController = class {
|
|
|
2804
2847
|
this.editor.commands.setContent(html, emitUpdate);
|
|
2805
2848
|
}
|
|
2806
2849
|
}
|
|
2850
|
+
/**
|
|
2851
|
+
* Replaces `from`..`to` with literal text: HTML in `text` is not parsed, and
|
|
2852
|
+
* newlines split paragraphs.
|
|
2853
|
+
*/
|
|
2854
|
+
insertPlainText(from, to, text) {
|
|
2855
|
+
const { state, view } = this.editor;
|
|
2856
|
+
const tr = text.includes("\n") || text.includes("\r") ? state.tr.replace(from, to, plainTextToSlice(text, state.schema)) : text ? state.tr.insertText(text, from, to) : state.tr.delete(from, to);
|
|
2857
|
+
view.dispatch(tr.scrollIntoView());
|
|
2858
|
+
}
|
|
2859
|
+
/** Inserts text literally at the selection; HTML in `text` is not parsed. */
|
|
2807
2860
|
insertText(text) {
|
|
2808
|
-
this.editor.
|
|
2861
|
+
const { from, to } = this.editor.state.selection;
|
|
2862
|
+
this.insertPlainText(from, to, text);
|
|
2809
2863
|
}
|
|
2864
|
+
/** Inserts text literally at `pos`; HTML in `text` is not parsed. */
|
|
2810
2865
|
insertTextAt(pos, text) {
|
|
2811
|
-
this.
|
|
2866
|
+
this.insertPlainText(pos, pos, text);
|
|
2867
|
+
this.editor.commands.focus();
|
|
2812
2868
|
}
|
|
2869
|
+
/** Replaces a range with literal text; HTML in `text` is not parsed. */
|
|
2813
2870
|
replaceRange(from, to, text) {
|
|
2814
|
-
this.
|
|
2871
|
+
this.insertPlainText(from, to, text);
|
|
2872
|
+
this.editor.commands.focus();
|
|
2815
2873
|
}
|
|
2816
2874
|
getCurrentContentString() {
|
|
2817
2875
|
if (this.plainTextMode) return this.getPlainText();
|
|
@@ -7025,6 +7083,8 @@ function hitTestAnnotation(imgPt, ann, padding = 10) {
|
|
|
7025
7083
|
SmilieReplacer,
|
|
7026
7084
|
SpreadsheetController,
|
|
7027
7085
|
StreamingLayoutManager,
|
|
7086
|
+
TEMPLATE_DEPTH_ERROR,
|
|
7087
|
+
TEMPLATE_LOOP_ERROR,
|
|
7028
7088
|
TEMPLATE_REGEX,
|
|
7029
7089
|
TemplateSuggestions,
|
|
7030
7090
|
VARIABLE_REGEX,
|
|
@@ -7070,6 +7130,7 @@ function hitTestAnnotation(imgPt, ann, padding = 10) {
|
|
|
7070
7130
|
getEditorText,
|
|
7071
7131
|
getLatestVersion,
|
|
7072
7132
|
getLayoutedNodes,
|
|
7133
|
+
getTemplateBody,
|
|
7073
7134
|
getViewportMetrics,
|
|
7074
7135
|
globalImageCache,
|
|
7075
7136
|
handleImageInsertion,
|