@broberg/cms-inline-edit 0.5.5 → 0.6.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/dist/index.cjs CHANGED
@@ -29,7 +29,8 @@ __export(index_exports, {
29
29
  positionPopover: () => positionPopover,
30
30
  renderCurrentRefLine: () => renderCurrentRefLine,
31
31
  rescanFields: () => rescanFields,
32
- resolveExistingRef: () => resolveExistingRef
32
+ resolveExistingRef: () => resolveExistingRef,
33
+ setLinkTarget: () => setLinkTarget
33
34
  });
34
35
  module.exports = __toCommonJS(index_exports);
35
36
 
@@ -43,6 +44,30 @@ function applyFieldSlice(current, original, next) {
43
44
  return current.slice(0, first) + next + current.slice(first + original.length);
44
45
  }
45
46
 
47
+ // src/link-target.ts
48
+ function isSchemeless(raw) {
49
+ const v = (raw || "").trim();
50
+ if (!v) return false;
51
+ if (/^[a-z][a-z0-9+.-]*:/i.test(v)) return false;
52
+ if (v.startsWith("//")) return false;
53
+ if (v.startsWith("/")) return false;
54
+ if (v.startsWith("#")) return false;
55
+ if (v.startsWith("?")) return false;
56
+ return true;
57
+ }
58
+ function withHttps(raw) {
59
+ return `https://${(raw || "").trim().replace(/^\/+/, "")}`;
60
+ }
61
+ function isExternalHost(raw, currentHost) {
62
+ const v = (raw || "").trim();
63
+ if (!/^https?:\/\//i.test(v)) return false;
64
+ try {
65
+ return new URL(v).host.toLowerCase() !== (currentHost || "").toLowerCase();
66
+ } catch {
67
+ return false;
68
+ }
69
+ }
70
+
46
71
  // src/token-safe.ts
47
72
  var TEXT_NODE = 3;
48
73
  var ELEMENT_NODE = 1;
@@ -97,6 +122,9 @@ var DEFAULT_LABELS = {
97
122
  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.",
98
123
  linkUrlHint: "En fri adresse peger pr\xE6cis d\xE9r, du skriver. Den f\xF8lger <b>ikke</b> med, hvis m\xE5let flytter sig.",
99
124
  linkUrl: "Adresse",
125
+ linkNewTab: "\xC5bn i ny fane",
126
+ 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
+ linkSchemelessFix: "Tilf\xF8j https://",
100
128
  linkInsert: "Inds\xE6t link",
101
129
  linkSave: "Gem \xE6ndring",
102
130
  linkCancel: "Annull\xE9r",
@@ -299,10 +327,12 @@ function showIdlePill() {
299
327
  removeIdlePill();
300
328
  const wrap = document.createElement("div");
301
329
  wrap.setAttribute("data-cms-inline-edit-idle", "");
330
+ wrap.setAttribute("data-testid", "inline-edit-idle");
302
331
  wrap.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:hidden;";
303
332
  const edit = document.createElement("button");
304
333
  edit.type = "button";
305
334
  edit.setAttribute("data-cms-inline-edit-enter", "");
335
+ edit.setAttribute("data-testid", "inline-edit-enter");
306
336
  edit.style.cssText = "display:inline-flex;align-items:center;gap:7px;background:#1c2027;color:#fff;font:600 12px system-ui,sans-serif;padding:8px 14px;border:none;cursor:pointer;";
307
337
  const editText = document.createElement("span");
308
338
  editText.textContent = options.connectLabel;
@@ -311,6 +341,7 @@ function showIdlePill() {
311
341
  const out = document.createElement("button");
312
342
  out.type = "button";
313
343
  out.setAttribute("data-cms-inline-edit-logout", "");
344
+ out.setAttribute("data-testid", "inline-edit-logout");
314
345
  out.textContent = options.logoutLabel;
315
346
  out.style.cssText = "background:#141821;color:#9aa4b2;font:600 11px system-ui,sans-serif;padding:8px 12px;border:none;border-left:1px solid rgba(255,255,255,.12);cursor:pointer;";
316
347
  out.addEventListener("click", () => {
@@ -474,6 +505,8 @@ var savedRange = null;
474
505
  var linkDialog = null;
475
506
  var linkPages = null;
476
507
  var linkPicked = null;
508
+ var linkNewTab = false;
509
+ var linkNewTabTouched = false;
477
510
  var linkEditing = null;
478
511
  function hideLinkDialog() {
479
512
  if (linkDialog) linkDialog.style.display = "none";
@@ -555,9 +588,13 @@ function renderLinkDialog() {
555
588
  if (!d) return;
556
589
  const L = uiLabels;
557
590
  const editing = !!linkEditing;
591
+ if (!editing) {
592
+ linkNewTab = false;
593
+ linkNewTabTouched = false;
594
+ }
558
595
  const existingRef = linkEditing?.getAttribute("data-cms-ref") ?? "";
559
596
  const onPageTab = !editing || !!existingRef;
560
- 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><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>`;
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>`;
561
598
  const q = (role) => d.querySelector(`[data-role="${role}"]`);
562
599
  const text = q("text");
563
600
  const urlIn = q("url");
@@ -565,6 +602,8 @@ function renderLinkDialog() {
565
602
  if (editing) {
566
603
  text.value = linkEditing?.getAttribute("data-cms-ref-label") === "auto" ? "" : linkEditing?.textContent ?? "";
567
604
  if (!existingRef) urlIn.value = linkEditing?.getAttribute("href") ?? "";
605
+ linkNewTab = linkEditing?.getAttribute("target") === "_blank";
606
+ linkNewTabTouched = true;
568
607
  q("remove").appendChild(buildRemoveLink());
569
608
  }
570
609
  d.querySelectorAll("[data-tab]").forEach((btn) => {
@@ -584,6 +623,16 @@ function renderLinkDialog() {
584
623
  urlIn.oninput = syncLinkDialog;
585
624
  q("cancel").onclick = hideLinkDialog;
586
625
  q("submit").onclick = applyLink;
626
+ q("newtab").onclick = () => {
627
+ linkNewTab = !linkNewTab;
628
+ linkNewTabTouched = true;
629
+ syncLinkDialog();
630
+ };
631
+ q("add-scheme").onclick = () => {
632
+ urlIn.value = withHttps(urlIn.value);
633
+ urlIn.focus();
634
+ syncLinkDialog();
635
+ };
587
636
  renderLinkList("");
588
637
  syncLinkDialog();
589
638
  }
@@ -689,6 +738,30 @@ function syncLinkDialog() {
689
738
  q("hint").innerHTML = own ? uiLabels.linkTextOwnHint : onPage ? uiLabels.linkTextAutoHint : uiLabels.linkUrlHint;
690
739
  q("live").style.display = onPage ? "flex" : "none";
691
740
  q("submit").disabled = onPage ? !linkPicked : !q("url").value.trim();
741
+ const raw = q("url").value;
742
+ const doubtful = !onPage && isSchemeless(raw);
743
+ q("schemeless").style.display = doubtful ? "block" : "none";
744
+ if (doubtful) {
745
+ q("schemeless-text").innerHTML = uiLabels.linkSchemeless.replace(
746
+ "{v}",
747
+ escapeHtml(raw.trim())
748
+ );
749
+ }
750
+ if (!onPage && !linkNewTabTouched) {
751
+ linkNewTab = isExternalHost(raw, typeof location !== "undefined" ? location.host : "");
752
+ }
753
+ const box = q("newtab-box");
754
+ if (box) box.setAttribute("style", checkboxCss(linkNewTab));
755
+ q("newtab").setAttribute("aria-pressed", String(linkNewTab));
756
+ }
757
+ function setLinkTarget(el, newTab) {
758
+ if (newTab) {
759
+ el.setAttribute("target", "_blank");
760
+ el.setAttribute("rel", "noopener");
761
+ } else {
762
+ el.removeAttribute("target");
763
+ el.removeAttribute("rel");
764
+ }
692
765
  }
693
766
  function applyLink() {
694
767
  const d = linkDialog;
@@ -700,18 +773,21 @@ function applyLink() {
700
773
  if (!href) return;
701
774
  const ref = onPage && linkPicked ? `${linkPicked.collection}:${linkPicked.slug}` : "";
702
775
  const label = own || (onPage ? linkPicked?.title ?? href : href);
776
+ const applyTarget = (el) => setLinkTarget(el, linkNewTab);
703
777
  if (linkEditing) {
704
778
  linkEditing.setAttribute("href", href);
705
779
  if (ref) linkEditing.setAttribute("data-cms-ref", ref);
706
780
  else linkEditing.removeAttribute("data-cms-ref");
707
781
  if (ref && !own) linkEditing.setAttribute("data-cms-ref-label", "auto");
708
782
  else linkEditing.removeAttribute("data-cms-ref-label");
783
+ applyTarget(linkEditing);
709
784
  linkEditing.textContent = label;
710
785
  hideLinkDialog();
711
786
  return;
712
787
  }
713
788
  const a = document.createElement("a");
714
789
  a.setAttribute("href", href);
790
+ applyTarget(a);
715
791
  if (ref) {
716
792
  a.setAttribute("data-cms-ref", ref);
717
793
  if (!own) a.setAttribute("data-cms-ref-label", "auto");
@@ -736,6 +812,7 @@ function escapeHtml(v) {
736
812
  return v.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
737
813
  }
738
814
  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;";
815
+ 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;");
739
816
  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;";
740
817
  var labelCss = () => "font-size:11px;letter-spacing:.06em;text-transform:uppercase;color:#9aa3b2;margin:12px 0 5px;display:block;";
741
818
  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;");
@@ -1099,6 +1176,7 @@ function showPill(el, state) {
1099
1176
  if (!pill) {
1100
1177
  pill = document.createElement("span");
1101
1178
  pill.setAttribute("data-cms-inline-edit-pill", "");
1179
+ pill.setAttribute("data-testid", "inline-edit-pill");
1102
1180
  document.body.appendChild(pill);
1103
1181
  pills.set(el, pill);
1104
1182
  }
@@ -1126,6 +1204,7 @@ function showActiveBadge(options) {
1126
1204
  const badge = document.createElement("button");
1127
1205
  badge.type = "button";
1128
1206
  badge.setAttribute("data-cms-inline-edit-badge", "");
1207
+ badge.setAttribute("data-testid", "inline-edit-badge");
1129
1208
  badge.style.cssText = "position:fixed;bottom:16px;left:16px;display:inline-flex;align-items:center;gap:7px;background:#1c2027;color:#fff;font:600 12px system-ui,sans-serif;padding:8px 14px;border-radius:999px;z-index:2147483647;box-shadow:0 4px 16px rgba(0,0,0,.3);border:none;cursor:pointer;opacity:.9;transition:opacity .15s;";
1130
1209
  const text = document.createElement("span");
1131
1210
  text.textContent = options.disconnectLabel;
@@ -1166,6 +1245,7 @@ function injectStyles() {
1166
1245
  positionPopover,
1167
1246
  renderCurrentRefLine,
1168
1247
  rescanFields,
1169
- resolveExistingRef
1248
+ resolveExistingRef,
1249
+ setLinkTarget
1170
1250
  });
1171
1251
  //# sourceMappingURL=index.cjs.map