@broberg/cms-inline-edit 0.4.21 → 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 +311 -11
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +21 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.js +311 -11
- 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 +1 -1
package/README.md
CHANGED
|
@@ -36,3 +36,56 @@ import { saveInlineEditField, verifyEditSession } from "@broberg/cms-inline-edit
|
|
|
36
36
|
```
|
|
37
37
|
|
|
38
38
|
See `docs/features/F157-inline-editing.md` in the [@webhouse/cms](https://github.com/webhousecode/cms) repo for the full design.
|
|
39
|
+
|
|
40
|
+
## Links to a page (live references)
|
|
41
|
+
|
|
42
|
+
The toolbar's link button offers a **free URL** or **a page on the site**. A page
|
|
43
|
+
link stores a reference next to a real, working href:
|
|
44
|
+
|
|
45
|
+
```html
|
|
46
|
+
<a href="/da/om-sanne" data-cms-ref="sider:om-sanne" data-cms-ref-label="auto">Om Sanne</a>
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Render it through `resolveCmsLinks()` and the link re-points itself when the page
|
|
50
|
+
moves or is renamed — nothing rewrites stored content:
|
|
51
|
+
|
|
52
|
+
```ts
|
|
53
|
+
import { resolveCmsLinks } from "@broberg/cms-inline-edit/server";
|
|
54
|
+
|
|
55
|
+
const html = resolveCmsLinks(renderMarkdown(doc.body), (collection, slug) => {
|
|
56
|
+
const page = findPage(collection, slug);
|
|
57
|
+
return page ? { url: page.path, title: page.title } : null;
|
|
58
|
+
});
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
`data-cms-ref-label="auto"` (set when the editor left the link text empty) also
|
|
62
|
+
follows the page's current title. An unknown reference keeps the href it has, so
|
|
63
|
+
a deleted page degrades to the last known link rather than a dead one — and a
|
|
64
|
+
site that never calls the resolver still ships working links.
|
|
65
|
+
|
|
66
|
+
The picker's page list comes from `GET /api/inline-edit/pages?site=<id>`
|
|
67
|
+
(published documents only).
|
|
68
|
+
|
|
69
|
+
## Markup in a PLAIN field — use `data-cms-token`
|
|
70
|
+
|
|
71
|
+
**Which save path a fix covers matters.** The serializer fixes in 0.4.21
|
|
72
|
+
(orphaned `<li>`) and 0.4.23 (top-level inline formatting, dropped images) live
|
|
73
|
+
in the **rich** path — a field marked `data-cms-richtext="true"`, serialised
|
|
74
|
+
through `htmlToMarkdown`.
|
|
75
|
+
|
|
76
|
+
A **plain** field is different: it saves `el.textContent`, and `textContent` has
|
|
77
|
+
no asterisks. So Markdown markup inside a plain field is lost on save, and
|
|
78
|
+
**0.4.23 does not change that.** If you are carrying a `data-cms-token`
|
|
79
|
+
workaround for markup in a plain field, KEEP IT after upgrading — it is not dead
|
|
80
|
+
code (reported by the sanneandersen session, 2026-08-17).
|
|
81
|
+
|
|
82
|
+
The supported way to keep markup in a plain field is `data-cms-token` on the
|
|
83
|
+
formatted segment, carrying the raw source:
|
|
84
|
+
|
|
85
|
+
```html
|
|
86
|
+
<span data-cms-token="**Alt fra Blad**"><strong>Alt fra Blad</strong></span>
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
`serializeTokenSafe()` round-trips those segments verbatim, so the stored value
|
|
90
|
+
keeps its `**…**` while the page shows the rendered form. Use the rich path
|
|
91
|
+
(`data-cms-richtext="true"`) when a field should support formatting generally.
|
package/dist/index.cjs
CHANGED
|
@@ -82,7 +82,27 @@ var DEFAULT_LABELS = {
|
|
|
82
82
|
done: "F\xE6rdig",
|
|
83
83
|
saving: "Gemmer\u2026",
|
|
84
84
|
saved: "Gemt \u2713",
|
|
85
|
-
error: "Fejl \u2014 pr\xF8v igen"
|
|
85
|
+
error: "Fejl \u2014 pr\xF8v igen",
|
|
86
|
+
link: "Link",
|
|
87
|
+
linkTabPage: "Side p\xE5 sitet",
|
|
88
|
+
linkTabUrl: "Fri adresse",
|
|
89
|
+
linkSearch: "S\xF8g efter en side\u2026",
|
|
90
|
+
linkText: "Linktekst",
|
|
91
|
+
linkTextAuto: "F\xF8lger sidens titel",
|
|
92
|
+
linkTextAutoHint: "<b>Tom = f\xF8lger sidens titel.</b> Omd\xF8bes siden, retter teksten sig selv. Skriver du din egen tekst, bliver den st\xE5ende.",
|
|
93
|
+
linkTextOwnHint: "Linket viser <b>din egen tekst</b>. Den bliver st\xE5ende, ogs\xE5 hvis siden omd\xF8bes.",
|
|
94
|
+
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.",
|
|
95
|
+
linkUrlHint: "En fri adresse peger pr\xE6cis d\xE9r, du skriver. Den f\xF8lger <b>ikke</b> med, hvis m\xE5let flytter sig.",
|
|
96
|
+
linkUrl: "Adresse",
|
|
97
|
+
linkInsert: "Inds\xE6t link",
|
|
98
|
+
linkSave: "Gem \xE6ndring",
|
|
99
|
+
linkCancel: "Annull\xE9r",
|
|
100
|
+
linkRemove: "Fjern link",
|
|
101
|
+
linkRemoveConfirm: "Fjern?",
|
|
102
|
+
linkYes: "Ja",
|
|
103
|
+
linkNo: "Nej",
|
|
104
|
+
linkEmpty: "Ingen sider fundet",
|
|
105
|
+
linkLoading: "Henter sider\u2026"
|
|
86
106
|
};
|
|
87
107
|
var uiLabels = DEFAULT_LABELS;
|
|
88
108
|
function resolveOptions(options) {
|
|
@@ -184,6 +204,7 @@ function isExpired(token) {
|
|
|
184
204
|
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>';
|
|
185
205
|
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>';
|
|
186
206
|
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>';
|
|
207
|
+
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>';
|
|
187
208
|
function makeIcon() {
|
|
188
209
|
const icon = document.createElement("span");
|
|
189
210
|
icon.style.cssText = "display:inline-flex;line-height:0;flex:0 0 auto";
|
|
@@ -342,6 +363,7 @@ function deactivateRich() {
|
|
|
342
363
|
el.removeAttribute("contenteditable");
|
|
343
364
|
el.classList.remove("cms-rich-editing");
|
|
344
365
|
hideRichToolbar();
|
|
366
|
+
hideLinkDialog();
|
|
345
367
|
if (el.innerHTML !== originalHtml) {
|
|
346
368
|
const value = mode === "html" ? el.innerHTML : htmlToMarkdown(el.innerHTML);
|
|
347
369
|
void saveField(el, value, token, options);
|
|
@@ -387,6 +409,10 @@ function buildRichToolbar() {
|
|
|
387
409
|
t.appendChild(toolbarButton(UL_SVG, uiLabels.unorderedList, () => document.execCommand("insertUnorderedList")));
|
|
388
410
|
t.appendChild(toolbarButton(OL_SVG, uiLabels.orderedList, () => document.execCommand("insertOrderedList")));
|
|
389
411
|
t.appendChild(sep());
|
|
412
|
+
const linkBtn = toolbarButton(LINK_SVG, uiLabels.link, () => toggleLinkDialog(linkBtn));
|
|
413
|
+
linkBtn.setAttribute("data-testid", "inline-toolbar-link");
|
|
414
|
+
t.appendChild(linkBtn);
|
|
415
|
+
t.appendChild(sep());
|
|
390
416
|
const clrLabel = document.createElement("label");
|
|
391
417
|
clrLabel.style.cssText = "display:flex;align-items:center;gap:5px;color:#9aa4b2;font-size:12px;cursor:pointer;";
|
|
392
418
|
clrLabel.textContent = uiLabels.color;
|
|
@@ -420,6 +446,231 @@ var EMOJIS = "\u{1F600} \u{1F603} \u{1F604} \u{1F601} \u{1F606} \u{1F609} \u{1F6
|
|
|
420
446
|
);
|
|
421
447
|
var emojiPicker = null;
|
|
422
448
|
var savedRange = null;
|
|
449
|
+
var linkDialog = null;
|
|
450
|
+
var linkPages = null;
|
|
451
|
+
var linkPicked = null;
|
|
452
|
+
var linkEditing = null;
|
|
453
|
+
function hideLinkDialog() {
|
|
454
|
+
if (linkDialog) linkDialog.style.display = "none";
|
|
455
|
+
linkPicked = null;
|
|
456
|
+
linkEditing = null;
|
|
457
|
+
}
|
|
458
|
+
function anchorAtCaret() {
|
|
459
|
+
const sel = window.getSelection();
|
|
460
|
+
if (!sel || sel.rangeCount === 0) return null;
|
|
461
|
+
let node = sel.getRangeAt(0).startContainer;
|
|
462
|
+
while (node && node !== document.body) {
|
|
463
|
+
if (node.nodeType === Node.ELEMENT_NODE && node.tagName === "A") {
|
|
464
|
+
return node;
|
|
465
|
+
}
|
|
466
|
+
node = node.parentNode;
|
|
467
|
+
}
|
|
468
|
+
return null;
|
|
469
|
+
}
|
|
470
|
+
async function fetchLinkablePages() {
|
|
471
|
+
if (linkPages) return linkPages;
|
|
472
|
+
if (!richCtx) return [];
|
|
473
|
+
const { options, token } = richCtx;
|
|
474
|
+
const url = `${options.cmsBaseUrl}/api/inline-edit/pages?site=${encodeURIComponent(options.siteId)}`;
|
|
475
|
+
const res = await fetch(url, { headers: { Authorization: `Bearer ${token}` } });
|
|
476
|
+
if (!res.ok) throw new Error(`pages ${res.status}`);
|
|
477
|
+
const body = await res.json();
|
|
478
|
+
linkPages = body.pages ?? [];
|
|
479
|
+
return linkPages;
|
|
480
|
+
}
|
|
481
|
+
function toggleLinkDialog(anchor) {
|
|
482
|
+
if (linkDialog && linkDialog.style.display === "block") {
|
|
483
|
+
hideLinkDialog();
|
|
484
|
+
return;
|
|
485
|
+
}
|
|
486
|
+
const sel = window.getSelection();
|
|
487
|
+
if (sel && sel.rangeCount > 0) savedRange = sel.getRangeAt(0).cloneRange();
|
|
488
|
+
linkEditing = anchorAtCaret();
|
|
489
|
+
if (!linkDialog) linkDialog = buildLinkDialog();
|
|
490
|
+
renderLinkDialog();
|
|
491
|
+
const rect = anchor.getBoundingClientRect();
|
|
492
|
+
linkDialog.style.top = `${rect.bottom + 8}px`;
|
|
493
|
+
linkDialog.style.left = `${Math.min(Math.max(8, rect.left - 180), window.innerWidth - 400)}px`;
|
|
494
|
+
linkDialog.style.display = "block";
|
|
495
|
+
}
|
|
496
|
+
function buildLinkDialog() {
|
|
497
|
+
const d = document.createElement("div");
|
|
498
|
+
d.setAttribute("data-cms-inline-edit-toolbar", "");
|
|
499
|
+
d.setAttribute("data-testid", "inline-link-dialog");
|
|
500
|
+
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;";
|
|
501
|
+
d.addEventListener("mousedown", (e) => {
|
|
502
|
+
const t = e.target;
|
|
503
|
+
if (t.tagName !== "INPUT") e.preventDefault();
|
|
504
|
+
e.stopPropagation();
|
|
505
|
+
});
|
|
506
|
+
document.body.appendChild(d);
|
|
507
|
+
return d;
|
|
508
|
+
}
|
|
509
|
+
function renderLinkDialog() {
|
|
510
|
+
const d = linkDialog;
|
|
511
|
+
if (!d) return;
|
|
512
|
+
const L = uiLabels;
|
|
513
|
+
const editing = !!linkEditing;
|
|
514
|
+
const existingRef = linkEditing?.getAttribute("data-cms-ref") ?? "";
|
|
515
|
+
const onPageTab = !editing || !!existingRef;
|
|
516
|
+
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>`;
|
|
517
|
+
const q = (role) => d.querySelector(`[data-role="${role}"]`);
|
|
518
|
+
const text = q("text");
|
|
519
|
+
const urlIn = q("url");
|
|
520
|
+
const search = q("search");
|
|
521
|
+
if (editing) {
|
|
522
|
+
text.value = linkEditing?.getAttribute("data-cms-ref-label") === "auto" ? "" : linkEditing?.textContent ?? "";
|
|
523
|
+
if (!existingRef) urlIn.value = linkEditing?.getAttribute("href") ?? "";
|
|
524
|
+
q("remove").appendChild(buildRemoveLink());
|
|
525
|
+
}
|
|
526
|
+
d.querySelectorAll("[data-tab]").forEach((btn) => {
|
|
527
|
+
btn.onclick = () => {
|
|
528
|
+
const page = btn.dataset.tab === "page";
|
|
529
|
+
d.querySelectorAll("[data-tab]").forEach((b) => {
|
|
530
|
+
b.setAttribute("style", tabCss(b.dataset.tab === "page" ? page : !page));
|
|
531
|
+
});
|
|
532
|
+
d.querySelector('[data-pane="page"]').style.display = page ? "block" : "none";
|
|
533
|
+
d.querySelector('[data-pane="url"]').style.display = page ? "none" : "block";
|
|
534
|
+
q("live").style.display = page ? "flex" : "none";
|
|
535
|
+
syncLinkDialog();
|
|
536
|
+
};
|
|
537
|
+
});
|
|
538
|
+
search.oninput = () => renderLinkList(search.value);
|
|
539
|
+
text.oninput = syncLinkDialog;
|
|
540
|
+
urlIn.oninput = syncLinkDialog;
|
|
541
|
+
q("cancel").onclick = hideLinkDialog;
|
|
542
|
+
q("submit").onclick = applyLink;
|
|
543
|
+
renderLinkList("");
|
|
544
|
+
syncLinkDialog();
|
|
545
|
+
}
|
|
546
|
+
function buildRemoveLink() {
|
|
547
|
+
const wrap = document.createElement("div");
|
|
548
|
+
const btn = document.createElement("button");
|
|
549
|
+
btn.type = "button";
|
|
550
|
+
btn.setAttribute("data-testid", "inline-link-remove");
|
|
551
|
+
btn.textContent = uiLabels.linkRemove;
|
|
552
|
+
btn.style.cssText = "background:none;border:none;color:#ff8a8a;font-size:12.5px;cursor:pointer;padding:0 2px;";
|
|
553
|
+
btn.onclick = () => {
|
|
554
|
+
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>`;
|
|
555
|
+
wrap.querySelector('[data-testid="inline-link-remove-no"]').onclick = () => {
|
|
556
|
+
wrap.innerHTML = "";
|
|
557
|
+
wrap.appendChild(btn);
|
|
558
|
+
};
|
|
559
|
+
wrap.querySelector('[data-testid="inline-link-remove-yes"]').onclick = () => {
|
|
560
|
+
if (linkEditing) {
|
|
561
|
+
const parent = linkEditing.parentNode;
|
|
562
|
+
while (linkEditing.firstChild) parent?.insertBefore(linkEditing.firstChild, linkEditing);
|
|
563
|
+
parent?.removeChild(linkEditing);
|
|
564
|
+
}
|
|
565
|
+
hideLinkDialog();
|
|
566
|
+
};
|
|
567
|
+
};
|
|
568
|
+
wrap.appendChild(btn);
|
|
569
|
+
return wrap;
|
|
570
|
+
}
|
|
571
|
+
function renderLinkList(filter) {
|
|
572
|
+
const d = linkDialog;
|
|
573
|
+
if (!d) return;
|
|
574
|
+
const list = d.querySelector('[data-role="list"]');
|
|
575
|
+
if (!list) return;
|
|
576
|
+
const paint = (pages) => {
|
|
577
|
+
const f = filter.trim().toLowerCase();
|
|
578
|
+
const shown = pages.filter((p) => !f || `${p.title} ${p.path}`.toLowerCase().includes(f));
|
|
579
|
+
if (!shown.length) {
|
|
580
|
+
list.innerHTML = `<div style="padding:10px;font-size:12.5px;color:#9aa3b2">${uiLabels.linkEmpty}</div>`;
|
|
581
|
+
return;
|
|
582
|
+
}
|
|
583
|
+
list.innerHTML = "";
|
|
584
|
+
shown.forEach((p) => {
|
|
585
|
+
const on = linkPicked?.collection === p.collection && linkPicked?.slug === p.slug;
|
|
586
|
+
const row = document.createElement("div");
|
|
587
|
+
row.setAttribute("data-testid", `inline-link-page-${p.collection}-${p.slug}`);
|
|
588
|
+
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);" : "");
|
|
589
|
+
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>`;
|
|
590
|
+
row.onclick = () => {
|
|
591
|
+
linkPicked = p;
|
|
592
|
+
renderLinkList(filter);
|
|
593
|
+
syncLinkDialog();
|
|
594
|
+
};
|
|
595
|
+
list.appendChild(row);
|
|
596
|
+
});
|
|
597
|
+
};
|
|
598
|
+
if (linkPages) {
|
|
599
|
+
paint(linkPages);
|
|
600
|
+
return;
|
|
601
|
+
}
|
|
602
|
+
list.innerHTML = `<div style="padding:10px;font-size:12.5px;color:#9aa3b2">${uiLabels.linkLoading}</div>`;
|
|
603
|
+
fetchLinkablePages().then((pages) => {
|
|
604
|
+
const ref = linkEditing?.getAttribute("data-cms-ref");
|
|
605
|
+
if (ref && !linkPicked) {
|
|
606
|
+
const [c, ...rest] = ref.split(":");
|
|
607
|
+
linkPicked = pages.find((p) => p.collection === c && p.slug === rest.join(":")) ?? null;
|
|
608
|
+
}
|
|
609
|
+
paint(pages);
|
|
610
|
+
syncLinkDialog();
|
|
611
|
+
}).catch(() => {
|
|
612
|
+
list.innerHTML = `<div style="padding:10px;font-size:12.5px;color:#ff8a8a">${uiLabels.error}</div>`;
|
|
613
|
+
});
|
|
614
|
+
}
|
|
615
|
+
function syncLinkDialog() {
|
|
616
|
+
const d = linkDialog;
|
|
617
|
+
if (!d) return;
|
|
618
|
+
const q = (role) => d.querySelector(`[data-role="${role}"]`);
|
|
619
|
+
const onPage = d.querySelector('[data-pane="page"]').style.display !== "none";
|
|
620
|
+
const own = q("text").value.trim();
|
|
621
|
+
q("hint").innerHTML = own ? uiLabels.linkTextOwnHint : onPage ? uiLabels.linkTextAutoHint : uiLabels.linkUrlHint;
|
|
622
|
+
q("live").style.display = onPage ? "flex" : "none";
|
|
623
|
+
q("submit").disabled = onPage ? !linkPicked : !q("url").value.trim();
|
|
624
|
+
}
|
|
625
|
+
function applyLink() {
|
|
626
|
+
const d = linkDialog;
|
|
627
|
+
if (!d || !richCtx) return;
|
|
628
|
+
const q = (role) => d.querySelector(`[data-role="${role}"]`);
|
|
629
|
+
const onPage = d.querySelector('[data-pane="page"]').style.display !== "none";
|
|
630
|
+
const own = q("text").value.trim();
|
|
631
|
+
const href = onPage ? linkPicked?.path ?? "" : q("url").value.trim();
|
|
632
|
+
if (!href) return;
|
|
633
|
+
const ref = onPage && linkPicked ? `${linkPicked.collection}:${linkPicked.slug}` : "";
|
|
634
|
+
const label = own || (onPage ? linkPicked?.title ?? href : href);
|
|
635
|
+
if (linkEditing) {
|
|
636
|
+
linkEditing.setAttribute("href", href);
|
|
637
|
+
if (ref) linkEditing.setAttribute("data-cms-ref", ref);
|
|
638
|
+
else linkEditing.removeAttribute("data-cms-ref");
|
|
639
|
+
if (ref && !own) linkEditing.setAttribute("data-cms-ref-label", "auto");
|
|
640
|
+
else linkEditing.removeAttribute("data-cms-ref-label");
|
|
641
|
+
linkEditing.textContent = label;
|
|
642
|
+
hideLinkDialog();
|
|
643
|
+
return;
|
|
644
|
+
}
|
|
645
|
+
const a = document.createElement("a");
|
|
646
|
+
a.setAttribute("href", href);
|
|
647
|
+
if (ref) {
|
|
648
|
+
a.setAttribute("data-cms-ref", ref);
|
|
649
|
+
if (!own) a.setAttribute("data-cms-ref-label", "auto");
|
|
650
|
+
}
|
|
651
|
+
const sel = window.getSelection();
|
|
652
|
+
if (savedRange && sel) {
|
|
653
|
+
sel.removeAllRanges();
|
|
654
|
+
sel.addRange(savedRange);
|
|
655
|
+
const range = sel.getRangeAt(0);
|
|
656
|
+
a.textContent = own || range.toString() || label;
|
|
657
|
+
range.deleteContents();
|
|
658
|
+
range.insertNode(a);
|
|
659
|
+
sel.removeAllRanges();
|
|
660
|
+
} else {
|
|
661
|
+
a.textContent = label;
|
|
662
|
+
richCtx.el.appendChild(a);
|
|
663
|
+
}
|
|
664
|
+
savedRange = null;
|
|
665
|
+
hideLinkDialog();
|
|
666
|
+
}
|
|
667
|
+
function escapeHtml(v) {
|
|
668
|
+
return v.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
669
|
+
}
|
|
670
|
+
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;";
|
|
671
|
+
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;";
|
|
672
|
+
var labelCss = () => "font-size:11px;letter-spacing:.06em;text-transform:uppercase;color:#9aa3b2;margin:12px 0 5px;display:block;";
|
|
673
|
+
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;");
|
|
423
674
|
function toggleEmojiPicker(anchor) {
|
|
424
675
|
if (!emojiPicker) emojiPicker = buildEmojiPicker();
|
|
425
676
|
if (emojiPicker.style.display === "block") {
|
|
@@ -529,12 +780,44 @@ function reattachOrphanListItems(container) {
|
|
|
529
780
|
}
|
|
530
781
|
}
|
|
531
782
|
}
|
|
783
|
+
var BLOCK_TAGS = /* @__PURE__ */ new Set([
|
|
784
|
+
"h1",
|
|
785
|
+
"h2",
|
|
786
|
+
"h3",
|
|
787
|
+
"h4",
|
|
788
|
+
"h5",
|
|
789
|
+
"h6",
|
|
790
|
+
"p",
|
|
791
|
+
"div",
|
|
792
|
+
"ul",
|
|
793
|
+
"ol",
|
|
794
|
+
"li",
|
|
795
|
+
"blockquote",
|
|
796
|
+
"pre",
|
|
797
|
+
"hr",
|
|
798
|
+
"table"
|
|
799
|
+
]);
|
|
800
|
+
function isBlockNode(node) {
|
|
801
|
+
return node.nodeType === Node.ELEMENT_NODE && BLOCK_TAGS.has(node.tagName.toLowerCase());
|
|
802
|
+
}
|
|
532
803
|
function serializeBlockChildren(parent) {
|
|
533
804
|
const blocks = [];
|
|
534
|
-
|
|
535
|
-
|
|
805
|
+
let inlineRun = "";
|
|
806
|
+
const flushInline = () => {
|
|
807
|
+
const s = inlineRun.trim();
|
|
536
808
|
if (s) blocks.push(s);
|
|
809
|
+
inlineRun = "";
|
|
810
|
+
};
|
|
811
|
+
parent.childNodes.forEach((node) => {
|
|
812
|
+
if (isBlockNode(node)) {
|
|
813
|
+
flushInline();
|
|
814
|
+
const s = serializeBlock(node).trim();
|
|
815
|
+
if (s) blocks.push(s);
|
|
816
|
+
} else {
|
|
817
|
+
inlineRun += serializeInlineNode(node);
|
|
818
|
+
}
|
|
537
819
|
});
|
|
820
|
+
flushInline();
|
|
538
821
|
return blocks.join("\n\n");
|
|
539
822
|
}
|
|
540
823
|
function serializeBlock(node) {
|
|
@@ -608,14 +891,23 @@ function serializeList(listEl, ordered) {
|
|
|
608
891
|
});
|
|
609
892
|
return items.join("\n");
|
|
610
893
|
}
|
|
894
|
+
function escapeAttr(value) {
|
|
895
|
+
return value.replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<");
|
|
896
|
+
}
|
|
611
897
|
function serializeInline(node) {
|
|
612
898
|
let out = "";
|
|
613
899
|
node.childNodes.forEach((child) => {
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
900
|
+
out += serializeInlineNode(child);
|
|
901
|
+
});
|
|
902
|
+
return out;
|
|
903
|
+
}
|
|
904
|
+
function serializeInlineNode(child) {
|
|
905
|
+
if (child.nodeType === Node.TEXT_NODE) {
|
|
906
|
+
return (child.textContent ?? "").replace(/\s+/g, " ");
|
|
907
|
+
}
|
|
908
|
+
if (child.nodeType !== Node.ELEMENT_NODE) return "";
|
|
909
|
+
{
|
|
910
|
+
let out = "";
|
|
619
911
|
const el = child;
|
|
620
912
|
const tag = el.tagName.toLowerCase();
|
|
621
913
|
const inner = serializeInline(el);
|
|
@@ -636,7 +928,15 @@ function serializeInline(node) {
|
|
|
636
928
|
break;
|
|
637
929
|
case "a": {
|
|
638
930
|
const href = el.getAttribute("href") || "";
|
|
639
|
-
|
|
931
|
+
const ref = el.getAttribute("data-cms-ref");
|
|
932
|
+
if (ref) {
|
|
933
|
+
const attrs = [`href="${escapeAttr(href)}"`, `data-cms-ref="${escapeAttr(ref)}"`];
|
|
934
|
+
const label = el.getAttribute("data-cms-ref-label");
|
|
935
|
+
if (label) attrs.push(`data-cms-ref-label="${escapeAttr(label)}"`);
|
|
936
|
+
out += `<a ${attrs.join(" ")}>${inner}</a>`;
|
|
937
|
+
} else {
|
|
938
|
+
out += href ? `[${inner}](${href})` : inner;
|
|
939
|
+
}
|
|
640
940
|
break;
|
|
641
941
|
}
|
|
642
942
|
case "img": {
|
|
@@ -657,8 +957,8 @@ function serializeInline(node) {
|
|
|
657
957
|
default:
|
|
658
958
|
out += inner;
|
|
659
959
|
}
|
|
660
|
-
|
|
661
|
-
|
|
960
|
+
return out;
|
|
961
|
+
}
|
|
662
962
|
}
|
|
663
963
|
async function saveField(el, value, token, options) {
|
|
664
964
|
const collection = el.dataset.cmsCollection;
|