@collidecreatives/edit-mode 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +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-panel,.cem-toast\";\n\nconst SKIP_CHILD_TAGS = new Set([\"IMG\", \"PICTURE\", \"SVG\", \"VIDEO\", \"CANVAS\", \"IFRAME\", \"SCRIPT\", \"STYLE\"]);\nconst DRAFT_VERSION = 1;\n\ntype Config = Required<\n Omit<EditModeOptions, \"enabled\" | \"onCopy\" | \"mapPayload\" | \"storageKey\" | \"autoSave\">\n> &\n Pick<EditModeOptions, \"enabled\" | \"storageKey\" | \"autoSave\">;\n\ntype DraftChange = EditModeChange & {\n key: string;\n};\n\ntype Draft = {\n version: number;\n page: string;\n url: string;\n updatedAt: string;\n changes: DraftChange[];\n};\n\nconst defaults = {\n queryParam: \"edit\",\n queryValue: \"true\" as string | null,\n sessionKey: \"collide-edit-mode\",\n brandName: \"Edit Mode\",\n accentColour: \"#1e40af\",\n editableSelector: DEFAULT_EDITABLE_SELECTOR,\n skipSelector: DEFAULT_SKIP_SELECTOR,\n autoSave: true,\n};\n\nfunction hasDom() {\n return typeof window !== \"undefined\" && typeof document !== \"undefined\";\n}\n\nfunction shouldEnable(options: Config) {\n if (options.enabled !== undefined) return options.enabled;\n\n const url = new URL(window.location.href);\n const queryValue = url.searchParams.get(options.queryParam);\n const queryMatches =\n options.queryValue === null ? queryValue !== null : queryValue === options.queryValue;\n\n return queryMatches || window.sessionStorage.getItem(options.sessionKey) === \"1\";\n}\n\nfunction addStyles(accentColour: string) {\n const style = document.createElement(\"style\");\n style.dataset.editModeStyle = \"true\";\n style.textContent = [\n `.cem-edit-banner{position:fixed;top:0;left:0;right:0;z-index:2147483640;background:${accentColour};color:#fff;display:flex;align-items:center;justify-content:center;gap:8px;padding:8px 92px 8px 16px;font:13px/1.4 system-ui,-apple-system,sans-serif;box-shadow:0 1px 8px rgba(0,0,0,.18)}`,\n \".cem-edit-banner kbd{background:rgba(255,255,255,.18);padding:0 4px;border-radius:3px;font-size:11px}\",\n \".cem-exit-btn{position:absolute;right:8px;top:50%;transform:translateY(-50%);background:rgba(255,255,255,.15);border:1px solid rgba(255,255,255,.25);color:#fff;padding:4px 10px;border-radius:5px;font:12px system-ui,-apple-system,sans-serif;cursor:pointer;white-space:nowrap}\",\n \".cem-exit-btn:hover,.cem-exit-btn:focus-visible{background:rgba(255,255,255,.25);outline:none}\",\n \"[contenteditable=true]{cursor:text!important}\",\n \"[contenteditable=true]:hover{outline:2px dashed rgba(59,130,246,.5)!important;outline-offset:2px}\",\n \"[contenteditable=true]:focus{outline:2px solid #3b82f6!important;outline-offset:2px;background:rgba(59,130,246,.04)}\",\n `.cem-panel{position:fixed;right:16px;bottom:16px;z-index:2147483640;background:#fff;color:#111827;border:1px solid #e5e7eb;border-radius:12px;box-shadow:0 8px 30px rgba(0,0,0,.22);padding:10px;display:grid;gap:8px;width:min(320px,calc(100vw - 32px));font:13px/1.4 system-ui,-apple-system,sans-serif}`,\n \".cem-panel-actions{display:flex;gap:8px;flex-wrap:wrap}\",\n `.cem-copy-btn,.cem-download-btn,.cem-open-link-btn{border:0;border-radius:8px;padding:9px 12px;font:600 13px system-ui,-apple-system,sans-serif;cursor:pointer;white-space:nowrap}`,\n `.cem-copy-btn{background:${accentColour};color:#fff;flex:1}`,\n \".cem-download-btn,.cem-open-link-btn{background:#f3f4f6;color:#111827;border:1px solid #e5e7eb}\",\n \".cem-open-link-btn[hidden]{display:none}\",\n \".cem-copy-btn:hover,.cem-download-btn:hover,.cem-open-link-btn:hover{filter:brightness(.97)}\",\n \".cem-status{color:#4b5563;font-size:12px;min-height:17px}\",\n \".cem-toast{position:fixed;bottom:116px;right:16px;z-index:2147483641;background:#065f46;color:#fff;padding:10px 18px;border-radius:8px;font:14px system-ui,-apple-system,sans-serif;box-shadow:0 2px 12px rgba(0,0,0,.3);transition:opacity .3s}\",\n \"@media(max-width:520px){.cem-edit-banner{justify-content:flex-start;text-align:left}.cem-panel{left:16px;right:16px;width:auto}}\",\n ].join(\"\\n\");\n document.head.appendChild(style);\n return style;\n}\n\nfunction showToast(message: string) {\n document.querySelector(\".cem-toast\")?.remove();\n const toast = document.createElement(\"div\");\n toast.className = \"cem-toast\";\n toast.textContent = message;\n document.body.appendChild(toast);\n window.setTimeout(() => {\n toast.style.opacity = \"0\";\n window.setTimeout(() => toast.parentNode?.removeChild(toast), 300);\n }, 2500);\n}\n\nfunction fallbackCopy(text: string, label: string) {\n const textarea = document.createElement(\"textarea\");\n textarea.value = text;\n textarea.style.position = \"fixed\";\n textarea.style.left = \"-9999px\";\n document.body.appendChild(textarea);\n textarea.select();\n try {\n document.execCommand(\"copy\");\n } catch {\n // Ignore: the download button and autosaved draft still protect the work.\n }\n document.body.removeChild(textarea);\n showToast(label);\n}\n\nfunction getStorage() {\n try {\n const testKey = \"__cem_storage_test__\";\n window.localStorage.setItem(testKey, \"1\");\n window.localStorage.removeItem(testKey);\n return window.localStorage;\n } catch {\n try {\n return window.sessionStorage;\n } catch {\n return null;\n }\n }\n}\n\nfunction getDraftKey(config: Config) {\n const base = config.storageKey || `${config.sessionKey}:draft`;\n return `${base}:${window.location.origin}${window.location.pathname}`;\n}\n\nfunction isEditableElement(el: Element, skipSelector: string) {\n if (el.closest(skipSelector)) return false;\n\n const text = el.textContent?.trim() ?? \"\";\n if (!text) return false;\n\n const children = Array.from(el.children);\n if (children.length > 0 && children.every((child) => SKIP_CHILD_TAGS.has(child.tagName))) {\n return false;\n }\n\n return true;\n}\n\nfunction getElementKey(el: HTMLElement) {\n if (el.dataset.editId) return `data-edit-id:${el.dataset.editId}`;\n if (el.id) return `id:${el.id}`;\n\n const parts: string[] = [];\n let current: Element | null = el;\n\n while (current && current !== document.body && current !== document.documentElement) {\n const parentElement: HTMLElement | null = current.parentElement;\n if (!parentElement) break;\n\n const currentTag = current.tagName;\n const siblings = Array.from(parentElement.children).filter((child) => child.tagName === currentTag);\n const index = siblings.indexOf(current) + 1;\n parts.unshift(`${current.tagName.toLowerCase()}:nth-of-type(${index})`);\n current = parentElement;\n }\n\n return parts.join(\" > \");\n}\n\nfunction getEditPath(el: HTMLElement) {\n let context = \"\";\n let current = el.parentElement;\n\n while (current && current !== document.body && current !== document.documentElement) {\n if (current.id) {\n context = `#${current.id}`;\n break;\n }\n\n const section = current.getAttribute(\"data-section\");\n if (section) {\n context = section;\n break;\n }\n\n if (current.tagName === \"SECTION\") {\n const heading = current.querySelector(\"h1,h2,h3\");\n if (heading?.textContent) {\n context = heading.textContent.trim().slice(0, 40);\n break;\n }\n }\n\n if ([\"HEADER\", \"FOOTER\", \"MAIN\", \"NAV\"].includes(current.tagName)) {\n context = current.tagName.toLowerCase();\n break;\n }\n\n current = current.parentElement;\n }\n\n const tag = el.tagName.toLowerCase();\n const text = el.dataset.editOriginal || el.textContent?.trim() || \"\";\n const snippet = text.slice(0, 40) + (text.length > 40 ? \"...\" : \"\");\n return `${context ? `${context} > ` : \"\"}${tag}: \"${snippet}\"`;\n}\n\nfunction rewriteLinks(queryParam: string, queryValue: string | null) {\n document.querySelectorAll<HTMLAnchorElement>(\"a[href]\").forEach((anchor) => {\n const href = anchor.getAttribute(\"href\");\n if (!href || /^(https?:|mailto:|tel:|javascript:)/i.test(href)) return;\n\n try {\n const url = new URL(href, window.location.href);\n if (url.origin !== window.location.origin) return;\n\n url.searchParams.set(queryParam, queryValue ?? \"1\");\n const hash = url.hash;\n url.hash = \"\";\n anchor.setAttribute(\"href\", `${url.pathname}${url.search}${hash}`);\n } catch {\n // Leave unusual hrefs unchanged.\n }\n });\n}\n\nfunction buildPayload(changes: EditModeChange[]): EditModePayload {\n return {\n site: window.location.hostname,\n page: window.location.pathname,\n pageTitle: document.title,\n url: window.location.href,\n timestamp: new Date().toISOString(),\n changes,\n };\n}\n\nfunction loadDraft(storage: Storage | null, key: string): Draft | null {\n if (!storage) return null;\n\n try {\n const raw = storage.getItem(key);\n if (!raw) return null;\n const draft = JSON.parse(raw) as Draft;\n return draft?.version === DRAFT_VERSION && Array.isArray(draft.changes) ? draft : null;\n } catch {\n return null;\n }\n}\n\nfunction saveDraft(storage: Storage | null, key: string, draft: Draft) {\n if (!storage) return false;\n\n try {\n storage.setItem(key, JSON.stringify(draft));\n return true;\n } catch {\n return false;\n }\n}\n\nfunction downloadText(filename: string, text: string) {\n const blob = new Blob([text], { type: \"application/json\" });\n const url = URL.createObjectURL(blob);\n const anchor = document.createElement(\"a\");\n anchor.href = url;\n anchor.download = filename;\n anchor.style.display = \"none\";\n document.body.appendChild(anchor);\n anchor.click();\n anchor.remove();\n window.setTimeout(() => URL.revokeObjectURL(url), 1000);\n}\n\nfunction formatTime(date = new Date()) {\n return date.toLocaleTimeString([], { hour: \"2-digit\", minute: \"2-digit\" });\n}\n\nfunction initEditableElements(config: Config) {\n document.querySelectorAll<HTMLElement>(config.editableSelector).forEach((el) => {\n if (!isEditableElement(el, config.skipSelector)) return;\n\n const text = el.textContent?.trim() ?? \"\";\n el.contentEditable = \"true\";\n if (!el.dataset.editOriginal) el.dataset.editOriginal = text;\n if (!el.dataset.editKey) el.dataset.editKey = getElementKey(el);\n });\n}\n\nfunction applyDraft(draft: Draft | null, editableSelector: string) {\n if (!draft) return 0;\n\n const edits = new Map(draft.changes.map((change) => [change.key, change]));\n let restored = 0;\n\n document.querySelectorAll<HTMLElement>(editableSelector).forEach((el) => {\n const key = el.dataset.editKey;\n if (!key) return;\n\n const saved = edits.get(key);\n if (!saved) return;\n\n // Avoid stomping over changed templates. Restore only when the original still matches.\n if (el.dataset.editOriginal !== saved.original) return;\n\n el.textContent = saved.new;\n restored += 1;\n });\n\n return restored;\n}\n\nfunction collectDraftChanges(editableSelector: string): DraftChange[] {\n const changes: DraftChange[] = [];\n\n document.querySelectorAll<HTMLElement>(editableSelector).forEach((el) => {\n if (!el.isContentEditable && el.contentEditable !== \"true\") return;\n const original = el.dataset.editOriginal;\n const key = el.dataset.editKey;\n if (original === undefined || !key) return;\n\n const current = el.textContent?.trim() ?? \"\";\n if (original !== current) {\n changes.push({\n key,\n path: getEditPath(el),\n tag: el.tagName,\n original,\n new: current,\n });\n }\n });\n\n return changes;\n}\n\nfunction collectChanges(editableSelector: string): EditModeChange[] {\n return collectDraftChanges(editableSelector).map(({ key: _key, ...change }) => change);\n}\n\nexport function initClientEditMode(options: EditModeOptions = {}): EditModeInstance {\n if (!hasDom()) {\n return { active: false, getChanges: () => [], destroy: () => undefined };\n }\n\n const config: Config = { ...defaults, ...options };\n\n if (!shouldEnable(config)) {\n return { active: false, getChanges: () => [], destroy: () => undefined };\n }\n\n if (window.__COLLIDE_EDIT_MODE_ACTIVE) {\n return { active: true, getChanges: () => collectChanges(config.editableSelector), destroy: () => undefined };\n }\n\n window.__COLLIDE_EDIT_MODE_ACTIVE = true;\n window.sessionStorage.setItem(config.sessionKey, \"1\");\n\n const storage = getStorage();\n const draftKey = getDraftKey(config);\n const style = addStyles(config.accentColour);\n\n const banner = document.createElement(\"div\");\n banner.className = \"cem-edit-banner\";\n banner.innerHTML = `✏️ <strong>${config.brandName}</strong> <span>Click text to edit. Press <kbd>Esc</kbd> when done.</span>`;\n\n const exitButton = document.createElement(\"button\");\n exitButton.className = \"cem-exit-btn\";\n exitButton.type = \"button\";\n exitButton.textContent = \"Exit\";\n banner.appendChild(exitButton);\n document.body.prepend(banner);\n\n const panel = document.createElement(\"div\");\n panel.className = \"cem-panel\";\n panel.innerHTML = `\n <div class=\"cem-panel-actions\">\n <button class=\"cem-copy-btn\" type=\"button\">📋 Copy Changes</button>\n <button class=\"cem-download-btn\" type=\"button\">Download backup</button>\n <button class=\"cem-open-link-btn\" type=\"button\" hidden>Open link</button>\n </div>\n <div class=\"cem-status\" aria-live=\"polite\">Auto-save ready</div>\n `;\n document.body.appendChild(panel);\n\n const copyButton = panel.querySelector<HTMLButtonElement>(\".cem-copy-btn\");\n const downloadButton = panel.querySelector<HTMLButtonElement>(\".cem-download-btn\");\n const openLinkButton = panel.querySelector<HTMLButtonElement>(\".cem-open-link-btn\");\n const status = panel.querySelector<HTMLElement>(\".cem-status\");\n let activeLink: HTMLAnchorElement | null = null;\n\n document.querySelectorAll(\"[data-reveal]\").forEach((el) => el.classList.add(\"is-visible\"));\n rewriteLinks(config.queryParam, config.queryValue);\n initEditableElements(config);\n\n const restoredCount = applyDraft(loadDraft(storage, draftKey), config.editableSelector);\n\n const makeDraft = (): Draft => ({\n version: DRAFT_VERSION,\n page: window.location.pathname,\n url: window.location.href,\n updatedAt: new Date().toISOString(),\n changes: collectDraftChanges(config.editableSelector),\n });\n\n const updateUi = (message?: string) => {\n const count = collectChanges(config.editableSelector).length;\n if (copyButton) copyButton.textContent = count > 0 ? `📋 Copy Changes (${count})` : \"📋 Copy Changes\";\n if (openLinkButton) {\n openLinkButton.hidden = !activeLink;\n openLinkButton.textContent = activeLink ? `Open ${activeLink.hostname || \"link\"}` : \"Open link\";\n }\n if (status) {\n status.textContent =\n message ||\n `Auto-saved ${formatTime()} • ${count} change${count === 1 ? \"\" : \"s\"} • Links: edit text or Ctrl/⌘-click to open`;\n }\n };\n\n const persistDraft = () => {\n const draft = makeDraft();\n const saved = config.autoSave !== false && saveDraft(storage, draftKey, draft);\n updateUi(saved || config.autoSave === false ? undefined : \"⚠️ Auto-save unavailable — use Download backup\");\n return draft;\n };\n\n const finishEditing = () => {\n const activeElement = document.activeElement;\n if (!(activeElement instanceof HTMLElement)) return false;\n if (!activeElement.isContentEditable && activeElement.contentEditable !== \"true\") return false;\n\n activeElement.blur();\n window.getSelection()?.removeAllRanges();\n persistDraft();\n return true;\n };\n\n const finishEditingOnEscape = (event: KeyboardEvent) => {\n if (event.key !== \"Escape\") return;\n if (!finishEditing()) return;\n\n event.preventDefault();\n event.stopPropagation();\n };\n\n const handleDocumentClick = (event: MouseEvent) => {\n const target = event.target;\n if (!(target instanceof Element)) return;\n\n const anchor = target.closest<HTMLAnchorElement>(\"a[href]\");\n if (anchor && !anchor.closest(\".cem-panel\") && !anchor.closest(\".cem-edit-banner\")) {\n activeLink = anchor;\n updateUi(\"Link selected — edit the text, use Open link, or Ctrl/⌘-click to visit it\");\n\n if (event.metaKey || event.ctrlKey || event.altKey) {\n persistDraft();\n return;\n }\n\n event.preventDefault();\n return;\n }\n\n const button = target.closest(\"button\");\n if (button && !button.closest(\".cem-panel\") && !button.closest(\".cem-edit-banner\")) {\n event.preventDefault();\n event.stopImmediatePropagation();\n }\n };\n\n const handleFocusIn = (event: FocusEvent) => {\n const target = event.target;\n if (!(target instanceof Element)) return;\n\n activeLink = target.closest<HTMLAnchorElement>(\"a[href]\");\n updateUi(activeLink ? \"Link selected — edit the text or use Open link to navigate\" : undefined);\n };\n\n const handleInput = () => persistDraft();\n const handlePageHide = () => persistDraft();\n const handleVisibilityChange = () => {\n if (document.visibilityState === \"hidden\") persistDraft();\n };\n\n document.addEventListener(\"keydown\", finishEditingOnEscape);\n document.addEventListener(\"click\", handleDocumentClick, true);\n document.addEventListener(\"focusin\", handleFocusIn);\n document.addEventListener(\"input\", handleInput);\n window.addEventListener(\"pagehide\", handlePageHide);\n document.addEventListener(\"visibilitychange\", handleVisibilityChange);\n\n copyButton?.addEventListener(\"click\", () => {\n const draft = persistDraft();\n const payload = buildPayload(draft.changes);\n options.onCopy?.(payload);\n\n const copyPayload = options.mapPayload ? options.mapPayload(payload) : payload;\n const json = JSON.stringify(copyPayload, null, 2);\n const label = `✓ Copied ${draft.changes.length} change${draft.changes.length !== 1 ? \"s\" : \"\"} to clipboard`;\n\n if (navigator.clipboard?.writeText) {\n navigator.clipboard.writeText(json).then(() => showToast(label)).catch(() => fallbackCopy(json, label));\n } else {\n fallbackCopy(json, label);\n }\n });\n\n downloadButton?.addEventListener(\"click\", () => {\n const draft = persistDraft();\n const payload = buildPayload(draft.changes);\n const date = new Date().toISOString().slice(0, 10);\n downloadText(`edit-mode-${window.location.hostname}-${date}.json`, JSON.stringify(payload, null, 2));\n showToast(\"✓ Backup downloaded\");\n });\n\n openLinkButton?.addEventListener(\"click\", () => {\n if (!activeLink?.href) return;\n\n persistDraft();\n window.location.href = activeLink.href;\n });\n\n exitButton.addEventListener(\"click\", () => {\n persistDraft();\n window.sessionStorage.removeItem(config.sessionKey);\n const url = new URL(window.location.href);\n url.searchParams.delete(config.queryParam);\n window.location.href = url.toString();\n });\n\n persistDraft();\n if (restoredCount > 0) updateUi(`Restored ${restoredCount} saved edit${restoredCount === 1 ? \"\" : \"s\"} • Auto-save on`);\n\n return {\n active: true,\n getChanges: () => collectChanges(config.editableSelector),\n destroy: () => {\n persistDraft();\n document.removeEventListener(\"keydown\", finishEditingOnEscape);\n document.removeEventListener(\"click\", handleDocumentClick, true);\n document.removeEventListener(\"focusin\", handleFocusIn);\n document.removeEventListener(\"input\", handleInput);\n window.removeEventListener(\"pagehide\", handlePageHide);\n document.removeEventListener(\"visibilitychange\", handleVisibilityChange);\n banner.remove();\n panel.remove();\n style.remove();\n document.querySelectorAll<HTMLElement>(config.editableSelector).forEach((el) => {\n if (el.dataset.editOriginal !== undefined) {\n el.contentEditable = \"false\";\n delete el.dataset.editOriginal;\n delete el.dataset.editKey;\n }\n });\n window.__COLLIDE_EDIT_MODE_ACTIVE = false;\n },\n };\n}\n"],"mappings":"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","names":[]}
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-panel,.cem-toast\";\n\nconst SKIP_CHILD_TAGS = new Set([\"IMG\", \"PICTURE\", \"SVG\", \"VIDEO\", \"CANVAS\", \"IFRAME\", \"SCRIPT\", \"STYLE\"]);\nconst SINGLE_LINE_TAGS = new Set([\n \"H1\",\n \"H2\",\n \"H3\",\n \"H4\",\n \"H5\",\n \"H6\",\n \"BUTTON\",\n \"LABEL\",\n \"LEGEND\",\n \"DT\",\n \"TH\",\n \"TD\",\n \"FIGCAPTION\",\n \"A\",\n]);\nconst DRAFT_VERSION = 1;\nconst AUTOSAVE_DEBOUNCE_MS = 400;\n\ntype Config = Required<\n Omit<EditModeOptions, \"enabled\" | \"onCopy\" | \"mapPayload\" | \"storageKey\" | \"autoSave\">\n> &\n Pick<EditModeOptions, \"enabled\" | \"storageKey\" | \"autoSave\">;\n\ntype DraftChange = EditModeChange & {\n key: string;\n};\n\ntype Draft = {\n version: number;\n page: string;\n url: string;\n updatedAt: string;\n changes: DraftChange[];\n};\n\nconst defaults = {\n queryParam: \"edit\",\n queryValue: \"true\" as string | null,\n sessionKey: \"collide-edit-mode\",\n brandName: \"Edit Mode\",\n accentColour: \"#1e40af\",\n editableSelector: DEFAULT_EDITABLE_SELECTOR,\n skipSelector: DEFAULT_SKIP_SELECTOR,\n autoSave: true,\n};\n\nfunction hasDom() {\n return typeof window !== \"undefined\" && typeof document !== \"undefined\";\n}\n\nfunction shouldEnable(options: Config) {\n if (options.enabled !== undefined) return options.enabled;\n\n const url = new URL(window.location.href);\n const queryValue = url.searchParams.get(options.queryParam);\n const queryMatches =\n options.queryValue === null ? queryValue !== null : queryValue === options.queryValue;\n\n return queryMatches || window.sessionStorage.getItem(options.sessionKey) === \"1\";\n}\n\nfunction addStyles(accentColour: string) {\n const style = document.createElement(\"style\");\n style.dataset.editModeStyle = \"true\";\n style.textContent = [\n `.cem-edit-banner{position:fixed;top:0;left:0;right:0;z-index:2147483640;background:${accentColour};color:#fff;display:flex;align-items:center;justify-content:center;gap:8px;padding:8px 92px 8px 16px;font:13px/1.4 system-ui,-apple-system,sans-serif;box-shadow:0 1px 8px rgba(0,0,0,.18)}`,\n \".cem-edit-banner kbd{background:rgba(255,255,255,.18);padding:0 4px;border-radius:3px;font-size:11px}\",\n \".cem-exit-btn{position:absolute;right:8px;top:50%;transform:translateY(-50%);background:rgba(255,255,255,.15);border:1px solid rgba(255,255,255,.25);color:#fff;padding:4px 10px;border-radius:5px;font:12px system-ui,-apple-system,sans-serif;cursor:pointer;white-space:nowrap}\",\n \".cem-exit-btn:hover,.cem-exit-btn:focus-visible{background:rgba(255,255,255,.25);outline:none}\",\n \"[contenteditable=true]{cursor:text!important}\",\n \"[contenteditable=true]:hover{outline:2px dashed rgba(59,130,246,.5)!important;outline-offset:2px}\",\n \"[contenteditable=true]:focus{outline:2px solid #3b82f6!important;outline-offset:2px;background:rgba(59,130,246,.04)}\",\n \"[contenteditable=true].cem-changed{box-shadow:inset 3px 0 0 #10b981}\",\n `.cem-panel{position:fixed;right:16px;bottom:16px;z-index:2147483640;background:#fff;color:#111827;border:1px solid #e5e7eb;border-radius:12px;box-shadow:0 8px 30px rgba(0,0,0,.22);padding:10px;display:grid;gap:8px;width:min(320px,calc(100vw - 32px));font:13px/1.4 system-ui,-apple-system,sans-serif}`,\n \".cem-panel-actions{display:flex;gap:8px;flex-wrap:wrap}\",\n `.cem-copy-btn,.cem-review-btn,.cem-download-btn,.cem-open-link-btn{border:0;border-radius:8px;padding:9px 12px;font:600 13px system-ui,-apple-system,sans-serif;cursor:pointer;white-space:nowrap}`,\n `.cem-copy-btn{background:${accentColour};color:#fff;flex:1}`,\n \".cem-review-btn,.cem-download-btn,.cem-open-link-btn{background:#f3f4f6;color:#111827;border:1px solid #e5e7eb}\",\n \".cem-open-link-btn[hidden]{display:none}\",\n \".cem-copy-btn:hover,.cem-review-btn:hover,.cem-download-btn:hover,.cem-open-link-btn:hover{filter:brightness(.97)}\",\n \".cem-status{color:#4b5563;font-size:12px;min-height:17px}\",\n \".cem-review-list{max-height:220px;overflow-y:auto;display:grid;gap:6px;border-top:1px solid #e5e7eb;padding-top:8px}\",\n \".cem-review-list[hidden]{display:none}\",\n \".cem-review-empty{color:#6b7280;font-size:12px;margin:0;padding:4px 0}\",\n \".cem-review-item{display:grid;gap:4px;padding:8px;background:#f9fafb;border-radius:8px;border:1px solid #e5e7eb}\",\n \".cem-review-path{font-size:11px;color:#6b7280;font-weight:600;text-transform:uppercase;letter-spacing:.02em}\",\n \".cem-review-diff{font-size:12px;word-break:break-word}\",\n \".cem-review-diff del{color:#b91c1c;text-decoration:line-through;opacity:.75;display:block}\",\n \".cem-review-diff ins{color:#065f46;text-decoration:none;display:block}\",\n \".cem-revert-btn{justify-self:start;background:transparent;border:1px solid #e5e7eb;border-radius:6px;padding:3px 8px;font:12px system-ui,-apple-system,sans-serif;cursor:pointer;color:#374151}\",\n \".cem-revert-btn:hover{background:#fff}\",\n \".cem-toast{position:fixed;bottom:116px;right:16px;z-index:2147483641;background:#065f46;color:#fff;padding:10px 18px;border-radius:8px;font:14px system-ui,-apple-system,sans-serif;box-shadow:0 2px 12px rgba(0,0,0,.3);transition:opacity .3s}\",\n \"@media(max-width:520px){.cem-edit-banner{justify-content:flex-start;text-align:left}.cem-panel{left:16px;right:16px;width:auto}}\",\n ].join(\"\\n\");\n document.head.appendChild(style);\n return style;\n}\n\nfunction showToast(message: string) {\n document.querySelector(\".cem-toast\")?.remove();\n const toast = document.createElement(\"div\");\n toast.className = \"cem-toast\";\n toast.textContent = message;\n document.body.appendChild(toast);\n window.setTimeout(() => {\n toast.style.opacity = \"0\";\n window.setTimeout(() => toast.parentNode?.removeChild(toast), 300);\n }, 2500);\n}\n\nfunction fallbackCopy(text: string, label: string) {\n const textarea = document.createElement(\"textarea\");\n textarea.value = text;\n textarea.style.position = \"fixed\";\n textarea.style.left = \"-9999px\";\n document.body.appendChild(textarea);\n textarea.select();\n try {\n document.execCommand(\"copy\");\n } catch {\n // Ignore: the download button and autosaved draft still protect the work.\n }\n document.body.removeChild(textarea);\n showToast(label);\n}\n\nfunction getStorage() {\n try {\n const testKey = \"__cem_storage_test__\";\n window.localStorage.setItem(testKey, \"1\");\n window.localStorage.removeItem(testKey);\n return window.localStorage;\n } catch {\n try {\n return window.sessionStorage;\n } catch {\n return null;\n }\n }\n}\n\nfunction getDraftKey(config: Config) {\n const base = config.storageKey || `${config.sessionKey}:draft`;\n return `${base}:${window.location.origin}${window.location.pathname}`;\n}\n\nfunction escapeHtml(text: string) {\n return text.replace(/[&<>\"']/g, (char) => {\n switch (char) {\n case \"&\":\n return \"&amp;\";\n case \"<\":\n return \"&lt;\";\n case \">\":\n return \"&gt;\";\n case '\"':\n return \"&quot;\";\n default:\n return \"&#39;\";\n }\n });\n}\n\nfunction isEditableElement(el: Element, skipSelector: string) {\n if (el.closest(skipSelector)) return false;\n\n const text = el.textContent?.trim() ?? \"\";\n if (!text) return false;\n\n const children = Array.from(el.children);\n if (children.length > 0 && children.every((child) => SKIP_CHILD_TAGS.has(child.tagName))) {\n return false;\n }\n\n return true;\n}\n\nfunction getElementKey(el: HTMLElement) {\n if (el.dataset.editId) return `data-edit-id:${el.dataset.editId}`;\n if (el.id) return `id:${el.id}`;\n\n const parts: string[] = [];\n let current: Element | null = el;\n\n while (current && current !== document.body && current !== document.documentElement) {\n const parentElement: HTMLElement | null = current.parentElement;\n if (!parentElement) break;\n\n const currentTag = current.tagName;\n const siblings = Array.from(parentElement.children).filter((child) => child.tagName === currentTag);\n const index = siblings.indexOf(current) + 1;\n parts.unshift(`${current.tagName.toLowerCase()}:nth-of-type(${index})`);\n current = parentElement;\n }\n\n return parts.join(\" > \");\n}\n\nfunction getEditPath(el: HTMLElement) {\n let context = \"\";\n let current = el.parentElement;\n\n while (current && current !== document.body && current !== document.documentElement) {\n if (current.id) {\n context = `#${current.id}`;\n break;\n }\n\n const section = current.getAttribute(\"data-section\");\n if (section) {\n context = section;\n break;\n }\n\n if (current.tagName === \"SECTION\") {\n const heading = current.querySelector(\"h1,h2,h3\");\n if (heading?.textContent) {\n context = heading.textContent.trim().slice(0, 40);\n break;\n }\n }\n\n if ([\"HEADER\", \"FOOTER\", \"MAIN\", \"NAV\"].includes(current.tagName)) {\n context = current.tagName.toLowerCase();\n break;\n }\n\n current = current.parentElement;\n }\n\n const tag = el.tagName.toLowerCase();\n const text = el.dataset.editOriginal || el.textContent?.trim() || \"\";\n const snippet = text.slice(0, 40) + (text.length > 40 ? \"...\" : \"\");\n return `${context ? `${context} > ` : \"\"}${tag}: \"${snippet}\"`;\n}\n\nfunction rewriteLinks(queryParam: string, queryValue: string | null) {\n document.querySelectorAll<HTMLAnchorElement>(\"a[href]\").forEach((anchor) => {\n const href = anchor.getAttribute(\"href\");\n if (!href || /^(https?:|mailto:|tel:|javascript:)/i.test(href)) return;\n\n try {\n const url = new URL(href, window.location.href);\n if (url.origin !== window.location.origin) return;\n\n url.searchParams.set(queryParam, queryValue ?? \"1\");\n const hash = url.hash;\n url.hash = \"\";\n anchor.setAttribute(\"href\", `${url.pathname}${url.search}${hash}`);\n } catch {\n // Leave unusual hrefs unchanged.\n }\n });\n}\n\nfunction buildPayload(changes: EditModeChange[]): EditModePayload {\n return {\n site: window.location.hostname,\n page: window.location.pathname,\n pageTitle: document.title,\n url: window.location.href,\n timestamp: new Date().toISOString(),\n changes,\n };\n}\n\nfunction loadDraft(storage: Storage | null, key: string): Draft | null {\n if (!storage) return null;\n\n try {\n const raw = storage.getItem(key);\n if (!raw) return null;\n const draft = JSON.parse(raw) as Draft;\n return draft?.version === DRAFT_VERSION && Array.isArray(draft.changes) ? draft : null;\n } catch {\n return null;\n }\n}\n\nfunction saveDraft(storage: Storage | null, key: string, draft: Draft) {\n if (!storage) return false;\n\n try {\n storage.setItem(key, JSON.stringify(draft));\n return true;\n } catch {\n return false;\n }\n}\n\nfunction downloadText(filename: string, text: string) {\n const blob = new Blob([text], { type: \"application/json\" });\n const url = URL.createObjectURL(blob);\n const anchor = document.createElement(\"a\");\n anchor.href = url;\n anchor.download = filename;\n anchor.style.display = \"none\";\n document.body.appendChild(anchor);\n anchor.click();\n anchor.remove();\n window.setTimeout(() => URL.revokeObjectURL(url), 1000);\n}\n\nfunction formatTime(date = new Date()) {\n return date.toLocaleTimeString([], { hour: \"2-digit\", minute: \"2-digit\" });\n}\n\nfunction initEditableElements(config: Config) {\n // querySelectorAll returns document order, so an ancestor is always visited\n // before its descendants — skipping already-marked ancestors here prevents\n // nested contenteditable regions (e.g. an <a> inside an editable <p>), which\n // would otherwise report the same edit twice.\n document.querySelectorAll<HTMLElement>(config.editableSelector).forEach((el) => {\n if (!isEditableElement(el, config.skipSelector)) return;\n if (el.closest(\"[data-edit-key]\")) return;\n\n const text = el.textContent?.trim() ?? \"\";\n el.contentEditable = \"true\";\n if (!el.dataset.editOriginal) el.dataset.editOriginal = text;\n if (!el.dataset.editKey) el.dataset.editKey = getElementKey(el);\n });\n}\n\nfunction findEditableByKey(editableSelector: string, key: string) {\n const elements = document.querySelectorAll<HTMLElement>(editableSelector);\n for (const el of Array.from(elements)) {\n if (el.dataset.editKey === key) return el;\n }\n return null;\n}\n\nfunction applyDraft(draft: Draft | null, editableSelector: string) {\n if (!draft) return 0;\n\n const edits = new Map(draft.changes.map((change) => [change.key, change]));\n let restored = 0;\n\n document.querySelectorAll<HTMLElement>(editableSelector).forEach((el) => {\n const key = el.dataset.editKey;\n if (!key) return;\n\n const saved = edits.get(key);\n if (!saved) return;\n\n // Avoid stomping over changed templates. Restore only when the original still matches.\n if (el.dataset.editOriginal !== saved.original) return;\n\n el.textContent = saved.new;\n restored += 1;\n });\n\n return restored;\n}\n\nfunction markChanged(el: HTMLElement) {\n const original = el.dataset.editOriginal;\n if (original === undefined) return;\n const current = el.textContent?.trim() ?? \"\";\n el.classList.toggle(\"cem-changed\", original !== current);\n}\n\nfunction collectDraftChanges(editableSelector: string): DraftChange[] {\n const changes: DraftChange[] = [];\n\n document.querySelectorAll<HTMLElement>(editableSelector).forEach((el) => {\n if (!el.isContentEditable && el.contentEditable !== \"true\") return;\n const original = el.dataset.editOriginal;\n const key = el.dataset.editKey;\n if (original === undefined || !key) return;\n\n const current = el.textContent?.trim() ?? \"\";\n const changed = original !== current;\n el.classList.toggle(\"cem-changed\", changed);\n\n if (changed) {\n changes.push({\n key,\n path: getEditPath(el),\n tag: el.tagName,\n original,\n new: current,\n });\n }\n });\n\n return changes;\n}\n\nfunction collectChanges(editableSelector: string): EditModeChange[] {\n return collectDraftChanges(editableSelector).map(({ key: _key, ...change }) => change);\n}\n\nexport function initClientEditMode(options: EditModeOptions = {}): EditModeInstance {\n if (!hasDom()) {\n return { active: false, getChanges: () => [], destroy: () => undefined };\n }\n\n const config: Config = { ...defaults, ...options };\n\n if (!shouldEnable(config)) {\n return { active: false, getChanges: () => [], destroy: () => undefined };\n }\n\n if (window.__COLLIDE_EDIT_MODE_ACTIVE) {\n return { active: true, getChanges: () => collectChanges(config.editableSelector), destroy: () => undefined };\n }\n\n window.__COLLIDE_EDIT_MODE_ACTIVE = true;\n window.sessionStorage.setItem(config.sessionKey, \"1\");\n\n const storage = getStorage();\n const draftKey = getDraftKey(config);\n const style = addStyles(config.accentColour);\n\n const banner = document.createElement(\"div\");\n banner.className = \"cem-edit-banner\";\n banner.innerHTML = `✏️ <strong>${config.brandName}</strong> <span>Click text to edit. Press <kbd>Esc</kbd> when done.</span>`;\n\n const exitButton = document.createElement(\"button\");\n exitButton.className = \"cem-exit-btn\";\n exitButton.type = \"button\";\n exitButton.textContent = \"Exit\";\n banner.appendChild(exitButton);\n document.body.prepend(banner);\n\n const panel = document.createElement(\"div\");\n panel.className = \"cem-panel\";\n panel.innerHTML = `\n <div class=\"cem-panel-actions\">\n <button class=\"cem-copy-btn\" type=\"button\">📋 Copy Changes</button>\n <button class=\"cem-review-btn\" type=\"button\" aria-expanded=\"false\">Review</button>\n <button class=\"cem-download-btn\" type=\"button\">Download backup</button>\n <button class=\"cem-open-link-btn\" type=\"button\" hidden>Open link</button>\n </div>\n <div class=\"cem-review-list\" hidden></div>\n <div class=\"cem-status\" aria-live=\"polite\">Auto-save ready</div>\n `;\n document.body.appendChild(panel);\n\n const copyButton = panel.querySelector<HTMLButtonElement>(\".cem-copy-btn\");\n const reviewButton = panel.querySelector<HTMLButtonElement>(\".cem-review-btn\");\n const downloadButton = panel.querySelector<HTMLButtonElement>(\".cem-download-btn\");\n const openLinkButton = panel.querySelector<HTMLButtonElement>(\".cem-open-link-btn\");\n const reviewList = panel.querySelector<HTMLElement>(\".cem-review-list\");\n const status = panel.querySelector<HTMLElement>(\".cem-status\");\n let activeLink: HTMLAnchorElement | null = null;\n let reviewOpen = false;\n let currentDraftChanges: DraftChange[] = [];\n\n document.querySelectorAll(\"[data-reveal]\").forEach((el) => el.classList.add(\"is-visible\"));\n rewriteLinks(config.queryParam, config.queryValue);\n initEditableElements(config);\n\n const restoredCount = applyDraft(loadDraft(storage, draftKey), config.editableSelector);\n\n const makeDraft = (): Draft => ({\n version: DRAFT_VERSION,\n page: window.location.pathname,\n url: window.location.href,\n updatedAt: new Date().toISOString(),\n changes: collectDraftChanges(config.editableSelector),\n });\n\n const renderReviewList = () => {\n if (!reviewList) return;\n\n if (currentDraftChanges.length === 0) {\n reviewList.innerHTML = '<p class=\"cem-review-empty\">No changes yet.</p>';\n return;\n }\n\n reviewList.innerHTML = currentDraftChanges\n .map(\n (change, index) => `\n <div class=\"cem-review-item\">\n <div class=\"cem-review-path\">${escapeHtml(change.path)}</div>\n <div class=\"cem-review-diff\"><del>${escapeHtml(change.original)}</del><ins>${escapeHtml(change.new)}</ins></div>\n <button class=\"cem-revert-btn\" type=\"button\" data-index=\"${index}\">Revert</button>\n </div>\n `,\n )\n .join(\"\");\n };\n\n const renderStatus = (count: number, message?: string) => {\n if (copyButton) copyButton.textContent = count > 0 ? `📋 Copy Changes (${count})` : \"📋 Copy Changes\";\n if (openLinkButton) {\n openLinkButton.hidden = !activeLink;\n openLinkButton.textContent = activeLink ? `Open ${activeLink.hostname || \"link\"}` : \"Open link\";\n }\n if (status) {\n status.textContent =\n message ||\n `Auto-saved ${formatTime()} • ${count} change${count === 1 ? \"\" : \"s\"} • Links: edit text or Ctrl/⌘-click to open`;\n }\n };\n\n const updateUi = (message?: string) => {\n renderStatus(collectChanges(config.editableSelector).length, message);\n };\n\n const persistDraft = () => {\n const draft = makeDraft();\n currentDraftChanges = draft.changes;\n const saved = config.autoSave !== false && saveDraft(storage, draftKey, draft);\n renderStatus(draft.changes.length, saved || config.autoSave === false ? undefined : \"⚠️ Auto-save unavailable — use Download backup\");\n if (reviewOpen) renderReviewList();\n return draft;\n };\n\n let saveTimeout: number | null = null;\n\n const flushDraftSave = () => {\n if (saveTimeout !== null) {\n window.clearTimeout(saveTimeout);\n saveTimeout = null;\n }\n return persistDraft();\n };\n\n const scheduleDraftSave = () => {\n if (saveTimeout !== null) window.clearTimeout(saveTimeout);\n saveTimeout = window.setTimeout(() => {\n saveTimeout = null;\n persistDraft();\n }, AUTOSAVE_DEBOUNCE_MS);\n };\n\n const finishEditing = () => {\n const activeElement = document.activeElement;\n if (!(activeElement instanceof HTMLElement)) return false;\n if (!activeElement.isContentEditable && activeElement.contentEditable !== \"true\") return false;\n\n activeElement.blur();\n window.getSelection()?.removeAllRanges();\n flushDraftSave();\n return true;\n };\n\n const handleEditingKeydown = (event: KeyboardEvent) => {\n if (event.key === \"Escape\") {\n if (!finishEditing()) return;\n event.preventDefault();\n event.stopPropagation();\n return;\n }\n\n if (event.key === \"Enter\" && !event.shiftKey) {\n const target = event.target;\n if (\n target instanceof HTMLElement &&\n SINGLE_LINE_TAGS.has(target.tagName) &&\n (target.isContentEditable || target.contentEditable === \"true\")\n ) {\n event.preventDefault();\n finishEditing();\n }\n }\n };\n\n const handleDocumentClick = (event: MouseEvent) => {\n const target = event.target;\n if (!(target instanceof Element)) return;\n\n const anchor = target.closest<HTMLAnchorElement>(\"a[href]\");\n if (anchor && !anchor.closest(\".cem-panel\") && !anchor.closest(\".cem-edit-banner\")) {\n activeLink = anchor;\n updateUi(\"Link selected — edit the text, use Open link, or Ctrl/⌘-click to visit it\");\n\n if (event.metaKey || event.ctrlKey || event.altKey) {\n flushDraftSave();\n return;\n }\n\n event.preventDefault();\n return;\n }\n\n const button = target.closest(\"button\");\n if (button && !button.closest(\".cem-panel\") && !button.closest(\".cem-edit-banner\")) {\n event.preventDefault();\n event.stopImmediatePropagation();\n }\n };\n\n const handleFocusIn = (event: FocusEvent) => {\n const target = event.target;\n if (!(target instanceof Element)) return;\n\n activeLink = target.closest<HTMLAnchorElement>(\"a[href]\");\n updateUi(activeLink ? \"Link selected — edit the text or use Open link to navigate\" : undefined);\n };\n\n const handleInput = (event: Event) => {\n if (event.target instanceof HTMLElement) markChanged(event.target);\n scheduleDraftSave();\n };\n const handlePageHide = () => flushDraftSave();\n const handleVisibilityChange = () => {\n if (document.visibilityState === \"hidden\") flushDraftSave();\n };\n\n document.addEventListener(\"keydown\", handleEditingKeydown);\n document.addEventListener(\"click\", handleDocumentClick, true);\n document.addEventListener(\"focusin\", handleFocusIn);\n document.addEventListener(\"input\", handleInput);\n window.addEventListener(\"pagehide\", handlePageHide);\n document.addEventListener(\"visibilitychange\", handleVisibilityChange);\n\n reviewButton?.addEventListener(\"click\", () => {\n reviewOpen = !reviewOpen;\n if (reviewList) reviewList.hidden = !reviewOpen;\n reviewButton.textContent = reviewOpen ? \"Hide review\" : \"Review\";\n reviewButton.setAttribute(\"aria-expanded\", String(reviewOpen));\n if (reviewOpen) renderReviewList();\n });\n\n reviewList?.addEventListener(\"click\", (event) => {\n const target = event.target;\n if (!(target instanceof HTMLElement)) return;\n\n const revertButton = target.closest<HTMLButtonElement>(\".cem-revert-btn\");\n if (!revertButton) return;\n\n const change = currentDraftChanges[Number(revertButton.dataset.index)];\n if (!change) return;\n\n const el = findEditableByKey(config.editableSelector, change.key);\n if (el) el.textContent = change.original;\n\n flushDraftSave();\n });\n\n copyButton?.addEventListener(\"click\", () => {\n const draft = flushDraftSave();\n const payload = buildPayload(draft.changes);\n options.onCopy?.(payload);\n\n const copyPayload = options.mapPayload ? options.mapPayload(payload) : payload;\n const json = JSON.stringify(copyPayload, null, 2);\n const label = `✓ Copied ${draft.changes.length} change${draft.changes.length !== 1 ? \"s\" : \"\"} to clipboard`;\n\n if (navigator.clipboard?.writeText) {\n navigator.clipboard.writeText(json).then(() => showToast(label)).catch(() => fallbackCopy(json, label));\n } else {\n fallbackCopy(json, label);\n }\n });\n\n downloadButton?.addEventListener(\"click\", () => {\n const draft = flushDraftSave();\n const payload = buildPayload(draft.changes);\n const date = new Date().toISOString().slice(0, 10);\n downloadText(`edit-mode-${window.location.hostname}-${date}.json`, JSON.stringify(payload, null, 2));\n showToast(\"✓ Backup downloaded\");\n });\n\n openLinkButton?.addEventListener(\"click\", () => {\n if (!activeLink?.href) return;\n\n flushDraftSave();\n window.location.href = activeLink.href;\n });\n\n exitButton.addEventListener(\"click\", () => {\n flushDraftSave();\n window.sessionStorage.removeItem(config.sessionKey);\n const url = new URL(window.location.href);\n url.searchParams.delete(config.queryParam);\n window.location.href = url.toString();\n });\n\n persistDraft();\n if (restoredCount > 0) updateUi(`Restored ${restoredCount} saved edit${restoredCount === 1 ? \"\" : \"s\"} • Auto-save on`);\n\n return {\n active: true,\n getChanges: () => collectChanges(config.editableSelector),\n destroy: () => {\n flushDraftSave();\n document.removeEventListener(\"keydown\", handleEditingKeydown);\n document.removeEventListener(\"click\", handleDocumentClick, true);\n document.removeEventListener(\"focusin\", handleFocusIn);\n document.removeEventListener(\"input\", handleInput);\n window.removeEventListener(\"pagehide\", handlePageHide);\n document.removeEventListener(\"visibilitychange\", handleVisibilityChange);\n banner.remove();\n panel.remove();\n style.remove();\n document.querySelectorAll<HTMLElement>(config.editableSelector).forEach((el) => {\n if (el.dataset.editOriginal !== undefined) {\n el.contentEditable = \"false\";\n el.classList.remove(\"cem-changed\");\n delete el.dataset.editOriginal;\n delete el.dataset.editKey;\n }\n });\n window.__COLLIDE_EDIT_MODE_ACTIVE = false;\n },\n };\n}\n"],"mappings":"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","names":[]}
package/dist/index.js CHANGED
@@ -2,7 +2,24 @@
2
2
  var DEFAULT_EDITABLE_SELECTOR = "h1,h2,h3,h4,h5,h6,p,li,blockquote,figcaption,label,legend,dt,dd,th,td,a,button";
