@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.cjs CHANGED
@@ -67,6 +67,10 @@ function isExternalHost(raw, currentHost) {
67
67
  return false;
68
68
  }
69
69
  }
70
+ function isDangerousUrl(raw) {
71
+ const v = (raw || "").replace(/[\u0000-\u0020]/g, "");
72
+ return /^(?:javascript|data|vbscript):/i.test(v);
73
+ }
70
74
 
71
75
  // src/token-safe.ts
72
76
  var TEXT_NODE = 3;
@@ -125,6 +129,7 @@ var DEFAULT_LABELS = {
125
129
  linkNewTab: "\xC5bn i ny fane",
126
130
  linkSchemeless: "<b>{v}</b> l\xE6ses som en side p\xE5 <b>dette</b> site \u2014 ikke som en adresse ude p\xE5 nettet.",
127
131
  linkSchemelessFix: "Tilf\xF8j https://",
132
+ linkBlocked: "<b>{v}</b> kan ikke bruges som link \u2014 adresser af den type kan k\xF8re kode p\xE5 sitet.",
128
133
  linkInsert: "Inds\xE6t link",
129
134
  linkSave: "Gem \xE6ndring",
130
135
  linkCancel: "Annull\xE9r",
@@ -594,7 +599,7 @@ function renderLinkDialog() {
594
599
  }
595
600
  const existingRef = linkEditing?.getAttribute("data-cms-ref") ?? "";
596
601
  const onPageTab = !editing || !!existingRef;
597
- 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>`;
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" 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>`;
598
603
  const q = (role) => d.querySelector(`[data-role="${role}"]`);
599
604
  const text = q("text");
600
605
  const urlIn = q("url");
@@ -739,14 +744,29 @@ function syncLinkDialog() {
739
744
  q("live").style.display = onPage ? "flex" : "none";
740
745
  q("submit").disabled = onPage ? !linkPicked : !q("url").value.trim();
741
746
  const raw = q("url").value;
742
- const doubtful = !onPage && isSchemeless(raw);
743
- q("schemeless").style.display = doubtful ? "block" : "none";
744
- if (doubtful) {
747
+ const dangerous = !onPage && isDangerousUrl(raw);
748
+ const doubtful = !onPage && !dangerous && isSchemeless(raw);
749
+ const notice = q("schemeless");
750
+ const show = dangerous || doubtful;
751
+ if (dangerous) {
752
+ notice.setAttribute("style", noticeCss("blocked"));
753
+ notice.setAttribute("data-variant", "blocked");
754
+ q("schemeless-text").innerHTML = uiLabels.linkBlocked.replace(
755
+ "{v}",
756
+ escapeHtml(raw.trim())
757
+ );
758
+ q("submit").disabled = true;
759
+ } else if (doubtful) {
760
+ notice.setAttribute("style", noticeCss("schemeless"));
761
+ notice.setAttribute("data-variant", "schemeless");
745
762
  q("schemeless-text").innerHTML = uiLabels.linkSchemeless.replace(
746
763
  "{v}",
747
764
  escapeHtml(raw.trim())
748
765
  );
749
766
  }
767
+ notice.style.display = show ? "block" : "none";
768
+ q("schemeless-text").style.color = dangerous ? "#f5c6c6" : "#e8d6b0";
769
+ q("add-scheme").style.display = doubtful ? "" : "none";
750
770
  if (!onPage && !linkNewTabTouched) {
751
771
  linkNewTab = isExternalHost(raw, typeof location !== "undefined" ? location.host : "");
752
772
  }
@@ -771,6 +791,7 @@ function applyLink() {
771
791
  const own = q("text").value.trim();
772
792
  const href = onPage ? linkPicked?.path ?? "" : q("url").value.trim();
773
793
  if (!href) return;
794
+ if (isDangerousUrl(href)) return;
774
795
  const ref = onPage && linkPicked ? `${linkPicked.collection}:${linkPicked.slug}` : "";
775
796
  const label = own || (onPage ? linkPicked?.title ?? href : href);
776
797
  const applyTarget = (el) => setLinkTarget(el, linkNewTab);
@@ -812,6 +833,7 @@ function escapeHtml(v) {
812
833
  return v.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
813
834
  }
814
835
  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;";
836
+ 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)") + ";";
815
837
  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;");
816
838
  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;";
817
839
  var labelCss = () => "font-size:11px;letter-spacing:.06em;text-transform:uppercase;color:#9aa3b2;margin:12px 0 5px;display:block;";
@@ -1103,6 +1125,10 @@ function serializeInlineNode(child) {
1103
1125
  break;
1104
1126
  case "a": {
1105
1127
  const href = el.getAttribute("href") || "";
1128
+ if (isDangerousUrl(href)) {
1129
+ out += inner;
1130
+ break;
1131
+ }
1106
1132
  const extras = preservedLinkAttrs(el);
1107
1133
  if (extras.length > 0) {
1108
1134
  out += `<a ${[`href="${escapeAttr(href)}"`, ...extras].join(" ")}>${inner}</a>`;