@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.
- package/dist/browser.global.js +11 -3
- package/dist/browser.global.js.map +1 -1
- package/dist/index.cjs +156 -24
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +156 -24
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/browser.global.js
CHANGED
|
@@ -1,10 +1,18 @@
|
|
|
1
|
-
"use strict";var CollideEditMode=(()=>{var
|
|
2
|
-
`),document.head.appendChild(e),e}function
|
|
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"&";case"<":return"<";case">":return">";case'"':return""";default:return"'"}})}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
|
|
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 \"&\";\n case \"<\":\n return \"<\";\n case \">\":\n return \">\";\n case '\"':\n return \""\";\n default:\n return \"'\";\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 "&";
|
|
151
|
+
case "<":
|
|
152
|
+
return "<";
|
|
153
|
+
case ">":
|
|
154
|
+
return ">";
|
|
155
|
+
case '"':
|
|
156
|
+
return """;
|
|
157
|
+
default:
|
|
158
|
+
return "'";
|
|
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
|
-
|
|
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
|
|
335
|
-
|
|
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
|
-
|
|
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
|
-
|
|
458
|
+
flushDraftSave();
|
|
358
459
|
return true;
|
|
359
460
|
};
|
|
360
|
-
const
|
|
361
|
-
if (event.key
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
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
|
-
|
|
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 = () =>
|
|
393
|
-
|
|
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")
|
|
508
|
+
if (document.visibilityState === "hidden") flushDraftSave();
|
|
396
509
|
};
|
|
397
|
-
document.addEventListener("keydown",
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
556
|
+
flushDraftSave();
|
|
426
557
|
window.location.href = activeLink.href;
|
|
427
558
|
});
|
|
428
559
|
exitButton.addEventListener("click", () => {
|
|
429
|
-
|
|
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
|
-
|
|
442
|
-
document.removeEventListener("keydown",
|
|
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
|
}
|