{
  "version": 3,
  "sources": ["../src/modules/dom-helpers.js", "../src/modules/redactor-upload.js", "../src/modules/toast.js", "../src/modules/unsaved-guard.js", "../src/modules/allauth.js", "../src/project.js"],
  "sourcesContent": [
    "/* Small, cross-cutting DOM utilities used across the app. */\n\n/**\n * Remove an element and flush stray whitespace text nodes from its parent\n * so that CSS :empty triggers correctly after HTMX swaps.\n * @param {HTMLElement} el - The element to remove\n *\n * Usage in hx-on::after-request=\"removeForEmptyMsg(this)\"\n */\nexport const removeForEmptyMsg = (el) => {\n  if (!el) return;\n  const parent = el.parentElement;\n  el.remove();\n  if (parent && !parent.children.length) parent.textContent = \"\";\n};\n\n/**\n * Toggle all checkboxes with name ending in \"-checkbox\" to match the state of\n * the given input checkbox. Used for \"select all\" checkboxes in list views.\n * @param {HTMLInputElement} input - The checkbox input that triggered the toggle\n *\n * Usage in hx-on::change=\"toggleAll(this)\"\n */\nexport const toggleAll = (input) => {\n  const checkboxes = document.querySelectorAll('input[type=\"checkbox\"][name*=\"-checkbox\"]');\n  if (!checkboxes) return;\n  checkboxes.forEach((checkbox) => {\n    checkbox.checked = input.checked;\n    checkbox.dispatchEvent(new Event(\"change\"));\n  });\n};\n\n/**\n * Get the values of all checked checkboxes with name ending in \"-checkbox\".\n * Used for bulk actions in list views.\n *\n * Usage in hx-on::click=\"const selected = getSelected();\"\n * @returns {Array} - An array of values of the selected checkboxes\n */\nexport const getSelected = () => {\n  const checkboxes = document.querySelectorAll('input[type=\"checkbox\"][name*=\"-checkbox\"]');\n  const selected = [];\n  if (!checkboxes) return selected;\n  checkboxes.forEach((checkbox) => {\n    if (checkbox.checked && !checkbox.name.includes(\"toggleAll\")) {\n      selected.push(checkbox.value);\n    }\n  });\n  return selected;\n};\n\n/**\n * Copy to clipboard\n * @param {HTMLElement} element - The element to copy the text from\n * @param {string} textToCopy - The text to copy to the clipboard\n * example <span class=\"cursor-pointer tooltip tooltip-bottom\"\n *           data-tip=\"{% trans \"Click to copy\" %}\"\n *           data-tip-copysuccess=\"{% trans \"Copied!\" %}\"\n *           data-tip-copyfailure=\"{% trans \"Failed to copy\" %}\"\n *           onclick=\"copyToClipboard(this, '{{ textContent }}')\">{{ textContent }}</span>\n */\nexport const copyToClipboard = (element, textToCopy = \"\") => {\n  if (navigator.clipboard && element) {\n    const txt = textToCopy ?? element?.innerText ?? \"\";\n    const originalTip = element.dataset.tip;\n    const successTip = element.dataset.tipCopysuccess || gettext(\"Copied!\");\n    const failureTip = element.dataset.tipCopyfailure || gettext(\"Failed to copy\");\n    navigator.clipboard\n      .writeText(txt)\n      .then(() => {\n        element.classList.add(\"tooltip-success\");\n        element.dataset.tip = successTip;\n        setTimeout(() => {\n          element.classList.remove(\"tooltip-success\");\n          element.dataset.tip = originalTip;\n        }, 2000);\n      })\n      .catch(() => {\n        element.classList.add(\"tooltip-error\");\n        element.dataset.tip = failureTip;\n        setTimeout(() => {\n          element.classList.remove(\"tooltip-error\");\n          element.dataset.tip = originalTip;\n        }, 2000);\n      });\n  }\n};\n",
    "/* Presigned-URL image upload handler used by the Redactor rich-text editor. */\n\nexport const redactorUploadImage = async (upload, data, csrfToken, presignedUrl, finishUploadUrl) => {\n  const resp = {};\n  try {\n    let index = 1;\n    for (const key in data.files) {\n      if (typeof data.files[key] === \"object\") {\n        const file = data.files[key];\n        const originalName = file.name;\n        let response = await fetch(presignedUrl, {\n          method: \"POST\",\n          headers: {\n            \"X-CSRFToken\": csrfToken,\n            \"Content-Type\": \"application/json\",\n          },\n          body: JSON.stringify({\n            file_type: \"image\",\n            acl: \"public-read\",\n            original_file_name: originalName,\n          }),\n        });\n\n        if (!response.ok) {\n          throw new Error(\"Failed to get presigned URL\");\n        }\n\n        const presignedData = await response.json();\n\n        const formData = new FormData();\n        for (const field in presignedData.fields) {\n          formData.append(field, presignedData.fields[field]);\n        }\n        formData.set(\"file\", file);\n\n        response = await fetch(presignedData.url, {\n          method: \"POST\",\n          body: formData,\n        });\n\n        if (!response.ok) {\n          throw new Error(\"Failed to upload file\");\n        }\n\n        response = await fetch(finishUploadUrl, {\n          method: \"POST\",\n          headers: {\n            \"X-CSRFToken\": csrfToken,\n            \"Content-Type\": \"application/json\",\n          },\n          body: JSON.stringify({\n            asset_id: presignedData.asset,\n          }),\n        });\n\n        resp[`file-${index}`] = {\n          id: presignedData.asset,\n          url: `${presignedData.url}/${presignedData.fields.key}`,\n        };\n        index++;\n      }\n    }\n  } catch (error) {\n    console.error(\"Upload failed\", error);\n    return {\n      error: true,\n      message: error,\n    };\n  } finally {\n    console.log(\"Upload complete\", resp);\n    upload.complete(resp, data.e);\n  }\n};\n",
    "/**\n * Toast snippets\n * - Quick toast - default\n *   `showToast('Success message');`\n *\n * - Slow toast\n *   `showToast({ message: {% trans \"Read me carefully\" %}, type: 'error', duration: 'slow' });`\n *\n * - Keep toast (doesn't close automatically)\n *   `showToast({ message: {% trans \"Important info\" %}, type: 'success', duration: 'keep' });`\n */\n\nconst TIMEOUT_QUICK_MS = 4000;\nconst TIMEOUT_SLOW_MS = 7000;\n\nexport const showToast = (config) => {\n  const toast = document.getElementById(\"toast\");\n  const toastAlert = toast?.querySelector(\".alert\");\n  const messageSpan = document.getElementById(\"toast-message\");\n  if (!toast || !toastAlert || !messageSpan) return;\n\n  // Supports both string and config object\n  const message = typeof config === \"string\" ? config : config.message;\n  const type = typeof config === \"string\" ? \"success\" : config.type || \"success\";\n  const duration = typeof config === \"string\" ? \"quick\" : config.duration || \"quick\";\n\n  messageSpan.innerText = message;\n  toastAlert.classList.remove(\"alert-success\", \"alert-error\");\n  toastAlert.classList.add(`alert-${type}`);\n  toast.classList.remove(\"hidden\");\n\n  if (toast.timeoutId) clearTimeout(toast.timeoutId);\n  if (duration !== \"keep\") {\n    const timeoutMs = duration === \"slow\" ? TIMEOUT_SLOW_MS : TIMEOUT_QUICK_MS;\n    toast.timeoutId = setTimeout(() => {\n      toast.classList.add(\"hidden\");\n    }, timeoutMs);\n  }\n};\n\nexport const closeToast = () => {\n  const toast = document.getElementById(\"toast\");\n  if (!toast) return;\n  toast.classList.add(\"hidden\");\n  if (toast.timeoutId) {\n    clearTimeout(toast.timeoutId);\n    toast.timeoutId = null;\n  }\n};\n",
    "/**\n * Enables unsaved changes guard for a form\n * Prevents users from navigating away when they have unsaved changes\n *\n * @param {string} formId - ID of the form to guard\n * @param {Object} options - Configuration options\n * @param {string[]} [options.watchFields] - Field IDs to watch for changes (monitors input/change events)\n * @param {string} [options.dirtyEvent] - Custom event name that marks the form as dirty when dispatched on document\n * @param {Function} [options.isDirtyCheck] - Custom function to check if form is dirty (overrides data-is-dirty)\n * @param {string} [options.message] - Custom warning message\n * @param {boolean} [options.successShowToastClearsIsDirty] - Whether success toast events should clear dirty flag (default: false)\n *\n * @example\n * enableUnsavedChangesGuard('my-form', {\n *   watchFields: ['field1', 'field2'],\n *   message: 'You have unsaved changes. Leave anyway?',\n *   successShowToastClearsIsDirty: true\n * });\n */\nexport const enableUnsavedChangesGuard = (formId, options = {}) => {\n  const form = document.getElementById(formId);\n  if (!form) {\n    console.warn(`Form with ID \"${formId}\" not found`);\n    return;\n  }\n\n  const message = options.message || gettext(\"You have unsaved changes. Are you sure you want to leave?\");\n  const successClearsIsDirty = options.successShowToastClearsIsDirty || false;\n  const markDirty = () => {\n    form.dataset.isDirty = \"true\";\n  };\n  const markClean = () => {\n    form.dataset.isDirty = \"false\";\n  };\n  const isDirty = () => {\n    if (options.isDirtyCheck) return options.isDirtyCheck();\n    return form.dataset.isDirty === \"true\";\n  };\n  markClean();\n\n  // Store field references for cleanup\n  const fieldListeners = [];\n\n  // Watch form fields for input/change events\n  if (options.watchFields && options.watchFields.length > 0) {\n    options.watchFields.forEach((fieldId) => {\n      const field = document.getElementById(fieldId);\n      if (field) {\n        field.addEventListener(\"input\", markDirty);\n        field.addEventListener(\"change\", markDirty);\n        fieldListeners.push(field);\n      } else {\n        console.warn(`Field with ID \"${fieldId}\" not found`);\n      }\n    });\n  }\n\n  // Listen for a custom event\n  if (options.dirtyEvent) {\n    document.addEventListener(options.dirtyEvent, markDirty);\n  }\n\n  // Store showToast handler reference for cleanup\n  let showToastHandler = null;\n  if (successClearsIsDirty) {\n    showToastHandler = (event) => {\n      const toastConfig = event?.detail?.value || event?.detail || {};\n      const isSuccess = typeof toastConfig === \"string\" || !toastConfig.type || toastConfig.type === \"success\";\n      if (isSuccess) markClean();\n    };\n    window.addEventListener(\"showToast\", showToastHandler);\n  }\n\n  const beforeUnloadHandler = (evt) => {\n    if (isDirty()) {\n      evt.preventDefault();\n      evt.returnValue = message; // Modern browsers show a generic message\n      return message;\n    }\n  };\n  window.addEventListener(\"beforeunload\", beforeUnloadHandler);\n\n  const htmxConfirmHandler = (evt) => {\n    // Skip confirmation for the form's own HTMX submissions (save actions)\n    if (form.contains(evt.detail.elt)) return;\n    if (isDirty() && !confirm(message)) evt.preventDefault();\n  };\n  document.body.addEventListener(\"htmx:confirm\", htmxConfirmHandler);\n\n  return () => {\n    // Return cleanup function\n    window.removeEventListener(\"beforeunload\", beforeUnloadHandler);\n    document.body.removeEventListener(\"htmx:confirm\", htmxConfirmHandler);\n    if (options.dirtyEvent) {\n      document.removeEventListener(options.dirtyEvent, markDirty);\n    }\n    fieldListeners.forEach((field) => {\n      field.removeEventListener(\"input\", markDirty);\n      field.removeEventListener(\"change\", markDirty);\n    });\n    if (showToastHandler) {\n      window.removeEventListener(\"showToast\", showToastHandler);\n    }\n  };\n};\n",
    "/**\n * Every step is wrapped by entrance.html in a boosted island\n * (#crm-hx-allauth-content). htmx swaps the next step's island into place\n * natively, so multi-step flows (reauthenticate -> action -> recovery codes)\n * run in place with no JS - whether the island lives in the #crm-hx-modal\n * dialog (opened from the 2FA tab) or on a full entrance page.\n *\n * Uses the global htmx set by the served dependency <script> tag.\n */\nconst ISLAND_SELECTOR = \"#crm-hx-allauth-content\";\n\nwindow.htmx.on(\"htmx:beforeSwap\", (event) => {\n  const { elt, serverResponse, xhr } = event.detail;\n  if (!elt?.closest(ISLAND_SELECTOR)) return;\n  const stillInFlow = new DOMParser().parseFromString(serverResponse || \"\", \"text/html\").querySelector(ISLAND_SELECTOR);\n  if (stillInFlow) return; // next step -> let htmx swap it into place\n  event.detail.shouldSwap = false; // Left the flow. Skip the swap (native htmx attr) so the island isn't blanked\n  window.location.assign(xhr.responseURL); // Go to the fresh page\n});\n",
    "/* Project specific Javascript goes here. */\nimport { copyToClipboard, getSelected, removeForEmptyMsg, toggleAll } from \"./modules/dom-helpers.js\";\nimport { redactorUploadImage } from \"./modules/redactor-upload.js\";\nimport { closeToast, showToast } from \"./modules/toast.js\";\nimport { enableUnsavedChangesGuard } from \"./modules/unsaved-guard.js\";\nimport \"./modules/allauth.js\";\n\n// Expose helpers on window for template inline handlers / inline scripts.\nwindow.toggleAll = toggleAll;\nwindow.getSelected = getSelected;\nwindow.showToast = showToast;\nwindow.closeToast = closeToast;\nwindow.copyToClipboard = copyToClipboard;\nwindow.redactorUploadImage = redactorUploadImage;\nwindow.enableUnsavedChangesGuard = enableUnsavedChangesGuard;\nwindow.removeForEmptyMsg = removeForEmptyMsg;\n"
  ],
  "mappings": "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",
  "debugId": "E1065486DA3FB1B664756E2164756E21",
  "names": []
}