magic-editor-x 1.1.1 → 1.3.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/README.md +82 -5
- package/dist/_chunks/{App-mtrlABtd.js → App-CWXreQMz.js} +4 -4
- package/dist/_chunks/{App-B1FgOsWa.mjs → App-Czke2os9.mjs} +4 -4
- package/dist/_chunks/{LicensePage-BnyWSrWs.js → LicensePage-BIaCAB4C.js} +2 -2
- package/dist/_chunks/{LicensePage-CWH-AFR-.mjs → LicensePage-Cj6-z6rO.mjs} +2 -2
- package/dist/_chunks/{LiveCollaborationPanel-DbDHwr2C.js → LiveCollaborationPanel-BmAFvNll.js} +1 -1
- package/dist/_chunks/{LiveCollaborationPanel-ryjcDAA7.mjs → LiveCollaborationPanel-D11eAcKk.mjs} +1 -1
- package/dist/_chunks/{Settings-Bk9bxJTy.js → Settings-BsoK7S5l.js} +1 -1
- package/dist/_chunks/{Settings-D-V2MLVm.mjs → Settings-CsaF0hO7.mjs} +1 -1
- package/dist/_chunks/{de-CSrHZWEb.mjs → de-C_0Mj-Zo.mjs} +1 -0
- package/dist/_chunks/{de-CzSo1oD2.js → de-DVNVpAt9.js} +1 -0
- package/dist/_chunks/{en-DuQun2v4.mjs → en-B7AAf1ie.mjs} +1 -0
- package/dist/_chunks/{en-DxIkVPUh.js → en-C2hv5GsA.js} +1 -0
- package/dist/_chunks/{es-DAQ_97zx.js → es-BzJqcIST.js} +1 -0
- package/dist/_chunks/{es-DEB0CA8S.mjs → es-zdP8sd-f.mjs} +1 -0
- package/dist/_chunks/{fr-Bqkhvdx2.mjs → fr-CtAgOgH1.mjs} +1 -0
- package/dist/_chunks/{fr-ChPabvNP.js → fr-D3GwqAAJ.js} +1 -0
- package/dist/_chunks/{getTranslation-C4uWR0DB.mjs → getTranslation-CZ77ytJY.mjs} +7 -6
- package/dist/_chunks/{getTranslation-D35vbDap.js → getTranslation-DB9tlKh9.js} +2 -1
- package/dist/_chunks/{index-B5MzUyo0.mjs → index-BNxjfrVK.mjs} +9 -9
- package/dist/_chunks/{index-CQx7-dFP.js → index-BmWx_nJX.js} +1099 -87
- package/dist/_chunks/{index-BRVqbnOb.mjs → index-Byp_IaHi.mjs} +1100 -88
- package/dist/_chunks/{index-BiLy_f7C.js → index-DO-QpiC9.js} +9 -9
- package/dist/_chunks/{pt-BMoYltav.mjs → pt-BmUw6YMP.mjs} +1 -0
- package/dist/_chunks/{pt-Cm74LpyZ.js → pt-CFo0nTbj.js} +1 -0
- package/dist/_chunks/{tools-DNt2tioN.js → tools-3bMqs3Or.js} +103 -4
- package/dist/_chunks/{tools-CjnQJ9w2.mjs → tools-CNqrBm-q.mjs} +103 -4
- package/dist/admin/index.js +1 -1
- package/dist/admin/index.mjs +1 -1
- package/dist/server/index.js +37242 -1292
- package/dist/server/index.mjs +37228 -1285
- package/package.json +1 -1
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
3
|
const jsxRuntime = require("react/jsx-runtime");
|
|
4
4
|
const React = require("react");
|
|
5
|
-
const getTranslation = require("./getTranslation-
|
|
5
|
+
const getTranslation = require("./getTranslation-DB9tlKh9.js");
|
|
6
6
|
const styled = require("styled-components");
|
|
7
7
|
const outline = require("@heroicons/react/24/outline");
|
|
8
8
|
const EditorJS = require("@editorjs/editorjs");
|
|
9
|
-
const tools = require("./tools-
|
|
9
|
+
const tools = require("./tools-3bMqs3Or.js");
|
|
10
10
|
const admin = require("@strapi/strapi/admin");
|
|
11
|
-
const index = require("./index-
|
|
11
|
+
const index = require("./index-DO-QpiC9.js");
|
|
12
12
|
const socket_ioClient = require("socket.io-client");
|
|
13
13
|
const Y = require("yjs");
|
|
14
14
|
const yIndexeddb = require("y-indexeddb");
|
|
@@ -448,6 +448,89 @@ const getUserColor = (userId) => {
|
|
|
448
448
|
const hash = String(userId).split("").reduce((acc, char) => acc + char.charCodeAt(0), 0);
|
|
449
449
|
return CURSOR_COLORS[hash % CURSOR_COLORS.length];
|
|
450
450
|
};
|
|
451
|
+
const htmlToDelta = (html) => {
|
|
452
|
+
if (!html || html === "<br>" || html === "<br/>") return [];
|
|
453
|
+
const parser = new DOMParser();
|
|
454
|
+
const doc = parser.parseFromString(`<div>${html}</div>`, "text/html");
|
|
455
|
+
const delta = [];
|
|
456
|
+
function processNode(node, attributes = {}) {
|
|
457
|
+
if (node.nodeType === Node.TEXT_NODE) {
|
|
458
|
+
const text = node.textContent;
|
|
459
|
+
if (text) {
|
|
460
|
+
const attrs = Object.keys(attributes).length > 0 ? { ...attributes } : void 0;
|
|
461
|
+
delta.push({ insert: text, attributes: attrs });
|
|
462
|
+
}
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
465
|
+
if (node.nodeType === Node.ELEMENT_NODE) {
|
|
466
|
+
const newAttrs = { ...attributes };
|
|
467
|
+
const tagName = node.tagName.toLowerCase();
|
|
468
|
+
switch (tagName) {
|
|
469
|
+
case "b":
|
|
470
|
+
case "strong":
|
|
471
|
+
newAttrs.bold = true;
|
|
472
|
+
break;
|
|
473
|
+
case "i":
|
|
474
|
+
case "em":
|
|
475
|
+
newAttrs.italic = true;
|
|
476
|
+
break;
|
|
477
|
+
case "u":
|
|
478
|
+
newAttrs.underline = true;
|
|
479
|
+
break;
|
|
480
|
+
case "code":
|
|
481
|
+
newAttrs.code = true;
|
|
482
|
+
break;
|
|
483
|
+
case "a":
|
|
484
|
+
newAttrs.a = {
|
|
485
|
+
href: node.getAttribute("href") || "",
|
|
486
|
+
target: node.getAttribute("target") || "_blank",
|
|
487
|
+
rel: node.getAttribute("rel") || "noopener noreferrer"
|
|
488
|
+
};
|
|
489
|
+
break;
|
|
490
|
+
case "mark":
|
|
491
|
+
newAttrs.mark = true;
|
|
492
|
+
break;
|
|
493
|
+
case "br":
|
|
494
|
+
delta.push({ insert: "\n" });
|
|
495
|
+
return;
|
|
496
|
+
}
|
|
497
|
+
for (const child of node.childNodes) {
|
|
498
|
+
processNode(child, newAttrs);
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
const wrapper = doc.body.firstChild;
|
|
503
|
+
if (wrapper) {
|
|
504
|
+
for (const child of wrapper.childNodes) {
|
|
505
|
+
processNode(child);
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
return delta;
|
|
509
|
+
};
|
|
510
|
+
const deltaToHtml = (delta) => {
|
|
511
|
+
if (!delta || delta.length === 0) return "";
|
|
512
|
+
let html = "";
|
|
513
|
+
for (const op of delta) {
|
|
514
|
+
if (typeof op.insert !== "string") continue;
|
|
515
|
+
let text = op.insert;
|
|
516
|
+
text = text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/\n/g, "<br>");
|
|
517
|
+
const attrs = op.attributes || {};
|
|
518
|
+
let result = text;
|
|
519
|
+
if (attrs.code) result = `<code>${result}</code>`;
|
|
520
|
+
if (attrs.italic) result = `<i>${result}</i>`;
|
|
521
|
+
if (attrs.bold) result = `<b>${result}</b>`;
|
|
522
|
+
if (attrs.underline) result = `<u>${result}</u>`;
|
|
523
|
+
if (attrs.mark) result = `<mark>${result}</mark>`;
|
|
524
|
+
if (attrs.a) {
|
|
525
|
+
const href = attrs.a.href || "";
|
|
526
|
+
const target = attrs.a.target || "_blank";
|
|
527
|
+
const rel = attrs.a.rel || "noopener noreferrer";
|
|
528
|
+
result = `<a href="${href}" target="${target}" rel="${rel}">${result}</a>`;
|
|
529
|
+
}
|
|
530
|
+
html += result;
|
|
531
|
+
}
|
|
532
|
+
return html;
|
|
533
|
+
};
|
|
451
534
|
const useMagicCollaboration = ({
|
|
452
535
|
enabled,
|
|
453
536
|
roomId,
|
|
@@ -472,16 +555,47 @@ const useMagicCollaboration = ({
|
|
|
472
555
|
React.useEffect(() => {
|
|
473
556
|
onRemoteUpdateRef.current = onRemoteUpdate;
|
|
474
557
|
}, [onRemoteUpdate]);
|
|
475
|
-
const { doc, blocksMap, metaMap } = React.useMemo(() => {
|
|
558
|
+
const { doc, blocksMap, textMap, metaMap } = React.useMemo(() => {
|
|
476
559
|
const yDoc = new Y__namespace.Doc();
|
|
477
560
|
return {
|
|
478
561
|
doc: yDoc,
|
|
479
562
|
blocksMap: yDoc.getMap("blocks"),
|
|
480
|
-
//
|
|
563
|
+
// Block metadata (type, tunes)
|
|
564
|
+
textMap: yDoc.getMap("text"),
|
|
565
|
+
// Y.Text per block (character-level sync!)
|
|
481
566
|
metaMap: yDoc.getMap("meta")
|
|
482
|
-
//
|
|
567
|
+
// Document metadata (time, blockOrder)
|
|
483
568
|
};
|
|
484
569
|
}, [roomId]);
|
|
570
|
+
const getBlockText = React.useCallback((blockId) => {
|
|
571
|
+
if (!blockId) return null;
|
|
572
|
+
let ytext = textMap.get(blockId);
|
|
573
|
+
if (!ytext) {
|
|
574
|
+
ytext = new Y__namespace.Text();
|
|
575
|
+
textMap.set(blockId, ytext);
|
|
576
|
+
}
|
|
577
|
+
return ytext;
|
|
578
|
+
}, [textMap]);
|
|
579
|
+
const setBlockText = React.useCallback((blockId, html) => {
|
|
580
|
+
if (!blockId) return;
|
|
581
|
+
const ytext = getBlockText(blockId);
|
|
582
|
+
if (!ytext) return;
|
|
583
|
+
doc.transact(() => {
|
|
584
|
+
if (ytext.length > 0) {
|
|
585
|
+
ytext.delete(0, ytext.length);
|
|
586
|
+
}
|
|
587
|
+
const delta = htmlToDelta(html);
|
|
588
|
+
if (delta.length > 0) {
|
|
589
|
+
ytext.applyDelta(delta);
|
|
590
|
+
}
|
|
591
|
+
}, "local");
|
|
592
|
+
}, [doc, getBlockText]);
|
|
593
|
+
const getBlockTextHtml = React.useCallback((blockId) => {
|
|
594
|
+
if (!blockId) return "";
|
|
595
|
+
const ytext = textMap.get(blockId);
|
|
596
|
+
if (!ytext) return "";
|
|
597
|
+
return deltaToHtml(ytext.toDelta());
|
|
598
|
+
}, [textMap]);
|
|
485
599
|
React.useEffect(() => {
|
|
486
600
|
return () => {
|
|
487
601
|
doc.destroy();
|
|
@@ -546,9 +660,6 @@ const useMagicCollaboration = ({
|
|
|
546
660
|
persistenceRef.current = persistence;
|
|
547
661
|
persistence.on("synced", () => {
|
|
548
662
|
console.log("[Magic Collab] [CACHE] IndexedDB synced for room:", roomId);
|
|
549
|
-
const blockOrder = metaMap.get("blockOrder");
|
|
550
|
-
fetch("http://127.0.0.1:7242/ingest/12c1170b-f275-4a73-9ee7-3006bf7f0881", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ location: "useMagicCollaboration:indexeddb:synced", message: "IndexedDB synced - loaded local state", data: { blocksMapSize: blocksMap.size, blocksMapKeys: Array.from(blocksMap.keys()), blockOrder, roomId, persistenceKey }, timestamp: Date.now(), sessionId: "debug-session", hypothesisId: "H" }) }).catch(() => {
|
|
551
|
-
});
|
|
552
663
|
});
|
|
553
664
|
console.log("[Magic Collab] [CACHE] IndexedDB persistence initialized:", persistenceKey);
|
|
554
665
|
} catch (e) {
|
|
@@ -635,17 +746,11 @@ const useMagicCollaboration = ({
|
|
|
635
746
|
if (update) {
|
|
636
747
|
console.log("[Magic Collab] [SYNC] Syncing initial state, update size:", update.length, "bytes");
|
|
637
748
|
try {
|
|
638
|
-
const blockOrderBefore = metaMap.get("blockOrder");
|
|
639
|
-
fetch("http://127.0.0.1:7242/ingest/12c1170b-f275-4a73-9ee7-3006bf7f0881", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ location: "useMagicCollaboration:collab:sync:before", message: "BEFORE applying server sync", data: { blocksMapSize: blocksMap.size, blocksMapKeys: Array.from(blocksMap.keys()), blockOrder: blockOrderBefore, roomId }, timestamp: Date.now(), sessionId: "debug-session", hypothesisId: "F" }) }).catch(() => {
|
|
640
|
-
});
|
|
641
749
|
const beforeBlockCount = blocksMap.size;
|
|
642
750
|
console.log("[Magic Collab] [DATA] Y.Map BEFORE sync - block count:", beforeBlockCount);
|
|
643
751
|
Y__namespace.applyUpdate(doc, new Uint8Array(update), "remote");
|
|
644
752
|
const afterBlockCount = blocksMap.size;
|
|
645
753
|
console.log("[Magic Collab] [DATA] Y.Map AFTER sync - block count:", afterBlockCount);
|
|
646
|
-
const blockOrderAfter = metaMap.get("blockOrder");
|
|
647
|
-
fetch("http://127.0.0.1:7242/ingest/12c1170b-f275-4a73-9ee7-3006bf7f0881", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ location: "useMagicCollaboration:collab:sync:after", message: "AFTER applying server sync", data: { blocksMapSize: blocksMap.size, blocksMapKeys: Array.from(blocksMap.keys()), blockOrder: blockOrderAfter, roomId }, timestamp: Date.now(), sessionId: "debug-session", hypothesisId: "F" }) }).catch(() => {
|
|
648
|
-
});
|
|
649
754
|
if (onRemoteUpdateRef.current) {
|
|
650
755
|
console.log("[Magic Collab] [CALLBACK] Calling onRemoteUpdate callback after sync");
|
|
651
756
|
setTimeout(() => {
|
|
@@ -661,15 +766,11 @@ const useMagicCollaboration = ({
|
|
|
661
766
|
if (update) {
|
|
662
767
|
console.log("[Magic Collab] [UPDATE] Received remote update:", update.length, "bytes");
|
|
663
768
|
try {
|
|
664
|
-
fetch("http://127.0.0.1:7242/ingest/12c1170b-f275-4a73-9ee7-3006bf7f0881", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ location: "useMagicCollaboration:collab:update:before", message: "BEFORE applying remote update", data: { blocksMapSize: blocksMap.size, blocksMapKeys: Array.from(blocksMap.keys()), blockOrder: metaMap.get("blockOrder"), updateSize: update.length, roomId }, timestamp: Date.now(), sessionId: "debug-session", hypothesisId: "G" }) }).catch(() => {
|
|
665
|
-
});
|
|
666
769
|
const beforeBlockCount = blocksMap.size;
|
|
667
770
|
console.log("[Magic Collab] [DATA] Y.Map BEFORE update - blocks:", beforeBlockCount);
|
|
668
771
|
Y__namespace.applyUpdate(doc, new Uint8Array(update), "remote");
|
|
669
772
|
const afterBlockCount = blocksMap.size;
|
|
670
773
|
console.log("[Magic Collab] [DATA] Y.Map AFTER update - blocks:", afterBlockCount);
|
|
671
|
-
fetch("http://127.0.0.1:7242/ingest/12c1170b-f275-4a73-9ee7-3006bf7f0881", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ location: "useMagicCollaboration:collab:update:after", message: "AFTER applying remote update", data: { blocksMapSize: blocksMap.size, blocksMapKeys: Array.from(blocksMap.keys()), blockOrder: metaMap.get("blockOrder"), roomId }, timestamp: Date.now(), sessionId: "debug-session", hypothesisId: "G" }) }).catch(() => {
|
|
672
|
-
});
|
|
673
774
|
if (onRemoteUpdateRef.current) {
|
|
674
775
|
console.log("[Magic Collab] [CALLBACK] Calling onRemoteUpdate callback");
|
|
675
776
|
setTimeout(() => {
|
|
@@ -793,13 +894,30 @@ const useMagicCollaboration = ({
|
|
|
793
894
|
return user ? getUserColor(user.id) : CURSOR_COLORS[0];
|
|
794
895
|
}, [user]);
|
|
795
896
|
return {
|
|
897
|
+
// Y.js Document & Maps
|
|
796
898
|
doc,
|
|
797
899
|
blocksMap,
|
|
798
|
-
// Y.Map for block
|
|
900
|
+
// Y.Map for block metadata (type, tunes)
|
|
901
|
+
textMap,
|
|
902
|
+
// Y.Map<blockId, Y.Text> for character-level text sync
|
|
799
903
|
metaMap,
|
|
800
|
-
// Y.Map for metadata (
|
|
904
|
+
// Y.Map for document metadata (time, blockOrder)
|
|
905
|
+
// Character-level text helpers
|
|
906
|
+
getBlockText,
|
|
907
|
+
// Get Y.Text for a block (creates if not exists)
|
|
908
|
+
setBlockText,
|
|
909
|
+
// Set block text from HTML
|
|
910
|
+
getBlockTextHtml,
|
|
911
|
+
// Get block text as HTML
|
|
912
|
+
// Utility functions
|
|
913
|
+
htmlToDelta,
|
|
914
|
+
// Convert HTML to delta
|
|
915
|
+
deltaToHtml,
|
|
916
|
+
// Convert delta to HTML
|
|
917
|
+
// Connection status
|
|
801
918
|
status,
|
|
802
919
|
error,
|
|
920
|
+
// Collaboration
|
|
803
921
|
peers,
|
|
804
922
|
awareness,
|
|
805
923
|
emitAwareness,
|
|
@@ -1097,6 +1215,136 @@ const useAIActions = ({ licenseKey, editorInstanceRef, isReady, onNoCredits }) =
|
|
|
1097
1215
|
}, [replaceText, appendText, onNoCredits]);
|
|
1098
1216
|
return { handleAIAction };
|
|
1099
1217
|
};
|
|
1218
|
+
const useWebtoolsLinks = () => {
|
|
1219
|
+
const getPlugin = admin.useStrapiApp("WebtoolsLinks", (state) => state.getPlugin);
|
|
1220
|
+
const linksPlugin = React.useMemo(() => {
|
|
1221
|
+
try {
|
|
1222
|
+
return getPlugin?.("webtools-addon-links");
|
|
1223
|
+
} catch (e) {
|
|
1224
|
+
return null;
|
|
1225
|
+
}
|
|
1226
|
+
}, [getPlugin]);
|
|
1227
|
+
const isAvailable = React.useMemo(() => {
|
|
1228
|
+
const available = !!linksPlugin?.apis?.openLinkPicker;
|
|
1229
|
+
if (typeof window !== "undefined" && !window.__WEBTOOLS_LINKS_CHECKED__) {
|
|
1230
|
+
window.__WEBTOOLS_LINKS_CHECKED__ = true;
|
|
1231
|
+
if (available) {
|
|
1232
|
+
console.log("[Magic Editor X] [SUCCESS] Webtools Links addon detected - Link Picker enabled");
|
|
1233
|
+
} else {
|
|
1234
|
+
console.log("[Magic Editor X] [INFO] Webtools Links addon not installed - Link Picker disabled");
|
|
1235
|
+
}
|
|
1236
|
+
}
|
|
1237
|
+
return available;
|
|
1238
|
+
}, [linksPlugin]);
|
|
1239
|
+
const openLinkPicker = React.useCallback(async ({ initialHref = "", initialText = "" } = {}) => {
|
|
1240
|
+
if (!linksPlugin?.apis?.openLinkPicker) {
|
|
1241
|
+
console.warn("[Magic Editor X] Webtools Link Picker not available");
|
|
1242
|
+
return null;
|
|
1243
|
+
}
|
|
1244
|
+
console.log("[Magic Editor X] Opening Webtools Link Picker with:", {
|
|
1245
|
+
linkType: "both",
|
|
1246
|
+
initialHref: initialHref || "(empty)",
|
|
1247
|
+
initialText: initialText || "(empty)"
|
|
1248
|
+
});
|
|
1249
|
+
try {
|
|
1250
|
+
const result = await linksPlugin.apis.openLinkPicker({
|
|
1251
|
+
linkType: "both",
|
|
1252
|
+
// Allow both internal and external links
|
|
1253
|
+
initialHref: initialHref || "",
|
|
1254
|
+
initialText: initialText || ""
|
|
1255
|
+
});
|
|
1256
|
+
console.log("[Magic Editor X] Webtools picker result:", result);
|
|
1257
|
+
if (result && result.href) {
|
|
1258
|
+
console.log("[Magic Editor X] Webtools link selected:", result);
|
|
1259
|
+
return {
|
|
1260
|
+
href: result.href,
|
|
1261
|
+
label: result.label || initialText || ""
|
|
1262
|
+
};
|
|
1263
|
+
}
|
|
1264
|
+
return null;
|
|
1265
|
+
} catch (error) {
|
|
1266
|
+
console.error("[Magic Editor X] Error opening Webtools Link Picker:", error);
|
|
1267
|
+
return null;
|
|
1268
|
+
}
|
|
1269
|
+
}, [linksPlugin]);
|
|
1270
|
+
return {
|
|
1271
|
+
isAvailable,
|
|
1272
|
+
openLinkPicker
|
|
1273
|
+
};
|
|
1274
|
+
};
|
|
1275
|
+
const apiBase = "/magic-editor-x";
|
|
1276
|
+
const useVersionHistory = () => {
|
|
1277
|
+
const { get, post } = admin.useFetchClient();
|
|
1278
|
+
const { tier } = useLicense();
|
|
1279
|
+
const [snapshots, setSnapshots] = React.useState([]);
|
|
1280
|
+
const [loading, setLoading] = React.useState(false);
|
|
1281
|
+
const [error, setError] = React.useState(null);
|
|
1282
|
+
const fetchSnapshots = React.useCallback(
|
|
1283
|
+
async (roomId) => {
|
|
1284
|
+
if (!roomId) return;
|
|
1285
|
+
setLoading(true);
|
|
1286
|
+
setError(null);
|
|
1287
|
+
try {
|
|
1288
|
+
const { data } = await get(`${apiBase}/snapshots/${roomId}`);
|
|
1289
|
+
setSnapshots(data?.data || []);
|
|
1290
|
+
} catch (err) {
|
|
1291
|
+
setError(err?.message || "Failed to load snapshots");
|
|
1292
|
+
} finally {
|
|
1293
|
+
setLoading(false);
|
|
1294
|
+
}
|
|
1295
|
+
},
|
|
1296
|
+
[get]
|
|
1297
|
+
);
|
|
1298
|
+
const restoreSnapshot = React.useCallback(
|
|
1299
|
+
async (documentId, roomId) => {
|
|
1300
|
+
if (!documentId) return;
|
|
1301
|
+
setLoading(true);
|
|
1302
|
+
setError(null);
|
|
1303
|
+
try {
|
|
1304
|
+
const { data } = await post(`${apiBase}/snapshots/restore/${documentId}`, { roomId });
|
|
1305
|
+
return data?.data;
|
|
1306
|
+
} catch (err) {
|
|
1307
|
+
setError(err?.message || "Failed to restore snapshot");
|
|
1308
|
+
throw err;
|
|
1309
|
+
} finally {
|
|
1310
|
+
setLoading(false);
|
|
1311
|
+
}
|
|
1312
|
+
},
|
|
1313
|
+
[get]
|
|
1314
|
+
);
|
|
1315
|
+
const createSnapshot = React.useCallback(
|
|
1316
|
+
async ({ roomId, contentType, entryId, fieldName, content }) => {
|
|
1317
|
+
if (!roomId || !contentType || !entryId || !fieldName) return;
|
|
1318
|
+
setLoading(true);
|
|
1319
|
+
setError(null);
|
|
1320
|
+
try {
|
|
1321
|
+
const { data } = await post(`${apiBase}/snapshots/${roomId}`, {
|
|
1322
|
+
contentType,
|
|
1323
|
+
entryId,
|
|
1324
|
+
fieldName,
|
|
1325
|
+
content
|
|
1326
|
+
// Include editor content as fallback
|
|
1327
|
+
});
|
|
1328
|
+
return data?.data;
|
|
1329
|
+
} catch (err) {
|
|
1330
|
+
setError(err?.message || "Failed to create snapshot");
|
|
1331
|
+
throw err;
|
|
1332
|
+
} finally {
|
|
1333
|
+
setLoading(false);
|
|
1334
|
+
}
|
|
1335
|
+
},
|
|
1336
|
+
[post]
|
|
1337
|
+
);
|
|
1338
|
+
return {
|
|
1339
|
+
snapshots,
|
|
1340
|
+
loading,
|
|
1341
|
+
error,
|
|
1342
|
+
tier,
|
|
1343
|
+
fetchSnapshots,
|
|
1344
|
+
restoreSnapshot,
|
|
1345
|
+
createSnapshot
|
|
1346
|
+
};
|
|
1347
|
+
};
|
|
1100
1348
|
const Overlay$1 = styled__default.default.div`
|
|
1101
1349
|
position: fixed;
|
|
1102
1350
|
top: 0;
|
|
@@ -1120,7 +1368,7 @@ const PopupContainer = styled__default.default.div`
|
|
|
1120
1368
|
display: flex;
|
|
1121
1369
|
flex-direction: column;
|
|
1122
1370
|
`;
|
|
1123
|
-
const Header$
|
|
1371
|
+
const Header$2 = styled__default.default.div`
|
|
1124
1372
|
background: linear-gradient(135deg, #7C3AED 0%, #a855f7 100%);
|
|
1125
1373
|
padding: 20px 24px;
|
|
1126
1374
|
color: white;
|
|
@@ -1142,7 +1390,7 @@ const CreditsBadge = styled__default.default.div`
|
|
|
1142
1390
|
font-size: 13px;
|
|
1143
1391
|
font-weight: 500;
|
|
1144
1392
|
`;
|
|
1145
|
-
const Content$
|
|
1393
|
+
const Content$2 = styled__default.default.div`
|
|
1146
1394
|
padding: 24px;
|
|
1147
1395
|
overflow-y: auto;
|
|
1148
1396
|
flex: 1;
|
|
@@ -1407,7 +1655,7 @@ const AIAssistantPopup = ({ selectedText, licenseKey, onClose, onApply }) => {
|
|
|
1407
1655
|
return () => window.removeEventListener("keydown", handleKeyDown);
|
|
1408
1656
|
}, [onClose]);
|
|
1409
1657
|
return /* @__PURE__ */ jsxRuntime.jsx(Overlay$1, { onClick: handleOverlayClick, children: /* @__PURE__ */ jsxRuntime.jsxs(PopupContainer, { onClick: (e) => e.stopPropagation(), children: [
|
|
1410
|
-
/* @__PURE__ */ jsxRuntime.jsxs(Header$
|
|
1658
|
+
/* @__PURE__ */ jsxRuntime.jsxs(Header$2, { children: [
|
|
1411
1659
|
/* @__PURE__ */ jsxRuntime.jsxs(HeaderTitle, { children: [
|
|
1412
1660
|
/* @__PURE__ */ jsxRuntime.jsx(SparklesIcon, {}),
|
|
1413
1661
|
/* @__PURE__ */ jsxRuntime.jsx("span", { children: "KI-Assistent" })
|
|
@@ -1419,7 +1667,7 @@ const AIAssistantPopup = ({ selectedText, licenseKey, onClose, onApply }) => {
|
|
|
1419
1667
|
!usage?.tier && "Wird geladen..."
|
|
1420
1668
|
] })
|
|
1421
1669
|
] }),
|
|
1422
|
-
/* @__PURE__ */ jsxRuntime.jsxs(Content$
|
|
1670
|
+
/* @__PURE__ */ jsxRuntime.jsxs(Content$2, { children: [
|
|
1423
1671
|
/* @__PURE__ */ jsxRuntime.jsx(TextPreview, { children: selectedText.length > 300 ? selectedText.substring(0, 300) + "..." : selectedText }),
|
|
1424
1672
|
/* @__PURE__ */ jsxRuntime.jsxs(TypeButtons, { children: [
|
|
1425
1673
|
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
@@ -1506,6 +1754,132 @@ const AIAssistantPopup = ({ selectedText, licenseKey, onClose, onApply }) => {
|
|
|
1506
1754
|
] })
|
|
1507
1755
|
] }) });
|
|
1508
1756
|
};
|
|
1757
|
+
const PanelWrapper = styled__default.default(getTranslation.Box)`
|
|
1758
|
+
width: 320px;
|
|
1759
|
+
background: ${({ theme }) => theme.colors.neutral0};
|
|
1760
|
+
border: 1px solid ${({ theme }) => theme.colors.neutral150};
|
|
1761
|
+
border-radius: 8px;
|
|
1762
|
+
box-shadow: ${({ theme }) => theme.shadows.filterShadow};
|
|
1763
|
+
display: flex;
|
|
1764
|
+
flex-direction: column;
|
|
1765
|
+
max-height: 70vh;
|
|
1766
|
+
`;
|
|
1767
|
+
const Header$1 = styled__default.default(getTranslation.Flex)`
|
|
1768
|
+
padding: 12px 16px;
|
|
1769
|
+
border-bottom: 1px solid ${({ theme }) => theme.colors.neutral150};
|
|
1770
|
+
`;
|
|
1771
|
+
const Content$1 = styled__default.default(getTranslation.Box)`
|
|
1772
|
+
padding: 12px 16px;
|
|
1773
|
+
overflow-y: auto;
|
|
1774
|
+
`;
|
|
1775
|
+
const Item = styled__default.default(getTranslation.Box)`
|
|
1776
|
+
padding: 10px 12px;
|
|
1777
|
+
border: 1px solid ${({ theme }) => theme.colors.neutral150};
|
|
1778
|
+
border-radius: 6px;
|
|
1779
|
+
margin-bottom: 10px;
|
|
1780
|
+
`;
|
|
1781
|
+
const Meta = styled__default.default(getTranslation.Typography)`
|
|
1782
|
+
color: ${({ theme }) => theme.colors.neutral500};
|
|
1783
|
+
font-size: 12px;
|
|
1784
|
+
`;
|
|
1785
|
+
const PremiumBadge = styled__default.default(getTranslation.Box)`
|
|
1786
|
+
background: ${({ theme }) => theme.colors.primary100};
|
|
1787
|
+
color: ${({ theme }) => theme.colors.primary600};
|
|
1788
|
+
border-radius: 6px;
|
|
1789
|
+
padding: 8px 10px;
|
|
1790
|
+
display: inline-flex;
|
|
1791
|
+
align-items: center;
|
|
1792
|
+
gap: 8px;
|
|
1793
|
+
font-weight: 600;
|
|
1794
|
+
margin-top: 8px;
|
|
1795
|
+
`;
|
|
1796
|
+
const safeDateFrom = (value) => {
|
|
1797
|
+
if (!value) return null;
|
|
1798
|
+
if (value instanceof Date) {
|
|
1799
|
+
return isNaN(value.getTime()) ? null : value;
|
|
1800
|
+
}
|
|
1801
|
+
try {
|
|
1802
|
+
const parsed = new Date(value);
|
|
1803
|
+
return isNaN(parsed.getTime()) ? null : parsed;
|
|
1804
|
+
} catch {
|
|
1805
|
+
return null;
|
|
1806
|
+
}
|
|
1807
|
+
};
|
|
1808
|
+
const formatDate = (dateValue) => {
|
|
1809
|
+
const date = safeDateFrom(dateValue);
|
|
1810
|
+
if (!date) return "—";
|
|
1811
|
+
try {
|
|
1812
|
+
return date.toLocaleString();
|
|
1813
|
+
} catch {
|
|
1814
|
+
return "—";
|
|
1815
|
+
}
|
|
1816
|
+
};
|
|
1817
|
+
const VersionHistoryPanel = ({
|
|
1818
|
+
snapshots,
|
|
1819
|
+
loading,
|
|
1820
|
+
error,
|
|
1821
|
+
onRestore,
|
|
1822
|
+
onCreate,
|
|
1823
|
+
tier,
|
|
1824
|
+
onClose
|
|
1825
|
+
}) => {
|
|
1826
|
+
const { formatMessage } = getTranslation.useIntl();
|
|
1827
|
+
const canRestore = tier !== "free";
|
|
1828
|
+
const t = (id, defaultMessage) => formatMessage(
|
|
1829
|
+
{ id: getTranslation.getTranslation(id), defaultMessage },
|
|
1830
|
+
{}
|
|
1831
|
+
);
|
|
1832
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(PanelWrapper, { "data-testid": "version-history-panel", children: [
|
|
1833
|
+
/* @__PURE__ */ jsxRuntime.jsxs(Header$1, { justifyContent: "space-between", alignItems: "center", children: [
|
|
1834
|
+
/* @__PURE__ */ jsxRuntime.jsxs(getTranslation.Flex, { gap: 8, alignItems: "center", children: [
|
|
1835
|
+
/* @__PURE__ */ jsxRuntime.jsx(outline.ClockIcon, { width: 18 }),
|
|
1836
|
+
/* @__PURE__ */ jsxRuntime.jsx(getTranslation.Typography, { fontWeight: "bold", children: t("versionHistory.title", "Version History") })
|
|
1837
|
+
] }),
|
|
1838
|
+
/* @__PURE__ */ jsxRuntime.jsx(getTranslation.Button, { size: "S", variant: "tertiary", onClick: onClose, children: t("versionHistory.close", "Close") })
|
|
1839
|
+
] }),
|
|
1840
|
+
/* @__PURE__ */ jsxRuntime.jsxs(Content$1, { children: [
|
|
1841
|
+
loading && /* @__PURE__ */ jsxRuntime.jsx(getTranslation.Typography, { children: t("versionHistory.loading", "Loading versions...") }),
|
|
1842
|
+
error && /* @__PURE__ */ jsxRuntime.jsx(getTranslation.Typography, { textColor: "danger600", children: error }),
|
|
1843
|
+
!loading && !error && snapshots.length === 0 && /* @__PURE__ */ jsxRuntime.jsx(getTranslation.Typography, { children: t("versionHistory.noSnapshots", "No versions saved yet") }),
|
|
1844
|
+
!loading && !error && snapshots.map((snap) => /* @__PURE__ */ jsxRuntime.jsxs(Item, { children: [
|
|
1845
|
+
/* @__PURE__ */ jsxRuntime.jsxs(getTranslation.Flex, { justifyContent: "space-between", alignItems: "center", children: [
|
|
1846
|
+
/* @__PURE__ */ jsxRuntime.jsxs(getTranslation.Typography, { fontWeight: "bold", children: [
|
|
1847
|
+
t("versionHistory.version", "Version"),
|
|
1848
|
+
" ",
|
|
1849
|
+
snap.version
|
|
1850
|
+
] }),
|
|
1851
|
+
/* @__PURE__ */ jsxRuntime.jsx(getTranslation.Typography, { variant: "pi", children: formatDate(snap.createdAt) })
|
|
1852
|
+
] }),
|
|
1853
|
+
/* @__PURE__ */ jsxRuntime.jsxs(Meta, { children: [
|
|
1854
|
+
t("versionHistory.createdBy", "By"),
|
|
1855
|
+
" ",
|
|
1856
|
+
snap.createdBy?.firstname ? `${snap.createdBy.firstname} ${snap.createdBy.lastname || ""}`.trim() : "—"
|
|
1857
|
+
] }),
|
|
1858
|
+
/* @__PURE__ */ jsxRuntime.jsx(getTranslation.Divider, { marginTop: 2, marginBottom: 2 }),
|
|
1859
|
+
canRestore ? /* @__PURE__ */ jsxRuntime.jsx(getTranslation.Flex, { gap: 8, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1860
|
+
getTranslation.Button,
|
|
1861
|
+
{
|
|
1862
|
+
size: "S",
|
|
1863
|
+
variant: "secondary",
|
|
1864
|
+
onClick: () => onRestore?.(snap),
|
|
1865
|
+
children: t("versionHistory.restore", "Restore")
|
|
1866
|
+
}
|
|
1867
|
+
) }) : /* @__PURE__ */ jsxRuntime.jsxs(PremiumBadge, { children: [
|
|
1868
|
+
/* @__PURE__ */ jsxRuntime.jsx(outline.ExclamationTriangleIcon, { width: 16 }),
|
|
1869
|
+
t("versionHistory.premiumOnly", "Premium feature")
|
|
1870
|
+
] })
|
|
1871
|
+
] }, snap.documentId || snap.id)),
|
|
1872
|
+
/* @__PURE__ */ jsxRuntime.jsx(getTranslation.Divider, { marginTop: 4, marginBottom: 4 }),
|
|
1873
|
+
canRestore ? /* @__PURE__ */ jsxRuntime.jsx(getTranslation.Button, { size: "S", fullWidth: true, variant: "default", onClick: onCreate, disabled: loading, children: t("versionHistory.create", "Create Snapshot") }) : /* @__PURE__ */ jsxRuntime.jsxs(getTranslation.Box, { children: [
|
|
1874
|
+
/* @__PURE__ */ jsxRuntime.jsx(getTranslation.Button, { size: "S", fullWidth: true, variant: "default", disabled: true, children: t("versionHistory.create", "Create Snapshot") }),
|
|
1875
|
+
/* @__PURE__ */ jsxRuntime.jsxs(PremiumBadge, { style: { marginTop: "8px", width: "100%", justifyContent: "center" }, children: [
|
|
1876
|
+
/* @__PURE__ */ jsxRuntime.jsx(outline.ExclamationTriangleIcon, { width: 16 }),
|
|
1877
|
+
t("versionHistory.premiumOnly", "Premium feature")
|
|
1878
|
+
] })
|
|
1879
|
+
] })
|
|
1880
|
+
] })
|
|
1881
|
+
] });
|
|
1882
|
+
};
|
|
1509
1883
|
const Overlay = styled__default.default.div`
|
|
1510
1884
|
position: fixed;
|
|
1511
1885
|
top: 0;
|
|
@@ -1762,19 +2136,118 @@ const FullscreenGlobalStyle = styled.createGlobalStyle`
|
|
|
1762
2136
|
}
|
|
1763
2137
|
`;
|
|
1764
2138
|
const EditorJSGlobalStyles = styled.createGlobalStyle`
|
|
1765
|
-
/*
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
2139
|
+
/* ============================================
|
|
2140
|
+
INLINE TOOLBAR - EditorJS 2.31
|
|
2141
|
+
Structure: .ce-inline-toolbar > .ce-popover--inline > .ce-popover__items > .ce-popover-item-html > .ce-inline-tool
|
|
2142
|
+
============================================ */
|
|
2143
|
+
|
|
2144
|
+
/* Hide "Nothing found" message when inline tools ARE present */
|
|
2145
|
+
.ce-popover--inline .ce-popover__nothing-found-message {
|
|
2146
|
+
display: none !important;
|
|
2147
|
+
}
|
|
2148
|
+
|
|
2149
|
+
/* Inline Toolbar Popover - horizontal layout for tool buttons */
|
|
2150
|
+
.ce-popover--inline.ce-popover--opened {
|
|
2151
|
+
display: block !important;
|
|
2152
|
+
opacity: 1 !important;
|
|
2153
|
+
visibility: visible !important;
|
|
1772
2154
|
z-index: 99999 !important;
|
|
2155
|
+
background: white !important;
|
|
2156
|
+
border: 1px solid #e2e8f0 !important;
|
|
2157
|
+
border-radius: 8px !important;
|
|
2158
|
+
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.12) !important;
|
|
2159
|
+
padding: 4px !important;
|
|
1773
2160
|
}
|
|
1774
2161
|
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
2162
|
+
.ce-popover--inline .ce-popover__container {
|
|
2163
|
+
display: block !important;
|
|
2164
|
+
}
|
|
2165
|
+
|
|
2166
|
+
/* Items container - HORIZONTAL layout for inline tools */
|
|
2167
|
+
.ce-popover--inline .ce-popover__items {
|
|
2168
|
+
display: flex !important;
|
|
2169
|
+
flex-direction: row !important;
|
|
2170
|
+
flex-wrap: wrap !important;
|
|
2171
|
+
align-items: center !important;
|
|
2172
|
+
gap: 2px !important;
|
|
2173
|
+
opacity: 1 !important;
|
|
2174
|
+
visibility: visible !important;
|
|
2175
|
+
}
|
|
2176
|
+
|
|
2177
|
+
/* Custom HTML wrapper for inline tools */
|
|
2178
|
+
.ce-popover--inline .ce-popover-item-html {
|
|
2179
|
+
display: flex !important;
|
|
2180
|
+
opacity: 1 !important;
|
|
2181
|
+
visibility: visible !important;
|
|
2182
|
+
}
|
|
2183
|
+
|
|
2184
|
+
/* The actual inline tool buttons (Bold, Italic, etc.) */
|
|
2185
|
+
.ce-popover--inline .ce-inline-tool {
|
|
2186
|
+
display: flex !important;
|
|
2187
|
+
align-items: center !important;
|
|
2188
|
+
justify-content: center !important;
|
|
2189
|
+
width: 32px !important;
|
|
2190
|
+
height: 32px !important;
|
|
2191
|
+
opacity: 1 !important;
|
|
2192
|
+
visibility: visible !important;
|
|
2193
|
+
background: transparent !important;
|
|
2194
|
+
border: none !important;
|
|
2195
|
+
border-radius: 6px !important;
|
|
2196
|
+
cursor: pointer !important;
|
|
2197
|
+
color: #64748b !important;
|
|
2198
|
+
transition: background 0.15s ease, color 0.15s ease !important;
|
|
2199
|
+
}
|
|
2200
|
+
|
|
2201
|
+
.ce-popover--inline .ce-inline-tool:hover {
|
|
2202
|
+
background: #f1f5f9 !important;
|
|
2203
|
+
color: #334155 !important;
|
|
2204
|
+
}
|
|
2205
|
+
|
|
2206
|
+
.ce-popover--inline .ce-inline-tool--active {
|
|
2207
|
+
background: #ede9fe !important;
|
|
2208
|
+
color: #7C3AED !important;
|
|
2209
|
+
}
|
|
2210
|
+
|
|
2211
|
+
.ce-popover--inline .ce-inline-tool svg {
|
|
2212
|
+
width: 18px !important;
|
|
2213
|
+
height: 18px !important;
|
|
2214
|
+
}
|
|
2215
|
+
|
|
2216
|
+
/* Convert-to button (block type changer) */
|
|
2217
|
+
.ce-popover--inline .ce-popover-item[data-item-name="convert-to"] {
|
|
2218
|
+
display: flex !important;
|
|
2219
|
+
align-items: center !important;
|
|
2220
|
+
padding: 4px 8px !important;
|
|
2221
|
+
border-radius: 6px !important;
|
|
2222
|
+
cursor: pointer !important;
|
|
2223
|
+
}
|
|
2224
|
+
|
|
2225
|
+
.ce-popover--inline .ce-popover-item[data-item-name="convert-to"]:hover {
|
|
2226
|
+
background: #f1f5f9 !important;
|
|
2227
|
+
}
|
|
2228
|
+
|
|
2229
|
+
/* Separator line between convert-to and inline tools */
|
|
2230
|
+
.ce-popover--inline .ce-popover-item-separator {
|
|
2231
|
+
width: 1px !important;
|
|
2232
|
+
height: 24px !important;
|
|
2233
|
+
background: #e2e8f0 !important;
|
|
2234
|
+
margin: 0 4px !important;
|
|
2235
|
+
}
|
|
2236
|
+
|
|
2237
|
+
.ce-popover--inline .ce-popover-item-separator__line {
|
|
2238
|
+
display: none !important;
|
|
2239
|
+
}
|
|
2240
|
+
|
|
2241
|
+
/* ============================================
|
|
2242
|
+
GLOBAL Z-INDEX FOR ALL EDITOR POPOVERS
|
|
2243
|
+
============================================ */
|
|
2244
|
+
|
|
2245
|
+
body > .ce-popover,
|
|
2246
|
+
body > .ce-inline-toolbar,
|
|
2247
|
+
.ce-popover--opened,
|
|
2248
|
+
.ce-inline-toolbar,
|
|
2249
|
+
.ce-settings,
|
|
2250
|
+
.ce-conversion-toolbar {
|
|
1778
2251
|
z-index: 99999 !important;
|
|
1779
2252
|
}
|
|
1780
2253
|
|
|
@@ -1820,11 +2293,14 @@ const EditorJSGlobalStyles = styled.createGlobalStyle`
|
|
|
1820
2293
|
|
|
1821
2294
|
/* ============================================
|
|
1822
2295
|
STRAPI MEDIA LIBRARY - Higher z-index for fullscreen
|
|
2296
|
+
Must be higher than fullscreen z-index (9999)
|
|
1823
2297
|
============================================ */
|
|
1824
2298
|
[data-react-portal],
|
|
1825
2299
|
.ReactModalPortal,
|
|
1826
2300
|
[role="dialog"],
|
|
1827
2301
|
[data-strapi-modal="true"],
|
|
2302
|
+
[class*="Dialog"],
|
|
2303
|
+
[class*="Modal"],
|
|
1828
2304
|
.upload-dialog,
|
|
1829
2305
|
[class*="Modal"],
|
|
1830
2306
|
[class*="modal"],
|
|
@@ -2473,15 +2949,36 @@ const EditorContent = styled__default.default.div`
|
|
|
2473
2949
|
flex: 1;
|
|
2474
2950
|
overflow: visible; /* Allow toolbars/popovers to escape */
|
|
2475
2951
|
position: relative;
|
|
2476
|
-
padding: 24px
|
|
2952
|
+
padding: 24px;
|
|
2477
2953
|
min-height: 200px;
|
|
2478
2954
|
|
|
2479
2955
|
${(props) => props.$isFullscreen && styled.css`
|
|
2480
|
-
padding: clamp(
|
|
2956
|
+
padding: clamp(24px, 3vw, 48px);
|
|
2481
2957
|
width: 100%;
|
|
2482
2958
|
max-width: 100%;
|
|
2483
2959
|
margin: 0;
|
|
2484
2960
|
align-self: stretch;
|
|
2961
|
+
|
|
2962
|
+
/* Make blocks stretch full width in fullscreen */
|
|
2963
|
+
.codex-editor {
|
|
2964
|
+
width: 100%;
|
|
2965
|
+
}
|
|
2966
|
+
|
|
2967
|
+
.ce-block__content,
|
|
2968
|
+
.ce-toolbar__content {
|
|
2969
|
+
max-width: 100% !important;
|
|
2970
|
+
padding: 0 !important;
|
|
2971
|
+
}
|
|
2972
|
+
|
|
2973
|
+
.ce-toolbar {
|
|
2974
|
+
max-width: 100% !important;
|
|
2975
|
+
left: 0 !important;
|
|
2976
|
+
transform: none !important;
|
|
2977
|
+
}
|
|
2978
|
+
|
|
2979
|
+
.ce-toolbar__actions {
|
|
2980
|
+
right: 0 !important;
|
|
2981
|
+
}
|
|
2485
2982
|
`}
|
|
2486
2983
|
`;
|
|
2487
2984
|
const EditorWrapper = styled__default.default.div`
|
|
@@ -2556,18 +3053,29 @@ const EditorWrapper = styled__default.default.div`
|
|
|
2556
3053
|
TOOLBAR INSIDE EDITOR - Position Fix
|
|
2557
3054
|
============================================ */
|
|
2558
3055
|
|
|
2559
|
-
/*
|
|
3056
|
+
/* Content area - full container width */
|
|
2560
3057
|
.codex-editor__redactor {
|
|
2561
3058
|
padding-bottom: 100px !important;
|
|
2562
|
-
padding-left:
|
|
2563
|
-
|
|
3059
|
+
padding-left: 0 !important;
|
|
3060
|
+
padding-right: 0 !important;
|
|
3061
|
+
margin: 0 !important;
|
|
3062
|
+
max-width: 100% !important;
|
|
3063
|
+
width: 100% !important;
|
|
2564
3064
|
}
|
|
2565
3065
|
|
|
2566
|
-
/* Content blocks - full width
|
|
3066
|
+
/* Content blocks - full width, no centering */
|
|
2567
3067
|
.ce-block__content {
|
|
2568
|
-
max-width: 100
|
|
2569
|
-
margin
|
|
2570
|
-
|
|
3068
|
+
max-width: 100% !important;
|
|
3069
|
+
margin: 0 !important;
|
|
3070
|
+
padding: 0 16px !important;
|
|
3071
|
+
}
|
|
3072
|
+
|
|
3073
|
+
/* Paragraph and other editable elements - full width */
|
|
3074
|
+
.ce-paragraph,
|
|
3075
|
+
.ce-header,
|
|
3076
|
+
.cdx-block {
|
|
3077
|
+
max-width: 100% !important;
|
|
3078
|
+
width: 100% !important;
|
|
2571
3079
|
}
|
|
2572
3080
|
|
|
2573
3081
|
/* ============================================
|
|
@@ -2616,24 +3124,29 @@ const EditorWrapper = styled__default.default.div`
|
|
|
2616
3124
|
border-radius: 6px;
|
|
2617
3125
|
}
|
|
2618
3126
|
|
|
2619
|
-
/* Toolbar positioning -
|
|
3127
|
+
/* Toolbar positioning - full width */
|
|
2620
3128
|
.ce-toolbar__content {
|
|
2621
|
-
max-width: 100
|
|
2622
|
-
margin
|
|
3129
|
+
max-width: 100% !important;
|
|
3130
|
+
margin: 0 !important;
|
|
3131
|
+
padding: 0 16px !important;
|
|
2623
3132
|
}
|
|
2624
3133
|
|
|
2625
3134
|
.ce-toolbar {
|
|
2626
3135
|
left: 0 !important;
|
|
3136
|
+
right: 0 !important;
|
|
3137
|
+
transform: none !important;
|
|
3138
|
+
width: 100% !important;
|
|
3139
|
+
max-width: 100% !important;
|
|
3140
|
+
padding-left: 8px !important;
|
|
2627
3141
|
}
|
|
2628
3142
|
|
|
2629
3143
|
.ce-toolbar__plus {
|
|
2630
|
-
left: 0 !important;
|
|
2631
3144
|
position: relative !important;
|
|
2632
3145
|
}
|
|
2633
3146
|
|
|
2634
3147
|
.ce-toolbar__actions {
|
|
2635
|
-
right: 0 !important;
|
|
2636
3148
|
position: absolute !important;
|
|
3149
|
+
right: 16px !important;
|
|
2637
3150
|
}
|
|
2638
3151
|
|
|
2639
3152
|
/* Settings button (⋮⋮) */
|
|
@@ -3119,6 +3632,35 @@ const FooterButton = styled__default.default.button`
|
|
|
3119
3632
|
}
|
|
3120
3633
|
}
|
|
3121
3634
|
`;
|
|
3635
|
+
const WebtoolsPromoLink = styled__default.default.a`
|
|
3636
|
+
display: inline-flex;
|
|
3637
|
+
align-items: center;
|
|
3638
|
+
gap: 6px;
|
|
3639
|
+
padding: 4px 10px;
|
|
3640
|
+
background: linear-gradient(135deg, rgba(99, 102, 241, 0.08) 0%, rgba(139, 92, 246, 0.08) 100%);
|
|
3641
|
+
border: 1px solid rgba(99, 102, 241, 0.2);
|
|
3642
|
+
border-radius: 6px;
|
|
3643
|
+
font-size: 11px;
|
|
3644
|
+
color: #6366f1;
|
|
3645
|
+
text-decoration: none;
|
|
3646
|
+
transition: all 0.2s ease;
|
|
3647
|
+
white-space: nowrap;
|
|
3648
|
+
|
|
3649
|
+
svg {
|
|
3650
|
+
width: 12px;
|
|
3651
|
+
height: 12px;
|
|
3652
|
+
}
|
|
3653
|
+
|
|
3654
|
+
&:hover {
|
|
3655
|
+
background: linear-gradient(135deg, rgba(99, 102, 241, 0.15) 0%, rgba(139, 92, 246, 0.15) 100%);
|
|
3656
|
+
border-color: rgba(99, 102, 241, 0.4);
|
|
3657
|
+
transform: translateY(-1px);
|
|
3658
|
+
}
|
|
3659
|
+
|
|
3660
|
+
@media (max-width: 768px) {
|
|
3661
|
+
display: none;
|
|
3662
|
+
}
|
|
3663
|
+
`;
|
|
3122
3664
|
const LoadingOverlay = styled__default.default.div`
|
|
3123
3665
|
position: absolute;
|
|
3124
3666
|
top: 0;
|
|
@@ -3133,6 +3675,18 @@ const LoadingOverlay = styled__default.default.div`
|
|
|
3133
3675
|
gap: 12px;
|
|
3134
3676
|
z-index: 10;
|
|
3135
3677
|
`;
|
|
3678
|
+
const VersionHistoryOverlay = styled__default.default.div`
|
|
3679
|
+
position: fixed;
|
|
3680
|
+
top: 0;
|
|
3681
|
+
left: 0;
|
|
3682
|
+
right: 0;
|
|
3683
|
+
bottom: 0;
|
|
3684
|
+
background: rgba(0, 0, 0, 0.5);
|
|
3685
|
+
display: flex;
|
|
3686
|
+
align-items: center;
|
|
3687
|
+
justify-content: center;
|
|
3688
|
+
z-index: 99999;
|
|
3689
|
+
`;
|
|
3136
3690
|
const LoadingText = styled__default.default.span`
|
|
3137
3691
|
font-size: 13px;
|
|
3138
3692
|
color: #64748b;
|
|
@@ -3348,10 +3902,12 @@ const Editor = React.forwardRef(({
|
|
|
3348
3902
|
}, ref) => {
|
|
3349
3903
|
const { formatMessage } = getTranslation.useIntl();
|
|
3350
3904
|
const t = (id, defaultMessage) => formatMessage({ id: getTranslation.getTranslation(id), defaultMessage });
|
|
3351
|
-
const { licenseData } = useLicense();
|
|
3905
|
+
const { licenseData, tier: licenseTier } = useLicense();
|
|
3906
|
+
const { isAvailable: isWebtoolsAvailable, openLinkPicker: webtoolsOpenLinkPicker } = useWebtoolsLinks();
|
|
3352
3907
|
const editorRef = React.useRef(null);
|
|
3353
3908
|
const editorInstanceRef = React.useRef(null);
|
|
3354
3909
|
const containerRef = React.useRef(null);
|
|
3910
|
+
const webtoolsSelectionRef = React.useRef({ text: "", range: null, blockIndex: -1, existingAnchor: null, existingHref: "" });
|
|
3355
3911
|
const isReadyRef = React.useRef(false);
|
|
3356
3912
|
const [isReady, setIsReady] = React.useState(false);
|
|
3357
3913
|
const [showCreditsModal, setShowCreditsModal] = React.useState(false);
|
|
@@ -3387,6 +3943,51 @@ const Editor = React.forwardRef(({
|
|
|
3387
3943
|
const [aiSelectedText, setAISelectedText] = React.useState("");
|
|
3388
3944
|
const aiSelectionRangeRef = React.useRef(null);
|
|
3389
3945
|
const [aiLoading, setAILoading] = React.useState(false);
|
|
3946
|
+
const [showVersionHistory, setShowVersionHistory] = React.useState(false);
|
|
3947
|
+
React.useEffect(() => {
|
|
3948
|
+
if (!isWebtoolsAvailable || !editorRef.current) return;
|
|
3949
|
+
const updateWebtoolsSelection = () => {
|
|
3950
|
+
const selection = window.getSelection();
|
|
3951
|
+
if (!selection || selection.rangeCount === 0) return;
|
|
3952
|
+
const range = selection.getRangeAt(0);
|
|
3953
|
+
if (!editorRef.current.contains(range.commonAncestorContainer)) return;
|
|
3954
|
+
const selectedText = selection.toString().trim();
|
|
3955
|
+
let existingAnchor = null;
|
|
3956
|
+
let existingHref = "";
|
|
3957
|
+
let node = range.commonAncestorContainer;
|
|
3958
|
+
while (node && node !== editorRef.current) {
|
|
3959
|
+
if (node.nodeName === "A") {
|
|
3960
|
+
existingAnchor = node;
|
|
3961
|
+
existingHref = node.href || "";
|
|
3962
|
+
break;
|
|
3963
|
+
}
|
|
3964
|
+
node = node.parentNode;
|
|
3965
|
+
}
|
|
3966
|
+
if (!existingAnchor) {
|
|
3967
|
+
node = range.startContainer;
|
|
3968
|
+
while (node && node !== editorRef.current) {
|
|
3969
|
+
if (node.nodeName === "A") {
|
|
3970
|
+
existingAnchor = node;
|
|
3971
|
+
existingHref = node.href || "";
|
|
3972
|
+
break;
|
|
3973
|
+
}
|
|
3974
|
+
node = node.parentNode;
|
|
3975
|
+
}
|
|
3976
|
+
}
|
|
3977
|
+
const blockIndex = editorInstanceRef.current?.blocks?.getCurrentBlockIndex?.() ?? -1;
|
|
3978
|
+
webtoolsSelectionRef.current = {
|
|
3979
|
+
text: existingAnchor ? existingAnchor.textContent : selectedText,
|
|
3980
|
+
range: range.cloneRange(),
|
|
3981
|
+
blockIndex,
|
|
3982
|
+
existingAnchor,
|
|
3983
|
+
existingHref
|
|
3984
|
+
};
|
|
3985
|
+
};
|
|
3986
|
+
document.addEventListener("selectionchange", updateWebtoolsSelection);
|
|
3987
|
+
return () => {
|
|
3988
|
+
document.removeEventListener("selectionchange", updateWebtoolsSelection);
|
|
3989
|
+
};
|
|
3990
|
+
}, [isWebtoolsAvailable, isReady]);
|
|
3390
3991
|
const serializedInitialValue = React.useMemo(() => {
|
|
3391
3992
|
if (!value) {
|
|
3392
3993
|
return "";
|
|
@@ -3414,12 +4015,25 @@ const Editor = React.forwardRef(({
|
|
|
3414
4015
|
const {
|
|
3415
4016
|
doc: yDoc,
|
|
3416
4017
|
blocksMap: yBlocksMap,
|
|
4018
|
+
textMap: yTextMap,
|
|
4019
|
+
// NEW: Y.Map<blockId, Y.Text> for character-level sync
|
|
3417
4020
|
metaMap: yMetaMap,
|
|
4021
|
+
// Character-level text helpers
|
|
4022
|
+
getBlockText,
|
|
4023
|
+
// NEW: Get Y.Text for a block
|
|
4024
|
+
setBlockText,
|
|
4025
|
+
// NEW: Get block text as HTML
|
|
4026
|
+
// Utility functions
|
|
4027
|
+
htmlToDelta: collabHtmlToDelta,
|
|
4028
|
+
deltaToHtml: collabDeltaToHtml,
|
|
4029
|
+
// Connection status
|
|
3418
4030
|
status: collabStatus,
|
|
3419
4031
|
error: collabError,
|
|
4032
|
+
// Collaboration
|
|
3420
4033
|
peers: collabPeers,
|
|
3421
4034
|
awareness: collabAwareness,
|
|
3422
4035
|
emitAwareness,
|
|
4036
|
+
// Role-based access control
|
|
3423
4037
|
collabRole,
|
|
3424
4038
|
canEdit: collabCanEdit
|
|
3425
4039
|
} = useMagicCollaboration({
|
|
@@ -3434,6 +4048,172 @@ const Editor = React.forwardRef(({
|
|
|
3434
4048
|
}
|
|
3435
4049
|
}
|
|
3436
4050
|
});
|
|
4051
|
+
const yTextBindingsRef = React.useRef(/* @__PURE__ */ new Map());
|
|
4052
|
+
const bindBlockToYText = React.useCallback((blockId, element) => {
|
|
4053
|
+
if (!collabEnabled || !blockId || !element || !yTextMap) return;
|
|
4054
|
+
if (yTextBindingsRef.current.has(blockId)) {
|
|
4055
|
+
const existing = yTextBindingsRef.current.get(blockId);
|
|
4056
|
+
if (existing.element === element) return;
|
|
4057
|
+
unbindBlockFromYText(blockId);
|
|
4058
|
+
}
|
|
4059
|
+
const ytext = getBlockText(blockId);
|
|
4060
|
+
if (!ytext) return;
|
|
4061
|
+
let isUpdating = false;
|
|
4062
|
+
const ytextObserver = (event) => {
|
|
4063
|
+
if (isUpdating) return;
|
|
4064
|
+
if (event.transaction.local) return;
|
|
4065
|
+
isUpdating = true;
|
|
4066
|
+
try {
|
|
4067
|
+
const selection = window.getSelection();
|
|
4068
|
+
let cursorOffset = 0;
|
|
4069
|
+
if (selection && selection.rangeCount > 0 && element.contains(selection.anchorNode)) {
|
|
4070
|
+
const range = selection.getRangeAt(0);
|
|
4071
|
+
const preCaretRange = document.createRange();
|
|
4072
|
+
preCaretRange.selectNodeContents(element);
|
|
4073
|
+
preCaretRange.setEnd(range.startContainer, range.startOffset);
|
|
4074
|
+
cursorOffset = preCaretRange.toString().length;
|
|
4075
|
+
}
|
|
4076
|
+
const html = collabDeltaToHtml(ytext.toDelta());
|
|
4077
|
+
if (element.innerHTML !== html) {
|
|
4078
|
+
element.innerHTML = html || "";
|
|
4079
|
+
}
|
|
4080
|
+
if (document.activeElement === element && cursorOffset > 0) {
|
|
4081
|
+
try {
|
|
4082
|
+
const textNode = element.firstChild;
|
|
4083
|
+
if (textNode && textNode.nodeType === Node.TEXT_NODE) {
|
|
4084
|
+
const newRange = document.createRange();
|
|
4085
|
+
const pos = Math.min(cursorOffset, textNode.length);
|
|
4086
|
+
newRange.setStart(textNode, pos);
|
|
4087
|
+
newRange.setEnd(textNode, pos);
|
|
4088
|
+
selection.removeAllRanges();
|
|
4089
|
+
selection.addRange(newRange);
|
|
4090
|
+
}
|
|
4091
|
+
} catch (e) {
|
|
4092
|
+
}
|
|
4093
|
+
}
|
|
4094
|
+
} finally {
|
|
4095
|
+
isUpdating = false;
|
|
4096
|
+
}
|
|
4097
|
+
};
|
|
4098
|
+
const inputHandler = () => {
|
|
4099
|
+
if (isUpdating) return;
|
|
4100
|
+
isUpdating = true;
|
|
4101
|
+
try {
|
|
4102
|
+
const html = element.innerHTML;
|
|
4103
|
+
const newDelta = collabHtmlToDelta(html);
|
|
4104
|
+
const currentDelta = ytext.toDelta();
|
|
4105
|
+
yDoc.transact(() => {
|
|
4106
|
+
if (ytext.length > 0) {
|
|
4107
|
+
ytext.delete(0, ytext.length);
|
|
4108
|
+
}
|
|
4109
|
+
if (newDelta.length > 0) {
|
|
4110
|
+
ytext.applyDelta(newDelta);
|
|
4111
|
+
}
|
|
4112
|
+
}, "local");
|
|
4113
|
+
} finally {
|
|
4114
|
+
isUpdating = false;
|
|
4115
|
+
}
|
|
4116
|
+
};
|
|
4117
|
+
ytext.observe(ytextObserver);
|
|
4118
|
+
element.addEventListener("input", inputHandler);
|
|
4119
|
+
yTextBindingsRef.current.set(blockId, {
|
|
4120
|
+
ytext,
|
|
4121
|
+
element,
|
|
4122
|
+
ytextObserver,
|
|
4123
|
+
inputHandler
|
|
4124
|
+
});
|
|
4125
|
+
console.log("[Magic Editor X] [CHAR-SYNC] Bound block to Y.Text:", blockId);
|
|
4126
|
+
}, [collabEnabled, yTextMap, yDoc, getBlockText, collabHtmlToDelta, collabDeltaToHtml]);
|
|
4127
|
+
const unbindBlockFromYText = React.useCallback((blockId) => {
|
|
4128
|
+
const binding = yTextBindingsRef.current.get(blockId);
|
|
4129
|
+
if (!binding) return;
|
|
4130
|
+
binding.ytext.unobserve(binding.ytextObserver);
|
|
4131
|
+
binding.element.removeEventListener("input", binding.inputHandler);
|
|
4132
|
+
yTextBindingsRef.current.delete(blockId);
|
|
4133
|
+
console.log("[Magic Editor X] [CHAR-SYNC] Unbound block from Y.Text:", blockId);
|
|
4134
|
+
}, []);
|
|
4135
|
+
const bindAllBlocksToYText = React.useCallback(() => {
|
|
4136
|
+
if (!collabEnabled || !editorInstanceRef.current || !yTextMap) return;
|
|
4137
|
+
const editor = editorInstanceRef.current;
|
|
4138
|
+
if (!editor.blocks || typeof editor.blocks.getBlocksCount !== "function") return;
|
|
4139
|
+
const blockCount = editor.blocks.getBlocksCount();
|
|
4140
|
+
console.log("[Magic Editor X] [CHAR-SYNC] Binding", blockCount, "blocks to Y.Text");
|
|
4141
|
+
for (let i = 0; i < blockCount; i++) {
|
|
4142
|
+
try {
|
|
4143
|
+
const block = editor.blocks.getBlockByIndex(i);
|
|
4144
|
+
if (!block || !block.id) continue;
|
|
4145
|
+
const blockHolder = block.holder;
|
|
4146
|
+
if (!blockHolder) continue;
|
|
4147
|
+
const contentEditable = blockHolder.querySelector('[contenteditable="true"]');
|
|
4148
|
+
if (contentEditable) {
|
|
4149
|
+
const ytext = getBlockText(block.id);
|
|
4150
|
+
if (ytext && ytext.length === 0) {
|
|
4151
|
+
const currentHtml = contentEditable.innerHTML;
|
|
4152
|
+
if (currentHtml && currentHtml !== "<br>") {
|
|
4153
|
+
setBlockText(block.id, currentHtml);
|
|
4154
|
+
}
|
|
4155
|
+
}
|
|
4156
|
+
bindBlockToYText(block.id, contentEditable);
|
|
4157
|
+
}
|
|
4158
|
+
} catch (e) {
|
|
4159
|
+
console.warn("[Magic Editor X] [CHAR-SYNC] Error binding block:", e);
|
|
4160
|
+
}
|
|
4161
|
+
}
|
|
4162
|
+
}, [collabEnabled, yTextMap, getBlockText, setBlockText, bindBlockToYText]);
|
|
4163
|
+
const blockObserverRef = React.useRef(null);
|
|
4164
|
+
React.useEffect(() => {
|
|
4165
|
+
if (!collabEnabled || !editorRef.current || !yTextMap) return;
|
|
4166
|
+
const observer = new MutationObserver((mutations) => {
|
|
4167
|
+
let hasNewBlocks = false;
|
|
4168
|
+
for (const mutation of mutations) {
|
|
4169
|
+
if (mutation.type === "childList" && mutation.addedNodes.length > 0) {
|
|
4170
|
+
for (const node of mutation.addedNodes) {
|
|
4171
|
+
if (node.nodeType === Node.ELEMENT_NODE && (node.classList?.contains("ce-block") || node.querySelector?.(".ce-block"))) {
|
|
4172
|
+
hasNewBlocks = true;
|
|
4173
|
+
break;
|
|
4174
|
+
}
|
|
4175
|
+
}
|
|
4176
|
+
}
|
|
4177
|
+
if (hasNewBlocks) break;
|
|
4178
|
+
}
|
|
4179
|
+
if (hasNewBlocks) {
|
|
4180
|
+
setTimeout(() => {
|
|
4181
|
+
bindAllBlocksToYText();
|
|
4182
|
+
}, 50);
|
|
4183
|
+
}
|
|
4184
|
+
});
|
|
4185
|
+
observer.observe(editorRef.current, {
|
|
4186
|
+
childList: true,
|
|
4187
|
+
subtree: true
|
|
4188
|
+
});
|
|
4189
|
+
blockObserverRef.current = observer;
|
|
4190
|
+
return () => {
|
|
4191
|
+
observer.disconnect();
|
|
4192
|
+
blockObserverRef.current = null;
|
|
4193
|
+
};
|
|
4194
|
+
}, [collabEnabled, yTextMap, bindAllBlocksToYText]);
|
|
4195
|
+
React.useEffect(() => {
|
|
4196
|
+
return () => {
|
|
4197
|
+
yTextBindingsRef.current.forEach((binding, blockId) => {
|
|
4198
|
+
binding.ytext.unobserve(binding.ytextObserver);
|
|
4199
|
+
binding.element.removeEventListener("input", binding.inputHandler);
|
|
4200
|
+
});
|
|
4201
|
+
yTextBindingsRef.current.clear();
|
|
4202
|
+
};
|
|
4203
|
+
}, []);
|
|
4204
|
+
const {
|
|
4205
|
+
snapshots,
|
|
4206
|
+
loading: versionHistoryLoading,
|
|
4207
|
+
error: versionHistoryError,
|
|
4208
|
+
fetchSnapshots,
|
|
4209
|
+
restoreSnapshot,
|
|
4210
|
+
createSnapshot
|
|
4211
|
+
} = useVersionHistory();
|
|
4212
|
+
React.useEffect(() => {
|
|
4213
|
+
if (showVersionHistory && collabRoomId) {
|
|
4214
|
+
fetchSnapshots(collabRoomId);
|
|
4215
|
+
}
|
|
4216
|
+
}, [showVersionHistory, collabRoomId, fetchSnapshots]);
|
|
3437
4217
|
React.useMemo(() => {
|
|
3438
4218
|
switch (collabRole) {
|
|
3439
4219
|
case "viewer":
|
|
@@ -3767,23 +4547,20 @@ const Editor = React.forwardRef(({
|
|
|
3767
4547
|
setWordCount(plainText.split(/\s+/).filter((w) => w.length > 0).length);
|
|
3768
4548
|
}, []);
|
|
3769
4549
|
const renderFromYDoc = React.useCallback(async () => {
|
|
3770
|
-
fetch("http://127.0.0.1:7242/ingest/12c1170b-f275-4a73-9ee7-3006bf7f0881", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ location: "EditorJS/index.jsx:renderFromYDoc:entry", message: "renderFromYDoc called", data: { collabEnabled, hasYBlocksMap: !!yBlocksMap, hasYDoc: !!yDoc, hasYMetaMap: !!yMetaMap }, timestamp: Date.now(), sessionId: "debug-session", hypothesisId: "A" }) }).catch(() => {
|
|
3771
|
-
});
|
|
3772
4550
|
if (!collabEnabled || !yBlocksMap || !yDoc) {
|
|
3773
|
-
fetch("http://127.0.0.1:7242/ingest/12c1170b-f275-4a73-9ee7-3006bf7f0881", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ location: "EditorJS/index.jsx:renderFromYDoc:guard1", message: "EARLY EXIT: missing collab deps", data: { collabEnabled, hasYBlocksMap: !!yBlocksMap, hasYDoc: !!yDoc }, timestamp: Date.now(), sessionId: "debug-session", hypothesisId: "A" }) }).catch(() => {
|
|
3774
|
-
});
|
|
3775
4551
|
return;
|
|
3776
4552
|
}
|
|
3777
4553
|
const editor = editorInstanceRef.current;
|
|
3778
4554
|
if (!editor || !isReadyRef.current) {
|
|
3779
|
-
|
|
3780
|
-
|
|
4555
|
+
pendingRenderRef.current = pendingRenderRef.current || true;
|
|
4556
|
+
return;
|
|
4557
|
+
}
|
|
4558
|
+
if (!editor.blocks || typeof editor.blocks.getBlocksCount !== "function") {
|
|
4559
|
+
console.warn("[Magic Editor X] Editor blocks API not ready for renderFromYDoc");
|
|
3781
4560
|
pendingRenderRef.current = pendingRenderRef.current || true;
|
|
3782
4561
|
return;
|
|
3783
4562
|
}
|
|
3784
4563
|
if (isApplyingRemoteRef.current) {
|
|
3785
|
-
fetch("http://127.0.0.1:7242/ingest/12c1170b-f275-4a73-9ee7-3006bf7f0881", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ location: "EditorJS/index.jsx:renderFromYDoc:guard3", message: "EARLY EXIT: isApplyingRemote is true (race condition)", data: { isApplyingRemote: true }, timestamp: Date.now(), sessionId: "debug-session", hypothesisId: "B" }) }).catch(() => {
|
|
3786
|
-
});
|
|
3787
4564
|
return;
|
|
3788
4565
|
}
|
|
3789
4566
|
isApplyingRemoteRef.current = true;
|
|
@@ -3801,29 +4578,35 @@ const Editor = React.forwardRef(({
|
|
|
3801
4578
|
yOrder = Array.from(yBlocksMap.keys());
|
|
3802
4579
|
}
|
|
3803
4580
|
const yBlocks = [];
|
|
3804
|
-
fetch("http://127.0.0.1:7242/ingest/12c1170b-f275-4a73-9ee7-3006bf7f0881", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ location: "EditorJS/index.jsx:renderFromYDoc:yState", message: "Y.js state before parsing", data: { blockOrderFromMeta: !!blockOrderJson, yBlocksMapSize: yBlocksMap?.size, yOrder, yBlocksMapKeys: Array.from(yBlocksMap?.keys() || []) }, timestamp: Date.now(), sessionId: "debug-session", hypothesisId: "C" }) }).catch(() => {
|
|
3805
|
-
});
|
|
3806
4581
|
yOrder.forEach((id) => {
|
|
3807
4582
|
const json = yBlocksMap.get(id);
|
|
3808
4583
|
if (json) {
|
|
3809
4584
|
try {
|
|
3810
|
-
const
|
|
3811
|
-
|
|
4585
|
+
const blockData = JSON.parse(json);
|
|
4586
|
+
if (blockData.type && !blockData.data) {
|
|
4587
|
+
const ytext = yTextMap?.get(id);
|
|
4588
|
+
const textContent = ytext ? collabDeltaToHtml(ytext.toDelta()) : "";
|
|
4589
|
+
yBlocks.push({
|
|
4590
|
+
id,
|
|
4591
|
+
type: blockData.type,
|
|
4592
|
+
data: { text: textContent },
|
|
4593
|
+
tunes: blockData.tunes || {}
|
|
4594
|
+
});
|
|
4595
|
+
} else if (blockData.type && blockData.data) {
|
|
4596
|
+
yBlocks.push({
|
|
4597
|
+
id,
|
|
4598
|
+
...blockData
|
|
4599
|
+
});
|
|
4600
|
+
}
|
|
3812
4601
|
} catch (e) {
|
|
3813
4602
|
console.warn("[Magic Editor X] Invalid block JSON:", id);
|
|
3814
4603
|
}
|
|
3815
4604
|
}
|
|
3816
4605
|
});
|
|
3817
|
-
fetch("http://127.0.0.1:7242/ingest/12c1170b-f275-4a73-9ee7-3006bf7f0881", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ location: "EditorJS/index.jsx:renderFromYDoc:parsed", message: "Parsed yBlocks from Y.Map", data: { yBlocksCount: yBlocks.length, yBlockIds: yBlocks.map((b) => b.id), yBlockTypes: yBlocks.map((b) => b.type) }, timestamp: Date.now(), sessionId: "debug-session", hypothesisId: "A" }) }).catch(() => {
|
|
3818
|
-
});
|
|
3819
4606
|
const parsed = { blocks: yBlocks };
|
|
3820
4607
|
const normalizedParsed = serializeForCompare(parsed);
|
|
3821
4608
|
const renderFull = async () => {
|
|
3822
|
-
fetch("http://127.0.0.1:7242/ingest/12c1170b-f275-4a73-9ee7-3006bf7f0881", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ location: "EditorJS/index.jsx:renderFull:start", message: "FULL RENDER triggered", data: { blocksToRender: yBlocks.length, blockIds: yBlocks.map((b) => b.id) }, timestamp: Date.now(), sessionId: "debug-session", hypothesisId: "E" }) }).catch(() => {
|
|
3823
|
-
});
|
|
3824
4609
|
await editor.render(parsed);
|
|
3825
|
-
fetch("http://127.0.0.1:7242/ingest/12c1170b-f275-4a73-9ee7-3006bf7f0881", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ location: "EditorJS/index.jsx:renderFull:done", message: "FULL RENDER completed", data: { newBlockCount: editor.blocks.getBlocksCount() }, timestamp: Date.now(), sessionId: "debug-session", hypothesisId: "E" }) }).catch(() => {
|
|
3826
|
-
});
|
|
3827
4610
|
lastSerializedValueRef.current = normalizedParsed;
|
|
3828
4611
|
setBlocksCount(yBlocks.length);
|
|
3829
4612
|
calculateStats(parsed);
|
|
@@ -3836,8 +4619,6 @@ const Editor = React.forwardRef(({
|
|
|
3836
4619
|
currentBlocks.push({ id: block.id, index: i });
|
|
3837
4620
|
}
|
|
3838
4621
|
}
|
|
3839
|
-
fetch("http://127.0.0.1:7242/ingest/12c1170b-f275-4a73-9ee7-3006bf7f0881", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ location: "EditorJS/index.jsx:renderFromYDoc:comparison", message: "Comparing editor vs Y.js state", data: { editorBlockCount: blockCount, yBlocksCount: yBlocks.length, editorBlockIds: currentBlocks.map((b) => b.id), yBlockIds: yBlocks.map((b) => b.id) }, timestamp: Date.now(), sessionId: "debug-session", hypothesisId: "E" }) }).catch(() => {
|
|
3840
|
-
});
|
|
3841
4622
|
if (blockCount !== yBlocks.length) {
|
|
3842
4623
|
console.log("[Magic Editor X] [SYNC] Structural change detected (count mismatch). Falling back to full render.");
|
|
3843
4624
|
await renderFull();
|
|
@@ -4031,6 +4812,7 @@ const Editor = React.forwardRef(({
|
|
|
4031
4812
|
return;
|
|
4032
4813
|
}
|
|
4033
4814
|
const editor = editorInstanceRef.current;
|
|
4815
|
+
if (!editor.blocks || typeof editor.blocks.getBlocksCount !== "function") return;
|
|
4034
4816
|
const lastIndex = editor.blocks.getBlocksCount();
|
|
4035
4817
|
editor.blocks.insert(blockType, {}, {}, lastIndex, true);
|
|
4036
4818
|
editor.caret.setToBlock(lastIndex);
|
|
@@ -4042,7 +4824,7 @@ const Editor = React.forwardRef(({
|
|
|
4042
4824
|
const emptyPayload = JSON.stringify({ blocks: [] });
|
|
4043
4825
|
lastSerializedValueRef.current = emptyPayload;
|
|
4044
4826
|
pushLocalToCollab(emptyPayload);
|
|
4045
|
-
onChange({ target: { name, value: null, type: "
|
|
4827
|
+
onChange({ target: { name, value: null, type: "json" } });
|
|
4046
4828
|
setBlocksCount(0);
|
|
4047
4829
|
setWordCount(0);
|
|
4048
4830
|
setCharCount(0);
|
|
@@ -4055,7 +4837,11 @@ const Editor = React.forwardRef(({
|
|
|
4055
4837
|
}, [isReady]);
|
|
4056
4838
|
React.useEffect(() => {
|
|
4057
4839
|
if (editorRef.current && !editorInstanceRef.current) {
|
|
4058
|
-
const tools$1 = tools.getTools({
|
|
4840
|
+
const tools$1 = tools.getTools({
|
|
4841
|
+
mediaLibToggleFunc,
|
|
4842
|
+
pluginId: index.PLUGIN_ID,
|
|
4843
|
+
openLinkPicker: isWebtoolsAvailable ? webtoolsOpenLinkPicker : null
|
|
4844
|
+
});
|
|
4059
4845
|
let initialData = void 0;
|
|
4060
4846
|
if (value) {
|
|
4061
4847
|
try {
|
|
@@ -4074,6 +4860,18 @@ const Editor = React.forwardRef(({
|
|
|
4074
4860
|
editorRef.current.classList.add("editor-readonly");
|
|
4075
4861
|
}
|
|
4076
4862
|
}
|
|
4863
|
+
console.log("[Magic Editor X] Registered tools:", Object.keys(tools$1));
|
|
4864
|
+
const inlineTools = Object.entries(tools$1).filter(([name2, config]) => {
|
|
4865
|
+
const toolClass = config.class || config;
|
|
4866
|
+
const isInline = toolClass?.isInline === true;
|
|
4867
|
+
if (isInline) {
|
|
4868
|
+
console.log(`[Magic Editor X] Found inline tool: ${name2}`, toolClass);
|
|
4869
|
+
}
|
|
4870
|
+
return isInline;
|
|
4871
|
+
}).map(([name2]) => name2);
|
|
4872
|
+
console.log("[Magic Editor X] Inline tools found:", inlineTools);
|
|
4873
|
+
console.log("[Magic Editor X] Marker isInline:", tools$1.marker?.class?.isInline);
|
|
4874
|
+
console.log("[Magic Editor X] Bold isInline:", tools$1.bold?.class?.isInline);
|
|
4077
4875
|
const editor = new EditorJS__default.default({
|
|
4078
4876
|
holder: editorRef.current,
|
|
4079
4877
|
tools: tools$1,
|
|
@@ -4082,23 +4880,35 @@ const Editor = React.forwardRef(({
|
|
|
4082
4880
|
placeholder: customPlaceholder,
|
|
4083
4881
|
minHeight: 200,
|
|
4084
4882
|
autofocus: false,
|
|
4883
|
+
// Note: Do NOT set inlineToolbar here - each block tool controls its own inline toolbar
|
|
4884
|
+
// The inline tools (bold, italic, marker, etc.) are automatically available when block tools have inlineToolbar: true
|
|
4085
4885
|
onReady: async () => {
|
|
4086
4886
|
isReadyRef.current = true;
|
|
4087
4887
|
setIsReady(true);
|
|
4088
4888
|
console.log("[Magic Editor X] [READY] Editor onReady fired");
|
|
4089
4889
|
console.log("[Magic Editor X] [READY] Editor holder:", editorRef.current?.id);
|
|
4090
|
-
|
|
4091
|
-
|
|
4092
|
-
|
|
4093
|
-
|
|
4094
|
-
|
|
4095
|
-
|
|
4096
|
-
|
|
4097
|
-
|
|
4098
|
-
|
|
4099
|
-
|
|
4100
|
-
|
|
4101
|
-
|
|
4890
|
+
setTimeout(() => {
|
|
4891
|
+
try {
|
|
4892
|
+
if (editor && editor.blocks && typeof editor.blocks.getBlocksCount === "function") {
|
|
4893
|
+
tools.initUndoRedo(editor);
|
|
4894
|
+
console.log("[Magic Editor X] [SUCCESS] Undo/Redo initialized");
|
|
4895
|
+
} else {
|
|
4896
|
+
console.warn("[Magic Editor X] Editor blocks API not ready for Undo/Redo");
|
|
4897
|
+
}
|
|
4898
|
+
} catch (e) {
|
|
4899
|
+
console.warn("[Magic Editor X] Could not initialize Undo/Redo:", e);
|
|
4900
|
+
}
|
|
4901
|
+
try {
|
|
4902
|
+
if (editor && editor.blocks && typeof editor.blocks.getBlocksCount === "function") {
|
|
4903
|
+
tools.initDragDrop(editor);
|
|
4904
|
+
console.log("[Magic Editor X] [SUCCESS] Drag & Drop initialized");
|
|
4905
|
+
} else {
|
|
4906
|
+
console.warn("[Magic Editor X] Editor blocks API not ready for Drag & Drop");
|
|
4907
|
+
}
|
|
4908
|
+
} catch (e) {
|
|
4909
|
+
console.warn("[Magic Editor X] Could not initialize Drag & Drop:", e);
|
|
4910
|
+
}
|
|
4911
|
+
}, 500);
|
|
4102
4912
|
if (pendingRenderRef.current) {
|
|
4103
4913
|
try {
|
|
4104
4914
|
if (typeof pendingRenderRef.current === "object" && pendingRenderRef.current.blocks) {
|
|
@@ -4121,6 +4931,52 @@ const Editor = React.forwardRef(({
|
|
|
4121
4931
|
}
|
|
4122
4932
|
pendingRenderRef.current = null;
|
|
4123
4933
|
}
|
|
4934
|
+
if (collabEnabled && yTextMap) {
|
|
4935
|
+
setTimeout(() => {
|
|
4936
|
+
bindAllBlocksToYText();
|
|
4937
|
+
}, 100);
|
|
4938
|
+
}
|
|
4939
|
+
if (isWebtoolsAvailable && webtoolsOpenLinkPicker && editorRef.current) {
|
|
4940
|
+
const handleLinkClick = async (e) => {
|
|
4941
|
+
const anchor = e.target.closest("a");
|
|
4942
|
+
if (anchor && editorRef.current?.contains(anchor)) {
|
|
4943
|
+
e.preventDefault();
|
|
4944
|
+
e.stopPropagation();
|
|
4945
|
+
const existingHref = anchor.href || "";
|
|
4946
|
+
const existingText = anchor.textContent || "";
|
|
4947
|
+
console.log("[Magic Editor X] Link clicked, opening editor:", existingHref);
|
|
4948
|
+
try {
|
|
4949
|
+
const selection = window.getSelection();
|
|
4950
|
+
const range = document.createRange();
|
|
4951
|
+
range.selectNodeContents(anchor);
|
|
4952
|
+
selection.removeAllRanges();
|
|
4953
|
+
selection.addRange(range);
|
|
4954
|
+
const result = await webtoolsOpenLinkPicker({
|
|
4955
|
+
initialText: existingText,
|
|
4956
|
+
initialHref: existingHref
|
|
4957
|
+
});
|
|
4958
|
+
if (result && result.href) {
|
|
4959
|
+
anchor.href = result.href;
|
|
4960
|
+
if (result.label && result.label !== existingText) {
|
|
4961
|
+
anchor.textContent = result.label;
|
|
4962
|
+
}
|
|
4963
|
+
console.log("[Magic Editor X] Link updated:", result.href);
|
|
4964
|
+
} else if (result === null) {
|
|
4965
|
+
console.log("[Magic Editor X] Link edit cancelled");
|
|
4966
|
+
}
|
|
4967
|
+
} catch (err) {
|
|
4968
|
+
console.error("[Magic Editor X] Error editing link:", err);
|
|
4969
|
+
}
|
|
4970
|
+
}
|
|
4971
|
+
};
|
|
4972
|
+
editorRef.current.addEventListener("click", handleLinkClick);
|
|
4973
|
+
const cleanup = () => {
|
|
4974
|
+
editorRef.current?.removeEventListener("click", handleLinkClick);
|
|
4975
|
+
};
|
|
4976
|
+
if (!editorRef.current._linkClickCleanup) {
|
|
4977
|
+
editorRef.current._linkClickCleanup = cleanup;
|
|
4978
|
+
}
|
|
4979
|
+
}
|
|
4124
4980
|
},
|
|
4125
4981
|
onChange: async (api) => {
|
|
4126
4982
|
try {
|
|
@@ -4140,9 +4996,9 @@ const Editor = React.forwardRef(({
|
|
|
4140
4996
|
pushLocalToCollabRef.current?.(docPayload);
|
|
4141
4997
|
lastSerializedValueRef.current = normalized;
|
|
4142
4998
|
if (count === 0) {
|
|
4143
|
-
onChange({ target: { name, value: null, type: "
|
|
4999
|
+
onChange({ target: { name, value: null, type: "json" } });
|
|
4144
5000
|
} else {
|
|
4145
|
-
onChange({ target: { name, value:
|
|
5001
|
+
onChange({ target: { name, value: outputData, type: "json" } });
|
|
4146
5002
|
}
|
|
4147
5003
|
} catch (error2) {
|
|
4148
5004
|
console.error("[Magic Editor X] Error in onChange:", error2);
|
|
@@ -4155,6 +5011,10 @@ const Editor = React.forwardRef(({
|
|
|
4155
5011
|
console.log("[Magic Editor X] [CLEANUP] Editor component unmounting, destroying editor");
|
|
4156
5012
|
isReadyRef.current = false;
|
|
4157
5013
|
setIsReady(false);
|
|
5014
|
+
if (editorRef.current?._linkClickCleanup) {
|
|
5015
|
+
editorRef.current._linkClickCleanup();
|
|
5016
|
+
delete editorRef.current._linkClickCleanup;
|
|
5017
|
+
}
|
|
4158
5018
|
if (editorInstanceRef.current && editorInstanceRef.current.destroy) {
|
|
4159
5019
|
try {
|
|
4160
5020
|
editorInstanceRef.current.destroy();
|
|
@@ -4265,6 +5125,96 @@ const Editor = React.forwardRef(({
|
|
|
4265
5125
|
children: /* @__PURE__ */ jsxRuntime.jsx(outline.SparklesIcon, {})
|
|
4266
5126
|
}
|
|
4267
5127
|
),
|
|
5128
|
+
isWebtoolsAvailable && /* @__PURE__ */ jsxRuntime.jsx(
|
|
5129
|
+
ToolButton,
|
|
5130
|
+
{
|
|
5131
|
+
type: "button",
|
|
5132
|
+
"data-tooltip": "Webtools Link Picker",
|
|
5133
|
+
onClick: async () => {
|
|
5134
|
+
if (!editorInstanceRef.current || !isReady) {
|
|
5135
|
+
console.warn("[Magic Editor X] Editor not ready");
|
|
5136
|
+
return;
|
|
5137
|
+
}
|
|
5138
|
+
const editor = editorInstanceRef.current;
|
|
5139
|
+
const {
|
|
5140
|
+
text: selectedText,
|
|
5141
|
+
range: savedRange,
|
|
5142
|
+
blockIndex,
|
|
5143
|
+
existingAnchor,
|
|
5144
|
+
existingHref
|
|
5145
|
+
} = webtoolsSelectionRef.current;
|
|
5146
|
+
console.log("[Magic Editor X] Webtools button clicked with stored selection:", {
|
|
5147
|
+
text: selectedText || "(none)",
|
|
5148
|
+
existingHref: existingHref || "(new link)",
|
|
5149
|
+
hasRange: !!savedRange,
|
|
5150
|
+
blockIndex
|
|
5151
|
+
});
|
|
5152
|
+
const currentBlockIndex = blockIndex >= 0 ? blockIndex : editor.blocks.getCurrentBlockIndex();
|
|
5153
|
+
const result = await webtoolsOpenLinkPicker({
|
|
5154
|
+
initialText: selectedText || "",
|
|
5155
|
+
initialHref: existingHref || ""
|
|
5156
|
+
});
|
|
5157
|
+
if (result && result.href) {
|
|
5158
|
+
const linkText = result.label || selectedText || result.href;
|
|
5159
|
+
const linkHtml = `<a href="${result.href}" target="_blank" rel="noopener noreferrer">${linkText}</a>`;
|
|
5160
|
+
if (existingAnchor && existingAnchor.parentNode) {
|
|
5161
|
+
try {
|
|
5162
|
+
existingAnchor.href = result.href;
|
|
5163
|
+
existingAnchor.textContent = linkText;
|
|
5164
|
+
const contentEditable = existingAnchor.closest('[contenteditable="true"]');
|
|
5165
|
+
if (contentEditable) {
|
|
5166
|
+
contentEditable.dispatchEvent(new Event("input", { bubbles: true }));
|
|
5167
|
+
}
|
|
5168
|
+
console.log("[Magic Editor X] Webtools link UPDATED:", {
|
|
5169
|
+
oldHref: existingHref,
|
|
5170
|
+
newHref: result.href,
|
|
5171
|
+
text: linkText
|
|
5172
|
+
});
|
|
5173
|
+
} catch (e) {
|
|
5174
|
+
console.error("[Magic Editor X] Failed to update link:", e);
|
|
5175
|
+
}
|
|
5176
|
+
} else if (savedRange && selectedText && currentBlockIndex >= 0) {
|
|
5177
|
+
try {
|
|
5178
|
+
const blockHolder = editor.blocks.getBlockByIndex(currentBlockIndex)?.holder;
|
|
5179
|
+
const contentEditable = blockHolder?.querySelector('[contenteditable="true"]');
|
|
5180
|
+
if (contentEditable) {
|
|
5181
|
+
const selection = window.getSelection();
|
|
5182
|
+
selection.removeAllRanges();
|
|
5183
|
+
selection.addRange(savedRange);
|
|
5184
|
+
document.execCommand("insertHTML", false, linkHtml);
|
|
5185
|
+
contentEditable.dispatchEvent(new Event("input", { bubbles: true }));
|
|
5186
|
+
console.log("[Magic Editor X] Webtools link CREATED:", {
|
|
5187
|
+
text: linkText,
|
|
5188
|
+
href: result.href
|
|
5189
|
+
});
|
|
5190
|
+
} else {
|
|
5191
|
+
editor.blocks.insert("paragraph", { text: linkHtml }, {}, currentBlockIndex + 1, true);
|
|
5192
|
+
}
|
|
5193
|
+
} catch (e) {
|
|
5194
|
+
console.error("[Magic Editor X] Failed to insert link:", e);
|
|
5195
|
+
editor.blocks.insert("paragraph", { text: linkHtml }, {}, currentBlockIndex + 1, true);
|
|
5196
|
+
}
|
|
5197
|
+
} else if (currentBlockIndex >= 0) {
|
|
5198
|
+
editor.blocks.insert("paragraph", {
|
|
5199
|
+
text: linkHtml
|
|
5200
|
+
}, {}, currentBlockIndex + 1, true);
|
|
5201
|
+
editor.caret.setToBlock(currentBlockIndex + 1);
|
|
5202
|
+
console.log("[Magic Editor X] Webtools link inserted (no selection):", result);
|
|
5203
|
+
} else {
|
|
5204
|
+
editor.blocks.insert("paragraph", { text: linkHtml });
|
|
5205
|
+
}
|
|
5206
|
+
}
|
|
5207
|
+
webtoolsSelectionRef.current = { text: "", range: null, blockIndex: -1, existingAnchor: null, existingHref: "" };
|
|
5208
|
+
},
|
|
5209
|
+
disabled: collabEnabled && collabCanEdit === false,
|
|
5210
|
+
style: {
|
|
5211
|
+
background: "linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)",
|
|
5212
|
+
color: "white",
|
|
5213
|
+
...collabEnabled && collabCanEdit === false ? { opacity: 0.4, cursor: "not-allowed" } : {}
|
|
5214
|
+
},
|
|
5215
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(outline.LinkIcon, {})
|
|
5216
|
+
}
|
|
5217
|
+
),
|
|
4268
5218
|
/* @__PURE__ */ jsxRuntime.jsx(ToolbarDivider, {}),
|
|
4269
5219
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4270
5220
|
ToolButton,
|
|
@@ -4380,9 +5330,26 @@ const Editor = React.forwardRef(({
|
|
|
4380
5330
|
/* @__PURE__ */ jsxRuntime.jsx("strong", { children: charCount }),
|
|
4381
5331
|
" ",
|
|
4382
5332
|
t("editor.characters", "Zeichen")
|
|
4383
|
-
] })
|
|
5333
|
+
] }),
|
|
5334
|
+
!isWebtoolsAvailable && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
5335
|
+
WebtoolsPromoLink,
|
|
5336
|
+
{
|
|
5337
|
+
href: "https://www.pluginpal.io/plugin/webtools",
|
|
5338
|
+
target: "_blank",
|
|
5339
|
+
rel: "noopener noreferrer",
|
|
5340
|
+
title: "Get Webtools Links addon for internal link management",
|
|
5341
|
+
children: [
|
|
5342
|
+
/* @__PURE__ */ jsxRuntime.jsx(outline.LinkIcon, {}),
|
|
5343
|
+
"Internal Links? Get Webtools"
|
|
5344
|
+
]
|
|
5345
|
+
}
|
|
5346
|
+
)
|
|
4384
5347
|
] }),
|
|
4385
5348
|
/* @__PURE__ */ jsxRuntime.jsxs(FooterRight, { children: [
|
|
5349
|
+
/* @__PURE__ */ jsxRuntime.jsxs(FooterButton, { type: "button", onClick: () => setShowVersionHistory(true), children: [
|
|
5350
|
+
/* @__PURE__ */ jsxRuntime.jsx(outline.ClockIcon, {}),
|
|
5351
|
+
t("editor.versionHistory", "History")
|
|
5352
|
+
] }),
|
|
4386
5353
|
!(collabEnabled && collabCanEdit === false) && /* @__PURE__ */ jsxRuntime.jsxs(FooterButton, { type: "button", onClick: () => handleInsertBlock("mediaLib"), children: [
|
|
4387
5354
|
/* @__PURE__ */ jsxRuntime.jsx(outline.PhotoIcon, {}),
|
|
4388
5355
|
t("editor.mediaLibrary", "Media Library")
|
|
@@ -4455,6 +5422,51 @@ const Editor = React.forwardRef(({
|
|
|
4455
5422
|
}
|
|
4456
5423
|
}
|
|
4457
5424
|
),
|
|
5425
|
+
showVersionHistory && /* @__PURE__ */ jsxRuntime.jsx(VersionHistoryOverlay, { onClick: () => setShowVersionHistory(false), children: /* @__PURE__ */ jsxRuntime.jsx("div", { onClick: (e) => e.stopPropagation(), children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
5426
|
+
VersionHistoryPanel,
|
|
5427
|
+
{
|
|
5428
|
+
snapshots,
|
|
5429
|
+
loading: versionHistoryLoading,
|
|
5430
|
+
error: versionHistoryError,
|
|
5431
|
+
tier: licenseTier,
|
|
5432
|
+
onClose: () => setShowVersionHistory(false),
|
|
5433
|
+
onRestore: async (snapshot) => {
|
|
5434
|
+
if (snapshot.documentId && editorInstanceRef.current && isReady) {
|
|
5435
|
+
try {
|
|
5436
|
+
const result = await restoreSnapshot(snapshot.documentId, collabRoomId);
|
|
5437
|
+
const contentToRestore = result?.jsonContent || snapshot.jsonContent;
|
|
5438
|
+
if (contentToRestore && editorInstanceRef.current) {
|
|
5439
|
+
await editorInstanceRef.current.render(contentToRestore);
|
|
5440
|
+
setShowVersionHistory(false);
|
|
5441
|
+
onChange({ target: { name, value: contentToRestore, type: "json" } });
|
|
5442
|
+
}
|
|
5443
|
+
} catch (err) {
|
|
5444
|
+
console.error("[Magic Editor X] Failed to restore snapshot:", err?.message);
|
|
5445
|
+
}
|
|
5446
|
+
}
|
|
5447
|
+
},
|
|
5448
|
+
onCreate: async () => {
|
|
5449
|
+
if (collabRoomId && editorInstanceRef.current && isReady) {
|
|
5450
|
+
const [contentType, entryId, fieldName] = collabRoomId.split("|");
|
|
5451
|
+
if (contentType && entryId && fieldName) {
|
|
5452
|
+
try {
|
|
5453
|
+
const editorContent = await editorInstanceRef.current.save();
|
|
5454
|
+
await createSnapshot({
|
|
5455
|
+
roomId: collabRoomId,
|
|
5456
|
+
contentType,
|
|
5457
|
+
entryId,
|
|
5458
|
+
fieldName,
|
|
5459
|
+
content: editorContent
|
|
5460
|
+
});
|
|
5461
|
+
fetchSnapshots(collabRoomId);
|
|
5462
|
+
} catch (err) {
|
|
5463
|
+
console.error("[Magic Editor X] Failed to create snapshot:", err?.message);
|
|
5464
|
+
}
|
|
5465
|
+
}
|
|
5466
|
+
}
|
|
5467
|
+
}
|
|
5468
|
+
}
|
|
5469
|
+
) }) }),
|
|
4458
5470
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4459
5471
|
CreditsModal,
|
|
4460
5472
|
{
|