@broberg/cms-inline-edit 0.4.21 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +53 -0
- package/dist/index.cjs +318 -12
- 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 +318 -12
- 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) {
|
|
@@ -143,12 +163,18 @@ function getConnectedToken(options) {
|
|
|
143
163
|
captureTokenFromUrl(resolved);
|
|
144
164
|
const token = localStorage.getItem(resolved.storageKey);
|
|
145
165
|
if (!token) return null;
|
|
146
|
-
if (isExpired(token)) {
|
|
166
|
+
if (isExpired(token) || !isForThisSite(token, resolved.siteId)) {
|
|
147
167
|
localStorage.removeItem(resolved.storageKey);
|
|
148
168
|
return null;
|
|
149
169
|
}
|
|
150
170
|
return token;
|
|
151
171
|
}
|
|
172
|
+
function isForThisSite(token, siteId) {
|
|
173
|
+
const claims = decodeJwtPayload(token);
|
|
174
|
+
const site = claims?.site;
|
|
175
|
+
if (typeof site !== "string" || !site) return true;
|
|
176
|
+
return site === siteId;
|
|
177
|
+
}
|
|
152
178
|
function buildConnectUrl(options, returnUrl) {
|
|
153
179
|
const resolved = resolveOptions(options);
|
|
154
180
|
return `${resolved.cmsBaseUrl}/admin/inline-edit/connect?site=${encodeURIComponent(resolved.siteId)}&return=${encodeURIComponent(returnUrl)}`;
|
|
@@ -184,6 +210,7 @@ function isExpired(token) {
|
|
|
184
210
|
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
211
|
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
212
|
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>';
|
|
213
|
+
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
214
|
function makeIcon() {
|
|
188
215
|
const icon = document.createElement("span");
|
|
189
216
|
icon.style.cssText = "display:inline-flex;line-height:0;flex:0 0 auto";
|
|
@@ -342,6 +369,7 @@ function deactivateRich() {
|
|
|
342
369
|
el.removeAttribute("contenteditable");
|
|
343
370
|
el.classList.remove("cms-rich-editing");
|
|
344
371
|
hideRichToolbar();
|
|
372
|
+
hideLinkDialog();
|
|
345
373
|
if (el.innerHTML !== originalHtml) {
|
|
346
374
|
const value = mode === "html" ? el.innerHTML : htmlToMarkdown(el.innerHTML);
|
|
347
375
|
void saveField(el, value, token, options);
|
|
@@ -387,6 +415,10 @@ function buildRichToolbar() {
|
|
|
387
415
|
t.appendChild(toolbarButton(UL_SVG, uiLabels.unorderedList, () => document.execCommand("insertUnorderedList")));
|
|
388
416
|
t.appendChild(toolbarButton(OL_SVG, uiLabels.orderedList, () => document.execCommand("insertOrderedList")));
|
|
389
417
|
t.appendChild(sep());
|
|
418
|
+
const linkBtn = toolbarButton(LINK_SVG, uiLabels.link, () => toggleLinkDialog(linkBtn));
|
|
419
|
+
linkBtn.setAttribute("data-testid", "inline-toolbar-link");
|
|
420
|
+
t.appendChild(linkBtn);
|
|
421
|
+
t.appendChild(sep());
|
|
390
422
|
const clrLabel = document.createElement("label");
|
|
391
423
|
clrLabel.style.cssText = "display:flex;align-items:center;gap:5px;color:#9aa4b2;font-size:12px;cursor:pointer;";
|
|
392
424
|
clrLabel.textContent = uiLabels.color;
|
|
@@ -420,6 +452,231 @@ var EMOJIS = "\u{1F600} \u{1F603} \u{1F604} \u{1F601} \u{1F606} \u{1F609} \u{1F6
|
|
|
420
452
|
);
|
|
421
453
|
var emojiPicker = null;
|
|
422
454
|
var savedRange = null;
|
|
455
|
+
var linkDialog = null;
|
|
456
|
+
var linkPages = null;
|
|
457
|
+
var linkPicked = null;
|
|
458
|
+
var linkEditing = null;
|
|
459
|
+
function hideLinkDialog() {
|
|
460
|
+
if (linkDialog) linkDialog.style.display = "none";
|
|
461
|
+
linkPicked = null;
|
|
462
|
+
linkEditing = null;
|
|
463
|
+
}
|
|
464
|
+
function anchorAtCaret() {
|
|
465
|
+
const sel = window.getSelection();
|
|
466
|
+
if (!sel || sel.rangeCount === 0) return null;
|
|
467
|
+
let node = sel.getRangeAt(0).startContainer;
|
|
468
|
+
while (node && node !== document.body) {
|
|
469
|
+
if (node.nodeType === Node.ELEMENT_NODE && node.tagName === "A") {
|
|
470
|
+
return node;
|
|
471
|
+
}
|
|
472
|
+
node = node.parentNode;
|
|
473
|
+
}
|
|
474
|
+
return null;
|
|
475
|
+
}
|
|
476
|
+
async function fetchLinkablePages() {
|
|
477
|
+
if (linkPages) return linkPages;
|
|
478
|
+
if (!richCtx) return [];
|
|
479
|
+
const { options, token } = richCtx;
|
|
480
|
+
const url = `${options.cmsBaseUrl}/api/inline-edit/pages?site=${encodeURIComponent(options.siteId)}`;
|
|
481
|
+
const res = await fetch(url, { headers: { Authorization: `Bearer ${token}` } });
|
|
482
|
+
if (!res.ok) throw new Error(`pages ${res.status}`);
|
|
483
|
+
const body = await res.json();
|
|
484
|
+
linkPages = body.pages ?? [];
|
|
485
|
+
return linkPages;
|
|
486
|
+
}
|
|
487
|
+
function toggleLinkDialog(anchor) {
|
|
488
|
+
if (linkDialog && linkDialog.style.display === "block") {
|
|
489
|
+
hideLinkDialog();
|
|
490
|
+
return;
|
|
491
|
+
}
|
|
492
|
+
const sel = window.getSelection();
|
|
493
|
+
if (sel && sel.rangeCount > 0) savedRange = sel.getRangeAt(0).cloneRange();
|
|
494
|
+
linkEditing = anchorAtCaret();
|
|
495
|
+
if (!linkDialog) linkDialog = buildLinkDialog();
|
|
496
|
+
renderLinkDialog();
|
|
497
|
+
const rect = anchor.getBoundingClientRect();
|
|
498
|
+
linkDialog.style.top = `${rect.bottom + 8}px`;
|
|
499
|
+
linkDialog.style.left = `${Math.min(Math.max(8, rect.left - 180), window.innerWidth - 400)}px`;
|
|
500
|
+
linkDialog.style.display = "block";
|
|
501
|
+
}
|
|
502
|
+
function buildLinkDialog() {
|
|
503
|
+
const d = document.createElement("div");
|
|
504
|
+
d.setAttribute("data-cms-inline-edit-toolbar", "");
|
|
505
|
+
d.setAttribute("data-testid", "inline-link-dialog");
|
|
506
|
+
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;";
|
|
507
|
+
d.addEventListener("mousedown", (e) => {
|
|
508
|
+
const t = e.target;
|
|
509
|
+
if (t.tagName !== "INPUT") e.preventDefault();
|
|
510
|
+
e.stopPropagation();
|
|
511
|
+
});
|
|
512
|
+
document.body.appendChild(d);
|
|
513
|
+
return d;
|
|
514
|
+
}
|
|
515
|
+
function renderLinkDialog() {
|
|
516
|
+
const d = linkDialog;
|
|
517
|
+
if (!d) return;
|
|
518
|
+
const L = uiLabels;
|
|
519
|
+
const editing = !!linkEditing;
|
|
520
|
+
const existingRef = linkEditing?.getAttribute("data-cms-ref") ?? "";
|
|
521
|
+
const onPageTab = !editing || !!existingRef;
|
|
522
|
+
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>`;
|
|
523
|
+
const q = (role) => d.querySelector(`[data-role="${role}"]`);
|
|
524
|
+
const text = q("text");
|
|
525
|
+
const urlIn = q("url");
|
|
526
|
+
const search = q("search");
|
|
527
|
+
if (editing) {
|
|
528
|
+
text.value = linkEditing?.getAttribute("data-cms-ref-label") === "auto" ? "" : linkEditing?.textContent ?? "";
|
|
529
|
+
if (!existingRef) urlIn.value = linkEditing?.getAttribute("href") ?? "";
|
|
530
|
+
q("remove").appendChild(buildRemoveLink());
|
|
531
|
+
}
|
|
532
|
+
d.querySelectorAll("[data-tab]").forEach((btn) => {
|
|
533
|
+
btn.onclick = () => {
|
|
534
|
+
const page = btn.dataset.tab === "page";
|
|
535
|
+
d.querySelectorAll("[data-tab]").forEach((b) => {
|
|
536
|
+
b.setAttribute("style", tabCss(b.dataset.tab === "page" ? page : !page));
|
|
537
|
+
});
|
|
538
|
+
d.querySelector('[data-pane="page"]').style.display = page ? "block" : "none";
|
|
539
|
+
d.querySelector('[data-pane="url"]').style.display = page ? "none" : "block";
|
|
540
|
+
q("live").style.display = page ? "flex" : "none";
|
|
541
|
+
syncLinkDialog();
|
|
542
|
+
};
|
|
543
|
+
});
|
|
544
|
+
search.oninput = () => renderLinkList(search.value);
|
|
545
|
+
text.oninput = syncLinkDialog;
|
|
546
|
+
urlIn.oninput = syncLinkDialog;
|
|
547
|
+
q("cancel").onclick = hideLinkDialog;
|
|
548
|
+
q("submit").onclick = applyLink;
|
|
549
|
+
renderLinkList("");
|
|
550
|
+
syncLinkDialog();
|
|
551
|
+
}
|
|
552
|
+
function buildRemoveLink() {
|
|
553
|
+
const wrap = document.createElement("div");
|
|
554
|
+
const btn = document.createElement("button");
|
|
555
|
+
btn.type = "button";
|
|
556
|
+
btn.setAttribute("data-testid", "inline-link-remove");
|
|
557
|
+
btn.textContent = uiLabels.linkRemove;
|
|
558
|
+
btn.style.cssText = "background:none;border:none;color:#ff8a8a;font-size:12.5px;cursor:pointer;padding:0 2px;";
|
|
559
|
+
btn.onclick = () => {
|
|
560
|
+
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>`;
|
|
561
|
+
wrap.querySelector('[data-testid="inline-link-remove-no"]').onclick = () => {
|
|
562
|
+
wrap.innerHTML = "";
|
|
563
|
+
wrap.appendChild(btn);
|
|
564
|
+
};
|
|
565
|
+
wrap.querySelector('[data-testid="inline-link-remove-yes"]').onclick = () => {
|
|
566
|
+
if (linkEditing) {
|
|
567
|
+
const parent = linkEditing.parentNode;
|
|
568
|
+
while (linkEditing.firstChild) parent?.insertBefore(linkEditing.firstChild, linkEditing);
|
|
569
|
+
parent?.removeChild(linkEditing);
|
|
570
|
+
}
|
|
571
|
+
hideLinkDialog();
|
|
572
|
+
};
|
|
573
|
+
};
|
|
574
|
+
wrap.appendChild(btn);
|
|
575
|
+
return wrap;
|
|
576
|
+
}
|
|
577
|
+
function renderLinkList(filter) {
|
|
578
|
+
const d = linkDialog;
|
|
579
|
+
if (!d) return;
|
|
580
|
+
const list = d.querySelector('[data-role="list"]');
|
|
581
|
+
if (!list) return;
|
|
582
|
+
const paint = (pages) => {
|
|
583
|
+
const f = filter.trim().toLowerCase();
|
|
584
|
+
const shown = pages.filter((p) => !f || `${p.title} ${p.path}`.toLowerCase().includes(f));
|
|
585
|
+
if (!shown.length) {
|
|
586
|
+
list.innerHTML = `<div style="padding:10px;font-size:12.5px;color:#9aa3b2">${uiLabels.linkEmpty}</div>`;
|
|
587
|
+
return;
|
|
588
|
+
}
|
|
589
|
+
list.innerHTML = "";
|
|
590
|
+
shown.forEach((p) => {
|
|
591
|
+
const on = linkPicked?.collection === p.collection && linkPicked?.slug === p.slug;
|
|
592
|
+
const row = document.createElement("div");
|
|
593
|
+
row.setAttribute("data-testid", `inline-link-page-${p.collection}-${p.slug}`);
|
|
594
|
+
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);" : "");
|
|
595
|
+
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>`;
|
|
596
|
+
row.onclick = () => {
|
|
597
|
+
linkPicked = p;
|
|
598
|
+
renderLinkList(filter);
|
|
599
|
+
syncLinkDialog();
|
|
600
|
+
};
|
|
601
|
+
list.appendChild(row);
|
|
602
|
+
});
|
|
603
|
+
};
|
|
604
|
+
if (linkPages) {
|
|
605
|
+
paint(linkPages);
|
|
606
|
+
return;
|
|
607
|
+
}
|
|
608
|
+
list.innerHTML = `<div style="padding:10px;font-size:12.5px;color:#9aa3b2">${uiLabels.linkLoading}</div>`;
|
|
609
|
+
fetchLinkablePages().then((pages) => {
|
|
610
|
+
const ref = linkEditing?.getAttribute("data-cms-ref");
|
|
611
|
+
if (ref && !linkPicked) {
|
|
612
|
+
const [c, ...rest] = ref.split(":");
|
|
613
|
+
linkPicked = pages.find((p) => p.collection === c && p.slug === rest.join(":")) ?? null;
|
|
614
|
+
}
|
|
615
|
+
paint(pages);
|
|
616
|
+
syncLinkDialog();
|
|
617
|
+
}).catch(() => {
|
|
618
|
+
list.innerHTML = `<div style="padding:10px;font-size:12.5px;color:#ff8a8a">${uiLabels.error}</div>`;
|
|
619
|
+
});
|
|
620
|
+
}
|
|
621
|
+
function syncLinkDialog() {
|
|
622
|
+
const d = linkDialog;
|
|
623
|
+
if (!d) return;
|
|
624
|
+
const q = (role) => d.querySelector(`[data-role="${role}"]`);
|
|
625
|
+
const onPage = d.querySelector('[data-pane="page"]').style.display !== "none";
|
|
626
|
+
const own = q("text").value.trim();
|
|
627
|
+
q("hint").innerHTML = own ? uiLabels.linkTextOwnHint : onPage ? uiLabels.linkTextAutoHint : uiLabels.linkUrlHint;
|
|
628
|
+
q("live").style.display = onPage ? "flex" : "none";
|
|
629
|
+
q("submit").disabled = onPage ? !linkPicked : !q("url").value.trim();
|
|
630
|
+
}
|
|
631
|
+
function applyLink() {
|
|
632
|
+
const d = linkDialog;
|
|
633
|
+
if (!d || !richCtx) return;
|
|
634
|
+
const q = (role) => d.querySelector(`[data-role="${role}"]`);
|
|
635
|
+
const onPage = d.querySelector('[data-pane="page"]').style.display !== "none";
|
|
636
|
+
const own = q("text").value.trim();
|
|
637
|
+
const href = onPage ? linkPicked?.path ?? "" : q("url").value.trim();
|
|
638
|
+
if (!href) return;
|
|
639
|
+
const ref = onPage && linkPicked ? `${linkPicked.collection}:${linkPicked.slug}` : "";
|
|
640
|
+
const label = own || (onPage ? linkPicked?.title ?? href : href);
|
|
641
|
+
if (linkEditing) {
|
|
642
|
+
linkEditing.setAttribute("href", href);
|
|
643
|
+
if (ref) linkEditing.setAttribute("data-cms-ref", ref);
|
|
644
|
+
else linkEditing.removeAttribute("data-cms-ref");
|
|
645
|
+
if (ref && !own) linkEditing.setAttribute("data-cms-ref-label", "auto");
|
|
646
|
+
else linkEditing.removeAttribute("data-cms-ref-label");
|
|
647
|
+
linkEditing.textContent = label;
|
|
648
|
+
hideLinkDialog();
|
|
649
|
+
return;
|
|
650
|
+
}
|
|
651
|
+
const a = document.createElement("a");
|
|
652
|
+
a.setAttribute("href", href);
|
|
653
|
+
if (ref) {
|
|
654
|
+
a.setAttribute("data-cms-ref", ref);
|
|
655
|
+
if (!own) a.setAttribute("data-cms-ref-label", "auto");
|
|
656
|
+
}
|
|
657
|
+
const sel = window.getSelection();
|
|
658
|
+
if (savedRange && sel) {
|
|
659
|
+
sel.removeAllRanges();
|
|
660
|
+
sel.addRange(savedRange);
|
|
661
|
+
const range = sel.getRangeAt(0);
|
|
662
|
+
a.textContent = own || range.toString() || label;
|
|
663
|
+
range.deleteContents();
|
|
664
|
+
range.insertNode(a);
|
|
665
|
+
sel.removeAllRanges();
|
|
666
|
+
} else {
|
|
667
|
+
a.textContent = label;
|
|
668
|
+
richCtx.el.appendChild(a);
|
|
669
|
+
}
|
|
670
|
+
savedRange = null;
|
|
671
|
+
hideLinkDialog();
|
|
672
|
+
}
|
|
673
|
+
function escapeHtml(v) {
|
|
674
|
+
return v.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
675
|
+
}
|
|
676
|
+
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;";
|
|
677
|
+
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;";
|
|
678
|
+
var labelCss = () => "font-size:11px;letter-spacing:.06em;text-transform:uppercase;color:#9aa3b2;margin:12px 0 5px;display:block;";
|
|
679
|
+
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
680
|
function toggleEmojiPicker(anchor) {
|
|
424
681
|
if (!emojiPicker) emojiPicker = buildEmojiPicker();
|
|
425
682
|
if (emojiPicker.style.display === "block") {
|
|
@@ -529,12 +786,44 @@ function reattachOrphanListItems(container) {
|
|
|
529
786
|
}
|
|
530
787
|
}
|
|
531
788
|
}
|
|
789
|
+
var BLOCK_TAGS = /* @__PURE__ */ new Set([
|
|
790
|
+
"h1",
|
|
791
|
+
"h2",
|
|
792
|
+
"h3",
|
|
793
|
+
"h4",
|
|
794
|
+
"h5",
|
|
795
|
+
"h6",
|
|
796
|
+
"p",
|
|
797
|
+
"div",
|
|
798
|
+
"ul",
|
|
799
|
+
"ol",
|
|
800
|
+
"li",
|
|
801
|
+
"blockquote",
|
|
802
|
+
"pre",
|
|
803
|
+
"hr",
|
|
804
|
+
"table"
|
|
805
|
+
]);
|
|
806
|
+
function isBlockNode(node) {
|
|
807
|
+
return node.nodeType === Node.ELEMENT_NODE && BLOCK_TAGS.has(node.tagName.toLowerCase());
|
|
808
|
+
}
|
|
532
809
|
function serializeBlockChildren(parent) {
|
|
533
810
|
const blocks = [];
|
|
534
|
-
|
|
535
|
-
|
|
811
|
+
let inlineRun = "";
|
|
812
|
+
const flushInline = () => {
|
|
813
|
+
const s = inlineRun.trim();
|
|
536
814
|
if (s) blocks.push(s);
|
|
815
|
+
inlineRun = "";
|
|
816
|
+
};
|
|
817
|
+
parent.childNodes.forEach((node) => {
|
|
818
|
+
if (isBlockNode(node)) {
|
|
819
|
+
flushInline();
|
|
820
|
+
const s = serializeBlock(node).trim();
|
|
821
|
+
if (s) blocks.push(s);
|
|
822
|
+
} else {
|
|
823
|
+
inlineRun += serializeInlineNode(node);
|
|
824
|
+
}
|
|
537
825
|
});
|
|
826
|
+
flushInline();
|
|
538
827
|
return blocks.join("\n\n");
|
|
539
828
|
}
|
|
540
829
|
function serializeBlock(node) {
|
|
@@ -608,14 +897,23 @@ function serializeList(listEl, ordered) {
|
|
|
608
897
|
});
|
|
609
898
|
return items.join("\n");
|
|
610
899
|
}
|
|
900
|
+
function escapeAttr(value) {
|
|
901
|
+
return value.replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<");
|
|
902
|
+
}
|
|
611
903
|
function serializeInline(node) {
|
|
612
904
|
let out = "";
|
|
613
905
|
node.childNodes.forEach((child) => {
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
906
|
+
out += serializeInlineNode(child);
|
|
907
|
+
});
|
|
908
|
+
return out;
|
|
909
|
+
}
|
|
910
|
+
function serializeInlineNode(child) {
|
|
911
|
+
if (child.nodeType === Node.TEXT_NODE) {
|
|
912
|
+
return (child.textContent ?? "").replace(/\s+/g, " ");
|
|
913
|
+
}
|
|
914
|
+
if (child.nodeType !== Node.ELEMENT_NODE) return "";
|
|
915
|
+
{
|
|
916
|
+
let out = "";
|
|
619
917
|
const el = child;
|
|
620
918
|
const tag = el.tagName.toLowerCase();
|
|
621
919
|
const inner = serializeInline(el);
|
|
@@ -636,7 +934,15 @@ function serializeInline(node) {
|
|
|
636
934
|
break;
|
|
637
935
|
case "a": {
|
|
638
936
|
const href = el.getAttribute("href") || "";
|
|
639
|
-
|
|
937
|
+
const ref = el.getAttribute("data-cms-ref");
|
|
938
|
+
if (ref) {
|
|
939
|
+
const attrs = [`href="${escapeAttr(href)}"`, `data-cms-ref="${escapeAttr(ref)}"`];
|
|
940
|
+
const label = el.getAttribute("data-cms-ref-label");
|
|
941
|
+
if (label) attrs.push(`data-cms-ref-label="${escapeAttr(label)}"`);
|
|
942
|
+
out += `<a ${attrs.join(" ")}>${inner}</a>`;
|
|
943
|
+
} else {
|
|
944
|
+
out += href ? `[${inner}](${href})` : inner;
|
|
945
|
+
}
|
|
640
946
|
break;
|
|
641
947
|
}
|
|
642
948
|
case "img": {
|
|
@@ -657,8 +963,8 @@ function serializeInline(node) {
|
|
|
657
963
|
default:
|
|
658
964
|
out += inner;
|
|
659
965
|
}
|
|
660
|
-
|
|
661
|
-
|
|
966
|
+
return out;
|
|
967
|
+
}
|
|
662
968
|
}
|
|
663
969
|
async function saveField(el, value, token, options) {
|
|
664
970
|
const collection = el.dataset.cmsCollection;
|