@pixerate/editor 0.14.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/index.cjs +117 -43
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +8 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +122 -44
- 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
|
@@ -2492,38 +2492,98 @@ function escapeHtml(text) {
|
|
|
2492
2492
|
if (!text) return "";
|
|
2493
2493
|
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
2494
2494
|
}
|
|
2495
|
+
var PARAGRAPH_HTML_REGEX = /^\s*<p[\s>][\s\S]*<\/p>\s*$/i;
|
|
2495
2496
|
function plainTextToTipTapHtml(text) {
|
|
2496
2497
|
if (!text) return "<p></p>";
|
|
2497
|
-
if (
|
|
2498
|
+
if (PARAGRAPH_HTML_REGEX.test(text)) {
|
|
2498
2499
|
return text;
|
|
2499
2500
|
}
|
|
2500
2501
|
return text.replace(/\r\n/g, "\n").replace(/\r/g, "\n").split("\n").map((line) => `<p>${escapeHtml(line)}</p>`).join("");
|
|
2501
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
|
+
}
|
|
2502
2536
|
function htmlToPlainText(html) {
|
|
2503
2537
|
if (!html) return "";
|
|
2504
|
-
const mentionNormalized = html.replace(
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
)
|
|
2508
|
-
/<span[^>]*data-type=["']mention["'][^>]*data-id=["']([^"']+)["'][^>]*>.*?<\/span>/gi,
|
|
2509
|
-
"@$1"
|
|
2510
|
-
);
|
|
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
|
+
});
|
|
2511
2542
|
if (typeof document !== "undefined") {
|
|
2512
|
-
const
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
const
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
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$/, "");
|
|
2527
2587
|
}
|
|
2528
2588
|
function getEditorText(editor) {
|
|
2529
2589
|
if (!editor) return "";
|
|
@@ -2582,9 +2642,7 @@ function extractMentionsFromDoc(docOrEditor) {
|
|
|
2582
2642
|
}
|
|
2583
2643
|
if (typeof docOrEditor === "string") {
|
|
2584
2644
|
if (typeof document !== "undefined") {
|
|
2585
|
-
|
|
2586
|
-
div.innerHTML = docOrEditor;
|
|
2587
|
-
div.querySelectorAll('span[data-type="mention"]').forEach((el) => {
|
|
2645
|
+
parseHtmlFragment(docOrEditor).querySelectorAll('span[data-type="mention"]').forEach((el) => {
|
|
2588
2646
|
mentions.push({
|
|
2589
2647
|
id: el.getAttribute("data-id") || "",
|
|
2590
2648
|
label: el.getAttribute("data-label") || "",
|
|
@@ -2677,6 +2735,20 @@ function getEditorMarkdown(editor) {
|
|
|
2677
2735
|
// src/controller.ts
|
|
2678
2736
|
var import_core10 = require("@tiptap/core");
|
|
2679
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
|
+
}
|
|
2680
2752
|
var EditorController = class {
|
|
2681
2753
|
editor;
|
|
2682
2754
|
plainTextMode;
|
|
@@ -2732,20 +2804,7 @@ var EditorController = class {
|
|
|
2732
2804
|
clipboardTextParser: (text, context, _plain, view) => {
|
|
2733
2805
|
const schema = context?.doc?.type?.schema || context?.schema || view?.state?.schema || this.editor.schema;
|
|
2734
2806
|
if (!schema) return import_model2.Slice.empty;
|
|
2735
|
-
|
|
2736
|
-
const nodes = [];
|
|
2737
|
-
blocks.forEach((line) => {
|
|
2738
|
-
const nodeJson = { type: "paragraph" };
|
|
2739
|
-
if (line.length > 0) {
|
|
2740
|
-
nodeJson.content = [{ type: "text", text: line }];
|
|
2741
|
-
}
|
|
2742
|
-
try {
|
|
2743
|
-
const node = import_model2.Node.fromJSON(schema, nodeJson);
|
|
2744
|
-
nodes.push(node);
|
|
2745
|
-
} catch {
|
|
2746
|
-
}
|
|
2747
|
-
});
|
|
2748
|
-
return import_model2.Slice.maxOpen(import_model2.Fragment.fromArray(nodes));
|
|
2807
|
+
return plainTextToSlice(text, schema);
|
|
2749
2808
|
}
|
|
2750
2809
|
}
|
|
2751
2810
|
});
|
|
@@ -2788,14 +2847,29 @@ var EditorController = class {
|
|
|
2788
2847
|
this.editor.commands.setContent(html, emitUpdate);
|
|
2789
2848
|
}
|
|
2790
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. */
|
|
2791
2860
|
insertText(text) {
|
|
2792
|
-
this.editor.
|
|
2861
|
+
const { from, to } = this.editor.state.selection;
|
|
2862
|
+
this.insertPlainText(from, to, text);
|
|
2793
2863
|
}
|
|
2864
|
+
/** Inserts text literally at `pos`; HTML in `text` is not parsed. */
|
|
2794
2865
|
insertTextAt(pos, text) {
|
|
2795
|
-
this.
|
|
2866
|
+
this.insertPlainText(pos, pos, text);
|
|
2867
|
+
this.editor.commands.focus();
|
|
2796
2868
|
}
|
|
2869
|
+
/** Replaces a range with literal text; HTML in `text` is not parsed. */
|
|
2797
2870
|
replaceRange(from, to, text) {
|
|
2798
|
-
this.
|
|
2871
|
+
this.insertPlainText(from, to, text);
|
|
2872
|
+
this.editor.commands.focus();
|
|
2799
2873
|
}
|
|
2800
2874
|
getCurrentContentString() {
|
|
2801
2875
|
if (this.plainTextMode) return this.getPlainText();
|