@broberg/cms-inline-edit 0.4.20 → 0.5.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 +53 -0
- package/dist/index.cjs +376 -13
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +34 -1
- package/dist/index.d.ts +34 -1
- package/dist/index.js +375 -13
- package/dist/index.js.map +1 -1
- package/dist/server/index.cjs +29 -0
- package/dist/server/index.cjs.map +1 -1
- package/dist/server/index.d.cts +10 -1
- package/dist/server/index.d.ts +10 -1
- package/dist/server/index.js +28 -0
- package/dist/server/index.js.map +1 -1
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -8,6 +8,36 @@ function applyFieldSlice(current, original, next) {
|
|
|
8
8
|
return current.slice(0, first) + next + current.slice(first + original.length);
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
+
// src/token-safe.ts
|
|
12
|
+
var TEXT_NODE = 3;
|
|
13
|
+
var ELEMENT_NODE = 1;
|
|
14
|
+
function hasTokenChips(el) {
|
|
15
|
+
return el.querySelector("[data-cms-token]") !== null;
|
|
16
|
+
}
|
|
17
|
+
function serializeTokenSafe(el) {
|
|
18
|
+
let out = "";
|
|
19
|
+
el.childNodes.forEach((node) => {
|
|
20
|
+
if (node.nodeType === TEXT_NODE) {
|
|
21
|
+
out += node.textContent ?? "";
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
if (node.nodeType !== ELEMENT_NODE) return;
|
|
25
|
+
const element = node;
|
|
26
|
+
const token = element.getAttribute("data-cms-token");
|
|
27
|
+
if (token) {
|
|
28
|
+
out += token;
|
|
29
|
+
} else {
|
|
30
|
+
out += serializeTokenSafe(element);
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
return out;
|
|
34
|
+
}
|
|
35
|
+
function lockTokenChips(el) {
|
|
36
|
+
el.querySelectorAll("[data-cms-token]").forEach((chip) => {
|
|
37
|
+
chip.setAttribute("contenteditable", "false");
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
11
41
|
// src/index.ts
|
|
12
42
|
var DEFAULT_LABELS = {
|
|
13
43
|
bold: "Fed",
|
|
@@ -20,7 +50,27 @@ var DEFAULT_LABELS = {
|
|
|
20
50
|
done: "F\xE6rdig",
|
|
21
51
|
saving: "Gemmer\u2026",
|
|
22
52
|
saved: "Gemt \u2713",
|
|
23
|
-
error: "Fejl \u2014 pr\xF8v igen"
|
|
53
|
+
error: "Fejl \u2014 pr\xF8v igen",
|
|
54
|
+
link: "Link",
|
|
55
|
+
linkTabPage: "Side p\xE5 sitet",
|
|
56
|
+
linkTabUrl: "Fri adresse",
|
|
57
|
+
linkSearch: "S\xF8g efter en side\u2026",
|
|
58
|
+
linkText: "Linktekst",
|
|
59
|
+
linkTextAuto: "F\xF8lger sidens titel",
|
|
60
|
+
linkTextAutoHint: "<b>Tom = f\xF8lger sidens titel.</b> Omd\xF8bes siden, retter teksten sig selv. Skriver du din egen tekst, bliver den st\xE5ende.",
|
|
61
|
+
linkTextOwnHint: "Linket viser <b>din egen tekst</b>. Den bliver st\xE5ende, ogs\xE5 hvis siden omd\xF8bes.",
|
|
62
|
+
linkLiveHint: "Linket peger p\xE5 <b>siden</b> \u2014 ikke p\xE5 en adresse. Flyttes siden, eller f\xE5r den et nyt navn, retter linket sig selv.",
|
|
63
|
+
linkUrlHint: "En fri adresse peger pr\xE6cis d\xE9r, du skriver. Den f\xF8lger <b>ikke</b> med, hvis m\xE5let flytter sig.",
|
|
64
|
+
linkUrl: "Adresse",
|
|
65
|
+
linkInsert: "Inds\xE6t link",
|
|
66
|
+
linkSave: "Gem \xE6ndring",
|
|
67
|
+
linkCancel: "Annull\xE9r",
|
|
68
|
+
linkRemove: "Fjern link",
|
|
69
|
+
linkRemoveConfirm: "Fjern?",
|
|
70
|
+
linkYes: "Ja",
|
|
71
|
+
linkNo: "Nej",
|
|
72
|
+
linkEmpty: "Ingen sider fundet",
|
|
73
|
+
linkLoading: "Henter sider\u2026"
|
|
24
74
|
};
|
|
25
75
|
var uiLabels = DEFAULT_LABELS;
|
|
26
76
|
function resolveOptions(options) {
|
|
@@ -122,6 +172,7 @@ function isExpired(token) {
|
|
|
122
172
|
var SQUARE_PEN_SVG = '<svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.375 2.625a1 1 0 0 1 3 3l-9.013 9.014a2 2 0 0 1-.853.505l-2.873.84a.5.5 0 0 1-.62-.62l.84-2.873a2 2 0 0 1 .506-.852z"/></svg>';
|
|
123
173
|
var UL_SVG = '<svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><line x1="8" x2="21" y1="6" y2="6"/><line x1="8" x2="21" y1="12" y2="12"/><line x1="8" x2="21" y1="18" y2="18"/><line x1="3" x2="3.01" y1="6" y2="6"/><line x1="3" x2="3.01" y1="12" y2="12"/><line x1="3" x2="3.01" y1="18" y2="18"/></svg>';
|
|
124
174
|
var OL_SVG = '<svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><line x1="10" x2="21" y1="6" y2="6"/><line x1="10" x2="21" y1="12" y2="12"/><line x1="10" x2="21" y1="18" y2="18"/><path d="M4 6h1v4"/><path d="M4 10h2"/><path d="M6 18H4c0-1 2-2 2-3s-1-1.5-2-1"/></svg>';
|
|
175
|
+
var LINK_SVG = '<svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M10 13a5 5 0 0 0 7.5.5l3-3a5 5 0 0 0-7-7l-1.7 1.7"/><path d="M14 11a5 5 0 0 0-7.5-.5l-3 3a5 5 0 0 0 7 7l1.7-1.7"/></svg>';
|
|
125
176
|
function makeIcon() {
|
|
126
177
|
const icon = document.createElement("span");
|
|
127
178
|
icon.style.cssText = "display:inline-flex;line-height:0;flex:0 0 auto";
|
|
@@ -228,14 +279,16 @@ function wireField(el, token, options) {
|
|
|
228
279
|
if (el.getAttribute("contenteditable") === "true") return;
|
|
229
280
|
e.preventDefault();
|
|
230
281
|
e.stopPropagation();
|
|
231
|
-
|
|
282
|
+
const tokenSafe = hasTokenChips(el);
|
|
283
|
+
el.dataset.cmsOriginalValue = tokenSafe ? serializeTokenSafe(el) : el.textContent ?? "";
|
|
232
284
|
el.setAttribute("contenteditable", "true");
|
|
285
|
+
if (tokenSafe) lockTokenChips(el);
|
|
233
286
|
el.focus();
|
|
234
287
|
});
|
|
235
288
|
el.addEventListener("blur", () => {
|
|
236
289
|
el.removeAttribute("contenteditable");
|
|
237
290
|
const original = el.dataset.cmsOriginalValue ?? "";
|
|
238
|
-
const current = el.textContent ?? "";
|
|
291
|
+
const current = hasTokenChips(el) ? serializeTokenSafe(el) : el.textContent ?? "";
|
|
239
292
|
if (current.trim() === original.trim()) return;
|
|
240
293
|
void saveField(el, current.trim(), token, options);
|
|
241
294
|
});
|
|
@@ -278,6 +331,7 @@ function deactivateRich() {
|
|
|
278
331
|
el.removeAttribute("contenteditable");
|
|
279
332
|
el.classList.remove("cms-rich-editing");
|
|
280
333
|
hideRichToolbar();
|
|
334
|
+
hideLinkDialog();
|
|
281
335
|
if (el.innerHTML !== originalHtml) {
|
|
282
336
|
const value = mode === "html" ? el.innerHTML : htmlToMarkdown(el.innerHTML);
|
|
283
337
|
void saveField(el, value, token, options);
|
|
@@ -323,6 +377,10 @@ function buildRichToolbar() {
|
|
|
323
377
|
t.appendChild(toolbarButton(UL_SVG, uiLabels.unorderedList, () => document.execCommand("insertUnorderedList")));
|
|
324
378
|
t.appendChild(toolbarButton(OL_SVG, uiLabels.orderedList, () => document.execCommand("insertOrderedList")));
|
|
325
379
|
t.appendChild(sep());
|
|
380
|
+
const linkBtn = toolbarButton(LINK_SVG, uiLabels.link, () => toggleLinkDialog(linkBtn));
|
|
381
|
+
linkBtn.setAttribute("data-testid", "inline-toolbar-link");
|
|
382
|
+
t.appendChild(linkBtn);
|
|
383
|
+
t.appendChild(sep());
|
|
326
384
|
const clrLabel = document.createElement("label");
|
|
327
385
|
clrLabel.style.cssText = "display:flex;align-items:center;gap:5px;color:#9aa4b2;font-size:12px;cursor:pointer;";
|
|
328
386
|
clrLabel.textContent = uiLabels.color;
|
|
@@ -356,6 +414,231 @@ var EMOJIS = "\u{1F600} \u{1F603} \u{1F604} \u{1F601} \u{1F606} \u{1F609} \u{1F6
|
|
|
356
414
|
);
|
|
357
415
|
var emojiPicker = null;
|
|
358
416
|
var savedRange = null;
|
|
417
|
+
var linkDialog = null;
|
|
418
|
+
var linkPages = null;
|
|
419
|
+
var linkPicked = null;
|
|
420
|
+
var linkEditing = null;
|
|
421
|
+
function hideLinkDialog() {
|
|
422
|
+
if (linkDialog) linkDialog.style.display = "none";
|
|
423
|
+
linkPicked = null;
|
|
424
|
+
linkEditing = null;
|
|
425
|
+
}
|
|
426
|
+
function anchorAtCaret() {
|
|
427
|
+
const sel = window.getSelection();
|
|
428
|
+
if (!sel || sel.rangeCount === 0) return null;
|
|
429
|
+
let node = sel.getRangeAt(0).startContainer;
|
|
430
|
+
while (node && node !== document.body) {
|
|
431
|
+
if (node.nodeType === Node.ELEMENT_NODE && node.tagName === "A") {
|
|
432
|
+
return node;
|
|
433
|
+
}
|
|
434
|
+
node = node.parentNode;
|
|
435
|
+
}
|
|
436
|
+
return null;
|
|
437
|
+
}
|
|
438
|
+
async function fetchLinkablePages() {
|
|
439
|
+
if (linkPages) return linkPages;
|
|
440
|
+
if (!richCtx) return [];
|
|
441
|
+
const { options, token } = richCtx;
|
|
442
|
+
const url = `${options.cmsBaseUrl}/api/inline-edit/pages?site=${encodeURIComponent(options.siteId)}`;
|
|
443
|
+
const res = await fetch(url, { headers: { Authorization: `Bearer ${token}` } });
|
|
444
|
+
if (!res.ok) throw new Error(`pages ${res.status}`);
|
|
445
|
+
const body = await res.json();
|
|
446
|
+
linkPages = body.pages ?? [];
|
|
447
|
+
return linkPages;
|
|
448
|
+
}
|
|
449
|
+
function toggleLinkDialog(anchor) {
|
|
450
|
+
if (linkDialog && linkDialog.style.display === "block") {
|
|
451
|
+
hideLinkDialog();
|
|
452
|
+
return;
|
|
453
|
+
}
|
|
454
|
+
const sel = window.getSelection();
|
|
455
|
+
if (sel && sel.rangeCount > 0) savedRange = sel.getRangeAt(0).cloneRange();
|
|
456
|
+
linkEditing = anchorAtCaret();
|
|
457
|
+
if (!linkDialog) linkDialog = buildLinkDialog();
|
|
458
|
+
renderLinkDialog();
|
|
459
|
+
const rect = anchor.getBoundingClientRect();
|
|
460
|
+
linkDialog.style.top = `${rect.bottom + 8}px`;
|
|
461
|
+
linkDialog.style.left = `${Math.min(Math.max(8, rect.left - 180), window.innerWidth - 400)}px`;
|
|
462
|
+
linkDialog.style.display = "block";
|
|
463
|
+
}
|
|
464
|
+
function buildLinkDialog() {
|
|
465
|
+
const d = document.createElement("div");
|
|
466
|
+
d.setAttribute("data-cms-inline-edit-toolbar", "");
|
|
467
|
+
d.setAttribute("data-testid", "inline-link-dialog");
|
|
468
|
+
d.style.cssText = "position:fixed;z-index:2147483646;background:#1c2027;border:1px solid #3a3f4a;border-radius:10px;width:392px;display:none;box-shadow:0 8px 32px rgba(0,0,0,.5);font-family:system-ui,sans-serif;color:#fff;overflow:hidden;";
|
|
469
|
+
d.addEventListener("mousedown", (e) => {
|
|
470
|
+
const t = e.target;
|
|
471
|
+
if (t.tagName !== "INPUT") e.preventDefault();
|
|
472
|
+
e.stopPropagation();
|
|
473
|
+
});
|
|
474
|
+
document.body.appendChild(d);
|
|
475
|
+
return d;
|
|
476
|
+
}
|
|
477
|
+
function renderLinkDialog() {
|
|
478
|
+
const d = linkDialog;
|
|
479
|
+
if (!d) return;
|
|
480
|
+
const L = uiLabels;
|
|
481
|
+
const editing = !!linkEditing;
|
|
482
|
+
const existingRef = linkEditing?.getAttribute("data-cms-ref") ?? "";
|
|
483
|
+
const onPageTab = !editing || !!existingRef;
|
|
484
|
+
d.innerHTML = `<div style="display:flex;gap:4px;padding:8px 8px 0"><button type="button" data-testid="inline-link-tab-page" data-tab="page" style="${tabCss(onPageTab)}">${L.linkTabPage}</button><button type="button" data-testid="inline-link-tab-url" data-tab="url" style="${tabCss(!onPageTab)}">${L.linkTabUrl}</button></div><div style="padding:10px 12px 12px"><div data-pane="page" style="display:${onPageTab ? "block" : "none"}"><input data-testid="inline-link-search" data-role="search" placeholder="${L.linkSearch}" style="${fieldCss()}"><div data-role="list" data-testid="inline-link-list" style="margin-top:8px;max-height:196px;overflow-y:auto;border:1px solid #3a3f4a;border-radius:8px;background:#141821"></div></div><div data-pane="url" style="display:${onPageTab ? "none" : "block"}"><label style="${labelCss()}">${L.linkUrl}</label><input data-testid="inline-link-url" data-role="url" placeholder="https://\u2026" style="${fieldCss()}"></div><label style="${labelCss()}">${L.linkText}</label><input data-testid="inline-link-text" data-role="text" placeholder="${L.linkTextAuto}" style="${fieldCss()}"><p data-role="hint" style="font-size:11.5px;color:#9aa3b2;margin:6px 0 0;line-height:1.5"></p><div data-role="live" style="display:flex;gap:8px;margin-top:12px;padding:9px 10px;background:rgba(0,178,255,.08);border:1px solid rgba(0,178,255,.28);border-radius:8px"><p style="margin:0;font-size:11.5px;color:#c9d1dd;line-height:1.55">${L.linkLiveHint}</p></div><div style="display:flex;gap:8px;align-items:center;margin-top:14px"><div data-role="remove"></div><div style="flex:1"></div><button type="button" data-testid="inline-link-cancel" data-role="cancel" style="${btnCss(false)}">${L.linkCancel}</button><button type="button" data-testid="inline-link-submit" data-role="submit" style="${btnCss(true)}" disabled>${editing ? L.linkSave : L.linkInsert}</button></div></div>`;
|
|
485
|
+
const q = (role) => d.querySelector(`[data-role="${role}"]`);
|
|
486
|
+
const text = q("text");
|
|
487
|
+
const urlIn = q("url");
|
|
488
|
+
const search = q("search");
|
|
489
|
+
if (editing) {
|
|
490
|
+
text.value = linkEditing?.getAttribute("data-cms-ref-label") === "auto" ? "" : linkEditing?.textContent ?? "";
|
|
491
|
+
if (!existingRef) urlIn.value = linkEditing?.getAttribute("href") ?? "";
|
|
492
|
+
q("remove").appendChild(buildRemoveLink());
|
|
493
|
+
}
|
|
494
|
+
d.querySelectorAll("[data-tab]").forEach((btn) => {
|
|
495
|
+
btn.onclick = () => {
|
|
496
|
+
const page = btn.dataset.tab === "page";
|
|
497
|
+
d.querySelectorAll("[data-tab]").forEach((b) => {
|
|
498
|
+
b.setAttribute("style", tabCss(b.dataset.tab === "page" ? page : !page));
|
|
499
|
+
});
|
|
500
|
+
d.querySelector('[data-pane="page"]').style.display = page ? "block" : "none";
|
|
501
|
+
d.querySelector('[data-pane="url"]').style.display = page ? "none" : "block";
|
|
502
|
+
q("live").style.display = page ? "flex" : "none";
|
|
503
|
+
syncLinkDialog();
|
|
504
|
+
};
|
|
505
|
+
});
|
|
506
|
+
search.oninput = () => renderLinkList(search.value);
|
|
507
|
+
text.oninput = syncLinkDialog;
|
|
508
|
+
urlIn.oninput = syncLinkDialog;
|
|
509
|
+
q("cancel").onclick = hideLinkDialog;
|
|
510
|
+
q("submit").onclick = applyLink;
|
|
511
|
+
renderLinkList("");
|
|
512
|
+
syncLinkDialog();
|
|
513
|
+
}
|
|
514
|
+
function buildRemoveLink() {
|
|
515
|
+
const wrap = document.createElement("div");
|
|
516
|
+
const btn = document.createElement("button");
|
|
517
|
+
btn.type = "button";
|
|
518
|
+
btn.setAttribute("data-testid", "inline-link-remove");
|
|
519
|
+
btn.textContent = uiLabels.linkRemove;
|
|
520
|
+
btn.style.cssText = "background:none;border:none;color:#ff8a8a;font-size:12.5px;cursor:pointer;padding:0 2px;";
|
|
521
|
+
btn.onclick = () => {
|
|
522
|
+
wrap.innerHTML = `<span style="font-size:11.5px;color:#ff8a8a;font-weight:500;padding:0 2px">${uiLabels.linkRemoveConfirm}</span><button type="button" data-testid="inline-link-remove-yes" style="font-size:11px;padding:2px 8px;border-radius:4px;border:none;background:#c0392b;color:#fff;cursor:pointer;line-height:1.4;margin-left:6px">${uiLabels.linkYes}</button><button type="button" data-testid="inline-link-remove-no" style="font-size:11px;padding:2px 8px;border-radius:4px;border:1px solid #3a3f4a;background:none;color:#fff;cursor:pointer;line-height:1.4;margin-left:6px">${uiLabels.linkNo}</button>`;
|
|
523
|
+
wrap.querySelector('[data-testid="inline-link-remove-no"]').onclick = () => {
|
|
524
|
+
wrap.innerHTML = "";
|
|
525
|
+
wrap.appendChild(btn);
|
|
526
|
+
};
|
|
527
|
+
wrap.querySelector('[data-testid="inline-link-remove-yes"]').onclick = () => {
|
|
528
|
+
if (linkEditing) {
|
|
529
|
+
const parent = linkEditing.parentNode;
|
|
530
|
+
while (linkEditing.firstChild) parent?.insertBefore(linkEditing.firstChild, linkEditing);
|
|
531
|
+
parent?.removeChild(linkEditing);
|
|
532
|
+
}
|
|
533
|
+
hideLinkDialog();
|
|
534
|
+
};
|
|
535
|
+
};
|
|
536
|
+
wrap.appendChild(btn);
|
|
537
|
+
return wrap;
|
|
538
|
+
}
|
|
539
|
+
function renderLinkList(filter) {
|
|
540
|
+
const d = linkDialog;
|
|
541
|
+
if (!d) return;
|
|
542
|
+
const list = d.querySelector('[data-role="list"]');
|
|
543
|
+
if (!list) return;
|
|
544
|
+
const paint = (pages) => {
|
|
545
|
+
const f = filter.trim().toLowerCase();
|
|
546
|
+
const shown = pages.filter((p) => !f || `${p.title} ${p.path}`.toLowerCase().includes(f));
|
|
547
|
+
if (!shown.length) {
|
|
548
|
+
list.innerHTML = `<div style="padding:10px;font-size:12.5px;color:#9aa3b2">${uiLabels.linkEmpty}</div>`;
|
|
549
|
+
return;
|
|
550
|
+
}
|
|
551
|
+
list.innerHTML = "";
|
|
552
|
+
shown.forEach((p) => {
|
|
553
|
+
const on = linkPicked?.collection === p.collection && linkPicked?.slug === p.slug;
|
|
554
|
+
const row = document.createElement("div");
|
|
555
|
+
row.setAttribute("data-testid", `inline-link-page-${p.collection}-${p.slug}`);
|
|
556
|
+
row.style.cssText = "display:flex;align-items:center;gap:10px;padding:8px 10px;cursor:pointer;border-bottom:1px solid #232833;" + (on ? "background:rgba(0,178,255,.14);" : "");
|
|
557
|
+
row.innerHTML = `<div style="min-width:0;flex:1"><div style="font-size:13.5px;line-height:1.3;white-space:nowrap;overflow:hidden;text-overflow:ellipsis">${escapeHtml(p.title)}</div><div style="font-size:11.5px;color:#9aa3b2;font-family:ui-monospace,Menlo,monospace;white-space:nowrap;overflow:hidden;text-overflow:ellipsis">${escapeHtml(p.path)}</div></div><span style="flex:none;font-size:10px;text-transform:uppercase;color:#9aa3b2;border:1px solid #3a3f4a;border-radius:4px;padding:2px 6px;white-space:nowrap">${escapeHtml(p.label)}</span>`;
|
|
558
|
+
row.onclick = () => {
|
|
559
|
+
linkPicked = p;
|
|
560
|
+
renderLinkList(filter);
|
|
561
|
+
syncLinkDialog();
|
|
562
|
+
};
|
|
563
|
+
list.appendChild(row);
|
|
564
|
+
});
|
|
565
|
+
};
|
|
566
|
+
if (linkPages) {
|
|
567
|
+
paint(linkPages);
|
|
568
|
+
return;
|
|
569
|
+
}
|
|
570
|
+
list.innerHTML = `<div style="padding:10px;font-size:12.5px;color:#9aa3b2">${uiLabels.linkLoading}</div>`;
|
|
571
|
+
fetchLinkablePages().then((pages) => {
|
|
572
|
+
const ref = linkEditing?.getAttribute("data-cms-ref");
|
|
573
|
+
if (ref && !linkPicked) {
|
|
574
|
+
const [c, ...rest] = ref.split(":");
|
|
575
|
+
linkPicked = pages.find((p) => p.collection === c && p.slug === rest.join(":")) ?? null;
|
|
576
|
+
}
|
|
577
|
+
paint(pages);
|
|
578
|
+
syncLinkDialog();
|
|
579
|
+
}).catch(() => {
|
|
580
|
+
list.innerHTML = `<div style="padding:10px;font-size:12.5px;color:#ff8a8a">${uiLabels.error}</div>`;
|
|
581
|
+
});
|
|
582
|
+
}
|
|
583
|
+
function syncLinkDialog() {
|
|
584
|
+
const d = linkDialog;
|
|
585
|
+
if (!d) return;
|
|
586
|
+
const q = (role) => d.querySelector(`[data-role="${role}"]`);
|
|
587
|
+
const onPage = d.querySelector('[data-pane="page"]').style.display !== "none";
|
|
588
|
+
const own = q("text").value.trim();
|
|
589
|
+
q("hint").innerHTML = own ? uiLabels.linkTextOwnHint : onPage ? uiLabels.linkTextAutoHint : uiLabels.linkUrlHint;
|
|
590
|
+
q("live").style.display = onPage ? "flex" : "none";
|
|
591
|
+
q("submit").disabled = onPage ? !linkPicked : !q("url").value.trim();
|
|
592
|
+
}
|
|
593
|
+
function applyLink() {
|
|
594
|
+
const d = linkDialog;
|
|
595
|
+
if (!d || !richCtx) return;
|
|
596
|
+
const q = (role) => d.querySelector(`[data-role="${role}"]`);
|
|
597
|
+
const onPage = d.querySelector('[data-pane="page"]').style.display !== "none";
|
|
598
|
+
const own = q("text").value.trim();
|
|
599
|
+
const href = onPage ? linkPicked?.path ?? "" : q("url").value.trim();
|
|
600
|
+
if (!href) return;
|
|
601
|
+
const ref = onPage && linkPicked ? `${linkPicked.collection}:${linkPicked.slug}` : "";
|
|
602
|
+
const label = own || (onPage ? linkPicked?.title ?? href : href);
|
|
603
|
+
if (linkEditing) {
|
|
604
|
+
linkEditing.setAttribute("href", href);
|
|
605
|
+
if (ref) linkEditing.setAttribute("data-cms-ref", ref);
|
|
606
|
+
else linkEditing.removeAttribute("data-cms-ref");
|
|
607
|
+
if (ref && !own) linkEditing.setAttribute("data-cms-ref-label", "auto");
|
|
608
|
+
else linkEditing.removeAttribute("data-cms-ref-label");
|
|
609
|
+
linkEditing.textContent = label;
|
|
610
|
+
hideLinkDialog();
|
|
611
|
+
return;
|
|
612
|
+
}
|
|
613
|
+
const a = document.createElement("a");
|
|
614
|
+
a.setAttribute("href", href);
|
|
615
|
+
if (ref) {
|
|
616
|
+
a.setAttribute("data-cms-ref", ref);
|
|
617
|
+
if (!own) a.setAttribute("data-cms-ref-label", "auto");
|
|
618
|
+
}
|
|
619
|
+
const sel = window.getSelection();
|
|
620
|
+
if (savedRange && sel) {
|
|
621
|
+
sel.removeAllRanges();
|
|
622
|
+
sel.addRange(savedRange);
|
|
623
|
+
const range = sel.getRangeAt(0);
|
|
624
|
+
a.textContent = own || range.toString() || label;
|
|
625
|
+
range.deleteContents();
|
|
626
|
+
range.insertNode(a);
|
|
627
|
+
sel.removeAllRanges();
|
|
628
|
+
} else {
|
|
629
|
+
a.textContent = label;
|
|
630
|
+
richCtx.el.appendChild(a);
|
|
631
|
+
}
|
|
632
|
+
savedRange = null;
|
|
633
|
+
hideLinkDialog();
|
|
634
|
+
}
|
|
635
|
+
function escapeHtml(v) {
|
|
636
|
+
return v.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
637
|
+
}
|
|
638
|
+
var tabCss = (on) => "flex:1;background:" + (on ? "#2a2f38" : "none") + ";border:1px solid " + (on ? "#3a3f4a" : "transparent") + ";color:" + (on ? "#fff" : "#9aa3b2") + ";height:32px;border-radius:7px;font-size:13px;cursor:pointer;font-weight:500;font-family:inherit;";
|
|
639
|
+
var fieldCss = () => "width:100%;background:#141821;border:1px solid #3a3f4a;color:#fff;height:34px;border-radius:7px;padding:0 10px;font-size:13.5px;outline:none;font-family:inherit;box-sizing:border-box;";
|
|
640
|
+
var labelCss = () => "font-size:11px;letter-spacing:.06em;text-transform:uppercase;color:#9aa3b2;margin:12px 0 5px;display:block;";
|
|
641
|
+
var btnCss = (primary) => "height:33px;border-radius:7px;font-size:13px;cursor:pointer;padding:0 14px;font-weight:600;font-family:inherit;" + (primary ? "background:#00b2ff;color:#04121b;border:none;" : "background:none;border:1px solid #3a3f4a;color:#fff;font-weight:500;");
|
|
359
642
|
function toggleEmojiPicker(anchor) {
|
|
360
643
|
if (!emojiPicker) emojiPicker = buildEmojiPicker();
|
|
361
644
|
if (emojiPicker.style.display === "block") {
|
|
@@ -439,14 +722,70 @@ function setDeepField(data, path, value) {
|
|
|
439
722
|
function htmlToMarkdown(html) {
|
|
440
723
|
const container = document.createElement("div");
|
|
441
724
|
container.innerHTML = html;
|
|
725
|
+
reattachOrphanListItems(container);
|
|
442
726
|
return serializeBlockChildren(container).replace(/\n{3,}/g, "\n\n").trim() + "\n";
|
|
443
727
|
}
|
|
728
|
+
function isList(el) {
|
|
729
|
+
if (!el) return false;
|
|
730
|
+
const tag = el.tagName.toLowerCase();
|
|
731
|
+
return tag === "ul" || tag === "ol";
|
|
732
|
+
}
|
|
733
|
+
function reattachOrphanListItems(container) {
|
|
734
|
+
const orphans = Array.from(container.children).filter(
|
|
735
|
+
(c) => c.tagName.toLowerCase() === "li"
|
|
736
|
+
);
|
|
737
|
+
for (const li of orphans) {
|
|
738
|
+
const prev = li.previousElementSibling;
|
|
739
|
+
const next = li.nextElementSibling;
|
|
740
|
+
if (isList(prev)) {
|
|
741
|
+
prev.appendChild(li);
|
|
742
|
+
} else if (isList(next)) {
|
|
743
|
+
next.insertBefore(li, next.firstChild);
|
|
744
|
+
} else {
|
|
745
|
+
const list = container.ownerDocument.createElement("ul");
|
|
746
|
+
li.replaceWith(list);
|
|
747
|
+
list.appendChild(li);
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
var BLOCK_TAGS = /* @__PURE__ */ new Set([
|
|
752
|
+
"h1",
|
|
753
|
+
"h2",
|
|
754
|
+
"h3",
|
|
755
|
+
"h4",
|
|
756
|
+
"h5",
|
|
757
|
+
"h6",
|
|
758
|
+
"p",
|
|
759
|
+
"div",
|
|
760
|
+
"ul",
|
|
761
|
+
"ol",
|
|
762
|
+
"li",
|
|
763
|
+
"blockquote",
|
|
764
|
+
"pre",
|
|
765
|
+
"hr",
|
|
766
|
+
"table"
|
|
767
|
+
]);
|
|
768
|
+
function isBlockNode(node) {
|
|
769
|
+
return node.nodeType === Node.ELEMENT_NODE && BLOCK_TAGS.has(node.tagName.toLowerCase());
|
|
770
|
+
}
|
|
444
771
|
function serializeBlockChildren(parent) {
|
|
445
772
|
const blocks = [];
|
|
446
|
-
|
|
447
|
-
|
|
773
|
+
let inlineRun = "";
|
|
774
|
+
const flushInline = () => {
|
|
775
|
+
const s = inlineRun.trim();
|
|
448
776
|
if (s) blocks.push(s);
|
|
777
|
+
inlineRun = "";
|
|
778
|
+
};
|
|
779
|
+
parent.childNodes.forEach((node) => {
|
|
780
|
+
if (isBlockNode(node)) {
|
|
781
|
+
flushInline();
|
|
782
|
+
const s = serializeBlock(node).trim();
|
|
783
|
+
if (s) blocks.push(s);
|
|
784
|
+
} else {
|
|
785
|
+
inlineRun += serializeInlineNode(node);
|
|
786
|
+
}
|
|
449
787
|
});
|
|
788
|
+
flushInline();
|
|
450
789
|
return blocks.join("\n\n");
|
|
451
790
|
}
|
|
452
791
|
function serializeBlock(node) {
|
|
@@ -474,6 +813,11 @@ function serializeBlock(node) {
|
|
|
474
813
|
return serializeList(el, false);
|
|
475
814
|
case "ol":
|
|
476
815
|
return serializeList(el, true);
|
|
816
|
+
// Backstop for an orphaned <li> that reattachOrphanListItems could not
|
|
817
|
+
// place (e.g. nested inside another block). Without this it falls through
|
|
818
|
+
// to the inline default and loses its marker — silent content loss.
|
|
819
|
+
case "li":
|
|
820
|
+
return "- " + serializeInline(el).trim();
|
|
477
821
|
case "blockquote":
|
|
478
822
|
return serializeBlockChildren(el).split("\n").map((l) => l ? "> " + l : ">").join("\n");
|
|
479
823
|
case "pre":
|
|
@@ -515,14 +859,23 @@ function serializeList(listEl, ordered) {
|
|
|
515
859
|
});
|
|
516
860
|
return items.join("\n");
|
|
517
861
|
}
|
|
862
|
+
function escapeAttr(value) {
|
|
863
|
+
return value.replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<");
|
|
864
|
+
}
|
|
518
865
|
function serializeInline(node) {
|
|
519
866
|
let out = "";
|
|
520
867
|
node.childNodes.forEach((child) => {
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
868
|
+
out += serializeInlineNode(child);
|
|
869
|
+
});
|
|
870
|
+
return out;
|
|
871
|
+
}
|
|
872
|
+
function serializeInlineNode(child) {
|
|
873
|
+
if (child.nodeType === Node.TEXT_NODE) {
|
|
874
|
+
return (child.textContent ?? "").replace(/\s+/g, " ");
|
|
875
|
+
}
|
|
876
|
+
if (child.nodeType !== Node.ELEMENT_NODE) return "";
|
|
877
|
+
{
|
|
878
|
+
let out = "";
|
|
526
879
|
const el = child;
|
|
527
880
|
const tag = el.tagName.toLowerCase();
|
|
528
881
|
const inner = serializeInline(el);
|
|
@@ -543,7 +896,15 @@ function serializeInline(node) {
|
|
|
543
896
|
break;
|
|
544
897
|
case "a": {
|
|
545
898
|
const href = el.getAttribute("href") || "";
|
|
546
|
-
|
|
899
|
+
const ref = el.getAttribute("data-cms-ref");
|
|
900
|
+
if (ref) {
|
|
901
|
+
const attrs = [`href="${escapeAttr(href)}"`, `data-cms-ref="${escapeAttr(ref)}"`];
|
|
902
|
+
const label = el.getAttribute("data-cms-ref-label");
|
|
903
|
+
if (label) attrs.push(`data-cms-ref-label="${escapeAttr(label)}"`);
|
|
904
|
+
out += `<a ${attrs.join(" ")}>${inner}</a>`;
|
|
905
|
+
} else {
|
|
906
|
+
out += href ? `[${inner}](${href})` : inner;
|
|
907
|
+
}
|
|
547
908
|
break;
|
|
548
909
|
}
|
|
549
910
|
case "img": {
|
|
@@ -564,8 +925,8 @@ function serializeInline(node) {
|
|
|
564
925
|
default:
|
|
565
926
|
out += inner;
|
|
566
927
|
}
|
|
567
|
-
|
|
568
|
-
|
|
928
|
+
return out;
|
|
929
|
+
}
|
|
569
930
|
}
|
|
570
931
|
async function saveField(el, value, token, options) {
|
|
571
932
|
const collection = el.dataset.cmsCollection;
|
|
@@ -672,6 +1033,7 @@ export {
|
|
|
672
1033
|
buildConnectUrl,
|
|
673
1034
|
disconnect,
|
|
674
1035
|
getConnectedToken,
|
|
1036
|
+
htmlToMarkdown,
|
|
675
1037
|
initInlineEdit,
|
|
676
1038
|
rescanFields
|
|
677
1039
|
};
|