@collidecreatives/edit-mode 0.1.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/README.md ADDED
@@ -0,0 +1,114 @@
1
+ # @collidecreatives/edit-mode
2
+
3
+ Tiny browser edit mode overlay for collecting client copy changes.
4
+
5
+ It lets a client open a page with `?edit=true`, click visible text, make copy tweaks, then copy a structured JSON change list.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ npm install @collidecreatives/edit-mode
11
+ ```
12
+
13
+ ## Vite / Statamic / Laravel
14
+
15
+ ```js
16
+ // resources/js/site.js
17
+ import { initClientEditMode } from "@collidecreatives/edit-mode";
18
+
19
+ initClientEditMode({
20
+ brandName: "Client Edit Mode",
21
+ sessionKey: "mm-edit-mode",
22
+ });
23
+ ```
24
+
25
+ Open any page with:
26
+
27
+ ```txt
28
+ ?edit=true
29
+ ```
30
+
31
+ Example:
32
+
33
+ ```txt
34
+ https://example.com/about?edit=true
35
+ ```
36
+
37
+ ## Static browser script
38
+
39
+ Use the IIFE build if a site cannot bundle npm packages:
40
+
41
+ ```html
42
+ <script
43
+ src="/edit-mode.js"
44
+ data-brand-name="Client Edit Mode"
45
+ data-session-key="client-edit-mode"
46
+ ></script>
47
+ ```
48
+
49
+ The browser build auto-initialises. It only activates when `?edit=true` is present, when the session key is already set, or when you initialise manually with `enabled: true`.
50
+
51
+ ## Options
52
+
53
+ ```ts
54
+ initClientEditMode({
55
+ enabled: undefined,
56
+ queryParam: "edit",
57
+ queryValue: "true",
58
+ sessionKey: "collide-edit-mode",
59
+ brandName: "Edit Mode",
60
+ accentColour: "#1e40af",
61
+ editableSelector: "h1,h2,h3,h4,h5,h6,p,li,blockquote,figcaption,label,legend,dt,dd,th,td,a,button",
62
+ skipSelector: "[data-no-edit],[data-edit-mode-skip],form,input,textarea,select,option,script,style,svg,canvas,iframe",
63
+ onCopy: (payload) => console.log(payload),
64
+ });
65
+ ```
66
+
67
+ ## Opt out in templates
68
+
69
+ ```html
70
+ <nav data-no-edit>
71
+ ...
72
+ </nav>
73
+ ```
74
+
75
+ ## Payload
76
+
77
+ ```json
78
+ {
79
+ "site": "example.com",
80
+ "page": "/about",
81
+ "pageTitle": "About",
82
+ "url": "https://example.com/about?edit=true",
83
+ "timestamp": "2026-07-01T00:00:00.000Z",
84
+ "changes": [
85
+ {
86
+ "path": "#hero > h1: \"Old heading\"",
87
+ "tag": "H1",
88
+ "original": "Old heading",
89
+ "new": "New heading"
90
+ }
91
+ ]
92
+ }
93
+ ```
94
+
95
+ ## Build
96
+
97
+ ```bash
98
+ npm run build
99
+ ```
100
+
101
+ Outputs:
102
+
103
+ - `dist/index.js`
104
+ - `dist/index.cjs`
105
+ - `dist/index.d.ts`
106
+ - `dist/browser.global.js`
107
+
108
+ ## Publish
109
+
110
+ ```bash
111
+ npm publish --access public
112
+ ```
113
+
114
+ If publishing privately, configure npm/GitHub Packages first.
@@ -0,0 +1,3 @@
1
+ "use strict";var CollideEditMode=(()=>{var g=Object.defineProperty;var E=Object.getOwnPropertyDescriptor;var x=Object.getOwnPropertyNames;var C=Object.prototype.hasOwnProperty;var w=(t,e)=>{for(var n in e)g(t,n,{get:e[n],enumerable:!0})},S=(t,e,n,i)=>{if(e&&typeof e=="object"||typeof e=="function")for(let o of x(e))!C.call(t,o)&&o!==n&&g(t,o,{get:()=>e[o],enumerable:!(i=E(e,o))||i.enumerable});return t};var M=t=>S(g({},"__esModule",{value:!0}),t);var D={};w(D,{initClientEditMode:()=>m});var v="h1,h2,h3,h4,h5,h6,p,li,blockquote,figcaption,label,legend,dt,dd,th,td,a,button",L="[data-no-edit],[data-edit-mode-skip],form,input,textarea,select,option,script,style,svg,canvas,iframe,.cem-edit-banner,.cem-copy-btn,.cem-toast",k=new Set(["IMG","PICTURE","SVG","VIDEO","CANVAS","IFRAME","SCRIPT","STYLE"]),I={queryParam:"edit",queryValue:"true",sessionKey:"collide-edit-mode",brandName:"Edit Mode",accentColour:"#1e40af",editableSelector:v,skipSelector:L};function T(){return typeof window<"u"&&typeof document<"u"}function O(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 q(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;text-align:center;padding:7px 56px 7px 16px;font-size:13px;font-family:system-ui,-apple-system,sans-serif;line-height:1.4}`,".cem-edit-banner kbd{background:rgba(255,255,255,0.15);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,0.15);border:1px solid rgba(255,255,255,0.25);color:#fff;padding:3px 10px;border-radius:4px;font-size:12px;cursor:pointer;font-family:inherit;white-space:nowrap}",".cem-exit-btn:hover{background:rgba(255,255,255,0.25)}","[contenteditable=true]{cursor:text!important}","[contenteditable=true]:hover{outline:2px dashed rgba(59,130,246,0.5)!important;outline-offset:2px}","[contenteditable=true]:focus{outline:2px solid #3b82f6!important;outline-offset:2px;background:rgba(59,130,246,0.04)}",`.cem-copy-btn{position:fixed;bottom:80px;right:16px;z-index:2147483640;background:${t};color:#fff;border:none;border-radius:8px;padding:10px 18px;font-size:14px;font-weight:600;font-family:system-ui,-apple-system,sans-serif;cursor:pointer;box-shadow:0 2px 12px rgba(0,0,0,0.3);transition:transform .15s,opacity .15s;white-space:nowrap}`,".cem-copy-btn:hover{transform:scale(1.05)}",".cem-copy-btn:active{transform:scale(.97)}",".cem-toast{position:fixed;bottom:132px;right:16px;z-index:2147483641;background:#065f46;color:#fff;padding:10px 18px;border-radius:8px;font-size:14px;font-family:system-ui,-apple-system,sans-serif;box-shadow:0 2px 12px rgba(0,0,0,0.3);transition:opacity .3s}","@media(min-width:768px){.cem-copy-btn{bottom:24px;right:24px}.cem-toast{bottom:76px;right:24px}}"].join(`
2
+ `),document.head.appendChild(e),e}function y(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 h(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),y(e)}function P(t,e){if(t.closest(e)||!(t.textContent?.trim()??""))return!1;let i=Array.from(t.children);return!(i.length>0&&i.every(o=>k.has(o.tagName)))}function A(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 r=n.querySelector("h1,h2,h3");if(r?.textContent){e=r.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(),o=t.dataset.editOriginal||t.textContent?.trim()||"",l=o.slice(0,40)+(o.length>40?"...":"");return`${e?`${e} > `:""}${i}: "${l}"`}function N(t,e){document.querySelectorAll("a[href]").forEach(n=>{let i=n.getAttribute("href");if(!(!i||/^(https?:|mailto:|tel:|javascript:)/i.test(i)))try{let o=new URL(i,window.location.href);if(o.origin!==window.location.origin)return;o.searchParams.set(t,e??"1");let l=o.hash;o.hash="",n.setAttribute("href",`${o.pathname}${o.search}${l}`)}catch{}})}function m(t={}){if(!T())return{active:!1,getChanges:()=>[],destroy:()=>{}};let e={...I,...t};if(!O(e))return{active:!1,getChanges:()=>[],destroy:()=>{}};if(window.__COLLIDE_EDIT_MODE_ACTIVE)return{active:!0,getChanges:()=>u(e.editableSelector),destroy:()=>{}};window.__COLLIDE_EDIT_MODE_ACTIVE=!0,window.sessionStorage.setItem(e.sessionKey,"1");let n=q(e.accentColour),i=document.createElement("div");i.className="cem-edit-banner",i.innerHTML=`\u270F\uFE0F <strong>${e.brandName}</strong> \u2014 Click any text to edit. Press <kbd>Esc</kbd> to finish editing.`;let o=document.createElement("button");o.className="cem-exit-btn",o.textContent="\u2715 Exit",o.addEventListener("click",()=>{window.sessionStorage.removeItem(e.sessionKey);let a=new URL(window.location.href);a.searchParams.delete(e.queryParam),window.location.href=a.toString()}),i.appendChild(o),document.body.prepend(i),document.querySelectorAll("[data-reveal]").forEach(a=>a.classList.add("is-visible")),N(e.queryParam,e.queryValue),document.querySelectorAll(e.editableSelector).forEach(a=>{if(!P(a,e.skipSelector))return;let d=a.textContent?.trim()??"";a.contentEditable="true",a.dataset.editOriginal||(a.dataset.editOriginal=d)});let s=a=>{let d=a.target;if(!(d instanceof Element))return;let c=d.closest("button");c&&!c.classList.contains("cem-copy-btn")&&!c.closest(".cem-edit-banner")&&(a.preventDefault(),a.stopImmediatePropagation())};document.addEventListener("click",s,!0);let r=document.createElement("button");r.className="cem-copy-btn",document.body.appendChild(r);let p=()=>{let a=u(e.editableSelector).length;r.textContent=a>0?`\u{1F4CB} Copy Changes (${a})`:"\u{1F4CB} Copy Changes"};return document.addEventListener("input",p),p(),r.addEventListener("click",()=>{let a=u(e.editableSelector),d={site:window.location.hostname,page:window.location.pathname,pageTitle:document.title,url:window.location.href,timestamp:new Date().toISOString(),changes:a};t.onCopy?.(d);let c=t.mapPayload?t.mapPayload(d):d,f=JSON.stringify(c,null,2),b=`\u2713 Copied ${a.length} change${a.length!==1?"s":""} to clipboard`;navigator.clipboard?.writeText?navigator.clipboard.writeText(f).then(()=>y(b)).catch(()=>h(f,b)):h(f,b)}),{active:!0,getChanges:()=>u(e.editableSelector),destroy:()=>{document.removeEventListener("click",s,!0),document.removeEventListener("input",p),i.remove(),r.remove(),n.remove(),document.querySelectorAll(e.editableSelector).forEach(a=>{a.dataset.editOriginal!==void 0&&(a.contentEditable="false",delete a.dataset.editOriginal)}),window.__COLLIDE_EDIT_MODE_ACTIVE=!1}}}function u(t){let e=[];return document.querySelectorAll(t).forEach(n=>{if(!n.isContentEditable&&n.contentEditable!=="true")return;let i=n.dataset.editOriginal;if(i===void 0)return;let o=n.textContent?.trim()??"";i!==o&&e.push({path:A(n),tag:n.tagName,original:i,new:o})}),e}var _={initClientEditMode:m};if(typeof window<"u"){window.CollideEditMode=_;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),m(e)}return M(D);})();
3
+ //# sourceMappingURL=browser.global.js.map
@@ -0,0 +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-copy-btn,.cem-toast\";\n\nconst SKIP_CHILD_TAGS = new Set([\"IMG\", \"PICTURE\", \"SVG\", \"VIDEO\", \"CANVAS\", \"IFRAME\", \"SCRIPT\", \"STYLE\"]);\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};\n\nfunction hasDom() {\n return typeof window !== \"undefined\" && typeof document !== \"undefined\";\n}\n\nfunction shouldEnable(options: Required<Omit<EditModeOptions, \"enabled\" | \"onCopy\" | \"mapPayload\">> & Pick<EditModeOptions, \"enabled\">) {\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;text-align:center;padding:7px 56px 7px 16px;font-size:13px;font-family:system-ui,-apple-system,sans-serif;line-height:1.4}`,\n \".cem-edit-banner kbd{background:rgba(255,255,255,0.15);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,0.15);border:1px solid rgba(255,255,255,0.25);color:#fff;padding:3px 10px;border-radius:4px;font-size:12px;cursor:pointer;font-family:inherit;white-space:nowrap}\",\n \".cem-exit-btn:hover{background:rgba(255,255,255,0.25)}\",\n \"[contenteditable=true]{cursor:text!important}\",\n \"[contenteditable=true]:hover{outline:2px dashed rgba(59,130,246,0.5)!important;outline-offset:2px}\",\n \"[contenteditable=true]:focus{outline:2px solid #3b82f6!important;outline-offset:2px;background:rgba(59,130,246,0.04)}\",\n `.cem-copy-btn{position:fixed;bottom:80px;right:16px;z-index:2147483640;background:${accentColour};color:#fff;border:none;border-radius:8px;padding:10px 18px;font-size:14px;font-weight:600;font-family:system-ui,-apple-system,sans-serif;cursor:pointer;box-shadow:0 2px 12px rgba(0,0,0,0.3);transition:transform .15s,opacity .15s;white-space:nowrap}`,\n \".cem-copy-btn:hover{transform:scale(1.05)}\",\n \".cem-copy-btn:active{transform:scale(.97)}\",\n \".cem-toast{position:fixed;bottom:132px;right:16px;z-index:2147483641;background:#065f46;color:#fff;padding:10px 18px;border-radius:8px;font-size:14px;font-family:system-ui,-apple-system,sans-serif;box-shadow:0 2px 12px rgba(0,0,0,0.3);transition:opacity .3s}\",\n \"@media(min-width:768px){.cem-copy-btn{bottom:24px;right:24px}.cem-toast{bottom:76px;right:24px}}\",\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 JSON remains visible via devtools and caller onCopy still runs.\n }\n document.body.removeChild(textarea);\n showToast(label);\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 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\nexport function initClientEditMode(options: EditModeOptions = {}): EditModeInstance {\n if (!hasDom()) {\n return { active: false, getChanges: () => [], destroy: () => undefined };\n }\n\n const 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 style = addStyles(config.accentColour);\n\n const banner = document.createElement(\"div\");\n banner.className = \"cem-edit-banner\";\n banner.innerHTML = `✏️ <strong>${config.brandName}</strong> — Click any text to edit. Press <kbd>Esc</kbd> to finish editing.`;\n\n const exitButton = document.createElement(\"button\");\n exitButton.className = \"cem-exit-btn\";\n exitButton.textContent = \"✕ Exit\";\n exitButton.addEventListener(\"click\", () => {\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 banner.appendChild(exitButton);\n document.body.prepend(banner);\n\n document.querySelectorAll(\"[data-reveal]\").forEach((el) => el.classList.add(\"is-visible\"));\n rewriteLinks(config.queryParam, config.queryValue);\n\n const initEditable = () => {\n document.querySelectorAll<HTMLElement>(config.editableSelector).forEach((el) => {\n if (!isEditableElement(el, config.skipSelector)) return;\n const text = el.textContent?.trim() ?? \"\";\n el.contentEditable = \"true\";\n if (!el.dataset.editOriginal) el.dataset.editOriginal = text;\n });\n };\n initEditable();\n\n const blockButtonClicks = (event: MouseEvent) => {\n const target = event.target;\n if (!(target instanceof Element)) return;\n const button = target.closest(\"button\");\n if (button && !button.classList.contains(\"cem-copy-btn\") && !button.closest(\".cem-edit-banner\")) {\n event.preventDefault();\n event.stopImmediatePropagation();\n }\n };\n document.addEventListener(\"click\", blockButtonClicks, true);\n\n const copyButton = document.createElement(\"button\");\n copyButton.className = \"cem-copy-btn\";\n document.body.appendChild(copyButton);\n\n const updateCounter = () => {\n const count = collectChanges(config.editableSelector).length;\n copyButton.textContent = count > 0 ? `📋 Copy Changes (${count})` : \"📋 Copy Changes\";\n };\n\n document.addEventListener(\"input\", updateCounter);\n updateCounter();\n\n copyButton.addEventListener(\"click\", () => {\n const changes = collectChanges(config.editableSelector);\n const payload: EditModePayload = {\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 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 ${changes.length} change${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 return {\n active: true,\n getChanges: () => collectChanges(config.editableSelector),\n destroy: () => {\n document.removeEventListener(\"click\", blockButtonClicks, true);\n document.removeEventListener(\"input\", updateCounter);\n banner.remove();\n copyButton.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 }\n });\n window.__COLLIDE_EDIT_MODE_ACTIVE = false;\n },\n };\n}\n\nfunction collectChanges(editableSelector: string): EditModeChange[] {\n const changes: EditModeChange[] = [];\n\n document.querySelectorAll<HTMLElement>(editableSelector).forEach((el) => {\n if (!el.isContentEditable && el.contentEditable !== \"true\") return;\n const original = el.dataset.editOriginal;\n if (original === undefined) return;\n\n const current = el.textContent?.trim() ?? \"\";\n if (original !== current) {\n changes.push({\n path: getEditPath(el),\n tag: el.tagName,\n original,\n new: current,\n });\n }\n });\n\n return changes;\n}\n"],"mappings":"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","names":["browser_exports","__export","initClientEditMode","DEFAULT_EDITABLE_SELECTOR","DEFAULT_SKIP_SELECTOR","SKIP_CHILD_TAGS","defaults","hasDom","shouldEnable","options","queryValue","addStyles","accentColour","style","showToast","message","toast","fallbackCopy","text","label","textarea","isEditableElement","el","skipSelector","children","child","getEditPath","context","current","section","heading","tag","snippet","rewriteLinks","queryParam","anchor","href","url","hash","initClientEditMode","config","collectChanges","banner","exitButton","blockButtonClicks","event","target","button","copyButton","updateCounter","count","changes","payload","copyPayload","json","editableSelector","original","api","initClientEditMode","script","options"]}
package/dist/index.cjs ADDED
@@ -0,0 +1,268 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var src_exports = {};
22
+ __export(src_exports, {
23
+ initClientEditMode: () => initClientEditMode
24
+ });
25
+ module.exports = __toCommonJS(src_exports);
26
+ var DEFAULT_EDITABLE_SELECTOR = "h1,h2,h3,h4,h5,h6,p,li,blockquote,figcaption,label,legend,dt,dd,th,td,a,button";
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-copy-btn,.cem-toast";
28
+ var SKIP_CHILD_TAGS = /* @__PURE__ */ new Set(["IMG", "PICTURE", "SVG", "VIDEO", "CANVAS", "IFRAME", "SCRIPT", "STYLE"]);
29
+ var defaults = {
30
+ queryParam: "edit",
31
+ queryValue: "true",
32
+ sessionKey: "collide-edit-mode",
33
+ brandName: "Edit Mode",
34
+ accentColour: "#1e40af",
35
+ editableSelector: DEFAULT_EDITABLE_SELECTOR,
36
+ skipSelector: DEFAULT_SKIP_SELECTOR
37
+ };
38
+ function hasDom() {
39
+ return typeof window !== "undefined" && typeof document !== "undefined";
40
+ }
41
+ function shouldEnable(options) {
42
+ if (options.enabled !== void 0) return options.enabled;
43
+ const url = new URL(window.location.href);
44
+ const queryValue = url.searchParams.get(options.queryParam);
45
+ const queryMatches = options.queryValue === null ? queryValue !== null : queryValue === options.queryValue;
46
+ return queryMatches || window.sessionStorage.getItem(options.sessionKey) === "1";
47
+ }
48
+ function addStyles(accentColour) {
49
+ const style = document.createElement("style");
50
+ style.dataset.editModeStyle = "true";
51
+ style.textContent = [
52
+ `.cem-edit-banner{position:fixed;top:0;left:0;right:0;z-index:2147483640;background:${accentColour};color:#fff;text-align:center;padding:7px 56px 7px 16px;font-size:13px;font-family:system-ui,-apple-system,sans-serif;line-height:1.4}`,
53
+ ".cem-edit-banner kbd{background:rgba(255,255,255,0.15);padding:0 4px;border-radius:3px;font-size:11px}",
54
+ ".cem-exit-btn{position:absolute;right:8px;top:50%;transform:translateY(-50%);background:rgba(255,255,255,0.15);border:1px solid rgba(255,255,255,0.25);color:#fff;padding:3px 10px;border-radius:4px;font-size:12px;cursor:pointer;font-family:inherit;white-space:nowrap}",
55
+ ".cem-exit-btn:hover{background:rgba(255,255,255,0.25)}",
56
+ "[contenteditable=true]{cursor:text!important}",
57
+ "[contenteditable=true]:hover{outline:2px dashed rgba(59,130,246,0.5)!important;outline-offset:2px}",
58
+ "[contenteditable=true]:focus{outline:2px solid #3b82f6!important;outline-offset:2px;background:rgba(59,130,246,0.04)}",
59
+ `.cem-copy-btn{position:fixed;bottom:80px;right:16px;z-index:2147483640;background:${accentColour};color:#fff;border:none;border-radius:8px;padding:10px 18px;font-size:14px;font-weight:600;font-family:system-ui,-apple-system,sans-serif;cursor:pointer;box-shadow:0 2px 12px rgba(0,0,0,0.3);transition:transform .15s,opacity .15s;white-space:nowrap}`,
60
+ ".cem-copy-btn:hover{transform:scale(1.05)}",
61
+ ".cem-copy-btn:active{transform:scale(.97)}",
62
+ ".cem-toast{position:fixed;bottom:132px;right:16px;z-index:2147483641;background:#065f46;color:#fff;padding:10px 18px;border-radius:8px;font-size:14px;font-family:system-ui,-apple-system,sans-serif;box-shadow:0 2px 12px rgba(0,0,0,0.3);transition:opacity .3s}",
63
+ "@media(min-width:768px){.cem-copy-btn{bottom:24px;right:24px}.cem-toast{bottom:76px;right:24px}}"
64
+ ].join("\n");
65
+ document.head.appendChild(style);
66
+ return style;
67
+ }
68
+ function showToast(message) {
69
+ document.querySelector(".cem-toast")?.remove();
70
+ const toast = document.createElement("div");
71
+ toast.className = "cem-toast";
72
+ toast.textContent = message;
73
+ document.body.appendChild(toast);
74
+ window.setTimeout(() => {
75
+ toast.style.opacity = "0";
76
+ window.setTimeout(() => toast.parentNode?.removeChild(toast), 300);
77
+ }, 2500);
78
+ }
79
+ function fallbackCopy(text, label) {
80
+ const textarea = document.createElement("textarea");
81
+ textarea.value = text;
82
+ textarea.style.position = "fixed";
83
+ textarea.style.left = "-9999px";
84
+ document.body.appendChild(textarea);
85
+ textarea.select();
86
+ try {
87
+ document.execCommand("copy");
88
+ } catch {
89
+ }
90
+ document.body.removeChild(textarea);
91
+ showToast(label);
92
+ }
93
+ function isEditableElement(el, skipSelector) {
94
+ if (el.closest(skipSelector)) return false;
95
+ const text = el.textContent?.trim() ?? "";
96
+ if (!text) return false;
97
+ const children = Array.from(el.children);
98
+ if (children.length > 0 && children.every((child) => SKIP_CHILD_TAGS.has(child.tagName))) {
99
+ return false;
100
+ }
101
+ return true;
102
+ }
103
+ function getEditPath(el) {
104
+ let context = "";
105
+ let current = el.parentElement;
106
+ while (current && current !== document.body && current !== document.documentElement) {
107
+ if (current.id) {
108
+ context = `#${current.id}`;
109
+ break;
110
+ }
111
+ const section = current.getAttribute("data-section");
112
+ if (section) {
113
+ context = section;
114
+ break;
115
+ }
116
+ if (current.tagName === "SECTION") {
117
+ const heading = current.querySelector("h1,h2,h3");
118
+ if (heading?.textContent) {
119
+ context = heading.textContent.trim().slice(0, 40);
120
+ break;
121
+ }
122
+ }
123
+ if (["HEADER", "FOOTER", "MAIN", "NAV"].includes(current.tagName)) {
124
+ context = current.tagName.toLowerCase();
125
+ break;
126
+ }
127
+ current = current.parentElement;
128
+ }
129
+ const tag = el.tagName.toLowerCase();
130
+ const text = el.dataset.editOriginal || el.textContent?.trim() || "";
131
+ const snippet = text.slice(0, 40) + (text.length > 40 ? "..." : "");
132
+ return `${context ? `${context} > ` : ""}${tag}: "${snippet}"`;
133
+ }
134
+ function rewriteLinks(queryParam, queryValue) {
135
+ document.querySelectorAll("a[href]").forEach((anchor) => {
136
+ const href = anchor.getAttribute("href");
137
+ if (!href || /^(https?:|mailto:|tel:|javascript:)/i.test(href)) return;
138
+ try {
139
+ const url = new URL(href, window.location.href);
140
+ if (url.origin !== window.location.origin) return;
141
+ url.searchParams.set(queryParam, queryValue ?? "1");
142
+ const hash = url.hash;
143
+ url.hash = "";
144
+ anchor.setAttribute("href", `${url.pathname}${url.search}${hash}`);
145
+ } catch {
146
+ }
147
+ });
148
+ }
149
+ function initClientEditMode(options = {}) {
150
+ if (!hasDom()) {
151
+ return { active: false, getChanges: () => [], destroy: () => void 0 };
152
+ }
153
+ const config = { ...defaults, ...options };
154
+ if (!shouldEnable(config)) {
155
+ return { active: false, getChanges: () => [], destroy: () => void 0 };
156
+ }
157
+ if (window.__COLLIDE_EDIT_MODE_ACTIVE) {
158
+ return { active: true, getChanges: () => collectChanges(config.editableSelector), destroy: () => void 0 };
159
+ }
160
+ window.__COLLIDE_EDIT_MODE_ACTIVE = true;
161
+ window.sessionStorage.setItem(config.sessionKey, "1");
162
+ const style = addStyles(config.accentColour);
163
+ const banner = document.createElement("div");
164
+ banner.className = "cem-edit-banner";
165
+ banner.innerHTML = `\u270F\uFE0F <strong>${config.brandName}</strong> \u2014 Click any text to edit. Press <kbd>Esc</kbd> to finish editing.`;
166
+ const exitButton = document.createElement("button");
167
+ exitButton.className = "cem-exit-btn";
168
+ exitButton.textContent = "\u2715 Exit";
169
+ exitButton.addEventListener("click", () => {
170
+ window.sessionStorage.removeItem(config.sessionKey);
171
+ const url = new URL(window.location.href);
172
+ url.searchParams.delete(config.queryParam);
173
+ window.location.href = url.toString();
174
+ });
175
+ banner.appendChild(exitButton);
176
+ document.body.prepend(banner);
177
+ document.querySelectorAll("[data-reveal]").forEach((el) => el.classList.add("is-visible"));
178
+ rewriteLinks(config.queryParam, config.queryValue);
179
+ const initEditable = () => {
180
+ document.querySelectorAll(config.editableSelector).forEach((el) => {
181
+ if (!isEditableElement(el, config.skipSelector)) return;
182
+ const text = el.textContent?.trim() ?? "";
183
+ el.contentEditable = "true";
184
+ if (!el.dataset.editOriginal) el.dataset.editOriginal = text;
185
+ });
186
+ };
187
+ initEditable();
188
+ const blockButtonClicks = (event) => {
189
+ const target = event.target;
190
+ if (!(target instanceof Element)) return;
191
+ const button = target.closest("button");
192
+ if (button && !button.classList.contains("cem-copy-btn") && !button.closest(".cem-edit-banner")) {
193
+ event.preventDefault();
194
+ event.stopImmediatePropagation();
195
+ }
196
+ };
197
+ document.addEventListener("click", blockButtonClicks, true);
198
+ const copyButton = document.createElement("button");
199
+ copyButton.className = "cem-copy-btn";
200
+ document.body.appendChild(copyButton);
201
+ const updateCounter = () => {
202
+ const count = collectChanges(config.editableSelector).length;
203
+ copyButton.textContent = count > 0 ? `\u{1F4CB} Copy Changes (${count})` : "\u{1F4CB} Copy Changes";
204
+ };
205
+ document.addEventListener("input", updateCounter);
206
+ updateCounter();
207
+ copyButton.addEventListener("click", () => {
208
+ const changes = collectChanges(config.editableSelector);
209
+ const payload = {
210
+ site: window.location.hostname,
211
+ page: window.location.pathname,
212
+ pageTitle: document.title,
213
+ url: window.location.href,
214
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
215
+ changes
216
+ };
217
+ options.onCopy?.(payload);
218
+ const copyPayload = options.mapPayload ? options.mapPayload(payload) : payload;
219
+ const json = JSON.stringify(copyPayload, null, 2);
220
+ const label = `\u2713 Copied ${changes.length} change${changes.length !== 1 ? "s" : ""} to clipboard`;
221
+ if (navigator.clipboard?.writeText) {
222
+ navigator.clipboard.writeText(json).then(() => showToast(label)).catch(() => fallbackCopy(json, label));
223
+ } else {
224
+ fallbackCopy(json, label);
225
+ }
226
+ });
227
+ return {
228
+ active: true,
229
+ getChanges: () => collectChanges(config.editableSelector),
230
+ destroy: () => {
231
+ document.removeEventListener("click", blockButtonClicks, true);
232
+ document.removeEventListener("input", updateCounter);
233
+ banner.remove();
234
+ copyButton.remove();
235
+ style.remove();
236
+ document.querySelectorAll(config.editableSelector).forEach((el) => {
237
+ if (el.dataset.editOriginal !== void 0) {
238
+ el.contentEditable = "false";
239
+ delete el.dataset.editOriginal;
240
+ }
241
+ });
242
+ window.__COLLIDE_EDIT_MODE_ACTIVE = false;
243
+ }
244
+ };
245
+ }
246
+ function collectChanges(editableSelector) {
247
+ const changes = [];
248
+ document.querySelectorAll(editableSelector).forEach((el) => {
249
+ if (!el.isContentEditable && el.contentEditable !== "true") return;
250
+ const original = el.dataset.editOriginal;
251
+ if (original === void 0) return;
252
+ const current = el.textContent?.trim() ?? "";
253
+ if (original !== current) {
254
+ changes.push({
255
+ path: getEditPath(el),
256
+ tag: el.tagName,
257
+ original,
258
+ new: current
259
+ });
260
+ }
261
+ });
262
+ return changes;
263
+ }
264
+ // Annotate the CommonJS export names for ESM import in node:
265
+ 0 && (module.exports = {
266
+ initClientEditMode
267
+ });
268
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["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-copy-btn,.cem-toast\";\n\nconst SKIP_CHILD_TAGS = new Set([\"IMG\", \"PICTURE\", \"SVG\", \"VIDEO\", \"CANVAS\", \"IFRAME\", \"SCRIPT\", \"STYLE\"]);\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};\n\nfunction hasDom() {\n return typeof window !== \"undefined\" && typeof document !== \"undefined\";\n}\n\nfunction shouldEnable(options: Required<Omit<EditModeOptions, \"enabled\" | \"onCopy\" | \"mapPayload\">> & Pick<EditModeOptions, \"enabled\">) {\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;text-align:center;padding:7px 56px 7px 16px;font-size:13px;font-family:system-ui,-apple-system,sans-serif;line-height:1.4}`,\n \".cem-edit-banner kbd{background:rgba(255,255,255,0.15);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,0.15);border:1px solid rgba(255,255,255,0.25);color:#fff;padding:3px 10px;border-radius:4px;font-size:12px;cursor:pointer;font-family:inherit;white-space:nowrap}\",\n \".cem-exit-btn:hover{background:rgba(255,255,255,0.25)}\",\n \"[contenteditable=true]{cursor:text!important}\",\n \"[contenteditable=true]:hover{outline:2px dashed rgba(59,130,246,0.5)!important;outline-offset:2px}\",\n \"[contenteditable=true]:focus{outline:2px solid #3b82f6!important;outline-offset:2px;background:rgba(59,130,246,0.04)}\",\n `.cem-copy-btn{position:fixed;bottom:80px;right:16px;z-index:2147483640;background:${accentColour};color:#fff;border:none;border-radius:8px;padding:10px 18px;font-size:14px;font-weight:600;font-family:system-ui,-apple-system,sans-serif;cursor:pointer;box-shadow:0 2px 12px rgba(0,0,0,0.3);transition:transform .15s,opacity .15s;white-space:nowrap}`,\n \".cem-copy-btn:hover{transform:scale(1.05)}\",\n \".cem-copy-btn:active{transform:scale(.97)}\",\n \".cem-toast{position:fixed;bottom:132px;right:16px;z-index:2147483641;background:#065f46;color:#fff;padding:10px 18px;border-radius:8px;font-size:14px;font-family:system-ui,-apple-system,sans-serif;box-shadow:0 2px 12px rgba(0,0,0,0.3);transition:opacity .3s}\",\n \"@media(min-width:768px){.cem-copy-btn{bottom:24px;right:24px}.cem-toast{bottom:76px;right:24px}}\",\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 JSON remains visible via devtools and caller onCopy still runs.\n }\n document.body.removeChild(textarea);\n showToast(label);\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 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\nexport function initClientEditMode(options: EditModeOptions = {}): EditModeInstance {\n if (!hasDom()) {\n return { active: false, getChanges: () => [], destroy: () => undefined };\n }\n\n const 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 style = addStyles(config.accentColour);\n\n const banner = document.createElement(\"div\");\n banner.className = \"cem-edit-banner\";\n banner.innerHTML = `✏️ <strong>${config.brandName}</strong> — Click any text to edit. Press <kbd>Esc</kbd> to finish editing.`;\n\n const exitButton = document.createElement(\"button\");\n exitButton.className = \"cem-exit-btn\";\n exitButton.textContent = \"✕ Exit\";\n exitButton.addEventListener(\"click\", () => {\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 banner.appendChild(exitButton);\n document.body.prepend(banner);\n\n document.querySelectorAll(\"[data-reveal]\").forEach((el) => el.classList.add(\"is-visible\"));\n rewriteLinks(config.queryParam, config.queryValue);\n\n const initEditable = () => {\n document.querySelectorAll<HTMLElement>(config.editableSelector).forEach((el) => {\n if (!isEditableElement(el, config.skipSelector)) return;\n const text = el.textContent?.trim() ?? \"\";\n el.contentEditable = \"true\";\n if (!el.dataset.editOriginal) el.dataset.editOriginal = text;\n });\n };\n initEditable();\n\n const blockButtonClicks = (event: MouseEvent) => {\n const target = event.target;\n if (!(target instanceof Element)) return;\n const button = target.closest(\"button\");\n if (button && !button.classList.contains(\"cem-copy-btn\") && !button.closest(\".cem-edit-banner\")) {\n event.preventDefault();\n event.stopImmediatePropagation();\n }\n };\n document.addEventListener(\"click\", blockButtonClicks, true);\n\n const copyButton = document.createElement(\"button\");\n copyButton.className = \"cem-copy-btn\";\n document.body.appendChild(copyButton);\n\n const updateCounter = () => {\n const count = collectChanges(config.editableSelector).length;\n copyButton.textContent = count > 0 ? `📋 Copy Changes (${count})` : \"📋 Copy Changes\";\n };\n\n document.addEventListener(\"input\", updateCounter);\n updateCounter();\n\n copyButton.addEventListener(\"click\", () => {\n const changes = collectChanges(config.editableSelector);\n const payload: EditModePayload = {\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 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 ${changes.length} change${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 return {\n active: true,\n getChanges: () => collectChanges(config.editableSelector),\n destroy: () => {\n document.removeEventListener(\"click\", blockButtonClicks, true);\n document.removeEventListener(\"input\", updateCounter);\n banner.remove();\n copyButton.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 }\n });\n window.__COLLIDE_EDIT_MODE_ACTIVE = false;\n },\n };\n}\n\nfunction collectChanges(editableSelector: string): EditModeChange[] {\n const changes: EditModeChange[] = [];\n\n document.querySelectorAll<HTMLElement>(editableSelector).forEach((el) => {\n if (!el.isContentEditable && el.contentEditable !== \"true\") return;\n const original = el.dataset.editOriginal;\n if (original === undefined) return;\n\n const current = el.textContent?.trim() ?? \"\";\n if (original !== current) {\n changes.push({\n path: getEditPath(el),\n tag: el.tagName,\n original,\n new: current,\n });\n }\n });\n\n return changes;\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,53 @@
1
+ type EditModeChange = {
2
+ path: string;
3
+ tag: string;
4
+ original: string;
5
+ new: string;
6
+ };
7
+ type EditModePayload = {
8
+ site: string;
9
+ page: string;
10
+ pageTitle: string;
11
+ url: string;
12
+ timestamp: string;
13
+ changes: EditModeChange[];
14
+ };
15
+ type EditModeOptions = {
16
+ /** Force edit mode on/off. When omitted, query string or session storage controls activation. */
17
+ enabled?: boolean;
18
+ /** Query parameter that enables edit mode. */
19
+ queryParam?: string;
20
+ /** Query value that enables edit mode. Set to null to allow any value. */
21
+ queryValue?: string | null;
22
+ /** Session storage key used to keep edit mode active across pages. */
23
+ sessionKey?: string;
24
+ /** User-facing label in the banner. */
25
+ brandName?: string;
26
+ /** Main UI colour. */
27
+ accentColour?: string;
28
+ /** Text-ish elements that become editable. */
29
+ editableSelector?: string;
30
+ /** Elements that should never become editable. */
31
+ skipSelector?: string;
32
+ /** Called after the copy button builds the payload. */
33
+ onCopy?: (payload: EditModePayload) => void;
34
+ /** Custom payload mapper before clipboard copy. */
35
+ mapPayload?: (payload: EditModePayload) => unknown;
36
+ };
37
+ type EditModeInstance = {
38
+ active: boolean;
39
+ getChanges: () => EditModeChange[];
40
+ destroy: () => void;
41
+ };
42
+ declare global {
43
+ interface Window {
44
+ __COLLIDE_EDIT_MODE_ACTIVE?: boolean;
45
+ CollideEditMode?: {
46
+ initClientEditMode: (options?: EditModeOptions) => EditModeInstance;
47
+ };
48
+ }
49
+ }
50
+
51
+ declare function initClientEditMode(options?: EditModeOptions): EditModeInstance;
52
+
53
+ export { type EditModeChange, type EditModeInstance, type EditModeOptions, type EditModePayload, initClientEditMode };
@@ -0,0 +1,53 @@
1
+ type EditModeChange = {
2
+ path: string;
3
+ tag: string;
4
+ original: string;
5
+ new: string;
6
+ };
7
+ type EditModePayload = {
8
+ site: string;
9
+ page: string;
10
+ pageTitle: string;
11
+ url: string;
12
+ timestamp: string;
13
+ changes: EditModeChange[];
14
+ };
15
+ type EditModeOptions = {
16
+ /** Force edit mode on/off. When omitted, query string or session storage controls activation. */
17
+ enabled?: boolean;
18
+ /** Query parameter that enables edit mode. */
19
+ queryParam?: string;
20
+ /** Query value that enables edit mode. Set to null to allow any value. */
21
+ queryValue?: string | null;
22
+ /** Session storage key used to keep edit mode active across pages. */
23
+ sessionKey?: string;
24
+ /** User-facing label in the banner. */
25
+ brandName?: string;
26
+ /** Main UI colour. */
27
+ accentColour?: string;
28
+ /** Text-ish elements that become editable. */
29
+ editableSelector?: string;
30
+ /** Elements that should never become editable. */
31
+ skipSelector?: string;
32
+ /** Called after the copy button builds the payload. */
33
+ onCopy?: (payload: EditModePayload) => void;
34
+ /** Custom payload mapper before clipboard copy. */
35
+ mapPayload?: (payload: EditModePayload) => unknown;
36
+ };
37
+ type EditModeInstance = {
38
+ active: boolean;
39
+ getChanges: () => EditModeChange[];
40
+ destroy: () => void;
41
+ };
42
+ declare global {
43
+ interface Window {
44
+ __COLLIDE_EDIT_MODE_ACTIVE?: boolean;
45
+ CollideEditMode?: {
46
+ initClientEditMode: (options?: EditModeOptions) => EditModeInstance;
47
+ };
48
+ }
49
+ }
50
+
51
+ declare function initClientEditMode(options?: EditModeOptions): EditModeInstance;
52
+
53
+ export { type EditModeChange, type EditModeInstance, type EditModeOptions, type EditModePayload, initClientEditMode };
package/dist/index.js ADDED
@@ -0,0 +1,243 @@
1
+ // src/index.ts
2
+ var DEFAULT_EDITABLE_SELECTOR = "h1,h2,h3,h4,h5,h6,p,li,blockquote,figcaption,label,legend,dt,dd,th,td,a,button";
3
+ 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-copy-btn,.cem-toast";
4
+ var SKIP_CHILD_TAGS = /* @__PURE__ */ new Set(["IMG", "PICTURE", "SVG", "VIDEO", "CANVAS", "IFRAME", "SCRIPT", "STYLE"]);
5
+ var defaults = {
6
+ queryParam: "edit",
7
+ queryValue: "true",
8
+ sessionKey: "collide-edit-mode",
9
+ brandName: "Edit Mode",
10
+ accentColour: "#1e40af",
11
+ editableSelector: DEFAULT_EDITABLE_SELECTOR,
12
+ skipSelector: DEFAULT_SKIP_SELECTOR
13
+ };
14
+ function hasDom() {
15
+ return typeof window !== "undefined" && typeof document !== "undefined";
16
+ }
17
+ function shouldEnable(options) {
18
+ if (options.enabled !== void 0) return options.enabled;
19
+ const url = new URL(window.location.href);
20
+ const queryValue = url.searchParams.get(options.queryParam);
21
+ const queryMatches = options.queryValue === null ? queryValue !== null : queryValue === options.queryValue;
22
+ return queryMatches || window.sessionStorage.getItem(options.sessionKey) === "1";
23
+ }
24
+ function addStyles(accentColour) {
25
+ const style = document.createElement("style");
26
+ style.dataset.editModeStyle = "true";
27
+ style.textContent = [
28
+ `.cem-edit-banner{position:fixed;top:0;left:0;right:0;z-index:2147483640;background:${accentColour};color:#fff;text-align:center;padding:7px 56px 7px 16px;font-size:13px;font-family:system-ui,-apple-system,sans-serif;line-height:1.4}`,
29
+ ".cem-edit-banner kbd{background:rgba(255,255,255,0.15);padding:0 4px;border-radius:3px;font-size:11px}",
30
+ ".cem-exit-btn{position:absolute;right:8px;top:50%;transform:translateY(-50%);background:rgba(255,255,255,0.15);border:1px solid rgba(255,255,255,0.25);color:#fff;padding:3px 10px;border-radius:4px;font-size:12px;cursor:pointer;font-family:inherit;white-space:nowrap}",
31
+ ".cem-exit-btn:hover{background:rgba(255,255,255,0.25)}",
32
+ "[contenteditable=true]{cursor:text!important}",
33
+ "[contenteditable=true]:hover{outline:2px dashed rgba(59,130,246,0.5)!important;outline-offset:2px}",
34
+ "[contenteditable=true]:focus{outline:2px solid #3b82f6!important;outline-offset:2px;background:rgba(59,130,246,0.04)}",
35
+ `.cem-copy-btn{position:fixed;bottom:80px;right:16px;z-index:2147483640;background:${accentColour};color:#fff;border:none;border-radius:8px;padding:10px 18px;font-size:14px;font-weight:600;font-family:system-ui,-apple-system,sans-serif;cursor:pointer;box-shadow:0 2px 12px rgba(0,0,0,0.3);transition:transform .15s,opacity .15s;white-space:nowrap}`,
36
+ ".cem-copy-btn:hover{transform:scale(1.05)}",
37
+ ".cem-copy-btn:active{transform:scale(.97)}",
38
+ ".cem-toast{position:fixed;bottom:132px;right:16px;z-index:2147483641;background:#065f46;color:#fff;padding:10px 18px;border-radius:8px;font-size:14px;font-family:system-ui,-apple-system,sans-serif;box-shadow:0 2px 12px rgba(0,0,0,0.3);transition:opacity .3s}",
39
+ "@media(min-width:768px){.cem-copy-btn{bottom:24px;right:24px}.cem-toast{bottom:76px;right:24px}}"
40
+ ].join("\n");
41
+ document.head.appendChild(style);
42
+ return style;
43
+ }
44
+ function showToast(message) {
45
+ document.querySelector(".cem-toast")?.remove();
46
+ const toast = document.createElement("div");
47
+ toast.className = "cem-toast";
48
+ toast.textContent = message;
49
+ document.body.appendChild(toast);
50
+ window.setTimeout(() => {
51
+ toast.style.opacity = "0";
52
+ window.setTimeout(() => toast.parentNode?.removeChild(toast), 300);
53
+ }, 2500);
54
+ }
55
+ function fallbackCopy(text, label) {
56
+ const textarea = document.createElement("textarea");
57
+ textarea.value = text;
58
+ textarea.style.position = "fixed";
59
+ textarea.style.left = "-9999px";
60
+ document.body.appendChild(textarea);
61
+ textarea.select();
62
+ try {
63
+ document.execCommand("copy");
64
+ } catch {
65
+ }
66
+ document.body.removeChild(textarea);
67
+ showToast(label);
68
+ }
69
+ function isEditableElement(el, skipSelector) {
70
+ if (el.closest(skipSelector)) return false;
71
+ const text = el.textContent?.trim() ?? "";
72
+ if (!text) return false;
73
+ const children = Array.from(el.children);
74
+ if (children.length > 0 && children.every((child) => SKIP_CHILD_TAGS.has(child.tagName))) {
75
+ return false;
76
+ }
77
+ return true;
78
+ }
79
+ function getEditPath(el) {
80
+ let context = "";
81
+ let current = el.parentElement;
82
+ while (current && current !== document.body && current !== document.documentElement) {
83
+ if (current.id) {
84
+ context = `#${current.id}`;
85
+ break;
86
+ }
87
+ const section = current.getAttribute("data-section");
88
+ if (section) {
89
+ context = section;
90
+ break;
91
+ }
92
+ if (current.tagName === "SECTION") {
93
+ const heading = current.querySelector("h1,h2,h3");
94
+ if (heading?.textContent) {
95
+ context = heading.textContent.trim().slice(0, 40);
96
+ break;
97
+ }
98
+ }
99
+ if (["HEADER", "FOOTER", "MAIN", "NAV"].includes(current.tagName)) {
100
+ context = current.tagName.toLowerCase();
101
+ break;
102
+ }
103
+ current = current.parentElement;
104
+ }
105
+ const tag = el.tagName.toLowerCase();
106
+ const text = el.dataset.editOriginal || el.textContent?.trim() || "";
107
+ const snippet = text.slice(0, 40) + (text.length > 40 ? "..." : "");
108
+ return `${context ? `${context} > ` : ""}${tag}: "${snippet}"`;
109
+ }
110
+ function rewriteLinks(queryParam, queryValue) {
111
+ document.querySelectorAll("a[href]").forEach((anchor) => {
112
+ const href = anchor.getAttribute("href");
113
+ if (!href || /^(https?:|mailto:|tel:|javascript:)/i.test(href)) return;
114
+ try {
115
+ const url = new URL(href, window.location.href);
116
+ if (url.origin !== window.location.origin) return;
117
+ url.searchParams.set(queryParam, queryValue ?? "1");
118
+ const hash = url.hash;
119
+ url.hash = "";
120
+ anchor.setAttribute("href", `${url.pathname}${url.search}${hash}`);
121
+ } catch {
122
+ }
123
+ });
124
+ }
125
+ function initClientEditMode(options = {}) {
126
+ if (!hasDom()) {
127
+ return { active: false, getChanges: () => [], destroy: () => void 0 };
128
+ }
129
+ const config = { ...defaults, ...options };
130
+ if (!shouldEnable(config)) {
131
+ return { active: false, getChanges: () => [], destroy: () => void 0 };
132
+ }
133
+ if (window.__COLLIDE_EDIT_MODE_ACTIVE) {
134
+ return { active: true, getChanges: () => collectChanges(config.editableSelector), destroy: () => void 0 };
135
+ }
136
+ window.__COLLIDE_EDIT_MODE_ACTIVE = true;
137
+ window.sessionStorage.setItem(config.sessionKey, "1");
138
+ const style = addStyles(config.accentColour);
139
+ const banner = document.createElement("div");
140
+ banner.className = "cem-edit-banner";
141
+ banner.innerHTML = `\u270F\uFE0F <strong>${config.brandName}</strong> \u2014 Click any text to edit. Press <kbd>Esc</kbd> to finish editing.`;
142
+ const exitButton = document.createElement("button");
143
+ exitButton.className = "cem-exit-btn";
144
+ exitButton.textContent = "\u2715 Exit";
145
+ exitButton.addEventListener("click", () => {
146
+ window.sessionStorage.removeItem(config.sessionKey);
147
+ const url = new URL(window.location.href);
148
+ url.searchParams.delete(config.queryParam);
149
+ window.location.href = url.toString();
150
+ });
151
+ banner.appendChild(exitButton);
152
+ document.body.prepend(banner);
153
+ document.querySelectorAll("[data-reveal]").forEach((el) => el.classList.add("is-visible"));
154
+ rewriteLinks(config.queryParam, config.queryValue);
155
+ const initEditable = () => {
156
+ document.querySelectorAll(config.editableSelector).forEach((el) => {
157
+ if (!isEditableElement(el, config.skipSelector)) return;
158
+ const text = el.textContent?.trim() ?? "";
159
+ el.contentEditable = "true";
160
+ if (!el.dataset.editOriginal) el.dataset.editOriginal = text;
161
+ });
162
+ };
163
+ initEditable();
164
+ const blockButtonClicks = (event) => {
165
+ const target = event.target;
166
+ if (!(target instanceof Element)) return;
167
+ const button = target.closest("button");
168
+ if (button && !button.classList.contains("cem-copy-btn") && !button.closest(".cem-edit-banner")) {
169
+ event.preventDefault();
170
+ event.stopImmediatePropagation();
171
+ }
172
+ };
173
+ document.addEventListener("click", blockButtonClicks, true);
174
+ const copyButton = document.createElement("button");
175
+ copyButton.className = "cem-copy-btn";
176
+ document.body.appendChild(copyButton);
177
+ const updateCounter = () => {
178
+ const count = collectChanges(config.editableSelector).length;
179
+ copyButton.textContent = count > 0 ? `\u{1F4CB} Copy Changes (${count})` : "\u{1F4CB} Copy Changes";
180
+ };
181
+ document.addEventListener("input", updateCounter);
182
+ updateCounter();
183
+ copyButton.addEventListener("click", () => {
184
+ const changes = collectChanges(config.editableSelector);
185
+ const payload = {
186
+ site: window.location.hostname,
187
+ page: window.location.pathname,
188
+ pageTitle: document.title,
189
+ url: window.location.href,
190
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
191
+ changes
192
+ };
193
+ options.onCopy?.(payload);
194
+ const copyPayload = options.mapPayload ? options.mapPayload(payload) : payload;
195
+ const json = JSON.stringify(copyPayload, null, 2);
196
+ const label = `\u2713 Copied ${changes.length} change${changes.length !== 1 ? "s" : ""} to clipboard`;
197
+ if (navigator.clipboard?.writeText) {
198
+ navigator.clipboard.writeText(json).then(() => showToast(label)).catch(() => fallbackCopy(json, label));
199
+ } else {
200
+ fallbackCopy(json, label);
201
+ }
202
+ });
203
+ return {
204
+ active: true,
205
+ getChanges: () => collectChanges(config.editableSelector),
206
+ destroy: () => {
207
+ document.removeEventListener("click", blockButtonClicks, true);
208
+ document.removeEventListener("input", updateCounter);
209
+ banner.remove();
210
+ copyButton.remove();
211
+ style.remove();
212
+ document.querySelectorAll(config.editableSelector).forEach((el) => {
213
+ if (el.dataset.editOriginal !== void 0) {
214
+ el.contentEditable = "false";
215
+ delete el.dataset.editOriginal;
216
+ }
217
+ });
218
+ window.__COLLIDE_EDIT_MODE_ACTIVE = false;
219
+ }
220
+ };
221
+ }
222
+ function collectChanges(editableSelector) {
223
+ const changes = [];
224
+ document.querySelectorAll(editableSelector).forEach((el) => {
225
+ if (!el.isContentEditable && el.contentEditable !== "true") return;
226
+ const original = el.dataset.editOriginal;
227
+ if (original === void 0) return;
228
+ const current = el.textContent?.trim() ?? "";
229
+ if (original !== current) {
230
+ changes.push({
231
+ path: getEditPath(el),
232
+ tag: el.tagName,
233
+ original,
234
+ new: current
235
+ });
236
+ }
237
+ });
238
+ return changes;
239
+ }
240
+ export {
241
+ initClientEditMode
242
+ };
243
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["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-copy-btn,.cem-toast\";\n\nconst SKIP_CHILD_TAGS = new Set([\"IMG\", \"PICTURE\", \"SVG\", \"VIDEO\", \"CANVAS\", \"IFRAME\", \"SCRIPT\", \"STYLE\"]);\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};\n\nfunction hasDom() {\n return typeof window !== \"undefined\" && typeof document !== \"undefined\";\n}\n\nfunction shouldEnable(options: Required<Omit<EditModeOptions, \"enabled\" | \"onCopy\" | \"mapPayload\">> & Pick<EditModeOptions, \"enabled\">) {\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;text-align:center;padding:7px 56px 7px 16px;font-size:13px;font-family:system-ui,-apple-system,sans-serif;line-height:1.4}`,\n \".cem-edit-banner kbd{background:rgba(255,255,255,0.15);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,0.15);border:1px solid rgba(255,255,255,0.25);color:#fff;padding:3px 10px;border-radius:4px;font-size:12px;cursor:pointer;font-family:inherit;white-space:nowrap}\",\n \".cem-exit-btn:hover{background:rgba(255,255,255,0.25)}\",\n \"[contenteditable=true]{cursor:text!important}\",\n \"[contenteditable=true]:hover{outline:2px dashed rgba(59,130,246,0.5)!important;outline-offset:2px}\",\n \"[contenteditable=true]:focus{outline:2px solid #3b82f6!important;outline-offset:2px;background:rgba(59,130,246,0.04)}\",\n `.cem-copy-btn{position:fixed;bottom:80px;right:16px;z-index:2147483640;background:${accentColour};color:#fff;border:none;border-radius:8px;padding:10px 18px;font-size:14px;font-weight:600;font-family:system-ui,-apple-system,sans-serif;cursor:pointer;box-shadow:0 2px 12px rgba(0,0,0,0.3);transition:transform .15s,opacity .15s;white-space:nowrap}`,\n \".cem-copy-btn:hover{transform:scale(1.05)}\",\n \".cem-copy-btn:active{transform:scale(.97)}\",\n \".cem-toast{position:fixed;bottom:132px;right:16px;z-index:2147483641;background:#065f46;color:#fff;padding:10px 18px;border-radius:8px;font-size:14px;font-family:system-ui,-apple-system,sans-serif;box-shadow:0 2px 12px rgba(0,0,0,0.3);transition:opacity .3s}\",\n \"@media(min-width:768px){.cem-copy-btn{bottom:24px;right:24px}.cem-toast{bottom:76px;right:24px}}\",\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 JSON remains visible via devtools and caller onCopy still runs.\n }\n document.body.removeChild(textarea);\n showToast(label);\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 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\nexport function initClientEditMode(options: EditModeOptions = {}): EditModeInstance {\n if (!hasDom()) {\n return { active: false, getChanges: () => [], destroy: () => undefined };\n }\n\n const 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 style = addStyles(config.accentColour);\n\n const banner = document.createElement(\"div\");\n banner.className = \"cem-edit-banner\";\n banner.innerHTML = `✏️ <strong>${config.brandName}</strong> — Click any text to edit. Press <kbd>Esc</kbd> to finish editing.`;\n\n const exitButton = document.createElement(\"button\");\n exitButton.className = \"cem-exit-btn\";\n exitButton.textContent = \"✕ Exit\";\n exitButton.addEventListener(\"click\", () => {\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 banner.appendChild(exitButton);\n document.body.prepend(banner);\n\n document.querySelectorAll(\"[data-reveal]\").forEach((el) => el.classList.add(\"is-visible\"));\n rewriteLinks(config.queryParam, config.queryValue);\n\n const initEditable = () => {\n document.querySelectorAll<HTMLElement>(config.editableSelector).forEach((el) => {\n if (!isEditableElement(el, config.skipSelector)) return;\n const text = el.textContent?.trim() ?? \"\";\n el.contentEditable = \"true\";\n if (!el.dataset.editOriginal) el.dataset.editOriginal = text;\n });\n };\n initEditable();\n\n const blockButtonClicks = (event: MouseEvent) => {\n const target = event.target;\n if (!(target instanceof Element)) return;\n const button = target.closest(\"button\");\n if (button && !button.classList.contains(\"cem-copy-btn\") && !button.closest(\".cem-edit-banner\")) {\n event.preventDefault();\n event.stopImmediatePropagation();\n }\n };\n document.addEventListener(\"click\", blockButtonClicks, true);\n\n const copyButton = document.createElement(\"button\");\n copyButton.className = \"cem-copy-btn\";\n document.body.appendChild(copyButton);\n\n const updateCounter = () => {\n const count = collectChanges(config.editableSelector).length;\n copyButton.textContent = count > 0 ? `📋 Copy Changes (${count})` : \"📋 Copy Changes\";\n };\n\n document.addEventListener(\"input\", updateCounter);\n updateCounter();\n\n copyButton.addEventListener(\"click\", () => {\n const changes = collectChanges(config.editableSelector);\n const payload: EditModePayload = {\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 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 ${changes.length} change${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 return {\n active: true,\n getChanges: () => collectChanges(config.editableSelector),\n destroy: () => {\n document.removeEventListener(\"click\", blockButtonClicks, true);\n document.removeEventListener(\"input\", updateCounter);\n banner.remove();\n copyButton.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 }\n });\n window.__COLLIDE_EDIT_MODE_ACTIVE = false;\n },\n };\n}\n\nfunction collectChanges(editableSelector: string): EditModeChange[] {\n const changes: EditModeChange[] = [];\n\n document.querySelectorAll<HTMLElement>(editableSelector).forEach((el) => {\n if (!el.isContentEditable && el.contentEditable !== \"true\") return;\n const original = el.dataset.editOriginal;\n if (original === undefined) return;\n\n const current = el.textContent?.trim() ?? \"\";\n if (original !== current) {\n changes.push({\n path: getEditPath(el),\n tag: el.tagName,\n original,\n new: current,\n });\n }\n });\n\n return changes;\n}\n"],"mappings":"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","names":[]}
package/package.json ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "@collidecreatives/edit-mode",
3
+ "version": "0.1.0",
4
+ "description": "Tiny browser edit mode overlay for collecting client copy changes.",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "author": "Collide",
8
+ "publishConfig": {
9
+ "access": "public"
10
+ },
11
+ "sideEffects": false,
12
+ "files": [
13
+ "dist",
14
+ "README.md"
15
+ ],
16
+ "main": "./dist/index.cjs",
17
+ "module": "./dist/index.js",
18
+ "types": "./dist/index.d.ts",
19
+ "exports": {
20
+ ".": {
21
+ "types": "./dist/index.d.ts",
22
+ "import": "./dist/index.js",
23
+ "require": "./dist/index.cjs"
24
+ },
25
+ "./browser": "./dist/browser.global.js"
26
+ },
27
+ "scripts": {
28
+ "build": "tsup",
29
+ "dev": "tsup --watch",
30
+ "test": "vitest run",
31
+ "typecheck": "tsc --noEmit",
32
+ "prepublishOnly": "npm run typecheck && npm run test && npm run build"
33
+ },
34
+ "keywords": [
35
+ "client-editing",
36
+ "copy-review",
37
+ "contenteditable",
38
+ "statamic",
39
+ "astro"
40
+ ],
41
+ "devDependencies": {
42
+ "@types/node": "^24.0.0",
43
+ "happy-dom": "^20.0.0",
44
+ "tsup": "^8.5.0",
45
+ "typescript": "^5.8.0",
46
+ "vitest": "^4.0.0"
47
+ }
48
+ }