3
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-panel,.cem-toast";
4
4
  var SKIP_CHILD_TAGS = /* @__PURE__ */ new Set(["IMG", "PICTURE", "SVG", "VIDEO", "CANVAS", "IFRAME", "SCRIPT", "STYLE"]);
5
+ var SINGLE_LINE_TAGS = /* @__PURE__ */ new Set([
6
+ "H1",
7
+ "H2",
8
+ "H3",
9
+ "H4",
10
+ "H5",
11
+ "H6",
12
+ "BUTTON",
13
+ "LABEL",
14
+ "LEGEND",
15
+ "DT",
16
+ "TH",
17
+ "TD",
18
+ "FIGCAPTION",
19
+ "A"
20
+ ]);
5
21
  var DRAFT_VERSION = 1;
22
+ var AUTOSAVE_DEBOUNCE_MS = 400;
6
23
  var defaults = {
7
24
  queryParam: "edit",
8
25
  queryValue: "true",
@@ -34,14 +51,25 @@ function addStyles(accentColour) {
34
51
  "[contenteditable=true]{cursor:text!important}",
35
52
  "[contenteditable=true]:hover{outline:2px dashed rgba(59,130,246,.5)!important;outline-offset:2px}",
36
53
  "[contenteditable=true]:focus{outline:2px solid #3b82f6!important;outline-offset:2px;background:rgba(59,130,246,.04)}",
54
+ "[contenteditable=true].cem-changed{box-shadow:inset 3px 0 0 #10b981}",
37
55
  `.cem-panel{position:fixed;right:16px;bottom:16px;z-index:2147483640;background:#fff;color:#111827;border:1px solid #e5e7eb;border-radius:12px;box-shadow:0 8px 30px rgba(0,0,0,.22);padding:10px;display:grid;gap:8px;width:min(320px,calc(100vw - 32px));font:13px/1.4 system-ui,-apple-system,sans-serif}`,
38
56
  ".cem-panel-actions{display:flex;gap:8px;flex-wrap:wrap}",
39
- `.cem-copy-btn,.cem-download-btn,.cem-open-link-btn{border:0;border-radius:8px;padding:9px 12px;font:600 13px system-ui,-apple-system,sans-serif;cursor:pointer;white-space:nowrap}`,
57
+ `.cem-copy-btn,.cem-review-btn,.cem-download-btn,.cem-open-link-btn{border:0;border-radius:8px;padding:9px 12px;font:600 13px system-ui,-apple-system,sans-serif;cursor:pointer;white-space:nowrap}`,
40
58
  `.cem-copy-btn{background:${accentColour};color:#fff;flex:1}`,
41
- ".cem-download-btn,.cem-open-link-btn{background:#f3f4f6;color:#111827;border:1px solid #e5e7eb}",
59
+ ".cem-review-btn,.cem-download-btn,.cem-open-link-btn{background:#f3f4f6;color:#111827;border:1px solid #e5e7eb}",
42
60
  ".cem-open-link-btn[hidden]{display:none}",
43
- ".cem-copy-btn:hover,.cem-download-btn:hover,.cem-open-link-btn:hover{filter:brightness(.97)}",
61
+ ".cem-copy-btn:hover,.cem-review-btn:hover,.cem-download-btn:hover,.cem-open-link-btn:hover{filter:brightness(.97)}",
44
62
  ".cem-status{color:#4b5563;font-size:12px;min-height:17px}",
63
+ ".cem-review-list{max-height:220px;overflow-y:auto;display:grid;gap:6px;border-top:1px solid #e5e7eb;padding-top:8px}",
64
+ ".cem-review-list[hidden]{display:none}",
65
+ ".cem-review-empty{color:#6b7280;font-size:12px;margin:0;padding:4px 0}",
66
+ ".cem-review-item{display:grid;gap:4px;padding:8px;background:#f9fafb;border-radius:8px;border:1px solid #e5e7eb}",
67
+ ".cem-review-path{font-size:11px;color:#6b7280;font-weight:600;text-transform:uppercase;letter-spacing:.02em}",
68
+ ".cem-review-diff{font-size:12px;word-break:break-word}",
69
+ ".cem-review-diff del{color:#b91c1c;text-decoration:line-through;opacity:.75;display:block}",
70
+ ".cem-review-diff ins{color:#065f46;text-decoration:none;display:block}",
71
+ ".cem-revert-btn{justify-self:start;background:transparent;border:1px solid #e5e7eb;border-radius:6px;padding:3px 8px;font:12px system-ui,-apple-system,sans-serif;cursor:pointer;color:#374151}",
72
+ ".cem-revert-btn:hover{background:#fff}",
45
73
  ".cem-toast{position:fixed;bottom:116px;right:16px;z-index:2147483641;background:#065f46;color:#fff;padding:10px 18px;border-radius:8px;font:14px system-ui,-apple-system,sans-serif;box-shadow:0 2px 12px rgba(0,0,0,.3);transition:opacity .3s}",
46
74
  "@media(max-width:520px){.cem-edit-banner{justify-content:flex-start;text-align:left}.cem-panel{left:16px;right:16px;width:auto}}"
47
75
  ].join("\n");
@@ -91,6 +119,22 @@ function getDraftKey(config) {
91
119
  const base = config.storageKey || `${config.sessionKey}:draft`;
92
120
  return `${base}:${window.location.origin}${window.location.pathname}`;
93
121
  }
122
+ function escapeHtml(text) {
123
+ return text.replace(/[&<>"']/g, (char) => {
124
+ switch (char) {
125
+ case "&":
126
+ return "&amp;";
127
+ case "<":
128
+ return "&lt;";
129
+ case ">":
130
+ return "&gt;";
131
+ case '"':
132
+ return "&quot;";
133
+ default:
134
+ return "&#39;";
135
+ }
136
+ });
137
+ }
94
138
  function isEditableElement(el, skipSelector) {
95
139
  if (el.closest(skipSelector)) return false;
96
140
  const text = el.textContent?.trim() ?? "";
@@ -211,12 +255,20 @@ function formatTime(date = /* @__PURE__ */ new Date()) {
211
255
  function initEditableElements(config) {
212
256
  document.querySelectorAll(config.editableSelector).forEach((el) => {
213
257
  if (!isEditableElement(el, config.skipSelector)) return;
258
+ if (el.closest("[data-edit-key]")) return;
214
259
  const text = el.textContent?.trim() ?? "";
215
260
  el.contentEditable = "true";
216
261
  if (!el.dataset.editOriginal) el.dataset.editOriginal = text;
217
262
  if (!el.dataset.editKey) el.dataset.editKey = getElementKey(el);
218
263
  });
219
264
  }
265
+ function findEditableByKey(editableSelector, key) {
266
+ const elements = document.querySelectorAll(editableSelector);
267
+ for (const el of Array.from(elements)) {
268
+ if (el.dataset.editKey === key) return el;
269
+ }
270
+ return null;
271
+ }
220
272
  function applyDraft(draft, editableSelector) {
221
273
  if (!draft) return 0;
222
274
  const edits = new Map(draft.changes.map((change) => [change.key, change]));
@@ -232,6 +284,12 @@ function applyDraft(draft, editableSelector) {
232
284
  });
233
285
  return restored;
234
286
  }
287
+ function markChanged(el) {
288
+ const original = el.dataset.editOriginal;
289
+ if (original === void 0) return;
290
+ const current = el.textContent?.trim() ?? "";
291
+ el.classList.toggle("cem-changed", original !== current);
292
+ }
235
293
  function collectDraftChanges(editableSelector) {
236
294
  const changes = [];
237
295
  document.querySelectorAll(editableSelector).forEach((el) => {
@@ -240,7 +298,9 @@ function collectDraftChanges(editableSelector) {
240
298
  const key = el.dataset.editKey;
241
299
  if (original === void 0 || !key) return;
242
300
  const current = el.textContent?.trim() ?? "";
243
- if (original !== current) {
301
+ const changed = original !== current;
302
+ el.classList.toggle("cem-changed", changed);
303
+ if (changed) {
244
304
  changes.push({
245
305
  key,
246
306
  path: getEditPath(el),
@@ -285,17 +345,23 @@ function initClientEditMode(options = {}) {
285
345
  panel.innerHTML = `
286
346
  <div class="cem-panel-actions">
287
347
  <button class="cem-copy-btn" type="button">\u{1F4CB} Copy Changes</button>
348
+ <button class="cem-review-btn" type="button" aria-expanded="false">Review</button>
288
349
  <button class="cem-download-btn" type="button">Download backup</button>
289
350
  <button class="cem-open-link-btn" type="button" hidden>Open link</button>
290
351
  </div>
352
+ <div class="cem-review-list" hidden></div>
291
353
  <div class="cem-status" aria-live="polite">Auto-save ready</div>
292
354
  `;
293
355
  document.body.appendChild(panel);
294
356
  const copyButton = panel.querySelector(".cem-copy-btn");
357
+ const reviewButton = panel.querySelector(".cem-review-btn");
295
358
  const downloadButton = panel.querySelector(".cem-download-btn");
296
359
  const openLinkButton = panel.querySelector(".cem-open-link-btn");
360
+ const reviewList = panel.querySelector(".cem-review-list");
297
361
  const status = panel.querySelector(".cem-status");
298
362
  let activeLink = null;
363
+ let reviewOpen = false;
364
+ let currentDraftChanges = [];
299
365
  document.querySelectorAll("[data-reveal]").forEach((el) => el.classList.add("is-visible"));
300
366
  rewriteLinks(config.queryParam, config.queryValue);
301
367
  initEditableElements(config);
@@ -307,8 +373,23 @@ function initClientEditMode(options = {}) {
307
373
  updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
308
374
  changes: collectDraftChanges(config.editableSelector)
309
375
  });
310
- const updateUi = (message) => {
311
- const count = collectChanges(config.editableSelector).length;
376
+ const renderReviewList = () => {
377
+ if (!reviewList) return;
378
+ if (currentDraftChanges.length === 0) {
379
+ reviewList.innerHTML = '<p class="cem-review-empty">No changes yet.</p>';
380
+ return;
381
+ }
382
+ reviewList.innerHTML = currentDraftChanges.map(
383
+ (change, index) => `
384
+ <div class="cem-review-item">
385
+ <div class="cem-review-path">${escapeHtml(change.path)}</div>
386
+ <div class="cem-review-diff"><del>${escapeHtml(change.original)}</del><ins>${escapeHtml(change.new)}</ins></div>
387
+ <button class="cem-revert-btn" type="button" data-index="${index}">Revert</button>
388
+ </div>
389
+ `
390
+ ).join("");
391
+ };
392
+ const renderStatus = (count, message) => {
312
393
  if (copyButton) copyButton.textContent = count > 0 ? `\u{1F4CB} Copy Changes (${count})` : "\u{1F4CB} Copy Changes";
313
394
  if (openLinkButton) {
314
395
  openLinkButton.hidden = !activeLink;
@@ -318,26 +399,55 @@ function initClientEditMode(options = {}) {
318
399
  status.textContent = message || `Auto-saved ${formatTime()} \u2022 ${count} change${count === 1 ? "" : "s"} \u2022 Links: edit text or Ctrl/\u2318-click to open`;
319
400
  }
320
401
  };
402
+ const updateUi = (message) => {
403
+ renderStatus(collectChanges(config.editableSelector).length, message);
404
+ };
321
405
  const persistDraft = () => {
322
406
  const draft = makeDraft();
407
+ currentDraftChanges = draft.changes;
323
408
  const saved = config.autoSave !== false && saveDraft(storage, draftKey, draft);
324
- updateUi(saved || config.autoSave === false ? void 0 : "\u26A0\uFE0F Auto-save unavailable \u2014 use Download backup");
409
+ renderStatus(draft.changes.length, saved || config.autoSave === false ? void 0 : "\u26A0\uFE0F Auto-save unavailable \u2014 use Download backup");
410
+ if (reviewOpen) renderReviewList();
325
411
  return draft;
326
412
  };
413
+ let saveTimeout = null;
414
+ const flushDraftSave = () => {
415
+ if (saveTimeout !== null) {
416
+ window.clearTimeout(saveTimeout);
417
+ saveTimeout = null;
418
+ }
419
+ return persistDraft();
420
+ };
421
+ const scheduleDraftSave = () => {
422
+ if (saveTimeout !== null) window.clearTimeout(saveTimeout);
423
+ saveTimeout = window.setTimeout(() => {
424
+ saveTimeout = null;
425
+ persistDraft();
426
+ }, AUTOSAVE_DEBOUNCE_MS);
427
+ };
327
428
  const finishEditing = () => {
328
429
  const activeElement = document.activeElement;
329
430
  if (!(activeElement instanceof HTMLElement)) return false;
330
431
  if (!activeElement.isContentEditable && activeElement.contentEditable !== "true") return false;
331
432
  activeElement.blur();
332
433
  window.getSelection()?.removeAllRanges();
333
- persistDraft();
434
+ flushDraftSave();
334
435
  return true;
335
436
  };
336
- const finishEditingOnEscape = (event) => {
337
- if (event.key !== "Escape") return;
338
- if (!finishEditing()) return;
339
- event.preventDefault();
340
- event.stopPropagation();
437
+ const handleEditingKeydown = (event) => {
438
+ if (event.key === "Escape") {
439
+ if (!finishEditing()) return;
440
+ event.preventDefault();
441
+ event.stopPropagation();
442
+ return;
443
+ }
444
+ if (event.key === "Enter" && !event.shiftKey) {
445
+ const target = event.target;
446
+ if (target instanceof HTMLElement && SINGLE_LINE_TAGS.has(target.tagName) && (target.isContentEditable || target.contentEditable === "true")) {
447
+ event.preventDefault();
448
+ finishEditing();
449
+ }
450
+ }
341
451
  };
342
452
  const handleDocumentClick = (event) => {
343
453
  const target = event.target;
@@ -347,7 +457,7 @@ function initClientEditMode(options = {}) {
347
457
  activeLink = anchor;
348
458
  updateUi("Link selected \u2014 edit the text, use Open link, or Ctrl/\u2318-click to visit it");
349
459
  if (event.metaKey || event.ctrlKey || event.altKey) {
350
- persistDraft();
460
+ flushDraftSave();
351
461
  return;
352
462
  }
353
463
  event.preventDefault();
@@ -365,19 +475,40 @@ function initClientEditMode(options = {}) {
365
475
  activeLink = target.closest("a[href]");
366
476
  updateUi(activeLink ? "Link selected \u2014 edit the text or use Open link to navigate" : void 0);
367
477
  };
368
- const handleInput = () => persistDraft();
369
- const handlePageHide = () => persistDraft();
478
+ const handleInput = (event) => {
479
+ if (event.target instanceof HTMLElement) markChanged(event.target);
480
+ scheduleDraftSave();
481
+ };
482
+ const handlePageHide = () => flushDraftSave();
370
483
  const handleVisibilityChange = () => {
371
- if (document.visibilityState === "hidden") persistDraft();
484
+ if (document.visibilityState === "hidden") flushDraftSave();
372
485
  };
373
- document.addEventListener("keydown", finishEditingOnEscape);
486
+ document.addEventListener("keydown", handleEditingKeydown);
374
487
  document.addEventListener("click", handleDocumentClick, true);
375
488
  document.addEventListener("focusin", handleFocusIn);
376
489
  document.addEventListener("input", handleInput);
377
490
  window.addEventListener("pagehide", handlePageHide);
378
491
  document.addEventListener("visibilitychange", handleVisibilityChange);
492
+ reviewButton?.addEventListener("click", () => {
493
+ reviewOpen = !reviewOpen;
494
+ if (reviewList) reviewList.hidden = !reviewOpen;
495
+ reviewButton.textContent = reviewOpen ? "Hide review" : "Review";
496
+ reviewButton.setAttribute("aria-expanded", String(reviewOpen));
497
+ if (reviewOpen) renderReviewList();
498
+ });
499
+ reviewList?.addEventListener("click", (event) => {
500
+ const target = event.target;
501
+ if (!(target instanceof HTMLElement)) return;
502
+ const revertButton = target.closest(".cem-revert-btn");
503
+ if (!revertButton) return;
504
+ const change = currentDraftChanges[Number(revertButton.dataset.index)];
505
+ if (!change) return;
506
+ const el = findEditableByKey(config.editableSelector, change.key);
507
+ if (el) el.textContent = change.original;
508
+ flushDraftSave();
509
+ });
379
510
  copyButton?.addEventListener("click", () => {
380
- const draft = persistDraft();
511
+ const draft = flushDraftSave();
381
512
  const payload = buildPayload(draft.changes);
382
513
  options.onCopy?.(payload);
383
514
  const copyPayload = options.mapPayload ? options.mapPayload(payload) : payload;
@@ -390,7 +521,7 @@ function initClientEditMode(options = {}) {
390
521
  }
391
522
  });
392
523
  downloadButton?.addEventListener("click", () => {
393
- const draft = persistDraft();
524
+ const draft = flushDraftSave();
394
525
  const payload = buildPayload(draft.changes);
395
526
  const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
396
527
  downloadText(`edit-mode-${window.location.hostname}-${date}.json`, JSON.stringify(payload, null, 2));
@@ -398,11 +529,11 @@ function initClientEditMode(options = {}) {
398
529
  });
399
530
  openLinkButton?.addEventListener("click", () => {
400
531
  if (!activeLink?.href) return;
401
- persistDraft();
532
+ flushDraftSave();
402
533
  window.location.href = activeLink.href;
403
534
  });
404
535
  exitButton.addEventListener("click", () => {
405
- persistDraft();
536
+ flushDraftSave();
406
537
  window.sessionStorage.removeItem(config.sessionKey);
407
538
  const url = new URL(window.location.href);
408
539
  url.searchParams.delete(config.queryParam);
@@ -414,8 +545,8 @@ function initClientEditMode(options = {}) {
414
545
  active: true,
415
546
  getChanges: () => collectChanges(config.editableSelector),
416
547
  destroy: () => {
417
- persistDraft();
418
- document.removeEventListener("keydown", finishEditingOnEscape);
548
+ flushDraftSave();
549
+ document.removeEventListener("keydown", handleEditingKeydown);
419
550
  document.removeEventListener("click", handleDocumentClick, true);
420
551
  document.removeEventListener("focusin", handleFocusIn);
421
552
  document.removeEventListener("input", handleInput);
@@ -427,6 +558,7 @@ function initClientEditMode(options = {}) {
427
558
  document.querySelectorAll(config.editableSelector).forEach((el) => {
428
559
  if (el.dataset.editOriginal !== void 0) {
429
560
  el.contentEditable = "false";
561
+ el.classList.remove("cem-changed");
430
562
  delete el.dataset.editOriginal;
431
563
  delete el.dataset.editKey;
432
564
  }