@broberg/cms-inline-edit 0.6.2 → 0.7.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/dist/index.cjs +266 -15
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +70 -1
- package/dist/index.d.ts +70 -1
- package/dist/index.js +259 -14
- package/dist/index.js.map +1 -1
- package/dist/server/index.cjs +14 -1
- package/dist/server/index.cjs.map +1 -1
- package/dist/server/index.d.cts +18 -1
- package/dist/server/index.d.ts +18 -1
- package/dist/server/index.js +13 -1
- package/dist/server/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -23,17 +23,64 @@ __export(index_exports, {
|
|
|
23
23
|
applyFieldSlice: () => applyFieldSlice,
|
|
24
24
|
buildConnectUrl: () => buildConnectUrl,
|
|
25
25
|
disconnect: () => disconnect,
|
|
26
|
+
extractLinkTargets: () => extractLinkTargets,
|
|
26
27
|
getConnectedToken: () => getConnectedToken,
|
|
27
28
|
htmlToMarkdown: () => htmlToMarkdown,
|
|
28
29
|
initInlineEdit: () => initInlineEdit,
|
|
30
|
+
isBareEmail: () => isBareEmail,
|
|
31
|
+
isDangerousUrl: () => isDangerousUrl,
|
|
32
|
+
isExternalHost: () => isExternalHost,
|
|
33
|
+
isSchemeless: () => isSchemeless,
|
|
29
34
|
positionPopover: () => positionPopover,
|
|
30
35
|
renderCurrentRefLine: () => renderCurrentRefLine,
|
|
31
36
|
rescanFields: () => rescanFields,
|
|
32
37
|
resolveExistingRef: () => resolveExistingRef,
|
|
33
|
-
setLinkTarget: () => setLinkTarget
|
|
38
|
+
setLinkTarget: () => setLinkTarget,
|
|
39
|
+
withHttps: () => withHttps
|
|
34
40
|
});
|
|
35
41
|
module.exports = __toCommonJS(index_exports);
|
|
36
42
|
|
|
43
|
+
// src/page-tools.ts
|
|
44
|
+
function parseTags(input) {
|
|
45
|
+
const set = /* @__PURE__ */ new Set();
|
|
46
|
+
const ud = [];
|
|
47
|
+
for (const raa of input.split(",")) {
|
|
48
|
+
const t = raa.trim();
|
|
49
|
+
if (!t) continue;
|
|
50
|
+
const noegle = t.toLowerCase();
|
|
51
|
+
if (set.has(noegle)) continue;
|
|
52
|
+
set.add(noegle);
|
|
53
|
+
ud.push(t);
|
|
54
|
+
}
|
|
55
|
+
return ud;
|
|
56
|
+
}
|
|
57
|
+
function mergeTags(eksisterende, nye) {
|
|
58
|
+
const basis = Array.isArray(eksisterende) ? eksisterende.filter((t) => typeof t === "string") : [];
|
|
59
|
+
const kendte = new Set(basis.map((t) => t.toLowerCase()));
|
|
60
|
+
const ud = [...basis];
|
|
61
|
+
for (const t of nye) {
|
|
62
|
+
if (kendte.has(t.toLowerCase())) continue;
|
|
63
|
+
kendte.add(t.toLowerCase());
|
|
64
|
+
ud.push(t);
|
|
65
|
+
}
|
|
66
|
+
return ud;
|
|
67
|
+
}
|
|
68
|
+
function primaryDocRef(ankre) {
|
|
69
|
+
const taeller = /* @__PURE__ */ new Map();
|
|
70
|
+
for (const a of ankre) {
|
|
71
|
+
if (!a.collection || !a.slug || a.collection === "globals") continue;
|
|
72
|
+
const noegle = `${a.collection} ${a.slug}`;
|
|
73
|
+
const post = taeller.get(noegle) ?? { collection: a.collection, slug: a.slug, n: 0 };
|
|
74
|
+
post.n++;
|
|
75
|
+
taeller.set(noegle, post);
|
|
76
|
+
}
|
|
77
|
+
let bedst = null;
|
|
78
|
+
for (const post of taeller.values()) {
|
|
79
|
+
if (!bedst || post.n > bedst.n) bedst = post;
|
|
80
|
+
}
|
|
81
|
+
return bedst ? { collection: bedst.collection, slug: bedst.slug } : null;
|
|
82
|
+
}
|
|
83
|
+
|
|
37
84
|
// src/field-slice.ts
|
|
38
85
|
function applyFieldSlice(current, original, next) {
|
|
39
86
|
const first = current.indexOf(original);
|
|
@@ -48,6 +95,7 @@ function applyFieldSlice(current, original, next) {
|
|
|
48
95
|
function isSchemeless(raw) {
|
|
49
96
|
const v = (raw || "").trim();
|
|
50
97
|
if (!v) return false;
|
|
98
|
+
if (isBareEmail(v)) return false;
|
|
51
99
|
if (/^[a-z][a-z0-9+.-]*:/i.test(v)) return false;
|
|
52
100
|
if (v.startsWith("//")) return false;
|
|
53
101
|
if (v.startsWith("/")) return false;
|
|
@@ -55,6 +103,9 @@ function isSchemeless(raw) {
|
|
|
55
103
|
if (v.startsWith("?")) return false;
|
|
56
104
|
return true;
|
|
57
105
|
}
|
|
106
|
+
function isBareEmail(raw) {
|
|
107
|
+
return /^[^\s@/:?#]+@[^\s@/:?#]+\.[a-z]{2,}$/i.test((raw || "").trim());
|
|
108
|
+
}
|
|
58
109
|
function withHttps(raw) {
|
|
59
110
|
return `https://${(raw || "").trim().replace(/^\/+/, "")}`;
|
|
60
111
|
}
|
|
@@ -67,6 +118,18 @@ function isExternalHost(raw, currentHost) {
|
|
|
67
118
|
return false;
|
|
68
119
|
}
|
|
69
120
|
}
|
|
121
|
+
function extractLinkTargets(text) {
|
|
122
|
+
const out = [];
|
|
123
|
+
if (typeof text !== "string") return out;
|
|
124
|
+
for (const m of text.matchAll(/<a\b[^>]*?\bhref\s*=\s*("([^"]*)"|'([^']*)'|([^\s"'>]+))/gi)) {
|
|
125
|
+
out.push({ syntax: "html", value: m[2] ?? m[3] ?? m[4] ?? "" });
|
|
126
|
+
}
|
|
127
|
+
for (const m of text.matchAll(/(!)?\[(?:[^\]\[]|\[[^\]]*\])*\]\(([^)\s]+)/g)) {
|
|
128
|
+
if (m[1]) continue;
|
|
129
|
+
out.push({ syntax: "markdown", value: m[2] ?? "" });
|
|
130
|
+
}
|
|
131
|
+
return out;
|
|
132
|
+
}
|
|
70
133
|
function isDangerousUrl(raw) {
|
|
71
134
|
const v = (raw || "").replace(/[\u0000-\u0020]/g, "");
|
|
72
135
|
return /^(?:javascript|data|vbscript):/i.test(v);
|
|
@@ -248,6 +311,7 @@ var SQUARE_PEN_SVG = '<svg xmlns="http://www.w3.org/2000/svg" width="15" height=
|
|
|
248
311
|
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>';
|
|
249
312
|
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>';
|
|
250
313
|
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>';
|
|
314
|
+
var WRENCH_SVG = '<svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z"/></svg>';
|
|
251
315
|
function makeIcon() {
|
|
252
316
|
const icon = document.createElement("span");
|
|
253
317
|
icon.style.cssText = "display:inline-flex;line-height:0;flex:0 0 auto";
|
|
@@ -599,7 +663,7 @@ function renderLinkDialog() {
|
|
|
599
663
|
}
|
|
600
664
|
const existingRef = linkEditing?.getAttribute("data-cms-ref") ?? "";
|
|
601
665
|
const onPageTab = !editing || !!existingRef;
|
|
602
|
-
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="current" data-testid="inline-link-current" style="display:none;margin-top:8px;padding:7px 10px;background:rgba(0,178,255,.08);border:1px solid rgba(0,178,255,.28);border-radius:8px;font-size:11.5px;color:#c9d1dd;line-height:1.45"></div><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 data-role="schemeless"
|
|
666
|
+
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="current" data-testid="inline-link-current" style="display:none;margin-top:8px;padding:7px 10px;background:rgba(0,178,255,.08);border:1px solid rgba(0,178,255,.28);border-radius:8px;font-size:11.5px;color:#c9d1dd;line-height:1.45"></div><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 data-role="schemeless" style="${noticeCss("schemeless")}"><p data-role="schemeless-text" style="margin:0;font-size:11.5px;line-height:1.5"></p><button type="button" data-testid="inline-link-add-scheme" data-role="add-scheme" style="${btnCss(false)};margin-top:7px;height:26px;font-size:12px">${L.linkSchemelessFix}</button></div></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><button type="button" data-role="newtab" data-testid="inline-link-newtab" aria-pressed="false" style="display:flex;align-items:center;gap:8px;margin-top:12px;background:none;border:none;padding:0;cursor:pointer;font-family:inherit;font-size:12.5px;color:#c9d1dd"><span data-role="newtab-box" style="${checkboxCss(false)}"></span><span>${L.linkNewTab}</span></button><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, true)}" disabled>${editing ? L.linkSave : L.linkInsert}</button></div></div>`;
|
|
603
667
|
const q = (role) => d.querySelector(`[data-role="${role}"]`);
|
|
604
668
|
const text = q("text");
|
|
605
669
|
const urlIn = q("url");
|
|
@@ -607,7 +671,7 @@ function renderLinkDialog() {
|
|
|
607
671
|
if (editing) {
|
|
608
672
|
text.value = linkEditing?.getAttribute("data-cms-ref-label") === "auto" ? "" : linkEditing?.textContent ?? "";
|
|
609
673
|
if (!existingRef) urlIn.value = linkEditing?.getAttribute("href") ?? "";
|
|
610
|
-
linkNewTab = linkEditing?.getAttribute("target") === "_blank";
|
|
674
|
+
linkNewTab = (linkEditing?.getAttribute("target") ?? "").trim().toLowerCase() === "_blank";
|
|
611
675
|
linkNewTabTouched = true;
|
|
612
676
|
q("remove").appendChild(buildRemoveLink());
|
|
613
677
|
}
|
|
@@ -742,7 +806,8 @@ function syncLinkDialog() {
|
|
|
742
806
|
const own = q("text").value.trim();
|
|
743
807
|
q("hint").innerHTML = own ? uiLabels.linkTextOwnHint : onPage ? uiLabels.linkTextAutoHint : uiLabels.linkUrlHint;
|
|
744
808
|
q("live").style.display = onPage ? "flex" : "none";
|
|
745
|
-
|
|
809
|
+
const submit = q("submit");
|
|
810
|
+
submit.disabled = onPage ? !linkPicked : !q("url").value.trim();
|
|
746
811
|
const raw = q("url").value;
|
|
747
812
|
const dangerous = !onPage && isDangerousUrl(raw);
|
|
748
813
|
const doubtful = !onPage && !dangerous && isSchemeless(raw);
|
|
@@ -753,34 +818,45 @@ function syncLinkDialog() {
|
|
|
753
818
|
notice.setAttribute("data-variant", "blocked");
|
|
754
819
|
q("schemeless-text").innerHTML = uiLabels.linkBlocked.replace(
|
|
755
820
|
"{v}",
|
|
756
|
-
escapeHtml(raw.trim())
|
|
821
|
+
() => escapeHtml(raw.trim())
|
|
757
822
|
);
|
|
758
|
-
|
|
823
|
+
submit.disabled = true;
|
|
759
824
|
} else if (doubtful) {
|
|
760
825
|
notice.setAttribute("style", noticeCss("schemeless"));
|
|
761
826
|
notice.setAttribute("data-variant", "schemeless");
|
|
762
827
|
q("schemeless-text").innerHTML = uiLabels.linkSchemeless.replace(
|
|
763
828
|
"{v}",
|
|
764
|
-
escapeHtml(raw.trim())
|
|
829
|
+
() => escapeHtml(raw.trim())
|
|
765
830
|
);
|
|
766
831
|
}
|
|
832
|
+
submit.setAttribute("style", btnCss(true, submit.disabled));
|
|
833
|
+
if (!show) {
|
|
834
|
+
notice.removeAttribute("data-variant");
|
|
835
|
+
notice.removeAttribute("data-testid");
|
|
836
|
+
q("schemeless-text").innerHTML = "";
|
|
837
|
+
} else {
|
|
838
|
+
notice.setAttribute("data-testid", dangerous ? "inline-link-blocked" : "inline-link-schemeless");
|
|
839
|
+
}
|
|
767
840
|
notice.style.display = show ? "block" : "none";
|
|
768
841
|
q("schemeless-text").style.color = dangerous ? "#f5c6c6" : "#e8d6b0";
|
|
769
842
|
q("add-scheme").style.display = doubtful ? "" : "none";
|
|
770
|
-
if (!
|
|
771
|
-
linkNewTab = isExternalHost(raw, typeof location !== "undefined" ? location.host : "");
|
|
843
|
+
if (!linkNewTabTouched) {
|
|
844
|
+
linkNewTab = onPage ? false : isExternalHost(raw, typeof location !== "undefined" ? location.host : "");
|
|
772
845
|
}
|
|
773
846
|
const box = q("newtab-box");
|
|
774
847
|
if (box) box.setAttribute("style", checkboxCss(linkNewTab));
|
|
775
848
|
q("newtab").setAttribute("aria-pressed", String(linkNewTab));
|
|
776
849
|
}
|
|
777
850
|
function setLinkTarget(el, newTab) {
|
|
851
|
+
const tokens = (el.getAttribute("rel") ?? "").split(/\s+/).filter(Boolean);
|
|
852
|
+
const kept = tokens.filter((t) => t.toLowerCase() !== "noopener");
|
|
778
853
|
if (newTab) {
|
|
779
854
|
el.setAttribute("target", "_blank");
|
|
780
|
-
el.setAttribute("rel", "noopener");
|
|
855
|
+
el.setAttribute("rel", ["noopener", ...kept].join(" "));
|
|
781
856
|
} else {
|
|
782
857
|
el.removeAttribute("target");
|
|
783
|
-
el.
|
|
858
|
+
if (kept.length) el.setAttribute("rel", kept.join(" "));
|
|
859
|
+
else el.removeAttribute("rel");
|
|
784
860
|
}
|
|
785
861
|
}
|
|
786
862
|
function applyLink() {
|
|
@@ -837,7 +913,11 @@ var noticeCss = (variant) => "display:none;margin-top:8px;padding:8px 10px;borde
|
|
|
837
913
|
var checkboxCss = (on) => "width:16px;height:16px;flex:0 0 16px;border-radius:4px;box-sizing:border-box;display:inline-block;" + (on ? `background:#00b2ff;border:1px solid #00b2ff;background-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'><path d='M4 8.5l2.6 2.6L12 5.8' fill='none' stroke='white' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/></svg>");background-size:100% 100%;` : "background:#141821;border:1px solid #3a3f4a;");
|
|
838
914
|
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;";
|
|
839
915
|
var labelCss = () => "font-size:11px;letter-spacing:.06em;text-transform:uppercase;color:#9aa3b2;margin:12px 0 5px;display:block;";
|
|
840
|
-
var btnCss = (primary) => "height:33px;border-radius:7px;font-size:13px;
|
|
916
|
+
var btnCss = (primary, disabled = false) => "height:33px;border-radius:7px;font-size:13px;padding:0 14px;font-weight:600;font-family:inherit;" + // A disabled button that still looks clickable is the silent state this whole
|
|
917
|
+
// card exists to remove: the editor clicks, nothing happens, and the dialog
|
|
918
|
+
// reads as broken rather than as refusing. It matters more now that `disabled`
|
|
919
|
+
// is what stops a javascript: address being inserted.
|
|
920
|
+
(disabled ? "cursor:not-allowed;opacity:.42;" : "cursor:pointer;") + (primary ? "background:#00b2ff;color:#04121b;border:none;" : "background:none;border:1px solid #3a3f4a;color:#fff;font-weight:500;");
|
|
841
921
|
function toggleEmojiPicker(anchor) {
|
|
842
922
|
if (!emojiPicker) emojiPicker = buildEmojiPicker();
|
|
843
923
|
if (emojiPicker.style.display === "block") {
|
|
@@ -1066,7 +1146,7 @@ function preservedLinkAttrs(el) {
|
|
|
1066
1146
|
const name = attr.name.toLowerCase();
|
|
1067
1147
|
if (name === "href") continue;
|
|
1068
1148
|
if (name.startsWith("on")) continue;
|
|
1069
|
-
if (
|
|
1149
|
+
if (isDangerousUrl(attr.value)) continue;
|
|
1070
1150
|
out.push(`${name}="${escapeAttr(attr.value)}"`);
|
|
1071
1151
|
}
|
|
1072
1152
|
return out;
|
|
@@ -1238,7 +1318,172 @@ function showActiveBadge(options) {
|
|
|
1238
1318
|
badge.addEventListener("mouseenter", () => badge.style.opacity = "1");
|
|
1239
1319
|
badge.addEventListener("mouseleave", () => badge.style.opacity = ".9");
|
|
1240
1320
|
badge.addEventListener("click", exitEditMode);
|
|
1241
|
-
|
|
1321
|
+
badge.style.position = "static";
|
|
1322
|
+
badge.style.borderRadius = "999px 0 0 999px";
|
|
1323
|
+
const raekke = document.createElement("div");
|
|
1324
|
+
raekke.setAttribute("data-cms-inline-edit-toolsrow", "");
|
|
1325
|
+
raekke.style.cssText = "position:fixed;bottom:16px;left:16px;display:inline-flex;align-items:stretch;z-index:2147483647;box-shadow:0 4px 16px rgba(0,0,0,.3);border-radius:999px;overflow:visible;";
|
|
1326
|
+
const tools = document.createElement("button");
|
|
1327
|
+
tools.type = "button";
|
|
1328
|
+
tools.setAttribute("data-cms-inline-edit-tools", "");
|
|
1329
|
+
tools.setAttribute("data-testid", "inline-edit-tools");
|
|
1330
|
+
tools.setAttribute("aria-label", "Tools");
|
|
1331
|
+
tools.title = "Tools";
|
|
1332
|
+
tools.style.cssText = "display:inline-flex;align-items:center;background:#1c2027;color:#8b93a3;padding:8px 12px;border:none;border-left:1px solid rgba(255,255,255,.12);border-radius:0 999px 999px 0;cursor:pointer;font:600 12px system-ui,sans-serif;";
|
|
1333
|
+
tools.innerHTML = WRENCH_SVG;
|
|
1334
|
+
tools.addEventListener("mouseenter", () => tools.style.color = "#fff");
|
|
1335
|
+
tools.addEventListener("mouseleave", () => tools.style.color = "#8b93a3");
|
|
1336
|
+
tools.addEventListener("click", () => togglePageTools(options, raekke));
|
|
1337
|
+
raekke.append(badge, tools);
|
|
1338
|
+
document.body.appendChild(raekke);
|
|
1339
|
+
}
|
|
1340
|
+
async function hentDoc(collection, slug, token, options) {
|
|
1341
|
+
const res = await fetch(`${options.cmsBaseUrl}/api/cms/${collection}/${slug}?site=${options.siteId}`, {
|
|
1342
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
1343
|
+
});
|
|
1344
|
+
if (!res.ok) throw new Error(`GET failed: ${res.status}`);
|
|
1345
|
+
const doc = await res.json();
|
|
1346
|
+
return JSON.parse(JSON.stringify(doc.data ?? {}));
|
|
1347
|
+
}
|
|
1348
|
+
async function gemDoc(collection, slug, data, token, options) {
|
|
1349
|
+
const res = await fetch(`${options.cmsBaseUrl}/api/cms/${collection}/${slug}?site=${options.siteId}`, {
|
|
1350
|
+
method: "PATCH",
|
|
1351
|
+
headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json" },
|
|
1352
|
+
body: JSON.stringify({ data })
|
|
1353
|
+
});
|
|
1354
|
+
if (!res.ok) throw new Error(`PATCH failed: ${res.status}`);
|
|
1355
|
+
}
|
|
1356
|
+
function togglePageTools(options, forankring) {
|
|
1357
|
+
const \u00E5ben = document.querySelector("[data-cms-inline-edit-tools-popup]");
|
|
1358
|
+
if (\u00E5ben) {
|
|
1359
|
+
\u00E5ben.remove();
|
|
1360
|
+
return;
|
|
1361
|
+
}
|
|
1362
|
+
const token = getConnectedToken(options);
|
|
1363
|
+
if (!token) return;
|
|
1364
|
+
const ankre = Array.from(
|
|
1365
|
+
document.querySelectorAll("[data-cms-field]")
|
|
1366
|
+
).map((el) => {
|
|
1367
|
+
const a = {};
|
|
1368
|
+
if (el.dataset.cmsCollection) a.collection = el.dataset.cmsCollection;
|
|
1369
|
+
if (el.dataset.cmsSlug) a.slug = el.dataset.cmsSlug;
|
|
1370
|
+
return a;
|
|
1371
|
+
});
|
|
1372
|
+
const ref = primaryDocRef(ankre);
|
|
1373
|
+
if (!ref) return;
|
|
1374
|
+
const popup = document.createElement("div");
|
|
1375
|
+
popup.setAttribute("data-cms-inline-edit-tools-popup", "");
|
|
1376
|
+
popup.setAttribute("data-testid", "page-tools-popup");
|
|
1377
|
+
popup.style.cssText = "position:fixed;bottom:56px;left:16px;z-index:2147483647;background:#1c2027;border:1px solid #3a3f4a;border-radius:12px;padding:10px;min-width:230px;box-shadow:0 8px 32px rgba(0,0,0,.5);font-family:system-ui,sans-serif;color:#fff;display:flex;flex-direction:column;gap:8px;";
|
|
1378
|
+
const featRow = document.createElement("button");
|
|
1379
|
+
featRow.type = "button";
|
|
1380
|
+
featRow.setAttribute("data-testid", "page-tools-featured");
|
|
1381
|
+
featRow.style.cssText = "display:flex;align-items:center;gap:8px;background:rgba(255,255,255,.04);border:1px solid #3a3f4a;border-radius:9px;padding:8px 10px;font:600 12px system-ui,sans-serif;color:#fff;cursor:pointer;text-align:left;";
|
|
1382
|
+
featRow.textContent = "\u2605 Featured";
|
|
1383
|
+
featRow.disabled = true;
|
|
1384
|
+
popup.appendChild(featRow);
|
|
1385
|
+
const visFeatured = (til) => {
|
|
1386
|
+
featRow.style.borderColor = til ? "#00b2ff" : "#3a3f4a";
|
|
1387
|
+
featRow.style.color = til ? "#40c8ff" : "#fff";
|
|
1388
|
+
featRow.style.background = til ? "rgba(0,178,255,.13)" : "rgba(255,255,255,.04)";
|
|
1389
|
+
};
|
|
1390
|
+
let featured = false;
|
|
1391
|
+
void hentDoc(ref.collection, ref.slug, token, options).then((data) => {
|
|
1392
|
+
featured = data.featured === true;
|
|
1393
|
+
visFeatured(featured);
|
|
1394
|
+
featRow.disabled = false;
|
|
1395
|
+
}).catch(() => {
|
|
1396
|
+
featRow.textContent = "\u2605 Featured \u2014 kunne ikke l\xE6ses";
|
|
1397
|
+
});
|
|
1398
|
+
featRow.addEventListener("click", async () => {
|
|
1399
|
+
featRow.disabled = true;
|
|
1400
|
+
try {
|
|
1401
|
+
const data = await hentDoc(ref.collection, ref.slug, token, options);
|
|
1402
|
+
data.featured = !(data.featured === true);
|
|
1403
|
+
await gemDoc(ref.collection, ref.slug, data, token, options);
|
|
1404
|
+
const frisk = await hentDoc(ref.collection, ref.slug, token, options);
|
|
1405
|
+
featured = frisk.featured === true;
|
|
1406
|
+
visFeatured(featured);
|
|
1407
|
+
} catch {
|
|
1408
|
+
featRow.style.borderColor = "#f3522c";
|
|
1409
|
+
} finally {
|
|
1410
|
+
featRow.disabled = false;
|
|
1411
|
+
}
|
|
1412
|
+
});
|
|
1413
|
+
const tagRow = document.createElement("div");
|
|
1414
|
+
tagRow.style.cssText = "display:flex;align-items:center;gap:6px;";
|
|
1415
|
+
const plus = document.createElement("button");
|
|
1416
|
+
plus.type = "button";
|
|
1417
|
+
plus.setAttribute("data-testid", "page-tools-tags-plus");
|
|
1418
|
+
plus.style.cssText = "background:rgba(255,255,255,.04);border:1px solid #3a3f4a;border-radius:9px;padding:8px 12px;color:#fff;font:600 12px system-ui,sans-serif;cursor:pointer;";
|
|
1419
|
+
plus.textContent = "# Tags +";
|
|
1420
|
+
const felt = document.createElement("input");
|
|
1421
|
+
felt.type = "text";
|
|
1422
|
+
felt.placeholder = "tag1, tag2, tag3";
|
|
1423
|
+
felt.setAttribute("data-testid", "page-tools-tags-input");
|
|
1424
|
+
felt.style.cssText = "flex:1;min-width:0;display:none;background:#12151a;border:1px solid #3a3f4a;border-radius:8px;padding:7px 9px;color:#fff;font:12px system-ui,sans-serif;outline:none;";
|
|
1425
|
+
const gem = document.createElement("button");
|
|
1426
|
+
gem.type = "button";
|
|
1427
|
+
gem.setAttribute("data-testid", "page-tools-tags-gem");
|
|
1428
|
+
gem.style.cssText = "display:none;background:#00b2ff;border:none;border-radius:8px;padding:7px 11px;color:#04222f;font:700 12px system-ui,sans-serif;cursor:pointer;";
|
|
1429
|
+
gem.textContent = "Gem";
|
|
1430
|
+
plus.addEventListener("click", () => {
|
|
1431
|
+
plus.style.display = "none";
|
|
1432
|
+
felt.style.display = "block";
|
|
1433
|
+
gem.style.display = "inline-block";
|
|
1434
|
+
felt.focus();
|
|
1435
|
+
});
|
|
1436
|
+
const gemTags = async () => {
|
|
1437
|
+
const nye = parseTags(felt.value);
|
|
1438
|
+
if (!nye.length) return;
|
|
1439
|
+
gem.disabled = true;
|
|
1440
|
+
gem.textContent = "\u2026";
|
|
1441
|
+
try {
|
|
1442
|
+
const data = await hentDoc(ref.collection, ref.slug, token, options);
|
|
1443
|
+
data.tags = mergeTags(data.tags, nye);
|
|
1444
|
+
await gemDoc(ref.collection, ref.slug, data, token, options);
|
|
1445
|
+
const frisk = await hentDoc(ref.collection, ref.slug, token, options);
|
|
1446
|
+
const holdt = Array.isArray(frisk.tags) && nye.every((t) => frisk.tags.some((e) => String(e).toLowerCase() === t.toLowerCase()));
|
|
1447
|
+
gem.textContent = holdt ? "Gemt \u2713" : "Fejl";
|
|
1448
|
+
if (holdt) {
|
|
1449
|
+
felt.value = "";
|
|
1450
|
+
setTimeout(() => {
|
|
1451
|
+
gem.textContent = "Gem";
|
|
1452
|
+
gem.disabled = false;
|
|
1453
|
+
}, 1200);
|
|
1454
|
+
} else {
|
|
1455
|
+
gem.disabled = false;
|
|
1456
|
+
}
|
|
1457
|
+
} catch {
|
|
1458
|
+
gem.textContent = "Fejl";
|
|
1459
|
+
gem.disabled = false;
|
|
1460
|
+
}
|
|
1461
|
+
};
|
|
1462
|
+
gem.addEventListener("click", () => void gemTags());
|
|
1463
|
+
felt.addEventListener("keydown", (e) => {
|
|
1464
|
+
if (e.key === "Enter") {
|
|
1465
|
+
e.preventDefault();
|
|
1466
|
+
void gemTags();
|
|
1467
|
+
}
|
|
1468
|
+
});
|
|
1469
|
+
tagRow.append(plus, felt, gem);
|
|
1470
|
+
popup.appendChild(tagRow);
|
|
1471
|
+
const lukP\u00E5Klik = (e) => {
|
|
1472
|
+
if (!popup.contains(e.target) && !forankring.contains(e.target)) luk();
|
|
1473
|
+
};
|
|
1474
|
+
const lukP\u00E5Esc = (e) => {
|
|
1475
|
+
if (e.key === "Escape") luk();
|
|
1476
|
+
};
|
|
1477
|
+
const luk = () => {
|
|
1478
|
+
popup.remove();
|
|
1479
|
+
document.removeEventListener("mousedown", lukP\u00E5Klik);
|
|
1480
|
+
document.removeEventListener("keydown", lukP\u00E5Esc);
|
|
1481
|
+
};
|
|
1482
|
+
setTimeout(() => {
|
|
1483
|
+
document.addEventListener("mousedown", lukP\u00E5Klik);
|
|
1484
|
+
document.addEventListener("keydown", lukP\u00E5Esc);
|
|
1485
|
+
}, 0);
|
|
1486
|
+
document.body.appendChild(popup);
|
|
1242
1487
|
}
|
|
1243
1488
|
function decodeJwtPayload(token) {
|
|
1244
1489
|
try {
|
|
@@ -1265,13 +1510,19 @@ function injectStyles() {
|
|
|
1265
1510
|
applyFieldSlice,
|
|
1266
1511
|
buildConnectUrl,
|
|
1267
1512
|
disconnect,
|
|
1513
|
+
extractLinkTargets,
|
|
1268
1514
|
getConnectedToken,
|
|
1269
1515
|
htmlToMarkdown,
|
|
1270
1516
|
initInlineEdit,
|
|
1517
|
+
isBareEmail,
|
|
1518
|
+
isDangerousUrl,
|
|
1519
|
+
isExternalHost,
|
|
1520
|
+
isSchemeless,
|
|
1271
1521
|
positionPopover,
|
|
1272
1522
|
renderCurrentRefLine,
|
|
1273
1523
|
rescanFields,
|
|
1274
1524
|
resolveExistingRef,
|
|
1275
|
-
setLinkTarget
|
|
1525
|
+
setLinkTarget,
|
|
1526
|
+
withHttps
|
|
1276
1527
|
});
|
|
1277
1528
|
//# sourceMappingURL=index.cjs.map
|