@broberg/cms-inline-edit 0.6.1 → 0.6.2

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.d.cts CHANGED
@@ -46,6 +46,7 @@ interface InlineEditLabels {
46
46
  linkNewTab?: string;
47
47
  linkSchemeless?: string;
48
48
  linkSchemelessFix?: string;
49
+ linkBlocked?: string;
49
50
  linkInsert?: string;
50
51
  linkSave?: string;
51
52
  linkCancel?: string;
package/dist/index.d.ts CHANGED
@@ -46,6 +46,7 @@ interface InlineEditLabels {
46
46
  linkNewTab?: string;
47
47
  linkSchemeless?: string;
48
48
  linkSchemelessFix?: string;
49
+ linkBlocked?: string;
49
50
  linkInsert?: string;
50
51
  linkSave?: string;
51
52
  linkCancel?: string;
package/dist/index.js CHANGED
@@ -31,6 +31,10 @@ function isExternalHost(raw, currentHost) {
31
31
  return false;
32
32
  }
33
33
  }
34
+ function isDangerousUrl(raw) {
35
+ const v = (raw || "").replace(/[\u0000-\u0020]/g, "");
36
+ return /^(?:javascript|data|vbscript):/i.test(v);
37
+ }
34
38
 
35
39
  // src/token-safe.ts
36
40
  var TEXT_NODE = 3;
@@ -89,6 +93,7 @@ var DEFAULT_LABELS = {
89
93
  linkNewTab: "\xC5bn i ny fane",
90
94
  linkSchemeless: "<b>{v}</b> l\xE6ses som en side p\xE5 <b>dette</b> site \u2014 ikke som en adresse ude p\xE5 nettet.",
91
95
  linkSchemelessFix: "Tilf\xF8j https://",
96
+ linkBlocked: "<b>{v}</b> kan ikke bruges som link \u2014 adresser af den type kan k\xF8re kode p\xE5 sitet.",
92
97
  linkInsert: "Inds\xE6t link",
93
98
  linkSave: "Gem \xE6ndring",
94
99
  linkCancel: "Annull\xE9r",
@@ -558,7 +563,7 @@ function renderLinkDialog() {
558
563
  }
559
564
  const existingRef = linkEditing?.getAttribute("data-cms-ref") ?? "";
560
565
  const onPageTab = !editing || !!existingRef;
561
- 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" data-testid="inline-link-schemeless" style="display:none;margin-top:8px;padding:8px 10px;background:rgba(255,176,32,.10);border:1px solid rgba(255,176,32,.34);border-radius:8px"><p data-role="schemeless-text" style="margin:0;font-size:11.5px;color:#e8d6b0;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)}" disabled>${editing ? L.linkSave : L.linkInsert}</button></div></div>`;
566
+ 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" data-testid="inline-link-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)}" disabled>${editing ? L.linkSave : L.linkInsert}</button></div></div>`;
562
567
  const q = (role) => d.querySelector(`[data-role="${role}"]`);
563
568
  const text = q("text");
564
569
  const urlIn = q("url");
@@ -703,14 +708,29 @@ function syncLinkDialog() {
703
708
  q("live").style.display = onPage ? "flex" : "none";
704
709
  q("submit").disabled = onPage ? !linkPicked : !q("url").value.trim();
705
710
  const raw = q("url").value;
706
- const doubtful = !onPage && isSchemeless(raw);
707
- q("schemeless").style.display = doubtful ? "block" : "none";
708
- if (doubtful) {
711
+ const dangerous = !onPage && isDangerousUrl(raw);
712
+ const doubtful = !onPage && !dangerous && isSchemeless(raw);
713
+ const notice = q("schemeless");
714
+ const show = dangerous || doubtful;
715
+ if (dangerous) {
716
+ notice.setAttribute("style", noticeCss("blocked"));
717
+ notice.setAttribute("data-variant", "blocked");
718
+ q("schemeless-text").innerHTML = uiLabels.linkBlocked.replace(
719
+ "{v}",
720
+ escapeHtml(raw.trim())
721
+ );
722
+ q("submit").disabled = true;
723
+ } else if (doubtful) {
724
+ notice.setAttribute("style", noticeCss("schemeless"));
725
+ notice.setAttribute("data-variant", "schemeless");
709
726
  q("schemeless-text").innerHTML = uiLabels.linkSchemeless.replace(
710
727
  "{v}",
711
728
  escapeHtml(raw.trim())
712
729
  );
713
730
  }
731
+ notice.style.display = show ? "block" : "none";
732
+ q("schemeless-text").style.color = dangerous ? "#f5c6c6" : "#e8d6b0";
733
+ q("add-scheme").style.display = doubtful ? "" : "none";
714
734
  if (!onPage && !linkNewTabTouched) {
715
735
  linkNewTab = isExternalHost(raw, typeof location !== "undefined" ? location.host : "");
716
736
  }
@@ -735,6 +755,7 @@ function applyLink() {
735
755
  const own = q("text").value.trim();
736
756
  const href = onPage ? linkPicked?.path ?? "" : q("url").value.trim();
737
757
  if (!href) return;
758
+ if (isDangerousUrl(href)) return;
738
759
  const ref = onPage && linkPicked ? `${linkPicked.collection}:${linkPicked.slug}` : "";
739
760
  const label = own || (onPage ? linkPicked?.title ?? href : href);
740
761
  const applyTarget = (el) => setLinkTarget(el, linkNewTab);
@@ -776,6 +797,7 @@ function escapeHtml(v) {
776
797
  return v.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
777
798
  }
778
799
  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;";
800
+ var noticeCss = (variant) => "display:none;margin-top:8px;padding:8px 10px;border-radius:8px;background:" + (variant === "blocked" ? "rgba(255,90,90,.10)" : "rgba(255,176,32,.10)") + ";border:1px solid " + (variant === "blocked" ? "rgba(255,90,90,.38)" : "rgba(255,176,32,.34)") + ";";
779
801
  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;");
780
802
  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;";
781
803
  var labelCss = () => "font-size:11px;letter-spacing:.06em;text-transform:uppercase;color:#9aa3b2;margin:12px 0 5px;display:block;";
@@ -1067,6 +1089,10 @@ function serializeInlineNode(child) {
1067
1089
  break;
1068
1090
  case "a": {
1069
1091
  const href = el.getAttribute("href") || "";
1092
+ if (isDangerousUrl(href)) {
1093
+ out += inner;
1094
+ break;
1095
+ }
1070
1096
  const extras = preservedLinkAttrs(el);
1071
1097
  if (extras.length > 0) {
1072
1098
  out += `<a ${[`href="${escapeAttr(href)}"`, ...extras].join(" ")}>${inner}</a>`;