@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 +114 -0
- package/dist/browser.global.js +3 -0
- package/dist/browser.global.js.map +1 -0
- package/dist/index.cjs +268 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +53 -0
- package/dist/index.d.ts +53 -0
- package/dist/index.js +243 -0
- package/dist/index.js.map +1 -0
- package/package.json +48 -0
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":"mcAAA,IAAAA,EAAA,GAAAC,EAAAD,EAAA,wBAAAE,ICIA,IAAMC,EACJ,iFAEIC,EACJ,kJAEIC,EAAkB,IAAI,IAAI,CAAC,MAAO,UAAW,MAAO,QAAS,SAAU,SAAU,SAAU,OAAO,CAAC,EAEnGC,EAAW,CACf,WAAY,OACZ,WAAY,OACZ,WAAY,oBACZ,UAAW,YACX,aAAc,UACd,iBAAkBH,EAClB,aAAcC,CAChB,EAEA,SAASG,GAAS,CAChB,OAAO,OAAO,OAAW,KAAe,OAAO,SAAa,GAC9D,CAEA,SAASC,EAAaC,EAAkH,CACtI,GAAIA,EAAQ,UAAY,OAAW,OAAOA,EAAQ,QAGlD,IAAMC,EADM,IAAI,IAAI,OAAO,SAAS,IAAI,EACjB,aAAa,IAAID,EAAQ,UAAU,EAI1D,OAFEA,EAAQ,aAAe,KAAOC,IAAe,KAAOA,IAAeD,EAAQ,aAEtD,OAAO,eAAe,QAAQA,EAAQ,UAAU,IAAM,GAC/E,CAEA,SAASE,EAAUC,EAAsB,CACvC,IAAMC,EAAQ,SAAS,cAAc,OAAO,EAC5C,OAAAA,EAAM,QAAQ,cAAgB,OAC9BA,EAAM,YAAc,CAClB,sFAAsFD,CAAY,yIAClG,yGACA,6QACA,yDACA,gDACA,qGACA,wHACA,qFAAqFA,CAAY,4PACjG,6CACA,6CACA,qQACA,kGACF,EAAE,KAAK;AAAA,CAAI,EACX,SAAS,KAAK,YAAYC,CAAK,EACxBA,CACT,CAEA,SAASC,EAAUC,EAAiB,CAClC,SAAS,cAAc,YAAY,GAAG,OAAO,EAC7C,IAAMC,EAAQ,SAAS,cAAc,KAAK,EAC1CA,EAAM,UAAY,YAClBA,EAAM,YAAcD,EACpB,SAAS,KAAK,YAAYC,CAAK,EAC/B,OAAO,WAAW,IAAM,CACtBA,EAAM,MAAM,QAAU,IACtB,OAAO,WAAW,IAAMA,EAAM,YAAY,YAAYA,CAAK,EAAG,GAAG,CACnE,EAAG,IAAI,CACT,CAEA,SAASC,EAAaC,EAAcC,EAAe,CACjD,IAAMC,EAAW,SAAS,cAAc,UAAU,EAClDA,EAAS,MAAQF,EACjBE,EAAS,MAAM,SAAW,QAC1BA,EAAS,MAAM,KAAO,UACtB,SAAS,KAAK,YAAYA,CAAQ,EAClCA,EAAS,OAAO,EAChB,GAAI,CACF,SAAS,YAAY,MAAM,CAC7B,MAAQ,CAER,CACA,SAAS,KAAK,YAAYA,CAAQ,EAClCN,EAAUK,CAAK,CACjB,CAEA,SAASE,EAAkBC,EAAaC,EAAsB,CAI5D,GAHID,EAAG,QAAQC,CAAY,GAGvB,EADSD,EAAG,aAAa,KAAK,GAAK,IAC5B,MAAO,GAElB,IAAME,EAAW,MAAM,KAAKF,EAAG,QAAQ,EACvC,MAAI,EAAAE,EAAS,OAAS,GAAKA,EAAS,MAAOC,GAAUpB,EAAgB,IAAIoB,EAAM,OAAO,CAAC,EAKzF,CAEA,SAASC,EAAYJ,EAAiB,CACpC,IAAIK,EAAU,GACVC,EAAUN,EAAG,cAEjB,KAAOM,GAAWA,IAAY,SAAS,MAAQA,IAAY,SAAS,iBAAiB,CACnF,GAAIA,EAAQ,GAAI,CACdD,EAAU,IAAIC,EAAQ,EAAE,GACxB,KACF,CAEA,IAAMC,EAAUD,EAAQ,aAAa,cAAc,EACnD,GAAIC,EAAS,CACXF,EAAUE,EACV,KACF,CAEA,GAAID,EAAQ,UAAY,UAAW,CACjC,IAAME,EAAUF,EAAQ,cAAc,UAAU,EAChD,GAAIE,GAAS,YAAa,CACxBH,EAAUG,EAAQ,YAAY,KAAK,EAAE,MAAM,EAAG,EAAE,EAChD,KACF,CACF,CAEA,GAAI,CAAC,SAAU,SAAU,OAAQ,KAAK,EAAE,SAASF,EAAQ,OAAO,EAAG,CACjED,EAAUC,EAAQ,QAAQ,YAAY,EACtC,KACF,CAEAA,EAAUA,EAAQ,aACpB,CAEA,IAAMG,EAAMT,EAAG,QAAQ,YAAY,EAC7BJ,EAAOI,EAAG,QAAQ,cAAgBA,EAAG,aAAa,KAAK,GAAK,GAC5DU,EAAUd,EAAK,MAAM,EAAG,EAAE,GAAKA,EAAK,OAAS,GAAK,MAAQ,IAChE,MAAO,GAAGS,EAAU,GAAGA,CAAO,MAAQ,EAAE,GAAGI,CAAG,MAAMC,CAAO,GAC7D,CAEA,SAASC,EAAaC,EAAoBxB,EAA2B,CACnE,SAAS,iBAAoC,SAAS,EAAE,QAASyB,GAAW,CAC1E,IAAMC,EAAOD,EAAO,aAAa,MAAM,EACvC,GAAI,GAACC,GAAQ,uCAAuC,KAAKA,CAAI,GAE7D,GAAI,CACF,IAAMC,EAAM,IAAI,IAAID,EAAM,OAAO,SAAS,IAAI,EAC9C,GAAIC,EAAI,SAAW,OAAO,SAAS,OAAQ,OAE3CA,EAAI,aAAa,IAAIH,EAAYxB,GAAc,GAAG,EAClD,IAAM4B,EAAOD,EAAI,KACjBA,EAAI,KAAO,GACXF,EAAO,aAAa,OAAQ,GAAGE,EAAI,QAAQ,GAAGA,EAAI,MAAM,GAAGC,CAAI,EAAE,CACnE,MAAQ,CAER,CACF,CAAC,CACH,CAEO,SAASC,EAAmB9B,EAA2B,CAAC,EAAqB,CAClF,GAAI,CAACF,EAAO,EACV,MAAO,CAAE,OAAQ,GAAO,WAAY,IAAM,CAAC,EAAG,QAAS,IAAG,EAAa,EAGzE,IAAMiC,EAAS,CAAE,GAAGlC,EAAU,GAAGG,CAAQ,EAEzC,GAAI,CAACD,EAAagC,CAAM,EACtB,MAAO,CAAE,OAAQ,GAAO,WAAY,IAAM,CAAC,EAAG,QAAS,IAAG,EAAa,EAGzE,GAAI,OAAO,2BACT,MAAO,CAAE,OAAQ,GAAM,WAAY,IAAMC,EAAeD,EAAO,gBAAgB,EAAG,QAAS,IAAG,EAAa,EAG7G,OAAO,2BAA6B,GACpC,OAAO,eAAe,QAAQA,EAAO,WAAY,GAAG,EAEpD,IAAM3B,EAAQF,EAAU6B,EAAO,YAAY,EAErCE,EAAS,SAAS,cAAc,KAAK,EAC3CA,EAAO,UAAY,kBACnBA,EAAO,UAAY,wBAAcF,EAAO,SAAS,mFAEjD,IAAMG,EAAa,SAAS,cAAc,QAAQ,EAClDA,EAAW,UAAY,eACvBA,EAAW,YAAc,cACzBA,EAAW,iBAAiB,QAAS,IAAM,CACzC,OAAO,eAAe,WAAWH,EAAO,UAAU,EAClD,IAAMH,EAAM,IAAI,IAAI,OAAO,SAAS,IAAI,EACxCA,EAAI,aAAa,OAAOG,EAAO,UAAU,EACzC,OAAO,SAAS,KAAOH,EAAI,SAAS,CACtC,CAAC,EACDK,EAAO,YAAYC,CAAU,EAC7B,SAAS,KAAK,QAAQD,CAAM,EAE5B,SAAS,iBAAiB,eAAe,EAAE,QAASpB,GAAOA,EAAG,UAAU,IAAI,YAAY,CAAC,EACzFW,EAAaO,EAAO,WAAYA,EAAO,UAAU,EAG/C,SAAS,iBAA8BA,EAAO,gBAAgB,EAAE,QAASlB,GAAO,CAC9E,GAAI,CAACD,EAAkBC,EAAIkB,EAAO,YAAY,EAAG,OACjD,IAAMtB,EAAOI,EAAG,aAAa,KAAK,GAAK,GACvCA,EAAG,gBAAkB,OAChBA,EAAG,QAAQ,eAAcA,EAAG,QAAQ,aAAeJ,EAC1D,CAAC,EAIH,IAAM0B,EAAqBC,GAAsB,CAC/C,IAAMC,EAASD,EAAM,OACrB,GAAI,EAAEC,aAAkB,SAAU,OAClC,IAAMC,EAASD,EAAO,QAAQ,QAAQ,EAClCC,GAAU,CAACA,EAAO,UAAU,SAAS,cAAc,GAAK,CAACA,EAAO,QAAQ,kBAAkB,IAC5FF,EAAM,eAAe,EACrBA,EAAM,yBAAyB,EAEnC,EACA,SAAS,iBAAiB,QAASD,EAAmB,EAAI,EAE1D,IAAMI,EAAa,SAAS,cAAc,QAAQ,EAClDA,EAAW,UAAY,eACvB,SAAS,KAAK,YAAYA,CAAU,EAEpC,IAAMC,EAAgB,IAAM,CAC1B,IAAMC,EAAQT,EAAeD,EAAO,gBAAgB,EAAE,OACtDQ,EAAW,YAAcE,EAAQ,EAAI,2BAAoBA,CAAK,IAAM,wBACtE,EAEA,gBAAS,iBAAiB,QAASD,CAAa,EAChDA,EAAc,EAEdD,EAAW,iBAAiB,QAAS,IAAM,CACzC,IAAMG,EAAUV,EAAeD,EAAO,gBAAgB,EAChDY,EAA2B,CAC/B,KAAM,OAAO,SAAS,SACtB,KAAM,OAAO,SAAS,SACtB,UAAW,SAAS,MACpB,IAAK,OAAO,SAAS,KACrB,UAAW,IAAI,KAAK,EAAE,YAAY,EAClC,QAAAD,CACF,EAEA1C,EAAQ,SAAS2C,CAAO,EAExB,IAAMC,EAAc5C,EAAQ,WAAaA,EAAQ,WAAW2C,CAAO,EAAIA,EACjEE,EAAO,KAAK,UAAUD,EAAa,KAAM,CAAC,EAC1ClC,EAAQ,iBAAYgC,EAAQ,MAAM,UAAUA,EAAQ,SAAW,EAAI,IAAM,EAAE,gBAE7E,UAAU,WAAW,UACvB,UAAU,UAAU,UAAUG,CAAI,EAAE,KAAK,IAAMxC,EAAUK,CAAK,CAAC,EAAE,MAAM,IAAMF,EAAaqC,EAAMnC,CAAK,CAAC,EAEtGF,EAAaqC,EAAMnC,CAAK,CAE5B,CAAC,EAEM,CACL,OAAQ,GACR,WAAY,IAAMsB,EAAeD,EAAO,gBAAgB,EACxD,QAAS,IAAM,CACb,SAAS,oBAAoB,QAASI,EAAmB,EAAI,EAC7D,SAAS,oBAAoB,QAASK,CAAa,EACnDP,EAAO,OAAO,EACdM,EAAW,OAAO,EAClBnC,EAAM,OAAO,EACb,SAAS,iBAA8B2B,EAAO,gBAAgB,EAAE,QAASlB,GAAO,CAC1EA,EAAG,QAAQ,eAAiB,SAC9BA,EAAG,gBAAkB,QACrB,OAAOA,EAAG,QAAQ,aAEtB,CAAC,EACD,OAAO,2BAA6B,EACtC,CACF,CACF,CAEA,SAASmB,EAAec,EAA4C,CAClE,IAAMJ,EAA4B,CAAC,EAEnC,gBAAS,iBAA8BI,CAAgB,EAAE,QAASjC,GAAO,CACvE,GAAI,CAACA,EAAG,mBAAqBA,EAAG,kBAAoB,OAAQ,OAC5D,IAAMkC,EAAWlC,EAAG,QAAQ,aAC5B,GAAIkC,IAAa,OAAW,OAE5B,IAAM5B,EAAUN,EAAG,aAAa,KAAK,GAAK,GACtCkC,IAAa5B,GACfuB,EAAQ,KAAK,CACX,KAAMzB,EAAYJ,CAAE,EACpB,IAAKA,EAAG,QACR,SAAAkC,EACA,IAAK5B,CACP,CAAC,CAEL,CAAC,EAEMuB,CACT,CDlSA,IAAMM,EAAM,CAAE,mBAAAC,CAAmB,EAEjC,GAAI,OAAO,OAAW,IAAa,CACjC,OAAO,gBAAkBD,EAEzB,IAAME,EAAS,SAAS,cAClBC,EAA2B,CAAC,EAE9BD,GAAQ,QAAQ,YAAWC,EAAQ,UAAYD,EAAO,QAAQ,WAC9DA,GAAQ,QAAQ,aAAYC,EAAQ,WAAaD,EAAO,QAAQ,YAChEA,GAAQ,QAAQ,aAAYC,EAAQ,WAAaD,EAAO,QAAQ,YAChEA,GAAQ,QAAQ,aAAYC,EAAQ,WAAaD,EAAO,QAAQ,YAChEA,GAAQ,QAAQ,eAAcC,EAAQ,aAAeD,EAAO,QAAQ,cACpEA,GAAQ,QAAQ,mBAAkBC,EAAQ,iBAAmBD,EAAO,QAAQ,kBAC5EA,GAAQ,QAAQ,eAAcC,EAAQ,aAAeD,EAAO,QAAQ,cAExED,EAAmBE,CAAO,CAC5B","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":[]}
|
package/dist/index.d.cts
ADDED
|
@@ -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.d.ts
ADDED
|
@@ -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":";AAIA,IAAM,4BACJ;AAEF,IAAM,wBACJ;AAEF,IAAM,kBAAkB,oBAAI,IAAI,CAAC,OAAO,WAAW,OAAO,SAAS,UAAU,UAAU,UAAU,OAAO,CAAC;AAEzG,IAAM,WAAW;AAAA,EACf,YAAY;AAAA,EACZ,YAAY;AAAA,EACZ,YAAY;AAAA,EACZ,WAAW;AAAA,EACX,cAAc;AAAA,EACd,kBAAkB;AAAA,EAClB,cAAc;AAChB;AAEA,SAAS,SAAS;AAChB,SAAO,OAAO,WAAW,eAAe,OAAO,aAAa;AAC9D;AAEA,SAAS,aAAa,SAAkH;AACtI,MAAI,QAAQ,YAAY,OAAW,QAAO,QAAQ;AAElD,QAAM,MAAM,IAAI,IAAI,OAAO,SAAS,IAAI;AACxC,QAAM,aAAa,IAAI,aAAa,IAAI,QAAQ,UAAU;AAC1D,QAAM,eACJ,QAAQ,eAAe,OAAO,eAAe,OAAO,eAAe,QAAQ;AAE7E,SAAO,gBAAgB,OAAO,eAAe,QAAQ,QAAQ,UAAU,MAAM;AAC/E;AAEA,SAAS,UAAU,cAAsB;AACvC,QAAM,QAAQ,SAAS,cAAc,OAAO;AAC5C,QAAM,QAAQ,gBAAgB;AAC9B,QAAM,cAAc;AAAA,IAClB,sFAAsF,YAAY;AAAA,IAClG;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,qFAAqF,YAAY;AAAA,IACjG;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,EAAE,KAAK,IAAI;AACX,WAAS,KAAK,YAAY,KAAK;AAC/B,SAAO;AACT;AAEA,SAAS,UAAU,SAAiB;AAClC,WAAS,cAAc,YAAY,GAAG,OAAO;AAC7C,QAAM,QAAQ,SAAS,cAAc,KAAK;AAC1C,QAAM,YAAY;AAClB,QAAM,cAAc;AACpB,WAAS,KAAK,YAAY,KAAK;AAC/B,SAAO,WAAW,MAAM;AACtB,UAAM,MAAM,UAAU;AACtB,WAAO,WAAW,MAAM,MAAM,YAAY,YAAY,KAAK,GAAG,GAAG;AAAA,EACnE,GAAG,IAAI;AACT;AAEA,SAAS,aAAa,MAAc,OAAe;AACjD,QAAM,WAAW,SAAS,cAAc,UAAU;AAClD,WAAS,QAAQ;AACjB,WAAS,MAAM,WAAW;AAC1B,WAAS,MAAM,OAAO;AACtB,WAAS,KAAK,YAAY,QAAQ;AAClC,WAAS,OAAO;AAChB,MAAI;AACF,aAAS,YAAY,MAAM;AAAA,EAC7B,QAAQ;AAAA,EAER;AACA,WAAS,KAAK,YAAY,QAAQ;AAClC,YAAU,KAAK;AACjB;AAEA,SAAS,kBAAkB,IAAa,cAAsB;AAC5D,MAAI,GAAG,QAAQ,YAAY,EAAG,QAAO;AAErC,QAAM,OAAO,GAAG,aAAa,KAAK,KAAK;AACvC,MAAI,CAAC,KAAM,QAAO;AAElB,QAAM,WAAW,MAAM,KAAK,GAAG,QAAQ;AACvC,MAAI,SAAS,SAAS,KAAK,SAAS,MAAM,CAAC,UAAU,gBAAgB,IAAI,MAAM,OAAO,CAAC,GAAG;AACxF,WAAO;AAAA,EACT;AAEA,SAAO;AACT;AAEA,SAAS,YAAY,IAAiB;AACpC,MAAI,UAAU;AACd,MAAI,UAAU,GAAG;AAEjB,SAAO,WAAW,YAAY,SAAS,QAAQ,YAAY,SAAS,iBAAiB;AACnF,QAAI,QAAQ,IAAI;AACd,gBAAU,IAAI,QAAQ,EAAE;AACxB;AAAA,IACF;AAEA,UAAM,UAAU,QAAQ,aAAa,cAAc;AACnD,QAAI,SAAS;AACX,gBAAU;AACV;AAAA,IACF;AAEA,QAAI,QAAQ,YAAY,WAAW;AACjC,YAAM,UAAU,QAAQ,cAAc,UAAU;AAChD,UAAI,SAAS,aAAa;AACxB,kBAAU,QAAQ,YAAY,KAAK,EAAE,MAAM,GAAG,EAAE;AAChD;AAAA,MACF;AAAA,IACF;AAEA,QAAI,CAAC,UAAU,UAAU,QAAQ,KAAK,EAAE,SAAS,QAAQ,OAAO,GAAG;AACjE,gBAAU,QAAQ,QAAQ,YAAY;AACtC;AAAA,IACF;AAEA,cAAU,QAAQ;AAAA,EACpB;AAEA,QAAM,MAAM,GAAG,QAAQ,YAAY;AACnC,QAAM,OAAO,GAAG,QAAQ,gBAAgB,GAAG,aAAa,KAAK,KAAK;AAClE,QAAM,UAAU,KAAK,MAAM,GAAG,EAAE,KAAK,KAAK,SAAS,KAAK,QAAQ;AAChE,SAAO,GAAG,UAAU,GAAG,OAAO,QAAQ,EAAE,GAAG,GAAG,MAAM,OAAO;AAC7D;AAEA,SAAS,aAAa,YAAoB,YAA2B;AACnE,WAAS,iBAAoC,SAAS,EAAE,QAAQ,CAAC,WAAW;AAC1E,UAAM,OAAO,OAAO,aAAa,MAAM;AACvC,QAAI,CAAC,QAAQ,uCAAuC,KAAK,IAAI,EAAG;AAEhE,QAAI;AACF,YAAM,MAAM,IAAI,IAAI,MAAM,OAAO,SAAS,IAAI;AAC9C,UAAI,IAAI,WAAW,OAAO,SAAS,OAAQ;AAE3C,UAAI,aAAa,IAAI,YAAY,cAAc,GAAG;AAClD,YAAM,OAAO,IAAI;AACjB,UAAI,OAAO;AACX,aAAO,aAAa,QAAQ,GAAG,IAAI,QAAQ,GAAG,IAAI,MAAM,GAAG,IAAI,EAAE;AAAA,IACnE,QAAQ;AAAA,IAER;AAAA,EACF,CAAC;AACH;AAEO,SAAS,mBAAmB,UAA2B,CAAC,GAAqB;AAClF,MAAI,CAAC,OAAO,GAAG;AACb,WAAO,EAAE,QAAQ,OAAO,YAAY,MAAM,CAAC,GAAG,SAAS,MAAM,OAAU;AAAA,EACzE;AAEA,QAAM,SAAS,EAAE,GAAG,UAAU,GAAG,QAAQ;AAEzC,MAAI,CAAC,aAAa,MAAM,GAAG;AACzB,WAAO,EAAE,QAAQ,OAAO,YAAY,MAAM,CAAC,GAAG,SAAS,MAAM,OAAU;AAAA,EACzE;AAEA,MAAI,OAAO,4BAA4B;AACrC,WAAO,EAAE,QAAQ,MAAM,YAAY,MAAM,eAAe,OAAO,gBAAgB,GAAG,SAAS,MAAM,OAAU;AAAA,EAC7G;AAEA,SAAO,6BAA6B;AACpC,SAAO,eAAe,QAAQ,OAAO,YAAY,GAAG;AAEpD,QAAM,QAAQ,UAAU,OAAO,YAAY;AAE3C,QAAM,SAAS,SAAS,cAAc,KAAK;AAC3C,SAAO,YAAY;AACnB,SAAO,YAAY,wBAAc,OAAO,SAAS;AAEjD,QAAM,aAAa,SAAS,cAAc,QAAQ;AAClD,aAAW,YAAY;AACvB,aAAW,cAAc;AACzB,aAAW,iBAAiB,SAAS,MAAM;AACzC,WAAO,eAAe,WAAW,OAAO,UAAU;AAClD,UAAM,MAAM,IAAI,IAAI,OAAO,SAAS,IAAI;AACxC,QAAI,aAAa,OAAO,OAAO,UAAU;AACzC,WAAO,SAAS,OAAO,IAAI,SAAS;AAAA,EACtC,CAAC;AACD,SAAO,YAAY,UAAU;AAC7B,WAAS,KAAK,QAAQ,MAAM;AAE5B,WAAS,iBAAiB,eAAe,EAAE,QAAQ,CAAC,OAAO,GAAG,UAAU,IAAI,YAAY,CAAC;AACzF,eAAa,OAAO,YAAY,OAAO,UAAU;AAEjD,QAAM,eAAe,MAAM;AACzB,aAAS,iBAA8B,OAAO,gBAAgB,EAAE,QAAQ,CAAC,OAAO;AAC9E,UAAI,CAAC,kBAAkB,IAAI,OAAO,YAAY,EAAG;AACjD,YAAM,OAAO,GAAG,aAAa,KAAK,KAAK;AACvC,SAAG,kBAAkB;AACrB,UAAI,CAAC,GAAG,QAAQ,aAAc,IAAG,QAAQ,eAAe;AAAA,IAC1D,CAAC;AAAA,EACH;AACA,eAAa;AAEb,QAAM,oBAAoB,CAAC,UAAsB;AAC/C,UAAM,SAAS,MAAM;AACrB,QAAI,EAAE,kBAAkB,SAAU;AAClC,UAAM,SAAS,OAAO,QAAQ,QAAQ;AACtC,QAAI,UAAU,CAAC,OAAO,UAAU,SAAS,cAAc,KAAK,CAAC,OAAO,QAAQ,kBAAkB,GAAG;AAC/F,YAAM,eAAe;AACrB,YAAM,yBAAyB;AAAA,IACjC;AAAA,EACF;AACA,WAAS,iBAAiB,SAAS,mBAAmB,IAAI;AAE1D,QAAM,aAAa,SAAS,cAAc,QAAQ;AAClD,aAAW,YAAY;AACvB,WAAS,KAAK,YAAY,UAAU;AAEpC,QAAM,gBAAgB,MAAM;AAC1B,UAAM,QAAQ,eAAe,OAAO,gBAAgB,EAAE;AACtD,eAAW,cAAc,QAAQ,IAAI,2BAAoB,KAAK,MAAM;AAAA,EACtE;AAEA,WAAS,iBAAiB,SAAS,aAAa;AAChD,gBAAc;AAEd,aAAW,iBAAiB,SAAS,MAAM;AACzC,UAAM,UAAU,eAAe,OAAO,gBAAgB;AACtD,UAAM,UAA2B;AAAA,MAC/B,MAAM,OAAO,SAAS;AAAA,MACtB,MAAM,OAAO,SAAS;AAAA,MACtB,WAAW,SAAS;AAAA,MACpB,KAAK,OAAO,SAAS;AAAA,MACrB,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,MAClC;AAAA,IACF;AAEA,YAAQ,SAAS,OAAO;AAExB,UAAM,cAAc,QAAQ,aAAa,QAAQ,WAAW,OAAO,IAAI;AACvE,UAAM,OAAO,KAAK,UAAU,aAAa,MAAM,CAAC;AAChD,UAAM,QAAQ,iBAAY,QAAQ,MAAM,UAAU,QAAQ,WAAW,IAAI,MAAM,EAAE;AAEjF,QAAI,UAAU,WAAW,WAAW;AAClC,gBAAU,UAAU,UAAU,IAAI,EAAE,KAAK,MAAM,UAAU,KAAK,CAAC,EAAE,MAAM,MAAM,aAAa,MAAM,KAAK,CAAC;AAAA,IACxG,OAAO;AACL,mBAAa,MAAM,KAAK;AAAA,IAC1B;AAAA,EACF,CAAC;AAED,SAAO;AAAA,IACL,QAAQ;AAAA,IACR,YAAY,MAAM,eAAe,OAAO,gBAAgB;AAAA,IACxD,SAAS,MAAM;AACb,eAAS,oBAAoB,SAAS,mBAAmB,IAAI;AAC7D,eAAS,oBAAoB,SAAS,aAAa;AACnD,aAAO,OAAO;AACd,iBAAW,OAAO;AAClB,YAAM,OAAO;AACb,eAAS,iBAA8B,OAAO,gBAAgB,EAAE,QAAQ,CAAC,OAAO;AAC9E,YAAI,GAAG,QAAQ,iBAAiB,QAAW;AACzC,aAAG,kBAAkB;AACrB,iBAAO,GAAG,QAAQ;AAAA,QACpB;AAAA,MACF,CAAC;AACD,aAAO,6BAA6B;AAAA,IACtC;AAAA,EACF;AACF;AAEA,SAAS,eAAe,kBAA4C;AAClE,QAAM,UAA4B,CAAC;AAEnC,WAAS,iBAA8B,gBAAgB,EAAE,QAAQ,CAAC,OAAO;AACvE,QAAI,CAAC,GAAG,qBAAqB,GAAG,oBAAoB,OAAQ;AAC5D,UAAM,WAAW,GAAG,QAAQ;AAC5B,QAAI,aAAa,OAAW;AAE5B,UAAM,UAAU,GAAG,aAAa,KAAK,KAAK;AAC1C,QAAI,aAAa,SAAS;AACxB,cAAQ,KAAK;AAAA,QACX,MAAM,YAAY,EAAE;AAAA,QACpB,KAAK,GAAG;AAAA,QACR;AAAA,QACA,KAAK;AAAA,MACP,CAAC;AAAA,IACH;AAAA,EACF,CAAC;AAED,SAAO;AACT;","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
|
+
}
|