@collidecreatives/edit-mode 0.2.0 → 0.3.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.
@@ -1,10 +1,18 @@
1
- "use strict";var CollideEditMode=(()=>{var w=Object.defineProperty;var P=Object.getOwnPropertyDescriptor;var K=Object.getOwnPropertyNames;var H=Object.prototype.hasOwnProperty;var R=(t,e)=>{for(var n in e)w(t,n,{get:e[n],enumerable:!0})},V=(t,e,n,i)=>{if(e&&typeof e=="object"||typeof e=="function")for(let a of K(e))!H.call(t,a)&&a!==n&&w(t,a,{get:()=>e[a],enumerable:!(i=P(e,a))||i.enumerable});return t};var U=t=>V(w({},"__esModule",{value:!0}),t);var ce={};R(ce,{initClientEditMode:()=>y});var j="h1,h2,h3,h4,h5,h6,p,li,blockquote,figcaption,label,legend,dt,dd,th,td,a,button",B="[data-no-edit],[data-edit-mode-skip],form,input,textarea,select,option,script,style,svg,canvas,iframe,.cem-edit-banner,.cem-panel,.cem-toast",F=new Set(["IMG","PICTURE","SVG","VIDEO","CANVAS","IFRAME","SCRIPT","STYLE"]),A=1,z={queryParam:"edit",queryValue:"true",sessionKey:"collide-edit-mode",brandName:"Edit Mode",accentColour:"#1e40af",editableSelector:j,skipSelector:B,autoSave:!0};function J(){return typeof window<"u"&&typeof document<"u"}function G(t){if(t.enabled!==void 0)return t.enabled;let n=new URL(window.location.href).searchParams.get(t.queryParam);return(t.queryValue===null?n!==null:n===t.queryValue)||window.sessionStorage.getItem(t.sessionKey)==="1"}function Y(t){let e=document.createElement("style");return e.dataset.editModeStyle="true",e.textContent=[`.cem-edit-banner{position:fixed;top:0;left:0;right:0;z-index:2147483640;background:${t};color:#fff;display:flex;align-items:center;justify-content:center;gap:8px;padding:8px 92px 8px 16px;font:13px/1.4 system-ui,-apple-system,sans-serif;box-shadow:0 1px 8px rgba(0,0,0,.18)}`,".cem-edit-banner kbd{background:rgba(255,255,255,.18);padding:0 4px;border-radius:3px;font-size:11px}",".cem-exit-btn{position:absolute;right:8px;top:50%;transform:translateY(-50%);background:rgba(255,255,255,.15);border:1px solid rgba(255,255,255,.25);color:#fff;padding:4px 10px;border-radius:5px;font:12px system-ui,-apple-system,sans-serif;cursor:pointer;white-space:nowrap}",".cem-exit-btn:hover,.cem-exit-btn:focus-visible{background:rgba(255,255,255,.25);outline:none}","[contenteditable=true]{cursor:text!important}","[contenteditable=true]:hover{outline:2px dashed rgba(59,130,246,.5)!important;outline-offset:2px}","[contenteditable=true]:focus{outline:2px solid #3b82f6!important;outline-offset:2px;background:rgba(59,130,246,.04)}",".cem-panel{position:fixed;right:16px;bottom:16px;z-index:2147483640;background:#fff;color:#111827;border:1px solid #e5e7eb;border-radius:12px;box-shadow:0 8px 30px rgba(0,0,0,.22);padding:10px;display:grid;gap:8px;width:min(320px,calc(100vw - 32px));font:13px/1.4 system-ui,-apple-system,sans-serif}",".cem-panel-actions{display:flex;gap:8px;flex-wrap:wrap}",".cem-copy-btn,.cem-download-btn,.cem-open-link-btn{border:0;border-radius:8px;padding:9px 12px;font:600 13px system-ui,-apple-system,sans-serif;cursor:pointer;white-space:nowrap}",`.cem-copy-btn{background:${t};color:#fff;flex:1}`,".cem-download-btn,.cem-open-link-btn{background:#f3f4f6;color:#111827;border:1px solid #e5e7eb}",".cem-open-link-btn[hidden]{display:none}",".cem-copy-btn:hover,.cem-download-btn:hover,.cem-open-link-btn:hover{filter:brightness(.97)}",".cem-status{color:#4b5563;font-size:12px;min-height:17px}",".cem-toast{position:fixed;bottom:116px;right:16px;z-index:2147483641;background:#065f46;color:#fff;padding:10px 18px;border-radius:8px;font:14px system-ui,-apple-system,sans-serif;box-shadow:0 2px 12px rgba(0,0,0,.3);transition:opacity .3s}","@media(max-width:520px){.cem-edit-banner{justify-content:flex-start;text-align:left}.cem-panel{left:16px;right:16px;width:auto}}"].join(`
2
- `),document.head.appendChild(e),e}function v(t){document.querySelector(".cem-toast")?.remove();let e=document.createElement("div");e.className="cem-toast",e.textContent=t,document.body.appendChild(e),window.setTimeout(()=>{e.style.opacity="0",window.setTimeout(()=>e.parentNode?.removeChild(e),300)},2500)}function D(t,e){let n=document.createElement("textarea");n.value=t,n.style.position="fixed",n.style.left="-9999px",document.body.appendChild(n),n.select();try{document.execCommand("copy")}catch{}document.body.removeChild(n),v(e)}function Q(){try{let t="__cem_storage_test__";return window.localStorage.setItem(t,"1"),window.localStorage.removeItem(t),window.localStorage}catch{try{return window.sessionStorage}catch{return null}}}function W(t){return`${t.storageKey||`${t.sessionKey}:draft`}:${window.location.origin}${window.location.pathname}`}function X(t,e){if(t.closest(e)||!(t.textContent?.trim()??""))return!1;let i=Array.from(t.children);return!(i.length>0&&i.every(a=>F.has(a.tagName)))}function Z(t){if(t.dataset.editId)return`data-edit-id:${t.dataset.editId}`;if(t.id)return`id:${t.id}`;let e=[],n=t;for(;n&&n!==document.body&&n!==document.documentElement;){let i=n.parentElement;if(!i)break;let a=n.tagName,d=Array.from(i.children).filter(c=>c.tagName===a).indexOf(n)+1;e.unshift(`${n.tagName.toLowerCase()}:nth-of-type(${d})`),n=i}return e.join(" > ")}function ee(t){let e="",n=t.parentElement;for(;n&&n!==document.body&&n!==document.documentElement;){if(n.id){e=`#${n.id}`;break}let d=n.getAttribute("data-section");if(d){e=d;break}if(n.tagName==="SECTION"){let c=n.querySelector("h1,h2,h3");if(c?.textContent){e=c.textContent.trim().slice(0,40);break}}if(["HEADER","FOOTER","MAIN","NAV"].includes(n.tagName)){e=n.tagName.toLowerCase();break}n=n.parentElement}let i=t.tagName.toLowerCase(),a=t.dataset.editOriginal||t.textContent?.trim()||"",s=a.slice(0,40)+(a.length>40?"...":"");return`${e?`${e} > `:""}${i}: "${s}"`}function te(t,e){document.querySelectorAll("a[href]").forEach(n=>{let i=n.getAttribute("href");if(!(!i||/^(https?:|mailto:|tel:|javascript:)/i.test(i)))try{let a=new URL(i,window.location.href);if(a.origin!==window.location.origin)return;a.searchParams.set(t,e??"1");let s=a.hash;a.hash="",n.setAttribute("href",`${a.pathname}${a.search}${s}`)}catch{}})}function I(t){return{site:window.location.hostname,page:window.location.pathname,pageTitle:document.title,url:window.location.href,timestamp:new Date().toISOString(),changes:t}}function ne(t,e){if(!t)return null;try{let n=t.getItem(e);if(!n)return null;let i=JSON.parse(n);return i?.version===A&&Array.isArray(i.changes)?i:null}catch{return null}}function oe(t,e,n){if(!t)return!1;try{return t.setItem(e,JSON.stringify(n)),!0}catch{return!1}}function ae(t,e){let n=new Blob([e],{type:"application/json"}),i=URL.createObjectURL(n),a=document.createElement("a");a.href=i,a.download=t,a.style.display="none",document.body.appendChild(a),a.click(),a.remove(),window.setTimeout(()=>URL.revokeObjectURL(i),1e3)}function ie(t=new Date){return t.toLocaleTimeString([],{hour:"2-digit",minute:"2-digit"})}function re(t){document.querySelectorAll(t.editableSelector).forEach(e=>{if(!X(e,t.skipSelector))return;let n=e.textContent?.trim()??"";e.contentEditable="true",e.dataset.editOriginal||(e.dataset.editOriginal=n),e.dataset.editKey||(e.dataset.editKey=Z(e))})}function se(t,e){if(!t)return 0;let n=new Map(t.changes.map(a=>[a.key,a])),i=0;return document.querySelectorAll(e).forEach(a=>{let s=a.dataset.editKey;if(!s)return;let d=n.get(s);d&&a.dataset.editOriginal===d.original&&(a.textContent=d.new,i+=1)}),i}function q(t){let e=[];return document.querySelectorAll(t).forEach(n=>{if(!n.isContentEditable&&n.contentEditable!=="true")return;let i=n.dataset.editOriginal,a=n.dataset.editKey;if(i===void 0||!a)return;let s=n.textContent?.trim()??"";i!==s&&e.push({key:a,path:ee(n),tag:n.tagName,original:i,new:s})}),e}function x(t){return q(t).map(({key:e,...n})=>n)}function y(t={}){if(!J())return{active:!1,getChanges:()=>[],destroy:()=>{}};let e={...z,...t};if(!G(e))return{active:!1,getChanges:()=>[],destroy:()=>{}};if(window.__COLLIDE_EDIT_MODE_ACTIVE)return{active:!0,getChanges:()=>x(e.editableSelector),destroy:()=>{}};window.__COLLIDE_EDIT_MODE_ACTIVE=!0,window.sessionStorage.setItem(e.sessionKey,"1");let n=Q(),i=W(e),a=Y(e.accentColour),s=document.createElement("div");s.className="cem-edit-banner",s.innerHTML=`\u270F\uFE0F <strong>${e.brandName}</strong> <span>Click text to edit. Press <kbd>Esc</kbd> when done.</span>`;let d=document.createElement("button");d.className="cem-exit-btn",d.type="button",d.textContent="Exit",s.appendChild(d),document.body.prepend(s);let c=document.createElement("div");c.className="cem-panel",c.innerHTML=`
1
+ "use strict";var CollideEditMode=(()=>{var k=Object.defineProperty;var G=Object.getOwnPropertyDescriptor;var J=Object.getOwnPropertyNames;var Y=Object.prototype.hasOwnProperty;var Q=(t,e)=>{for(var n in e)k(t,n,{get:e[n],enumerable:!0})},W=(t,e,n,a)=>{if(e&&typeof e=="object"||typeof e=="function")for(let i of J(e))!Y.call(t,i)&&i!==n&&k(t,i,{get:()=>e[i],enumerable:!(a=G(e,i))||a.enumerable});return t};var X=t=>W(k({},"__esModule",{value:!0}),t);var Ce={};Q(Ce,{initClientEditMode:()=>E});var Z="h1,h2,h3,h4,h5,h6,p,li,blockquote,figcaption,label,legend,dt,dd,th,td,a,button",ee="[data-no-edit],[data-edit-mode-skip],form,input,textarea,select,option,script,style,svg,canvas,iframe,.cem-edit-banner,.cem-panel,.cem-toast",te=new Set(["IMG","PICTURE","SVG","VIDEO","CANVAS","IFRAME","SCRIPT","STYLE"]),ne=new Set(["H1","H2","H3","H4","H5","H6","BUTTON","LABEL","LEGEND","DT","TH","TD","FIGCAPTION","A"]),V=1,oe=400,ie={queryParam:"edit",queryValue:"true",sessionKey:"collide-edit-mode",brandName:"Edit Mode",accentColour:"#1e40af",editableSelector:Z,skipSelector:ee,autoSave:!0};function ae(){return typeof window<"u"&&typeof document<"u"}function re(t){if(t.enabled!==void 0)return t.enabled;let n=new URL(window.location.href).searchParams.get(t.queryParam);return(t.queryValue===null?n!==null:n===t.queryValue)||window.sessionStorage.getItem(t.sessionKey)==="1"}function se(t){let e=document.createElement("style");return e.dataset.editModeStyle="true",e.textContent=[`.cem-edit-banner{position:fixed;top:0;left:0;right:0;z-index:2147483640;background:${t};color:#fff;display:flex;align-items:center;justify-content:center;gap:8px;padding:8px 92px 8px 16px;font:13px/1.4 system-ui,-apple-system,sans-serif;box-shadow:0 1px 8px rgba(0,0,0,.18)}`,".cem-edit-banner kbd{background:rgba(255,255,255,.18);padding:0 4px;border-radius:3px;font-size:11px}",".cem-exit-btn{position:absolute;right:8px;top:50%;transform:translateY(-50%);background:rgba(255,255,255,.15);border:1px solid rgba(255,255,255,.25);color:#fff;padding:4px 10px;border-radius:5px;font:12px system-ui,-apple-system,sans-serif;cursor:pointer;white-space:nowrap}",".cem-exit-btn:hover,.cem-exit-btn:focus-visible{background:rgba(255,255,255,.25);outline:none}","[contenteditable=true]{cursor:text!important}","[contenteditable=true]:hover{outline:2px dashed rgba(59,130,246,.5)!important;outline-offset:2px}","[contenteditable=true]:focus{outline:2px solid #3b82f6!important;outline-offset:2px;background:rgba(59,130,246,.04)}","[contenteditable=true].cem-changed{box-shadow:inset 3px 0 0 #10b981}",".cem-panel{position:fixed;right:16px;bottom:16px;z-index:2147483640;background:#fff;color:#111827;border:1px solid #e5e7eb;border-radius:12px;box-shadow:0 8px 30px rgba(0,0,0,.22);padding:10px;display:grid;gap:8px;width:min(320px,calc(100vw - 32px));font:13px/1.4 system-ui,-apple-system,sans-serif}",".cem-panel-actions{display:flex;gap:8px;flex-wrap:wrap}",".cem-copy-btn,.cem-review-btn,.cem-download-btn,.cem-open-link-btn{border:0;border-radius:8px;padding:9px 12px;font:600 13px system-ui,-apple-system,sans-serif;cursor:pointer;white-space:nowrap}",`.cem-copy-btn{background:${t};color:#fff;flex:1}`,".cem-review-btn,.cem-download-btn,.cem-open-link-btn{background:#f3f4f6;color:#111827;border:1px solid #e5e7eb}",".cem-open-link-btn[hidden]{display:none}",".cem-copy-btn:hover,.cem-review-btn:hover,.cem-download-btn:hover,.cem-open-link-btn:hover{filter:brightness(.97)}",".cem-status{color:#4b5563;font-size:12px;min-height:17px}",".cem-review-list{max-height:220px;overflow-y:auto;display:grid;gap:6px;border-top:1px solid #e5e7eb;padding-top:8px}",".cem-review-list[hidden]{display:none}",".cem-review-empty{color:#6b7280;font-size:12px;margin:0;padding:4px 0}",".cem-review-item{display:grid;gap:4px;padding:8px;background:#f9fafb;border-radius:8px;border:1px solid #e5e7eb}",".cem-review-path{font-size:11px;color:#6b7280;font-weight:600;text-transform:uppercase;letter-spacing:.02em}",".cem-review-diff{font-size:12px;word-break:break-word}",".cem-review-diff del{color:#b91c1c;text-decoration:line-through;opacity:.75;display:block}",".cem-review-diff ins{color:#065f46;text-decoration:none;display:block}",".cem-revert-btn{justify-self:start;background:transparent;border:1px solid #e5e7eb;border-radius:6px;padding:3px 8px;font:12px system-ui,-apple-system,sans-serif;cursor:pointer;color:#374151}",".cem-revert-btn:hover{background:#fff}",".cem-toast{position:fixed;bottom:116px;right:16px;z-index:2147483641;background:#065f46;color:#fff;padding:10px 18px;border-radius:8px;font:14px system-ui,-apple-system,sans-serif;box-shadow:0 2px 12px rgba(0,0,0,.3);transition:opacity .3s}","@media(max-width:520px){.cem-edit-banner{justify-content:flex-start;text-align:left}.cem-panel{left:16px;right:16px;width:auto}}"].join(`
2
+ `),document.head.appendChild(e),e}function D(t){document.querySelector(".cem-toast")?.remove();let e=document.createElement("div");e.className="cem-toast",e.textContent=t,document.body.appendChild(e),window.setTimeout(()=>{e.style.opacity="0",window.setTimeout(()=>e.parentNode?.removeChild(e),300)},2500)}function R(t,e){let n=document.createElement("textarea");n.value=t,n.style.position="fixed",n.style.left="-9999px",document.body.appendChild(n),n.select();try{document.execCommand("copy")}catch{}document.body.removeChild(n),D(e)}function de(){try{let t="__cem_storage_test__";return window.localStorage.setItem(t,"1"),window.localStorage.removeItem(t),window.localStorage}catch{try{return window.sessionStorage}catch{return null}}}function ce(t){return`${t.storageKey||`${t.sessionKey}:draft`}:${window.location.origin}${window.location.pathname}`}function T(t){return t.replace(/[&<>"']/g,e=>{switch(e){case"&":return"&amp;";case"<":return"&lt;";case">":return"&gt;";case'"':return"&quot;";default:return"&#39;"}})}function le(t,e){if(t.closest(e)||!(t.textContent?.trim()??""))return!1;let a=Array.from(t.children);return!(a.length>0&&a.every(i=>te.has(i.tagName)))}function ue(t){if(t.dataset.editId)return`data-edit-id:${t.dataset.editId}`;if(t.id)return`id:${t.id}`;let e=[],n=t;for(;n&&n!==document.body&&n!==document.documentElement;){let a=n.parentElement;if(!a)break;let i=n.tagName,s=Array.from(a.children).filter(c=>c.tagName===i).indexOf(n)+1;e.unshift(`${n.tagName.toLowerCase()}:nth-of-type(${s})`),n=a}return e.join(" > ")}function me(t){let e="",n=t.parentElement;for(;n&&n!==document.body&&n!==document.documentElement;){if(n.id){e=`#${n.id}`;break}let s=n.getAttribute("data-section");if(s){e=s;break}if(n.tagName==="SECTION"){let c=n.querySelector("h1,h2,h3");if(c?.textContent){e=c.textContent.trim().slice(0,40);break}}if(["HEADER","FOOTER","MAIN","NAV"].includes(n.tagName)){e=n.tagName.toLowerCase();break}n=n.parentElement}let a=t.tagName.toLowerCase(),i=t.dataset.editOriginal||t.textContent?.trim()||"",d=i.slice(0,40)+(i.length>40?"...":"");return`${e?`${e} > `:""}${a}: "${d}"`}function pe(t,e){document.querySelectorAll("a[href]").forEach(n=>{let a=n.getAttribute("href");if(!(!a||/^(https?:|mailto:|tel:|javascript:)/i.test(a)))try{let i=new URL(a,window.location.href);if(i.origin!==window.location.origin)return;i.searchParams.set(t,e??"1");let d=i.hash;i.hash="",n.setAttribute("href",`${i.pathname}${i.search}${d}`)}catch{}})}function B(t){return{site:window.location.hostname,page:window.location.pathname,pageTitle:document.title,url:window.location.href,timestamp:new Date().toISOString(),changes:t}}function fe(t,e){if(!t)return null;try{let n=t.getItem(e);if(!n)return null;let a=JSON.parse(n);return a?.version===V&&Array.isArray(a.changes)?a:null}catch{return null}}function ge(t,e,n){if(!t)return!1;try{return t.setItem(e,JSON.stringify(n)),!0}catch{return!1}}function be(t,e){let n=new Blob([e],{type:"application/json"}),a=URL.createObjectURL(n),i=document.createElement("a");i.href=a,i.download=t,i.style.display="none",document.body.appendChild(i),i.click(),i.remove(),window.setTimeout(()=>URL.revokeObjectURL(a),1e3)}function he(t=new Date){return t.toLocaleTimeString([],{hour:"2-digit",minute:"2-digit"})}function ye(t){document.querySelectorAll(t.editableSelector).forEach(e=>{if(!le(e,t.skipSelector)||e.closest("[data-edit-key]"))return;let n=e.textContent?.trim()??"";e.contentEditable="true",e.dataset.editOriginal||(e.dataset.editOriginal=n),e.dataset.editKey||(e.dataset.editKey=ue(e))})}function we(t,e){let n=document.querySelectorAll(t);for(let a of Array.from(n))if(a.dataset.editKey===e)return a;return null}function Ee(t,e){if(!t)return 0;let n=new Map(t.changes.map(i=>[i.key,i])),a=0;return document.querySelectorAll(e).forEach(i=>{let d=i.dataset.editKey;if(!d)return;let s=n.get(d);s&&i.dataset.editOriginal===s.original&&(i.textContent=s.new,a+=1)}),a}function xe(t){let e=t.dataset.editOriginal;if(e===void 0)return;let n=t.textContent?.trim()??"";t.classList.toggle("cem-changed",e!==n)}function U(t){let e=[];return document.querySelectorAll(t).forEach(n=>{if(!n.isContentEditable&&n.contentEditable!=="true")return;let a=n.dataset.editOriginal,i=n.dataset.editKey;if(a===void 0||!i)return;let d=n.textContent?.trim()??"",s=a!==d;n.classList.toggle("cem-changed",s),s&&e.push({key:i,path:me(n),tag:n.tagName,original:a,new:d})}),e}function M(t){return U(t).map(({key:e,...n})=>n)}function E(t={}){if(!ae())return{active:!1,getChanges:()=>[],destroy:()=>{}};let e={...ie,...t};if(!re(e))return{active:!1,getChanges:()=>[],destroy:()=>{}};if(window.__COLLIDE_EDIT_MODE_ACTIVE)return{active:!0,getChanges:()=>M(e.editableSelector),destroy:()=>{}};window.__COLLIDE_EDIT_MODE_ACTIVE=!0,window.sessionStorage.setItem(e.sessionKey,"1");let n=de(),a=ce(e),i=se(e.accentColour),d=document.createElement("div");d.className="cem-edit-banner",d.innerHTML=`\u270F\uFE0F <strong>${e.brandName}</strong> <span>Click text to edit. Press <kbd>Esc</kbd> when done.</span>`;let s=document.createElement("button");s.className="cem-exit-btn",s.type="button",s.textContent="Exit",d.appendChild(s),document.body.prepend(d);let c=document.createElement("div");c.className="cem-panel",c.innerHTML=`
3
3
  <div class="cem-panel-actions">
4
4
  <button class="cem-copy-btn" type="button">\u{1F4CB} Copy Changes</button>
5
+ <button class="cem-review-btn" type="button" aria-expanded="false">Review</button>
5
6
  <button class="cem-download-btn" type="button">Download backup</button>
6
7
  <button class="cem-open-link-btn" type="button" hidden>Open link</button>
7
8
  </div>
9
+ <div class="cem-review-list" hidden></div>
8
10
  <div class="cem-status" aria-live="polite">Auto-save ready</div>
9
- `,document.body.appendChild(c);let h=c.querySelector(".cem-copy-btn"),$=c.querySelector(".cem-download-btn"),f=c.querySelector(".cem-open-link-btn"),C=c.querySelector(".cem-status"),u=null;document.querySelectorAll("[data-reveal]").forEach(o=>o.classList.add("is-visible")),te(e.queryParam,e.queryValue),re(e);let b=se(ne(n,i),e.editableSelector),_=()=>({version:A,page:window.location.pathname,url:window.location.href,updatedAt:new Date().toISOString(),changes:q(e.editableSelector)}),g=o=>{let r=x(e.editableSelector).length;h&&(h.textContent=r>0?`\u{1F4CB} Copy Changes (${r})`:"\u{1F4CB} Copy Changes"),f&&(f.hidden=!u,f.textContent=u?`Open ${u.hostname||"link"}`:"Open link"),C&&(C.textContent=o||`Auto-saved ${ie()} \u2022 ${r} change${r===1?"":"s"} \u2022 Links: edit text or Ctrl/\u2318-click to open`)},l=()=>{let o=_(),r=e.autoSave!==!1&&oe(n,i,o);return g(r||e.autoSave===!1?void 0:"\u26A0\uFE0F Auto-save unavailable \u2014 use Download backup"),o},N=()=>{let o=document.activeElement;return!(o instanceof HTMLElement)||!o.isContentEditable&&o.contentEditable!=="true"?!1:(o.blur(),window.getSelection()?.removeAllRanges(),l(),!0)},S=o=>{o.key==="Escape"&&N()&&(o.preventDefault(),o.stopPropagation())},k=o=>{let r=o.target;if(!(r instanceof Element))return;let p=r.closest("a[href]");if(p&&!p.closest(".cem-panel")&&!p.closest(".cem-edit-banner")){if(u=p,g("Link selected \u2014 edit the text, use Open link, or Ctrl/\u2318-click to visit it"),o.metaKey||o.ctrlKey||o.altKey){l();return}o.preventDefault();return}let m=r.closest("button");m&&!m.closest(".cem-panel")&&!m.closest(".cem-edit-banner")&&(o.preventDefault(),o.stopImmediatePropagation())},L=o=>{let r=o.target;r instanceof Element&&(u=r.closest("a[href]"),g(u?"Link selected \u2014 edit the text or use Open link to navigate":void 0))},M=()=>l(),T=()=>l(),O=()=>{document.visibilityState==="hidden"&&l()};return document.addEventListener("keydown",S),document.addEventListener("click",k,!0),document.addEventListener("focusin",L),document.addEventListener("input",M),window.addEventListener("pagehide",T),document.addEventListener("visibilitychange",O),h?.addEventListener("click",()=>{let o=l(),r=I(o.changes);t.onCopy?.(r);let p=t.mapPayload?t.mapPayload(r):r,m=JSON.stringify(p,null,2),E=`\u2713 Copied ${o.changes.length} change${o.changes.length!==1?"s":""} to clipboard`;navigator.clipboard?.writeText?navigator.clipboard.writeText(m).then(()=>v(E)).catch(()=>D(m,E)):D(m,E)}),$?.addEventListener("click",()=>{let o=l(),r=I(o.changes),p=new Date().toISOString().slice(0,10);ae(`edit-mode-${window.location.hostname}-${p}.json`,JSON.stringify(r,null,2)),v("\u2713 Backup downloaded")}),f?.addEventListener("click",()=>{u?.href&&(l(),window.location.href=u.href)}),d.addEventListener("click",()=>{l(),window.sessionStorage.removeItem(e.sessionKey);let o=new URL(window.location.href);o.searchParams.delete(e.queryParam),window.location.href=o.toString()}),l(),b>0&&g(`Restored ${b} saved edit${b===1?"":"s"} \u2022 Auto-save on`),{active:!0,getChanges:()=>x(e.editableSelector),destroy:()=>{l(),document.removeEventListener("keydown",S),document.removeEventListener("click",k,!0),document.removeEventListener("focusin",L),document.removeEventListener("input",M),window.removeEventListener("pagehide",T),document.removeEventListener("visibilitychange",O),s.remove(),c.remove(),a.remove(),document.querySelectorAll(e.editableSelector).forEach(o=>{o.dataset.editOriginal!==void 0&&(o.contentEditable="false",delete o.dataset.editOriginal,delete o.dataset.editKey)}),window.__COLLIDE_EDIT_MODE_ACTIVE=!1}}}var de={initClientEditMode:y};if(typeof window<"u"){window.CollideEditMode=de;let t=document.currentScript,e={};t?.dataset.brandName&&(e.brandName=t.dataset.brandName),t?.dataset.queryParam&&(e.queryParam=t.dataset.queryParam),t?.dataset.queryValue&&(e.queryValue=t.dataset.queryValue),t?.dataset.sessionKey&&(e.sessionKey=t.dataset.sessionKey),t?.dataset.accentColour&&(e.accentColour=t.dataset.accentColour),t?.dataset.editableSelector&&(e.editableSelector=t.dataset.editableSelector),t?.dataset.skipSelector&&(e.skipSelector=t.dataset.skipSelector),y(e)}return U(ce);})();
11
+ `,document.body.appendChild(c);let x=c.querySelector(".cem-copy-btn"),v=c.querySelector(".cem-review-btn"),j=c.querySelector(".cem-download-btn"),y=c.querySelector(".cem-open-link-btn"),b=c.querySelector(".cem-review-list"),O=c.querySelector(".cem-status"),p=null,f=!1,w=[];document.querySelectorAll("[data-reveal]").forEach(o=>o.classList.add("is-visible")),pe(e.queryParam,e.queryValue),ye(e);let C=Ee(fe(n,a),e.editableSelector),z=()=>({version:V,page:window.location.pathname,url:window.location.href,updatedAt:new Date().toISOString(),changes:U(e.editableSelector)}),A=()=>{if(b){if(w.length===0){b.innerHTML='<p class="cem-review-empty">No changes yet.</p>';return}b.innerHTML=w.map((o,r)=>`
12
+ <div class="cem-review-item">
13
+ <div class="cem-review-path">${T(o.path)}</div>
14
+ <div class="cem-review-diff"><del>${T(o.original)}</del><ins>${T(o.new)}</ins></div>
15
+ <button class="cem-revert-btn" type="button" data-index="${r}">Revert</button>
16
+ </div>
17
+ `).join("")}},I=(o,r)=>{x&&(x.textContent=o>0?`\u{1F4CB} Copy Changes (${o})`:"\u{1F4CB} Copy Changes"),y&&(y.hidden=!p,y.textContent=p?`Open ${p.hostname||"link"}`:"Open link"),O&&(O.textContent=r||`Auto-saved ${he()} \u2022 ${o} change${o===1?"":"s"} \u2022 Links: edit text or Ctrl/\u2318-click to open`)},L=o=>{I(M(e.editableSelector).length,o)},S=()=>{let o=z();w=o.changes;let r=e.autoSave!==!1&&ge(n,a,o);return I(o.changes.length,r||e.autoSave===!1?void 0:"\u26A0\uFE0F Auto-save unavailable \u2014 use Download backup"),f&&A(),o},g=null,m=()=>(g!==null&&(window.clearTimeout(g),g=null),S()),F=()=>{g!==null&&window.clearTimeout(g),g=window.setTimeout(()=>{g=null,S()},oe)},H=()=>{let o=document.activeElement;return!(o instanceof HTMLElement)||!o.isContentEditable&&o.contentEditable!=="true"?!1:(o.blur(),window.getSelection()?.removeAllRanges(),m(),!0)},N=o=>{if(o.key==="Escape"){if(!H())return;o.preventDefault(),o.stopPropagation();return}if(o.key==="Enter"&&!o.shiftKey){let r=o.target;r instanceof HTMLElement&&ne.has(r.tagName)&&(r.isContentEditable||r.contentEditable==="true")&&(o.preventDefault(),H())}},q=o=>{let r=o.target;if(!(r instanceof Element))return;let l=r.closest("a[href]");if(l&&!l.closest(".cem-panel")&&!l.closest(".cem-edit-banner")){if(p=l,L("Link selected \u2014 edit the text, use Open link, or Ctrl/\u2318-click to visit it"),o.metaKey||o.ctrlKey||o.altKey){m();return}o.preventDefault();return}let u=r.closest("button");u&&!u.closest(".cem-panel")&&!u.closest(".cem-edit-banner")&&(o.preventDefault(),o.stopImmediatePropagation())},$=o=>{let r=o.target;r instanceof Element&&(p=r.closest("a[href]"),L(p?"Link selected \u2014 edit the text or use Open link to navigate":void 0))},_=o=>{o.target instanceof HTMLElement&&xe(o.target),F()},K=()=>m(),P=()=>{document.visibilityState==="hidden"&&m()};return document.addEventListener("keydown",N),document.addEventListener("click",q,!0),document.addEventListener("focusin",$),document.addEventListener("input",_),window.addEventListener("pagehide",K),document.addEventListener("visibilitychange",P),v?.addEventListener("click",()=>{f=!f,b&&(b.hidden=!f),v.textContent=f?"Hide review":"Review",v.setAttribute("aria-expanded",String(f)),f&&A()}),b?.addEventListener("click",o=>{let r=o.target;if(!(r instanceof HTMLElement))return;let l=r.closest(".cem-revert-btn");if(!l)return;let u=w[Number(l.dataset.index)];if(!u)return;let h=we(e.editableSelector,u.key);h&&(h.textContent=u.original),m()}),x?.addEventListener("click",()=>{let o=m(),r=B(o.changes);t.onCopy?.(r);let l=t.mapPayload?t.mapPayload(r):r,u=JSON.stringify(l,null,2),h=`\u2713 Copied ${o.changes.length} change${o.changes.length!==1?"s":""} to clipboard`;navigator.clipboard?.writeText?navigator.clipboard.writeText(u).then(()=>D(h)).catch(()=>R(u,h)):R(u,h)}),j?.addEventListener("click",()=>{let o=m(),r=B(o.changes),l=new Date().toISOString().slice(0,10);be(`edit-mode-${window.location.hostname}-${l}.json`,JSON.stringify(r,null,2)),D("\u2713 Backup downloaded")}),y?.addEventListener("click",()=>{p?.href&&(m(),window.location.href=p.href)}),s.addEventListener("click",()=>{m(),window.sessionStorage.removeItem(e.sessionKey);let o=new URL(window.location.href);o.searchParams.delete(e.queryParam),window.location.href=o.toString()}),S(),C>0&&L(`Restored ${C} saved edit${C===1?"":"s"} \u2022 Auto-save on`),{active:!0,getChanges:()=>M(e.editableSelector),destroy:()=>{m(),document.removeEventListener("keydown",N),document.removeEventListener("click",q,!0),document.removeEventListener("focusin",$),document.removeEventListener("input",_),window.removeEventListener("pagehide",K),document.removeEventListener("visibilitychange",P),d.remove(),c.remove(),i.remove(),document.querySelectorAll(e.editableSelector).forEach(o=>{o.dataset.editOriginal!==void 0&&(o.contentEditable="false",o.classList.remove("cem-changed"),delete o.dataset.editOriginal,delete o.dataset.editKey)}),window.__COLLIDE_EDIT_MODE_ACTIVE=!1}}}var ve={initClientEditMode:E};if(typeof window<"u"){window.CollideEditMode=ve;let t=document.currentScript,e={};t?.dataset.brandName&&(e.brandName=t.dataset.brandName),t?.dataset.queryParam&&(e.queryParam=t.dataset.queryParam),t?.dataset.queryValue&&(e.queryValue=t.dataset.queryValue),t?.dataset.sessionKey&&(e.sessionKey=t.dataset.sessionKey),t?.dataset.accentColour&&(e.accentColour=t.dataset.accentColour),t?.dataset.editableSelector&&(e.editableSelector=t.dataset.editableSelector),t?.dataset.skipSelector&&(e.skipSelector=t.dataset.skipSelector),E(e)}return X(Ce);})();
10
18
  //# sourceMappingURL=browser.global.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/browser.ts","../src/index.ts"],"sourcesContent":["import { initClientEditMode } from \"./index\";\nimport type { EditModeInstance, EditModeOptions } from \"./types\";\n\nconst api = { initClientEditMode };\n\nif (typeof window !== \"undefined\") {\n window.CollideEditMode = api;\n\n const script = document.currentScript as HTMLScriptElement | null;\n const options: EditModeOptions = {};\n\n if (script?.dataset.brandName) options.brandName = script.dataset.brandName;\n if (script?.dataset.queryParam) options.queryParam = script.dataset.queryParam;\n if (script?.dataset.queryValue) options.queryValue = script.dataset.queryValue;\n if (script?.dataset.sessionKey) options.sessionKey = script.dataset.sessionKey;\n if (script?.dataset.accentColour) options.accentColour = script.dataset.accentColour;\n if (script?.dataset.editableSelector) options.editableSelector = script.dataset.editableSelector;\n if (script?.dataset.skipSelector) options.skipSelector = script.dataset.skipSelector;\n\n initClientEditMode(options);\n}\n\nexport { initClientEditMode };\nexport type { EditModeInstance, EditModeOptions };\n","import type { EditModeChange, EditModeInstance, EditModeOptions, EditModePayload } from \"./types\";\n\nexport type { EditModeChange, EditModeInstance, EditModeOptions, EditModePayload } from \"./types\";\n\nconst DEFAULT_EDITABLE_SELECTOR =\n \"h1,h2,h3,h4,h5,h6,p,li,blockquote,figcaption,label,legend,dt,dd,th,td,a,button\";\n\nconst DEFAULT_SKIP_SELECTOR =\n \"[data-no-edit],[data-edit-mode-skip],form,input,textarea,select,option,script,style,svg,canvas,iframe,.cem-edit-banner,.cem-panel,.cem-toast\";\n\nconst SKIP_CHILD_TAGS = new Set([\"IMG\", \"PICTURE\", \"SVG\", \"VIDEO\", \"CANVAS\", \"IFRAME\", \"SCRIPT\", \"STYLE\"]);\nconst DRAFT_VERSION = 1;\n\ntype Config = Required<\n Omit<EditModeOptions, \"enabled\" | \"onCopy\" | \"mapPayload\" | \"storageKey\" | \"autoSave\">\n> &\n Pick<EditModeOptions, \"enabled\" | \"storageKey\" | \"autoSave\">;\n\ntype DraftChange = EditModeChange & {\n key: string;\n};\n\ntype Draft = {\n version: number;\n page: string;\n url: string;\n updatedAt: string;\n changes: DraftChange[];\n};\n\nconst defaults = {\n queryParam: \"edit\",\n queryValue: \"true\" as string | null,\n sessionKey: \"collide-edit-mode\",\n brandName: \"Edit Mode\",\n accentColour: \"#1e40af\",\n editableSelector: DEFAULT_EDITABLE_SELECTOR,\n skipSelector: DEFAULT_SKIP_SELECTOR,\n autoSave: true,\n};\n\nfunction hasDom() {\n return typeof window !== \"undefined\" && typeof document !== \"undefined\";\n}\n\nfunction shouldEnable(options: Config) {\n if (options.enabled !== undefined) return options.enabled;\n\n const url = new URL(window.location.href);\n const queryValue = url.searchParams.get(options.queryParam);\n const queryMatches =\n options.queryValue === null ? queryValue !== null : queryValue === options.queryValue;\n\n return queryMatches || window.sessionStorage.getItem(options.sessionKey) === \"1\";\n}\n\nfunction addStyles(accentColour: string) {\n const style = document.createElement(\"style\");\n style.dataset.editModeStyle = \"true\";\n style.textContent = [\n `.cem-edit-banner{position:fixed;top:0;left:0;right:0;z-index:2147483640;background:${accentColour};color:#fff;display:flex;align-items:center;justify-content:center;gap:8px;padding:8px 92px 8px 16px;font:13px/1.4 system-ui,-apple-system,sans-serif;box-shadow:0 1px 8px rgba(0,0,0,.18)}`,\n \".cem-edit-banner kbd{background:rgba(255,255,255,.18);padding:0 4px;border-radius:3px;font-size:11px}\",\n \".cem-exit-btn{position:absolute;right:8px;top:50%;transform:translateY(-50%);background:rgba(255,255,255,.15);border:1px solid rgba(255,255,255,.25);color:#fff;padding:4px 10px;border-radius:5px;font:12px system-ui,-apple-system,sans-serif;cursor:pointer;white-space:nowrap}\",\n \".cem-exit-btn:hover,.cem-exit-btn:focus-visible{background:rgba(255,255,255,.25);outline:none}\",\n \"[contenteditable=true]{cursor:text!important}\",\n \"[contenteditable=true]:hover{outline:2px dashed rgba(59,130,246,.5)!important;outline-offset:2px}\",\n \"[contenteditable=true]:focus{outline:2px solid #3b82f6!important;outline-offset:2px;background:rgba(59,130,246,.04)}\",\n `.cem-panel{position:fixed;right:16px;bottom:16px;z-index:2147483640;background:#fff;color:#111827;border:1px solid #e5e7eb;border-radius:12px;box-shadow:0 8px 30px rgba(0,0,0,.22);padding:10px;display:grid;gap:8px;width:min(320px,calc(100vw - 32px));font:13px/1.4 system-ui,-apple-system,sans-serif}`,\n \".cem-panel-actions{display:flex;gap:8px;flex-wrap:wrap}\",\n `.cem-copy-btn,.cem-download-btn,.cem-open-link-btn{border:0;border-radius:8px;padding:9px 12px;font:600 13px system-ui,-apple-system,sans-serif;cursor:pointer;white-space:nowrap}`,\n `.cem-copy-btn{background:${accentColour};color:#fff;flex:1}`,\n \".cem-download-btn,.cem-open-link-btn{background:#f3f4f6;color:#111827;border:1px solid #e5e7eb}\",\n \".cem-open-link-btn[hidden]{display:none}\",\n \".cem-copy-btn:hover,.cem-download-btn:hover,.cem-open-link-btn:hover{filter:brightness(.97)}\",\n \".cem-status{color:#4b5563;font-size:12px;min-height:17px}\",\n \".cem-toast{position:fixed;bottom:116px;right:16px;z-index:2147483641;background:#065f46;color:#fff;padding:10px 18px;border-radius:8px;font:14px system-ui,-apple-system,sans-serif;box-shadow:0 2px 12px rgba(0,0,0,.3);transition:opacity .3s}\",\n \"@media(max-width:520px){.cem-edit-banner{justify-content:flex-start;text-align:left}.cem-panel{left:16px;right:16px;width:auto}}\",\n ].join(\"\\n\");\n document.head.appendChild(style);\n return style;\n}\n\nfunction showToast(message: string) {\n document.querySelector(\".cem-toast\")?.remove();\n const toast = document.createElement(\"div\");\n toast.className = \"cem-toast\";\n toast.textContent = message;\n document.body.appendChild(toast);\n window.setTimeout(() => {\n toast.style.opacity = \"0\";\n window.setTimeout(() => toast.parentNode?.removeChild(toast), 300);\n }, 2500);\n}\n\nfunction fallbackCopy(text: string, label: string) {\n const textarea = document.createElement(\"textarea\");\n textarea.value = text;\n textarea.style.position = \"fixed\";\n textarea.style.left = \"-9999px\";\n document.body.appendChild(textarea);\n textarea.select();\n try {\n document.execCommand(\"copy\");\n } catch {\n // Ignore: the download button and autosaved draft still protect the work.\n }\n document.body.removeChild(textarea);\n showToast(label);\n}\n\nfunction getStorage() {\n try {\n const testKey = \"__cem_storage_test__\";\n window.localStorage.setItem(testKey, \"1\");\n window.localStorage.removeItem(testKey);\n return window.localStorage;\n } catch {\n try {\n return window.sessionStorage;\n } catch {\n return null;\n }\n }\n}\n\nfunction getDraftKey(config: Config) {\n const base = config.storageKey || `${config.sessionKey}:draft`;\n return `${base}:${window.location.origin}${window.location.pathname}`;\n}\n\nfunction isEditableElement(el: Element, skipSelector: string) {\n if (el.closest(skipSelector)) return false;\n\n const text = el.textContent?.trim() ?? \"\";\n if (!text) return false;\n\n const children = Array.from(el.children);\n if (children.length > 0 && children.every((child) => SKIP_CHILD_TAGS.has(child.tagName))) {\n return false;\n }\n\n return true;\n}\n\nfunction getElementKey(el: HTMLElement) {\n if (el.dataset.editId) return `data-edit-id:${el.dataset.editId}`;\n if (el.id) return `id:${el.id}`;\n\n const parts: string[] = [];\n let current: Element | null = el;\n\n while (current && current !== document.body && current !== document.documentElement) {\n const parentElement: HTMLElement | null = current.parentElement;\n if (!parentElement) break;\n\n const currentTag = current.tagName;\n const siblings = Array.from(parentElement.children).filter((child) => child.tagName === currentTag);\n const index = siblings.indexOf(current) + 1;\n parts.unshift(`${current.tagName.toLowerCase()}:nth-of-type(${index})`);\n current = parentElement;\n }\n\n return parts.join(\" > \");\n}\n\nfunction getEditPath(el: HTMLElement) {\n let context = \"\";\n let current = el.parentElement;\n\n while (current && current !== document.body && current !== document.documentElement) {\n if (current.id) {\n context = `#${current.id}`;\n break;\n }\n\n const section = current.getAttribute(\"data-section\");\n if (section) {\n context = section;\n break;\n }\n\n if (current.tagName === \"SECTION\") {\n const heading = current.querySelector(\"h1,h2,h3\");\n if (heading?.textContent) {\n context = heading.textContent.trim().slice(0, 40);\n break;\n }\n }\n\n if ([\"HEADER\", \"FOOTER\", \"MAIN\", \"NAV\"].includes(current.tagName)) {\n context = current.tagName.toLowerCase();\n break;\n }\n\n current = current.parentElement;\n }\n\n const tag = el.tagName.toLowerCase();\n const text = el.dataset.editOriginal || el.textContent?.trim() || \"\";\n const snippet = text.slice(0, 40) + (text.length > 40 ? \"...\" : \"\");\n return `${context ? `${context} > ` : \"\"}${tag}: \"${snippet}\"`;\n}\n\nfunction rewriteLinks(queryParam: string, queryValue: string | null) {\n document.querySelectorAll<HTMLAnchorElement>(\"a[href]\").forEach((anchor) => {\n const href = anchor.getAttribute(\"href\");\n if (!href || /^(https?:|mailto:|tel:|javascript:)/i.test(href)) return;\n\n try {\n const url = new URL(href, window.location.href);\n if (url.origin !== window.location.origin) return;\n\n url.searchParams.set(queryParam, queryValue ?? \"1\");\n const hash = url.hash;\n url.hash = \"\";\n anchor.setAttribute(\"href\", `${url.pathname}${url.search}${hash}`);\n } catch {\n // Leave unusual hrefs unchanged.\n }\n });\n}\n\nfunction buildPayload(changes: EditModeChange[]): EditModePayload {\n return {\n site: window.location.hostname,\n page: window.location.pathname,\n pageTitle: document.title,\n url: window.location.href,\n timestamp: new Date().toISOString(),\n changes,\n };\n}\n\nfunction loadDraft(storage: Storage | null, key: string): Draft | null {\n if (!storage) return null;\n\n try {\n const raw = storage.getItem(key);\n if (!raw) return null;\n const draft = JSON.parse(raw) as Draft;\n return draft?.version === DRAFT_VERSION && Array.isArray(draft.changes) ? draft : null;\n } catch {\n return null;\n }\n}\n\nfunction saveDraft(storage: Storage | null, key: string, draft: Draft) {\n if (!storage) return false;\n\n try {\n storage.setItem(key, JSON.stringify(draft));\n return true;\n } catch {\n return false;\n }\n}\n\nfunction downloadText(filename: string, text: string) {\n const blob = new Blob([text], { type: \"application/json\" });\n const url = URL.createObjectURL(blob);\n const anchor = document.createElement(\"a\");\n anchor.href = url;\n anchor.download = filename;\n anchor.style.display = \"none\";\n document.body.appendChild(anchor);\n anchor.click();\n anchor.remove();\n window.setTimeout(() => URL.revokeObjectURL(url), 1000);\n}\n\nfunction formatTime(date = new Date()) {\n return date.toLocaleTimeString([], { hour: \"2-digit\", minute: \"2-digit\" });\n}\n\nfunction initEditableElements(config: Config) {\n document.querySelectorAll<HTMLElement>(config.editableSelector).forEach((el) => {\n if (!isEditableElement(el, config.skipSelector)) return;\n\n const text = el.textContent?.trim() ?? \"\";\n el.contentEditable = \"true\";\n if (!el.dataset.editOriginal) el.dataset.editOriginal = text;\n if (!el.dataset.editKey) el.dataset.editKey = getElementKey(el);\n });\n}\n\nfunction applyDraft(draft: Draft | null, editableSelector: string) {\n if (!draft) return 0;\n\n const edits = new Map(draft.changes.map((change) => [change.key, change]));\n let restored = 0;\n\n document.querySelectorAll<HTMLElement>(editableSelector).forEach((el) => {\n const key = el.dataset.editKey;\n if (!key) return;\n\n const saved = edits.get(key);\n if (!saved) return;\n\n // Avoid stomping over changed templates. Restore only when the original still matches.\n if (el.dataset.editOriginal !== saved.original) return;\n\n el.textContent = saved.new;\n restored += 1;\n });\n\n return restored;\n}\n\nfunction collectDraftChanges(editableSelector: string): DraftChange[] {\n const changes: DraftChange[] = [];\n\n document.querySelectorAll<HTMLElement>(editableSelector).forEach((el) => {\n if (!el.isContentEditable && el.contentEditable !== \"true\") return;\n const original = el.dataset.editOriginal;\n const key = el.dataset.editKey;\n if (original === undefined || !key) return;\n\n const current = el.textContent?.trim() ?? \"\";\n if (original !== current) {\n changes.push({\n key,\n path: getEditPath(el),\n tag: el.tagName,\n original,\n new: current,\n });\n }\n });\n\n return changes;\n}\n\nfunction collectChanges(editableSelector: string): EditModeChange[] {\n return collectDraftChanges(editableSelector).map(({ key: _key, ...change }) => change);\n}\n\nexport function initClientEditMode(options: EditModeOptions = {}): EditModeInstance {\n if (!hasDom()) {\n return { active: false, getChanges: () => [], destroy: () => undefined };\n }\n\n const config: Config = { ...defaults, ...options };\n\n if (!shouldEnable(config)) {\n return { active: false, getChanges: () => [], destroy: () => undefined };\n }\n\n if (window.__COLLIDE_EDIT_MODE_ACTIVE) {\n return { active: true, getChanges: () => collectChanges(config.editableSelector), destroy: () => undefined };\n }\n\n window.__COLLIDE_EDIT_MODE_ACTIVE = true;\n window.sessionStorage.setItem(config.sessionKey, \"1\");\n\n const storage = getStorage();\n const draftKey = getDraftKey(config);\n const style = addStyles(config.accentColour);\n\n const banner = document.createElement(\"div\");\n banner.className = \"cem-edit-banner\";\n banner.innerHTML = `✏️ <strong>${config.brandName}</strong> <span>Click text to edit. Press <kbd>Esc</kbd> when done.</span>`;\n\n const exitButton = document.createElement(\"button\");\n exitButton.className = \"cem-exit-btn\";\n exitButton.type = \"button\";\n exitButton.textContent = \"Exit\";\n banner.appendChild(exitButton);\n document.body.prepend(banner);\n\n const panel = document.createElement(\"div\");\n panel.className = \"cem-panel\";\n panel.innerHTML = `\n <div class=\"cem-panel-actions\">\n <button class=\"cem-copy-btn\" type=\"button\">📋 Copy Changes</button>\n <button class=\"cem-download-btn\" type=\"button\">Download backup</button>\n <button class=\"cem-open-link-btn\" type=\"button\" hidden>Open link</button>\n </div>\n <div class=\"cem-status\" aria-live=\"polite\">Auto-save ready</div>\n `;\n document.body.appendChild(panel);\n\n const copyButton = panel.querySelector<HTMLButtonElement>(\".cem-copy-btn\");\n const downloadButton = panel.querySelector<HTMLButtonElement>(\".cem-download-btn\");\n const openLinkButton = panel.querySelector<HTMLButtonElement>(\".cem-open-link-btn\");\n const status = panel.querySelector<HTMLElement>(\".cem-status\");\n let activeLink: HTMLAnchorElement | null = null;\n\n document.querySelectorAll(\"[data-reveal]\").forEach((el) => el.classList.add(\"is-visible\"));\n rewriteLinks(config.queryParam, config.queryValue);\n initEditableElements(config);\n\n const restoredCount = applyDraft(loadDraft(storage, draftKey), config.editableSelector);\n\n const makeDraft = (): Draft => ({\n version: DRAFT_VERSION,\n page: window.location.pathname,\n url: window.location.href,\n updatedAt: new Date().toISOString(),\n changes: collectDraftChanges(config.editableSelector),\n });\n\n const updateUi = (message?: string) => {\n const count = collectChanges(config.editableSelector).length;\n if (copyButton) copyButton.textContent = count > 0 ? `📋 Copy Changes (${count})` : \"📋 Copy Changes\";\n if (openLinkButton) {\n openLinkButton.hidden = !activeLink;\n openLinkButton.textContent = activeLink ? `Open ${activeLink.hostname || \"link\"}` : \"Open link\";\n }\n if (status) {\n status.textContent =\n message ||\n `Auto-saved ${formatTime()} • ${count} change${count === 1 ? \"\" : \"s\"} • Links: edit text or Ctrl/⌘-click to open`;\n }\n };\n\n const persistDraft = () => {\n const draft = makeDraft();\n const saved = config.autoSave !== false && saveDraft(storage, draftKey, draft);\n updateUi(saved || config.autoSave === false ? undefined : \"⚠️ Auto-save unavailable — use Download backup\");\n return draft;\n };\n\n const finishEditing = () => {\n const activeElement = document.activeElement;\n if (!(activeElement instanceof HTMLElement)) return false;\n if (!activeElement.isContentEditable && activeElement.contentEditable !== \"true\") return false;\n\n activeElement.blur();\n window.getSelection()?.removeAllRanges();\n persistDraft();\n return true;\n };\n\n const finishEditingOnEscape = (event: KeyboardEvent) => {\n if (event.key !== \"Escape\") return;\n if (!finishEditing()) return;\n\n event.preventDefault();\n event.stopPropagation();\n };\n\n const handleDocumentClick = (event: MouseEvent) => {\n const target = event.target;\n if (!(target instanceof Element)) return;\n\n const anchor = target.closest<HTMLAnchorElement>(\"a[href]\");\n if (anchor && !anchor.closest(\".cem-panel\") && !anchor.closest(\".cem-edit-banner\")) {\n activeLink = anchor;\n updateUi(\"Link selected — edit the text, use Open link, or Ctrl/⌘-click to visit it\");\n\n if (event.metaKey || event.ctrlKey || event.altKey) {\n persistDraft();\n return;\n }\n\n event.preventDefault();\n return;\n }\n\n const button = target.closest(\"button\");\n if (button && !button.closest(\".cem-panel\") && !button.closest(\".cem-edit-banner\")) {\n event.preventDefault();\n event.stopImmediatePropagation();\n }\n };\n\n const handleFocusIn = (event: FocusEvent) => {\n const target = event.target;\n if (!(target instanceof Element)) return;\n\n activeLink = target.closest<HTMLAnchorElement>(\"a[href]\");\n updateUi(activeLink ? \"Link selected — edit the text or use Open link to navigate\" : undefined);\n };\n\n const handleInput = () => persistDraft();\n const handlePageHide = () => persistDraft();\n const handleVisibilityChange = () => {\n if (document.visibilityState === \"hidden\") persistDraft();\n };\n\n document.addEventListener(\"keydown\", finishEditingOnEscape);\n document.addEventListener(\"click\", handleDocumentClick, true);\n document.addEventListener(\"focusin\", handleFocusIn);\n document.addEventListener(\"input\", handleInput);\n window.addEventListener(\"pagehide\", handlePageHide);\n document.addEventListener(\"visibilitychange\", handleVisibilityChange);\n\n copyButton?.addEventListener(\"click\", () => {\n const draft = persistDraft();\n const payload = buildPayload(draft.changes);\n options.onCopy?.(payload);\n\n const copyPayload = options.mapPayload ? options.mapPayload(payload) : payload;\n const json = JSON.stringify(copyPayload, null, 2);\n const label = `✓ Copied ${draft.changes.length} change${draft.changes.length !== 1 ? \"s\" : \"\"} to clipboard`;\n\n if (navigator.clipboard?.writeText) {\n navigator.clipboard.writeText(json).then(() => showToast(label)).catch(() => fallbackCopy(json, label));\n } else {\n fallbackCopy(json, label);\n }\n });\n\n downloadButton?.addEventListener(\"click\", () => {\n const draft = persistDraft();\n const payload = buildPayload(draft.changes);\n const date = new Date().toISOString().slice(0, 10);\n downloadText(`edit-mode-${window.location.hostname}-${date}.json`, JSON.stringify(payload, null, 2));\n showToast(\"✓ Backup downloaded\");\n });\n\n openLinkButton?.addEventListener(\"click\", () => {\n if (!activeLink?.href) return;\n\n persistDraft();\n window.location.href = activeLink.href;\n });\n\n exitButton.addEventListener(\"click\", () => {\n persistDraft();\n window.sessionStorage.removeItem(config.sessionKey);\n const url = new URL(window.location.href);\n url.searchParams.delete(config.queryParam);\n window.location.href = url.toString();\n });\n\n persistDraft();\n if (restoredCount > 0) updateUi(`Restored ${restoredCount} saved edit${restoredCount === 1 ? \"\" : \"s\"} • Auto-save on`);\n\n return {\n active: true,\n getChanges: () => collectChanges(config.editableSelector),\n destroy: () => {\n persistDraft();\n document.removeEventListener(\"keydown\", finishEditingOnEscape);\n document.removeEventListener(\"click\", handleDocumentClick, true);\n document.removeEventListener(\"focusin\", handleFocusIn);\n document.removeEventListener(\"input\", handleInput);\n window.removeEventListener(\"pagehide\", handlePageHide);\n document.removeEventListener(\"visibilitychange\", handleVisibilityChange);\n banner.remove();\n panel.remove();\n style.remove();\n document.querySelectorAll<HTMLElement>(config.editableSelector).forEach((el) => {\n if (el.dataset.editOriginal !== undefined) {\n el.contentEditable = \"false\";\n delete el.dataset.editOriginal;\n delete el.dataset.editKey;\n }\n });\n window.__COLLIDE_EDIT_MODE_ACTIVE = false;\n },\n };\n}\n"],"mappings":"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","names":["browser_exports","__export","initClientEditMode","DEFAULT_EDITABLE_SELECTOR","DEFAULT_SKIP_SELECTOR","SKIP_CHILD_TAGS","DRAFT_VERSION","defaults","hasDom","shouldEnable","options","queryValue","addStyles","accentColour","style","showToast","message","toast","fallbackCopy","text","label","textarea","getStorage","testKey","getDraftKey","config","isEditableElement","el","skipSelector","children","child","getElementKey","parts","current","parentElement","currentTag","index","getEditPath","context","section","heading","tag","snippet","rewriteLinks","queryParam","anchor","href","url","hash","buildPayload","changes","loadDraft","storage","key","raw","draft","saveDraft","downloadText","filename","blob","formatTime","date","initEditableElements","applyDraft","editableSelector","edits","change","restored","saved","collectDraftChanges","original","collectChanges","_key","initClientEditMode","draftKey","banner","exitButton","panel","copyButton","downloadButton","openLinkButton","status","activeLink","restoredCount","makeDraft","updateUi","count","persistDraft","finishEditing","activeElement","finishEditingOnEscape","event","handleDocumentClick","target","button","handleFocusIn","handleInput","handlePageHide","handleVisibilityChange","payload","copyPayload","json","api","initClientEditMode","script","options"]}
1
+ {"version":3,"sources":["../src/browser.ts","../src/index.ts"],"sourcesContent":["import { initClientEditMode } from \"./index\";\nimport type { EditModeInstance, EditModeOptions } from \"./types\";\n\nconst api = { initClientEditMode };\n\nif (typeof window !== \"undefined\") {\n window.CollideEditMode = api;\n\n const script = document.currentScript as HTMLScriptElement | null;\n const options: EditModeOptions = {};\n\n if (script?.dataset.brandName) options.brandName = script.dataset.brandName;\n if (script?.dataset.queryParam) options.queryParam = script.dataset.queryParam;\n if (script?.dataset.queryValue) options.queryValue = script.dataset.queryValue;\n if (script?.dataset.sessionKey) options.sessionKey = script.dataset.sessionKey;\n if (script?.dataset.accentColour) options.accentColour = script.dataset.accentColour;\n if (script?.dataset.editableSelector) options.editableSelector = script.dataset.editableSelector;\n if (script?.dataset.skipSelector) options.skipSelector = script.dataset.skipSelector;\n\n initClientEditMode(options);\n}\n\nexport { initClientEditMode };\nexport type { EditModeInstance, EditModeOptions };\n","import type { EditModeChange, EditModeInstance, EditModeOptions, EditModePayload } from \"./types\";\n\nexport type { EditModeChange, EditModeInstance, EditModeOptions, EditModePayload } from \"./types\";\n\nconst DEFAULT_EDITABLE_SELECTOR =\n \"h1,h2,h3,h4,h5,h6,p,li,blockquote,figcaption,label,legend,dt,dd,th,td,a,button\";\n\nconst DEFAULT_SKIP_SELECTOR =\n \"[data-no-edit],[data-edit-mode-skip],form,input,textarea,select,option,script,style,svg,canvas,iframe,.cem-edit-banner,.cem-panel,.cem-toast\";\n\nconst SKIP_CHILD_TAGS = new Set([\"IMG\", \"PICTURE\", \"SVG\", \"VIDEO\", \"CANVAS\", \"IFRAME\", \"SCRIPT\", \"STYLE\"]);\nconst SINGLE_LINE_TAGS = new Set([\n \"H1\",\n \"H2\",\n \"H3\",\n \"H4\",\n \"H5\",\n \"H6\",\n \"BUTTON\",\n \"LABEL\",\n \"LEGEND\",\n \"DT\",\n \"TH\",\n \"TD\",\n \"FIGCAPTION\",\n \"A\",\n]);\nconst DRAFT_VERSION = 1;\nconst AUTOSAVE_DEBOUNCE_MS = 400;\n\ntype Config = Required<\n Omit<EditModeOptions, \"enabled\" | \"onCopy\" | \"mapPayload\" | \"storageKey\" | \"autoSave\">\n> &\n Pick<EditModeOptions, \"enabled\" | \"storageKey\" | \"autoSave\">;\n\ntype DraftChange = EditModeChange & {\n key: string;\n};\n\ntype Draft = {\n version: number;\n page: string;\n url: string;\n updatedAt: string;\n changes: DraftChange[];\n};\n\nconst defaults = {\n queryParam: \"edit\",\n queryValue: \"true\" as string | null,\n sessionKey: \"collide-edit-mode\",\n brandName: \"Edit Mode\",\n accentColour: \"#1e40af\",\n editableSelector: DEFAULT_EDITABLE_SELECTOR,\n skipSelector: DEFAULT_SKIP_SELECTOR,\n autoSave: true,\n};\n\nfunction hasDom() {\n return typeof window !== \"undefined\" && typeof document !== \"undefined\";\n}\n\nfunction shouldEnable(options: Config) {\n if (options.enabled !== undefined) return options.enabled;\n\n const url = new URL(window.location.href);\n const queryValue = url.searchParams.get(options.queryParam);\n const queryMatches =\n options.queryValue === null ? queryValue !== null : queryValue === options.queryValue;\n\n return queryMatches || window.sessionStorage.getItem(options.sessionKey) === \"1\";\n}\n\nfunction addStyles(accentColour: string) {\n const style = document.createElement(\"style\");\n style.dataset.editModeStyle = \"true\";\n style.textContent = [\n `.cem-edit-banner{position:fixed;top:0;left:0;right:0;z-index:2147483640;background:${accentColour};color:#fff;display:flex;align-items:center;justify-content:center;gap:8px;padding:8px 92px 8px 16px;font:13px/1.4 system-ui,-apple-system,sans-serif;box-shadow:0 1px 8px rgba(0,0,0,.18)}`,\n \".cem-edit-banner kbd{background:rgba(255,255,255,.18);padding:0 4px;border-radius:3px;font-size:11px}\",\n \".cem-exit-btn{position:absolute;right:8px;top:50%;transform:translateY(-50%);background:rgba(255,255,255,.15);border:1px solid rgba(255,255,255,.25);color:#fff;padding:4px 10px;border-radius:5px;font:12px system-ui,-apple-system,sans-serif;cursor:pointer;white-space:nowrap}\",\n \".cem-exit-btn:hover,.cem-exit-btn:focus-visible{background:rgba(255,255,255,.25);outline:none}\",\n \"[contenteditable=true]{cursor:text!important}\",\n \"[contenteditable=true]:hover{outline:2px dashed rgba(59,130,246,.5)!important;outline-offset:2px}\",\n \"[contenteditable=true]:focus{outline:2px solid #3b82f6!important;outline-offset:2px;background:rgba(59,130,246,.04)}\",\n \"[contenteditable=true].cem-changed{box-shadow:inset 3px 0 0 #10b981}\",\n `.cem-panel{position:fixed;right:16px;bottom:16px;z-index:2147483640;background:#fff;color:#111827;border:1px solid #e5e7eb;border-radius:12px;box-shadow:0 8px 30px rgba(0,0,0,.22);padding:10px;display:grid;gap:8px;width:min(320px,calc(100vw - 32px));font:13px/1.4 system-ui,-apple-system,sans-serif}`,\n \".cem-panel-actions{display:flex;gap:8px;flex-wrap:wrap}\",\n `.cem-copy-btn,.cem-review-btn,.cem-download-btn,.cem-open-link-btn{border:0;border-radius:8px;padding:9px 12px;font:600 13px system-ui,-apple-system,sans-serif;cursor:pointer;white-space:nowrap}`,\n `.cem-copy-btn{background:${accentColour};color:#fff;flex:1}`,\n \".cem-review-btn,.cem-download-btn,.cem-open-link-btn{background:#f3f4f6;color:#111827;border:1px solid #e5e7eb}\",\n \".cem-open-link-btn[hidden]{display:none}\",\n \".cem-copy-btn:hover,.cem-review-btn:hover,.cem-download-btn:hover,.cem-open-link-btn:hover{filter:brightness(.97)}\",\n \".cem-status{color:#4b5563;font-size:12px;min-height:17px}\",\n \".cem-review-list{max-height:220px;overflow-y:auto;display:grid;gap:6px;border-top:1px solid #e5e7eb;padding-top:8px}\",\n \".cem-review-list[hidden]{display:none}\",\n \".cem-review-empty{color:#6b7280;font-size:12px;margin:0;padding:4px 0}\",\n \".cem-review-item{display:grid;gap:4px;padding:8px;background:#f9fafb;border-radius:8px;border:1px solid #e5e7eb}\",\n \".cem-review-path{font-size:11px;color:#6b7280;font-weight:600;text-transform:uppercase;letter-spacing:.02em}\",\n \".cem-review-diff{font-size:12px;word-break:break-word}\",\n \".cem-review-diff del{color:#b91c1c;text-decoration:line-through;opacity:.75;display:block}\",\n \".cem-review-diff ins{color:#065f46;text-decoration:none;display:block}\",\n \".cem-revert-btn{justify-self:start;background:transparent;border:1px solid #e5e7eb;border-radius:6px;padding:3px 8px;font:12px system-ui,-apple-system,sans-serif;cursor:pointer;color:#374151}\",\n \".cem-revert-btn:hover{background:#fff}\",\n \".cem-toast{position:fixed;bottom:116px;right:16px;z-index:2147483641;background:#065f46;color:#fff;padding:10px 18px;border-radius:8px;font:14px system-ui,-apple-system,sans-serif;box-shadow:0 2px 12px rgba(0,0,0,.3);transition:opacity .3s}\",\n \"@media(max-width:520px){.cem-edit-banner{justify-content:flex-start;text-align:left}.cem-panel{left:16px;right:16px;width:auto}}\",\n ].join(\"\\n\");\n document.head.appendChild(style);\n return style;\n}\n\nfunction showToast(message: string) {\n document.querySelector(\".cem-toast\")?.remove();\n const toast = document.createElement(\"div\");\n toast.className = \"cem-toast\";\n toast.textContent = message;\n document.body.appendChild(toast);\n window.setTimeout(() => {\n toast.style.opacity = \"0\";\n window.setTimeout(() => toast.parentNode?.removeChild(toast), 300);\n }, 2500);\n}\n\nfunction fallbackCopy(text: string, label: string) {\n const textarea = document.createElement(\"textarea\");\n textarea.value = text;\n textarea.style.position = \"fixed\";\n textarea.style.left = \"-9999px\";\n document.body.appendChild(textarea);\n textarea.select();\n try {\n document.execCommand(\"copy\");\n } catch {\n // Ignore: the download button and autosaved draft still protect the work.\n }\n document.body.removeChild(textarea);\n showToast(label);\n}\n\nfunction getStorage() {\n try {\n const testKey = \"__cem_storage_test__\";\n window.localStorage.setItem(testKey, \"1\");\n window.localStorage.removeItem(testKey);\n return window.localStorage;\n } catch {\n try {\n return window.sessionStorage;\n } catch {\n return null;\n }\n }\n}\n\nfunction getDraftKey(config: Config) {\n const base = config.storageKey || `${config.sessionKey}:draft`;\n return `${base}:${window.location.origin}${window.location.pathname}`;\n}\n\nfunction escapeHtml(text: string) {\n return text.replace(/[&<>\"']/g, (char) => {\n switch (char) {\n case \"&\":\n return \"&amp;\";\n case \"<\":\n return \"&lt;\";\n case \">\":\n return \"&gt;\";\n case '\"':\n return \"&quot;\";\n default:\n return \"&#39;\";\n }\n });\n}\n\nfunction isEditableElement(el: Element, skipSelector: string) {\n if (el.closest(skipSelector)) return false;\n\n const text = el.textContent?.trim() ?? \"\";\n if (!text) return false;\n\n const children = Array.from(el.children);\n if (children.length > 0 && children.every((child) => SKIP_CHILD_TAGS.has(child.tagName))) {\n return false;\n }\n\n return true;\n}\n\nfunction getElementKey(el: HTMLElement) {\n if (el.dataset.editId) return `data-edit-id:${el.dataset.editId}`;\n if (el.id) return `id:${el.id}`;\n\n const parts: string[] = [];\n let current: Element | null = el;\n\n while (current && current !== document.body && current !== document.documentElement) {\n const parentElement: HTMLElement | null = current.parentElement;\n if (!parentElement) break;\n\n const currentTag = current.tagName;\n const siblings = Array.from(parentElement.children).filter((child) => child.tagName === currentTag);\n const index = siblings.indexOf(current) + 1;\n parts.unshift(`${current.tagName.toLowerCase()}:nth-of-type(${index})`);\n current = parentElement;\n }\n\n return parts.join(\" > \");\n}\n\nfunction getEditPath(el: HTMLElement) {\n let context = \"\";\n let current = el.parentElement;\n\n while (current && current !== document.body && current !== document.documentElement) {\n if (current.id) {\n context = `#${current.id}`;\n break;\n }\n\n const section = current.getAttribute(\"data-section\");\n if (section) {\n context = section;\n break;\n }\n\n if (current.tagName === \"SECTION\") {\n const heading = current.querySelector(\"h1,h2,h3\");\n if (heading?.textContent) {\n context = heading.textContent.trim().slice(0, 40);\n break;\n }\n }\n\n if ([\"HEADER\", \"FOOTER\", \"MAIN\", \"NAV\"].includes(current.tagName)) {\n context = current.tagName.toLowerCase();\n break;\n }\n\n current = current.parentElement;\n }\n\n const tag = el.tagName.toLowerCase();\n const text = el.dataset.editOriginal || el.textContent?.trim() || \"\";\n const snippet = text.slice(0, 40) + (text.length > 40 ? \"...\" : \"\");\n return `${context ? `${context} > ` : \"\"}${tag}: \"${snippet}\"`;\n}\n\nfunction rewriteLinks(queryParam: string, queryValue: string | null) {\n document.querySelectorAll<HTMLAnchorElement>(\"a[href]\").forEach((anchor) => {\n const href = anchor.getAttribute(\"href\");\n if (!href || /^(https?:|mailto:|tel:|javascript:)/i.test(href)) return;\n\n try {\n const url = new URL(href, window.location.href);\n if (url.origin !== window.location.origin) return;\n\n url.searchParams.set(queryParam, queryValue ?? \"1\");\n const hash = url.hash;\n url.hash = \"\";\n anchor.setAttribute(\"href\", `${url.pathname}${url.search}${hash}`);\n } catch {\n // Leave unusual hrefs unchanged.\n }\n });\n}\n\nfunction buildPayload(changes: EditModeChange[]): EditModePayload {\n return {\n site: window.location.hostname,\n page: window.location.pathname,\n pageTitle: document.title,\n url: window.location.href,\n timestamp: new Date().toISOString(),\n changes,\n };\n}\n\nfunction loadDraft(storage: Storage | null, key: string): Draft | null {\n if (!storage) return null;\n\n try {\n const raw = storage.getItem(key);\n if (!raw) return null;\n const draft = JSON.parse(raw) as Draft;\n return draft?.version === DRAFT_VERSION && Array.isArray(draft.changes) ? draft : null;\n } catch {\n return null;\n }\n}\n\nfunction saveDraft(storage: Storage | null, key: string, draft: Draft) {\n if (!storage) return false;\n\n try {\n storage.setItem(key, JSON.stringify(draft));\n return true;\n } catch {\n return false;\n }\n}\n\nfunction downloadText(filename: string, text: string) {\n const blob = new Blob([text], { type: \"application/json\" });\n const url = URL.createObjectURL(blob);\n const anchor = document.createElement(\"a\");\n anchor.href = url;\n anchor.download = filename;\n anchor.style.display = \"none\";\n document.body.appendChild(anchor);\n anchor.click();\n anchor.remove();\n window.setTimeout(() => URL.revokeObjectURL(url), 1000);\n}\n\nfunction formatTime(date = new Date()) {\n return date.toLocaleTimeString([], { hour: \"2-digit\", minute: \"2-digit\" });\n}\n\nfunction initEditableElements(config: Config) {\n // querySelectorAll returns document order, so an ancestor is always visited\n // before its descendants — skipping already-marked ancestors here prevents\n // nested contenteditable regions (e.g. an <a> inside an editable <p>), which\n // would otherwise report the same edit twice.\n document.querySelectorAll<HTMLElement>(config.editableSelector).forEach((el) => {\n if (!isEditableElement(el, config.skipSelector)) return;\n if (el.closest(\"[data-edit-key]\")) return;\n\n const text = el.textContent?.trim() ?? \"\";\n el.contentEditable = \"true\";\n if (!el.dataset.editOriginal) el.dataset.editOriginal = text;\n if (!el.dataset.editKey) el.dataset.editKey = getElementKey(el);\n });\n}\n\nfunction findEditableByKey(editableSelector: string, key: string) {\n const elements = document.querySelectorAll<HTMLElement>(editableSelector);\n for (const el of Array.from(elements)) {\n if (el.dataset.editKey === key) return el;\n }\n return null;\n}\n\nfunction applyDraft(draft: Draft | null, editableSelector: string) {\n if (!draft) return 0;\n\n const edits = new Map(draft.changes.map((change) => [change.key, change]));\n let restored = 0;\n\n document.querySelectorAll<HTMLElement>(editableSelector).forEach((el) => {\n const key = el.dataset.editKey;\n if (!key) return;\n\n const saved = edits.get(key);\n if (!saved) return;\n\n // Avoid stomping over changed templates. Restore only when the original still matches.\n if (el.dataset.editOriginal !== saved.original) return;\n\n el.textContent = saved.new;\n restored += 1;\n });\n\n return restored;\n}\n\nfunction markChanged(el: HTMLElement) {\n const original = el.dataset.editOriginal;\n if (original === undefined) return;\n const current = el.textContent?.trim() ?? \"\";\n el.classList.toggle(\"cem-changed\", original !== current);\n}\n\nfunction collectDraftChanges(editableSelector: string): DraftChange[] {\n const changes: DraftChange[] = [];\n\n document.querySelectorAll<HTMLElement>(editableSelector).forEach((el) => {\n if (!el.isContentEditable && el.contentEditable !== \"true\") return;\n const original = el.dataset.editOriginal;\n const key = el.dataset.editKey;\n if (original === undefined || !key) return;\n\n const current = el.textContent?.trim() ?? \"\";\n const changed = original !== current;\n el.classList.toggle(\"cem-changed\", changed);\n\n if (changed) {\n changes.push({\n key,\n path: getEditPath(el),\n tag: el.tagName,\n original,\n new: current,\n });\n }\n });\n\n return changes;\n}\n\nfunction collectChanges(editableSelector: string): EditModeChange[] {\n return collectDraftChanges(editableSelector).map(({ key: _key, ...change }) => change);\n}\n\nexport function initClientEditMode(options: EditModeOptions = {}): EditModeInstance {\n if (!hasDom()) {\n return { active: false, getChanges: () => [], destroy: () => undefined };\n }\n\n const config: Config = { ...defaults, ...options };\n\n if (!shouldEnable(config)) {\n return { active: false, getChanges: () => [], destroy: () => undefined };\n }\n\n if (window.__COLLIDE_EDIT_MODE_ACTIVE) {\n return { active: true, getChanges: () => collectChanges(config.editableSelector), destroy: () => undefined };\n }\n\n window.__COLLIDE_EDIT_MODE_ACTIVE = true;\n window.sessionStorage.setItem(config.sessionKey, \"1\");\n\n const storage = getStorage();\n const draftKey = getDraftKey(config);\n const style = addStyles(config.accentColour);\n\n const banner = document.createElement(\"div\");\n banner.className = \"cem-edit-banner\";\n banner.innerHTML = `✏️ <strong>${config.brandName}</strong> <span>Click text to edit. Press <kbd>Esc</kbd> when done.</span>`;\n\n const exitButton = document.createElement(\"button\");\n exitButton.className = \"cem-exit-btn\";\n exitButton.type = \"button\";\n exitButton.textContent = \"Exit\";\n banner.appendChild(exitButton);\n document.body.prepend(banner);\n\n const panel = document.createElement(\"div\");\n panel.className = \"cem-panel\";\n panel.innerHTML = `\n <div class=\"cem-panel-actions\">\n <button class=\"cem-copy-btn\" type=\"button\">📋 Copy Changes</button>\n <button class=\"cem-review-btn\" type=\"button\" aria-expanded=\"false\">Review</button>\n <button class=\"cem-download-btn\" type=\"button\">Download backup</button>\n <button class=\"cem-open-link-btn\" type=\"button\" hidden>Open link</button>\n </div>\n <div class=\"cem-review-list\" hidden></div>\n <div class=\"cem-status\" aria-live=\"polite\">Auto-save ready</div>\n `;\n document.body.appendChild(panel);\n\n const copyButton = panel.querySelector<HTMLButtonElement>(\".cem-copy-btn\");\n const reviewButton = panel.querySelector<HTMLButtonElement>(\".cem-review-btn\");\n const downloadButton = panel.querySelector<HTMLButtonElement>(\".cem-download-btn\");\n const openLinkButton = panel.querySelector<HTMLButtonElement>(\".cem-open-link-btn\");\n const reviewList = panel.querySelector<HTMLElement>(\".cem-review-list\");\n const status = panel.querySelector<HTMLElement>(\".cem-status\");\n let activeLink: HTMLAnchorElement | null = null;\n let reviewOpen = false;\n let currentDraftChanges: DraftChange[] = [];\n\n document.querySelectorAll(\"[data-reveal]\").forEach((el) => el.classList.add(\"is-visible\"));\n rewriteLinks(config.queryParam, config.queryValue);\n initEditableElements(config);\n\n const restoredCount = applyDraft(loadDraft(storage, draftKey), config.editableSelector);\n\n const makeDraft = (): Draft => ({\n version: DRAFT_VERSION,\n page: window.location.pathname,\n url: window.location.href,\n updatedAt: new Date().toISOString(),\n changes: collectDraftChanges(config.editableSelector),\n });\n\n const renderReviewList = () => {\n if (!reviewList) return;\n\n if (currentDraftChanges.length === 0) {\n reviewList.innerHTML = '<p class=\"cem-review-empty\">No changes yet.</p>';\n return;\n }\n\n reviewList.innerHTML = currentDraftChanges\n .map(\n (change, index) => `\n <div class=\"cem-review-item\">\n <div class=\"cem-review-path\">${escapeHtml(change.path)}</div>\n <div class=\"cem-review-diff\"><del>${escapeHtml(change.original)}</del><ins>${escapeHtml(change.new)}</ins></div>\n <button class=\"cem-revert-btn\" type=\"button\" data-index=\"${index}\">Revert</button>\n </div>\n `,\n )\n .join(\"\");\n };\n\n const renderStatus = (count: number, message?: string) => {\n if (copyButton) copyButton.textContent = count > 0 ? `📋 Copy Changes (${count})` : \"📋 Copy Changes\";\n if (openLinkButton) {\n openLinkButton.hidden = !activeLink;\n openLinkButton.textContent = activeLink ? `Open ${activeLink.hostname || \"link\"}` : \"Open link\";\n }\n if (status) {\n status.textContent =\n message ||\n `Auto-saved ${formatTime()} • ${count} change${count === 1 ? \"\" : \"s\"} • Links: edit text or Ctrl/⌘-click to open`;\n }\n };\n\n const updateUi = (message?: string) => {\n renderStatus(collectChanges(config.editableSelector).length, message);\n };\n\n const persistDraft = () => {\n const draft = makeDraft();\n currentDraftChanges = draft.changes;\n const saved = config.autoSave !== false && saveDraft(storage, draftKey, draft);\n renderStatus(draft.changes.length, saved || config.autoSave === false ? undefined : \"⚠️ Auto-save unavailable — use Download backup\");\n if (reviewOpen) renderReviewList();\n return draft;\n };\n\n let saveTimeout: number | null = null;\n\n const flushDraftSave = () => {\n if (saveTimeout !== null) {\n window.clearTimeout(saveTimeout);\n saveTimeout = null;\n }\n return persistDraft();\n };\n\n const scheduleDraftSave = () => {\n if (saveTimeout !== null) window.clearTimeout(saveTimeout);\n saveTimeout = window.setTimeout(() => {\n saveTimeout = null;\n persistDraft();\n }, AUTOSAVE_DEBOUNCE_MS);\n };\n\n const finishEditing = () => {\n const activeElement = document.activeElement;\n if (!(activeElement instanceof HTMLElement)) return false;\n if (!activeElement.isContentEditable && activeElement.contentEditable !== \"true\") return false;\n\n activeElement.blur();\n window.getSelection()?.removeAllRanges();\n flushDraftSave();\n return true;\n };\n\n const handleEditingKeydown = (event: KeyboardEvent) => {\n if (event.key === \"Escape\") {\n if (!finishEditing()) return;\n event.preventDefault();\n event.stopPropagation();\n return;\n }\n\n if (event.key === \"Enter\" && !event.shiftKey) {\n const target = event.target;\n if (\n target instanceof HTMLElement &&\n SINGLE_LINE_TAGS.has(target.tagName) &&\n (target.isContentEditable || target.contentEditable === \"true\")\n ) {\n event.preventDefault();\n finishEditing();\n }\n }\n };\n\n const handleDocumentClick = (event: MouseEvent) => {\n const target = event.target;\n if (!(target instanceof Element)) return;\n\n const anchor = target.closest<HTMLAnchorElement>(\"a[href]\");\n if (anchor && !anchor.closest(\".cem-panel\") && !anchor.closest(\".cem-edit-banner\")) {\n activeLink = anchor;\n updateUi(\"Link selected — edit the text, use Open link, or Ctrl/⌘-click to visit it\");\n\n if (event.metaKey || event.ctrlKey || event.altKey) {\n flushDraftSave();\n return;\n }\n\n event.preventDefault();\n return;\n }\n\n const button = target.closest(\"button\");\n if (button && !button.closest(\".cem-panel\") && !button.closest(\".cem-edit-banner\")) {\n event.preventDefault();\n event.stopImmediatePropagation();\n }\n };\n\n const handleFocusIn = (event: FocusEvent) => {\n const target = event.target;\n if (!(target instanceof Element)) return;\n\n activeLink = target.closest<HTMLAnchorElement>(\"a[href]\");\n updateUi(activeLink ? \"Link selected — edit the text or use Open link to navigate\" : undefined);\n };\n\n const handleInput = (event: Event) => {\n if (event.target instanceof HTMLElement) markChanged(event.target);\n scheduleDraftSave();\n };\n const handlePageHide = () => flushDraftSave();\n const handleVisibilityChange = () => {\n if (document.visibilityState === \"hidden\") flushDraftSave();\n };\n\n document.addEventListener(\"keydown\", handleEditingKeydown);\n document.addEventListener(\"click\", handleDocumentClick, true);\n document.addEventListener(\"focusin\", handleFocusIn);\n document.addEventListener(\"input\", handleInput);\n window.addEventListener(\"pagehide\", handlePageHide);\n document.addEventListener(\"visibilitychange\", handleVisibilityChange);\n\n reviewButton?.addEventListener(\"click\", () => {\n reviewOpen = !reviewOpen;\n if (reviewList) reviewList.hidden = !reviewOpen;\n reviewButton.textContent = reviewOpen ? \"Hide review\" : \"Review\";\n reviewButton.setAttribute(\"aria-expanded\", String(reviewOpen));\n if (reviewOpen) renderReviewList();\n });\n\n reviewList?.addEventListener(\"click\", (event) => {\n const target = event.target;\n if (!(target instanceof HTMLElement)) return;\n\n const revertButton = target.closest<HTMLButtonElement>(\".cem-revert-btn\");\n if (!revertButton) return;\n\n const change = currentDraftChanges[Number(revertButton.dataset.index)];\n if (!change) return;\n\n const el = findEditableByKey(config.editableSelector, change.key);\n if (el) el.textContent = change.original;\n\n flushDraftSave();\n });\n\n copyButton?.addEventListener(\"click\", () => {\n const draft = flushDraftSave();\n const payload = buildPayload(draft.changes);\n options.onCopy?.(payload);\n\n const copyPayload = options.mapPayload ? options.mapPayload(payload) : payload;\n const json = JSON.stringify(copyPayload, null, 2);\n const label = `✓ Copied ${draft.changes.length} change${draft.changes.length !== 1 ? \"s\" : \"\"} to clipboard`;\n\n if (navigator.clipboard?.writeText) {\n navigator.clipboard.writeText(json).then(() => showToast(label)).catch(() => fallbackCopy(json, label));\n } else {\n fallbackCopy(json, label);\n }\n });\n\n downloadButton?.addEventListener(\"click\", () => {\n const draft = flushDraftSave();\n const payload = buildPayload(draft.changes);\n const date = new Date().toISOString().slice(0, 10);\n downloadText(`edit-mode-${window.location.hostname}-${date}.json`, JSON.stringify(payload, null, 2));\n showToast(\"✓ Backup downloaded\");\n });\n\n openLinkButton?.addEventListener(\"click\", () => {\n if (!activeLink?.href) return;\n\n flushDraftSave();\n window.location.href = activeLink.href;\n });\n\n exitButton.addEventListener(\"click\", () => {\n flushDraftSave();\n window.sessionStorage.removeItem(config.sessionKey);\n const url = new URL(window.location.href);\n url.searchParams.delete(config.queryParam);\n window.location.href = url.toString();\n });\n\n persistDraft();\n if (restoredCount > 0) updateUi(`Restored ${restoredCount} saved edit${restoredCount === 1 ? \"\" : \"s\"} • Auto-save on`);\n\n return {\n active: true,\n getChanges: () => collectChanges(config.editableSelector),\n destroy: () => {\n flushDraftSave();\n document.removeEventListener(\"keydown\", handleEditingKeydown);\n document.removeEventListener(\"click\", handleDocumentClick, true);\n document.removeEventListener(\"focusin\", handleFocusIn);\n document.removeEventListener(\"input\", handleInput);\n window.removeEventListener(\"pagehide\", handlePageHide);\n document.removeEventListener(\"visibilitychange\", handleVisibilityChange);\n banner.remove();\n panel.remove();\n style.remove();\n document.querySelectorAll<HTMLElement>(config.editableSelector).forEach((el) => {\n if (el.dataset.editOriginal !== undefined) {\n el.contentEditable = \"false\";\n el.classList.remove(\"cem-changed\");\n delete el.dataset.editOriginal;\n delete el.dataset.editKey;\n }\n });\n window.__COLLIDE_EDIT_MODE_ACTIVE = false;\n },\n };\n}\n"],"mappings":"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","names":["browser_exports","__export","initClientEditMode","DEFAULT_EDITABLE_SELECTOR","DEFAULT_SKIP_SELECTOR","SKIP_CHILD_TAGS","SINGLE_LINE_TAGS","DRAFT_VERSION","AUTOSAVE_DEBOUNCE_MS","defaults","hasDom","shouldEnable","options","queryValue","addStyles","accentColour","style","showToast","message","toast","fallbackCopy","text","label","textarea","getStorage","testKey","getDraftKey","config","escapeHtml","char","isEditableElement","el","skipSelector","children","child","getElementKey","parts","current","parentElement","currentTag","index","getEditPath","context","section","heading","tag","snippet","rewriteLinks","queryParam","anchor","href","url","hash","buildPayload","changes","loadDraft","storage","key","raw","draft","saveDraft","downloadText","filename","blob","formatTime","date","initEditableElements","findEditableByKey","editableSelector","elements","applyDraft","edits","change","restored","saved","markChanged","original","collectDraftChanges","changed","collectChanges","_key","initClientEditMode","draftKey","banner","exitButton","panel","copyButton","reviewButton","downloadButton","openLinkButton","reviewList","status","activeLink","reviewOpen","currentDraftChanges","restoredCount","makeDraft","renderReviewList","renderStatus","count","updateUi","persistDraft","saveTimeout","flushDraftSave","scheduleDraftSave","finishEditing","activeElement","handleEditingKeydown","event","target","handleDocumentClick","button","handleFocusIn","handleInput","handlePageHide","handleVisibilityChange","revertButton","payload","copyPayload","json","api","initClientEditMode","script","options"]}
package/dist/index.cjs CHANGED
@@ -26,7 +26,24 @@ module.exports = __toCommonJS(src_exports);
26
26
  var DEFAULT_EDITABLE_SELECTOR = "h1,h2,h3,h4,h5,h6,p,li,blockquote,figcaption,label,legend,dt,dd,th,td,a,button";
27
27
  var DEFAULT_SKIP_SELECTOR = "[data-no-edit],[data-edit-mode-skip],form,input,textarea,select,option,script,style,svg,canvas,iframe,.cem-edit-banner,.cem-panel,.cem-toast";
28
28
  var SKIP_CHILD_TAGS = /* @__PURE__ */ new Set(["IMG", "PICTURE", "SVG", "VIDEO", "CANVAS", "IFRAME", "SCRIPT", "STYLE"]);
29
+ var SINGLE_LINE_TAGS = /* @__PURE__ */ new Set([
30
+ "H1",
31
+ "H2",
32
+ "H3",
33
+ "H4",
34
+ "H5",
35
+ "H6",
36
+ "BUTTON",
37
+ "LABEL",
38
+ "LEGEND",
39
+ "DT",
40
+ "TH",
41
+ "TD",
42
+ "FIGCAPTION",
43
+ "A"
44
+ ]);
29
45
  var DRAFT_VERSION = 1;
46
+ var AUTOSAVE_DEBOUNCE_MS = 400;
30
47
  var defaults = {
31
48
  queryParam: "edit",
32
49
  queryValue: "true",
@@ -58,14 +75,25 @@ function addStyles(accentColour) {
58
75
  "[contenteditable=true]{cursor:text!important}",
59
76
  "[contenteditable=true]:hover{outline:2px dashed rgba(59,130,246,.5)!important;outline-offset:2px}",
60
77
  "[contenteditable=true]:focus{outline:2px solid #3b82f6!important;outline-offset:2px;background:rgba(59,130,246,.04)}",
78
+ "[contenteditable=true].cem-changed{box-shadow:inset 3px 0 0 #10b981}",
61
79
  `.cem-panel{position:fixed;right:16px;bottom:16px;z-index:2147483640;background:#fff;color:#111827;border:1px solid #e5e7eb;border-radius:12px;box-shadow:0 8px 30px rgba(0,0,0,.22);padding:10px;display:grid;gap:8px;width:min(320px,calc(100vw - 32px));font:13px/1.4 system-ui,-apple-system,sans-serif}`,
62
80
  ".cem-panel-actions{display:flex;gap:8px;flex-wrap:wrap}",
63
- `.cem-copy-btn,.cem-download-btn,.cem-open-link-btn{border:0;border-radius:8px;padding:9px 12px;font:600 13px system-ui,-apple-system,sans-serif;cursor:pointer;white-space:nowrap}`,
81
+ `.cem-copy-btn,.cem-review-btn,.cem-download-btn,.cem-open-link-btn{border:0;border-radius:8px;padding:9px 12px;font:600 13px system-ui,-apple-system,sans-serif;cursor:pointer;white-space:nowrap}`,
64
82
  `.cem-copy-btn{background:${accentColour};color:#fff;flex:1}`,
65
- ".cem-download-btn,.cem-open-link-btn{background:#f3f4f6;color:#111827;border:1px solid #e5e7eb}",
83
+ ".cem-review-btn,.cem-download-btn,.cem-open-link-btn{background:#f3f4f6;color:#111827;border:1px solid #e5e7eb}",
66
84
  ".cem-open-link-btn[hidden]{display:none}",
67
- ".cem-copy-btn:hover,.cem-download-btn:hover,.cem-open-link-btn:hover{filter:brightness(.97)}",
85
+ ".cem-copy-btn:hover,.cem-review-btn:hover,.cem-download-btn:hover,.cem-open-link-btn:hover{filter:brightness(.97)}",
68
86
  ".cem-status{color:#4b5563;font-size:12px;min-height:17px}",
87
+ ".cem-review-list{max-height:220px;overflow-y:auto;display:grid;gap:6px;border-top:1px solid #e5e7eb;padding-top:8px}",
88
+ ".cem-review-list[hidden]{display:none}",
89
+ ".cem-review-empty{color:#6b7280;font-size:12px;margin:0;padding:4px 0}",
90
+ ".cem-review-item{display:grid;gap:4px;padding:8px;background:#f9fafb;border-radius:8px;border:1px solid #e5e7eb}",
91
+ ".cem-review-path{font-size:11px;color:#6b7280;font-weight:600;text-transform:uppercase;letter-spacing:.02em}",
92
+ ".cem-review-diff{font-size:12px;word-break:break-word}",
93
+ ".cem-review-diff del{color:#b91c1c;text-decoration:line-through;opacity:.75;display:block}",
94
+ ".cem-review-diff ins{color:#065f46;text-decoration:none;display:block}",
95
+ ".cem-revert-btn{justify-self:start;background:transparent;border:1px solid #e5e7eb;border-radius:6px;padding:3px 8px;font:12px system-ui,-apple-system,sans-serif;cursor:pointer;color:#374151}",
96
+ ".cem-revert-btn:hover{background:#fff}",
69
97
  ".cem-toast{position:fixed;bottom:116px;right:16px;z-index:2147483641;background:#065f46;color:#fff;padding:10px 18px;border-radius:8px;font:14px system-ui,-apple-system,sans-serif;box-shadow:0 2px 12px rgba(0,0,0,.3);transition:opacity .3s}",
70
98
  "@media(max-width:520px){.cem-edit-banner{justify-content:flex-start;text-align:left}.cem-panel{left:16px;right:16px;width:auto}}"
71
99
  ].join("\n");
@@ -115,6 +143,22 @@ function getDraftKey(config) {
115
143
  const base = config.storageKey || `${config.sessionKey}:draft`;
116
144
  return `${base}:${window.location.origin}${window.location.pathname}`;
117
145
  }
146
+ function escapeHtml(text) {
147
+ return text.replace(/[&<>"']/g, (char) => {
148
+ switch (char) {
149
+ case "&":
150
+ return "&amp;";
151
+ case "<":
152
+ return "&lt;";
153
+ case ">":
154
+ return "&gt;";
155
+ case '"':
156
+ return "&quot;";
157
+ default:
158
+ return "&#39;";
159
+ }
160
+ });
161
+ }
118
162
  function isEditableElement(el, skipSelector) {
119
163
  if (el.closest(skipSelector)) return false;
120
164
  const text = el.textContent?.trim() ?? "";
@@ -235,12 +279,20 @@ function formatTime(date = /* @__PURE__ */ new Date()) {
235
279
  function initEditableElements(config) {
236
280
  document.querySelectorAll(config.editableSelector).forEach((el) => {
237
281
  if (!isEditableElement(el, config.skipSelector)) return;
282
+ if (el.closest("[data-edit-key]")) return;
238
283
  const text = el.textContent?.trim() ?? "";
239
284
  el.contentEditable = "true";
240
285
  if (!el.dataset.editOriginal) el.dataset.editOriginal = text;
241
286
  if (!el.dataset.editKey) el.dataset.editKey = getElementKey(el);
242
287
  });
243
288
  }
289
+ function findEditableByKey(editableSelector, key) {
290
+ const elements = document.querySelectorAll(editableSelector);
291
+ for (const el of Array.from(elements)) {
292
+ if (el.dataset.editKey === key) return el;
293
+ }
294
+ return null;
295
+ }
244
296
  function applyDraft(draft, editableSelector) {
245
297
  if (!draft) return 0;
246
298
  const edits = new Map(draft.changes.map((change) => [change.key, change]));
@@ -256,6 +308,12 @@ function applyDraft(draft, editableSelector) {
256
308
  });
257
309
  return restored;
258
310
  }
311
+ function markChanged(el) {
312
+ const original = el.dataset.editOriginal;
313
+ if (original === void 0) return;
314
+ const current = el.textContent?.trim() ?? "";
315
+ el.classList.toggle("cem-changed", original !== current);
316
+ }
259
317
  function collectDraftChanges(editableSelector) {
260
318
  const changes = [];
261
319
  document.querySelectorAll(editableSelector).forEach((el) => {
@@ -264,7 +322,9 @@ function collectDraftChanges(editableSelector) {
264
322
  const key = el.dataset.editKey;
265
323
  if (original === void 0 || !key) return;
266
324
  const current = el.textContent?.trim() ?? "";
267
- if (original !== current) {
325
+ const changed = original !== current;
326
+ el.classList.toggle("cem-changed", changed);
327
+ if (changed) {
268
328
  changes.push({
269
329
  key,
270
330
  path: getEditPath(el),
@@ -309,17 +369,23 @@ function initClientEditMode(options = {}) {
309
369
  panel.innerHTML = `
310
370
  <div class="cem-panel-actions">
311
371
  <button class="cem-copy-btn" type="button">\u{1F4CB} Copy Changes</button>
372
+ <button class="cem-review-btn" type="button" aria-expanded="false">Review</button>
312
373
  <button class="cem-download-btn" type="button">Download backup</button>
313
374
  <button class="cem-open-link-btn" type="button" hidden>Open link</button>
314
375
  </div>
376
+ <div class="cem-review-list" hidden></div>
315
377
  <div class="cem-status" aria-live="polite">Auto-save ready</div>
316
378
  `;
317
379
  document.body.appendChild(panel);
318
380
  const copyButton = panel.querySelector(".cem-copy-btn");
381
+ const reviewButton = panel.querySelector(".cem-review-btn");
319
382
  const downloadButton = panel.querySelector(".cem-download-btn");
320
383
  const openLinkButton = panel.querySelector(".cem-open-link-btn");
384
+ const reviewList = panel.querySelector(".cem-review-list");
321
385
  const status = panel.querySelector(".cem-status");
322
386
  let activeLink = null;
387
+ let reviewOpen = false;
388
+ let currentDraftChanges = [];
323
389
  document.querySelectorAll("[data-reveal]").forEach((el) => el.classList.add("is-visible"));
324
390
  rewriteLinks(config.queryParam, config.queryValue);
325
391
  initEditableElements(config);
@@ -331,8 +397,23 @@ function initClientEditMode(options = {}) {
331
397
  updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
332
398
  changes: collectDraftChanges(config.editableSelector)
333
399
  });
334
- const updateUi = (message) => {
335
- const count = collectChanges(config.editableSelector).length;
400
+ const renderReviewList = () => {
401
+ if (!reviewList) return;
402
+ if (currentDraftChanges.length === 0) {
403
+ reviewList.innerHTML = '<p class="cem-review-empty">No changes yet.</p>';
404
+ return;
405
+ }
406
+ reviewList.innerHTML = currentDraftChanges.map(
407
+ (change, index) => `
408
+ <div class="cem-review-item">
409
+ <div class="cem-review-path">${escapeHtml(change.path)}</div>
410
+ <div class="cem-review-diff"><del>${escapeHtml(change.original)}</del><ins>${escapeHtml(change.new)}</ins></div>
411
+ <button class="cem-revert-btn" type="button" data-index="${index}">Revert</button>
412
+ </div>
413
+ `
414
+ ).join("");
415
+ };
416
+ const renderStatus = (count, message) => {
336
417
  if (copyButton) copyButton.textContent = count > 0 ? `\u{1F4CB} Copy Changes (${count})` : "\u{1F4CB} Copy Changes";
337
418
  if (openLinkButton) {
338
419
  openLinkButton.hidden = !activeLink;
@@ -342,26 +423,55 @@ function initClientEditMode(options = {}) {
342
423
  status.textContent = message || `Auto-saved ${formatTime()} \u2022 ${count} change${count === 1 ? "" : "s"} \u2022 Links: edit text or Ctrl/\u2318-click to open`;
343
424
  }
344
425
  };
426
+ const updateUi = (message) => {
427
+ renderStatus(collectChanges(config.editableSelector).length, message);
428
+ };
345
429
  const persistDraft = () => {
346
430
  const draft = makeDraft();
431
+ currentDraftChanges = draft.changes;
347
432
  const saved = config.autoSave !== false && saveDraft(storage, draftKey, draft);
348
- updateUi(saved || config.autoSave === false ? void 0 : "\u26A0\uFE0F Auto-save unavailable \u2014 use Download backup");
433
+ renderStatus(draft.changes.length, saved || config.autoSave === false ? void 0 : "\u26A0\uFE0F Auto-save unavailable \u2014 use Download backup");
434
+ if (reviewOpen) renderReviewList();
349
435
  return draft;
350
436
  };
437
+ let saveTimeout = null;
438
+ const flushDraftSave = () => {
439
+ if (saveTimeout !== null) {
440
+ window.clearTimeout(saveTimeout);
441
+ saveTimeout = null;
442
+ }
443
+ return persistDraft();
444
+ };
445
+ const scheduleDraftSave = () => {
446
+ if (saveTimeout !== null) window.clearTimeout(saveTimeout);
447
+ saveTimeout = window.setTimeout(() => {
448
+ saveTimeout = null;
449
+ persistDraft();
450
+ }, AUTOSAVE_DEBOUNCE_MS);
451
+ };
351
452
  const finishEditing = () => {
352
453
  const activeElement = document.activeElement;
353
454
  if (!(activeElement instanceof HTMLElement)) return false;
354
455
  if (!activeElement.isContentEditable && activeElement.contentEditable !== "true") return false;
355
456
  activeElement.blur();
356
457
  window.getSelection()?.removeAllRanges();
357
- persistDraft();
458
+ flushDraftSave();
358
459
  return true;
359
460
  };
360
- const finishEditingOnEscape = (event) => {
361
- if (event.key !== "Escape") return;
362
- if (!finishEditing()) return;
363
- event.preventDefault();
364
- event.stopPropagation();
461
+ const handleEditingKeydown = (event) => {
462
+ if (event.key === "Escape") {
463
+ if (!finishEditing()) return;
464
+ event.preventDefault();
465
+ event.stopPropagation();
466
+ return;
467
+ }
468
+ if (event.key === "Enter" && !event.shiftKey) {
469
+ const target = event.target;
470
+ if (target instanceof HTMLElement && SINGLE_LINE_TAGS.has(target.tagName) && (target.isContentEditable || target.contentEditable === "true")) {
471
+ event.preventDefault();
472
+ finishEditing();
473
+ }
474
+ }
365
475
  };
366
476
  const handleDocumentClick = (event) => {
367
477
  const target = event.target;
@@ -371,7 +481,7 @@ function initClientEditMode(options = {}) {
371
481
  activeLink = anchor;
372
482
  updateUi("Link selected \u2014 edit the text, use Open link, or Ctrl/\u2318-click to visit it");
373
483
  if (event.metaKey || event.ctrlKey || event.altKey) {
374
- persistDraft();
484
+ flushDraftSave();
375
485
  return;
376
486
  }
377
487
  event.preventDefault();
@@ -389,19 +499,40 @@ function initClientEditMode(options = {}) {
389
499
  activeLink = target.closest("a[href]");
390
500
  updateUi(activeLink ? "Link selected \u2014 edit the text or use Open link to navigate" : void 0);
391
501
  };
392
- const handleInput = () => persistDraft();
393
- const handlePageHide = () => persistDraft();
502
+ const handleInput = (event) => {
503
+ if (event.target instanceof HTMLElement) markChanged(event.target);
504
+ scheduleDraftSave();
505
+ };
506
+ const handlePageHide = () => flushDraftSave();
394
507
  const handleVisibilityChange = () => {
395
- if (document.visibilityState === "hidden") persistDraft();
508
+ if (document.visibilityState === "hidden") flushDraftSave();
396
509
  };
397
- document.addEventListener("keydown", finishEditingOnEscape);
510
+ document.addEventListener("keydown", handleEditingKeydown);
398
511
  document.addEventListener("click", handleDocumentClick, true);
399
512
  document.addEventListener("focusin", handleFocusIn);
400
513
  document.addEventListener("input", handleInput);
401
514
  window.addEventListener("pagehide", handlePageHide);
402
515
  document.addEventListener("visibilitychange", handleVisibilityChange);
516
+ reviewButton?.addEventListener("click", () => {
517
+ reviewOpen = !reviewOpen;
518
+ if (reviewList) reviewList.hidden = !reviewOpen;
519
+ reviewButton.textContent = reviewOpen ? "Hide review" : "Review";
520
+ reviewButton.setAttribute("aria-expanded", String(reviewOpen));
521
+ if (reviewOpen) renderReviewList();
522
+ });
523
+ reviewList?.addEventListener("click", (event) => {
524
+ const target = event.target;
525
+ if (!(target instanceof HTMLElement)) return;
526
+ const revertButton = target.closest(".cem-revert-btn");
527
+ if (!revertButton) return;
528
+ const change = currentDraftChanges[Number(revertButton.dataset.index)];
529
+ if (!change) return;
530
+ const el = findEditableByKey(config.editableSelector, change.key);
531
+ if (el) el.textContent = change.original;
532
+ flushDraftSave();
533
+ });
403
534
  copyButton?.addEventListener("click", () => {
404
- const draft = persistDraft();
535
+ const draft = flushDraftSave();
405
536
  const payload = buildPayload(draft.changes);
406
537
  options.onCopy?.(payload);
407
538
  const copyPayload = options.mapPayload ? options.mapPayload(payload) : payload;
@@ -414,7 +545,7 @@ function initClientEditMode(options = {}) {
414
545
  }
415
546
  });
416
547
  downloadButton?.addEventListener("click", () => {
417
- const draft = persistDraft();
548
+ const draft = flushDraftSave();
418
549
  const payload = buildPayload(draft.changes);
419
550
  const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
420
551
  downloadText(`edit-mode-${window.location.hostname}-${date}.json`, JSON.stringify(payload, null, 2));
@@ -422,11 +553,11 @@ function initClientEditMode(options = {}) {
422
553
  });
423
554
  openLinkButton?.addEventListener("click", () => {
424
555
  if (!activeLink?.href) return;
425
- persistDraft();
556
+ flushDraftSave();
426
557
  window.location.href = activeLink.href;
427
558
  });
428
559
  exitButton.addEventListener("click", () => {
429
- persistDraft();
560
+ flushDraftSave();
430
561
  window.sessionStorage.removeItem(config.sessionKey);
431
562
  const url = new URL(window.location.href);
432
563
  url.searchParams.delete(config.queryParam);
@@ -438,8 +569,8 @@ function initClientEditMode(options = {}) {
438
569
  active: true,
439
570
  getChanges: () => collectChanges(config.editableSelector),
440
571
  destroy: () => {
441
- persistDraft();
442
- document.removeEventListener("keydown", finishEditingOnEscape);
572
+ flushDraftSave();
573
+ document.removeEventListener("keydown", handleEditingKeydown);
443
574
  document.removeEventListener("click", handleDocumentClick, true);
444
575
  document.removeEventListener("focusin", handleFocusIn);
445
576
  document.removeEventListener("input", handleInput);
@@ -451,6 +582,7 @@ function initClientEditMode(options = {}) {
451
582
  document.querySelectorAll(config.editableSelector).forEach((el) => {
452
583
  if (el.dataset.editOriginal !== void 0) {
453
584
  el.contentEditable = "false";
585
+ el.classList.remove("cem-changed");
454
586
  delete el.dataset.editOriginal;
455
587
  delete el.dataset.editKey;
456
588
  }