{"version":3,"file":"toolbar-CG22TbK7.mjs","names":[],"sources":["../src/visual-editing/toolbar-bootstrap.ts","../src/visual-editing/toolbar.ts"],"sourcesContent":["/**\n * EmDash Toolbar Bootstrap (toolbar: \"client\")\n *\n * A tiny script injected into every public HTML response when the client\n * toolbar mode is enabled. It is identical for every visitor, so the HTML\n * stays fully cacheable by shared caches (Workers Cache, Cache Everything,\n * Fastly, Varnish, …).\n *\n * Behavior: if this browser has logged into the admin (non-secret\n * localStorage flag set by the admin SPA), render a small \"Edit\" pill.\n * Clicking it verifies the session server-side and reloads the page with the\n * `_edit` query param — that URL is always rendered fresh with the full\n * server-side toolbar. Logged-out browsers pay one localStorage read and\n * nothing else. See Discussion #1742.\n */\n\n/**\n * Non-secret localStorage flag set by the admin SPA when an editor session\n * exists in this browser. It only means \"a session may exist\" — the click\n * handler verifies the real session before entering edit mode. The literal\n * is duplicated in `@premium-cms/admin` (Shell/Header), which cannot import\n * from core.\n */\nexport const EDITOR_FLAG_KEY = \"emdash-editor\";\n\n/**\n * localStorage flag set when the user dismisses the toolbar in this browser.\n * Cleared the next time an editor opens the admin. Also duplicated in\n * `@premium-cms/admin`.\n */\nexport const TOOLBAR_DISMISSED_KEY = \"emdash-toolbar-dismissed\";\n\n/**\n * Query param that requests a fresh (never cached) editor render. Presence is\n * verified server-side: non-editors are redirected to the canonical URL.\n */\nexport const EDIT_PARAM = \"_edit\";\n\nexport function renderToolbarBootstrap(): string {\n\treturn `\n<!-- EmDash Toolbar Bootstrap -->\n<script>\n(function() {\n  var flag, dismissed;\n  try {\n    flag = localStorage.getItem(\"${EDITOR_FLAG_KEY}\");\n    dismissed = localStorage.getItem(\"${TOOLBAR_DISMISSED_KEY}\");\n  } catch (e) {\n    return;\n  }\n  if (!flag || dismissed) return;\n  // The server toolbar is present on _edit / edit-mode / preview renders.\n  if (document.getElementById(\"emdash-toolbar\")) return;\n\n  var root = document.createElement(\"div\");\n  root.id = \"emdash-toolbar-bootstrap\";\n  root.style.cssText = \"position:fixed;bottom:16px;left:50%;transform:translateX(-50%);z-index:999999;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;font-size:13px;line-height:1;-webkit-font-smoothing:antialiased;\";\n\n  var inner = document.createElement(\"div\");\n  inner.style.cssText = \"display:flex;align-items:center;gap:10px;padding:8px 16px;background:#1a1a1a;color:#e0e0e0;border-radius:999px;box-shadow:0 4px 24px rgba(0,0,0,0.3),0 0 0 1px rgba(255,255,255,0.08);white-space:nowrap;user-select:none;\";\n\n  var logo = document.createElement(\"span\");\n  logo.textContent = \"EmDash\";\n  logo.style.cssText = \"font-weight:600;font-size:12px;letter-spacing:0.02em;color:#fff;opacity:0.7;\";\n\n  var divider = document.createElement(\"span\");\n  divider.style.cssText = \"width:1px;height:16px;background:rgba(255,255,255,0.15);\";\n\n  var editBtn = document.createElement(\"button\");\n  editBtn.textContent = \"Edit\";\n  editBtn.style.cssText = \"padding:4px 12px;background:#3b82f6;color:#fff;border:none;border-radius:999px;font-size:12px;font-weight:600;cursor:pointer;font-family:inherit;\";\n\n  var closeBtn = document.createElement(\"button\");\n  closeBtn.textContent = \"\\\\u00d7\";\n  closeBtn.title = \"Hide toolbar\";\n  closeBtn.setAttribute(\"aria-label\", \"Hide toolbar\");\n  closeBtn.style.cssText = \"background:none;border:none;color:#666;cursor:pointer;font-size:16px;padding:0 2px;line-height:1;font-family:inherit;\";\n\n  editBtn.addEventListener(\"click\", function() {\n    editBtn.disabled = true;\n    fetch(\"/_emdash/api/auth/me\", {\n      credentials: \"same-origin\",\n      headers: { \"X-EmDash-Request\": \"1\" }\n    })\n    .then(function(r) { return r.ok ? r.json() : null; })\n    .then(function(body) {\n      var user = body && body.data;\n      if (user && user.role >= 30) {\n        var u = new URL(location.href);\n        u.searchParams.set(\"${EDIT_PARAM}\", \"1\");\n        location.href = u.toString();\n      } else if (user) {\n        // Logged in but not an editor — the flag is stale for this browser.\n        try { localStorage.removeItem(\"${EDITOR_FLAG_KEY}\"); } catch (e) {}\n        root.remove();\n      } else {\n        // No session — go to the admin login page.\n        location.href = \"/_emdash/admin/login\";\n      }\n    })\n    .catch(function() {\n      editBtn.disabled = false;\n    });\n  });\n\n  closeBtn.addEventListener(\"click\", function() {\n    try { localStorage.setItem(\"${TOOLBAR_DISMISSED_KEY}\", \"1\"); } catch (e) {}\n    root.remove();\n  });\n\n  inner.appendChild(logo);\n  inner.appendChild(divider);\n  inner.appendChild(editBtn);\n  inner.appendChild(closeBtn);\n  root.appendChild(inner);\n  document.body.appendChild(root);\n})();\n</script>\n`;\n}\n","/**\n * EmDash Visual Editing Toolbar\n *\n * A floating pill injected via middleware for authenticated editors.\n * Renders as a plain HTML string with inline styles and a <script> tag.\n * No dependencies — works on any page with a </body> tag.\n */\n\nexport interface ToolbarConfig {\n\teditMode: boolean;\n\tisPreview: boolean;\n}\n\nexport function renderToolbar(config: ToolbarConfig): string {\n\tconst { editMode, isPreview } = config;\n\n\treturn `\n<!-- EmDash Visual Editing Toolbar -->\n<div id=\"emdash-toolbar\" data-edit-mode=\"${editMode}\" data-preview=\"${isPreview}\">\n  <div class=\"emdash-tb-inner\">\n    <span class=\"emdash-tb-logo\">EmDash</span>\n\n    <div class=\"emdash-tb-divider\"></div>\n\n    <label class=\"emdash-tb-toggle\" title=\"Toggle edit mode\">\n      <input type=\"checkbox\" id=\"emdash-edit-toggle\" ${editMode ? \"checked\" : \"\"} />\n      <span class=\"emdash-tb-toggle-track\">\n        <span class=\"emdash-tb-toggle-thumb\"></span>\n      </span>\n      <span class=\"emdash-tb-toggle-label\">Edit</span>\n    </label>\n\n    <span class=\"emdash-tb-status\" id=\"emdash-tb-status\"></span>\n\n    <span class=\"emdash-tb-save-status\" id=\"emdash-tb-save-status\"></span>\n\n    <a class=\"emdash-tb-admin\" id=\"emdash-tb-admin\" href=\"#\" target=\"emdash-admin\" style=\"display:none\" title=\"Open in admin\">\n      <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6\"/><polyline points=\"15 3 21 3 21 9\"/><line x1=\"10\" y1=\"14\" x2=\"21\" y2=\"3\"/></svg>\n    </a>\n\n    <button class=\"emdash-tb-publish\" id=\"emdash-tb-publish\" style=\"display:none\">Publish</button>\n\n    <span class=\"emdash-tb-ext\" id=\"emdash-tb-ext\"></span>\n\n    <button class=\"emdash-tb-dismiss\" id=\"emdash-tb-dismiss\" title=\"Hide toolbar\" aria-label=\"Hide toolbar\">&times;</button>\n  </div>\n</div>\n\n<style>\n  #emdash-toolbar {\n    position: fixed;\n    bottom: 16px;\n    left: 50%;\n    transform: translateX(-50%);\n    z-index: 999999;\n    font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, sans-serif;\n    font-size: 13px;\n    line-height: 1;\n    -webkit-font-smoothing: antialiased;\n  }\n\n  .emdash-tb-inner {\n    display: flex;\n    align-items: center;\n    gap: 10px;\n    padding: 8px 16px;\n    background: #1a1a1a;\n    color: #e0e0e0;\n    border-radius: 999px;\n    box-shadow: 0 4px 24px rgba(0,0,0,0.3), 0 0 0 1px rgba(255,255,255,0.08);\n    white-space: nowrap;\n    user-select: none;\n  }\n\n  .emdash-tb-logo {\n    font-weight: 600;\n    font-size: 12px;\n    letter-spacing: 0.02em;\n    color: #fff;\n    opacity: 0.7;\n  }\n\n  .emdash-tb-divider {\n    width: 1px;\n    height: 16px;\n    background: rgba(255,255,255,0.15);\n  }\n\n  /* Toggle switch */\n  .emdash-tb-toggle {\n    display: flex;\n    align-items: center;\n    gap: 6px;\n    cursor: pointer;\n  }\n\n  .emdash-tb-toggle input {\n    position: absolute;\n    opacity: 0;\n    width: 0;\n    height: 0;\n  }\n\n  .emdash-tb-toggle-track {\n    position: relative;\n    width: 32px;\n    height: 18px;\n    background: #444;\n    border-radius: 9px;\n    transition: background 0.2s;\n  }\n\n  .emdash-tb-toggle input:checked + .emdash-tb-toggle-track {\n    background: #3b82f6;\n  }\n\n  .emdash-tb-toggle-thumb {\n    position: absolute;\n    top: 2px;\n    left: 2px;\n    width: 14px;\n    height: 14px;\n    background: #fff;\n    border-radius: 50%;\n    transition: transform 0.2s;\n  }\n\n  .emdash-tb-toggle input:checked + .emdash-tb-toggle-track .emdash-tb-toggle-thumb {\n    transform: translateX(14px);\n  }\n\n  .emdash-tb-toggle-label {\n    font-size: 12px;\n    color: #aaa;\n  }\n\n  .emdash-tb-toggle input:checked ~ .emdash-tb-toggle-label {\n    color: #fff;\n  }\n\n  /* Status area — flex for multiple badges */\n  .emdash-tb-status {\n    display: inline-flex;\n    gap: 6px;\n    align-items: center;\n  }\n\n  /* Badges */\n  .emdash-tb-badge {\n    display: inline-flex;\n    align-items: center;\n    padding: 3px 8px;\n    border-radius: 999px;\n    font-size: 11px;\n    font-weight: 600;\n    letter-spacing: 0.02em;\n    text-transform: uppercase;\n  }\n\n  .emdash-tb-badge--preview {\n    background: rgba(139,92,246,0.2);\n    color: #a78bfa;\n  }\n\n  .emdash-tb-badge--draft {\n    background: rgba(245,158,11,0.2);\n    color: #fbbf24;\n  }\n\n  .emdash-tb-badge--published {\n    background: rgba(34,197,94,0.2);\n    color: #4ade80;\n  }\n\n  .emdash-tb-badge--pending {\n    background: rgba(59,130,246,0.2);\n    color: #60a5fa;\n  }\n\n  .emdash-tb-badge--unsaved {\n    background: rgba(245,158,11,0.2);\n    color: #fbbf24;\n  }\n\n  .emdash-tb-badge--saving {\n    background: rgba(148,163,184,0.2);\n    color: #94a3b8;\n  }\n\n  .emdash-tb-badge--saved {\n    background: rgba(34,197,94,0.2);\n    color: #4ade80;\n    transition: opacity 0.3s;\n  }\n\n  .emdash-tb-badge--error {\n    background: rgba(239,68,68,0.2);\n    color: #f87171;\n  }\n\n  /* Admin link */\n  .emdash-tb-admin {\n    display: inline-flex;\n    align-items: center;\n    justify-content: center;\n    color: #888;\n    text-decoration: none;\n    padding: 2px;\n    border-radius: 4px;\n    transition: color 0.15s;\n  }\n\n  .emdash-tb-admin:hover {\n    color: #fff;\n  }\n\n  /* Publish button */\n  .emdash-tb-publish {\n    padding: 4px 12px;\n    background: #3b82f6;\n    color: #fff;\n    border: none;\n    border-radius: 999px;\n    font-size: 12px;\n    font-weight: 600;\n    cursor: pointer;\n    transition: background 0.15s;\n    font-family: inherit;\n  }\n\n  .emdash-tb-publish:hover {\n    background: #2563eb;\n  }\n\n  .emdash-tb-publish:disabled {\n    opacity: 0.5;\n    cursor: not-allowed;\n  }\n\n  /* Dismiss button */\n  .emdash-tb-ext {\n    display: flex;\n    align-items: center;\n    gap: 6px;\n  }\n  .emdash-tb-ext:empty {\n    display: none;\n  }\n  .emdash-tb-ext-btn {\n    padding: 4px 12px;\n    background: rgba(255, 255, 255, 0.08);\n    color: #e0e0e0;\n    border: 1px solid rgba(255, 255, 255, 0.12);\n    border-radius: 999px;\n    font-size: 12px;\n    font-weight: 600;\n    cursor: pointer;\n    font-family: inherit;\n    line-height: 1.2;\n  }\n  .emdash-tb-ext-btn:hover {\n    background: rgba(255, 255, 255, 0.16);\n    color: #fff;\n  }\n  .emdash-tb-ext-btn:disabled {\n    opacity: 0.5;\n    cursor: wait;\n  }\n  .emdash-tb-dismiss {\n    background: none;\n    border: none;\n    color: #666;\n    cursor: pointer;\n    font-size: 16px;\n    line-height: 1;\n    padding: 0 2px;\n    font-family: inherit;\n    transition: color 0.15s;\n  }\n\n  .emdash-tb-dismiss:hover {\n    color: #fff;\n  }\n\n  /* Edit mode: editable hover styles — uses :has() to check toolbar state */\n  body:has(#emdash-toolbar[data-edit-mode=\"true\"]) [data-emdash-ref] {\n    transition: box-shadow 0.15s, background-color 0.15s;\n  }\n\n  body:has(#emdash-toolbar[data-edit-mode=\"true\"]) [data-emdash-ref]:hover {\n    box-shadow: 0 0 0 2px rgba(59,130,246,0.5);\n    border-radius: 4px;\n    background-color: rgba(59,130,246,0.04);\n    cursor: text;\n  }\n\n  /* Active editing state — override hover pencil cursor */\n  [data-emdash-editing] {\n    box-shadow: 0 0 0 2px #3b82f6 !important;\n    border-radius: 4px !important;\n    background-color: rgba(59,130,246,0.04) !important;\n    cursor: text !important;\n  }\n\n  /* Suppress browser focus ring on contenteditable and tiptap editor */\n  [data-emdash-editing]:focus,\n  [data-emdash-ref] .tiptap:focus,\n  [data-emdash-ref] .ProseMirror:focus {\n    outline: none !important;\n  }\n\n  /* Image editor popover */\n  .emdash-img-popover {\n    position: fixed;\n    z-index: 1000000;\n    background: #1a1a1a;\n    border-radius: 12px;\n    box-shadow: 0 8px 32px rgba(0,0,0,0.4), 0 0 0 1px rgba(255,255,255,0.08);\n    color: #e0e0e0;\n    font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, sans-serif;\n    font-size: 13px;\n    width: 320px;\n    overflow: hidden;\n    animation: emdash-img-fadein 0.15s ease-out;\n  }\n\n  @keyframes emdash-img-fadein {\n    from { opacity: 0; transform: translateY(4px); }\n    to { opacity: 1; transform: translateY(0); }\n  }\n\n  .emdash-img-popover-header {\n    display: flex;\n    align-items: center;\n    justify-content: space-between;\n    padding: 10px 12px;\n    border-bottom: 1px solid rgba(255,255,255,0.08);\n  }\n\n  .emdash-img-popover-title {\n    font-weight: 600;\n    font-size: 12px;\n    text-transform: uppercase;\n    letter-spacing: 0.04em;\n    color: #999;\n  }\n\n  .emdash-img-popover-close {\n    background: none;\n    border: none;\n    color: #666;\n    cursor: pointer;\n    padding: 2px;\n    line-height: 1;\n    font-size: 16px;\n    border-radius: 4px;\n    transition: color 0.15s;\n  }\n\n  .emdash-img-popover-close:hover {\n    color: #fff;\n  }\n\n  .emdash-img-popover-body {\n    padding: 12px;\n    display: flex;\n    flex-direction: column;\n    gap: 10px;\n  }\n\n  .emdash-img-preview {\n    width: 100%;\n    max-height: 160px;\n    object-fit: contain;\n    border-radius: 6px;\n    background: #111;\n  }\n\n  .emdash-img-empty {\n    display: flex;\n    align-items: center;\n    justify-content: center;\n    height: 80px;\n    border: 2px dashed rgba(255,255,255,0.15);\n    border-radius: 6px;\n    color: #666;\n    font-size: 12px;\n  }\n\n  .emdash-img-field {\n    display: flex;\n    flex-direction: column;\n    gap: 4px;\n  }\n\n  .emdash-img-field label {\n    font-size: 11px;\n    font-weight: 600;\n    color: #888;\n    text-transform: uppercase;\n    letter-spacing: 0.04em;\n  }\n\n  .emdash-img-field input[type=\"text\"] {\n    background: #111;\n    border: 1px solid rgba(255,255,255,0.12);\n    border-radius: 6px;\n    color: #e0e0e0;\n    padding: 6px 8px;\n    font-size: 13px;\n    font-family: inherit;\n    outline: none;\n    transition: border-color 0.15s;\n  }\n\n  .emdash-img-field input[type=\"text\"]:focus {\n    border-color: #3b82f6;\n  }\n\n  .emdash-img-actions {\n    display: flex;\n    gap: 6px;\n  }\n\n  .emdash-img-btn {\n    flex: 1;\n    padding: 6px 10px;\n    border: 1px solid rgba(255,255,255,0.12);\n    border-radius: 6px;\n    background: #222;\n    color: #e0e0e0;\n    font-size: 12px;\n    font-family: inherit;\n    cursor: pointer;\n    transition: background 0.15s, border-color 0.15s;\n    text-align: center;\n    white-space: nowrap;\n  }\n\n  .emdash-img-btn:hover {\n    background: #333;\n    border-color: rgba(255,255,255,0.2);\n  }\n\n  .emdash-img-btn--primary {\n    background: #3b82f6;\n    border-color: #3b82f6;\n    color: #fff;\n  }\n\n  .emdash-img-btn--primary:hover {\n    background: #2563eb;\n    border-color: #2563eb;\n  }\n\n  .emdash-img-btn--danger {\n    color: #f87171;\n    border-color: rgba(248,113,113,0.3);\n  }\n\n  .emdash-img-btn--danger:hover {\n    background: rgba(248,113,113,0.1);\n    border-color: rgba(248,113,113,0.5);\n  }\n\n  /* Media browser within the popover */\n  .emdash-img-browser {\n    border-top: 1px solid rgba(255,255,255,0.08);\n    padding: 12px;\n  }\n\n  .emdash-img-browser-header {\n    display: flex;\n    align-items: center;\n    justify-content: space-between;\n    margin-bottom: 8px;\n  }\n\n  .emdash-img-browser-title {\n    font-size: 12px;\n    font-weight: 600;\n    color: #999;\n  }\n\n  .emdash-img-browser-back {\n    background: none;\n    border: none;\n    color: #3b82f6;\n    cursor: pointer;\n    font-size: 12px;\n    font-family: inherit;\n    padding: 2px 4px;\n  }\n\n  .emdash-img-browser-back:hover {\n    text-decoration: underline;\n  }\n\n  .emdash-img-grid {\n    display: grid;\n    grid-template-columns: repeat(3, 1fr);\n    gap: 6px;\n    max-height: 240px;\n    overflow-y: auto;\n  }\n\n  .emdash-img-grid-item {\n    aspect-ratio: 1;\n    border-radius: 4px;\n    overflow: hidden;\n    cursor: pointer;\n    border: 2px solid transparent;\n    transition: border-color 0.15s;\n    background: #111;\n  }\n\n  .emdash-img-grid-item:hover {\n    border-color: rgba(59,130,246,0.5);\n  }\n\n  .emdash-img-grid-item--selected {\n    border-color: #3b82f6;\n  }\n\n  .emdash-img-grid-item img {\n    width: 100%;\n    height: 100%;\n    object-fit: cover;\n  }\n\n  .emdash-img-loading {\n    display: flex;\n    align-items: center;\n    justify-content: center;\n    height: 80px;\n    color: #666;\n    font-size: 12px;\n  }\n\n  .emdash-img-drop {\n    border: 2px dashed #3b82f6;\n    background: rgba(59,130,246,0.05);\n  }\n\n  .emdash-img-uploading {\n    display: flex;\n    align-items: center;\n    gap: 8px;\n    padding: 8px 0;\n    color: #999;\n    font-size: 12px;\n  }\n\n  .emdash-img-popover-backdrop {\n    position: fixed;\n    inset: 0;\n    z-index: 999999;\n  }\n</style>\n\n<script>\n(function() {\n  var toolbar = document.getElementById(\"emdash-toolbar\");\n  var toggle = document.getElementById(\"emdash-edit-toggle\");\n  var statusEl = document.getElementById(\"emdash-tb-status\");\n  var saveStatusEl = document.getElementById(\"emdash-tb-save-status\");\n  var publishBtn = document.getElementById(\"emdash-tb-publish\");\n  if (!toolbar || !toggle || !statusEl || !publishBtn || !saveStatusEl) return;\n\n  // Dismissed in this browser (localStorage flag, cleared on the next admin\n  // visit). Remove the toolbar entirely instead of rendering it.\n  try {\n    if (localStorage.getItem(\"emdash-toolbar-dismissed\")) {\n      toolbar.remove();\n      return;\n    }\n  } catch (e) {\n    // localStorage unavailable — render normally\n  }\n\n  var isEditMode = toolbar.getAttribute(\"data-edit-mode\") === \"true\";\n\n  var dismissBtn = document.getElementById(\"emdash-tb-dismiss\");\n  if (dismissBtn) {\n    dismissBtn.addEventListener(\"click\", function() {\n      try { localStorage.setItem(\"emdash-toolbar-dismissed\", \"1\"); } catch (e) {}\n      // Dismissing while edit mode is on would strand the user in edit mode\n      // with no UI to leave it — clear the cookie too.\n      if (isEditMode) {\n        document.cookie = \"emdash-edit-mode=;path=/;expires=Thu, 01 Jan 1970 00:00:00 GMT\";\n        toolbar.remove();\n        location.replace(location.href);\n        return;\n      }\n      toolbar.remove();\n    });\n  }\n\n  // CSRF-protected fetch — adds X-EmDash-Request header to all API calls\n  function ecFetch(url, init) {\n    init = init || {};\n    init.headers = Object.assign({ \"X-EmDash-Request\": \"1\" }, init.headers || {});\n    return fetch(url, init);\n  }\n\n  // --- Save status tracking ---\n  var saveState = \"idle\"; // idle | unsaved | saving | saved | error\n  var saveHideTimer = null;\n  var pendingSavePromise = null;\n\n  function setSaveState(state) {\n    saveState = state;\n    clearTimeout(saveHideTimer);\n\n    switch (state) {\n      case \"unsaved\":\n        saveStatusEl.innerHTML = '<span class=\"emdash-tb-badge emdash-tb-badge--unsaved\">Unsaved</span>';\n        break;\n      case \"saving\":\n        saveStatusEl.innerHTML = '<span class=\"emdash-tb-badge emdash-tb-badge--saving\">Saving\\u2026</span>';\n        break;\n      case \"saved\":\n        saveStatusEl.innerHTML = '<span class=\"emdash-tb-badge emdash-tb-badge--saved\">Saved</span>';\n        saveHideTimer = setTimeout(function() {\n          saveStatusEl.innerHTML = \"\";\n          saveState = \"idle\";\n        }, 2000);\n        break;\n      case \"error\":\n        saveStatusEl.innerHTML = '<span class=\"emdash-tb-badge emdash-tb-badge--error\">Save failed</span>';\n        saveHideTimer = setTimeout(function() {\n          saveStatusEl.innerHTML = \"\";\n          saveState = \"idle\";\n        }, 3000);\n        break;\n      default:\n        saveStatusEl.innerHTML = \"\";\n    }\n  }\n\n  // Listen for save events from inline editors (e.g. PT editor)\n  document.addEventListener(\"emdash:save\", function(e) {\n    var detail = e.detail || {};\n    if (detail.state) {\n      setSaveState(detail.state);\n    }\n  });\n\n  document.addEventListener(\"emdash:content-changed\", function(e) {\n    var detail = e.detail || {};\n    if (detail.collection && detail.id) {\n      showUnpublishedChanges(detail.collection, detail.id);\n    }\n  });\n\n  // --- Entry status ---\n  var entryRef = null;\n\n  function updateStatus() {\n    if (!isEditMode) {\n      statusEl.innerHTML = \"\";\n      publishBtn.style.display = \"none\";\n      return;\n    }\n\n    var first = document.querySelector(\"[data-emdash-ref]\");\n    if (!first) {\n      statusEl.innerHTML = \"\";\n      publishBtn.style.display = \"none\";\n      return;\n    }\n\n    try {\n      var ref = JSON.parse(first.getAttribute(\"data-emdash-ref\"));\n      entryRef = ref;\n      if (!ref.status) return;\n\n      // Show admin link\n      var adminLink = document.getElementById(\"emdash-tb-admin\");\n      if (adminLink) {\n        adminLink.href = \"/_emdash/admin/content/\" + encodeURIComponent(ref.collection) + \"/\" + encodeURIComponent(ref.id);\n        adminLink.style.display = \"\";\n      }\n\n      if (ref.status === \"draft\") {\n        statusEl.innerHTML = '<span class=\"emdash-tb-badge emdash-tb-badge--draft\">Draft</span>';\n        publishBtn.style.display = \"\";\n        publishBtn.onclick = function() { publish(ref.collection, ref.id); };\n      } else if (ref.status === \"published\" && ref.hasDraft) {\n        statusEl.innerHTML = '<span class=\"emdash-tb-badge emdash-tb-badge--pending\">Unpublished changes</span>';\n        publishBtn.style.display = \"\";\n        publishBtn.onclick = function() { publish(ref.collection, ref.id); };\n      } else if (ref.status === \"published\") {\n        statusEl.innerHTML = '<span class=\"emdash-tb-badge emdash-tb-badge--published\">Published</span>';\n        publishBtn.style.display = \"none\";\n      }\n    } catch (e) {\n      // ignore parse errors\n    }\n  }\n\n  // Publish action\n  function publish(collection, id) {\n    if (pendingSavePromise) {\n      pendingSavePromise.then(function() { publish(collection, id); });\n      return;\n    }\n\n    publishBtn.disabled = true;\n    publishBtn.textContent = \"Publishing\\u2026\";\n\n    ecFetch(\"/_emdash/api/content/\" + encodeURIComponent(collection) + \"/\" + encodeURIComponent(id) + \"/publish\", {\n      method: \"POST\",\n      credentials: \"same-origin\",\n    })\n    .then(function(res) {\n      if (res.ok) {\n        if (document.startViewTransition) {\n          document.startViewTransition(function() { location.reload(); });\n        } else {\n          location.reload();\n        }\n      } else {\n        publishBtn.disabled = false;\n        publishBtn.textContent = \"Publish\";\n        console.error(\"Publish failed:\", res.status);\n      }\n    })\n    .catch(function(err) {\n      publishBtn.disabled = false;\n      publishBtn.textContent = \"Publish\";\n      console.error(\"Publish failed:\", err);\n    });\n  }\n\n  // Edit mode toggle\n  toggle.addEventListener(\"change\", function() {\n    if (toggle.checked) {\n      document.cookie = \"emdash-edit-mode=true;path=/;samesite=lax\";\n    } else {\n      document.cookie = \"emdash-edit-mode=;path=/;expires=Thu, 01 Jan 1970 00:00:00 GMT\";\n    }\n\n    if (document.startViewTransition) {\n      document.startViewTransition(function() { location.replace(location.href); });\n    } else {\n      location.replace(location.href);\n    }\n  });\n\n  // --- Inline editing ---\n\n  // Cached manifest (fetched once on first edit click)\n  var manifestCache = null;\n  var manifestPromise = null;\n\n  function fetchManifest() {\n    if (manifestCache) return Promise.resolve(manifestCache);\n    if (manifestPromise) return manifestPromise;\n    manifestPromise = ecFetch(\"/_emdash/api/manifest\", { credentials: \"same-origin\" })\n      .then(function(r) { return r.json(); })\n      .then(function(m) {\n        // The manifest endpoint wraps the payload in a { success, data } envelope (ApiResponse shape).\n        // Unwrap it so getFieldKind can read manifest.collections directly.\n        manifestCache = m && m.data ? m.data : m;\n        return manifestCache;\n      });\n    return manifestPromise;\n  }\n\n  function getFieldKind(manifest, collection, field) {\n    var col = manifest.collections && manifest.collections[collection];\n    if (!col || !col.fields) return null;\n    var f = col.fields[field];\n    return f ? f.kind : null;\n  }\n\n  // Load manifest early so the first click can resolve field kinds without racing the event.\n  if (isEditMode) {\n    fetchManifest();\n  }\n\n  // Save a single field value\n  function saveField(collection, id, field, value) {\n    setSaveState(\"saving\");\n    return ecFetch(\"/_emdash/api/content/\" + encodeURIComponent(collection) + \"/\" + encodeURIComponent(id), {\n      method: \"PUT\",\n      credentials: \"same-origin\",\n      headers: { \"Content-Type\": \"application/json\" },\n      body: JSON.stringify({ data: { [field]: value } }),\n    })\n    .then(function(res) {\n      if (res.ok) {\n        setSaveState(\"saved\");\n        // A save creates/updates a draft — show unpublished changes\n        showUnpublishedChanges(collection, id);\n      } else {\n        setSaveState(\"error\");\n        console.error(\"Save failed:\", res.status);\n      }\n    })\n    .catch(function(err) {\n      setSaveState(\"error\");\n      console.error(\"Save failed:\", err);\n    });\n  }\n\n  function showUnpublishedChanges(collection, id) {\n    statusEl.innerHTML = '<span class=\"emdash-tb-badge emdash-tb-badge--pending\">Unpublished changes</span>';\n    publishBtn.style.display = \"\";\n    publishBtn.disabled = false;\n    publishBtn.textContent = \"Publish\";\n    publishBtn.onclick = function() { publish(collection, id); };\n  }\n\n  // Plain text inline editing (contenteditable)\n  var currentlyEditing = null;\n\n  function startTextEdit(element, annotation) {\n    if (currentlyEditing === element) return;\n    if (currentlyEditing) endCurrentEdit();\n\n    currentlyEditing = element;\n    var originalText = element.textContent || \"\";\n\n    element.setAttribute(\"data-emdash-editing\", \"\");\n    element.contentEditable = \"plaintext-only\";\n    element.focus();\n\n    // Select all text\n    var range = document.createRange();\n    range.selectNodeContents(element);\n    var sel = window.getSelection();\n    sel.removeAllRanges();\n    sel.addRange(range);\n\n    // Track dirty state via input events\n    function handleInput() {\n      var current = (element.textContent || \"\").trim();\n      if (current !== originalText.trim()) {\n        setSaveState(\"unsaved\");\n      } else {\n        setSaveState(\"idle\");\n      }\n    }\n\n    function handleBlur() {\n      element.removeEventListener(\"blur\", handleBlur);\n      element.removeEventListener(\"keydown\", handleKeydown);\n      element.removeEventListener(\"input\", handleInput);\n      element.contentEditable = \"false\";\n      element.removeAttribute(\"data-emdash-editing\");\n      currentlyEditing = null;\n\n      var newValue = (element.textContent || \"\").trim();\n      if (newValue !== originalText.trim()) {\n        pendingSavePromise = saveField(annotation.collection, annotation.id, annotation.field, newValue).then(function() {\n          pendingSavePromise = null;\n        }, function() {\n          pendingSavePromise = null;\n        });\n      } else {\n        setSaveState(\"idle\");\n      }\n    }\n\n    function handleKeydown(e) {\n      if (e.key === \"Enter\" && !e.shiftKey) {\n        e.preventDefault();\n        element.blur();\n      }\n      if (e.key === \"Escape\") {\n        element.textContent = originalText;\n        setSaveState(\"idle\");\n        element.blur();\n      }\n    }\n\n    element.addEventListener(\"input\", handleInput);\n    element.addEventListener(\"blur\", handleBlur);\n    element.addEventListener(\"keydown\", handleKeydown);\n  }\n\n  function endCurrentEdit() {\n    if (currentlyEditing) {\n      currentlyEditing.blur();\n    }\n  }\n\n  // Fallback: open admin\n  function openAdmin(annotation) {\n    var url = \"/_emdash/admin/content/\" + encodeURIComponent(annotation.collection) + \"/\" + encodeURIComponent(annotation.id);\n    if (annotation.field) {\n      url += \"?field=\" + encodeURIComponent(annotation.field);\n    }\n    window.open(url, \"emdash-admin\");\n  }\n\n  // --- Inline image editing ---\n  var activeImagePopover = null;\n\n  function closeImagePopover() {\n    if (activeImagePopover) {\n      activeImagePopover.backdrop.remove();\n      activeImagePopover.popover.remove();\n      if (activeImagePopover.escapeHandler) {\n        document.removeEventListener(\"keydown\", activeImagePopover.escapeHandler);\n      }\n      activeImagePopover = null;\n    }\n  }\n\n  function startImageEdit(element, annotation) {\n    closeImagePopover();\n\n    // Find the current image value by fetching the entry\n    var collection = annotation.collection;\n    var id = annotation.id;\n    var field = annotation.field;\n\n    // Find img element inside the annotated container (or the element itself if it's an img)\n    var imgEl = element.tagName === \"IMG\" ? element : element.querySelector(\"img\");\n\n    // Fetch current field value from the content API\n    ecFetch(\"/_emdash/api/content/\" + encodeURIComponent(collection) + \"/\" + encodeURIComponent(id), {\n      credentials: \"same-origin\"\n    })\n    .then(function(r) { return r.json(); })\n    .then(function(entry) {\n      var currentValue = entry.data && entry.data[field];\n      showImagePopover(element, imgEl, annotation, currentValue);\n    })\n    .catch(function() {\n      // If fetch fails, still show popover with what we can infer from DOM\n      showImagePopover(element, imgEl, annotation, null);\n    });\n  }\n\n  function showImagePopover(element, imgEl, annotation, currentValue) {\n    closeImagePopover();\n\n    var collection = annotation.collection;\n    var id = annotation.id;\n    var field = annotation.field;\n\n    // Position near the element\n    var rect = element.getBoundingClientRect();\n    var viewportH = window.innerHeight;\n    var viewportW = window.innerWidth;\n\n    // Create backdrop for click-outside-to-close\n    var backdrop = document.createElement(\"div\");\n    backdrop.className = \"emdash-img-popover-backdrop\";\n    backdrop.addEventListener(\"click\", function(e) {\n      if (e.target === backdrop) closeImagePopover();\n    });\n\n    // Create popover\n    var popover = document.createElement(\"div\");\n    popover.className = \"emdash-img-popover\";\n\n    var currentSrc = currentValue ? (currentValue.previewUrl || currentValue.src) : (imgEl ? imgEl.src : null);\n    var currentAlt = currentValue ? (currentValue.alt || \"\") : (imgEl ? (imgEl.alt || \"\") : \"\");\n\n    // Build popover HTML\n    var html = '';\n    html += '<div class=\"emdash-img-popover-header\">';\n    html += '  <span class=\"emdash-img-popover-title\">Image</span>';\n    html += '  <button class=\"emdash-img-popover-close\" data-action=\"close\">&times;</button>';\n    html += '</div>';\n    html += '<div class=\"emdash-img-popover-body\" id=\"emdash-img-main\">';\n\n    if (currentSrc) {\n      html += '<img class=\"emdash-img-preview\" src=\"' + escapeAttr(currentSrc) + '\" alt=\"\" />';\n    } else {\n      html += '<div class=\"emdash-img-empty\">No image selected</div>';\n    }\n\n    html += '<div class=\"emdash-img-field\">';\n    html += '  <label for=\"emdash-img-alt\">Alt text</label>';\n    html += '  <input type=\"text\" id=\"emdash-img-alt\" value=\"' + escapeAttr(currentAlt) + '\" placeholder=\"Describe the image\" />';\n    html += '</div>';\n\n    html += '<div class=\"emdash-img-actions\">';\n    html += '  <button class=\"emdash-img-btn emdash-img-btn--primary\" data-action=\"browse\">Replace</button>';\n    html += '  <label class=\"emdash-img-btn\" style=\"cursor:pointer\">';\n    html += '    Upload';\n    html += '    <input type=\"file\" accept=\"image/*\" id=\"emdash-img-upload\" style=\"display:none\" />';\n    html += '  </label>';\n    if (currentSrc) {\n      html += '  <button class=\"emdash-img-btn emdash-img-btn--danger\" data-action=\"remove\">Remove</button>';\n    }\n    html += '</div>';\n    html += '</div>';\n\n    popover.innerHTML = html;\n\n    backdrop.appendChild(popover);\n    document.body.appendChild(backdrop);\n\n    // Position the popover\n    positionPopover(popover, rect, viewportW, viewportH);\n\n    // Escape key handler\n    function handleEscape(e) {\n      if (e.key === \"Escape\") {\n        closeImagePopover();\n        document.removeEventListener(\"keydown\", handleEscape);\n      }\n    }\n    document.addEventListener(\"keydown\", handleEscape);\n\n    activeImagePopover = {\n      backdrop: backdrop,\n      popover: popover,\n      annotation: annotation,\n      currentValue: currentValue,\n      element: element,\n      imgEl: imgEl,\n      escapeHandler: handleEscape\n    };\n\n    // Event handlers\n    popover.querySelector('[data-action=\"close\"]').addEventListener(\"click\", closeImagePopover);\n\n    popover.querySelector('[data-action=\"browse\"]').addEventListener(\"click\", function() {\n      showMediaBrowser(popover, annotation, currentValue, element, imgEl);\n    });\n\n    var uploadInput = popover.querySelector(\"#emdash-img-upload\");\n    uploadInput.addEventListener(\"change\", function(e) {\n      var file = e.target.files && e.target.files[0];\n      if (file) handleImageUpload(file, popover, annotation, element, imgEl);\n    });\n\n    var removeBtn = popover.querySelector('[data-action=\"remove\"]');\n    if (removeBtn) {\n      removeBtn.addEventListener(\"click\", function() {\n        saveField(collection, id, field, null).then(function() {\n          if (imgEl) {\n            imgEl.style.display = \"none\";\n          }\n          closeImagePopover();\n        });\n      });\n    }\n\n    // Save alt text on change (debounced)\n    var altInput = popover.querySelector(\"#emdash-img-alt\");\n    var altTimer = null;\n    altInput.addEventListener(\"input\", function() {\n      clearTimeout(altTimer);\n      altTimer = setTimeout(function() {\n        var newAlt = altInput.value;\n        if (currentValue) {\n          var updated = Object.assign({}, currentValue, { alt: newAlt });\n          saveField(collection, id, field, updated);\n          if (imgEl) imgEl.alt = newAlt;\n        }\n      }, 500);\n    });\n\n    // Handle drag and drop on the popover body\n    var body = popover.querySelector(\".emdash-img-popover-body\");\n    body.addEventListener(\"dragover\", function(e) {\n      e.preventDefault();\n      body.classList.add(\"emdash-img-drop\");\n    });\n    body.addEventListener(\"dragleave\", function() {\n      body.classList.remove(\"emdash-img-drop\");\n    });\n    body.addEventListener(\"drop\", function(e) {\n      e.preventDefault();\n      body.classList.remove(\"emdash-img-drop\");\n      var file = e.dataTransfer && e.dataTransfer.files && e.dataTransfer.files[0];\n      if (file && file.type.startsWith(\"image/\")) {\n        handleImageUpload(file, popover, annotation, element, imgEl);\n      }\n    });\n  }\n\n  function positionPopover(popover, targetRect, viewportW, viewportH) {\n    var popoverW = 320;\n    var gap = 8;\n\n    // Try to place to the right of the element\n    var left = targetRect.right + gap;\n    var top = targetRect.top;\n\n    // If it overflows right, place to the left\n    if (left + popoverW > viewportW - 16) {\n      left = targetRect.left - popoverW - gap;\n    }\n    // If it still overflows (narrow viewport), center below\n    if (left < 16) {\n      left = Math.max(16, (viewportW - popoverW) / 2);\n      top = targetRect.bottom + gap;\n    }\n    // Clamp vertically\n    if (top + 400 > viewportH - 80) { // 80 for toolbar\n      top = Math.max(16, viewportH - 480);\n    }\n    if (top < 16) top = 16;\n\n    popover.style.left = left + \"px\";\n    popover.style.top = top + \"px\";\n  }\n\n  function escapeAttr(str) {\n    return String(str || \"\").replace(/&/g, \"&amp;\").replace(/\"/g, \"&quot;\").replace(/</g, \"&lt;\").replace(/>/g, \"&gt;\");\n  }\n\n  function showMediaBrowser(popover, annotation, currentValue, element, imgEl) {\n    var mainBody = popover.querySelector(\"#emdash-img-main\");\n    if (mainBody) mainBody.style.display = \"none\";\n\n    // Remove existing browser if any\n    var existing = popover.querySelector(\".emdash-img-browser\");\n    if (existing) existing.remove();\n\n    var browser = document.createElement(\"div\");\n    browser.className = \"emdash-img-browser\";\n\n    browser.innerHTML = '<div class=\"emdash-img-browser-header\">' +\n      '<span class=\"emdash-img-browser-title\">Media Library</span>' +\n      '<button class=\"emdash-img-browser-back\">Back</button>' +\n      '</div>' +\n      '<div class=\"emdash-img-loading\">Loading\\u2026</div>';\n\n    popover.appendChild(browser);\n\n    browser.querySelector(\".emdash-img-browser-back\").addEventListener(\"click\", function() {\n      browser.remove();\n      if (mainBody) mainBody.style.display = \"\";\n    });\n\n    // Fetch media\n    ecFetch(\"/_emdash/api/media?mimeType=image/&limit=30\", { credentials: \"same-origin\" })\n    .then(function(r) { return r.json(); })\n    .then(function(data) {\n      var items = data.items || [];\n      var loadingEl = browser.querySelector(\".emdash-img-loading\");\n      if (loadingEl) loadingEl.remove();\n\n      if (items.length === 0) {\n        var empty = document.createElement(\"div\");\n        empty.className = \"emdash-img-loading\";\n        empty.textContent = \"No images found\";\n        browser.appendChild(empty);\n        return;\n      }\n\n      var grid = document.createElement(\"div\");\n      grid.className = \"emdash-img-grid\";\n\n      items.forEach(function(item) {\n        var thumb = document.createElement(\"div\");\n        thumb.className = \"emdash-img-grid-item\";\n        if (currentValue && currentValue.id === item.id) {\n          thumb.classList.add(\"emdash-img-grid-item--selected\");\n        }\n        var thumbUrl = item.url || item.previewUrl || (\"/_emdash/api/media/file/\" + item.storageKey);\n        thumb.innerHTML = '<img src=\"' + escapeAttr(thumbUrl) + '\" alt=\"' + escapeAttr(item.alt || item.filename || \"\") + '\" loading=\"lazy\" />';\n\n        thumb.addEventListener(\"click\", function() {\n          selectMediaItem(item, annotation, element, imgEl);\n        });\n\n        grid.appendChild(thumb);\n      });\n\n      browser.appendChild(grid);\n    })\n    .catch(function(err) {\n      var loadingEl = browser.querySelector(\".emdash-img-loading\");\n      if (loadingEl) loadingEl.textContent = \"Failed to load media\";\n      console.error(\"Media fetch error:\", err);\n    });\n  }\n\n  function selectMediaItem(item, annotation, element, imgEl) {\n    var collection = annotation.collection;\n    var id = annotation.id;\n    var field = annotation.field;\n\n    var isLocal = !item.provider || item.provider === \"local\";\n    var itemUrl = item.url || item.previewUrl || (\"/_emdash/api/media/file/\" + item.storageKey);\n\n    var newValue = {\n      id: item.id,\n      provider: item.provider || \"local\",\n      src: isLocal ? itemUrl : undefined,\n      previewUrl: isLocal ? undefined : itemUrl,\n      alt: item.alt || \"\",\n      width: item.width,\n      height: item.height,\n      meta: item.meta\n    };\n\n    // Clean undefined fields\n    Object.keys(newValue).forEach(function(k) {\n      if (newValue[k] === undefined) delete newValue[k];\n    });\n\n    saveField(collection, id, field, newValue).then(function() {\n      // Update the image in the DOM\n      if (imgEl) {\n        imgEl.src = itemUrl;\n        imgEl.alt = item.alt || \"\";\n        imgEl.style.display = \"\";\n      }\n      closeImagePopover();\n    });\n  }\n\n  function handleImageUpload(file, popover, annotation, element, imgEl) {\n    var collection = annotation.collection;\n    var id = annotation.id;\n    var field = annotation.field;\n\n    // Show uploading state\n    var mainBody = popover.querySelector(\"#emdash-img-main\");\n    var browserEl = popover.querySelector(\".emdash-img-browser\");\n    if (browserEl) browserEl.remove();\n    if (mainBody) {\n      mainBody.innerHTML = '<div class=\"emdash-img-uploading\">' +\n        '<span>Uploading ' + escapeAttr(file.name) + '\\u2026</span>' +\n        '</div>';\n      mainBody.style.display = \"\";\n    }\n\n    // Detect dimensions before upload\n    var dimPromise = new Promise(function(resolve) {\n      if (!file.type.startsWith(\"image/\")) return resolve({});\n      var img = new Image();\n      img.onload = function() {\n        resolve({ width: img.naturalWidth, height: img.naturalHeight });\n        URL.revokeObjectURL(img.src);\n      };\n      img.onerror = function() {\n        resolve({});\n        URL.revokeObjectURL(img.src);\n      };\n      img.src = URL.createObjectURL(file);\n    });\n\n    dimPromise.then(function(dims) {\n      // Generate a thumbnail for large images to avoid OOM in server-side\n      // blurhash generation on memory-constrained runtimes (Workers).\n      // Thumbnail fits within a 64x64 box (scale by max dimension) so that\n      // extreme aspect ratios don't explode into a huge canvas client-side.\n      var thumbPromise;\n      if (dims.width && dims.height && dims.width * dims.height * 4 > 32 * 1024 * 1024) {\n        thumbPromise = new Promise(function(resolve) {\n          try {\n            var maxDim = Math.max(dims.width, dims.height);\n            var scale = Math.min(1, 64 / maxDim);\n            var thumbW = Math.max(1, Math.round(dims.width * scale));\n            var thumbH = Math.max(1, Math.round(dims.height * scale));\n            var canvas = document.createElement(\"canvas\");\n            canvas.width = thumbW;\n            canvas.height = thumbH;\n            var ctx = canvas.getContext(\"2d\");\n            if (ctx) {\n              var img = new Image();\n              img.onload = function() {\n                try {\n                  ctx.drawImage(img, 0, 0, thumbW, thumbH);\n                  canvas.toBlob(function(blob) {\n                    URL.revokeObjectURL(img.src);\n                    resolve(blob);\n                  }, \"image/png\");\n                } catch (e) {\n                  URL.revokeObjectURL(img.src);\n                  resolve(null);\n                }\n              };\n              img.onerror = function() {\n                URL.revokeObjectURL(img.src);\n                resolve(null);\n              };\n              img.src = URL.createObjectURL(file);\n            } else {\n              resolve(null);\n            }\n          } catch (e) {\n            resolve(null);\n          }\n        });\n      } else {\n        thumbPromise = Promise.resolve(null);\n      }\n\n      return thumbPromise.then(function(thumbnail) {\n        var formData = new FormData();\n        formData.append(\"file\", file);\n        if (dims.width) formData.append(\"width\", String(dims.width));\n        if (dims.height) formData.append(\"height\", String(dims.height));\n        if (thumbnail) formData.append(\"thumbnail\", thumbnail, \"thumb.png\");\n\n        return ecFetch(\"/_emdash/api/media\", {\n          method: \"POST\",\n          credentials: \"same-origin\",\n          body: formData\n        });\n      });\n    })\n    .then(function(r) { return r.json(); })\n    .then(function(data) {\n      if (!data.item) throw new Error(\"Upload failed\");\n      var item = data.item;\n      selectMediaItem(item, annotation, element, imgEl);\n    })\n    .catch(function(err) {\n      console.error(\"Upload error:\", err);\n      setSaveState(\"error\");\n      closeImagePopover();\n    });\n  }\n\n  // Click handler for edit mode\n  if (isEditMode) {\n    document.addEventListener(\"click\", function(e) {\n      var target = e.target;\n\n      // Don't intercept clicks on elements currently being edited\n      if (target.hasAttribute && target.hasAttribute(\"data-emdash-editing\")) return;\n\n      // Walk up to find annotated element\n      while (target && target !== document.body) {\n        if (target.hasAttribute && target.hasAttribute(\"data-emdash-editing\")) return;\n\n        var ref = target.getAttribute && target.getAttribute(\"data-emdash-ref\");\n        if (ref) {\n          try {\n            var annotation = JSON.parse(ref);\n\n            // Entry-level annotation (no field) — keep walking for a field-level ancestor\n            if (!annotation.field) {\n              target = target.parentElement;\n              continue;\n            }\n\n            function dispatchInline(kind) {\n              closeImagePopover();\n              // Portable Text is edited in-page by InlinePortableTextEditor — do not open admin\n              if (kind === \"portableText\") {\n                return;\n              }\n              e.preventDefault();\n              e.stopPropagation();\n              if (kind === \"string\" || kind === \"text\") {\n                startTextEdit(target, annotation);\n              } else if (kind === \"image\") {\n                startImageEdit(target, annotation);\n              } else {\n                openAdmin(annotation);\n              }\n            }\n\n            if (manifestCache) {\n              dispatchInline(getFieldKind(manifestCache, annotation.collection, annotation.field));\n            } else {\n              fetchManifest().then(function(manifest) {\n                dispatchInline(getFieldKind(manifest, annotation.collection, annotation.field));\n              });\n            }\n          } catch (err) {\n            console.error(\"Failed to parse emdash ref:\", err);\n          }\n          return;\n        }\n        target = target.parentElement;\n      }\n    }, true);\n  }\n\n  // Buttons plugins add to the toolbar: a private plugin route named\n  // \\`toolbar\\` describes each one, and the script it names registers\n  // window.__emdashToolbarExtensions[pluginId] = { open(config) } on load.\n  var extContainer = document.getElementById(\"emdash-tb-ext\");\n  function openExtension(ext, btn) {\n    var registry = (window.__emdashToolbarExtensions = window.__emdashToolbarExtensions || {});\n    var entry = registry[ext.pluginId];\n    if (entry && typeof entry.open === \"function\") {\n      entry.open(ext.config || {}, ext);\n      return;\n    }\n    if (document.querySelector('script[data-emdash-extension=\"' + ext.pluginId + '\"]')) return;\n    btn.disabled = true;\n    var s = document.createElement(\"script\");\n    s.src = ext.script;\n    s.async = true;\n    s.setAttribute(\"data-emdash-extension\", ext.pluginId);\n    s.onload = function() {\n      btn.disabled = false;\n      var loaded = registry[ext.pluginId];\n      if (loaded && typeof loaded.open === \"function\") loaded.open(ext.config || {}, ext);\n    };\n    s.onerror = function() {\n      btn.disabled = false;\n    };\n    document.head.appendChild(s);\n  }\n  if (extContainer) {\n    ecFetch(\"/_emdash/api/toolbar/extensions\", { credentials: \"same-origin\" })\n      .then(function(r) { return r.ok ? r.json() : null; })\n      .then(function(body) {\n        var list = body && body.data && body.data.extensions;\n        if (!Array.isArray(list)) return;\n        list.forEach(function(ext) {\n          if (!ext || typeof ext.pluginId !== \"string\" || typeof ext.label !== \"string\") return;\n          var btn = document.createElement(\"button\");\n          btn.type = \"button\";\n          btn.className = \"emdash-tb-ext-btn\";\n          btn.textContent = ext.label;\n          btn.addEventListener(\"click\", function() { openExtension(ext, btn); });\n          extContainer.appendChild(btn);\n        });\n      })\n      .catch(function() {});\n  }\n\n  updateStatus();\n})();\n</script>\n`;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAuBA,MAAa,kBAAkB;;;;;;AAO/B,MAAa,wBAAwB;;;;;AAMrC,MAAa,aAAa;AAE1B,SAAgB,yBAAiC;AAChD,QAAO;;;;;;mCAM2B,gBAAgB;wCACX,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;8BA2ChC,WAAW;;;;yCAIA,gBAAgB;;;;;;;;;;;;;kCAavB,sBAAsB;;;;;;;;;;;;;;;;;AC7FxD,SAAgB,cAAc,QAA+B;CAC5D,MAAM,EAAE,UAAU,cAAc;AAEhC,QAAO;;2CAEmC,SAAS,kBAAkB,UAAU;;;;;;;uDAOzB,WAAW,YAAY,GAAG"}