@broberg/cms-inline-edit 0.6.2 → 0.6.3
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 +58 -14
- 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 +51 -13
- 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,14 +23,20 @@ __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
|
|
|
@@ -48,6 +54,7 @@ function applyFieldSlice(current, original, next) {
|
|
|
48
54
|
function isSchemeless(raw) {
|
|
49
55
|
const v = (raw || "").trim();
|
|
50
56
|
if (!v) return false;
|
|
57
|
+
if (isBareEmail(v)) return false;
|
|
51
58
|
if (/^[a-z][a-z0-9+.-]*:/i.test(v)) return false;
|
|
52
59
|
if (v.startsWith("//")) return false;
|
|
53
60
|
if (v.startsWith("/")) return false;
|
|
@@ -55,6 +62,9 @@ function isSchemeless(raw) {
|
|
|
55
62
|
if (v.startsWith("?")) return false;
|
|
56
63
|
return true;
|
|
57
64
|
}
|
|
65
|
+
function isBareEmail(raw) {
|
|
66
|
+
return /^[^\s@/:?#]+@[^\s@/:?#]+\.[a-z]{2,}$/i.test((raw || "").trim());
|
|
67
|
+
}
|
|
58
68
|
function withHttps(raw) {
|
|
59
69
|
return `https://${(raw || "").trim().replace(/^\/+/, "")}`;
|
|
60
70
|
}
|
|
@@ -67,6 +77,18 @@ function isExternalHost(raw, currentHost) {
|
|
|
67
77
|
return false;
|
|
68
78
|
}
|
|
69
79
|
}
|
|
80
|
+
function extractLinkTargets(text) {
|
|
81
|
+
const out = [];
|
|
82
|
+
if (typeof text !== "string") return out;
|
|
83
|
+
for (const m of text.matchAll(/<a\b[^>]*?\bhref\s*=\s*("([^"]*)"|'([^']*)'|([^\s"'>]+))/gi)) {
|
|
84
|
+
out.push({ syntax: "html", value: m[2] ?? m[3] ?? m[4] ?? "" });
|
|
85
|
+
}
|
|
86
|
+
for (const m of text.matchAll(/(!)?\[(?:[^\]\[]|\[[^\]]*\])*\]\(([^)\s]+)/g)) {
|
|
87
|
+
if (m[1]) continue;
|
|
88
|
+
out.push({ syntax: "markdown", value: m[2] ?? "" });
|
|
89
|
+
}
|
|
90
|
+
return out;
|
|
91
|
+
}
|
|
70
92
|
function isDangerousUrl(raw) {
|
|
71
93
|
const v = (raw || "").replace(/[\u0000-\u0020]/g, "");
|
|
72
94
|
return /^(?:javascript|data|vbscript):/i.test(v);
|
|
@@ -599,7 +621,7 @@ function renderLinkDialog() {
|
|
|
599
621
|
}
|
|
600
622
|
const existingRef = linkEditing?.getAttribute("data-cms-ref") ?? "";
|
|
601
623
|
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"
|
|
624
|
+
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
625
|
const q = (role) => d.querySelector(`[data-role="${role}"]`);
|
|
604
626
|
const text = q("text");
|
|
605
627
|
const urlIn = q("url");
|
|
@@ -607,7 +629,7 @@ function renderLinkDialog() {
|
|
|
607
629
|
if (editing) {
|
|
608
630
|
text.value = linkEditing?.getAttribute("data-cms-ref-label") === "auto" ? "" : linkEditing?.textContent ?? "";
|
|
609
631
|
if (!existingRef) urlIn.value = linkEditing?.getAttribute("href") ?? "";
|
|
610
|
-
linkNewTab = linkEditing?.getAttribute("target") === "_blank";
|
|
632
|
+
linkNewTab = (linkEditing?.getAttribute("target") ?? "").trim().toLowerCase() === "_blank";
|
|
611
633
|
linkNewTabTouched = true;
|
|
612
634
|
q("remove").appendChild(buildRemoveLink());
|
|
613
635
|
}
|
|
@@ -742,7 +764,8 @@ function syncLinkDialog() {
|
|
|
742
764
|
const own = q("text").value.trim();
|
|
743
765
|
q("hint").innerHTML = own ? uiLabels.linkTextOwnHint : onPage ? uiLabels.linkTextAutoHint : uiLabels.linkUrlHint;
|
|
744
766
|
q("live").style.display = onPage ? "flex" : "none";
|
|
745
|
-
|
|
767
|
+
const submit = q("submit");
|
|
768
|
+
submit.disabled = onPage ? !linkPicked : !q("url").value.trim();
|
|
746
769
|
const raw = q("url").value;
|
|
747
770
|
const dangerous = !onPage && isDangerousUrl(raw);
|
|
748
771
|
const doubtful = !onPage && !dangerous && isSchemeless(raw);
|
|
@@ -753,34 +776,45 @@ function syncLinkDialog() {
|
|
|
753
776
|
notice.setAttribute("data-variant", "blocked");
|
|
754
777
|
q("schemeless-text").innerHTML = uiLabels.linkBlocked.replace(
|
|
755
778
|
"{v}",
|
|
756
|
-
escapeHtml(raw.trim())
|
|
779
|
+
() => escapeHtml(raw.trim())
|
|
757
780
|
);
|
|
758
|
-
|
|
781
|
+
submit.disabled = true;
|
|
759
782
|
} else if (doubtful) {
|
|
760
783
|
notice.setAttribute("style", noticeCss("schemeless"));
|
|
761
784
|
notice.setAttribute("data-variant", "schemeless");
|
|
762
785
|
q("schemeless-text").innerHTML = uiLabels.linkSchemeless.replace(
|
|
763
786
|
"{v}",
|
|
764
|
-
escapeHtml(raw.trim())
|
|
787
|
+
() => escapeHtml(raw.trim())
|
|
765
788
|
);
|
|
766
789
|
}
|
|
790
|
+
submit.setAttribute("style", btnCss(true, submit.disabled));
|
|
791
|
+
if (!show) {
|
|
792
|
+
notice.removeAttribute("data-variant");
|
|
793
|
+
notice.removeAttribute("data-testid");
|
|
794
|
+
q("schemeless-text").innerHTML = "";
|
|
795
|
+
} else {
|
|
796
|
+
notice.setAttribute("data-testid", dangerous ? "inline-link-blocked" : "inline-link-schemeless");
|
|
797
|
+
}
|
|
767
798
|
notice.style.display = show ? "block" : "none";
|
|
768
799
|
q("schemeless-text").style.color = dangerous ? "#f5c6c6" : "#e8d6b0";
|
|
769
800
|
q("add-scheme").style.display = doubtful ? "" : "none";
|
|
770
|
-
if (!
|
|
771
|
-
linkNewTab = isExternalHost(raw, typeof location !== "undefined" ? location.host : "");
|
|
801
|
+
if (!linkNewTabTouched) {
|
|
802
|
+
linkNewTab = onPage ? false : isExternalHost(raw, typeof location !== "undefined" ? location.host : "");
|
|
772
803
|
}
|
|
773
804
|
const box = q("newtab-box");
|
|
774
805
|
if (box) box.setAttribute("style", checkboxCss(linkNewTab));
|
|
775
806
|
q("newtab").setAttribute("aria-pressed", String(linkNewTab));
|
|
776
807
|
}
|
|
777
808
|
function setLinkTarget(el, newTab) {
|
|
809
|
+
const tokens = (el.getAttribute("rel") ?? "").split(/\s+/).filter(Boolean);
|
|
810
|
+
const kept = tokens.filter((t) => t.toLowerCase() !== "noopener");
|
|
778
811
|
if (newTab) {
|
|
779
812
|
el.setAttribute("target", "_blank");
|
|
780
|
-
el.setAttribute("rel", "noopener");
|
|
813
|
+
el.setAttribute("rel", ["noopener", ...kept].join(" "));
|
|
781
814
|
} else {
|
|
782
815
|
el.removeAttribute("target");
|
|
783
|
-
el.
|
|
816
|
+
if (kept.length) el.setAttribute("rel", kept.join(" "));
|
|
817
|
+
else el.removeAttribute("rel");
|
|
784
818
|
}
|
|
785
819
|
}
|
|
786
820
|
function applyLink() {
|
|
@@ -837,7 +871,11 @@ var noticeCss = (variant) => "display:none;margin-top:8px;padding:8px 10px;borde
|
|
|
837
871
|
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
872
|
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
873
|
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;
|
|
874
|
+
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
|
|
875
|
+
// card exists to remove: the editor clicks, nothing happens, and the dialog
|
|
876
|
+
// reads as broken rather than as refusing. It matters more now that `disabled`
|
|
877
|
+
// is what stops a javascript: address being inserted.
|
|
878
|
+
(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
879
|
function toggleEmojiPicker(anchor) {
|
|
842
880
|
if (!emojiPicker) emojiPicker = buildEmojiPicker();
|
|
843
881
|
if (emojiPicker.style.display === "block") {
|
|
@@ -1066,7 +1104,7 @@ function preservedLinkAttrs(el) {
|
|
|
1066
1104
|
const name = attr.name.toLowerCase();
|
|
1067
1105
|
if (name === "href") continue;
|
|
1068
1106
|
if (name.startsWith("on")) continue;
|
|
1069
|
-
if (
|
|
1107
|
+
if (isDangerousUrl(attr.value)) continue;
|
|
1070
1108
|
out.push(`${name}="${escapeAttr(attr.value)}"`);
|
|
1071
1109
|
}
|
|
1072
1110
|
return out;
|
|
@@ -1265,13 +1303,19 @@ function injectStyles() {
|
|
|
1265
1303
|
applyFieldSlice,
|
|
1266
1304
|
buildConnectUrl,
|
|
1267
1305
|
disconnect,
|
|
1306
|
+
extractLinkTargets,
|
|
1268
1307
|
getConnectedToken,
|
|
1269
1308
|
htmlToMarkdown,
|
|
1270
1309
|
initInlineEdit,
|
|
1310
|
+
isBareEmail,
|
|
1311
|
+
isDangerousUrl,
|
|
1312
|
+
isExternalHost,
|
|
1313
|
+
isSchemeless,
|
|
1271
1314
|
positionPopover,
|
|
1272
1315
|
renderCurrentRefLine,
|
|
1273
1316
|
rescanFields,
|
|
1274
1317
|
resolveExistingRef,
|
|
1275
|
-
setLinkTarget
|
|
1318
|
+
setLinkTarget,
|
|
1319
|
+
withHttps
|
|
1276
1320
|
});
|
|
1277
1321
|
//# sourceMappingURL=index.cjs.map
|