alchemy_cms 8.4.1 → 8.4.2
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.
- checksums.yaml +4 -4
- data/app/assets/builds/alchemy/alchemy_admin.min.js +1 -1
- data/app/assets/builds/alchemy/alchemy_admin.min.js.map +1 -1
- data/app/components/alchemy/admin/main_navigation/menu.rb +19 -2
- data/app/javascript/alchemy_admin/components/elements_window.js +2 -0
- data/app/javascript/alchemy_admin/components/preview_window.js +8 -1
- data/lib/alchemy/configurations/menu_item.rb +29 -5
- data/lib/alchemy/upgrader/eight_four.rb +6 -3
- data/lib/alchemy/upgrader/register_module_rewriter.rb +24 -7
- data/lib/alchemy/version.rb +1 -1
- metadata +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"alchemy_admin.min.js","sources":["../../../javascript/alchemy_admin/i18n.js","../../../javascript/alchemy_admin/hotkeys.js","../../../javascript/alchemy_admin/utils/dom_helpers.js","../../../javascript/alchemy_admin/spinner.js","../../../javascript/alchemy_admin/utils/events.js","../../../javascript/alchemy_admin/dialog.js","../../../javascript/alchemy_admin/fixed_elements.js","../../../javascript/alchemy_admin/growler.js","../../../javascript/alchemy_admin/please_wait_overlay.js","../../../javascript/alchemy_admin/initializer.js","../../../javascript/alchemy_admin/utils/json_script.js","../../../javascript/alchemy_admin/routes.js","../../../javascript/alchemy_admin/link_dialog.js","../../../javascript/alchemy_admin/components/preview_window.js","../../../javascript/alchemy_admin/confirm_dialog.js","../../../javascript/alchemy_admin/ingredient_anchor_link.js","../../../javascript/alchemy_admin/turbo_stream_actions.js","../../../javascript/alchemy_admin/components/icon.js","../../../../node_modules/.pnpm/tom-select@2.6.2/node_modules/tom-select/dist/esm/utils.js","../../../javascript/alchemy_admin/utils/tom_select.js","../../../javascript/alchemy_admin/components/remote_select.js","../../../javascript/alchemy_admin/components/attachment_select.js","../../../javascript/alchemy_admin/components/auto_submit.js","../../../javascript/alchemy_admin/components/button.js","../../../javascript/alchemy_admin/components/char_counter.js","../../../javascript/alchemy_admin/components/clear_schedule_button.js","../../../javascript/alchemy_admin/components/clipboard_button.js","../../../javascript/alchemy_admin/components/select.js","../../../javascript/alchemy_admin/components/color_select.js","../../../javascript/alchemy_admin/components/conditional_field.js","../../../javascript/alchemy_admin/components/datepicker.js","../../../javascript/alchemy_admin/components/dialog_link.js","../../../javascript/alchemy_admin/components/dirty_guard.js","../../../javascript/alchemy_admin/utils/ajax.js","../../../javascript/alchemy_admin/components/dom_id_select.js","../../../javascript/alchemy_admin/components/element_editor/publish_element_button.js","../../../javascript/alchemy_admin/utils/dispatch_page_dirty_event.js","../../../javascript/alchemy_admin/components/element_editor/delete_element_button.js","../../../javascript/alchemy_admin/components/element_editor.js","../../../javascript/alchemy_admin/components/element_select.js","../../../javascript/alchemy_admin/components/elements_window.js","../../../javascript/alchemy_admin/components/elements_window_handle.js","../../../javascript/alchemy_admin/components/file_editor.js","../../../javascript/alchemy_admin/components/list_filter.js","../../../javascript/alchemy_admin/components/main_navi_entry.js","../../../javascript/alchemy_admin/components/menu_node.js","../../../javascript/alchemy_admin/components/message.js","../../../javascript/alchemy_admin/components/growl.js","../../../javascript/alchemy_admin/components/image_cropper.js","../../../javascript/alchemy_admin/image_overlay.js","../../../javascript/alchemy_admin/components/image_overlay_link.js","../../../javascript/alchemy_admin/components/ingredient_group.js","../../../javascript/alchemy_admin/components/link_buttons/link_button.js","../../../javascript/alchemy_admin/components/link_buttons/unlink_button.js","../../../javascript/alchemy_admin/components/link_buttons.js","../../../javascript/alchemy_admin/components/news_reader.js","../../../javascript/alchemy_admin/components/node_folder.js","../../../javascript/alchemy_admin/components/node_form.js","../../../javascript/alchemy_admin/components/node_select.js","../../../javascript/alchemy_admin/components/node_tree.js","../../../javascript/alchemy_admin/utils/format.js","../../../javascript/alchemy_admin/components/uploader/file_upload.js","../../../javascript/alchemy_admin/components/uploader/progress.js","../../../javascript/alchemy_admin/components/uploader.js","../../../javascript/alchemy_admin/components/overlay.js","../../../javascript/alchemy_admin/components/page_node.js","../../../javascript/alchemy_admin/components/page_publication_fields.js","../../../javascript/alchemy_admin/components/page_select.js","../../../javascript/alchemy_admin/components/picture_archive.js","../../../javascript/alchemy_admin/components/picture_description_select.js","../../../javascript/alchemy_admin/components/picture_editor.js","../../../javascript/alchemy_admin/utils/debounce.js","../../../javascript/alchemy_admin/utils/max.js","../../../javascript/alchemy_admin/components/picture_thumbnail.js","../../../javascript/alchemy_admin/components/publish_page_button.js","../../../javascript/alchemy_admin/components/sitemap.js","../../../javascript/alchemy_admin/components/sortable_elements.js","../../../javascript/alchemy_admin/components/spinner.js","../../../javascript/alchemy_admin/components/tags_autocomplete.js","../../../javascript/alchemy_admin/components/tinymce.js","../../../javascript/alchemy_admin/components/update_check.js","../../../javascript/alchemy_admin/shoelace_theme.js","../../../javascript/alchemy_admin.js"],"sourcesContent":["const KEY_SEPARATOR = /\\./\n\nfunction nestedTranslation(translations, key) {\n const keys = key.split(KEY_SEPARATOR)\n const group = translations[keys[0]]\n if (group) {\n return group[keys[1]] || key\n }\n return key\n}\n\nfunction getTranslation(key) {\n const locale = currentLocale()\n // Host engines import components standalone, outside of the admin layout\n // that defines the global.\n const translations = window.Alchemy?.translations\n\n if (!translations) {\n console.warn(`Translations for locale ${locale} not found!`)\n return key\n }\n\n if (KEY_SEPARATOR.test(key)) {\n return nestedTranslation(translations, key)\n }\n return translations[key] || key\n}\n\nexport function currentLocale() {\n if (document.documentElement.lang) {\n return document.documentElement.lang\n }\n return \"en\"\n}\n\nexport function translate(key, replacement = undefined) {\n let translation = getTranslation(key)\n\n if (replacement) {\n return translation.replace(/%\\{.+\\}/, replacement)\n }\n return translation\n}\n","import \"keymaster\"\n\nconst bindedHotkeys = []\n\nexport default function (scope = document) {\n // Unbind all previously registered hotkeys if we are not inside a dialog.\n if (scope === document) {\n bindedHotkeys.forEach((hotkey) => key.unbind(hotkey))\n }\n\n // Binds keyboard shortcuts to search fields.\n const search_fields = scope.querySelectorAll(\".search_input_field\")\n const search_fields_clear = scope.querySelectorAll(\n \".search_field_clear, .js_filter_field_clear\"\n )\n key(\"alt+f\", function () {\n key.setScope(\"search\")\n search_fields.forEach((el) => el.focus({ focusVisible: true }))\n return false\n })\n bindedHotkeys.push(\"alt+f\")\n key(\"esc\", \"search\", function () {\n search_fields_clear.forEach((el) => el.click())\n search_fields.forEach((el) => el.blur())\n })\n bindedHotkeys.push(\"esc\")\n\n // Binds click events to buttons with hotkeys.\n //\n // Simply add a data-alchemy-hotkey attribute to your link.\n // If a hotkey is triggered by user, the click event of the element gets triggerd.\n //\n scope.querySelectorAll(\"[data-alchemy-hotkey]\").forEach(function (el) {\n const hotkey = el.dataset.alchemyHotkey\n key(hotkey, () => el.click())\n bindedHotkeys.push(hotkey)\n })\n}\n","/**\n * create a HTML element\n * @param {string} text\n * @returns {HTMLElement}\n */\nexport function createHtmlElement(text) {\n const element = document.createElement(\"template\")\n element.innerHTML = text\n return element.content.children[0]\n}\n\n/**\n * wrap element with wrappingElement\n * @param {HTMLElement} element\n * @param {HTMLElement} wrappingElement\n */\nexport function wrap(element, wrappingElement) {\n element.replaceWith(wrappingElement)\n wrappingElement.appendChild(element)\n}\n","import { createHtmlElement } from \"alchemy_admin/utils/dom_helpers\"\n\nexport default class Spinner {\n constructor(size, color = \"currentColor\") {\n this.size = size\n this.color = color\n this.spinner = undefined\n }\n\n /**\n * @returns {HTMLElement|undefined}\n */\n get el() {\n return this.spinner\n }\n /**\n * @param {HTMLElement|undefined} parent\n */\n spin(parent) {\n if (typeof parent === \"undefined\") {\n parent = document.body\n }\n this.spinner = createHtmlElement(\n `<alchemy-spinner size=\"${this.size}\" color=\"${this.color}\"></alchemy-spinner>`\n )\n parent.append(this.spinner)\n return this\n }\n\n stop() {\n if (this.spinner) {\n this.spinner.remove()\n this.spinner = undefined\n }\n }\n}\n","// Dispatches a bubbling CustomEvent from the given element.\nexport function dispatchCustomEvent(element, name, detail = {}) {\n element.dispatchEvent(new CustomEvent(name, { bubbles: true, detail }))\n}\n\nexport function on(eventName, baseSelector, targetSelector, callback) {\n document.querySelectorAll(baseSelector).forEach((baseNode) => {\n baseNode.addEventListener(eventName, (evt) => {\n const targets = Array.from(baseNode.querySelectorAll(targetSelector))\n let currentNode = evt.target\n\n while (currentNode !== baseNode) {\n if (targets.includes(currentNode)) {\n callback.call(currentNode, evt)\n return\n }\n currentNode = currentNode.parentElement\n }\n })\n })\n}\n","import { Turbo } from \"@hotwired/turbo-rails\"\nimport Hotkeys from \"alchemy_admin/hotkeys\"\nimport Spinner from \"alchemy_admin/spinner\"\nimport { createHtmlElement } from \"alchemy_admin/utils/dom_helpers\"\nimport { dispatchCustomEvent } from \"alchemy_admin/utils/events\"\n\n// Collection of all current dialog instances\nconst currentDialogs = []\n\n// Id of the Turbo Frame wrapping the dialog content.\n// Keep in sync with Alchemy::Admin::DIALOG_FRAME_ID.\nexport const DIALOG_FRAME_ID = \"alchemy_dialog_frame\"\n\nconst TURBO_STREAM_CONTENT_TYPE = \"text/vnd.turbo-stream.html\"\n\nconst DEFAULTS = {\n header_height: 36,\n size: \"400x300\",\n padding: true,\n title: \"\",\n modal: true,\n overflow: \"visible\",\n ready: () => {},\n closed: () => {}\n}\n\nexport class Dialog {\n #previousFlashParent = null\n #closing = false\n\n // Arguments:\n // - url: The url to load the content from via ajax\n // - options: A object holding options\n // - size: The maximum size of the Dialog\n // - title: The title of the Dialog\n constructor(url, options = {}) {\n this.url = url\n this.options = { ...DEFAULTS, ...options }\n const size = this.options.size.split(\"x\")\n this.width = parseInt(size[0], 10)\n this.height = parseInt(size[1], 10)\n this.build()\n this.resize()\n }\n\n // Opens the Dialog and loads the content via ajax.\n open() {\n dispatchCustomEvent(this.dialog, \"Alchemy.DialogOpen\")\n this.bind_close_events()\n if (this.options.modal) {\n this.dialog_container.showModal()\n this.#adoptFlashNotices()\n } else {\n this.dialog_container.show()\n }\n window.requestAnimationFrame(() => {\n this.dialog_container.classList.add(\"open\")\n })\n document.body.classList.add(\"prevent-scrolling\")\n currentDialogs.push(this)\n this.load()\n }\n\n // Closes the Dialog and removes it from the DOM\n close() {\n // The container stays open during the closing transition, so close() can be\n // called again mid-fade (mashing Esc, a double click on the close button or\n // backdrop). Guard against re-entry, otherwise a second transitionend\n // listener is registered and the teardown runs twice.\n if (this.#closing) {\n return true\n }\n this.#closing = true\n dispatchCustomEvent(this.dialog, \"DialogClose.Alchemy\")\n this.dialog_container.classList.remove(\"open\")\n this.dialog_container.addEventListener(\n \"transitionend\",\n () => {\n // Move the flash notices back out before removing the container, so\n // they are not destroyed together with the dialog.\n this.#releaseFlashNotices()\n this.dialog_container.close()\n this.dialog_container.remove()\n currentDialogs.pop()\n if (currentDialogs.length === 0) {\n document.body.classList.remove(\"prevent-scrolling\")\n }\n if (this.options.closed != null) {\n this.options.closed()\n }\n },\n { once: true }\n )\n return true\n }\n\n // A modal <dialog> makes everything outside its subtree inert, so flash\n // notices rendered on the body would be visible but not interactable while\n // the dialog is open. Move them into the dialog, which is in the top layer\n // and not inert, for the dialog's lifetime and restore them on close.\n #adoptFlashNotices() {\n const flashNotices = document.getElementById(\"flash_notices\")\n if (flashNotices) {\n this.#previousFlashParent = flashNotices.parentElement\n this.dialog_container.append(flashNotices)\n this.#clearTransientNotices(flashNotices)\n }\n }\n\n // The notices are not cleared on the way back. Anything transient left at\n // this point was raised by the dialog itself, typically the growl of the\n // response that closes it, and has to outlive the dialog. Moving it re-arms\n // its dismiss timer, which is what a just-raised notice wants anyway.\n #releaseFlashNotices() {\n const flashNotices = document.getElementById(\"flash_notices\")\n if (flashNotices && this.#previousFlashParent) {\n this.#previousFlashParent.append(flashNotices)\n }\n }\n\n // Only the persistent error notices need to travel with the dialog to stay\n // interactable. Moving the auto-dismissing ones would restart their dismiss\n // timers (their connectedCallback re-arms the timer), making them linger, so\n // remove them instead. A fade-out is skipped on purpose: the just-moved\n // element has no transition baseline, so dismiss()'s transitionend would never\n // fire and the node would be stranded invisible-but-present.\n #clearTransientNotices(flashNotices) {\n flashNotices\n .querySelectorAll('alchemy-message[dismissable]:not([type=\"error\"])')\n .forEach((message) => message.remove())\n }\n\n // Loads the content via ajax and replaces the Dialog body with server response.\n //\n // The Turbo-Frame header tells the server this content is rendered for a\n // dialog, so it wraps the response in the dialog frame (see set_layout). The\n // same action requested as a full page is not wrapped.\n load() {\n this.show_spinner()\n fetch(this.url, {\n headers: { \"Turbo-Frame\": DIALOG_FRAME_ID }\n })\n .then(async (response) => {\n const responseText = await response.text()\n // A stream answers a request the frame must not render, whatever the\n // status: an expired session sends a `dialog_visit` to the login page.\n // Without this the stream is never run and the dialog shows a 401 the\n // user cannot act on.\n if (\n response.headers\n .get(\"content-type\")\n ?.includes(TURBO_STREAM_CONTENT_TYPE)\n ) {\n Turbo.renderStreamMessage(responseText)\n } else if (response.ok) {\n this.replace(responseText)\n } else {\n this.show_error({\n status: response.status,\n statusText: response.statusText,\n responseText\n })\n }\n })\n .catch(() => {\n this.show_error({ status: 0 })\n })\n }\n\n // Reloads the Dialog content\n reload() {\n this.dialog_body.innerHTML = \"\"\n this.load()\n }\n\n // Replaces the dialog body with given content and initializes it.\n replace(data) {\n this.remove_spinner()\n this.dialog_body.style.display = \"none\"\n this.dialog_body.innerHTML = data\n this.init()\n dispatchCustomEvent(this.dialog, \"DialogReady.Alchemy\", {\n body: this.dialog_body\n })\n if (this.options.ready != null) {\n this.options.ready(this.dialog_body)\n }\n this.dialog_body.style.display = \"\"\n }\n\n // Adds a spinner into Dialog body\n show_spinner() {\n this.spinner = new Spinner(\"medium\")\n this.spinner.spin(this.dialog_body)\n }\n\n // Removes the spinner from Dialog body\n remove_spinner() {\n this.spinner.stop()\n }\n\n // Initializes the Dialog body\n init() {\n Hotkeys(this.dialog_body)\n window.requestAnimationFrame(() => this.#focusInitialElement())\n }\n\n // Shoelace tab panels (link, page configure) only render a few frames after\n // the content loaded, so their fields can not take focus yet. Hence the retry.\n #focusInitialElement(attempts = 0) {\n const target =\n this.dialog_body.querySelector(\"[autofocus]\") ??\n this.dialog_body.querySelector(\"form [type='submit']\")\n target?.focus()\n if (document.activeElement === this.close_button && attempts < 20) {\n window.requestAnimationFrame(() =>\n this.#focusInitialElement(attempts + 1)\n )\n }\n }\n\n // Displays an error message\n show_error(xhr, statusText) {\n if (xhr.status === 422) {\n this.dialog_body.innerHTML = xhr.responseText\n this.init()\n return\n }\n\n const { error_body, error_header, error_type } = this.error_messages(\n xhr,\n statusText\n )\n\n this.dialog_body.innerHTML = `<alchemy-message type=\"${error_type}\">\n <h1>${error_header}</h1>\n <p>${error_body}</p>\n </alchemy-message>`\n }\n\n // Returns error message based on xhr status\n error_messages(xhr, statusText) {\n let error_body,\n error_header,\n error_type = \"warning\"\n\n switch (xhr.status) {\n case 0:\n error_header = \"The server does not respond.\"\n error_body = \"Please check server and try again.\"\n break\n case 403:\n error_header = \"You are not authorized!\"\n error_body = \"Please close this window.\"\n break\n default:\n error_type = \"error\"\n if (statusText) {\n error_header = statusText\n console.error(xhr.responseText)\n } else {\n error_header = `${xhr.statusText} (${xhr.status})`\n }\n error_body = \"Please check log and try again.\"\n }\n\n return { error_header, error_body, error_type }\n }\n\n // Binds close events on:\n // - Close button\n // - Overlay (if the Dialog is a modal)\n // - ESC Key (the dialog element's cancel event)\n bind_close_events() {\n this.close_button.addEventListener(\"click\", (e) => {\n e.preventDefault()\n this.close()\n })\n this.dialog_container.addEventListener(\"close\", (e) => {\n e.preventDefault()\n this.close()\n })\n this.dialog_container.classList.add(\"closable\")\n // Use pointerdown, not click: a click whose mousedown and mouseup land on\n // different nodes is dispatched to their common ancestor — this element for\n // anything inside the dialog — closing it although the backdrop was never\n // hit, e.g. when selecting text and releasing outside.\n this.dialog_container.addEventListener(\"pointerdown\", (e) => {\n if (e.target === this.dialog_container) {\n this.close()\n }\n })\n this.dialog_container.addEventListener(\"cancel\", (e) => {\n // The <dialog>'s own cancel event (Esc) targets the container, but a file\n // input's native cancel event (dismissing the OS file picker) bubbles up\n // to the same node. Only the former should close the dialog.\n if (e.target !== this.dialog_container) {\n return\n }\n e.preventDefault()\n this.close()\n })\n }\n\n // Builds the html structure of the Dialog\n build() {\n this.dialog_container = createHtmlElement(\n '<dialog class=\"alchemy-dialog-container\"></dialog>'\n )\n this.dialog = createHtmlElement('<div class=\"alchemy-dialog\"></div>')\n this.dialog_body = createHtmlElement(\n '<div class=\"alchemy-dialog-body\"></div>'\n )\n this.dialog_header = createHtmlElement(\n '<div class=\"alchemy-dialog-header\"></div>'\n )\n this.dialog_title = createHtmlElement(\n '<div class=\"alchemy-dialog-title\"></div>'\n )\n this.close_button = createHtmlElement(\n '<button class=\"alchemy-dialog-close\"><alchemy-icon name=\"close\"></alchemy-icon></button>'\n )\n this.dialog_title.textContent = this.options.title\n this.dialog_header.append(this.dialog_title)\n this.dialog_header.append(this.close_button)\n this.dialog.append(this.dialog_header)\n this.dialog.append(this.dialog_body)\n this.dialog_container.append(this.dialog)\n if (this.options.modal) {\n this.dialog.classList.add(\"modal\")\n }\n if (this.options.padding) {\n this.dialog_body.classList.add(\"padded\")\n }\n document.body.append(this.dialog_container)\n }\n\n // Sets the correct size of the dialog\n // It normalizes the given size, so that it never acceeds the window size.\n resize() {\n const { width, height } = this.getSize()\n\n this.dialog.style.width = `${width}px`\n this.dialog.style.minHeight = `${height}px`\n this.dialog.style.overflow = this.options.overflow\n\n if (this.options.overflow === \"hidden\") {\n this.dialog_body.style.height = `${height}px`\n this.dialog_body.style.overflow = \"auto\"\n } else {\n this.dialog_body.style.minHeight = `${height}px`\n this.dialog_body.style.overflow = \"visible\"\n }\n }\n\n getSize() {\n const padding = this.options.padding ? 16 : 0\n const doc_width = window.innerWidth\n const doc_height = window.innerHeight\n\n let width = this.width\n let height = this.height\n\n if (width >= doc_width) {\n width = doc_width - padding\n }\n\n if (height >= doc_height) {\n height = doc_height - padding - DEFAULTS.header_height\n }\n\n return { width, height }\n }\n}\n\n// Gets the last dialog instantiated, which is the current one.\nexport function currentDialog() {\n const { length } = currentDialogs\n if (length === 0) {\n return\n }\n return currentDialogs[length - 1]\n}\n\n// Utility function to close the current Dialog\n//\n// You can pass a callback function, that gets triggered after the Dialog gets closed.\n//\nexport function closeCurrentDialog(callback) {\n const dialog = currentDialog()\n if (dialog != null) {\n dialog.options.closed = callback\n return dialog.close()\n }\n}\n\n// Utility function to open a new Dialog\nexport function openDialog(url, options) {\n if (!url) {\n throw \"No url given! Please provide an url.\"\n }\n const dialog = new Dialog(url, options)\n dialog.open()\n}\n","// Creates a fixed element tab.\nexport function createTab(element_id, label) {\n const fixed_elements = document.getElementById(\"fixed-elements\")\n const panel_name = `fixed-element-${element_id}`\n\n const tab = `<sl-tab slot=\"nav\" panel=\"${panel_name}\">${label}</sl-tab>`\n const panel = `<sl-tab-panel name=\"${panel_name}\" style=\"--padding: 0\" />`\n\n fixed_elements.innerHTML += tab + panel\n\n window.requestAnimationFrame(function () {\n fixed_elements.show(panel_name)\n })\n}\n\nexport function removeTab(element_id) {\n const fixed_elements = document.getElementById(\"fixed-elements\")\n const panel_name = `fixed-element-${element_id}`\n\n fixed_elements.querySelector(`sl-tab[panel=\"${panel_name}\"]`).remove()\n fixed_elements.querySelector(`sl-tab-panel[name=\"${panel_name}\"]`).remove()\n\n fixed_elements.show(\"main-content-elements\")\n}\n","import { createHtmlElement } from \"alchemy_admin/utils/dom_helpers\"\n\nfunction build(message, flashType) {\n const flashNotices = document.getElementById(\"flash_notices\")\n const flashMessage = createHtmlElement(`\n <alchemy-message type=\"${flashType}\" dismissable>\n ${message}\n </alchemy-message>\n `)\n flashNotices.append(flashMessage)\n}\n\nexport function growl(message, style = \"notice\") {\n build(message, style)\n}\n","/**\n * To show the \"Please wait\" overlay.\n * Pass false to hide it.\n * @param {boolean} show\n */\nexport default function pleaseWaitOverlay(show = true) {\n document.querySelector(\"alchemy-overlay\").show = !!show\n}\n","import Hotkeys from \"alchemy_admin/hotkeys\"\nimport pleaseWaitOverlay from \"alchemy_admin/please_wait_overlay\"\nimport { openDialog } from \"alchemy_admin/dialog\"\n\n// Opens the help dialog when the user presses the \"?\" key outside of a field.\nfunction showHelp(evt) {\n if (\n !evt.target.matches(\"input, textarea\") &&\n String.fromCharCode(evt.which) === \"?\"\n ) {\n openDialog(\"/admin/help\", {\n title: Alchemy.t(\"help\"),\n size: \"500x400\"\n })\n return false\n } else {\n return true\n }\n}\n\nexport default function Initializer() {\n // We obviously have javascript enabled.\n document.documentElement.classList.remove(\"no-js\")\n\n // Initialize hotkeys.\n Hotkeys()\n\n // (Re)bind the help dialog hotkey.\n document.removeEventListener(\"keypress\", showHelp)\n document.addEventListener(\"keypress\", showHelp)\n\n // Add observer for please wait overlay.\n document.querySelectorAll(\".please_wait\").forEach((element) => {\n element.addEventListener(\"click\", pleaseWaitOverlay)\n })\n\n // Hack for enabling tab focus for <a>'s styled as button.\n document.querySelectorAll(\"a.button\").forEach((button) => {\n button.setAttribute(\"tabindex\", 0)\n })\n\n // Override the filter of keymaster.js so we can blur the fields on esc key.\n key.filter = function (event) {\n let tagName = (event.target || event.srcElement).tagName\n return (\n key.isPressed(\"esc\") ||\n !(tagName === \"INPUT\" || tagName === \"SELECT\" || tagName === \"TEXTAREA\")\n )\n }\n}\n","/**\n * Reads the data the server rendered into a JSON script tag\n * @param {string} id\n * @returns {any|undefined} undefined if no such script tag is present\n */\nexport function readJSONScript(id) {\n const script = document.getElementById(id)\n\n if (!script) return undefined\n\n return JSON.parse(script.textContent)\n}\n","import { readJSONScript } from \"alchemy_admin/utils/json_script\"\n\n// Member routes are rendered with 1 as the record id and become functions\n// that put the given id in its place.\nfunction buildRoutes({ collection = {}, member = {} } = {}) {\n const routes = { ...collection }\n\n Object.entries(member).forEach(([name, path]) => {\n routes[name] =\n typeof path === \"string\"\n ? (id) => path.replace(/1/, id)\n : buildRoutes({ member: path })\n })\n\n return routes\n}\n\nexport const routes = buildRoutes(readJSONScript(\"alchemy_routes\"))\n","import { translate } from \"alchemy_admin/i18n\"\nimport { Dialog } from \"alchemy_admin/dialog\"\nimport { routes } from \"alchemy_admin/routes\"\n\n// Matches a URL fragment (#anchor) at the end of a string.\n// Covers RFC 3986 unreserved characters (ALPHA, DIGIT, \"-\", \".\", \"_\", \"~\")\n// which are the characters valid in URL fragments and common in DOM element IDs.\nconst ANCHOR_REGEX = /#[\\w.~-]+$/\n\n// The server renders the expression as a JavaScript literal, so that whatever\n// a host application configures keeps the meaning it had as an inline script.\n// A replaced external tab may not render it, the server validates urls anyway.\nfunction isLinkUrl(url) {\n const literal =\n document.getElementById(\"external_link\")?.dataset.linkUrlRegexp\n if (!literal) return true\n\n const end = literal.lastIndexOf(\"/\")\n return new RegExp(literal.slice(1, end), literal.slice(end + 1)).test(url)\n}\n\n// Represents the link Dialog that appears, if a user clicks the link buttons\n// in TinyMCE or on an Ingredient that has links enabled (e.g. Picture)\n//\nexport class LinkDialog extends Dialog {\n #onCreateLink\n\n constructor(link) {\n const url = new URL(routes.link_admin_pages_path, window.location)\n const parameterMapping = {\n url: link.url,\n selected_tab: link.type,\n link_title: link.title,\n link_target: link.target\n }\n\n // searchParams.set would also add undefined values\n Object.keys(parameterMapping).forEach((key) => {\n if (parameterMapping[key]) {\n url.searchParams.set(key, parameterMapping[key])\n }\n })\n\n super(url.href, {\n size: \"600x320\",\n title: translate(\"Link\")\n })\n }\n\n /**\n * Called from Dialog class after the url was loaded\n */\n replace(data) {\n // let Dialog class handle the content replacement\n super.replace(data)\n this.#attachEvents()\n }\n\n /**\n * make the open method a promise\n * maybe in a future version the whole Dialog will respond with a promise result if the dialog is closing\n * @returns {Promise<unknown>}\n */\n open() {\n super.open()\n return new Promise((resolve) => (this.#onCreateLink = resolve))\n }\n\n /**\n * Attaches click events to forms in the link dialog.\n */\n #attachEvents() {\n // enable the dom selection in internal link tab\n const internalForm = document.querySelector(\n '[data-link-form-type=\"internal\"]'\n )\n const attachmentSelect = document.querySelector(\n '[data-link-form-type=\"file\"] alchemy-attachment-select'\n )\n\n internalForm.addEventListener(\"Alchemy.RemoteSelect.Change\", (e) => {\n this.#updatePage(e.detail.added)\n })\n\n attachmentSelect.addEventListener(\"Alchemy.RemoteSelect.Change\", (e) => {\n const attachment = e.detail.added\n document.getElementById(\"file_link\").value = attachment\n ? attachment.url\n : \"\"\n })\n\n document.querySelectorAll(\"[data-link-form-type]\").forEach((form) => {\n form.addEventListener(\"submit\", (e) => {\n e.preventDefault()\n this.#submitForm(e.target.dataset.linkFormType)\n })\n })\n }\n\n /**\n * update page select and set anchor select\n * @param page\n */\n #updatePage(page = null) {\n const internalLink = document.getElementById(\"internal_link\")\n const domIdSelect = document.querySelector(\n '[data-link-form-type=\"internal\"] alchemy-dom-id-api-select'\n )\n\n internalLink.value = page ? page.url_path : \"\"\n domIdSelect.page = page ? page.id : undefined\n }\n\n /**\n * submit the form itself\n * @param linkType\n */\n #submitForm(linkType) {\n const elementAnchor = document.getElementById(\"element_anchor\")\n let url = document.getElementById(`${linkType}_link`).value\n\n if (linkType === \"internal\" && elementAnchor.value !== \"\") {\n // remove possible fragments on the url and attach the fragment (which contains the #)\n url = url.replace(ANCHOR_REGEX, \"\") + elementAnchor.value\n } else if (linkType === \"external\" && !isLinkUrl(url)) {\n // show validation error and prevent link creation\n this.#showValidationError()\n return\n }\n\n // Create the link\n this.#onCreateLink({\n url: url.trim(),\n title: document.getElementById(`${linkType}_link_title`).value,\n target: document.getElementById(`${linkType}_link_target`)?.value,\n type: linkType\n })\n this.close()\n }\n\n /**\n * Shows validation errors\n */\n #showValidationError() {\n const errors = document.getElementById(\"errors\")\n errors.querySelector(\"ul\").innerHTML =\n `<li>${Alchemy.t(\"url_validation_failed\")}</li>`\n errors.style.display = \"block\"\n }\n}\n","import { growl } from \"alchemy_admin/growler\"\nimport { translate } from \"alchemy_admin/i18n\"\n\nclass PreviewWindow extends HTMLIFrameElement {\n #afterLoad\n #reloadIcon\n #loadTimeout\n\n handleEvent(evt) {\n if (evt.type === \"load\") {\n this.#clearLoadTimeout()\n this.#stopSpinner()\n this.#afterLoad?.call(this, evt)\n }\n }\n\n #onPreviewReady = (event) => {\n if (event.data.message === \"Alchemy.previewReady\") {\n this.#clearLoadTimeout()\n this.#stopSpinner()\n // Deliberately does not resolve #afterLoad: previewReady fires before the\n // images are loaded, so the page is not yet tall enough to scroll and a\n // scrollIntoView triggered now (e.g. focusing the saved element) is a\n // no-op. The load event handler resolves it once the layout is final.\n }\n }\n\n connectedCallback() {\n let url = this.url\n\n this.addEventListener(\"load\", this)\n this.#attachEvents()\n window.addEventListener(\"message\", this.#onPreviewReady)\n\n if (window.localStorage.getItem(\"alchemy-preview-url\")) {\n url = window.localStorage.getItem(\"alchemy-preview-url\")\n this.previewUrlSelect.value = url\n }\n\n this.refresh(url)\n }\n\n disconnectedCallback() {\n this.removeEventListener(\"load\", this)\n key.unbind(\"alt+r\")\n window.removeEventListener(\"message\", this.#onPreviewReady)\n }\n\n postMessage(data) {\n this.contentWindow.postMessage(data, \"*\")\n }\n\n resize(width) {\n this.style.width = `${width}px`\n }\n\n refresh(url) {\n this.#startSpinner()\n\n if (url) {\n this.src = url\n } else {\n this.src = this.url\n }\n\n // Set timeout as fallback - if iframe doesn't load, stop spinner anyway\n this.#clearLoadTimeout()\n this.#loadTimeout = setTimeout(() => {\n this.#stopSpinner()\n growl(translate(\"Preview failed to load\"), \"warning\")\n }, this.loadTimeout)\n\n return new Promise((resolve) => {\n this.#afterLoad = resolve\n })\n }\n\n set isDragged(dragged) {\n this.style.transitionProperty = dragged ? \"none\" : null\n this.style.pointerEvents = dragged ? \"none\" : null\n }\n\n #attachEvents() {\n this.reloadButton?.addEventListener(\"click\", (evt) => {\n evt.preventDefault()\n this.refresh()\n })\n\n key(\"alt+r\", () => this.refresh())\n\n this.sizeSelect.addEventListener(\"change\", (evt) => {\n const select = evt.target\n const width = select.value\n\n if (width === \"\") {\n this.style.width = null\n } else {\n this.resize(width)\n }\n })\n\n this.previewUrlSelect?.addEventListener(\"change\", (evt) => {\n const url = evt.target.value\n window.localStorage.setItem(\"alchemy-preview-url\", url)\n this.refresh(url)\n })\n }\n\n #startSpinner() {\n // Only save the reload icon if we're not already showing a spinner\n if (!this.reloadButton.innerHTML.includes(\"alchemy-spinner\")) {\n this.#reloadIcon = this.reloadButton.innerHTML\n }\n this.reloadButton.innerHTML = `<alchemy-spinner size=\"small\"></alchemy-spinner>`\n }\n\n #stopSpinner() {\n this.reloadButton.innerHTML = this.#reloadIcon\n }\n\n #clearLoadTimeout() {\n if (this.#loadTimeout) {\n clearTimeout(this.#loadTimeout)\n this.#loadTimeout = null\n }\n }\n\n get url() {\n return this.getAttribute(\"url\")\n }\n\n get loadTimeout() {\n const seconds = parseInt(this.getAttribute(\"load-timeout\"), 10)\n return (Number.isNaN(seconds) ? 5 : seconds) * 1000\n }\n\n get sizeSelect() {\n return document.querySelector(\"select#preview_size\")\n }\n\n get previewUrlSelect() {\n return document.querySelector(\"select#preview_url\")\n }\n\n get reloadButton() {\n return document.querySelector(\"#reload_preview_button\")\n }\n}\n\ncustomElements.define(\"alchemy-preview-window\", PreviewWindow, {\n extends: \"iframe\"\n})\n\nexport function reloadPreview() {\n const previewWindow = document.getElementById(\"alchemy_preview_window\")\n previewWindow.refresh()\n}\n","import { createHtmlElement } from \"alchemy_admin/utils/dom_helpers\"\nimport { currentDialog } from \"alchemy_admin/dialog\"\nimport { translate } from \"alchemy_admin/i18n\"\n\nconst getDefaults = () => ({\n // The default size of the dialog\n size: \"300x100\",\n title: translate(\"Please confirm\"),\n ok_label: translate(\"Yes\"),\n cancel_label: translate(\"No\"),\n on_ok() {}\n})\n\nclass ConfirmDialog {\n constructor(message, options = {}) {\n this.message = message\n this.options = { ...getDefaults(), ...options }\n this.#build()\n this.#bindEvents()\n }\n\n open() {\n requestAnimationFrame(() => {\n this.dialog.show()\n })\n }\n\n #build() {\n const width = this.options.size.split(\"x\")[0]\n this.dialog = createHtmlElement(`\n <sl-dialog label=\"${this.options.title}\" style=\"--width: ${width}px\">\n ${this.message}\n <button slot=\"footer\" type=\"reset\" class=\"secondary mx-1 my-0\" autofocus>\n ${this.options.cancel_label}\n </button>\n <button slot=\"footer\" type=\"submit\" class=\"mx-1 my-0\">\n ${this.options.ok_label}\n </button>\n </sl-dialog>\n `)\n // A native <dialog> opened with showModal() sits in the top layer and makes\n // everything outside its subtree inert. Append the confirm into the open\n // dialog (if any) so it stays visible and interactable above it, instead of\n // rendering below and unclickable on the body.\n const container = currentDialog()?.dialog_container ?? document.body\n container.append(this.dialog)\n }\n\n #bindEvents() {\n this.cancelButton.addEventListener(\"click\", (evt) => {\n evt.preventDefault()\n this.options.on_cancel()\n this.dialog.hide()\n })\n this.okButton.addEventListener(\"click\", (evt) => {\n evt.preventDefault()\n this.options.on_ok()\n this.dialog.hide()\n })\n // Prevent the dialog from closing when the user clicks on the overlay\n this.dialog.addEventListener(\"sl-request-close\", (event) => {\n if (event.detail.source === \"overlay\") {\n this.options.on_cancel()\n event.preventDefault()\n }\n })\n // Remove the dialog from the DOM after it has been hidden\n this.dialog.addEventListener(\"sl-after-hide\", () => {\n this.dialog.remove()\n })\n }\n\n get cancelButton() {\n return this.dialog.querySelector(\"button[type=reset]\")\n }\n\n get okButton() {\n return this.dialog.querySelector(\"button[type=submit]\")\n }\n}\n\n/* Opens a confirm dialog\n *\n * @param {string} message - The message that will be displayed to the user\n * @param {Object} [options={}] - Configuration options for the dialog\n * @param {string} [options.title=\"Please confirm\"] - The title of the overlay window\n * @param {string} [options.cancel_label=\"No\"] - The label of the cancel button\n * @param {string} [options.ok_label=\"Yes\"] - The label of the ok button\n *\n * @returns {Promise<void>} A promise that resolves to true when the OK button is clicked and\n * resolves to false when the cancel button is clicked. Works as confirm dialog replacement\n * for Turbo.confirm.\n */\nexport function openConfirmDialog(message, options = {}) {\n return new Promise((resolve) => {\n const dialog = new ConfirmDialog(message, {\n ...options,\n on_ok() {\n resolve(true)\n },\n on_cancel() {\n resolve(false)\n }\n })\n dialog.open()\n })\n}\n","export default class IngredientAnchorLink {\n static updateIcon(ingredientId, active = false) {\n const ingredientEditor = document.querySelector(\n `[data-ingredient-id=\"${ingredientId}\"]`\n )\n if (ingredientEditor) {\n const icon = ingredientEditor.querySelector(\n \".edit-ingredient-anchor-link alchemy-icon\"\n )\n icon.setAttribute(\"icon-style\", active ? \"fill\" : \"line\")\n }\n }\n}\n","import { Turbo } from \"@hotwired/turbo-rails\"\nimport { closeCurrentDialog } from \"alchemy_admin/dialog\"\nimport { reloadPreview } from \"alchemy_admin/components/preview_window\"\nimport { removeTab } from \"alchemy_admin/fixed_elements\"\nimport IngredientAnchorLink from \"alchemy_admin/ingredient_anchor_link\"\nimport pleaseWaitOverlay from \"alchemy_admin/please_wait_overlay\"\n\n// A dialog form submits into the dialog frame, so a redirect would be followed\n// inside the dialog and render the target page in it. The server answers a\n// successful submission with this action instead: close the dialog, then visit\n// the destination the action would have redirected to.\nTurbo.StreamActions.dialog_visit = function () {\n const url = this.getAttribute(\"url\")\n\n if (!closeCurrentDialog(() => Turbo.visit(url))) {\n Turbo.visit(url)\n }\n}\n\n// The editors observe their form field for mutations, so it must not be replaced.\n\nTurbo.StreamActions.assign_picture = function () {\n const [formField] = this.targetElements\n\n if (!formField) return\n\n formField.value = this.getAttribute(\"picture-id\")\n formField.setAttribute(\n \"data-image-file-width\",\n this.getAttribute(\"image-file-width\")\n )\n formField.setAttribute(\n \"data-image-file-height\",\n this.getAttribute(\"image-file-height\")\n )\n\n closeCurrentDialog(() => {\n formField.closest(\"alchemy-element-editor\")?.setDirty()\n })\n}\n\nTurbo.StreamActions.assign_attachment = function () {\n const [formField] = this.targetElements\n\n if (!formField) return\n\n const fileEditor = formField.parentElement\n\n formField.value = this.getAttribute(\"attachment-id\")\n fileEditor.querySelector(\":scope > .file_name\").textContent =\n this.getAttribute(\"attachment-name\")\n fileEditor\n .querySelector(\":scope > .file_icon\")\n .replaceChildren(this.templateContent)\n fileEditor\n .querySelector(\":scope > .remove_file_link\")\n .classList.remove(\"hidden\")\n\n closeCurrentDialog(() => {\n formField.closest(\"alchemy-element-editor\")?.setDirty(formField)\n })\n}\n\n// An intermediate action that closes the current dialog. This will be gone once\n// all dialogs resolve through a promise and no longer have to be closed\n// implicitly.\nTurbo.StreamActions.close_current_dialog = function () {\n closeCurrentDialog()\n}\n\nTurbo.StreamActions.reload_preview = function () {\n reloadPreview()\n}\n\nTurbo.StreamActions.remove_fixed_element = function () {\n removeTab(this.getAttribute(\"element-id\"))\n}\n\nTurbo.StreamActions.update_anchor_icon = function () {\n IngredientAnchorLink.updateIcon(\n this.getAttribute(\"ingredient-id\"),\n this.getAttribute(\"active\") === \"true\"\n )\n}\n\nTurbo.StreamActions.hide_please_wait_overlay = function () {\n pleaseWaitOverlay(false)\n}\n","class Icon extends HTMLElement {\n static get observedAttributes() {\n return [\"name\", \"size\", \"icon-style\"]\n }\n\n constructor() {\n super()\n this.spriteUrl =\n document\n .querySelector('link[rel=\"preload\"][as=\"image\"]')\n ?.getAttribute(\"href\") ?? \"\"\n }\n\n connectedCallback() {\n this.render()\n }\n\n attributeChangedCallback() {\n this.render()\n }\n\n render() {\n const sizeClass = this.size ? ` icon--${this.size}` : \"\"\n this.innerHTML = `<svg class=\"icon${sizeClass}\"><use href=\"${this.spriteUrl}#ri-${this.iconName}${this.style}\" /></svg>`\n }\n\n set name(value) {\n this.setAttribute(\"name\", value)\n }\n\n get iconName() {\n return this.getAttribute(\"name\")\n }\n\n get size() {\n return this.getAttribute(\"size\")\n }\n\n get style() {\n const value = this.getAttribute(\"icon-style\")\n switch (value) {\n case \"none\":\n return \"\"\n case null:\n return \"-line\"\n default:\n return `-${value}`\n }\n }\n}\n\n// Standalone components import this module next to the admin bundle, which\n// already defined the element from its own copy.\nif (!customElements.get(\"alchemy-icon\")) {\n customElements.define(\"alchemy-icon\", Icon)\n}\n","/**\n * Converts a scalar to its best string representation\n * for hash keys and HTML attribute values.\n *\n * Transformations:\n * 'str' -> 'str'\n * null -> ''\n * undefined -> ''\n * true -> '1'\n * false -> '0'\n * 0 -> '0'\n * 1 -> '1'\n *\n */\nexport const hash_key = (value) => {\n if (typeof value === 'undefined' || value === null)\n return null;\n return get_hash(value);\n};\nexport const get_hash = (value) => {\n if (typeof value === 'boolean')\n return value ? '1' : '0';\n return value + '';\n};\n/**\n * Escapes a string for use within HTML.\n *\n */\nexport const escape_html = (str) => {\n return (str + '')\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"');\n};\n/**\n * use setTimeout if timeout > 0\n */\nexport const timeout = (fn, timeout) => {\n if (timeout > 0) {\n return window.setTimeout(fn, timeout);\n }\n fn.call(null);\n return null;\n};\n/**\n * Debounce the user provided load function\n *\n */\nexport const loadDebounce = (fn, delay) => {\n var timeout;\n return function (value, callback) {\n var self = this;\n if (timeout) {\n self.loading = Math.max(self.loading - 1, 0);\n clearTimeout(timeout);\n }\n timeout = setTimeout(function () {\n timeout = null;\n self.loadedSearches[value] = true;\n fn.call(self, value, callback);\n }, delay);\n };\n};\n/**\n * Debounce all fired events types listed in `types`\n * while executing the provided `fn`.\n *\n */\nexport const debounce_events = (self, types, fn) => {\n var type;\n var trigger = self.trigger;\n var event_args = {};\n // override trigger method\n self.trigger = function () {\n var type = arguments[0];\n if (types.indexOf(type) !== -1) {\n event_args[type] = arguments;\n }\n else {\n return trigger.apply(self, arguments);\n }\n };\n // invoke provided function\n fn.apply(self, []);\n self.trigger = trigger;\n // trigger queued events\n for (type of types) {\n if (type in event_args) {\n trigger.apply(self, event_args[type]);\n }\n }\n};\n/**\n * Determines the current selection within a text input control.\n * Returns an object containing:\n * - start\n * - length\n *\n * Note: \"selectionStart, selectionEnd ... apply only to inputs of types text, search, URL, tel and password\"\n * \t- https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement/setSelectionRange\n */\nexport const getSelection = (input) => {\n return {\n start: input.selectionStart || 0,\n length: (input.selectionEnd || 0) - (input.selectionStart || 0),\n };\n};\n/**\n * Prevent default\n *\n */\nexport const preventDefault = (evt, stop = false) => {\n if (evt) {\n evt.preventDefault();\n if (stop) {\n evt.stopPropagation();\n }\n }\n};\n/**\n * Add event helper\n *\n */\nexport const addEvent = (target, type, callback, options) => {\n target.addEventListener(type, callback, options);\n};\n/**\n * Return true if the requested key is down\n * Will return false if more than one control character is pressed ( when [ctrl+shift+a] != [ctrl+a] )\n * The current evt may not always set ( eg calling advanceSelection() )\n *\n */\nexport const isKeyDown = (key_name, evt) => {\n if (!evt) {\n return false;\n }\n if (!evt[key_name]) {\n return false;\n }\n var count = (evt.altKey ? 1 : 0) + (evt.ctrlKey ? 1 : 0) + (evt.shiftKey ? 1 : 0) + (evt.metaKey ? 1 : 0);\n if (count === 1) {\n return true;\n }\n return false;\n};\n/**\n * Get the id of an element\n * If the id attribute is not set, set the attribute with the given id\n *\n */\nexport const getId = (el, id) => {\n const existing_id = el.getAttribute('id');\n if (existing_id) {\n return existing_id;\n }\n el.setAttribute('id', id);\n return id;\n};\n/**\n * Returns a string with backslashes added before characters that need to be escaped.\n */\nexport const addSlashes = (str) => {\n return str.replace(/[\\\\\"']/g, '\\\\$&');\n};\n/**\n *\n */\nexport const append = (parent, node) => {\n if (node)\n parent.append(node);\n};\n/**\n * Iterates over arrays and hashes.\n *\n * ```\n * iterate(this.items, function(item, id) {\n * // invoked for each item\n * });\n * ```\n *\n */\nexport const iterate = (object, callback) => {\n if (Array.isArray(object)) {\n object.forEach(callback);\n }\n else {\n for (var key in object) {\n if (object.hasOwnProperty(key)) {\n callback(object[key], key);\n }\n }\n }\n};\n//# sourceMappingURL=utils.js.map","import { translate } from \"alchemy_admin/i18n\"\nimport {\n autoUpdate,\n computePosition,\n flip,\n offset,\n shift,\n size\n} from \"@floating-ui/dom\"\n\nconst DROPDOWN_WINDOW_MARGIN = 16\nconst DROPDOWN_MIN_HEIGHT = 120\nconst DROPDOWN_MAX_WIDTH = 480\n\n// Tom Select hides the native element it enhances, so the element can not take\n// focus itself. Focus the Tom Select control instead, falling back to the\n// native focus while Tom Select has not initialized yet. Tom Select based custom\n// elements use this for their focus() override, so acting on an autofocus\n// attribute (e.g. from a dialog) lands on the visible control.\nexport function focusTomSelect(tomSelect, focusFallback) {\n if (tomSelect) {\n tomSelect.focus()\n } else {\n focusFallback()\n }\n}\n\n// Tom Select's `destroy()` removes the dropdown, but it never closes it, so the\n// `dropdown_close` handler below would not run. Close first, so a select that is\n// torn down while its dropdown is open (e.g. because a Turbo render replaces the\n// form) does not leave the full window dropdown mask behind, swallowing every\n// click.\nexport function teardownTomSelect(tomSelect) {\n tomSelect?.close(false)\n tomSelect?.destroy()\n}\n\n// Dropdown positioning shared by the Tom Select based components. It appends the\n// dropdown to the body and keeps it positioned with Floating UI so it is not\n// clipped inside dialogs or scrollable panels. The returned handlers are used as\n// Tom Select `onDropdownOpen`/`onDropdownClose` callbacks and are therefore\n// invoked with the Tom Select instance as `this`.\nexport function createDropdownPositioning() {\n const dropdownMask = document.createElement(\"div\")\n dropdownMask.className = \"ts-dropdown-mask\"\n\n let removeAutoUpdater = () => {}\n\n return {\n async onDropdownOpen() {\n // Make the dropdown at least as wide as the control.\n const styles = {\n minWidth: `${this.control.offsetWidth}px`\n }\n // If the select is inside a dialog, we need to ensure the dropdown appears above it.\n if (this.control.closest(\".alchemy-dialog-body, .alchemy-popover\")) {\n styles.zIndex = \"101\"\n }\n Object.assign(this.dropdown.style, styles)\n // Append the dropdown to the body to avoid overflow issues. Inside a\n // native modal dialog it has to go into the dialog element instead,\n // because content outside of the top layer would be inert and\n // unreachable.\n const dropdownRoot = this.control.closest(\"dialog\") || document.body\n dropdownRoot.append(dropdownMask)\n dropdownRoot.append(this.dropdown)\n // Use Floating UI to position the dropdown relative to the control.\n const updatePosition = async () => {\n // Use Floating UI to calculate the dropdown position\n const { x, y } = await computePosition(this.control, this.dropdown, {\n // Line the dropdown up with the left edge of the control. Without this\n // it is centered on the control and a dropdown grown wide by long\n // entries hangs over into the page left of the field.\n placement: \"bottom-start\",\n middleware: [\n // Make some space between the control and the dropdown to prevent overlap\n offset(2),\n // Flip to the opposite side if there’s not enough space\n flip(),\n // Keep the dropdown inside the window\n shift({ padding: DROPDOWN_WINDOW_MARGIN }),\n // Ensure the dropdown fits within the viewport\n size({\n padding: DROPDOWN_WINDOW_MARGIN,\n apply({ availableWidth, availableHeight, elements }) {\n // Keep long entries from stretching the dropdown across the\n // window, but never make it narrower than the control.\n Object.assign(elements.floating.style, {\n maxWidth: `max(${elements.reference.offsetWidth}px, min(${availableWidth}px, var(--select-dropdown-max-width, ${DROPDOWN_MAX_WIDTH}px)))`\n })\n Object.assign(\n elements.floating.querySelector(\".ts-dropdown-content\").style,\n {\n maxHeight: `${Math.max(DROPDOWN_MIN_HEIGHT, availableHeight - DROPDOWN_WINDOW_MARGIN)}px`\n }\n )\n }\n })\n ]\n })\n // Position the dropdown\n Object.assign(this.dropdown.style, {\n left: `${x}px`,\n top: `${y}px`\n })\n }\n // Update the dropdown position whenever the window resizes or scrolls.\n removeAutoUpdater = autoUpdate(\n this.control,\n this.dropdown,\n updatePosition\n )\n },\n onDropdownClose() {\n this.control_input.classList.remove(\"has-value\")\n this.control_input.blur()\n // Remove the dropdown from DOM when closed.\n this.dropdown.remove()\n dropdownMask.remove()\n // Cleanup the position auto-update when the dropdown is closed.\n removeAutoUpdater()\n }\n }\n}\n\n// Configuration for Tom Select's `remove_button` plugin. The plugin renders its\n// control as a non-semantic element (a `<div role=\"button\">` by default); render\n// a real `<button>` instead so assistive tech and the keyboard treat it as one.\n// The translated title also names the icon-only button via `aria-label`, like the\n// clear button does.\nexport function removeButton() {\n return {\n title: translate(\"Remove\"),\n html(data) {\n const button = document.createElement(\"button\")\n button.type = \"button\"\n button.className = data.className\n button.title = data.title\n button.setAttribute(\"aria-label\", data.title)\n button.tabIndex = -1\n button.innerHTML = `<alchemy-icon name=\"close\" size=\"sm\"></alchemy-icon>`\n return button\n }\n }\n}\n\n// Customize the \"create\" and \"no results\" dropdown messages with i18n. Shared by\n// the Tom Select based components as `render` functions.\nexport const dropdownMessages = {\n option_create(data, escape) {\n return `<div class=\"create\">\n ${translate(\"Add\")} <strong>${escape(data.input)}</strong>…\n </div>`\n },\n // Tom Select prepends this above the results it already has, which would\n // shift them.\n loading() {\n if (Object.keys(this.options).length > 0) return\n return `<div class=\"loading-results\">${translate(\"Please wait, loading\")}…</div>`\n },\n no_results() {\n return `<div class=\"no-results\">${translate(\"No results found\")}</div>`\n },\n // Shown at the bottom of the dropdown while the virtual scroll plugin appends\n // the next page of results.\n loading_more: () =>\n `<div class=\"loading-more\">${translate(\"Loading more results\")}…</div>`,\n // Shown at the bottom of the dropdown once every page has been loaded.\n no_more_results: () =>\n `<div class=\"no-more-results\">${translate(\"No more results\")}</div>`\n}\n","import TomSelect from \"tom-select\"\nimport \"alchemy_admin/components/icon\"\nimport { escape_html } from \"tom-select/utils\"\nimport { growl } from \"alchemy_admin/growler\"\nimport { translate } from \"alchemy_admin/i18n\"\nimport {\n createDropdownPositioning,\n dropdownMessages,\n focusTomSelect,\n teardownTomSelect\n} from \"alchemy_admin/utils/tom_select\"\n\n/**\n * Escapes a string for literal use inside a `RegExp`, so a term with\n * metacharacters (e.g. `(`) matches itself instead of being compiled as a\n * pattern, which could match the wrong text or throw a SyntaxError.\n * @param {string} value\n * @returns {string}\n */\nexport function escapeRegExp(value) {\n return String(value).replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\")\n}\n\nexport function hightlightTerm(name, term) {\n // The result is rendered raw as the option's primary text, so the name must\n // be escaped before the term markup is woven in — otherwise a crafted name\n // (e.g. a menu node title) is a stored XSS sink.\n const escapedName = escape_html(name)\n if (!term) return escapedName\n return escapedName.replace(\n new RegExp(escapeRegExp(term), \"gi\"),\n (match) => `<em>${match}</em>`\n )\n}\n\n/**\n * Serializes a (possibly nested) params object into a Rails/Ransack style query\n * string, e.g. `{ q: { name_cont: \"x\" }, page: 1 }` => `q%5Bname_cont%5D=x&page=1`.\n * @param {object} params\n * @param {string} [prefix]\n * @returns {string}\n */\nexport function serializeParams(params, prefix) {\n return Object.keys(params)\n .map((key) => {\n const value = params[key]\n const scopedKey = prefix ? `${prefix}[${key}]` : key\n if (value === null || value === undefined) return \"\"\n if (Array.isArray(value)) {\n return value\n .map(\n (item) =>\n `${encodeURIComponent(`${scopedKey}[]`)}=${encodeURIComponent(item)}`\n )\n .join(\"&\")\n }\n if (typeof value === \"object\") {\n return serializeParams(value, scopedKey)\n }\n return `${encodeURIComponent(scopedKey)}=${encodeURIComponent(value)}`\n })\n .filter((part) => part !== \"\")\n .join(\"&\")\n}\n\nexport class RemoteSelect extends HTMLElement {\n #tomSelect = null\n // The currently selected item, used to compute the added/removed delta that\n // consumers of the RemoteSelect.Change event rely on.\n #selectedItem = null\n // In multiple mode the same delta is computed against this map of the\n // currently selected records, keyed by their id.\n #selectedItems = new Map()\n\n connectedCallback() {\n const element = this.input\n element.classList.add(\"alchemy_selectbox\")\n if (element instanceof HTMLInputElement) {\n // Preselection is driven by the `selection` attribute, not by the input's\n // value. Clear the value during setup so Tom Select does not turn it into a\n // stray option and does not overwrite it with the selected id — the link\n // dialog renders a URL in this input and reads it back. Restore it afterwards.\n const inputValue = element.value\n element.value = \"\"\n this.#tomSelect = new TomSelect(element, this.tomSelectConfig)\n element.value = inputValue\n } else {\n // A multiple select wraps a <select>, which has no such value to protect.\n this.#tomSelect = new TomSelect(element, this.tomSelectConfig)\n }\n // A spinner shown inside the control while results load. Tom Select toggles\n // the wrapper's `loading` class, which reveals it (see the stylesheet).\n this.#tomSelect.control.append(document.createElement(\"sl-spinner\"))\n // Open the dropdown on click, mirroring the native <alchemy-select> (Tom\n // Select does not open on click on its own once openOnFocus is off).\n this.#tomSelect.control.addEventListener(\n \"click\",\n this.#tomSelect.open.bind(this.#tomSelect)\n )\n }\n\n disconnectedCallback() {\n teardownTomSelect(this.#tomSelect)\n this.#tomSelect = null\n }\n\n focus() {\n focusTomSelect(this.#tomSelect, () => super.focus())\n }\n\n /**\n * Optional on change handler.\n * @param {{added: ?object, removed: ?object}} event\n */\n onChange(event) {\n // Update selection attribute so re-attaching the component to the same\n // input (e.g. after dragndrop) does not reset the selection. In multiple\n // mode it holds the array of the currently selected records.\n if (this.multiple) {\n if (event.added) {\n this.#selectedItems.set(String(event.added.id), event.added)\n }\n if (event.removed) {\n this.#selectedItems.delete(String(event.removed.id))\n }\n this.setAttribute(\n \"selection\",\n JSON.stringify([...this.#selectedItems.values()])\n )\n } else if (event.added) {\n this.setAttribute(\"selection\", JSON.stringify(event.added))\n }\n this.dispatchCustomEvent(\"RemoteSelect.Change\", {\n removed: event.removed,\n added: event.added\n })\n }\n\n /**\n * Optional on open handler, called when the dropdown opens.\n * @param {Event} [event]\n */\n onOpen() {}\n\n /**\n * Dispatches a custom event with given name, namespaced under `Alchemy.`.\n * Subclasses may call this to emit their own events.\n * @param {string} name The name of the custom event\n * @param {object} detail Optional event details\n */\n dispatchCustomEvent(name, detail = {}) {\n this.dispatchEvent(\n new CustomEvent(`Alchemy.${name}`, { bubbles: true, detail })\n )\n }\n\n get allowClear() {\n return this.hasAttribute(\"allow-clear\")\n }\n\n get multiple() {\n return this.hasAttribute(\"multiple\")\n }\n\n get selection() {\n return this.getAttribute(\"selection\")\n }\n\n get placeholder() {\n return this.getAttribute(\"placeholder\") ?? \"\"\n }\n\n get queryParams() {\n return this.getAttribute(\"query-params\") ?? \"{}\"\n }\n\n get url() {\n return this.getAttribute(\"url\") ?? \"\"\n }\n\n get input() {\n // The wrapped control is an <input> in single mode and a <select> in\n // multiple mode. Tom Select keeps the original element in place and inserts\n // its own control after it, so the original always comes first.\n return this.querySelector(\"input, select\")\n }\n\n /**\n * Tom Select configuration.\n * @returns {object}\n */\n get tomSelectConfig() {\n const self = this\n const ajax = this.ajaxConfig\n const plugins = { virtual_scroll: {} }\n\n if (this.multiple) {\n plugins.remove_button = {}\n }\n\n if (this.allowClear) {\n plugins.clear_button = {\n html() {\n return `<button type=\"button\" class=\"clear-button\" aria-label=\"${translate(\n \"Clear selection\"\n )}\">\n <alchemy-icon name=\"close\" size=\"1x\"></alchemy-icon>\n </button>`\n }\n }\n }\n\n const { onDropdownOpen, onDropdownClose } = createDropdownPositioning()\n\n return {\n plugins,\n // The server returns each item with an `id` used as the option value.\n valueField: \"id\",\n // A single item unless the multiple attribute lifts the limit.\n maxItems: this.multiple ? null : 1,\n // Close the dropdown after selecting a single item; in multiple mode keep\n // it open so several items can be picked in a row.\n closeAfterSelect: !this.multiple,\n // Show every item returned by the server, not just the first 50.\n maxOptions: null,\n // Searching is done on the server, so keep every returned option.\n searchField: [],\n // Debounce the server requests while typing.\n loadThrottle: ajax.quietMillis,\n // Refresh the input state (hide the selection, show the loading dropdown)\n // on every keystroke without delay. The server request stays debounced by\n // loadThrottle, so this only affects the immediate UI feedback.\n refreshThrottle: 0,\n placeholder: this.placeholder,\n // Behave like the local <alchemy-select>: focusing neither opens the\n // dropdown nor fires a search. The first page of results is loaded when\n // the dropdown opens (see onDropdownOpen), i.e. on click or typing.\n openOnFocus: false,\n // Load even for an empty search term to show the initial result list.\n shouldLoad: () => true,\n firstUrl: (query) => this.#requestUrl(query, 1),\n load: (query, callback) => this.#load(query, callback),\n onInitialize() {\n if (!self.selection) return\n // The preselection only carries enough data to label an item. Mark each\n // one, so the option template can tell it apart from the complete records\n // the server returns. Loading the record drops the mark again.\n if (self.multiple) {\n const items = JSON.parse(self.selection)\n // Track the preselected items first, so the change handler recognizes\n // them as unchanged. Adding them silently avoids a spurious change\n // event while still rendering the items and marking the options.\n self.#selectedItems = new Map(\n items.map((item) => [String(item.id), item])\n )\n items.forEach((item) => {\n this.addOption({ ...item, $preselection: true })\n this.addItem(item.id, true)\n })\n } else {\n const item = JSON.parse(self.selection)\n // Track the preselected item first, so the change handler recognizes\n // it as unchanged. Adding it silently avoids a spurious change event\n // while still rendering the item and marking the option.\n self.#selectedItem = item\n this.addOption({ ...item, $preselection: true })\n this.addItem(item.id, true)\n }\n },\n onChange(value) {\n self.#onChange(value, this)\n },\n onType(term) {\n // While typing, give the search input an opaque background so it covers\n // the selected item behind it. Without this the previous selection shows\n // through the search box (see the `input-active` styles).\n this.control_input.classList.toggle(\"has-value\", term.length > 0)\n },\n onDropdownOpen() {\n onDropdownOpen.call(this)\n if (!this.loading) {\n this.load(this.lastValue ?? \"\")\n }\n self.onOpen()\n },\n onDropdownClose,\n render: {\n option(item, _escape) {\n // A preselection that the server has not returned yet only carries\n // enough data to label the item, so render it like the control does.\n // Everything else is a complete record and gets the list entry.\n if (item.$preselection) {\n return `<div>${self._renderResult(item)}</div>`\n }\n return self._renderListEntry(item, this.lastValue)\n },\n item(item, _escape) {\n return `<div>${self._renderResult(item)}</div>`\n },\n ...dropdownMessages\n }\n }\n }\n\n /**\n * Ajax configuration. Kept for API compatibility with subclasses that spread\n * `super.ajaxConfig` and augment it (e.g. adding request `params`).\n * @returns {object}\n */\n get ajaxConfig() {\n return {\n url: this.url,\n datatype: \"json\",\n quietMillis: 300,\n data: (term, page) => this._searchQuery(term, page),\n results: (response) => this._parseResponse(response)\n }\n }\n\n /**\n * Builds the request url for a given search term and page.\n * @param {string} term\n * @param {number} page\n * @returns {string}\n * @private\n */\n #requestUrl(term, page) {\n const query = serializeParams(this.ajaxConfig.data(term, page))\n const separator = this.url.includes(\"?\") ? \"&\" : \"?\"\n return query ? `${this.url}${separator}${query}` : this.url\n }\n\n /**\n * Loads results from the server and registers the next page url for the\n * virtual scroll plugin.\n * @param {string} query\n * @param {function} callback\n * @private\n */\n async #load(query, callback) {\n const ajax = this.ajaxConfig\n const url = this.#tomSelect.getUrl(query)\n const page = Number(\n new URL(url, window.location.origin).searchParams.get(\"page\")\n )\n try {\n const response = await fetch(url, {\n headers: { Accept: \"application/json\", ...(ajax.params?.headers ?? {}) }\n })\n // fetch only rejects on a network error, so an error response has to be\n // turned into one. Its body often parses as JSON just fine and would\n // otherwise reach the callback as an empty result set.\n if (!response.ok) {\n throw new Error(`${response.status} ${response.statusText}`)\n }\n const { results, more } = ajax.results(await response.json())\n if (more) {\n this.#tomSelect.setNextUrl(query, this.#requestUrl(query, page + 1))\n }\n callback(results)\n } catch (error) {\n growl(error.message || error, \"error\")\n console.error(`[alchemy] Loading ${url} failed`, error)\n // Tom Select only clears its loading state once the callback ran.\n callback()\n }\n }\n\n /**\n * Computes the added/removed delta from a Tom Select change and forwards it to\n * the `onChange` handler.\n * @param {string} value\n * @param {TomSelect} tomSelect\n * @private\n */\n #onChange(value, tomSelect) {\n if (this.multiple) {\n this.#onChangeMultiple(value, tomSelect)\n return\n }\n const previous = this.#selectedItem\n if (value === \"\" || value == null) {\n if (!previous) return\n this.#selectedItem = null\n this.onChange({ added: null, removed: previous })\n } else if (!previous || String(previous.id) !== String(value)) {\n const added = this.#itemData(tomSelect.options[value])\n this.#selectedItem = added\n this.onChange({ added, removed: previous })\n }\n }\n\n /**\n * Computes the per item added/removed deltas from a multiple Tom Select change\n * (whose value is the array of selected ids) by diffing it against the tracked\n * selection, and forwards each delta to the `onChange` handler.\n * @param {string[]} values\n * @param {TomSelect} tomSelect\n * @private\n */\n #onChangeMultiple(values, tomSelect) {\n const ids = (values ?? []).map(String)\n const previous = new Set(this.#selectedItems.keys())\n const current = new Set(ids)\n previous.forEach((id) => {\n if (!current.has(id)) {\n this.onChange({ added: null, removed: this.#selectedItems.get(id) })\n }\n })\n ids.forEach((id) => {\n if (!previous.has(id)) {\n this.onChange({\n added: this.#itemData(tomSelect.options[id]),\n removed: null\n })\n }\n })\n }\n\n /**\n * An option also carries the bookkeeping Tom Select keeps on it, among it the\n * node it was rendered into. Serializing that into the selection attribute\n * breaks the render cache once a component is attached to it again, so keep\n * the record itself only.\n * @param {?object} option\n * @returns {?object}\n * @private\n */\n #itemData(option) {\n if (!option) return null\n return Object.fromEntries(\n Object.entries(option).filter(([key]) => !key.startsWith(\"$\"))\n )\n }\n\n /**\n * Search query send to server.\n * @param {string} term\n * @param {number} page\n * @returns {object}\n * @private\n */\n _searchQuery(term, page) {\n return {\n q: {\n name_i_cont: term,\n ...JSON.parse(this.queryParams)\n },\n page: page\n }\n }\n\n /**\n * Parses server response into a results object.\n * @param {object} response\n * @returns {{results: Array, more: boolean}}\n * @private\n */\n _parseResponse(response) {\n const meta = response.meta\n return {\n results: response.data,\n more: meta.page * meta.per_page < meta.total_count\n }\n }\n\n /**\n * Renders a dropdown option from the slots a subclass describes in `_entry`.\n * External subclasses that still override `_renderListEntry` directly keep\n * their own markup and never reach this.\n * @param {object} item\n * @param {string} term\n * @returns {string}\n * @private\n */\n _renderListEntry(item, term) {\n return this.#renderEntry(this._entry(item, term))\n }\n\n /**\n * Renders the selected item (shown in the control) from the slots a subclass\n * describes in `_selectedEntry`. External subclasses that still override\n * `_renderResult` directly keep their own markup and never reach this.\n * @param {object} item\n * @returns {string}\n * @private\n */\n _renderResult(item) {\n return this.#renderSelection(this._selectedEntry(item))\n }\n\n /**\n * Describes the slots of a dropdown option. Subclasses override this and\n * return an object with any of `icon`/`media`, `primary`, `aside`,\n * `secondary`, and `secondaryAside`. See `#renderEntry` for the accepted\n * value shapes.\n * @param {object} item\n * @param {string} term\n * @returns {object}\n * @protected\n */\n _entry() {\n throw new Error(\"You need to define an _entry function on your sub class!\")\n }\n\n /**\n * Describes the slots of the selected item shown in the control. Subclasses\n * override this and return an object with `icon`/`media`, `primary`, and\n * `secondary`.\n * @param {object} item\n * @returns {object}\n * @protected\n */\n _selectedEntry() {\n throw new Error(\n \"You need to define a _selectedEntry function on your sub class!\"\n )\n }\n\n /**\n * Builds the two row grid markup of a dropdown option from its slots.\n * @param {object} slots\n * @returns {string}\n * @private\n */\n #renderEntry(slots) {\n return `\n <div class=\"remote-select--entry\">\n ${this.#leadColumn(slots)}\n ${this.#cell(\"remote-select--primary\", slots.primary, { raw: true })}\n ${this.#cell(\"remote-select--aside\", slots.aside)}\n ${this.#cell(\"remote-select--secondary\", slots.secondary)}\n ${this.#cell(\"remote-select--secondary-aside\", slots.secondaryAside)}\n </div>\n `\n }\n\n /**\n * Builds the single row markup of the selected item from its slots. Only the\n * leading column, the primary text, and the (fast shrinking) secondary text\n * are shown; the compact control has no room for more.\n * @param {object} slots\n * @returns {string}\n * @private\n */\n #renderSelection(slots) {\n return `\n <div class=\"remote-select--selection\">\n ${this.#leadColumn(slots)}\n ${this.#cell(\"remote-select--selection-name\", slots.primary, { raw: true })}\n ${this.#cell(\"remote-select--selection-aside\", slots.secondary)}\n </div>\n `\n }\n\n /**\n * Builds the leading column, either an icon or a media thumbnail. The two are\n * mutually exclusive; the icon wins if a subclass passes both.\n * @param {object} slots\n * @returns {string}\n * @private\n */\n #leadColumn(slots) {\n if (slots.icon) {\n return `<alchemy-icon class=\"remote-select--icon\" name=\"${escape_html(slots.icon)}\"></alchemy-icon>`\n }\n if (slots.media) {\n return `<img class=\"remote-select--media\" src=\"${escape_html(slots.media)}\" alt=\"\">`\n }\n return \"\"\n }\n\n /**\n * Renders a single cell from a slot value. Omitted or empty values render\n * nothing. A string is escaped, unless `raw` is set (the primary text is\n * pre-highlighted HTML). `{ text, truncate: \"head\" }` wraps the text in\n * `<bdi>` and truncates it from the head. `{ badge }` renders the pill style.\n * @param {string} className\n * @param {(string|object)} value\n * @param {{raw?: boolean}} [options]\n * @returns {string}\n * @private\n */\n #cell(className, value, { raw = false } = {}) {\n if (value == null || value === \"\") return \"\"\n\n if (typeof value === \"object\") {\n if (value.badge != null && value.badge !== \"\") {\n return `<span class=\"${className} remote-select--badge\">${escape_html(value.badge)}</span>`\n }\n if (value.text == null || value.text === \"\") return \"\"\n const text = raw ? value.text : escape_html(value.text)\n if (value.truncate === \"head\") {\n return `<span class=\"${className} remote-select--truncate-head\"><bdi>${text}</bdi></span>`\n }\n return `<span class=\"${className}\">${text}</span>`\n }\n\n return `<span class=\"${className}\">${raw ? value : escape_html(value)}</span>`\n }\n\n /**\n * hightlighted search term\n * @param {string} name\n * @param {string} term\n * @returns {string}\n * @private\n */\n _hightlightTerm(name, term) {\n return hightlightTerm(name, term)\n }\n}\n","import { RemoteSelect } from \"alchemy_admin/components/remote_select\"\n\nexport class AttachmentSelect extends RemoteSelect {\n _entry(attachment, term) {\n return {\n icon: attachment.icon_css_class,\n primary: this._hightlightTerm(attachment.name, term)\n }\n }\n\n _selectedEntry(attachment) {\n return {\n icon: attachment.icon_css_class,\n primary: attachment.name\n }\n }\n}\n\ncustomElements.define(\"alchemy-attachment-select\", AttachmentSelect)\n","// Dispatch a submit event on change of input or select elements\n// contained in a form, so that Turbo can submit the form.\nclass AutoSubmit extends HTMLElement {\n connectedCallback() {\n this.addEventListener(\"change\", this.#onChange)\n }\n\n disconnectedCallback() {\n this.removeEventListener(\"change\", this.#onChange)\n }\n\n #onChange = (event) => {\n // We need to dispatch a submit event, so that Turbo that listens\n // to it submits the search form us.\n const submitEvent = new Event(\"submit\", {\n bubbles: true,\n cancelable: true\n })\n event.target.form.dispatchEvent(submitEvent)\n return false\n }\n}\n\ncustomElements.define(\"alchemy-auto-submit\", AutoSubmit)\n","import Spinner from \"alchemy_admin/spinner\"\n\nclass Button extends HTMLButtonElement {\n connectedCallback() {\n if (this.form) {\n this.form.addEventListener(\"submit\", this)\n this.form.addEventListener(\"turbo:submit-end\", this)\n } else {\n console.warn(\"No form for button found!\", this)\n }\n }\n\n handleEvent(event) {\n switch (event.type) {\n case \"submit\":\n const isDisabled = this.getAttribute(\"disabled\") === \"disabled\"\n\n if (isDisabled) {\n event.preventDefault()\n event.stopPropagation()\n } else {\n this.disable()\n }\n break\n case \"turbo:submit-end\":\n this.enable()\n break\n }\n }\n\n disable() {\n const spinner = new Spinner(\"small\")\n const rect = this.getBoundingClientRect()\n\n this.dataset.initialButtonText = this.innerHTML\n this.setAttribute(\"disabled\", \"disabled\")\n this.setAttribute(\"tabindex\", \"-1\")\n this.classList.add(\"disabled\")\n this.style.width = `${rect.width}px`\n this.style.height = `${rect.height}px`\n this.innerHTML = \" \"\n\n spinner.spin(this)\n }\n\n enable() {\n this.classList.remove(\"disabled\")\n this.removeAttribute(\"disabled\")\n this.removeAttribute(\"tabindex\")\n this.style.width = null\n this.style.height = null\n this.innerHTML = this.dataset.initialButtonText\n }\n}\n\ncustomElements.define(\"alchemy-button\", Button, { extends: \"button\" })\n","/**\n * Show the character counter below input fields and textareas\n */\nimport { translate } from \"alchemy_admin/i18n\"\n\nclass CharCounter extends HTMLElement {\n connectedCallback() {\n this.translation = translate(\"allowed_chars\", this.maxChars)\n this.formField = this.getFormField()\n\n if (this.formField) {\n this.createDisplayElement()\n this.countCharacters()\n this.formField.addEventListener(\"keyup\", this)\n }\n }\n\n disconnectedCallback() {\n this.formField?.removeEventListener(\"keyup\", this)\n }\n\n handleEvent(event) {\n if (event.type === \"keyup\") this.countCharacters()\n }\n\n getFormField() {\n const formFields = this.querySelectorAll(\"input, textarea\")\n return formFields.length > 0 ? formFields[0] : undefined\n }\n\n createDisplayElement() {\n this.display = this.querySelector(\":scope > .alchemy-char-counter\")\n if (this.display) return\n this.display = document.createElement(\"small\")\n this.display.className = \"alchemy-char-counter\"\n this.formField.after(this.display)\n }\n\n countCharacters() {\n const charLength = this.formField.value.length\n this.display.textContent = `${charLength} ${this.translation}`\n this.display.classList.toggle(\"too-long\", charLength > this.maxChars)\n }\n\n get maxChars() {\n return this.getAttribute(\"max-chars\") ?? 60\n }\n}\n\ncustomElements.define(\"alchemy-char-counter\", CharCounter)\n","class ClearScheduleButton extends HTMLButtonElement {\n connectedCallback() {\n this.addEventListener(\"click\", this)\n }\n\n disconnectedCallback() {\n this.removeEventListener(\"click\", this)\n }\n\n handleEvent() {\n this.form\n ?.querySelectorAll('input[type=\"datetime-local\"]')\n .forEach((input) => {\n input.value = \"\"\n })\n }\n}\n\ncustomElements.define(\"alchemy-clear-schedule-button\", ClearScheduleButton, {\n extends: \"button\"\n})\n","import \"clipboard\"\nimport { growl } from \"alchemy_admin/growler\"\n\nclass ClipboardButton extends HTMLElement {\n connectedCallback() {\n this.innerHTML = '<alchemy-icon name=\"clipboard\"></alchemy-icon>'\n\n this.clipboard = new ClipboardJS(this, {\n text: () => {\n return this.getAttribute(\"content\")\n }\n })\n\n this.clipboard.on(\"success\", () => {\n growl(this.getAttribute(\"success-text\"))\n })\n }\n\n disconnectedCallback() {\n this.clipboard.destroy()\n }\n}\n\ncustomElements.define(\"alchemy-clipboard-button\", ClipboardButton)\n","import TomSelect from \"tom-select\"\nimport \"alchemy_admin/components/icon\"\nimport { translate } from \"alchemy_admin/i18n\"\nimport {\n createDropdownPositioning,\n dropdownMessages,\n focusTomSelect,\n removeButton,\n teardownTomSelect\n} from \"alchemy_admin/utils/tom_select\"\n\nexport class Select extends HTMLSelectElement {\n #tomSelect = null\n\n connectedCallback() {\n this.classList.add(\"alchemy_selectbox\")\n this.#initTomSelect()\n }\n\n disconnectedCallback() {\n this.#destroyTomSelect()\n }\n\n enable() {\n this.removeAttribute(\"disabled\")\n this.#tomSelect?.enable()\n }\n\n disable() {\n this.setAttribute(\"disabled\", \"disabled\")\n this.#tomSelect?.disable()\n }\n\n focus() {\n focusTomSelect(this.#tomSelect, () => super.focus())\n }\n\n setOptions(data, prompt = undefined) {\n const selectedValue = this.value\n\n // Tom Select needs to be rebuilt from the new native options, so tear it\n // down, replace the options and initialize it again.\n this.#destroyTomSelect()\n\n // reset the old options and insert the placeholder(s) first\n this.innerHTML = \"\"\n if (prompt) {\n this.add(new Option(prompt, \"\"))\n }\n\n // add the new options to the select\n data.forEach((item) => {\n this.add(new Option(item.text, item.id, false, item.id === selectedValue))\n })\n\n this.#initTomSelect()\n }\n\n get allowClear() {\n return this.dataset.hasOwnProperty(\"allowClear\") || this.multiple\n }\n\n get placeholder() {\n return this.getAttribute(\"placeholder\")\n }\n\n // Subclasses may return extra Tom Select render functions (e.g. custom\n // `option`/`item` templates) to merge on top of the defaults below.\n get renderers() {\n return {}\n }\n\n #initTomSelect() {\n const plugins = {}\n const hasPlaceholder = !!this.placeholder\n // Capture this before Tom Select initializes, since it rewrites the\n // select's selected option during setup.\n const hasSelectedOption = !!this.querySelector(\"option[selected]\")\n const { onDropdownOpen, onDropdownClose } = createDropdownPositioning()\n\n if (this.multiple) {\n plugins.remove_button = removeButton()\n }\n\n if (this.allowClear) {\n plugins.clear_button = {\n html() {\n return `<button type=\"button\" class=\"clear-button\" aria-label=\"${translate(\n \"Clear selection\"\n )}\">\n <alchemy-icon name=\"close\" size=\"1x\"></alchemy-icon>\n </button>`\n }\n }\n }\n\n const settings = {\n plugins,\n closeAfterSelect: !this.multiple,\n onInitialize: function () {\n if (this.input.autofocus) {\n this.focus()\n }\n // Tom Select auto-selects the first option when none is selected. With\n // a placeholder we want it to start empty instead, but only clear when\n // no option was explicitly marked selected, so a preselected value is\n // preserved.\n if (hasPlaceholder && !hasSelectedOption) {\n this.clear()\n }\n },\n onType(term) {\n this.control_input.classList.toggle(\"has-value\", term.length > 0)\n },\n // remove the transition after selection of option.\n refreshThrottle: 0,\n onDropdownOpen,\n onDropdownClose,\n allowEmptyOption: true,\n openOnFocus: false,\n // Keep options in their original order instead of sorting by value.\n sortField: \"$order\",\n // Show every option, not just the first 50 (e.g. the timezone select).\n maxOptions: null,\n // Customize the \"create\" and \"no results\" dropdown messages with i18n.\n render: {\n ...dropdownMessages,\n ...this.renderers\n }\n }\n\n this.#tomSelect = new TomSelect(this, settings)\n\n // Mimick the native select's click-to-open behavior.\n this.#tomSelect.control.addEventListener(\n \"click\",\n this.#tomSelect.open.bind(this.#tomSelect)\n )\n }\n\n #destroyTomSelect() {\n teardownTomSelect(this.#tomSelect)\n this.#tomSelect = null\n }\n}\n\ncustomElements.define(\"alchemy-select\", Select, { extends: \"select\" })\n","import { Select } from \"alchemy_admin/components/select\"\n\nconst renderColorOption = (data, escape) => {\n const swatch = data.swatch || data.value\n const colorIndicator =\n data.value === \"custom_color\"\n ? `<alchemy-icon name=\"palette\"></alchemy-icon>`\n : `<span class=\"color-indicator\" style=\"--color: ${escape(swatch)}\"></span>`\n\n return `\n <div class=\"select-color-option\">\n ${colorIndicator}\n <span>${escape(data.text)}</span>\n </div>`\n}\n\n// A color options select enhanced by Tom Select. It inherits from the shared\n// alchemy-select component to reuse the Tom Select setup, renders each option\n// and the selected item with a color swatch (or the custom-color icon), and\n// enables the adjacent color picker while a custom color is selected.\nexport class ColorSelect extends Select {\n connectedCallback() {\n super.connectedCallback()\n this.addEventListener(\"change\", this.#onChange)\n }\n\n disconnectedCallback() {\n super.disconnectedCallback()\n this.removeEventListener(\"change\", this.#onChange)\n }\n\n get renderers() {\n return {\n item: renderColorOption,\n option: renderColorOption\n }\n }\n\n #onChange = () => {\n const colorPicker = this.parentElement?.querySelector(\"input[type='color']\")\n if (colorPicker) {\n colorPicker.disabled = this.value !== \"custom_color\"\n }\n }\n}\n\ncustomElements.define(\"alchemy-color-select\", ColorSelect, {\n extends: \"select\"\n})\n\n// Free-form color input used when no color options are configured. Keeps the\n// text field and the native color picker in sync and enables the picker.\nexport class ColorInput extends HTMLElement {\n connectedCallback() {\n this.colorInput?.addEventListener(\"input\", this)\n this.textInput?.addEventListener(\"input\", this)\n if (this.colorInput) {\n this.colorInput.disabled = false\n }\n }\n\n handleEvent(event) {\n switch (event.target) {\n case this.colorInput:\n this.textInput.value = this.colorInput.value\n break\n case this.textInput:\n this.colorInput.value = this.textInput.value\n break\n }\n }\n\n disconnectedCallback() {\n this.colorInput?.removeEventListener(\"input\", this)\n this.textInput?.removeEventListener(\"input\", this)\n }\n\n get colorInput() {\n return this.querySelector(\"input[type='color']\")\n }\n\n get textInput() {\n return this.querySelector(\"input[type='text']\")\n }\n}\n\ncustomElements.define(\"alchemy-color-input\", ColorInput)\n","/**\n * Shows its fields only while the controlling checkbox is checked.\n *\n * Hidden fields are disabled as well, so they are not submitted and the stored\n * value is left untouched while the condition does not apply. A disabled\n * control (a fixed page attribute for example) keeps the fields disabled.\n *\n * <alchemy-conditional-field control=\"page_restricted\">\n * <input type=\"text\" name=\"page[foo]\">\n * </alchemy-conditional-field>\n */\nclass ConditionalField extends HTMLElement {\n connectedCallback() {\n this.control?.addEventListener(\"change\", this)\n this.toggle()\n }\n\n disconnectedCallback() {\n this.control?.removeEventListener(\"change\", this)\n }\n\n handleEvent(event) {\n if (event.type === \"change\") this.toggle()\n }\n\n toggle() {\n const active = !!this.control?.checked && !this.control.disabled\n\n this.hidden = !active\n this.fields.forEach((field) => {\n // Custom elements like alchemy-select need their own API to keep their\n // rendered control in sync, but might not be upgraded yet.\n if (typeof field.disable === \"function\") {\n active ? field.enable() : field.disable()\n } else {\n field.disabled = !active\n }\n })\n }\n\n get control() {\n return document.getElementById(this.getAttribute(\"control\"))\n }\n\n get fields() {\n return this.querySelectorAll(\"input, select, textarea\")\n }\n}\n\ncustomElements.define(\"alchemy-conditional-field\", ConditionalField)\n","import { translate, currentLocale } from \"alchemy_admin/i18n\"\nimport flatpickr from \"flatpickr\"\n\nconst locale = currentLocale()\n\nclass Datepicker extends HTMLElement {\n // Load the locales for flatpickr before setting it up.\n async connectedCallback() {\n // English is the default locale for flatpickr, so we don't need to load it\n if (locale !== \"en\") {\n await import(`flatpickr/${locale}.js`)\n }\n // Bail out if the element was disconnected while the locale was loading.\n // Otherwise flatpickr would leak a calendar onto a detached input.\n if (!this.isConnected) return\n\n this.flatpickr = flatpickr(this.inputField, this.flatpickrOptions)\n }\n\n disconnectedCallback() {\n this.flatpickr?.destroy()\n }\n\n get flatpickrOptions() {\n const enableTime = /time/.test(this.inputType)\n const options = {\n // alchemy_i18n supports `zh_CN` etc., but flatpickr only has two-letter codes (`zh`)\n locale: locale.slice(0, 2),\n altInput: true,\n altFormat: translate(`formats.${this.inputType}`),\n altInputClass: \"flatpickr-input\",\n enableTime,\n noCalendar: this.inputType === \"time\",\n time_24hr: translate(\"formats.time_24hr\"),\n onValueUpdate(_selectedDates, _dateStr, instance) {\n instance.element\n .closest(\"alchemy-element-editor\")\n ?.setDirty(this.inputField)\n }\n }\n\n if (enableTime) {\n options.dateFormat = \"Z\"\n }\n\n return options\n }\n\n get inputField() {\n return this.querySelector(\"input\")\n }\n\n get inputType() {\n return this.getAttribute(\"input-type\") || \"date\"\n }\n}\n\ncustomElements.define(\"alchemy-datepicker\", Datepicker)\n","import { Dialog } from \"alchemy_admin/dialog\"\n\nexport class DialogLink extends HTMLAnchorElement {\n connectedCallback() {\n this.addEventListener(\"click\", this)\n }\n\n disconnectedCallback() {\n this.removeEventListener(\"click\", this)\n }\n\n handleEvent(evt) {\n if (!this.disabled) {\n this.openDialog()\n }\n evt.preventDefault()\n }\n\n openDialog() {\n this.dialog = new Dialog(this.getAttribute(\"href\"), this.dialogOptions)\n this.dialog.open()\n }\n\n get dialogOptions() {\n const options = this.dataset.dialogOptions\n ? JSON.parse(this.dataset.dialogOptions)\n : {}\n return options\n }\n\n get disabled() {\n return this.classList.contains(\"disabled\")\n }\n}\n\ncustomElements.define(\"alchemy-dialog-link\", DialogLink, { extends: \"a\" })\n","import { Turbo } from \"@hotwired/turbo-rails\"\nimport { openConfirmDialog } from \"alchemy_admin/confirm_dialog\"\nimport { translate } from \"alchemy_admin/i18n\"\nimport pleaseWaitOverlay from \"alchemy_admin/please_wait_overlay\"\n\n// Set once the user agreed to leave, so that the navigation they agreed to is\n// not questioned again: a guarded form's redirect comes back through\n// turbo:before-visit.\nlet leaveConfirmed = false\n\n// Warns before a wrapped form takes the user away from a page that has unsaved\n// element changes. Links need no wrapping, turbo:before-visit covers them, but\n// Turbo dispatches that for a form only once its request has returned, which is\n// too late for a form that changes something on the server.\n//\n// Guards in the capture phase, so that a cancelled submit never reaches\n// Turbo's document level handlers, nor any component the guard sits in.\n//\n// Wrap an sl-tooltip, never the element inside it: Shoelace anchors to its\n// first slotted child, and this element has no box of its own to anchor to.\nclass DirtyGuard extends HTMLElement {\n connectedCallback() {\n this.addEventListener(\"submit\", this.#guard, true)\n }\n\n disconnectedCallback() {\n this.removeEventListener(\"submit\", this.#guard, true)\n }\n\n #guard = (event) => {\n if (leaveConfirmed || !isPageDirty()) return\n\n event.preventDefault()\n event.stopPropagation()\n\n const form = event.target\n confirmLeave().then((confirmed) => {\n if (confirmed) {\n pleaseWaitOverlay()\n form.requestSubmit()\n }\n })\n }\n}\n\nfunction isPageDirty() {\n return document.querySelectorAll(\"alchemy-element-editor.dirty\").length > 0\n}\n\nasync function confirmLeave() {\n const confirmed = await openConfirmDialog(translate(\"page_dirty_notice\"), {\n title: translate(\"warning\"),\n ok_label: translate(\"ok\"),\n cancel_label: translate(\"cancel\")\n })\n if (confirmed) {\n leaveConfirmed = true\n window.onbeforeunload = void 0\n }\n return confirmed\n}\n\ndocument.addEventListener(\"turbo:before-visit\", (event) => {\n if (leaveConfirmed || !isPageDirty()) return\n\n event.preventDefault()\n\n confirmLeave().then((confirmed) => {\n if (confirmed) Turbo.visit(event.detail.url)\n })\n})\n\ndocument.addEventListener(\"turbo:load\", () => {\n leaveConfirmed = false\n})\n\ncustomElements.define(\"alchemy-dirty-guard\", DirtyGuard)\n","const JSON_CONTENT_TYPE = \"application/json\"\nconst TURBO_STREAM_CONTENT_TYPE = \"text/vnd.turbo-stream.html\"\n\nfunction isGetRequest(method) {\n return method.toLowerCase() === \"get\"\n}\n\nfunction prepareURL(path, data, method) {\n const url = new URL(window.location.origin + path)\n\n if (data && isGetRequest(method)) {\n new URLSearchParams(data).forEach((value, key) => {\n url.searchParams.set(key, value)\n })\n }\n\n return url.toString()\n}\n\nfunction prepareHeaders(accept) {\n return {\n \"Content-Type\": \"application/json; charset=utf-8\",\n Accept: accept,\n \"X-Requested-With\": \"XMLHttpRequest\",\n \"X-CSRF-Token\": getToken()\n }\n}\n\nfunction prepareOptions(method, data, accept) {\n const headers = prepareHeaders(accept)\n const options = { method, headers }\n\n if (data && !isGetRequest(method)) {\n options.body = JSON.stringify(data)\n }\n\n return options\n}\n\nexport function getToken() {\n const metaTag = document.querySelector('meta[name=\"csrf-token\"]')\n return metaTag.attributes.content.textContent\n}\n\nexport function get(url, params) {\n return ajax(\"GET\", url, params)\n}\n\nexport function patch(url, data, accept) {\n return ajax(\"PATCH\", url, data, accept)\n}\n\nexport function post(url, data, accept = JSON_CONTENT_TYPE) {\n return ajax(\"POST\", url, data, accept)\n}\n\nexport default async function ajax(\n method,\n path,\n data,\n accept = JSON_CONTENT_TYPE\n) {\n const response = await fetch(\n prepareURL(path, data, method),\n prepareOptions(method, data, accept)\n )\n const contentType = response.headers.get(\"content-type\")\n const isJson = contentType?.includes(JSON_CONTENT_TYPE)\n const isTurboStream = contentType?.includes(TURBO_STREAM_CONTENT_TYPE)\n\n let responseData = null\n if (isJson) {\n responseData = await response.json()\n } else if (isTurboStream) {\n responseData = await response.text()\n // Automatically render Turbo Stream if Turbo is available\n if (typeof Turbo !== \"undefined\") {\n Turbo.renderStreamMessage(responseData)\n }\n }\n\n if (response.ok) {\n return { data: responseData, status: response.status }\n }\n\n // The session expired while the tab was open. Send the user to the login\n // page instead of leaving them with an error they cannot act on. Never\n // settle, so callers skip their error handling: the login page is what the\n // user needs to see, not a growl about a request they cannot retry.\n if (response.status === 401 && responseData?.redirect_url) {\n Turbo.visit(responseData.redirect_url)\n return new Promise(() => {})\n }\n\n throw responseData || new Error(\"An error occurred during the transaction\")\n}\n","import { get } from \"alchemy_admin/utils/ajax\"\nimport { translate } from \"alchemy_admin/i18n\"\nimport { growl } from \"alchemy_admin/growler\"\nimport { routes } from \"alchemy_admin/routes\"\n\nclass DomIdSelect extends HTMLElement {\n dataItem(hash) {\n return {\n id: `#${hash}`,\n text: `#${hash}`\n }\n }\n\n get selectElement() {\n return this.querySelector('select[is=\"alchemy-select\"]')\n }\n}\n\nclass DomIdApiSelect extends DomIdSelect {\n #pageId = undefined\n\n connectedCallback() {\n this.page = this.getAttribute(\"page\")\n }\n\n async #fetchDomIds() {\n try {\n const result = await get(routes.api_ingredients_path, {\n page_id: this.#pageId\n })\n const options = result.data.ingredients\n .filter((ingredient) => ingredient.data?.dom_id)\n .map((ingredient) => this.dataItem(ingredient.data.dom_id))\n const prompt =\n options.length > 0 ? translate(\"None\") : translate(\"No anchors found\")\n\n this.selectElement.setOptions(options, prompt)\n this.selectElement.enable()\n } catch (error) {\n growl(error.message || error, \"error\")\n console.error(error)\n }\n }\n\n #reset() {\n // wait a tick to initialize the alchemy-select\n requestAnimationFrame(() => {\n this.selectElement.disable()\n this.selectElement.setOptions([], translate(\"Select a page first\"))\n })\n }\n\n set page(pageId) {\n this.#pageId = pageId\n pageId ? this.#fetchDomIds() : this.#reset()\n }\n}\n\nclass DomIdPreviewSelect extends DomIdSelect {\n connectedCallback() {\n // wait a tick to let the browser initialize the inner select component\n requestAnimationFrame(() => {\n const frame = document.getElementById(\"alchemy_preview_window\")\n const elements = frame.contentDocument?.querySelectorAll(\"[id]\") || []\n if (elements.length > 0) {\n const options = Array.from(elements).map((element) => {\n return this.dataItem(element.id)\n })\n this.selectElement.setOptions(options, translate(\"None\"))\n }\n })\n }\n}\n\ncustomElements.define(\"alchemy-dom-id-api-select\", DomIdApiSelect)\ncustomElements.define(\"alchemy-dom-id-preview-select\", DomIdPreviewSelect)\n","export class PublishElementButton extends HTMLElement {\n #scheduleButtonVariant\n\n connectedCallback() {\n this.#scheduleButtonVariant = this.scheduleButton.getAttribute(\"variant\")\n this.publishButton.addEventListener(\"click\", this)\n this.dropdown.addEventListener(\"sl-show\", this)\n this.dropdown.addEventListener(\"sl-hide\", this)\n }\n\n disconnectedCallback() {\n this.publishButton.removeEventListener(\"click\", this)\n this.dropdown.removeEventListener(\"sl-show\", this)\n this.dropdown.removeEventListener(\"sl-hide\", this)\n }\n\n handleEvent(event) {\n switch (event.type) {\n case \"click\":\n this.publishButton.loading = true\n break\n case \"sl-show\":\n this.scheduleButton.setAttribute(\"variant\", \"primary\")\n break\n case \"sl-hide\":\n this.scheduleButton.setAttribute(\"variant\", this.#scheduleButtonVariant)\n break\n }\n }\n\n get publishButton() {\n return this.querySelector(\"sl-button[type='submit']\")\n }\n\n get dropdown() {\n return this.querySelector(\"sl-dropdown\")\n }\n\n get scheduleButton() {\n return this.querySelector(\"sl-button[slot='trigger']\")\n }\n}\n\ncustomElements.define(\"alchemy-publish-element-button\", PublishElementButton)\n","export function dispatchPageDirtyEvent(data) {\n document.dispatchEvent(\n new CustomEvent(\"alchemy:page-dirty\", {\n detail: { tooltip: data.publishButtonTooltip }\n })\n )\n}\n","import ajax from \"alchemy_admin/utils/ajax\"\nimport { removeTab } from \"alchemy_admin/fixed_elements\"\nimport { growl } from \"alchemy_admin/growler\"\nimport { reloadPreview } from \"alchemy_admin/components/preview_window\"\nimport { openConfirmDialog } from \"alchemy_admin/confirm_dialog\"\nimport { dispatchPageDirtyEvent } from \"alchemy_admin/utils/dispatch_page_dirty_event\"\n\nexport class DeleteElementButton extends HTMLElement {\n connectedCallback() {\n this.button?.addEventListener(\"click\", this)\n }\n\n disconnectedCallback() {\n this.button?.removeEventListener(\"click\", this)\n }\n\n async handleEvent() {\n const confirmed = await openConfirmDialog(this.message)\n if (confirmed) {\n try {\n const response = await ajax(\"DELETE\", this.url)\n this.#removeElement(response.data)\n } catch (error) {\n growl(error.message || error, \"error\")\n console.error(error)\n }\n }\n }\n\n #removeElement(data) {\n const elementEditor = this.closest(\"alchemy-element-editor\")\n elementEditor.addEventListener(\"transitionend\", () => {\n if (elementEditor.fixed) {\n removeTab(elementEditor.elementId)\n }\n elementEditor.remove()\n })\n elementEditor.classList.add(\"dismiss\")\n growl(data.message)\n if (data.pageHasUnpublishedChanges) {\n dispatchPageDirtyEvent(data)\n }\n reloadPreview()\n }\n\n get url() {\n return this.getAttribute(\"href\")\n }\n\n get message() {\n return this.getAttribute(\"message\")\n }\n\n get button() {\n return this.querySelector(\"button\")\n }\n}\n\ncustomElements.define(\"alchemy-delete-element-button\", DeleteElementButton)\n","import { post } from \"alchemy_admin/utils/ajax\"\nimport { growl } from \"alchemy_admin/growler\"\n\nimport \"alchemy_admin/components/element_editor/publish_element_button\"\nimport \"alchemy_admin/components/element_editor/delete_element_button\"\nimport { routes } from \"alchemy_admin/routes\"\n\nexport class ElementEditor extends HTMLElement {\n #form = null\n #header = null\n #toggleButton = null\n\n connectedCallback() {\n // The placeholder while be being dragged is empty.\n if (this.classList.contains(\"ui-sortable-placeholder\")) {\n return\n }\n\n // Add event listeners\n this.addEventListener(\"click\", this)\n // Triggered by child elements\n this.addEventListener(\"alchemy:element-update-title\", this)\n\n this.#form = this.form\n if (this.#form) {\n this.#form.addEventListener(\"change\", this.onChange)\n this.#form.addEventListener(\"turbo:submit-start\", this)\n this.#form.addEventListener(\"turbo:submit-end\", this)\n }\n\n this.#header = this.header\n this.#header?.addEventListener(\"dblclick\", this.#onHeaderDblclick)\n\n this.#toggleButton = this.toggleButton\n this.#toggleButton?.addEventListener(\"click\", this.#onToggleClick)\n\n // When newly created, focus the element and refresh the preview\n if (this.hasAttribute(\"created\")) {\n this.focusElement()\n this.previewWindow?.refresh().then(() => {\n this.focusElementPreview()\n })\n this.removeAttribute(\"created\")\n }\n }\n\n disconnectedCallback() {\n this.removeEventListener(\"click\", this)\n this.removeEventListener(\"alchemy:element-update-title\", this)\n if (this.#form) {\n this.#form.removeEventListener(\"change\", this.onChange)\n this.#form.removeEventListener(\"turbo:submit-start\", this)\n this.#form.removeEventListener(\"turbo:submit-end\", this)\n this.#form = null\n }\n this.#header?.removeEventListener(\"dblclick\", this.#onHeaderDblclick)\n this.#header = null\n this.#toggleButton?.removeEventListener(\"click\", this.#onToggleClick)\n this.#toggleButton = null\n }\n\n handleEvent(event) {\n switch (event.type) {\n case \"click\":\n const elementEditor = event.target.closest(\"alchemy-element-editor\")\n if (elementEditor === this) {\n this.onClickElement()\n }\n break\n case \"turbo:submit-start\":\n this.setClean()\n break\n case \"turbo:submit-end\":\n this.onSaveElement(event.detail.success)\n break\n case \"alchemy:element-update-title\":\n if (!this.hasEditors && event.target == this.firstChild) {\n this.setTitle(event.detail.title)\n }\n break\n }\n }\n\n onChange = (event) => {\n const target = event.target\n // SortableJS fires a native change event :/\n // and we do not want to set the element editor dirty\n // when this happens\n if (target.classList.contains(\"nested-elements\")) {\n return\n }\n this.setDirty(target)\n event.stopPropagation()\n return false\n }\n\n /**\n * Scrolls to and highlights element\n * Expands if collapsed\n * Also chooses the right fixed elements tab, if necessary.\n * Can be triggered through custom event 'FocusElementEditor.Alchemy'\n * Used by the elements on click events in the preview frame.\n */\n async focusElement() {\n // Select tab if necessary\n if (document.querySelector(\"#fixed-elements\")) {\n await this.selectTabForElement()\n }\n // Expand if necessary\n await this.expand()\n this.selectElement(true)\n }\n\n focusElementPreview() {\n this.previewWindow?.postMessage({\n message: \"Alchemy.focusElement\",\n element_id: this.elementId\n })\n }\n\n onClickElement() {\n this.selectElement()\n this.focusElementPreview()\n }\n\n /**\n * Applies the client-side effects of a save once the turbo stream rendered.\n *\n * The notice, error markup, header title, anchor icons and publish button are\n * server-rendered turbo streams. Only the preview refresh (which must focus\n * this element afterwards) and the error box toggle remain client-side.\n * @argument {boolean} success\n */\n onSaveElement(success) {\n if (success) {\n this.previewWindow?.refresh().then(() => {\n this.focusElementPreview()\n })\n } else {\n this.elementErrors.classList.remove(\"hidden\")\n }\n }\n\n /**\n * Smoothly scrolls to element\n */\n scrollToElement() {\n // The timeout gives the browser some time to calculate the position\n // of nested elements correctly\n setTimeout(() => {\n this.scrollIntoView({\n behavior: \"smooth\"\n })\n }, 50)\n }\n\n /**\n * Highlight element and optionally scroll into view\n * @param {boolean} scroll smoothly scroll element into view. Default (false)\n */\n selectElement(scroll = false) {\n document\n .querySelectorAll(\"alchemy-element-editor.selected\")\n .forEach((el) => {\n el.classList.remove(\"selected\")\n })\n window.requestAnimationFrame(() => {\n this.classList.add(\"selected\")\n })\n if (scroll) this.scrollToElement()\n }\n\n /**\n * Selects tab for given element\n * Resolves the promise if this is done.\n * @returns {Promise}\n */\n selectTabForElement() {\n return new Promise((resolve, reject) => {\n const tabs = document.querySelector(\"#fixed-elements\")\n const panel = this.closest(\"sl-tab-panel\")\n if (tabs && panel) {\n tabs.show(panel.getAttribute(\"name\"))\n resolve()\n } else {\n reject(new Error(\"No tabs present\"))\n }\n })\n }\n\n /**\n * Sets the element into clean (safed) state\n */\n setClean() {\n this.dirty = false\n window.onbeforeunload = null\n this.elementErrors.classList.add(\"hidden\")\n\n if (this.hasEditors) {\n this.body.querySelectorAll(\".ingredient-editor\").forEach((el) => {\n el.classList.remove(\"dirty\", \"validation_failed\")\n el.querySelectorAll(\"small.error\").forEach((e) => e.remove())\n })\n }\n }\n\n /**\n * Sets the element into dirty (unsafed) state\n * @param {HTMLElement} editor\n */\n setDirty(editor) {\n if (this.hasEditors) {\n this.dirty = true\n\n if (!window.onbeforeunload) {\n window.onbeforeunload = (event) => event.preventDefault()\n }\n\n editor?.closest(\".ingredient-editor\")?.classList.add(\"dirty\")\n }\n }\n\n /**\n * Sets the title quote\n * @param {string} title\n */\n setTitle(title) {\n const quote = this.querySelector(\".element-header .preview_text_quote\")\n // Fixed elements have no header, so there is no quote to update.\n if (quote) {\n quote.textContent = title\n }\n }\n\n /**\n * Expands or collapses element editor\n * If the element is dirty (has unsaved changes) it displays a confirm first.\n */\n async toggle() {\n if (this.collapsed) {\n await this.expand()\n } else {\n await this.collapse()\n }\n }\n\n /**\n * Collapses the element editor and persists the state on the server\n * @returns {Promise}\n */\n collapse() {\n if (this.collapsed || this.compact || this.fixed) {\n return Promise.resolve(\"Element is already collapsed.\")\n }\n\n const spinner = new Alchemy.Spinner(\"small\")\n spinner.spin(this.toggleButton)\n this.toggleIcon?.classList?.add(\"hidden\")\n return post(routes.collapse_admin_element_path(this.elementId))\n .then((response) => {\n const data = response.data\n\n this.collapsed = true\n this.toggleButton?.setAttribute(\"title\", data.title)\n\n // Collapse all nested elements if necessarry\n if (data.nestedElementIds.length) {\n const selector = data.nestedElementIds\n .map((id) => `#element_${id}`)\n .join(\", \")\n this.querySelectorAll(selector).forEach((nestedElement) => {\n nestedElement.collapsed = true\n nestedElement.toggleButton?.setAttribute(\"title\", data.title)\n })\n }\n })\n .catch((error) => {\n growl(error.message, \"error\")\n console.error(error)\n })\n .finally(() => {\n this.toggleIcon?.classList?.remove(\"hidden\")\n spinner.stop()\n })\n }\n\n /**\n * Collapses the element editor and persists the state on the server\n * @* @returns {Promise}\n */\n expand() {\n if (this.expanded && !this.compact) {\n return Promise.resolve(\"Element is already expanded.\")\n }\n\n if (this.compact && this.parentElementEditor) {\n return this.parentElementEditor.expand()\n } else {\n const spinner = new Alchemy.Spinner(\"small\")\n spinner.spin(this.toggleButton)\n this.toggleIcon?.classList.add(\"hidden\")\n\n return new Promise((resolve, reject) => {\n post(routes.expand_admin_element_path(this.elementId))\n .then((response) => {\n const data = response.data\n\n // First expand all parent elements if necessary\n if (data.parentElementIds.length) {\n const selector = data.parentElementIds\n .map((id) => `#element_${id}`)\n .join(\", \")\n document.querySelectorAll(selector).forEach((parentElement) => {\n parentElement.collapsed = false\n parentElement.toggleButton?.setAttribute(\"title\", data.title)\n })\n }\n // Finally expand ourselve\n this.collapsed = false\n this.toggleButton?.setAttribute(\"title\", data.title)\n // Resolve the promise that scrolls to the element very last\n resolve()\n })\n .catch((error) => {\n growl(error.message, \"error\")\n console.error(error)\n reject(error)\n })\n .finally(() => {\n this.toggleIcon?.classList?.remove(\"hidden\")\n spinner.stop()\n })\n })\n }\n }\n\n /**\n * Updates the quote in the element header and dispatches event\n * to parent elements\n * @param {string} title\n */\n updateTitle(title) {\n this.setTitle(title)\n this.dispatchEvent(\n new CustomEvent(\"alchemy:element-update-title\", {\n bubbles: true,\n detail: { title }\n })\n )\n }\n\n /**\n * Sets element published or hidden\n * @param {boolean}\n */\n set published(isPublished) {\n if (isPublished) {\n this.classList.remove(\"element-hidden\")\n } else {\n this.classList.add(\"element-hidden\")\n }\n }\n\n /**\n * Is element published or hidden\n * @returns {boolean}\n */\n get published() {\n return !this.classList.contains(\"hidden\")\n }\n\n /**\n * @returns {boolean}\n */\n get compact() {\n return this.getAttribute(\"compact\") !== null\n }\n\n /**\n * @returns {boolean}\n */\n get fixed() {\n return this.getAttribute(\"fixed\") !== null\n }\n\n /**\n * @param {boolean} value\n */\n set collapsed(value) {\n this.classList.toggle(\"folded\", value)\n this.classList.toggle(\"expanded\", !value)\n this.toggleIcon &&\n (this.toggleIcon.name = value ? \"arrow-left-s\" : \"arrow-down-s\")\n }\n\n /**\n * @returns {boolean}\n */\n get collapsed() {\n return this.classList.contains(\"folded\")\n }\n\n /**\n * @returns {boolean}\n */\n get expanded() {\n return !this.collapsed\n }\n\n /**\n * Toggles the dirty class\n *\n * @param {boolean} value\n */\n set dirty(value) {\n this.classList.toggle(\"dirty\", value)\n }\n\n /**\n * Returns the dirty state of this element\n *\n * @returns {boolean}\n */\n get dirty() {\n return this.classList.contains(\"dirty\")\n }\n\n /**\n * Returns the element header\n *\n * @returns {HTMLElement|undefined}\n */\n get header() {\n return this.querySelector(`.element-header`)\n }\n\n /**\n * Returns the immediate body container of this element if present\n *\n * Makes sure it does not return a nested elements body\n * by scoping the selector to this elements id.\n *\n * @returns {HTMLElement|undefined}\n */\n get body() {\n return this.querySelector(this.bodySelector)\n }\n\n get bodySelector() {\n return `#${this.id} > .element-body`\n }\n\n /**\n * Returns the immediate footer container of this element if present\n *\n * Makes sure it does not return a nested elements footer\n * by scoping the selector to this elements id.\n *\n * @returns {HTMLElement|undefined}\n */\n get footer() {\n return this.querySelector(`#${this.id} > .element-footer`)\n }\n\n /**\n * The collapse/expand toggle button\n *\n * @returns {HTMLButtonElement|undefined}\n */\n get toggleButton() {\n return this.querySelector(\".element-toggle\")\n }\n\n /**\n * The collapse/expand toggle buttons icon\n *\n * @returns {HTMLElement|undefined}\n */\n get toggleIcon() {\n return this.toggleButton?.querySelector(\"alchemy-icon\")\n }\n\n /**\n * The validation messages list container\n *\n * @returns {HTMLElement}\n */\n get elementErrors() {\n return this.body.querySelector(\".element_errors\")\n }\n\n /**\n * The element database id\n *\n * @returns {string}\n */\n get elementId() {\n return this.dataset.elementId\n }\n\n /**\n * The element defintion name\n *\n * @returns {string}\n */\n get elementName() {\n return this.dataset.elementName\n }\n\n /**\n * Does this element have ingredient editor fields?\n *\n * @returns {boolean}\n */\n get hasEditors() {\n return !!this.body?.querySelector(\".element-ingredient-editors\")\n }\n\n /**\n * Does this element have nested elements?\n *\n * @returns {boolean}\n */\n get hasChildren() {\n return !!this.querySelector(\".nested-elements\")\n }\n\n /**\n * The first child element editor if present\n *\n * @returns {HTMLButtonElement|undefined}\n */\n get firstChild() {\n return this.querySelector(\"alchemy-element-editor\")\n }\n\n /**\n * The form element if present\n *\n * Scoped to this element's own form so a wrapper element without ingredients\n * does not return a nested child's form.\n *\n * @returns {HTMLFormElement|undefined}\n */\n get form() {\n return this.querySelector(`#${this.id} > form.element-body`)\n }\n\n /**\n * The parent element editor if present\n *\n * @returns {ElementEditor|undefined}\n */\n get parentElementEditor() {\n return this.parentElement?.closest(\"alchemy-element-editor\")\n }\n\n get previewWindow() {\n return document.getElementById(\"alchemy_preview_window\")\n }\n\n #onHeaderDblclick = () => {\n this.toggle()\n }\n\n #onToggleClick = (evt) => {\n const elementEditor = evt.target.closest(\"alchemy-element-editor\")\n if (elementEditor === this) {\n this.toggle()\n }\n }\n}\n\ncustomElements.define(\"alchemy-element-editor\", ElementEditor)\n","import { Select } from \"alchemy_admin/components/select\"\n\nconst renderName = (icon, text) => `\n <div class=\"element-select-name\">\n ${icon}<span>${text}</span>\n </div>\n`\n\nexport class ElementSelect extends Select {\n get renderers() {\n return {\n item: (data, escape) => renderName(data.icon, escape(data.text)),\n option: (data, escape) => {\n const description = data.hint\n ? `<div class=\"element-select-description\">${escape(data.hint)}</div>`\n : \"\"\n return `\n <div class=\"element-select-item\">\n ${renderName(data.icon, escape(data.text))}\n ${description}\n </div>\n `\n }\n }\n }\n}\n\ncustomElements.define(\"alchemy-element-select\", ElementSelect, {\n extends: \"select\"\n})\n","import { ElementEditor } from \"alchemy_admin/components/element_editor\"\n\nclass ElementsWindow extends HTMLElement {\n #visible = true\n #turboFrame = null\n\n connectedCallback() {\n this.toggleButton?.addEventListener(\"click\", this.#onToggleClick)\n this.collapseButton?.addEventListener(\"click\", this.#onCollapseClick)\n window.addEventListener(\"message\", this.#onWindowMessage)\n document.body.addEventListener(\"click\", this.#onBodyClick)\n if (window.location.hash) {\n this.focusElementEditor(window.location.hash)\n }\n this.resize()\n }\n\n disconnectedCallback() {\n this.toggleButton?.removeEventListener(\"click\", this.#onToggleClick)\n this.collapseButton?.removeEventListener(\"click\", this.#onCollapseClick)\n window.removeEventListener(\"message\", this.#onWindowMessage)\n document.body.removeEventListener(\"click\", this.#onBodyClick)\n }\n\n #onToggleClick = (evt) => {\n evt.preventDefault()\n this.toggle()\n }\n\n #onCollapseClick = () => {\n this.collapseAllElements()\n }\n\n #onWindowMessage = (event) => {\n const data = event.data\n if (data?.message == \"Alchemy.focusElementEditor\") {\n const element = document.getElementById(`element_${data.element_id}`)\n this.show()\n element?.focusElement()\n }\n }\n\n #onBodyClick = (evt) => {\n if (!evt.target.closest(\"alchemy-element-editor\")) {\n this.querySelectorAll(\"alchemy-element-editor\").forEach((editor) => {\n editor.classList.remove(\"selected\")\n })\n this.previewWindow?.postMessage({ message: \"Alchemy.blurElements\" })\n }\n }\n\n collapseAllElements() {\n this.querySelectorAll(\n \"alchemy-element-editor:not([compact]):not([fixed])\"\n ).forEach((editor) => editor.collapse())\n }\n\n toggle() {\n this.#visible ? this.hide() : this.show()\n }\n\n show() {\n document.body.classList.add(\"elements-window-visible\")\n this.#visible = true\n this.toggleButton.closest(\"sl-tooltip\").content = Alchemy.t(\"Hide elements\")\n this.toggleButton\n .querySelector(\"alchemy-icon\")\n .setAttribute(\"name\", \"menu-unfold\")\n this.resize()\n }\n\n hide() {\n document.body.classList.remove(\"elements-window-visible\")\n document.body.style.removeProperty(\"--elements-window-width\")\n this.#visible = false\n this.toggleButton.closest(\"sl-tooltip\").content = Alchemy.t(\"Show elements\")\n this.toggleButton\n .querySelector(\"alchemy-icon\")\n .setAttribute(\"name\", \"menu-fold\")\n }\n\n resize(width) {\n if (width === undefined) {\n width = this.widthFromCookie\n }\n\n if (width) {\n document.body.style.setProperty(\"--elements-window-width\", `${width}px`)\n document.cookie = `alchemy-elements-window-width=${width}; SameSite=Lax; Path=/;`\n }\n }\n\n // Focus element editor and element in preview if URL contains hash to element editor.\n // The link is coming from the assignment view when showing assigned files or pictures.\n focusElementEditor(dom_id) {\n const el = document.querySelector(dom_id)\n\n if (el instanceof ElementEditor) {\n el.focusElement() && el.focusElementPreview()\n }\n }\n\n get collapseButton() {\n return this.querySelector(\"#collapse-all-elements-button\")\n }\n\n get toggleButton() {\n return document.querySelector(\"#element_window_button\")\n }\n\n get previewWindow() {\n return document.getElementById(\"alchemy_preview_window\")\n }\n\n get turboFrame() {\n if (!this.#turboFrame) {\n this.#turboFrame = this.closest(\"turbo-frame\")\n }\n return this.#turboFrame\n }\n\n get widthFromCookie() {\n return document.cookie\n .split(\"; \")\n .find((row) => row.startsWith(\"alchemy-elements-window-width=\"))\n ?.split(\"=\")[1]\n }\n\n set isDragged(dragged) {\n this.turboFrame.style.transitionProperty = dragged ? \"none\" : null\n this.turboFrame.style.pointerEvents = dragged ? \"none\" : null\n }\n}\n\ncustomElements.define(\"alchemy-elements-window\", ElementsWindow)\n","const MIN_WIDTH = 400\nconst MAX_WIDTH = 1000\nclass ElementsWindowHandle extends HTMLElement {\n #dragging = false\n #elementsWindow = null\n #previewWindow = null\n #minWidth = MIN_WIDTH\n #maxWidth = MAX_WIDTH\n\n connectedCallback() {\n this.addEventListener(\"mousedown\", this)\n window.addEventListener(\"mousemove\", this)\n window.addEventListener(\"mouseup\", this)\n }\n\n disconnectedCallback() {\n this.removeEventListener(\"mousedown\", this)\n window.removeEventListener(\"mousemove\", this)\n window.removeEventListener(\"mouseup\", this)\n }\n\n handleEvent(event) {\n switch (event.type) {\n case \"mousedown\":\n event.stopPropagation()\n this.onMouseDown()\n break\n case \"mouseup\":\n this.onMouseUp()\n break\n case \"mousemove\":\n if (this.#dragging) {\n this.onDrag(event.pageX)\n }\n break\n }\n }\n\n onMouseDown() {\n this.#dragging = true\n // Read the resolved min/max width the browser computed from the CSS\n // custom properties (incl. calc() and active media queries) once, so the\n // drag stays clamped to the same bounds the stylesheet defines.\n const styles = getComputedStyle(this.elementsWindow)\n this.#minWidth = parseFloat(styles.minWidth) || MIN_WIDTH\n this.#maxWidth =\n styles.maxWidth === \"none\" ? MAX_WIDTH : parseFloat(styles.maxWidth)\n this.elementsWindow.isDragged = true\n this.previewWindow.isDragged = true\n this.classList.add(\"is-dragged\")\n }\n\n onMouseUp() {\n this.#dragging = false\n this.elementsWindow.isDragged = false\n this.previewWindow.isDragged = false\n this.classList.remove(\"is-dragged\")\n }\n\n onDrag(pageX) {\n const elementWindowWidth = window.innerWidth - pageX\n const width = Math.min(\n Math.max(elementWindowWidth, this.#minWidth),\n this.#maxWidth\n )\n this.elementsWindow.resize(width)\n }\n\n get elementsWindow() {\n if (!this.#elementsWindow) {\n this.#elementsWindow = document.querySelector(\"alchemy-elements-window\")\n }\n return this.#elementsWindow\n }\n\n get previewWindow() {\n if (!this.#previewWindow) {\n this.#previewWindow = document.getElementById(\"alchemy_preview_window\")\n }\n return this.#previewWindow\n }\n}\n\ncustomElements.define(\"alchemy-elements-window-handle\", ElementsWindowHandle)\n","class FileEditor extends HTMLElement {\n connectedCallback() {\n this.deleteLink = this.querySelector(\".remove_file_link\")\n this.fileIcon = this.querySelector(\".file_icon\")\n this.fileName = this.querySelector(\".file_name\")\n this.formFieldId = this.deleteLink?.dataset.formFieldId\n this.formField = this.querySelector(`#${this.formFieldId}`)\n this.deleteLink?.addEventListener(\"click\", this)\n }\n\n disconnectedCallback() {\n this.deleteLink?.removeEventListener(\"click\", this)\n }\n\n handleEvent(event) {\n if (event.type === \"click\") this.removeFile()\n event.stopPropagation()\n }\n\n removeFile() {\n this.formField.value = \"\"\n this.fileIcon.innerHTML = \"\"\n this.fileName.innerHTML = \"\"\n this.deleteLink?.classList.add(\"hidden\")\n this.closest(\"alchemy-element-editor\").setDirty(this.formField)\n }\n}\n\ncustomElements.define(\"alchemy-file-editor\", FileEditor)\n","const DEFAULT_DEBOUNCE_TIME = 150\n\nclass ListFilter extends HTMLElement {\n #debounceTimer\n #filterField = null\n #clearButton = null\n\n connectedCallback() {\n if (this.hotkey) {\n key(this.hotkey, this.#onHotkey)\n }\n this.#filterField = this.filterField\n this.#filterField.addEventListener(\"keyup\", this.#onKeyup)\n this.#filterField.addEventListener(\"focus\", this.#onFocus)\n\n this.#clearButton = this.clearButton\n this.#clearButton.addEventListener(\"click\", this.#onClearClick)\n\n key(\"esc\", \"list_filter\", this.#onEscape)\n }\n\n disconnectedCallback() {\n clearTimeout(this.#debounceTimer)\n this.#filterField?.removeEventListener(\"keyup\", this.#onKeyup)\n this.#filterField?.removeEventListener(\"focus\", this.#onFocus)\n this.#filterField = null\n this.#clearButton?.removeEventListener(\"click\", this.#onClearClick)\n this.#clearButton = null\n if (this.hotkey) {\n key.unbind(this.hotkey)\n }\n key.unbind(\"esc\", \"list_filter\")\n }\n\n filter(term) {\n if (term === \"\") {\n this.clearButton.style.visibility = \"hidden\"\n }\n\n const matchedItems = []\n const itemsToShow = new Set()\n const lowerTerm = term.toLowerCase()\n\n // First pass: find matching items and mark their ancestors as visible too\n this.items.forEach((item) => {\n const name = item.getAttribute(this.nameAttribute)?.toLowerCase()\n // indexOf is much faster then match()\n if (name.indexOf(lowerTerm) !== -1) {\n matchedItems.push(item)\n itemsToShow.add(item)\n // Mark ancestor items as visible so nested matches stay visible\n let ancestor = item.parentElement?.closest(this.itemsSelector)\n while (ancestor) {\n itemsToShow.add(ancestor)\n ancestor = ancestor.parentElement?.closest(this.itemsSelector)\n }\n }\n })\n\n // Second pass: apply visibility\n this.items.forEach((item) => {\n item.classList.toggle(\"hidden\", !itemsToShow.has(item))\n })\n\n // Scroll into view if only one match\n if (matchedItems.length === 1) {\n matchedItems[0].scrollIntoView({ behavior: \"smooth\", block: \"nearest\" })\n }\n }\n\n clear() {\n this.filterField.value = \"\"\n this.clearButton.style.visibility = \"hidden\"\n this.items.forEach((item) => item.classList.remove(\"hidden\"))\n }\n\n get nameAttribute() {\n return this.getAttribute(\"name-attribute\") || \"name\"\n }\n\n get clearButton() {\n return this.querySelector('button[type=\"button\"]')\n }\n\n get filterField() {\n return this.querySelector('input[type=\"text\"]')\n }\n\n get items() {\n return document.querySelectorAll(this.itemsSelector)\n }\n\n get itemsSelector() {\n return this.getAttribute(\"items-selector\")\n }\n\n get debounceTime() {\n return parseInt(this.getAttribute(\"debounce-time\")) || DEFAULT_DEBOUNCE_TIME\n }\n\n get hotkey() {\n return this.getAttribute(\"hotkey\")\n }\n\n #onHotkey = () => {\n this.filterField.focus()\n return false\n }\n\n #onKeyup = () => {\n clearTimeout(this.#debounceTimer)\n this.#debounceTimer = setTimeout(() => {\n const term = this.filterField.value\n this.clearButton.style.visibility = term ? \"visible\" : \"hidden\"\n this.filter(term)\n }, this.debounceTime)\n }\n\n #onFocus = () => key.setScope(\"list_filter\")\n\n #onClearClick = (e) => {\n e.preventDefault()\n this.clear()\n }\n\n #onEscape = () => {\n this.clear()\n this.filterField.blur()\n }\n}\n\ncustomElements.define(\"alchemy-list-filter\", ListFilter)\n","import { autoUpdate, computePosition, flip } from \"@floating-ui/dom\"\n\nconst SCREEN_PADDING = 8\n\n// A main navigation entry lives inside the fixed, vertically scrollable menu\n// column. A scroll container clips its content in both axes, so the hover\n// flyout (this entry's label or its sub navigation) that reaches out to the\n// right of the column would be cut off. While the flyout is open we lift it out\n// of the clipping context by fixing it to the viewport and let Floating UI keep\n// it anchored to the entry.\nclass MainNaviEntry extends HTMLElement {\n #flyout = null\n #stopAutoUpdate = null\n\n connectedCallback() {\n this.addEventListener(\"mouseenter\", this.#open)\n this.addEventListener(\"mouseleave\", this.#close)\n }\n\n disconnectedCallback() {\n this.removeEventListener(\"mouseenter\", this.#open)\n this.removeEventListener(\"mouseleave\", this.#close)\n this.#close()\n }\n\n #open = () => {\n const flyout = this.#flyoutElement()\n if (!flyout) return\n\n this.#flyout = flyout\n this.#stopAutoUpdate = autoUpdate(this, flyout, () => {\n computePosition(this, flyout, {\n strategy: \"fixed\",\n placement: \"right-start\",\n // Keep the flyout anchored to the entry: when there is no room below,\n // flip to bottom alignment instead of drifting away from the entry.\n middleware: [\n flip({ fallbackPlacements: [\"right-end\"], padding: SCREEN_PADDING })\n ]\n }).then(({ x, y }) => {\n Object.assign(flyout.style, {\n position: \"fixed\",\n left: `${Math.round(x)}px`,\n top: `${Math.round(y)}px`\n })\n })\n })\n }\n\n #close = () => {\n this.#stopAutoUpdate?.()\n this.#stopAutoUpdate = null\n\n if (this.#flyout) {\n this.#flyout.style.position = \"\"\n this.#flyout.style.left = \"\"\n this.#flyout.style.top = \"\"\n this.#flyout = null\n }\n }\n\n // The entry's flyout element, but only when it is actually rendered as a\n // flyout. In the expanded menu the label and sub navigation sit in the normal\n // document flow (position: static) and must be left untouched.\n #flyoutElement() {\n const flyout =\n this.querySelector(\".sub_navigation\") || this.querySelector(\"label\")\n if (!flyout || getComputedStyle(flyout).position === \"static\") return null\n return flyout\n }\n}\n\ncustomElements.define(\"alchemy-main-navi-entry\", MainNaviEntry)\n\nexport default MainNaviEntry\n","import { patch } from \"alchemy_admin/utils/ajax\"\nimport { growl } from \"alchemy_admin/growler\"\nimport { routes } from \"alchemy_admin/routes\"\n\n/**\n * Custom element for menu nodes in the nodes tree\n * Handles folding and unfolding of its children\n */\nexport class AlchemyMenuNode extends HTMLElement {\n connectedCallback() {\n this.addEventListener(\"click\", this)\n }\n\n disconnectedCallback() {\n this.removeEventListener(\"click\", this)\n }\n\n handleEvent(event) {\n // Clicks of nested menu nodes bubble up here as well\n if (event.target.closest(\"alchemy-node-folder\") === this.folder) {\n this.toggleFolded(event)\n }\n }\n\n async toggleFolded(event) {\n event.preventDefault()\n\n try {\n await patch(routes.node.toggle_folded_api_path(this.nodeId))\n this.folded = !this.folded\n } catch (error) {\n growl(error.message || error, \"error\")\n }\n }\n\n /**\n * Shows the fold toggle only for nodes that have something to fold\n */\n updateFolder() {\n this.folder.hidden = !(this.hasChildren || this.folded)\n }\n\n get folded() {\n return this.hasAttribute(\"folded\")\n }\n\n set folded(value) {\n this.toggleAttribute(\"folded\", value)\n this.folder.toggleAttribute(\"folded\", value)\n this.childrenList.classList.toggle(\"folded\", value)\n }\n\n get hasChildren() {\n return (\n this.childrenList.querySelectorAll(\":scope > alchemy-menu-node\").length >\n 0\n )\n }\n\n get nodeId() {\n return this.getAttribute(\"node-id\")\n }\n\n get folder() {\n return this.querySelector(\":scope > li > .sitemap_node alchemy-node-folder\")\n }\n\n get childrenList() {\n return this.querySelector(\":scope > li > ul.children\")\n }\n}\n\ncustomElements.define(\"alchemy-menu-node\", AlchemyMenuNode)\n","const DISMISS_DELAY = 5000\n\nclass Message extends HTMLElement {\n #dismissTimeoutId = null\n\n handleEvent(event) {\n if (event.type === \"click\") {\n this.dismiss()\n }\n }\n\n connectedCallback() {\n if (!this.querySelector(\":scope > alchemy-icon\")) {\n const closeIcon =\n this.dismissable && this.type === \"error\"\n ? '<alchemy-icon name=\"close\"></alchemy-icon>'\n : \"\"\n this.insertAdjacentHTML(\n \"afterbegin\",\n `<alchemy-icon name=\"${this.iconName}\"></alchemy-icon>${closeIcon}`\n )\n }\n if (this.dismissable || this.type === \"error\") {\n this.addEventListener(\"click\", this)\n }\n if (this.dismissable && this.type !== \"error\") {\n this.#dismissTimeoutId = setTimeout(() => {\n this.dismiss()\n }, this.dismissDelay)\n }\n }\n\n disconnectedCallback() {\n if (this.#dismissTimeoutId !== null) {\n clearTimeout(this.#dismissTimeoutId)\n this.#dismissTimeoutId = null\n }\n }\n\n dismiss() {\n this.addEventListener(\"transitionend\", () => this.remove())\n this.classList.add(\"dismissed\")\n }\n\n get dismissable() {\n return this.hasAttribute(\"dismissable\")\n }\n\n get icon() {\n return this.getAttribute(\"icon\")\n }\n\n get type() {\n return this.getAttribute(\"type\") || \"notice\"\n }\n\n get dismissDelay() {\n return parseInt(\n this.noticesWrapper?.dataset.autoDismissDelay || DISMISS_DELAY\n )\n }\n\n get iconName() {\n switch (this.icon || this.type) {\n case \"warning\":\n case \"warn\":\n case \"alert\":\n return \"alert\"\n case \"notice\":\n return \"check\"\n case \"info\":\n case \"hint\":\n return \"information\"\n case \"error\":\n return \"bug\"\n default:\n return this.type\n }\n }\n\n get noticesWrapper() {\n return this.closest(\"#flash_notices\")\n }\n}\n\ncustomElements.define(\"alchemy-message\", Message)\n","import { growl } from \"alchemy_admin/growler\"\n\nclass Growl extends HTMLElement {\n connectedCallback() {\n growl(this.message, this.getAttribute(\"type\") || \"notice\")\n this.remove()\n }\n\n get message() {\n return this.getAttribute(\"message\") || this.innerHTML\n }\n}\n\ncustomElements.define(\"alchemy-growl\", Growl)\n","import Cropper from \"cropperjs\"\nimport { currentDialog } from \"alchemy_admin/dialog\"\n\nexport class ImageCropper extends HTMLElement {\n #cropper = null\n #cropFromField = null\n #cropSizeField = null\n\n connectedCallback() {\n this.image = this.querySelector(\"img\")\n this.form = this.querySelector(\"form\")\n this.#cropFromField = document.getElementById(\n this.getAttribute(\"crop-from-field-id\")\n )\n this.#cropSizeField = document.getElementById(\n this.getAttribute(\"crop-size-field-id\")\n )\n this.elementEditor = document.querySelector(\n `[data-element-id='${this.getAttribute(\"element-id\")}']`\n )\n this.form.addEventListener(\"submit\", this.#onSubmit)\n this.form.addEventListener(\"reset\", this.#onReset)\n this.#cropper = new Cropper(this.image, this.cropperOptions)\n }\n\n disconnectedCallback() {\n this.#cropper?.destroy()\n }\n\n get cropperOptions() {\n return {\n aspectRatio: this.aspectRatio,\n viewMode: 1,\n zoomable: false,\n checkCrossOrigin: false, // Prevent CORS issues\n checkOrientation: false, // Prevent loading the image via AJAX which can cause CORS issues\n data: this.box\n }\n }\n\n get aspectRatio() {\n const ratio = this.getAttribute(\"ratio\")\n // NaN lets cropperjs use a free aspect ratio.\n return ratio ? parseFloat(ratio) : NaN\n }\n\n get cropFrom() {\n if (this.#cropFromField?.value) {\n return this.#cropFromField.value.split(\"x\").map((v) => parseInt(v))\n }\n }\n\n get cropSize() {\n if (this.#cropSizeField?.value) {\n return this.#cropSizeField.value.split(\"x\").map((v) => parseInt(v))\n }\n }\n\n get box() {\n if (this.cropFrom && this.cropSize) {\n return {\n x: this.cropFrom[0],\n y: this.cropFrom[1],\n width: this.cropSize[0],\n height: this.cropSize[1]\n }\n } else {\n return this.defaultBox\n }\n }\n\n get defaultBox() {\n const box = JSON.parse(this.getAttribute(\"default-box\"))\n return { x: box[0], y: box[1], width: box[2], height: box[3] }\n }\n\n update(coords) {\n this.#cropFromField.value = `${coords.x}x${coords.y}`\n this.#cropFromField.dispatchEvent(new Event(\"change\"))\n this.#cropSizeField.value = `${coords.width}x${coords.height}`\n this.#cropSizeField.dispatchEvent(new Event(\"change\"))\n }\n\n reset() {\n const cropper = this.#cropper\n // Apply the default size. cropperjs clamps the crop box to its maximum size.\n cropper.setData(this.defaultBox)\n // When the default box sits at the maximum crop box size, sub-pixel rounding\n // makes cropperjs treat setData's box as oversized and revert its position\n // (renderCropBox resets top/left to their old values). Re-apply the position\n // in canvas coordinates afterwards – that does not touch the size, so it is\n // not reverted and the mask actually moves to the default box.\n const canvas = cropper.getCanvasData()\n const scale = canvas.width / canvas.naturalWidth\n cropper.setCropBoxData({\n left: canvas.left + this.defaultBox.x * scale,\n top: canvas.top + this.defaultBox.y * scale\n })\n this.update(this.defaultBox)\n }\n\n #onSubmit = (event) => {\n event.preventDefault()\n this.update(this.#cropper.getData(true))\n this.elementEditor?.setDirty()\n currentDialog()?.close()\n }\n\n #onReset = (event) => {\n event.preventDefault()\n this.reset()\n }\n}\n\ncustomElements.define(\"alchemy-image-cropper\", ImageCropper)\n","import { Dialog } from \"alchemy_admin/dialog\"\nimport { createHtmlElement } from \"alchemy_admin/utils/dom_helpers\"\n\nexport default class ImageOverlay extends Dialog {\n // The picture is rendered in a Turbo frame that replaces its content on every\n // navigation, so the picture elements are looked up on demand instead of being\n // bound once.\n #clickHandler = (e) => {\n if (e.target.closest(\".picture-overlay-handle\")) {\n e.preventDefault()\n this.dialog.classList.toggle(\"hide-form\")\n return\n }\n if (e.target.closest(\".zoomed-picture-background\")) {\n e.stopPropagation()\n if (e.target.nodeName === \"IMG\") {\n return\n }\n this.close()\n }\n }\n\n #keydownHandler = (e) => {\n if (e.target.nodeName === \"INPUT\" || e.target.nodeName === \"TEXTAREA\") {\n return\n }\n switch (e.key) {\n case \"ArrowLeft\":\n this.previous()\n e.preventDefault()\n break\n case \"ArrowRight\":\n this.next()\n e.preventDefault()\n break\n }\n }\n\n // The overlay always fills the viewport via its stylesheet, so the inline\n // dimensions the generic dialog applies would only cap it.\n resize() {}\n\n init() {\n this.dialog_body.removeEventListener(\"click\", this.#clickHandler)\n this.dialog_body.addEventListener(\"click\", this.#clickHandler)\n this.#initKeyboardNavigation()\n super.init()\n }\n\n previous() {\n this.dialog_body.querySelector(\".previous-picture\")?.click()\n }\n\n next() {\n this.dialog_body.querySelector(\".next-picture\")?.click()\n }\n\n close() {\n document.removeEventListener(\"keydown\", this.#keydownHandler)\n return super.close()\n }\n\n build() {\n this.dialog_container = createHtmlElement(\n '<dialog class=\"alchemy-image-overlay-container\"></dialog>'\n )\n this.dialog = createHtmlElement(\n '<div class=\"alchemy-image-overlay-dialog\"></div>'\n )\n this.dialog_body = createHtmlElement(\n '<div class=\"alchemy-image-overlay-body\"></div>'\n )\n this.close_button =\n createHtmlElement(`<a class=\"alchemy-image-overlay-close\">\n <alchemy-icon name=\"close\" size=\"xl\"></alchemy-icon>\n </a>`)\n this.dialog.append(this.close_button)\n this.dialog.append(this.dialog_body)\n this.dialog_container.append(this.dialog)\n document.body.append(this.dialog_container)\n }\n\n #initKeyboardNavigation() {\n document.removeEventListener(\"keydown\", this.#keydownHandler)\n document.addEventListener(\"keydown\", this.#keydownHandler)\n }\n}\n","import { DialogLink } from \"alchemy_admin/components/dialog_link\"\nimport ImageOverlay from \"alchemy_admin/image_overlay\"\n\n// Opens the picture in a full screen image overlay instead of navigating to it\nexport class ImageOverlayLink extends DialogLink {\n openDialog() {\n this.dialog = new ImageOverlay(this.href, { padding: false })\n this.dialog.open()\n }\n}\n\ncustomElements.define(\"alchemy-image-overlay-link\", ImageOverlayLink, {\n extends: \"a\"\n})\n","export class IngredientGroup extends HTMLDetailsElement {\n #localStorageKey = \"Alchemy.expanded_ingredient_groups\"\n\n connectedCallback() {\n this.addEventListener(\"toggle\", this)\n\n if (this.isInLocalStorage) {\n this.open = true\n }\n }\n\n disconnectedCallback() {\n this.removeEventListener(\"toggle\", this)\n }\n\n /**\n * Toggle visibility of the ingredient fields in this group\n */\n handleEvent() {\n let expanded_ingredient_groups = this.localStorageItem\n\n if (this.open) {\n this.toggleIcon.name = \"arrow-down-s\"\n if (!this.isInLocalStorage) expanded_ingredient_groups.push(this.id)\n } else {\n this.toggleIcon.name = \"arrow-right-s\"\n expanded_ingredient_groups = expanded_ingredient_groups.filter(\n (value) => value !== this.id\n )\n }\n\n localStorage.setItem(\n this.#localStorageKey,\n JSON.stringify(expanded_ingredient_groups)\n )\n }\n\n get isInLocalStorage() {\n return this.localStorageItem.includes(this.id)\n }\n\n get localStorageItem() {\n const item = localStorage.getItem(this.#localStorageKey)\n\n if (!item) return []\n\n try {\n return JSON.parse(item)\n } catch (error) {\n console.error(error)\n return []\n }\n }\n\n get toggleIcon() {\n return this.querySelector(\"alchemy-icon\")\n }\n}\n\ncustomElements.define(\"alchemy-ingredient-group\", IngredientGroup, {\n extends: \"details\"\n})\n","class LinkButton extends HTMLButtonElement {\n connectedCallback() {\n this.addEventListener(\"click\", this)\n this.classList.add(\"icon_button\")\n // Prevent accidental form submits if this component is wrapped inside a form\n this.setAttribute(\"type\", \"button\")\n this.innerHTML = '<alchemy-icon name=\"link\" icon-style=\"m\"></alchemy-icon>'\n }\n\n handleEvent(event) {\n const dialog = new Alchemy.LinkDialog({\n url: this.linkUrl,\n title: this.linkTitle,\n target: this.linkTarget,\n type: this.linkClass\n })\n dialog.open().then((link) => this.setLink(link))\n event.preventDefault()\n }\n\n setLink(link) {\n if (link.url === \"\") {\n this.classList.remove(\"linked\")\n this.dispatchEvent(new CustomEvent(\"alchemy:unlink\", { bubbles: true }))\n } else {\n this.classList.add(\"linked\")\n this.dispatchEvent(\n new CustomEvent(\"alchemy:link\", {\n bubbles: true,\n detail: link\n })\n )\n }\n }\n\n get linkUrl() {\n return this.linkButtons.linkUrlField.value\n }\n\n get linkTitle() {\n return this.linkButtons.linkTitleField.value\n }\n\n get linkTarget() {\n return this.linkButtons.linkTargetField.value\n }\n\n get linkClass() {\n return this.linkButtons.linkClassField.value\n }\n\n get linkButtons() {\n return this.closest(\"alchemy-link-buttons\")\n }\n}\n\ncustomElements.define(\"alchemy-link-button\", LinkButton, { extends: \"button\" })\n","class UnlinkButton extends HTMLButtonElement {\n connectedCallback() {\n this.addEventListener(\"click\", this)\n this.classList.add(\"icon_button\")\n // Prevent accidental form submits if this component is wrapped inside a form\n this.setAttribute(\"type\", \"button\")\n this.linked = this.linked\n this.innerHTML =\n '<alchemy-icon name=\"link-unlink\" icon-style=\"m\"></alchemy-icon>'\n }\n\n handleEvent(event) {\n if (this.linked) {\n this.linked = false\n this.blur()\n this.dispatchEvent(new CustomEvent(\"alchemy:unlink\", { bubbles: true }))\n }\n event.preventDefault()\n }\n\n set linked(isLinked) {\n if (isLinked) {\n this.classList.replace(\"disabled\", \"linked\")\n this.removeAttribute(\"tabindex\")\n } else {\n this.classList.replace(\"linked\", \"disabled\")\n this.setAttribute(\"tabindex\", \"-1\")\n }\n }\n\n get linked() {\n return this.classList.contains(\"linked\")\n }\n}\n\ncustomElements.define(\"alchemy-unlink-button\", UnlinkButton, {\n extends: \"button\"\n})\n","import \"alchemy_admin/components/link_buttons/link_button\"\nimport \"alchemy_admin/components/link_buttons/unlink_button\"\n\nclass LinkButtons extends HTMLElement {\n connectedCallback() {\n this.addEventListener(\"alchemy:link\", this)\n this.addEventListener(\"alchemy:unlink\", this)\n }\n\n disconnectedCallback() {\n this.removeEventListener(\"alchemy:link\", this)\n this.removeEventListener(\"alchemy:unlink\", this)\n }\n\n handleEvent(event) {\n switch (event.type) {\n case \"alchemy:link\":\n this.setLink(event.detail)\n break\n case \"alchemy:unlink\":\n this.removeLink()\n }\n event.stopPropagation()\n }\n\n setLink(data) {\n this.linkUrlField.value = data.url\n this.linkUrlField.dispatchEvent(new Event(\"change\"))\n this.linkTitleField.value = data.title\n this.linkClassField.value = data.type\n this.linkTargetField.value = data.target\n\n this.unlinkButton.linked = true\n this.setElementDirty()\n }\n\n removeLink() {\n this.linkUrlField.value = \"\"\n this.linkUrlField.dispatchEvent(new Event(\"change\"))\n this.linkTitleField.value = \"\"\n this.linkClassField.value = \"\"\n this.linkTargetField.value = \"\"\n\n this.linkButton.classList.remove(\"linked\")\n this.unlinkButton.linked = false\n\n this.setElementDirty()\n }\n\n setElementDirty() {\n this.elementEditor.setDirty(this)\n }\n\n get linkButton() {\n return this.querySelector('[is=\"alchemy-link-button\"]')\n }\n\n get unlinkButton() {\n return this.querySelector('[is=\"alchemy-unlink-button\"]')\n }\n\n get ingredientEditor() {\n const ingredientId = this.dataset.ingredientId\n return this.parentElement.closest(`[data-ingredient-id='${ingredientId}']`)\n }\n\n get elementEditor() {\n return this.closest(\"alchemy-element-editor\")\n }\n\n get linkUrlField() {\n return this.ingredientEditor.querySelector(\"[data-link-value]\")\n }\n\n get linkTitleField() {\n return this.ingredientEditor.querySelector(\"[data-link-title]\")\n }\n\n get linkTargetField() {\n return this.ingredientEditor.querySelector(\"[data-link-target]\")\n }\n\n get linkClassField() {\n return this.ingredientEditor.querySelector(\"[data-link-class]\")\n }\n}\n\ncustomElements.define(\"alchemy-link-buttons\", LinkButtons)\n","/**\n * Auto-rotating carousel for the dashboard news widget.\n *\n * Paging is handled by CSS scroll snap on `.news-reader--track`; this component\n * scrolls to the next slide on a timer (pausing while hovered), lets the user\n * swipe/scroll manually, and keeps the dot indicators in sync with the scroll\n * position.\n */\nclass NewsReader extends HTMLElement {\n connectedCallback() {\n this.track = this.querySelector(\".news-reader--track\")\n this.slides = Array.from(this.querySelectorAll(\".news-reader--item\"))\n if (!this.track || this.slides.length < 2) return\n\n this.currentIndex = 0\n this.createDots()\n this.setActive(0)\n this.startRotation()\n\n this.addEventListener(\"mouseenter\", this)\n this.addEventListener(\"mouseleave\", this)\n this.track.addEventListener(\"scroll\", this, { passive: true })\n }\n\n disconnectedCallback() {\n this.stopRotation()\n }\n\n handleEvent(event) {\n switch (event.type) {\n case \"mouseenter\":\n this.stopRotation()\n break\n case \"mouseleave\":\n this.startRotation()\n break\n case \"scroll\":\n this.syncActiveToScroll()\n break\n }\n }\n\n createDots() {\n this.dotList = document.createElement(\"div\")\n this.dotList.className = \"news-reader--dots\"\n this.dots = this.slides.map((_, index) => {\n const dot = document.createElement(\"button\")\n dot.type = \"button\"\n dot.className = \"news-reader--dot\"\n dot.setAttribute(\"aria-label\", `${index + 1}`)\n dot.addEventListener(\"click\", () => {\n this.goTo(index)\n this.startRotation()\n })\n this.dotList.append(dot)\n return dot\n })\n this.append(this.dotList)\n }\n\n goTo(index) {\n this.currentIndex = index\n this.slides[index].scrollIntoView({\n behavior: \"smooth\",\n inline: \"start\",\n block: \"nearest\"\n })\n this.setActive(index)\n }\n\n next() {\n this.goTo((this.currentIndex + 1) % this.slides.length)\n }\n\n // Keep the active dot in sync when the user scrolls the track manually.\n syncActiveToScroll() {\n const width = this.track.clientWidth\n if (!width) return\n const index = Math.round(this.track.scrollLeft / width)\n if (index !== this.currentIndex && this.slides[index]) {\n this.currentIndex = index\n this.setActive(index)\n }\n }\n\n setActive(index) {\n this.dots.forEach((dot, i) => {\n dot.classList.toggle(\"is-active\", i === index)\n })\n }\n\n startRotation() {\n this.stopRotation()\n this.timer = window.setInterval(() => this.next(), this.interval)\n }\n\n stopRotation() {\n if (this.timer) window.clearInterval(this.timer)\n this.timer = null\n }\n\n get interval() {\n return parseInt(this.getAttribute(\"interval\"), 10) || 8000\n }\n}\n\ncustomElements.define(\"alchemy-news-reader\", NewsReader)\n\nexport default NewsReader\n","/**\n * Custom element for the fold toggle of a menu node\n * Renders the fold arrow and keeps it in sync with the folded attribute\n */\nexport class AlchemyNodeFolder extends HTMLElement {\n static get observedAttributes() {\n return [\"folded\"]\n }\n\n connectedCallback() {\n this.innerHTML = `\n <button class=\"node_folder icon_button\">\n <alchemy-icon name=\"${this.iconName}\"></alchemy-icon>\n </button>\n `\n }\n\n attributeChangedCallback() {\n // Fires before connectedCallback for attributes present in the markup\n this.icon?.setAttribute(\"name\", this.iconName)\n }\n\n get icon() {\n return this.querySelector(\"alchemy-icon\")\n }\n\n get iconName() {\n return this.folded ? \"arrow-right-s\" : \"arrow-down-s\"\n }\n\n get folded() {\n return this.hasAttribute(\"folded\")\n }\n}\n\ncustomElements.define(\"alchemy-node-folder\", AlchemyNodeFolder)\n","// Syncs the node name and url fields with the selected page\nexport class NodeForm extends HTMLElement {\n connectedCallback() {\n this.addEventListener(\"Alchemy.RemoteSelect.Change\", this.#onPageSelect)\n }\n\n disconnectedCallback() {\n this.removeEventListener(\"Alchemy.RemoteSelect.Change\", this.#onPageSelect)\n }\n\n #onPageSelect = (event) => {\n const page = event.detail.added\n\n if (page) {\n this.nameField.setAttribute(\"placeholder\", page.name)\n this.urlField.value = page.url_path\n this.urlField.setAttribute(\"disabled\", \"disabled\")\n } else {\n this.nameField.removeAttribute(\"placeholder\")\n this.urlField.value = \"\"\n this.urlField.removeAttribute(\"disabled\")\n }\n }\n\n get nameField() {\n return this.querySelector(\"#node_name\")\n }\n\n get urlField() {\n return this.querySelector(\"#node_url\")\n }\n}\n\ncustomElements.define(\"alchemy-node-form\", NodeForm)\n","import { RemoteSelect } from \"alchemy_admin/components/remote_select\"\nimport { escape_html } from \"tom-select/utils\"\n\nclass NodeSelect extends RemoteSelect {\n _searchQuery(term, page) {\n return {\n filter: {\n name_or_page_name_i_cont: term,\n ...JSON.parse(this.queryParams)\n },\n page: page\n }\n }\n\n _entry(node, term) {\n return {\n icon: \"menu-2\",\n primary: this.#breadcrumb(node) + this._hightlightTerm(node.name, term),\n secondary: { text: node.url, truncate: \"head\" }\n }\n }\n\n _selectedEntry(node) {\n return {\n icon: \"menu-2\",\n primary: this.#breadcrumb(node) + escape_html(node.name)\n }\n }\n\n /**\n * Renders the node's ancestors as a breadcrumb that precedes its name. The\n * ancestors place the node in the tree, so a deeply nested node reads clearly.\n * @param {object} node\n * @returns {string}\n */\n #breadcrumb(node) {\n const ancestors = node.ancestors.map((ancestor) => ancestor.name)\n if (ancestors.length === 0) return \"\"\n const separator = `<alchemy-icon name=\"arrow-right-s\" class=\"node-select--separator\"></alchemy-icon>`\n return `<span class=\"node-select--ancestors\">${ancestors\n .map(escape_html)\n .join(separator)}</span>${separator}`\n }\n}\n\ncustomElements.define(\"alchemy-node-select\", NodeSelect)\n","import Sortable from \"sortablejs\"\nimport { patch } from \"alchemy_admin/utils/ajax\"\nimport { growl } from \"alchemy_admin/growler\"\nimport { translate } from \"alchemy_admin/i18n\"\nimport { routes } from \"alchemy_admin/routes\"\n\n/**\n * Custom element for the nodes tree container\n * Handles drag and drop sorting of its menu nodes\n */\nexport class AlchemyNodeTree extends HTMLElement {\n connectedCallback() {\n this.querySelectorAll(\"ul.children\").forEach((list) => {\n new Sortable(list, {\n group: \"nodes\",\n animation: 150,\n fallbackOnBody: true,\n swapThreshold: 0.65,\n handle: \".node_name\",\n invertSwap: true,\n draggable: \"alchemy-menu-node\",\n onEnd: (event) => this.handleSort(event)\n })\n })\n }\n\n async handleSort(event) {\n if (event.from === event.to && event.oldIndex === event.newIndex) {\n return\n }\n\n const url = routes.node.move_api_path(event.item.nodeId)\n const data = {\n target_parent_id: event.to.dataset.recordId,\n new_position: event.newIndex\n }\n\n try {\n await patch(url, data)\n this.updateFolders(event.from, event.to)\n growl(translate(\"Successfully moved menu item\"))\n } catch (error) {\n growl(error.message || error, \"error\")\n }\n }\n\n updateFolders(from, to) {\n from.closest(\"alchemy-menu-node\")?.updateFolder()\n\n if (from !== to) {\n to.closest(\"alchemy-menu-node\")?.updateFolder()\n }\n }\n}\n\ncustomElements.define(\"alchemy-node-tree\", AlchemyNodeTree)\n","export function formatFileSize(bytes) {\n let exponent = bytes === 0 ? 0 : Math.floor(Math.log(bytes) / Math.log(1024))\n\n // prevent format higher than GB\n if (exponent > 3) {\n exponent = 3\n }\n\n let value = (bytes / Math.pow(1024, exponent)).toFixed(2)\n return value + \" \" + [\"B\", \"kB\", \"MB\", \"GB\"][exponent]\n}\n","import { formatFileSize } from \"alchemy_admin/utils/format\"\nimport { translate } from \"alchemy_admin/i18n\"\nimport { growl } from \"alchemy_admin/growler\"\nimport { readJSONScript } from \"alchemy_admin/utils/json_script\"\n\nexport class FileUpload extends HTMLElement {\n // public — used by callers (Uploader, Progress, tests)\n file = null\n request = null\n progressEventLoaded = 0\n progressEventTotal = 0\n\n // private — backing state for getters/setters\n #valid = true\n #value = 0\n #status = undefined\n #errorMessage = \"\"\n\n connectedCallback() {\n this.innerHTML = `\n <sl-progress-bar value=\"${this.value}\"></sl-progress-bar>\n <div class=\"description\">\n <span class=\"file-name\">${this.file?.name}</span>\n <span class=\"loaded-size\">${this.loadedSize}</span>\n <span class=\"error-message\">${this.errorMessage}</span>\n </div>\n <sl-tooltip content=\"${translate(\"Abort upload\")}\">\n <button class=\"icon_button\" aria-label=\"${translate(\"Abort upload\")}\">\n <alchemy-icon name=\"close\"></alchemy-icon>\n </button>\n </sl-tooltip>\n `\n\n this.querySelector(\"button\").addEventListener(\"click\", () => this.cancel())\n\n if (this.file?.type.includes(\"image\")) {\n const reader = new FileReader()\n reader.readAsDataURL(this.file)\n reader.addEventListener(\"load\", () => {\n const image = new Image()\n image.src = reader.result\n this.prepend(image)\n })\n }\n }\n\n /**\n * Initialize the component with file and request\n * @param {File} file\n * @param {XMLHttpRequest} request\n */\n initialize(file, request) {\n this.file = file\n this.request = request\n this.progressEventTotal = file ? file.size : 0\n this.status = \"in-progress\"\n\n this.#validateFile()\n this.#addRequestEventListener()\n }\n\n /**\n * cancel the upload\n */\n cancel() {\n if (!this.finished) {\n this.status = \"canceled\"\n this.request?.abort()\n this.dispatchCustomEvent(\"FileUpload.Change\")\n }\n }\n\n /**\n * Dispatches a custom event with given name, namespaced under `Alchemy.`.\n * @param {string} name The name of the custom event\n */\n dispatchCustomEvent(name) {\n this.dispatchEvent(new CustomEvent(`Alchemy.${name}`, { bubbles: true }))\n }\n\n /**\n * validate given file with the uploader configuration\n */\n #validateFile() {\n const config = readJSONScript(\"alchemy_uploader_defaults\")\n const maxFileSize = config.file_size_limit * Math.pow(1024, 2) // in Byte\n let errorMessage = undefined\n\n if (this.file?.size > maxFileSize) {\n errorMessage = translate(\"Uploaded bytes exceed file size\")\n }\n\n const allowedFiletypes = this.file?.type.includes(\"image\")\n ? config.allowed_filetypes.alchemy_pictures\n : config.allowed_filetypes.alchemy_attachments\n\n // The allowlist holds filename extensions, so we cannot derive the format\n // from the MIME type: `application/msword` is not `doc` and every Office\n // Open XML type only yields `vnd`.\n const extension = this.file?.name.match(/\\.([^.]+)$/)?.[1].toLowerCase()\n\n const isFileFormatSupported =\n allowedFiletypes.includes(\"*\") ||\n allowedFiletypes.some((type) => type.toLowerCase() === extension)\n\n if (!isFileFormatSupported) {\n errorMessage = translate(\"File type not allowed\")\n }\n\n if (errorMessage) {\n this.valid = false\n this.errorMessage = errorMessage\n }\n }\n\n /**\n * register event listeners to react on request changes\n */\n #addRequestEventListener() {\n // prevent errors if the component will be called without a request - object\n if (!this.request) {\n return\n }\n\n // update the progress bar and currently loaded size information\n this.request.upload.onprogress = (progressEvent) => {\n this.progressEvent = progressEvent\n }\n\n // triggers, when the upload is done\n this.request.onload = () => {\n if (this.request.status < 400) {\n this.status = \"successful\"\n growl(this.responseMessage)\n } else {\n this.status = \"failed\"\n this.errorMessage = this.responseMessage\n }\n this.dispatchCustomEvent(\"FileUpload.Change\")\n }\n\n // catch request errors\n this.request.onerror = () => {\n this.errorMessage = translate(\"An error occurred during the transaction\")\n }\n }\n\n /**\n * @returns {boolean}\n */\n get active() {\n return this.valid && this.status !== \"canceled\"\n }\n\n /**\n * @returns {string}\n */\n get errorMessage() {\n return this.#errorMessage || \"\"\n }\n\n /**\n * @param {string} message\n */\n set errorMessage(message) {\n this.#errorMessage = message\n const errorMessageContainer = this.querySelector(\".error-message\")\n if (errorMessageContainer) {\n errorMessageContainer.textContent = message\n }\n growl(message, \"error\")\n }\n\n /**\n * @returns {boolean}\n */\n get finished() {\n return [\"canceled\", \"successful\", \"failed\"].includes(this.status)\n }\n\n /**\n * format the loaded and total size and present that as a string\n * @returns {string}\n */\n get loadedSize() {\n return `${formatFileSize(this.progressEventLoaded)} / ${formatFileSize(\n this.progressEventTotal\n )}`\n }\n\n /**\n * @returns {HTMLProgressElement|undefined}\n */\n get progressElement() {\n return this.querySelector(\"sl-progress-bar\")\n }\n\n /**\n * @param {ProgressEvent} progressEvent\n */\n set progressEvent(progressEvent) {\n this.progressEventLoaded = progressEvent.loaded\n this.progressEventTotal = progressEvent.total\n\n this.value = Math.round((progressEvent.loaded / progressEvent.total) * 100)\n this.querySelector(\".loaded-size\").textContent = this.loadedSize\n }\n\n /**\n * @returns {string}\n */\n get responseMessage() {\n try {\n const response = JSON.parse(this.request.responseText)\n return response[\"message\"]\n } catch (error) {\n return `${this.request.status}: ${this.request.statusText}`\n }\n }\n\n /**\n * @returns {string}\n */\n get status() {\n return this.#status\n }\n\n /**\n * @param {string} status\n */\n set status(status) {\n this.#status = status\n this.className = status\n\n this.progressElement?.toggleAttribute(\n \"indeterminate\",\n status === \"upload-finished\"\n )\n }\n\n /**\n * @returns {boolean}\n */\n get valid() {\n return this.#valid\n }\n\n /**\n * @param {boolean} isValid\n */\n set valid(isValid) {\n this.#valid = isValid\n this.classList.toggle(\"invalid\", !isValid)\n }\n\n /**\n * get the progress value of the current file\n * @returns {number}\n */\n get value() {\n return this.#value\n }\n\n /**\n * @param {number} value\n */\n set value(value) {\n this.#value = value\n if (this.progressElement) {\n this.progressElement.value = value\n }\n\n if (value === 100) {\n this.status = \"upload-finished\"\n }\n\n this.dispatchCustomEvent(\"FileUpload.Change\")\n }\n}\n\ncustomElements.define(\"alchemy-file-upload\", FileUpload)\n","import { FileUpload } from \"alchemy_admin/components/uploader/file_upload\"\nimport { formatFileSize } from \"alchemy_admin/utils/format\"\nimport { translate } from \"alchemy_admin/i18n\"\n\nconst template = (buttonLabel, fileCount) => `\n <sl-progress-bar value=\"0\"></sl-progress-bar>\n <div class=\"overall-progress-value\">\n <span class=\"value-text\"></span>\n\n <sl-tooltip content=\"${buttonLabel}\">\n <button class=\"icon_button\" aria-label=\"${buttonLabel}\">\n <alchemy-icon name=\"close\"></alchemy-icon>\n </button>\n </sl-tooltip>\n </div>\n <div class=\"single-uploads\" style=\"--progress-columns: ${\n fileCount > 3 ? 3 : fileCount\n }\"></div>\n <div class=\"overall-upload-value value-text\"></div>\n`\n\nexport class Progress extends HTMLElement {\n // public — accessed by Uploader and tests\n fileCount = 0\n\n // private — backing state and internals\n #fileUploads = []\n #buttonLabel = translate(\"Cancel all uploads\")\n #actionButton = null\n #visible = false\n #handleFileChange = () => this.#updateView()\n\n connectedCallback() {\n this.innerHTML = template(this.#buttonLabel, this.fileCount)\n this.visible = true\n\n this.#actionButton = this.querySelector(\"button\")\n this.#actionButton.addEventListener(\"click\", () => {\n if (this.finished) {\n this.onComplete(this.status)\n } else {\n this.cancel()\n }\n })\n\n this.#fileUploads.forEach((fileUpload) => {\n this.querySelector(\".single-uploads\").append(fileUpload)\n })\n\n this.#updateView()\n this.addEventListener(\"Alchemy.FileUpload.Change\", this.#handleFileChange)\n }\n\n disconnectedCallback() {\n this.removeEventListener(\n \"Alchemy.FileUpload.Change\",\n this.#handleFileChange\n )\n }\n\n /**\n * Initialize the component with file uploads\n * @param {FileUpload[]} fileUploads\n */\n initialize(fileUploads = []) {\n this.#fileUploads = fileUploads\n this.fileCount = fileUploads.length\n }\n\n /**\n * cancel requests in all remaining uploads\n */\n cancel() {\n this.#activeUploads().forEach((upload) => {\n upload.cancel()\n })\n this.#setupCloseButton()\n }\n\n /**\n * a complete hook to allow the uploader to react and trigger an event\n * it would be possible to trigger the event here, but the dispatching would happen\n * in the scope of that component and can't be cached o uploader - component level\n */\n onComplete(_status) {}\n\n /**\n * get all active upload components\n * @returns {FileUpload[]}\n */\n #activeUploads() {\n return this.#fileUploads.filter((upload) => upload.active)\n }\n\n /**\n * replace cancel button to be the close button\n */\n #setupCloseButton() {\n this.#buttonLabel = translate(\"Close\")\n this.#actionButton.ariaLabel = this.#buttonLabel\n this.#actionButton.parentElement.content = this.#buttonLabel // update tooltip content\n }\n\n /**\n * @param {string} field\n * @returns {number}\n */\n #sumFileProgresses(field) {\n return this.#activeUploads().reduce(\n (accumulator, upload) => upload[field] + accumulator,\n 0\n )\n }\n\n /**\n * don't render the whole element new, because it would prevent selecting buttons\n */\n #updateView() {\n const status = this.status\n this.className = status\n\n // update progress bar\n this.progressElement.value = this.totalProgress\n this.progressElement.toggleAttribute(\n \"indeterminate\",\n status === \"upload-finished\"\n )\n\n // show progress in file size and percentage\n this.querySelector(`.overall-progress-value > span`).textContent =\n this.overallProgressValue\n this.querySelector(`.overall-upload-value`).textContent =\n this.overallUploadSize\n\n if (this.finished) {\n this.#setupCloseButton()\n this.onComplete(status)\n } else {\n this.visible = true\n }\n }\n\n /**\n * @returns {boolean}\n */\n get finished() {\n return this.#activeUploads().every((entry) => entry.finished)\n }\n\n /**\n * @returns {string}\n */\n get overallUploadSize() {\n const uploadedFileCount = this.#activeUploads().filter(\n (fileProgress) => fileProgress.value >= 100\n ).length\n const overallProgressValue = `${\n this.totalProgress\n }% (${uploadedFileCount} / ${this.#activeUploads().length})`\n\n return `${formatFileSize(\n this.#sumFileProgresses(\"progressEventLoaded\")\n )} / ${formatFileSize(this.#sumFileProgresses(\"progressEventTotal\"))}`\n }\n\n /**\n * @returns {string}\n */\n get overallProgressValue() {\n const uploadedFileCount = this.#activeUploads().filter(\n (fileProgress) => fileProgress.value >= 100\n ).length\n return `${this.totalProgress}% (${uploadedFileCount} / ${\n this.#activeUploads().length\n })`\n }\n\n /**\n * @returns {HTMLProgressElement|undefined}\n */\n get progressElement() {\n return this.querySelector(\"sl-progress-bar\")\n }\n\n /**\n * get status of file progresses and accumulate the overall status\n * @returns {string}\n */\n get status() {\n const uploadsStatuses = this.#activeUploads().map(\n (upload) => upload.className\n )\n\n // mark as failed, if any upload failed\n if (uploadsStatuses.includes(\"failed\")) {\n return \"failed\"\n }\n\n // no active upload means that every upload was canceled\n if (uploadsStatuses.length === 0) {\n return \"canceled\"\n }\n\n // all uploads are successful or upload-finished or in-progress\n if (uploadsStatuses.every((entry) => entry === uploadsStatuses[0])) {\n return uploadsStatuses[0]\n }\n\n return \"in-progress\"\n }\n\n /**\n * @returns {number}\n */\n get totalProgress() {\n const totalSize = this.#activeUploads().reduce(\n (accumulator, upload) => accumulator + upload.file.size,\n 0\n )\n let totalProgress = Math.ceil(\n this.#activeUploads().reduce((accumulator, upload) => {\n const weight = upload.file.size / totalSize\n return upload.value * weight + accumulator\n }, 0)\n )\n // prevent rounding errors\n if (totalProgress > 100) {\n totalProgress = 100\n }\n return totalProgress\n }\n\n /**\n * @returns {boolean}\n */\n get visible() {\n return this.#visible\n }\n\n /**\n * @param {boolean} visible\n */\n set visible(visible) {\n this.classList.toggle(\"visible\", visible)\n this.#visible = visible\n }\n}\n\ncustomElements.define(\"alchemy-upload-progress\", Progress)\n","/**\n * @typedef {object} PersistedFile\n * @property {string} name\n * @property {number} size\n */\nimport { Progress } from \"alchemy_admin/components/uploader/progress\"\nimport { FileUpload } from \"alchemy_admin/components/uploader/file_upload\"\nimport { translate } from \"alchemy_admin/i18n\"\nimport { readJSONScript } from \"alchemy_admin/utils/json_script\"\nimport { getToken } from \"alchemy_admin/utils/ajax\"\n\nexport class Uploader extends HTMLElement {\n #dropzoneElement = null\n #isDraggedOver = false\n\n connectedCallback() {\n this.fileInput.addEventListener(\"change\", this.#onFileInputChange)\n if (this.dropzone) {\n this.#setupDropZone()\n }\n this.addEventListener(\"Alchemy.upload.successful\", this)\n }\n\n disconnectedCallback() {\n this.fileInput?.removeEventListener(\"change\", this.#onFileInputChange)\n if (this.#dropzoneElement) {\n this.#dropzoneElement.removeEventListener(\n \"dragleave\",\n this.#onDropzoneDragleave\n )\n this.#dropzoneElement.removeEventListener(\"drop\", this.#onDropzoneDrop)\n this.#dropzoneElement.removeEventListener(\n \"dragover\",\n this.#onDropzoneDragover\n )\n this.#dropzoneElement = null\n }\n }\n\n handleEvent(evt) {\n switch (evt.type) {\n case \"Alchemy.upload.successful\":\n this.#handleUploadComplete()\n break\n }\n }\n\n #onFileInputChange = (event) => {\n this.uploadFiles(Array.from(event.target.files))\n }\n\n #toggleDropzoneClass = (enabled) => {\n if (this.#isDraggedOver !== enabled) {\n this.#isDraggedOver = enabled\n this.#dropzoneElement.classList.toggle(\"dragover\")\n }\n }\n\n #onDropzoneDragleave = () => this.#toggleDropzoneClass(false)\n\n #onDropzoneDrop = async (event) => {\n event.preventDefault()\n this.#toggleDropzoneClass(false)\n\n const files = [...event.dataTransfer.items].map((item) => item.getAsFile())\n\n this.uploadFiles(files)\n }\n\n #onDropzoneDragover = (event) => {\n event.preventDefault() // dragover has to be disabled to use the custom drop event\n this.#toggleDropzoneClass(true)\n }\n\n #handleUploadComplete() {\n setTimeout(() => {\n const url = this.redirectUrl\n const turboFrame = this.closest(\"turbo-frame\")\n this.uploadProgress.visible = false\n\n if (!url) return\n\n if (turboFrame) {\n turboFrame.setAttribute(\"src\", url)\n turboFrame.reload()\n } else {\n Turbo.visit(url)\n }\n }, 750)\n }\n\n /**\n * add dragover class to indicate, if the file is draggable\n * @private\n */\n #setupDropZone() {\n this.#dropzoneElement = document.querySelector(this.dropzone)\n if (!this.#dropzoneElement) return\n\n this.#dropzoneElement.addEventListener(\n \"dragleave\",\n this.#onDropzoneDragleave\n )\n this.#dropzoneElement.addEventListener(\"drop\", this.#onDropzoneDrop)\n this.#dropzoneElement.addEventListener(\"dragover\", this.#onDropzoneDragover)\n }\n\n /**\n * @param {File[]} files\n */\n uploadFiles(files) {\n // prepare file progress bars and server request\n let fileUploadCount = 0\n const config = readJSONScript(\"alchemy_uploader_defaults\")\n\n const fileUploads = files.map((file) => {\n const request = new XMLHttpRequest()\n const fileUpload = new FileUpload()\n fileUpload.initialize(file, request)\n\n if (config.upload_limit - 1 < fileUploadCount) {\n fileUpload.valid = false\n fileUpload.errorMessage = translate(\"Maximum number of files exceeded\")\n } else if (fileUpload.valid) {\n fileUploadCount++\n this.#submitFile(request, file)\n }\n\n return fileUpload\n })\n\n this.#createProgress(fileUploads)\n }\n\n /**\n * @param {XMLHttpRequest} request\n * @param {File} file\n * @private\n */\n #submitFile(request, file) {\n const form = this.querySelector(\"form\")\n const formData = new FormData(form)\n formData.set(this.fileInput.name, file)\n request.open(\"POST\", form.action)\n request.setRequestHeader(\"X-CSRF-Token\", getToken())\n request.setRequestHeader(\"X-Requested-With\", \"XMLHttpRequest\")\n request.setRequestHeader(\"Accept\", \"application/json\")\n request.send(formData)\n }\n\n /**\n * create (and maybe remove the old) progress bar - component\n * @param {FileUpload[]} fileUploads\n * @private\n */\n #createProgress(fileUploads) {\n if (this.uploadProgress) {\n this.uploadProgress.cancel()\n this.uploadProgress.remove()\n }\n this.uploadProgress = new Progress()\n this.uploadProgress.initialize(fileUploads)\n this.uploadProgress.setAttribute(\"layout\", this.layout)\n this.uploadProgress.onComplete = (status) => {\n this.dispatchEvent(\n new CustomEvent(`Alchemy.upload.${status}`, { bubbles: true })\n )\n }\n\n this.#progressContainer.append(this.uploadProgress)\n }\n\n // The dropzone doubles as the progress container so the upload indicators\n // render inside the archive (and inside dialogs). Falls back to the body for\n // uploaders without a dropzone (e.g. the attachment replace button).\n get #progressContainer() {\n return this.#dropzoneElement ?? document.body\n }\n\n get dropzone() {\n return this.getAttribute(\"dropzone\")\n }\n\n // Shape of the upload indicators. Defaults to \"row\" so uploads fit naturally\n // into resource table views; set layout=\"card\" for the thumbnail grid.\n get layout() {\n return this.getAttribute(\"layout\") || \"row\"\n }\n\n /**\n * @returns {HTMLInputElement}\n */\n get fileInput() {\n return this.querySelector(\"input[type='file']\")\n }\n\n get redirectUrl() {\n return this.getAttribute(\"redirect-url\")\n }\n}\n\ncustomElements.define(\"alchemy-uploader\", Uploader)\n","class Overlay extends HTMLElement {\n connectedCallback() {\n this.innerHTML = `\n <alchemy-spinner></alchemy-spinner>\n <div id=\"overlay_text_box\">\n <span id=\"overlay_text\">${this.getAttribute(\"text\") ?? \"\"}</span>\n </div>\n `\n }\n\n set show(value) {\n this.classList.toggle(\"visible\", value)\n }\n}\n\ncustomElements.define(\"alchemy-overlay\", Overlay)\n","import { patch } from \"alchemy_admin/utils/ajax\"\nimport { growl } from \"alchemy_admin/growler\"\nimport Spinner from \"alchemy_admin/spinner\"\nimport { routes } from \"alchemy_admin/routes\"\n\nconst BUTTON = \"BUTTON\"\nconst SPAN = \"SPAN\"\n\n/**\n * Custom element for page nodes in the sitemap tree\n * Handles folding/unfolding of page children\n */\nexport class AlchemyPageNode extends HTMLElement {\n connectedCallback() {\n this.pageId = this.getAttribute(\"page-id\")\n this.folded = this.hasAttribute(\"folded\")\n\n this.folderButton?.addEventListener(\"click\", this)\n }\n\n disconnectedCallback() {\n this.folderButton?.removeEventListener(\"click\", this)\n }\n\n async handleEvent(event) {\n if (event.type === \"click\") {\n await this.handleFolderClick(event)\n }\n }\n\n async handleFolderClick(event) {\n event.preventDefault()\n event.stopPropagation()\n\n const folderButton = event.currentTarget\n folderButton.innerHTML = \"\"\n const spinner = new Spinner(\"small\")\n spinner.spin(folderButton)\n\n try {\n await patch(\n routes.fold_admin_page_path(this.pageId),\n null,\n \"text/vnd.turbo-stream.html\"\n )\n\n this.folded = !this.folded\n this.toggleAttribute(\"folded\", this.folded)\n this.toggleChildren()\n this.updateFolderIcon()\n } catch (error) {\n growl(error.message || error, \"error\")\n this.updateFolderIcon()\n } finally {\n spinner.stop()\n }\n }\n\n toggleChildren() {\n const childrenContainer = this.querySelector(\n `#page_${this.pageId}_children`\n )\n if (childrenContainer) {\n childrenContainer.classList.toggle(\"hidden\", this.folded)\n }\n }\n\n updateFolderIcon() {\n if (this.folderButton) {\n const iconName = this.folded ? \"arrow-right-s\" : \"arrow-down-s\"\n this.folderButton.innerHTML = `<alchemy-icon name=\"${iconName}\"></alchemy-icon>`\n }\n }\n\n /**\n * Updates the folder button state based on whether the node has children\n * Converts between button and span as needed\n */\n updateFolderButton() {\n const folderElement = this.querySelector(\".page_folder\")\n if (!folderElement) return\n\n const shouldShowButton = this.hasChildren || this.folded\n\n if (shouldShowButton && folderElement.tagName === SPAN) {\n // Convert span to button with icon\n const iconName = this.folded ? \"arrow-right-s\" : \"arrow-down-s\"\n folderElement.outerHTML = `<button class=\"page_folder icon_button\">\n <alchemy-icon name=\"${iconName}\"></alchemy-icon>\n </button>`\n\n // Re-attach event listener to the new button element\n this.folderButton?.addEventListener(\"click\", this)\n } else if (!shouldShowButton && folderElement.tagName === BUTTON) {\n // Convert button to empty span (no children and not folded)\n folderElement.outerHTML = '<span class=\"page_folder\"></span>'\n } else if (shouldShowButton && folderElement.tagName === BUTTON) {\n // Button exists, just update the icon direction\n this.updateFolderIcon()\n }\n }\n\n get hasChildren() {\n const childrenContainer = this.querySelector(\n `#page_${this.pageId}_children`\n )\n if (!childrenContainer) return false\n\n return (\n childrenContainer.querySelectorAll(\":scope > alchemy-page-node\").length >\n 0\n )\n }\n\n get folderButton() {\n return this.querySelector(\"button.page_folder\")\n }\n}\n\ncustomElements.define(\"alchemy-page-node\", AlchemyPageNode)\n","// Handles the page publication date fields\nexport class PagePublicationFields extends HTMLElement {\n connectedCallback() {\n this.publicField?.addEventListener(\"click\", this.#onClick)\n }\n\n disconnectedCallback() {\n this.publicField?.removeEventListener(\"click\", this.#onClick)\n }\n\n #onClick = (evt) => {\n const checkbox = evt.target\n const date = new Date()\n const now = new Date(\n date.getTime() - date.getTimezoneOffset() * 60000\n ).toISOString()\n\n if (checkbox.checked) {\n this.publicationDateFields.classList.remove(\"hidden\")\n this.publicOnPicker.value = now.substring(0, now.indexOf(\"T\") + 6)\n } else {\n this.publicationDateFields.classList.add(\"hidden\")\n this.publicOnPicker.value = \"\"\n }\n this.publicUntilPicker.value = \"\"\n }\n\n get publicField() {\n return this.querySelector(\"#page_public\")\n }\n\n get publicOnPicker() {\n return this.querySelector(\"input#page_public_on\")\n }\n\n get publicUntilPicker() {\n return this.querySelector(\"input#page_public_until\")\n }\n\n get publicationDateFields() {\n return this.querySelector(\".page-publication-date-fields\")\n }\n}\n\ncustomElements.define(\"alchemy-page-publication-fields\", PagePublicationFields)\n","import { RemoteSelect } from \"alchemy_admin/components/remote_select\"\n\nexport class PageSelect extends RemoteSelect {\n get pageId() {\n return this.selection ? JSON.parse(this.selection)[\"id\"] : undefined\n }\n\n _searchQuery(term, page) {\n return {\n q: {\n name_i_cont: term,\n ...JSON.parse(this.queryParams)\n },\n page: page\n }\n }\n\n _parseResponse(response) {\n const meta = response.meta\n return {\n results: response.pages,\n more: meta.page * meta.per_page < meta.total_count\n }\n }\n\n _entry(page, term) {\n return {\n icon: \"file-3\",\n primary: this._hightlightTerm(page.name, term),\n aside: page.site?.name,\n secondary: { text: page.url_path, truncate: \"head\" },\n secondaryAside: { badge: page.language_code }\n }\n }\n\n _selectedEntry(page) {\n return {\n icon: \"file-3\",\n primary: page.text || page.name,\n secondary: { text: page.url_path, truncate: \"head\" }\n }\n }\n}\n\ncustomElements.define(\"alchemy-page-select\", PageSelect)\n","import { openDialog } from \"alchemy_admin/dialog\"\n\n// Multiple picture select handler for the picture archive.\nexport class PictureArchive extends HTMLElement {\n #selectAllButton\n\n connectedCallback() {\n this.addEventListener(\"change\", this.#onCheckboxChange)\n this.addEventListener(\"click\", this.#onEditMultipleClick)\n // The select all button lives in the toolbar, outside of this element.\n this.#selectAllButton = document.querySelector(\"#select_all_pictures\")\n this.#selectAllButton?.addEventListener(\"click\", this.#onSelectAllClick)\n }\n\n disconnectedCallback() {\n this.removeEventListener(\"change\", this.#onCheckboxChange)\n this.removeEventListener(\"click\", this.#onEditMultipleClick)\n this.#selectAllButton?.removeEventListener(\"click\", this.#onSelectAllClick)\n }\n\n #onSelectAllClick = (event) => {\n event.preventDefault()\n\n this.#selectAllButton.classList.toggle(\"active\")\n\n const state = this.#selectAllButton.classList.contains(\"active\")\n\n this.#toggleCheckboxes(state)\n\n this.selectedItemTools.classList.toggle(\"hidden\", !state)\n }\n\n // make the item toolbar visible and show the checkbox also if it is not hovered anymore\n #onCheckboxChange = (event) => {\n const checkbox = event.target.closest(\".picture_tool.select input\")\n\n if (!checkbox) {\n return\n }\n\n this.selectedItemTools.classList.toggle(\n \"hidden\",\n this.checkedInputs.length === 0\n )\n checkbox.parentElement.classList.toggle(\"visible\", checkbox.checked)\n }\n\n // open the edit view in a dialog modal\n #onEditMultipleClick = (event) => {\n const link = event.target.closest(\"a#edit_multiple_pictures\")\n\n if (!link) {\n return\n }\n\n event.preventDefault()\n\n openDialog(this.#editMultiplePicturesUrl(link.href), {\n title: link.title,\n size: \"500x242\"\n })\n }\n\n #toggleCheckboxes(state) {\n this.querySelectorAll(\n \".picture_tool.select input[type='checkbox']\"\n ).forEach((checkbox) => {\n checkbox.checked = state\n checkbox.closest(\".picture_thumbnail\").classList.toggle(\"active\", state)\n })\n }\n\n #editMultiplePicturesUrl(href) {\n const url = new URL(href)\n\n this.checkedInputs.forEach((entry) =>\n url.searchParams.append(entry.name, entry.value)\n )\n\n return url.toString()\n }\n\n get checkedInputs() {\n return this.querySelectorAll(\"input:checked\")\n }\n\n get selectedItemTools() {\n return this.querySelector(\".selected_item_tools\")\n }\n}\n\ncustomElements.define(\"alchemy-picture-archive\", PictureArchive)\n","class PictureDescriptionSelect extends HTMLElement {\n connectedCallback() {\n this.addEventListener(\"change\", this)\n }\n\n disconnectedCallback() {\n this.removeEventListener(\"change\", this)\n }\n\n handleEvent(event) {\n switch (event.type) {\n case \"change\":\n this.onChange()\n break\n }\n }\n\n onChange() {\n const url = new URL(this.getAttribute(\"url\"))\n const select = this.querySelector(\"select\")\n url.searchParams.set(\"language_id\", select.value)\n Turbo.visit(url, { frame: \"picture_descriptions\" })\n }\n}\n\ncustomElements.define(\n \"alchemy-picture-description-select\",\n PictureDescriptionSelect\n)\n","import debounce from \"alchemy_admin/utils/debounce\"\nimport max from \"alchemy_admin/utils/max\"\nimport { get } from \"alchemy_admin/utils/ajax\"\nimport { growl } from \"alchemy_admin/growler\"\nimport { routes } from \"alchemy_admin/routes\"\n\nconst UPDATE_DELAY = 125\nconst IMAGE_PLACEHOLDER = '<alchemy-icon name=\"image\" size=\"xl\"></alchemy-icon>'\nconst THUMBNAIL_SIZE = \"160x120\"\n\nexport class PictureEditor extends HTMLElement {\n connectedCallback() {\n this.cropFromField = this.querySelector(\"[data-crop-from]\")\n this.cropSizeField = this.querySelector(\"[data-crop-size]\")\n this.pictureIdField = this.querySelector(\"[data-picture-id]\")\n this.targetSizeField = this.querySelector(\"[data-target-size]\")\n this.imageCropperField = this.querySelector(\"[data-image-cropper]\")\n this.image = this.querySelector(\"img\")\n this.pictureThumbnail = this.querySelector(\"alchemy-picture-thumbnail\")\n this.deleteButton = this.querySelector(\".picture_tool.delete\")\n this.cropLink = this.querySelector(\".crop_link\")\n\n this.targetSize = this.targetSizeField.dataset.targetSize\n this.pictureId = this.pictureIdField.value\n\n // The mutation observer is observing multiple fields that all get updated\n // simultaneously. We only want to update the image once, so we debounce.\n this.update = debounce(() => {\n this.updateImage()\n this.updateCropLink()\n }, UPDATE_DELAY)\n\n this.deleteButton?.addEventListener(\"click\", this.removeImage)\n\n this.observer = new MutationObserver(this.mutationCallback)\n this.observer.observe(this.cropFromField, { attributes: true })\n this.observer.observe(this.cropSizeField, { attributes: true })\n this.observer.observe(this.pictureIdField, { attributes: true })\n }\n\n disconnectedCallback() {\n this.observer.disconnect()\n }\n\n mutationCallback = (mutationsList) => {\n for (const mutation of mutationsList) {\n if (\"pictureId\" in mutation.target.dataset) {\n this.cropFromField.value = \"\"\n this.cropSizeField.value = \"\"\n this.pictureId = mutation.target.value\n }\n this.update()\n }\n }\n\n updateImage() {\n if (!this.pictureId) return\n\n this.pictureThumbnail.loading = true\n get(routes.url_admin_picture_path(this.pictureId), {\n crop: this.imageCropperEnabled,\n crop_from: this.cropFrom,\n crop_size: this.cropSize,\n flatten: true,\n size: THUMBNAIL_SIZE\n })\n .then(({ data }) => {\n this.pictureThumbnail.src = data.url\n this.pictureThumbnail.image.alt = data.alt\n this.pictureThumbnail.image.title = data.title\n this.setElementDirty()\n })\n .catch((error) => {\n console.error(error.message || error)\n growl(error.message || error, \"error\")\n })\n }\n\n removeImage = () => {\n this.pictureThumbnail.innerHTML = IMAGE_PLACEHOLDER\n this.pictureIdField.value = \"\"\n this.image = null\n this.cropLink.classList.add(\"disabled\")\n this.setElementDirty()\n }\n\n setElementDirty() {\n this.closest(\".element-editor\").setDirty(this)\n }\n\n updateCropLink() {\n if (!this.pictureId || !this.imageCropperEnabled) return\n\n this.cropLink.classList.remove(\"disabled\")\n\n if (this.cropLink.href.match(/(picture_id=)\\d+/)) {\n this.cropLink.href = this.cropLink.href.replace(\n /(picture_id=)\\d+/,\n \"$1\" + this.pictureId\n )\n } else {\n this.cropLink.href = this.cropLink.href + `&picture_id=${this.pictureId}`\n }\n }\n\n get cropFrom() {\n if (this.cropFromField.value === \"\") {\n return this.defaultCropFrom.join(\"x\")\n }\n return this.cropFromField.value\n }\n\n get cropSize() {\n if (this.cropSizeField.value === \"\") {\n return this.defaultCropSize.join(\"x\")\n }\n return this.cropSizeField.value\n }\n\n get defaultCropSize() {\n if (!this.imageCropperEnabled) return []\n\n const mask = this.targetSize.split(\"x\").map((n) => parseInt(n))\n const zoom = max(\n mask[0] / this.imageFileWidth,\n mask[1] / this.imageFileHeight\n )\n\n return [Math.round(mask[0] / zoom), Math.round(mask[1] / zoom)]\n }\n\n get defaultCropFrom() {\n if (!this.imageCropperEnabled) return []\n\n const dimensions = this.defaultCropSize\n\n return [\n Math.round((this.imageFileWidth - dimensions[0]) / 2),\n Math.round((this.imageFileHeight - dimensions[1]) / 2)\n ]\n }\n\n get imageFileWidth() {\n return parseInt(this.pictureIdField.dataset.imageFileWidth)\n }\n\n get imageFileHeight() {\n return parseInt(this.pictureIdField.dataset.imageFileHeight)\n }\n\n get imageCropperEnabled() {\n return this.targetSizeField.dataset.imageCropper === \"true\"\n }\n}\n\ncustomElements.define(\"alchemy-picture-editor\", PictureEditor)\n","export default function (func, delay) {\n let timeout\n\n return function (...args) {\n const that = this\n\n clearTimeout(timeout)\n timeout = setTimeout(() => func.apply(that, args), delay)\n }\n}\n","export default function (a, b) {\n return a >= b ? a : b\n}\n","// Shows spinner while loading images and\n// fades the image after its been loaded\n\nimport Spinner from \"alchemy_admin/spinner\"\n\nexport default class PictureThumbnail extends HTMLElement {\n constructor() {\n super()\n\n this.spinner = new Spinner(\"small\")\n }\n\n handleEvent(evt) {\n switch (evt.type) {\n case \"load\":\n this.#onLoaded()\n break\n case \"error\":\n this.#onError(evt)\n break\n default:\n break\n }\n }\n\n connectedCallback() {\n this.classList.add(\"thumbnail_background\")\n if (this.src && !this.image) {\n this.start()\n }\n this.#setImage()\n }\n\n disconnectedCallback() {\n this.image?.removeEventListener(\"load\", this)\n this.image?.removeEventListener(\"error\", this)\n this.stop()\n }\n\n createImage(src = this.src, alt = this.name) {\n this.image = new Image()\n this.image.src = src\n if (alt) {\n this.image.alt = alt\n }\n }\n\n start(src) {\n this.createImage(src)\n this.image.addEventListener(\"load\", this)\n this.image.addEventListener(\"error\", this)\n this.load()\n }\n\n load() {\n if (this.image?.complete) {\n return\n }\n this.setAttribute(\"loading\", \"loading\")\n this.innerHTML = \"\"\n this.spinner.spin(this)\n }\n\n stop() {\n this.classList.remove(\"loading\")\n this.spinner.stop()\n }\n\n #onLoaded() {\n this.spinner.stop()\n this.removeAttribute(\"loading\")\n }\n\n #onError(evt) {\n const message = `Could not load ${this.image.src}`\n const hoist = this.closest(\".ingredient-editor\")\n this.spinner.stop()\n this.innerHTML = `\n <sl-tooltip content=\"${message}\" ${hoist ? \"hoist\" : \"\"}>\n <alchemy-icon name=\"alert\" class=\"error\"></alchemy-icon>\n </sl-tooltip>\n `\n console.error(message, evt)\n }\n\n #setImage() {\n if (this.image?.complete) {\n this.replaceChildren(this.image)\n } else if (this.image) {\n this.append(this.image)\n }\n }\n\n set loading(value) {\n value ? this.load() : this.stop()\n }\n\n set src(src) {\n this.start(src)\n this.#setImage()\n }\n\n get name() {\n return this.getAttribute(\"name\")\n }\n\n get src() {\n return this.getAttribute(\"src\")\n }\n}\n\ncustomElements.define(\"alchemy-picture-thumbnail\", PictureThumbnail)\n","class PublishPageButton extends HTMLElement {\n connectedCallback() {\n this.addEventListener(\"submit\", this)\n document.addEventListener(\"alchemy:page-dirty\", this)\n }\n\n disconnectedCallback() {\n this.removeEventListener(\"submit\", this)\n document.removeEventListener(\"alchemy:page-dirty\", this)\n }\n\n handleEvent(event) {\n switch (event.type) {\n case \"alchemy:page-dirty\":\n this.markDirty(event.detail)\n break\n case \"submit\":\n this.button.loading = true\n break\n }\n }\n\n markDirty(detail) {\n this.button.variant = \"primary\"\n this.button.disabled = false\n this.tooltip.content = detail.tooltip\n }\n\n get button() {\n return this.querySelector(\"sl-button\")\n }\n\n get tooltip() {\n return this.querySelector(\"sl-tooltip\")\n }\n}\n\ncustomElements.define(\"alchemy-publish-page-button\", PublishPageButton)\n","import Sortable from \"sortablejs\"\nimport { growl } from \"alchemy_admin/growler\"\nimport { patch } from \"alchemy_admin/utils/ajax\"\nimport { translate } from \"alchemy_admin/i18n\"\nimport pleaseWaitOverlay from \"alchemy_admin/please_wait_overlay\"\nimport { routes } from \"alchemy_admin/routes\"\n\n/**\n * Custom element for the sitemap container\n * Handles search/filter functionality and drag-and-drop sorting\n */\nexport class AlchemySitemap extends HTMLElement {\n connectedCallback() {\n this.searchInput = document.querySelector(\".search_input_field\")\n this.clearButton = document.querySelector(\"#search_field_clear\")\n this.resultCounter = document.querySelector(\"#page_filter_result\")\n\n this.setupSearch()\n\n // Wait for child custom elements to be defined before setting up sortables\n requestAnimationFrame(() => {\n this.setupSortables()\n })\n\n // Set up MutationObserver to re-initialize sortables when children containers are added\n this.observer = new MutationObserver((mutations) => {\n mutations.forEach((mutation) => {\n mutation.addedNodes.forEach((node) => {\n if (node.nodeType !== Node.ELEMENT_NODE) return\n\n // If the added node itself is a children container, initialize it\n if (node.classList?.contains(\"children\")) {\n this.setupSortable(node)\n }\n\n // Also check for children containers nested within the added node\n // This handles cases where a parent element with nested children is added at once\n node\n .querySelectorAll(\".children\")\n .forEach((el) => this.setupSortable(el))\n })\n })\n })\n\n // Observe the sitemap for added nodes\n this.observer.observe(this, {\n childList: true,\n subtree: true\n })\n }\n\n disconnectedCallback() {\n this.teardownSearch()\n this.observer?.disconnect()\n }\n\n setupSearch() {\n this.searchInput?.addEventListener(\"input\", this)\n this.clearButton?.addEventListener(\"click\", this)\n }\n\n teardownSearch() {\n this.searchInput?.removeEventListener(\"input\", this)\n this.clearButton?.removeEventListener(\"click\", this)\n }\n\n handleEvent(event) {\n if (event.type === \"input\" && event.target === this.searchInput) {\n this.handleSearch(event)\n } else if (event.type === \"click\" && event.target === this.clearButton) {\n this.handleClearSearch(event)\n }\n }\n\n handleSearch(event) {\n const term = event.target.value.toLowerCase().trim()\n\n if (term === \"\") {\n this.clearFilter()\n return\n }\n\n this.filterPages(term)\n }\n\n filterPages(term) {\n const allPages = this.querySelectorAll(\".sitemap_page\")\n let matchCount = 0\n let firstMatch = null\n\n allPages.forEach((pageElement) => {\n const pageName = pageElement.getAttribute(\"name\") || \"\"\n\n if (pageName.toLowerCase().includes(term)) {\n pageElement.classList.add(\"highlight\")\n pageElement.classList.remove(\"no-match\")\n matchCount++\n if (!firstMatch) firstMatch = pageElement\n } else {\n pageElement.classList.remove(\"highlight\")\n pageElement.classList.add(\"no-match\")\n }\n })\n\n // Update result counter\n\n if (matchCount === 1) {\n this.resultCounter.textContent = `1 ${translate(\"page_found\")}`\n this.resultCounter.style.display = \"block\"\n } else if (matchCount > 1) {\n this.resultCounter.textContent = `${matchCount} ${translate(\"pages_found\")}`\n this.resultCounter.style.display = \"block\"\n } else {\n this.resultCounter.style.display = \"none\"\n }\n\n // Scroll first match into view\n if (firstMatch) {\n firstMatch.scrollIntoView({ behavior: \"smooth\", block: \"center\" })\n }\n }\n\n clearFilter() {\n const allPages = this.querySelectorAll(\".sitemap_page\")\n allPages.forEach((pageElement) => {\n pageElement.classList.remove(\"highlight\", \"no-match\")\n })\n\n this.resultCounter.style.display = \"none\"\n }\n\n handleClearSearch(event) {\n event.preventDefault()\n this.searchInput.value = \"\"\n this.clearFilter()\n }\n\n setupSortable(container) {\n new Sortable(container, {\n group: \"pages\",\n animation: 150,\n fallbackOnBody: true,\n swapThreshold: 0.65,\n handle: \".page-icon.handle\",\n draggable: \"alchemy-page-node\",\n onEnd: (evt) => this.handleSort(evt)\n })\n }\n\n setupSortables() {\n const sortables = this.querySelectorAll(\".children\")\n sortables.forEach((el) => this.setupSortable(el))\n }\n\n async handleSort(evt) {\n // Only proceed if actually moved to different position/container\n if (evt.from === evt.to && evt.oldIndex === evt.newIndex) {\n return\n }\n\n // evt.item is the <alchemy-page-node> element being dragged\n const pageNode = evt.item\n const pageId = pageNode.pageId\n const url = routes.move_admin_page_path(pageId)\n const data = {\n target_parent_id: evt.to.dataset.parentId,\n new_position: evt.newIndex\n }\n\n pleaseWaitOverlay(true)\n\n try {\n const response = await patch(url, data)\n const pageData = await response.data\n\n // Update the URL path of the moved page\n const pageEl = pageNode.querySelector(`#page_${pageId}`)\n if (pageEl) {\n const urlPathEl = pageEl.querySelector(\".sitemap_url\")\n if (urlPathEl && pageData.url_path) {\n urlPathEl.textContent = pageData.url_path\n }\n }\n\n // Update folder icons for affected parent pages\n this.updateFolderIcons(evt.from, evt.to)\n\n growl(translate(\"Successfully moved page\"))\n } catch (error) {\n growl(error.message || error, \"error\")\n // Revert the DOM change by reloading on error\n window.location.reload()\n } finally {\n pleaseWaitOverlay(false)\n }\n }\n\n updateFolderIcons(fromContainer, toContainer) {\n // Update folder icon for source parent (might now have no children)\n const fromParent = fromContainer.closest(\"alchemy-page-node\")\n fromParent?.updateFolderButton()\n\n // Update folder icon for destination parent (now definitely has children)\n if (fromContainer !== toContainer) {\n const toParent = toContainer.closest(\"alchemy-page-node\")\n toParent?.updateFolderButton()\n }\n }\n}\n\ncustomElements.define(\"alchemy-sitemap\", AlchemySitemap)\n","import Sortable from \"sortablejs\"\nimport { growl } from \"alchemy_admin/growler\"\nimport { post } from \"alchemy_admin/utils/ajax\"\nimport { reloadPreview } from \"alchemy_admin/components/preview_window\"\nimport { dispatchPageDirtyEvent } from \"alchemy_admin/utils/dispatch_page_dirty_event\"\nimport { routes } from \"alchemy_admin/routes\"\n\nconst SORTABLE_OPTIONS = {\n draggable: \".element-editor\",\n handle: \".element-handle.draggable\",\n ghostClass: \"dragged\",\n animation: 150,\n swapThreshold: 0.65,\n easing: \"cubic-bezier(1, 0, 0, 1)\"\n}\n\nfunction onStart(event) {\n const name = event.item.dataset.elementName\n document\n .querySelectorAll(`[data-droppable-elements~=\"${name}\"]`)\n .forEach((dropzone) => dropzone.classList.add(\"droppable-elements\"))\n}\n\nfunction onSort(event) {\n const item = event.item\n const parentElement = event.to.parentElement.closest(\".element-editor\")\n const params = {\n element_id: item.dataset.elementId,\n position: event.newIndex + 1\n }\n\n if (parentElement) {\n params.parent_element_id = parentElement.dataset.elementId\n }\n\n // Only send the request if the item was moved to a different container\n // or sorted in the same list. Not on the old list in order to avoid incrementing\n // the position of the other elements.\n if (event.target === event.to) {\n post(routes.order_admin_elements_path, params).then((response) => {\n const data = response.data\n growl(data.message)\n if (data.pageHasUnpublishedChanges) {\n dispatchPageDirtyEvent(data)\n }\n reloadPreview()\n item.updateTitle(data.preview_text)\n })\n }\n}\n\nfunction onEnd() {\n const dropzones = document.querySelectorAll(\"[data-droppable-elements]\")\n dropzones.forEach((dropzone) =>\n dropzone.classList.remove(\"droppable-elements\")\n )\n}\n\nclass SortableElements extends HTMLElement {\n connectedCallback() {\n const group = {\n name: this.dataset.elementName,\n put(to, _from, item) {\n return to.el.dataset.droppableElements\n .split(\" \")\n .includes(item.dataset.elementName)\n }\n }\n new Sortable(this, {\n ...SORTABLE_OPTIONS,\n onStart,\n onSort,\n onEnd,\n group\n })\n }\n}\n\ncustomElements.define(\"alchemy-sortable-elements\", SortableElements)\n","class Spinner extends HTMLElement {\n connectedCallback() {\n this.className = `spinner spinner--${this.size}`\n this.innerHTML = `\n <svg width=\"100%\" viewBox=\"0 0 28 28\" style=\"--spinner-color: ${this.color}\">\n <path\n class=\"hex1\"\n d=\"M5.938,18.07l-5.878-5.9l2.154-8.058l8.024-2.161l5.87,5.9l-2.144,8.058L5.938,18.07z\"\n />\n <path\n class=\"hex2\"\n d=\"M19.686,20.785l-4.731-4.754l1.725-6.487l6.468-1.742l4.733,4.754l-1.734,6.487L19.686,20.785z\"\n />\n <path\n class=\"hex3\"\n d=\"M11.708,26.294l-3.47-3.485l1.276-4.758l4.74-1.276l3.468,3.485l-1.265,4.758L11.708,26.294z\"\n />\n </svg>\n `\n }\n\n get size() {\n return this.getAttribute(\"size\") || \"medium\"\n }\n\n get color() {\n return this.getAttribute(\"color\") || \"currentColor\"\n }\n}\n\ncustomElements.define(\"alchemy-spinner\", Spinner)\n","import TomSelect from \"tom-select\"\nimport \"alchemy_admin/components/icon\"\nimport { get } from \"alchemy_admin/utils/ajax\"\nimport { growl } from \"alchemy_admin/growler\"\nimport {\n createDropdownPositioning,\n dropdownMessages,\n focusTomSelect,\n removeButton,\n teardownTomSelect\n} from \"alchemy_admin/utils/tom_select\"\n\nexport class TagsAutocomplete extends HTMLElement {\n #tomSelect = null\n\n connectedCallback() {\n this.classList.add(\"autocomplete_tag_list\")\n // Reuse the shared Tom Select styling by tagging the source input, whose\n // class Tom Select copies onto the generated wrapper.\n this.input.classList.add(\"alchemy_selectbox\")\n this.#tomSelect = new TomSelect(this.input, this.settings)\n }\n\n disconnectedCallback() {\n teardownTomSelect(this.#tomSelect)\n this.#tomSelect = null\n }\n\n focus() {\n focusTomSelect(this.#tomSelect, () => super.focus())\n }\n\n get input() {\n return this.getElementsByTagName(\"input\")[0]\n }\n\n get settings() {\n return {\n plugins: {\n remove_button: removeButton()\n },\n // The autocomplete endpoint returns each tag as `{ id, text }`.\n valueField: \"id\",\n // Tags are entered and stored as a comma separated list.\n delimiter: \",\",\n // Allow free tagging.\n create: true,\n // Hide the \"Add …\" create option while suggestions are still loading, so\n // it doesn't flash and then disappear once a matching tag arrives from\n // the server. When idle, keep Tom Select's default of not offering to\n // create a tag that already exists.\n createFilter(input) {\n if (this.loading) return false\n return this.settings.duplicates || !this.options[input]\n },\n // Do not keep a removed tag around as a lingering suggestion.\n persist: false,\n // Clear the typed text once a tag is added, so it doesn't linger in the\n // input after selecting an existing tag.\n clearAfterSelect: true,\n // Show every matching tag returned by the server.\n maxOptions: null,\n openOnFocus: false,\n // Apply the placeholder set on the wrapper element.\n placeholder: this.getAttribute(\"placeholder\"),\n // Load matching tags from the server as the user types.\n load: (query, callback) => {\n get(this.getAttribute(\"url\"), { term: query })\n .then((response) => callback(response.data))\n .catch((error) => {\n growl(error.message || error, \"error\")\n // Tom Select stays in loading state until the callback runs.\n callback()\n })\n },\n ...createDropdownPositioning(),\n render: {\n ...dropdownMessages\n }\n }\n }\n}\n\ncustomElements.define(\"alchemy-tags-autocomplete\", TagsAutocomplete)\n","import \"tinymce\"\nimport { currentLocale } from \"alchemy_admin/i18n\"\nimport { readJSONScript } from \"alchemy_admin/utils/json_script\"\n\nconst DARK_THEME = \"alchemy-dark\"\nconst LIGHT_THEME = \"alchemy\"\n\nexport class Tinymce extends HTMLElement {\n #min_height = null\n\n /**\n * the observer will initialize Tinymce if the textarea becomes visible\n */\n connectedCallback() {\n this.className = \"tinymce_container\"\n\n // Append the spinner if not already present (idempotent on reconnect/clone)\n if (!this.querySelector(\":scope > alchemy-spinner\")) {\n this.insertAdjacentHTML(\n \"beforeend\",\n `<alchemy-spinner size=\"small\"></alchemy-spinner>`\n )\n }\n\n // hide the textarea until TinyMCE is ready to show the editor\n this.style.minHeight = `${this.minHeight}px`\n this.editor.style.display = \"none\"\n\n const observerCallback = (entries, observer) => {\n entries.forEach((entry) => {\n if (entry.intersectionRatio > 0) {\n this._initTinymceEditor()\n // disable observer after the Tinymce was initialized\n observer.unobserve(entry.target)\n }\n })\n }\n\n const options = {\n root: document.getElementById(\"element_area\"),\n rootMargin: \"0px\",\n threshold: [0.05]\n }\n\n this.tinymceIntersectionObserver = new IntersectionObserver(\n observerCallback,\n options\n )\n this.tinymceIntersectionObserver.observe(this)\n\n // Set up theme change listener\n this._setupThemeChangeListener()\n }\n\n /**\n * disconnect intersection observer and remove Tinymce editor if the web components get destroyed\n */\n disconnectedCallback() {\n this.tinymceIntersectionObserver?.disconnect()\n\n // Remove theme change listener\n this._removeThemeChangeListener()\n\n tinymce.get(this.editorId)?.remove(this.editorId)\n }\n\n /**\n * initialize Richtext area after the Intersection observer triggered\n * @private\n */\n _initTinymceEditor() {\n tinymce.init(this.configuration).then((editors) => {\n editors.forEach((editor) => this._setupEditor(editor))\n })\n }\n\n /**\n * Setup editor after initialization\n * @param {Object} editor - The TinyMCE editor instance\n * @private\n */\n _setupEditor(editor) {\n // mark the editor container as visible\n // without these correction the editor remains hidden\n // after a drag and drop action\n editor.show()\n\n // remove the spinner after the Tinymce initialized (only on first init)\n const spinner = this.getElementsByTagName(\"alchemy-spinner\")[0]\n if (spinner) {\n spinner.remove()\n }\n\n // event listener to mark the editor as dirty\n if (this.elementEditor) {\n editor.on(\"dirty\", (evt) => {\n this.elementEditor.setDirty(evt.target.editorContainer)\n })\n editor.on(\"click\", () => this.elementEditor.onClickElement(false))\n }\n }\n\n /**\n * Set up listener for OS theme changes\n * @private\n */\n _setupThemeChangeListener() {\n this.darkModeMediaQuery = window.matchMedia(\"(prefers-color-scheme: dark)\")\n this.themeChangeHandler = (event) => this._handleThemeChange(event)\n this.darkModeMediaQuery.addEventListener(\"change\", this.themeChangeHandler)\n }\n\n /**\n * Remove theme change listener\n * @private\n */\n _removeThemeChangeListener() {\n if (this.darkModeMediaQuery && this.themeChangeHandler) {\n this.darkModeMediaQuery.removeEventListener(\n \"change\",\n this.themeChangeHandler\n )\n }\n }\n\n /**\n * Handle OS theme change and update TinyMCE skin\n * @param {MediaQueryListEvent} event - The media query change event\n * @private\n */\n _handleThemeChange(event) {\n const editor = tinymce.get(this.editorId)\n if (editor) {\n const skin = event.matches ? DARK_THEME : LIGHT_THEME\n const content_css = event.matches ? DARK_THEME : LIGHT_THEME\n\n // Update the skin by reinitializing the editor with new configuration\n editor.remove()\n tinymce\n .init({\n content_css,\n ...this.configuration,\n skin\n })\n .then((editors) => {\n editors.forEach((editor) => this._setupEditor(editor))\n })\n }\n }\n\n get configuration() {\n const customConfig = {}\n\n // read the attributes on the component and add them as custom configuration\n this.getAttributeNames().forEach((attributeName) => {\n if (![\"class\", \"id\", \"is\", \"name\", \"style\"].includes(attributeName)) {\n const config = this.getAttribute(attributeName)\n const key = attributeName.replaceAll(\"-\", \"_\")\n\n // Handle boolean HTML attributes (e.g., readonly=\"readonly\" or readonly=\"\")\n if (config === attributeName || config === \"\") {\n customConfig[key] = true\n } else {\n try {\n customConfig[key] = JSON.parse(config)\n } catch (e) {\n // also string values as parameter\n customConfig[key] = config\n }\n }\n }\n })\n\n const config = {\n content_css: this.preferredTheme,\n ...readJSONScript(\"alchemy_tinymce_defaults\"),\n ...customConfig,\n language: currentLocale(),\n selector: `#${this.editorId}`,\n skin: this.preferredTheme\n }\n\n // Tinymce has a height of 400px by default\n // if the element has a min_height set, we use this value for the height as well\n // so we do not need to set both values in the element configuration\n config.height = config.min_height\n\n return config\n }\n\n get preferredTheme() {\n return window.matchMedia(\"(prefers-color-scheme: dark)\").matches\n ? DARK_THEME\n : LIGHT_THEME\n }\n\n get editorId() {\n return this.editor.id\n }\n\n get editor() {\n return this.getElementsByTagName(\"textarea\")[0]\n }\n\n get elementEditor() {\n return document\n .getElementById(this.editorId)\n .closest(\"alchemy-element-editor\")\n }\n\n get minHeight() {\n return this.#min_height || this.configuration.min_height\n }\n\n set minHeight(value) {\n this.#min_height = value\n }\n}\n\ncustomElements.define(\"alchemy-tinymce\", Tinymce)\n","import Spinner from \"alchemy_admin/spinner\"\n\nclass UpdateCheck extends HTMLElement {\n async connectedCallback() {\n const spinner = new Spinner(\"small\")\n spinner.spin(this)\n\n try {\n const response = await fetch(this.url, { credentials: \"include\" })\n const responseJSON = await response.json()\n\n if (response.ok) {\n this.showStatus(responseJSON)\n } else {\n this.showError(response)\n }\n } catch (error) {\n this.showError(error)\n } finally {\n spinner.stop()\n }\n }\n\n get url() {\n return this.getAttribute(\"url\")\n }\n\n showStatus(responseJSON) {\n if (responseJSON[\"status\"] == \"true\") {\n this.querySelector(\".update_available\").classList.remove(\"hidden\")\n } else {\n this.querySelector(\".up_to_date\").classList.remove(\"hidden\")\n }\n }\n\n showError(error) {\n this.querySelector(\".error\").classList.remove(\"hidden\")\n console.error(\"[alchemy] Error fetching update status\", error)\n }\n}\n\ncustomElements.define(\"alchemy-update-check\", UpdateCheck)\n","import { registerIconLibrary, setDefaultAnimation } from \"shoelace\"\n\n// Change the default animation for all tooltips\nsetDefaultAnimation(\"tooltip.show\", {\n keyframes: [\n { transform: \"translateY(10px)\", opacity: \"0\" },\n { transform: \"translateY(0)\", opacity: \"1\" }\n ],\n options: {\n duration: 100\n }\n})\n\nsetDefaultAnimation(\"tooltip.hide\", {\n keyframes: [\n { transform: \"translateY(0)\", opacity: \"1\" },\n { transform: \"translateY(10px)\", opacity: \"0\" }\n ],\n options: {\n duration: 100\n }\n})\n\n// Change the default animation for all dialogs\nsetDefaultAnimation(\"dialog.show\", {\n keyframes: [\n { transform: \"scale(0.98)\", opacity: \"0\" },\n { transform: \"scale(1)\", opacity: \"1\" }\n ],\n options: {\n duration: 150\n }\n})\n\nsetDefaultAnimation(\"dialog.hide\", {\n keyframes: [\n { transform: \"scale(1)\", opacity: \"1\" },\n { transform: \"scale(0.98)\", opacity: \"0\" }\n ],\n options: {\n duration: 150\n }\n})\n\nconst spriteUrl = document\n .querySelector('link[rel=\"preload\"][as=\"image\"]')\n .getAttribute(\"href\")\n\nconst iconMap = {\n \"x-lg\": \"close\",\n caret: \"arrow-down-s\"\n}\n\nconst options = {\n resolver: (name) => `${spriteUrl}#ri-${iconMap[name] || name}-line`,\n mutator: (svg) => {\n svg.setAttribute(\"fill\", \"currentColor\")\n svg.setAttribute(\"viewBox\", \"0 0 24 24\")\n },\n spriteSheet: true\n}\n\nregisterIconLibrary(\"default\", options)\nregisterIconLibrary(\"system\", options)\n","import \"@ungap/custom-elements\"\nimport { Turbo } from \"@hotwired/turbo-rails\"\n\nimport { translate } from \"alchemy_admin/i18n\"\nimport { currentDialog, closeCurrentDialog } from \"alchemy_admin/dialog\"\nimport * as FixedElements from \"alchemy_admin/fixed_elements\"\nimport { growl } from \"alchemy_admin/growler\"\nimport Initializer from \"alchemy_admin/initializer\"\nimport { LinkDialog } from \"alchemy_admin/link_dialog\"\nimport pleaseWaitOverlay from \"alchemy_admin/please_wait_overlay\"\nimport Spinner from \"alchemy_admin/spinner\"\nimport { reloadPreview } from \"alchemy_admin/components/preview_window\"\nimport { openConfirmDialog } from \"alchemy_admin/confirm_dialog\"\n\nimport \"alchemy_admin/turbo_stream_actions\"\n\n// Web Components\nimport \"alchemy_admin/components\"\n\n// Shoelace Setup\nimport \"alchemy_admin/shoelace_theme\"\n\n// Global Alchemy object\nif (typeof window.Alchemy === \"undefined\") {\n window.Alchemy = {}\n}\n\n// Enhance the global Alchemy object with imported features\nObject.assign(Alchemy, {\n closeCurrentDialog,\n currentDialog,\n t: translate, // Global utility method for translating a given string\n FixedElements,\n growl,\n LinkDialog,\n pleaseWaitOverlay,\n Spinner,\n reloadPreview\n})\nTurbo.config.forms.confirm = openConfirmDialog\ndocument.addEventListener(\"turbo:load\", Initializer)\n\n// Public API for extensions\nexport { RemoteSelect } from \"alchemy_admin/components/remote_select\"\nexport { PageSelect } from \"alchemy_admin/components/page_select\"\nexport { AttachmentSelect } from \"alchemy_admin/components/attachment_select\"\nexport { ElementSelect } from \"alchemy_admin/components/element_select\"\nexport { TagsAutocomplete } from \"alchemy_admin/components/tags_autocomplete\"\nexport { Tinymce } from \"alchemy_admin/components/tinymce\"\nexport { on } from \"alchemy_admin/utils/events\"\n"],"names":["KEY_SEPARATOR","getTranslation","key","locale","currentLocale","translations","window","Alchemy","test","keys","split","group","nestedTranslation","console","warn","document","documentElement","lang","translate","replacement","undefined","translation","replace","bindedHotkeys","Hotkeys","scope","forEach","hotkey","unbind","search_fields","querySelectorAll","search_fields_clear","setScope","el","focus","focusVisible","push","click","blur","dataset","alchemyHotkey","createHtmlElement","text","element","createElement","innerHTML","content","children","Spinner$1","constructor","size","color","this","spinner","spin","parent","body","append","stop","remove","dispatchCustomEvent","name","detail","dispatchEvent","CustomEvent","bubbles","on","eventName","baseSelector","targetSelector","callback","baseNode","addEventListener","evt","targets","Array","from","currentNode","target","includes","call","parentElement","currentDialogs","DEFAULTS","header_height","padding","title","modal","overflow","ready","closed","Dialog","previousFlashParent","closing","url","options","width","parseInt","height","build","resize","open","dialog","bind_close_events","dialog_container","showModal","adoptFlashNotices","show","requestAnimationFrame","classList","add","load","close","releaseFlashNotices","pop","length","once","flashNotices","getElementById","clearTransientNotices","message","show_spinner","fetch","headers","then","async","response","responseText","get","Turbo","renderStreamMessage","ok","show_error","status","statusText","catch","reload","dialog_body","data","remove_spinner","style","display","init","Spinner","focusInitialElement","attempts","querySelector","activeElement","close_button","xhr","error_body","error_header","error_type","error_messages","error","e","preventDefault","dialog_header","dialog_title","textContent","getSize","minHeight","doc_width","innerWidth","doc_height","innerHeight","currentDialog","closeCurrentDialog","openDialog","removeTab","element_id","fixed_elements","panel_name","label","tab","panel","growl","flashType","flashMessage","pleaseWaitOverlay","showHelp","matches","String","fromCharCode","which","t","readJSONScript","id","script","JSON","parse","routes","buildRoutes","collection","member","Object","entries","path","ANCHOR_REGEX","PreviewWindow","HTMLIFrameElement","afterLoad","reloadIcon","loadTimeout","handleEvent","type","clearLoadTimeout","stopSpinner","onPreviewReady","event","connectedCallback","attachEvents","localStorage","getItem","previewUrlSelect","value","refresh","disconnectedCallback","removeEventListener","postMessage","contentWindow","startSpinner","src","setTimeout","Promise","resolve","isDragged","dragged","transitionProperty","pointerEvents","reloadButton","sizeSelect","setItem","clearTimeout","getAttribute","seconds","Number","isNaN","reloadPreview","customElements","define","extends","ConfirmDialog","ok_label","cancel_label","on_ok","bindEvents","cancelButton","on_cancel","hide","okButton","source","openConfirmDialog","IngredientAnchorLink","updateIcon","ingredientId","active","ingredientEditor","setAttribute","StreamActions","dialog_visit","visit","assign_picture","formField","targetElements","closest","setDirty","assign_attachment","fileEditor","replaceChildren","templateContent","close_current_dialog","reload_preview","remove_fixed_element","update_anchor_icon","hide_please_wait_overlay","Icon","HTMLElement","observedAttributes","super","spriteUrl","render","attributeChangedCallback","sizeClass","iconName","escape_html","str","focusTomSelect","tomSelect","focusFallback","teardownTomSelect","destroy","createDropdownPositioning","dropdownMask","className","removeAutoUpdater","onDropdownOpen","styles","minWidth","control","offsetWidth","zIndex","assign","dropdown","dropdownRoot","autoUpdate","x","y","computePosition","placement","middleware","offset","flip","shift","apply","availableWidth","availableHeight","elements","floating","maxWidth","reference","maxHeight","Math","max","left","top","onDropdownClose","control_input","removeButton","html","button","tabIndex","dropdownMessages","option_create","escape","input","loading","no_results","loading_more","no_more_results","hightlightTerm","term","escapedName","RegExp","match","serializeParams","params","prefix","map","scopedKey","isArray","item","encodeURIComponent","join","filter","part","RemoteSelect","selectedItem","selectedItems","Map","HTMLInputElement","inputValue","TomSelect","tomSelectConfig","bind","onChange","multiple","added","set","removed","delete","stringify","values","onOpen","allowClear","hasAttribute","selection","placeholder","queryParams","self","ajax","ajaxConfig","plugins","virtual_scroll","remove_button","clear_button","valueField","maxItems","closeAfterSelect","maxOptions","searchField","loadThrottle","quietMillis","refreshThrottle","openOnFocus","shouldLoad","firstUrl","query","requestUrl","onInitialize","items","addOption","$preselection","addItem","onType","toggle","lastValue","option","_escape","_renderResult","_renderListEntry","datatype","page","_searchQuery","results","_parseResponse","separator","getUrl","URL","location","origin","searchParams","Accept","Error","more","json","setNextUrl","onChangeMultiple","previous","itemData","ids","Set","current","has","fromEntries","startsWith","q","name_i_cont","meta","per_page","total_count","renderEntry","_entry","renderSelection","_selectedEntry","slots","leadColumn","cell","primary","raw","aside","secondary","secondaryAside","icon","media","badge","truncate","_hightlightTerm","AttachmentSelect","attachment","icon_css_class","AutoSubmit","submitEvent","Event","cancelable","form","Button","HTMLButtonElement","stopPropagation","disable","enable","rect","getBoundingClientRect","initialButtonText","removeAttribute","CharCounter","maxChars","getFormField","createDisplayElement","countCharacters","formFields","after","charLength","ClearScheduleButton","ClipboardButton","clipboard","ClipboardJS","Select","HTMLSelectElement","initTomSelect","destroyTomSelect","setOptions","prompt","selectedValue","Option","hasOwnProperty","renderers","hasPlaceholder","hasSelectedOption","settings","autofocus","clear","allowEmptyOption","sortField","renderColorOption","swatch","colorPicker","disabled","ColorInput","colorInput","textInput","ConditionalField","checked","hidden","fields","field","Datepicker","import","isConnected","flatpickr","inputField","flatpickrOptions","enableTime","inputType","slice","altInput","altFormat","altInputClass","noCalendar","time_24hr","onValueUpdate","_selectedDates","_dateStr","instance","dateFormat","DialogLink","HTMLAnchorElement","dialogOptions","contains","leaveConfirmed","DirtyGuard","guard","isPageDirty","confirmLeave","confirmed","requestSubmit","onbeforeunload","JSON_CONTENT_TYPE","isGetRequest","method","toLowerCase","prepareOptions","accept","getToken","prepareHeaders","attributes","patch","post","URLSearchParams","toString","prepareURL","contentType","isJson","isTurboStream","responseData","redirect_url","DomIdSelect","dataItem","hash","selectElement","pageId","fetchDomIds","api_ingredients_path","page_id","ingredients","ingredient","dom_id","reset","frame","contentDocument","PublishElementButton","scheduleButtonVariant","scheduleButton","publishButton","dispatchPageDirtyEvent","tooltip","publishButtonTooltip","DeleteElementButton","removeElement","elementEditor","fixed","elementId","pageHasUnpublishedChanges","ElementEditor","header","toggleButton","onHeaderDblclick","onToggleClick","focusElement","previewWindow","focusElementPreview","onClickElement","setClean","onSaveElement","success","hasEditors","firstChild","setTitle","selectTabForElement","expand","elementErrors","scrollToElement","scrollIntoView","behavior","scroll","reject","tabs","dirty","editor","quote","collapsed","collapse","compact","toggleIcon","collapse_admin_element_path","nestedElementIds","selector","nestedElement","finally","expanded","parentElementEditor","expand_admin_element_path","parentElementIds","updateTitle","published","isPublished","bodySelector","footer","elementName","hasChildren","renderName","ElementSelect","description","hint","ElementsWindow","visible","turboFrame","collapseButton","onCollapseClick","onWindowMessage","onBodyClick","focusElementEditor","collapseAllElements","removeProperty","widthFromCookie","setProperty","cookie","find","row","ElementsWindowHandle","dragging","elementsWindow","onMouseDown","onMouseUp","onDrag","pageX","getComputedStyle","parseFloat","elementWindowWidth","min","FileEditor","deleteLink","fileIcon","fileName","formFieldId","removeFile","ListFilter","debounceTimer","filterField","clearButton","onHotkey","onKeyup","onFocus","onClearClick","onEscape","visibility","matchedItems","itemsToShow","lowerTerm","nameAttribute","indexOf","ancestor","itemsSelector","block","debounceTime","MainNaviEntry","flyout","stopAutoUpdate","flyoutElement","strategy","fallbackPlacements","position","round","AlchemyMenuNode","folder","toggleFolded","node","toggle_folded_api_path","nodeId","folded","updateFolder","toggleAttribute","childrenList","Message","dismissTimeoutId","dismiss","closeIcon","dismissable","insertAdjacentHTML","dismissDelay","noticesWrapper","autoDismissDelay","Growl","ImageCropper","cropper","cropFromField","cropSizeField","image","onSubmit","onReset","Cropper","cropperOptions","aspectRatio","viewMode","zoomable","checkCrossOrigin","checkOrientation","box","ratio","NaN","cropFrom","v","cropSize","defaultBox","update","coords","setData","canvas","getCanvasData","scale","naturalWidth","setCropBoxData","getData","ImageOverlay","clickHandler","nodeName","keydownHandler","next","initKeyboardNavigation","href","IngredientGroup","HTMLDetailsElement","localStorageKey","isInLocalStorage","expanded_ingredient_groups","localStorageItem","LinkButton","LinkDialog","linkUrl","linkTitle","linkTarget","linkClass","link","setLink","linkButtons","linkUrlField","linkTitleField","linkTargetField","linkClassField","UnlinkButton","linked","isLinked","LinkButtons","removeLink","unlinkButton","setElementDirty","linkButton","NewsReader","track","slides","currentIndex","createDots","setActive","startRotation","passive","stopRotation","syncActiveToScroll","dotList","dots","_","index","dot","goTo","inline","clientWidth","scrollLeft","i","timer","setInterval","interval","clearInterval","AlchemyNodeFolder","NodeForm","onPageSelect","nameField","urlField","url_path","name_or_page_name_i_cont","breadcrumb","ancestors","AlchemyNodeTree","list","Sortable","animation","fallbackOnBody","swapThreshold","handle","invertSwap","draggable","onEnd","handleSort","to","oldIndex","newIndex","move_api_path","target_parent_id","recordId","new_position","updateFolders","formatFileSize","bytes","exponent","floor","log","pow","toFixed","FileUpload","file","request","progressEventLoaded","progressEventTotal","valid","errorMessage","loadedSize","cancel","reader","FileReader","readAsDataURL","Image","result","prepend","initialize","validateFile","addRequestEventListener","finished","abort","config","maxFileSize","file_size_limit","allowedFiletypes","allowed_filetypes","alchemy_pictures","alchemy_attachments","extension","some","upload","onprogress","progressEvent","onload","responseMessage","onerror","errorMessageContainer","progressElement","loaded","total","isValid","Progress","fileCount","fileUploads","buttonLabel","actionButton","handleFileChange","updateView","onComplete","fileUpload","activeUploads","setupCloseButton","_status","ariaLabel","sumFileProgresses","reduce","accumulator","totalProgress","overallProgressValue","overallUploadSize","every","entry","fileProgress","uploadedFileCount","uploadsStatuses","totalSize","ceil","weight","Uploader","dropzoneElement","isDraggedOver","fileInput","onFileInputChange","dropzone","setupDropZone","onDropzoneDragleave","onDropzoneDrop","onDropzoneDragover","handleUploadComplete","uploadFiles","files","toggleDropzoneClass","enabled","dataTransfer","getAsFile","redirectUrl","uploadProgress","fileUploadCount","XMLHttpRequest","upload_limit","submitFile","createProgress","formData","FormData","action","setRequestHeader","send","layout","progressContainer","Overlay","BUTTON","AlchemyPageNode","folderButton","handleFolderClick","currentTarget","fold_admin_page_path","toggleChildren","updateFolderIcon","childrenContainer","updateFolderButton","folderElement","shouldShowButton","tagName","outerHTML","PagePublicationFields","publicField","onClick","checkbox","date","Date","now","getTime","getTimezoneOffset","toISOString","publicationDateFields","publicOnPicker","substring","publicUntilPicker","PageSelect","pages","site","language_code","PictureArchive","selectAllButton","onCheckboxChange","onEditMultipleClick","onSelectAllClick","state","toggleCheckboxes","selectedItemTools","checkedInputs","editMultiplePicturesUrl","PictureDescriptionSelect","select","PictureEditor","pictureIdField","targetSizeField","imageCropperField","pictureThumbnail","deleteButton","cropLink","targetSize","pictureId","func","delay","timeout","args","that","debounce","updateImage","updateCropLink","removeImage","observer","MutationObserver","mutationCallback","observe","disconnect","mutationsList","mutation","url_admin_picture_path","crop","imageCropperEnabled","crop_from","crop_size","flatten","alt","defaultCropFrom","defaultCropSize","mask","n","zoom","a","imageFileWidth","b","imageFileHeight","dimensions","imageCropper","PictureThumbnail","onLoaded","onError","start","setImage","createImage","complete","hoist","PublishPageButton","markDirty","variant","AlchemySitemap","searchInput","resultCounter","setupSearch","setupSortables","mutations","addedNodes","nodeType","Node","ELEMENT_NODE","setupSortable","childList","subtree","teardownSearch","handleSearch","handleClearSearch","trim","filterPages","clearFilter","allPages","matchCount","firstMatch","pageElement","container","pageNode","move_admin_page_path","parentId","pageData","pageEl","urlPathEl","updateFolderIcons","fromContainer","toContainer","fromParent","toParent","SORTABLE_OPTIONS","ghostClass","easing","onStart","onSort","parent_element_id","order_admin_elements_path","preview_text","SortableElements","put","_from","droppableElements","TagsAutocomplete","getElementsByTagName","delimiter","create","createFilter","duplicates","persist","clearAfterSelect","DARK_THEME","LIGHT_THEME","Tinymce","min_height","root","rootMargin","threshold","tinymceIntersectionObserver","IntersectionObserver","intersectionRatio","_initTinymceEditor","unobserve","_setupThemeChangeListener","_removeThemeChangeListener","tinymce","editorId","configuration","editors","_setupEditor","editorContainer","darkModeMediaQuery","matchMedia","themeChangeHandler","_handleThemeChange","skin","content_css","customConfig","getAttributeNames","attributeName","replaceAll","preferredTheme","language","UpdateCheck","credentials","responseJSON","showStatus","showError","setDefaultAnimation","keyframes","transform","opacity","duration","iconMap","caret","resolver","mutator","svg","spriteSheet","registerIconLibrary","FixedElements","onCreateLink","link_admin_pages_path","parameterMapping","selected_tab","link_title","link_target","internalForm","attachmentSelect","updatePage","submitForm","linkFormType","internalLink","domIdSelect","linkType","elementAnchor","literal","linkUrlRegexp","end","lastIndexOf","isLinkUrl","showValidationError","errors","forms","confirm","srcElement","isPressed"],"mappings":"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","x_google_ignoreList":[18]}
|
|
1
|
+
{"version":3,"file":"alchemy_admin.min.js","sources":["../../../javascript/alchemy_admin/i18n.js","../../../javascript/alchemy_admin/hotkeys.js","../../../javascript/alchemy_admin/utils/dom_helpers.js","../../../javascript/alchemy_admin/spinner.js","../../../javascript/alchemy_admin/utils/events.js","../../../javascript/alchemy_admin/dialog.js","../../../javascript/alchemy_admin/fixed_elements.js","../../../javascript/alchemy_admin/growler.js","../../../javascript/alchemy_admin/please_wait_overlay.js","../../../javascript/alchemy_admin/initializer.js","../../../javascript/alchemy_admin/utils/json_script.js","../../../javascript/alchemy_admin/routes.js","../../../javascript/alchemy_admin/link_dialog.js","../../../javascript/alchemy_admin/components/preview_window.js","../../../javascript/alchemy_admin/confirm_dialog.js","../../../javascript/alchemy_admin/ingredient_anchor_link.js","../../../javascript/alchemy_admin/turbo_stream_actions.js","../../../javascript/alchemy_admin/components/icon.js","../../../../node_modules/.pnpm/tom-select@2.6.2/node_modules/tom-select/dist/esm/utils.js","../../../javascript/alchemy_admin/utils/tom_select.js","../../../javascript/alchemy_admin/components/remote_select.js","../../../javascript/alchemy_admin/components/attachment_select.js","../../../javascript/alchemy_admin/components/auto_submit.js","../../../javascript/alchemy_admin/components/button.js","../../../javascript/alchemy_admin/components/char_counter.js","../../../javascript/alchemy_admin/components/clear_schedule_button.js","../../../javascript/alchemy_admin/components/clipboard_button.js","../../../javascript/alchemy_admin/components/select.js","../../../javascript/alchemy_admin/components/color_select.js","../../../javascript/alchemy_admin/components/conditional_field.js","../../../javascript/alchemy_admin/components/datepicker.js","../../../javascript/alchemy_admin/components/dialog_link.js","../../../javascript/alchemy_admin/components/dirty_guard.js","../../../javascript/alchemy_admin/utils/ajax.js","../../../javascript/alchemy_admin/components/dom_id_select.js","../../../javascript/alchemy_admin/components/element_editor/publish_element_button.js","../../../javascript/alchemy_admin/utils/dispatch_page_dirty_event.js","../../../javascript/alchemy_admin/components/element_editor/delete_element_button.js","../../../javascript/alchemy_admin/components/element_editor.js","../../../javascript/alchemy_admin/components/element_select.js","../../../javascript/alchemy_admin/components/elements_window.js","../../../javascript/alchemy_admin/components/elements_window_handle.js","../../../javascript/alchemy_admin/components/file_editor.js","../../../javascript/alchemy_admin/components/list_filter.js","../../../javascript/alchemy_admin/components/main_navi_entry.js","../../../javascript/alchemy_admin/components/menu_node.js","../../../javascript/alchemy_admin/components/message.js","../../../javascript/alchemy_admin/components/growl.js","../../../javascript/alchemy_admin/components/image_cropper.js","../../../javascript/alchemy_admin/image_overlay.js","../../../javascript/alchemy_admin/components/image_overlay_link.js","../../../javascript/alchemy_admin/components/ingredient_group.js","../../../javascript/alchemy_admin/components/link_buttons/link_button.js","../../../javascript/alchemy_admin/components/link_buttons/unlink_button.js","../../../javascript/alchemy_admin/components/link_buttons.js","../../../javascript/alchemy_admin/components/news_reader.js","../../../javascript/alchemy_admin/components/node_folder.js","../../../javascript/alchemy_admin/components/node_form.js","../../../javascript/alchemy_admin/components/node_select.js","../../../javascript/alchemy_admin/components/node_tree.js","../../../javascript/alchemy_admin/utils/format.js","../../../javascript/alchemy_admin/components/uploader/file_upload.js","../../../javascript/alchemy_admin/components/uploader/progress.js","../../../javascript/alchemy_admin/components/uploader.js","../../../javascript/alchemy_admin/components/overlay.js","../../../javascript/alchemy_admin/components/page_node.js","../../../javascript/alchemy_admin/components/page_publication_fields.js","../../../javascript/alchemy_admin/components/page_select.js","../../../javascript/alchemy_admin/components/picture_archive.js","../../../javascript/alchemy_admin/components/picture_description_select.js","../../../javascript/alchemy_admin/components/picture_editor.js","../../../javascript/alchemy_admin/utils/debounce.js","../../../javascript/alchemy_admin/utils/max.js","../../../javascript/alchemy_admin/components/picture_thumbnail.js","../../../javascript/alchemy_admin/components/publish_page_button.js","../../../javascript/alchemy_admin/components/sitemap.js","../../../javascript/alchemy_admin/components/sortable_elements.js","../../../javascript/alchemy_admin/components/spinner.js","../../../javascript/alchemy_admin/components/tags_autocomplete.js","../../../javascript/alchemy_admin/components/tinymce.js","../../../javascript/alchemy_admin/components/update_check.js","../../../javascript/alchemy_admin/shoelace_theme.js","../../../javascript/alchemy_admin.js"],"sourcesContent":["const KEY_SEPARATOR = /\\./\n\nfunction nestedTranslation(translations, key) {\n const keys = key.split(KEY_SEPARATOR)\n const group = translations[keys[0]]\n if (group) {\n return group[keys[1]] || key\n }\n return key\n}\n\nfunction getTranslation(key) {\n const locale = currentLocale()\n // Host engines import components standalone, outside of the admin layout\n // that defines the global.\n const translations = window.Alchemy?.translations\n\n if (!translations) {\n console.warn(`Translations for locale ${locale} not found!`)\n return key\n }\n\n if (KEY_SEPARATOR.test(key)) {\n return nestedTranslation(translations, key)\n }\n return translations[key] || key\n}\n\nexport function currentLocale() {\n if (document.documentElement.lang) {\n return document.documentElement.lang\n }\n return \"en\"\n}\n\nexport function translate(key, replacement = undefined) {\n let translation = getTranslation(key)\n\n if (replacement) {\n return translation.replace(/%\\{.+\\}/, replacement)\n }\n return translation\n}\n","import \"keymaster\"\n\nconst bindedHotkeys = []\n\nexport default function (scope = document) {\n // Unbind all previously registered hotkeys if we are not inside a dialog.\n if (scope === document) {\n bindedHotkeys.forEach((hotkey) => key.unbind(hotkey))\n }\n\n // Binds keyboard shortcuts to search fields.\n const search_fields = scope.querySelectorAll(\".search_input_field\")\n const search_fields_clear = scope.querySelectorAll(\n \".search_field_clear, .js_filter_field_clear\"\n )\n key(\"alt+f\", function () {\n key.setScope(\"search\")\n search_fields.forEach((el) => el.focus({ focusVisible: true }))\n return false\n })\n bindedHotkeys.push(\"alt+f\")\n key(\"esc\", \"search\", function () {\n search_fields_clear.forEach((el) => el.click())\n search_fields.forEach((el) => el.blur())\n })\n bindedHotkeys.push(\"esc\")\n\n // Binds click events to buttons with hotkeys.\n //\n // Simply add a data-alchemy-hotkey attribute to your link.\n // If a hotkey is triggered by user, the click event of the element gets triggerd.\n //\n scope.querySelectorAll(\"[data-alchemy-hotkey]\").forEach(function (el) {\n const hotkey = el.dataset.alchemyHotkey\n key(hotkey, () => el.click())\n bindedHotkeys.push(hotkey)\n })\n}\n","/**\n * create a HTML element\n * @param {string} text\n * @returns {HTMLElement}\n */\nexport function createHtmlElement(text) {\n const element = document.createElement(\"template\")\n element.innerHTML = text\n return element.content.children[0]\n}\n\n/**\n * wrap element with wrappingElement\n * @param {HTMLElement} element\n * @param {HTMLElement} wrappingElement\n */\nexport function wrap(element, wrappingElement) {\n element.replaceWith(wrappingElement)\n wrappingElement.appendChild(element)\n}\n","import { createHtmlElement } from \"alchemy_admin/utils/dom_helpers\"\n\nexport default class Spinner {\n constructor(size, color = \"currentColor\") {\n this.size = size\n this.color = color\n this.spinner = undefined\n }\n\n /**\n * @returns {HTMLElement|undefined}\n */\n get el() {\n return this.spinner\n }\n /**\n * @param {HTMLElement|undefined} parent\n */\n spin(parent) {\n if (typeof parent === \"undefined\") {\n parent = document.body\n }\n this.spinner = createHtmlElement(\n `<alchemy-spinner size=\"${this.size}\" color=\"${this.color}\"></alchemy-spinner>`\n )\n parent.append(this.spinner)\n return this\n }\n\n stop() {\n if (this.spinner) {\n this.spinner.remove()\n this.spinner = undefined\n }\n }\n}\n","// Dispatches a bubbling CustomEvent from the given element.\nexport function dispatchCustomEvent(element, name, detail = {}) {\n element.dispatchEvent(new CustomEvent(name, { bubbles: true, detail }))\n}\n\nexport function on(eventName, baseSelector, targetSelector, callback) {\n document.querySelectorAll(baseSelector).forEach((baseNode) => {\n baseNode.addEventListener(eventName, (evt) => {\n const targets = Array.from(baseNode.querySelectorAll(targetSelector))\n let currentNode = evt.target\n\n while (currentNode !== baseNode) {\n if (targets.includes(currentNode)) {\n callback.call(currentNode, evt)\n return\n }\n currentNode = currentNode.parentElement\n }\n })\n })\n}\n","import { Turbo } from \"@hotwired/turbo-rails\"\nimport Hotkeys from \"alchemy_admin/hotkeys\"\nimport Spinner from \"alchemy_admin/spinner\"\nimport { createHtmlElement } from \"alchemy_admin/utils/dom_helpers\"\nimport { dispatchCustomEvent } from \"alchemy_admin/utils/events\"\n\n// Collection of all current dialog instances\nconst currentDialogs = []\n\n// Id of the Turbo Frame wrapping the dialog content.\n// Keep in sync with Alchemy::Admin::DIALOG_FRAME_ID.\nexport const DIALOG_FRAME_ID = \"alchemy_dialog_frame\"\n\nconst TURBO_STREAM_CONTENT_TYPE = \"text/vnd.turbo-stream.html\"\n\nconst DEFAULTS = {\n header_height: 36,\n size: \"400x300\",\n padding: true,\n title: \"\",\n modal: true,\n overflow: \"visible\",\n ready: () => {},\n closed: () => {}\n}\n\nexport class Dialog {\n #previousFlashParent = null\n #closing = false\n\n // Arguments:\n // - url: The url to load the content from via ajax\n // - options: A object holding options\n // - size: The maximum size of the Dialog\n // - title: The title of the Dialog\n constructor(url, options = {}) {\n this.url = url\n this.options = { ...DEFAULTS, ...options }\n const size = this.options.size.split(\"x\")\n this.width = parseInt(size[0], 10)\n this.height = parseInt(size[1], 10)\n this.build()\n this.resize()\n }\n\n // Opens the Dialog and loads the content via ajax.\n open() {\n dispatchCustomEvent(this.dialog, \"Alchemy.DialogOpen\")\n this.bind_close_events()\n if (this.options.modal) {\n this.dialog_container.showModal()\n this.#adoptFlashNotices()\n } else {\n this.dialog_container.show()\n }\n window.requestAnimationFrame(() => {\n this.dialog_container.classList.add(\"open\")\n })\n document.body.classList.add(\"prevent-scrolling\")\n currentDialogs.push(this)\n this.load()\n }\n\n // Closes the Dialog and removes it from the DOM\n close() {\n // The container stays open during the closing transition, so close() can be\n // called again mid-fade (mashing Esc, a double click on the close button or\n // backdrop). Guard against re-entry, otherwise a second transitionend\n // listener is registered and the teardown runs twice.\n if (this.#closing) {\n return true\n }\n this.#closing = true\n dispatchCustomEvent(this.dialog, \"DialogClose.Alchemy\")\n this.dialog_container.classList.remove(\"open\")\n this.dialog_container.addEventListener(\n \"transitionend\",\n () => {\n // Move the flash notices back out before removing the container, so\n // they are not destroyed together with the dialog.\n this.#releaseFlashNotices()\n this.dialog_container.close()\n this.dialog_container.remove()\n currentDialogs.pop()\n if (currentDialogs.length === 0) {\n document.body.classList.remove(\"prevent-scrolling\")\n }\n if (this.options.closed != null) {\n this.options.closed()\n }\n },\n { once: true }\n )\n return true\n }\n\n // A modal <dialog> makes everything outside its subtree inert, so flash\n // notices rendered on the body would be visible but not interactable while\n // the dialog is open. Move them into the dialog, which is in the top layer\n // and not inert, for the dialog's lifetime and restore them on close.\n #adoptFlashNotices() {\n const flashNotices = document.getElementById(\"flash_notices\")\n if (flashNotices) {\n this.#previousFlashParent = flashNotices.parentElement\n this.dialog_container.append(flashNotices)\n this.#clearTransientNotices(flashNotices)\n }\n }\n\n // The notices are not cleared on the way back. Anything transient left at\n // this point was raised by the dialog itself, typically the growl of the\n // response that closes it, and has to outlive the dialog. Moving it re-arms\n // its dismiss timer, which is what a just-raised notice wants anyway.\n #releaseFlashNotices() {\n const flashNotices = document.getElementById(\"flash_notices\")\n if (flashNotices && this.#previousFlashParent) {\n this.#previousFlashParent.append(flashNotices)\n }\n }\n\n // Only the persistent error notices need to travel with the dialog to stay\n // interactable. Moving the auto-dismissing ones would restart their dismiss\n // timers (their connectedCallback re-arms the timer), making them linger, so\n // remove them instead. A fade-out is skipped on purpose: the just-moved\n // element has no transition baseline, so dismiss()'s transitionend would never\n // fire and the node would be stranded invisible-but-present.\n #clearTransientNotices(flashNotices) {\n flashNotices\n .querySelectorAll('alchemy-message[dismissable]:not([type=\"error\"])')\n .forEach((message) => message.remove())\n }\n\n // Loads the content via ajax and replaces the Dialog body with server response.\n //\n // The Turbo-Frame header tells the server this content is rendered for a\n // dialog, so it wraps the response in the dialog frame (see set_layout). The\n // same action requested as a full page is not wrapped.\n load() {\n this.show_spinner()\n fetch(this.url, {\n headers: { \"Turbo-Frame\": DIALOG_FRAME_ID }\n })\n .then(async (response) => {\n const responseText = await response.text()\n // A stream answers a request the frame must not render, whatever the\n // status: an expired session sends a `dialog_visit` to the login page.\n // Without this the stream is never run and the dialog shows a 401 the\n // user cannot act on.\n if (\n response.headers\n .get(\"content-type\")\n ?.includes(TURBO_STREAM_CONTENT_TYPE)\n ) {\n Turbo.renderStreamMessage(responseText)\n } else if (response.ok) {\n this.replace(responseText)\n } else {\n this.show_error({\n status: response.status,\n statusText: response.statusText,\n responseText\n })\n }\n })\n .catch(() => {\n this.show_error({ status: 0 })\n })\n }\n\n // Reloads the Dialog content\n reload() {\n this.dialog_body.innerHTML = \"\"\n this.load()\n }\n\n // Replaces the dialog body with given content and initializes it.\n replace(data) {\n this.remove_spinner()\n this.dialog_body.style.display = \"none\"\n this.dialog_body.innerHTML = data\n this.init()\n dispatchCustomEvent(this.dialog, \"DialogReady.Alchemy\", {\n body: this.dialog_body\n })\n if (this.options.ready != null) {\n this.options.ready(this.dialog_body)\n }\n this.dialog_body.style.display = \"\"\n }\n\n // Adds a spinner into Dialog body\n show_spinner() {\n this.spinner = new Spinner(\"medium\")\n this.spinner.spin(this.dialog_body)\n }\n\n // Removes the spinner from Dialog body\n remove_spinner() {\n this.spinner.stop()\n }\n\n // Initializes the Dialog body\n init() {\n Hotkeys(this.dialog_body)\n window.requestAnimationFrame(() => this.#focusInitialElement())\n }\n\n // Shoelace tab panels (link, page configure) only render a few frames after\n // the content loaded, so their fields can not take focus yet. Hence the retry.\n #focusInitialElement(attempts = 0) {\n const target =\n this.dialog_body.querySelector(\"[autofocus]\") ??\n this.dialog_body.querySelector(\"form [type='submit']\")\n target?.focus()\n if (document.activeElement === this.close_button && attempts < 20) {\n window.requestAnimationFrame(() =>\n this.#focusInitialElement(attempts + 1)\n )\n }\n }\n\n // Displays an error message\n show_error(xhr, statusText) {\n if (xhr.status === 422) {\n this.dialog_body.innerHTML = xhr.responseText\n this.init()\n return\n }\n\n const { error_body, error_header, error_type } = this.error_messages(\n xhr,\n statusText\n )\n\n this.dialog_body.innerHTML = `<alchemy-message type=\"${error_type}\">\n <h1>${error_header}</h1>\n <p>${error_body}</p>\n </alchemy-message>`\n }\n\n // Returns error message based on xhr status\n error_messages(xhr, statusText) {\n let error_body,\n error_header,\n error_type = \"warning\"\n\n switch (xhr.status) {\n case 0:\n error_header = \"The server does not respond.\"\n error_body = \"Please check server and try again.\"\n break\n case 403:\n error_header = \"You are not authorized!\"\n error_body = \"Please close this window.\"\n break\n default:\n error_type = \"error\"\n if (statusText) {\n error_header = statusText\n console.error(xhr.responseText)\n } else {\n error_header = `${xhr.statusText} (${xhr.status})`\n }\n error_body = \"Please check log and try again.\"\n }\n\n return { error_header, error_body, error_type }\n }\n\n // Binds close events on:\n // - Close button\n // - Overlay (if the Dialog is a modal)\n // - ESC Key (the dialog element's cancel event)\n bind_close_events() {\n this.close_button.addEventListener(\"click\", (e) => {\n e.preventDefault()\n this.close()\n })\n this.dialog_container.addEventListener(\"close\", (e) => {\n e.preventDefault()\n this.close()\n })\n this.dialog_container.classList.add(\"closable\")\n // Use pointerdown, not click: a click whose mousedown and mouseup land on\n // different nodes is dispatched to their common ancestor — this element for\n // anything inside the dialog — closing it although the backdrop was never\n // hit, e.g. when selecting text and releasing outside.\n this.dialog_container.addEventListener(\"pointerdown\", (e) => {\n if (e.target === this.dialog_container) {\n this.close()\n }\n })\n this.dialog_container.addEventListener(\"cancel\", (e) => {\n // The <dialog>'s own cancel event (Esc) targets the container, but a file\n // input's native cancel event (dismissing the OS file picker) bubbles up\n // to the same node. Only the former should close the dialog.\n if (e.target !== this.dialog_container) {\n return\n }\n e.preventDefault()\n this.close()\n })\n }\n\n // Builds the html structure of the Dialog\n build() {\n this.dialog_container = createHtmlElement(\n '<dialog class=\"alchemy-dialog-container\"></dialog>'\n )\n this.dialog = createHtmlElement('<div class=\"alchemy-dialog\"></div>')\n this.dialog_body = createHtmlElement(\n '<div class=\"alchemy-dialog-body\"></div>'\n )\n this.dialog_header = createHtmlElement(\n '<div class=\"alchemy-dialog-header\"></div>'\n )\n this.dialog_title = createHtmlElement(\n '<div class=\"alchemy-dialog-title\"></div>'\n )\n this.close_button = createHtmlElement(\n '<button class=\"alchemy-dialog-close\"><alchemy-icon name=\"close\"></alchemy-icon></button>'\n )\n this.dialog_title.textContent = this.options.title\n this.dialog_header.append(this.dialog_title)\n this.dialog_header.append(this.close_button)\n this.dialog.append(this.dialog_header)\n this.dialog.append(this.dialog_body)\n this.dialog_container.append(this.dialog)\n if (this.options.modal) {\n this.dialog.classList.add(\"modal\")\n }\n if (this.options.padding) {\n this.dialog_body.classList.add(\"padded\")\n }\n document.body.append(this.dialog_container)\n }\n\n // Sets the correct size of the dialog\n // It normalizes the given size, so that it never acceeds the window size.\n resize() {\n const { width, height } = this.getSize()\n\n this.dialog.style.width = `${width}px`\n this.dialog.style.minHeight = `${height}px`\n this.dialog.style.overflow = this.options.overflow\n\n if (this.options.overflow === \"hidden\") {\n this.dialog_body.style.height = `${height}px`\n this.dialog_body.style.overflow = \"auto\"\n } else {\n this.dialog_body.style.minHeight = `${height}px`\n this.dialog_body.style.overflow = \"visible\"\n }\n }\n\n getSize() {\n const padding = this.options.padding ? 16 : 0\n const doc_width = window.innerWidth\n const doc_height = window.innerHeight\n\n let width = this.width\n let height = this.height\n\n if (width >= doc_width) {\n width = doc_width - padding\n }\n\n if (height >= doc_height) {\n height = doc_height - padding - DEFAULTS.header_height\n }\n\n return { width, height }\n }\n}\n\n// Gets the last dialog instantiated, which is the current one.\nexport function currentDialog() {\n const { length } = currentDialogs\n if (length === 0) {\n return\n }\n return currentDialogs[length - 1]\n}\n\n// Utility function to close the current Dialog\n//\n// You can pass a callback function, that gets triggered after the Dialog gets closed.\n//\nexport function closeCurrentDialog(callback) {\n const dialog = currentDialog()\n if (dialog != null) {\n dialog.options.closed = callback\n return dialog.close()\n }\n}\n\n// Utility function to open a new Dialog\nexport function openDialog(url, options) {\n if (!url) {\n throw \"No url given! Please provide an url.\"\n }\n const dialog = new Dialog(url, options)\n dialog.open()\n}\n","// Creates a fixed element tab.\nexport function createTab(element_id, label) {\n const fixed_elements = document.getElementById(\"fixed-elements\")\n const panel_name = `fixed-element-${element_id}`\n\n const tab = `<sl-tab slot=\"nav\" panel=\"${panel_name}\">${label}</sl-tab>`\n const panel = `<sl-tab-panel name=\"${panel_name}\" style=\"--padding: 0\" />`\n\n fixed_elements.innerHTML += tab + panel\n\n window.requestAnimationFrame(function () {\n fixed_elements.show(panel_name)\n })\n}\n\nexport function removeTab(element_id) {\n const fixed_elements = document.getElementById(\"fixed-elements\")\n const panel_name = `fixed-element-${element_id}`\n\n fixed_elements.querySelector(`sl-tab[panel=\"${panel_name}\"]`).remove()\n fixed_elements.querySelector(`sl-tab-panel[name=\"${panel_name}\"]`).remove()\n\n fixed_elements.show(\"main-content-elements\")\n}\n","import { createHtmlElement } from \"alchemy_admin/utils/dom_helpers\"\n\nfunction build(message, flashType) {\n const flashNotices = document.getElementById(\"flash_notices\")\n const flashMessage = createHtmlElement(`\n <alchemy-message type=\"${flashType}\" dismissable>\n ${message}\n </alchemy-message>\n `)\n flashNotices.append(flashMessage)\n}\n\nexport function growl(message, style = \"notice\") {\n build(message, style)\n}\n","/**\n * To show the \"Please wait\" overlay.\n * Pass false to hide it.\n * @param {boolean} show\n */\nexport default function pleaseWaitOverlay(show = true) {\n document.querySelector(\"alchemy-overlay\").show = !!show\n}\n","import Hotkeys from \"alchemy_admin/hotkeys\"\nimport pleaseWaitOverlay from \"alchemy_admin/please_wait_overlay\"\nimport { openDialog } from \"alchemy_admin/dialog\"\n\n// Opens the help dialog when the user presses the \"?\" key outside of a field.\nfunction showHelp(evt) {\n if (\n !evt.target.matches(\"input, textarea\") &&\n String.fromCharCode(evt.which) === \"?\"\n ) {\n openDialog(\"/admin/help\", {\n title: Alchemy.t(\"help\"),\n size: \"500x400\"\n })\n return false\n } else {\n return true\n }\n}\n\nexport default function Initializer() {\n // We obviously have javascript enabled.\n document.documentElement.classList.remove(\"no-js\")\n\n // Initialize hotkeys.\n Hotkeys()\n\n // (Re)bind the help dialog hotkey.\n document.removeEventListener(\"keypress\", showHelp)\n document.addEventListener(\"keypress\", showHelp)\n\n // Add observer for please wait overlay.\n document.querySelectorAll(\".please_wait\").forEach((element) => {\n element.addEventListener(\"click\", pleaseWaitOverlay)\n })\n\n // Hack for enabling tab focus for <a>'s styled as button.\n document.querySelectorAll(\"a.button\").forEach((button) => {\n button.setAttribute(\"tabindex\", 0)\n })\n\n // Override the filter of keymaster.js so we can blur the fields on esc key.\n key.filter = function (event) {\n let tagName = (event.target || event.srcElement).tagName\n return (\n key.isPressed(\"esc\") ||\n !(tagName === \"INPUT\" || tagName === \"SELECT\" || tagName === \"TEXTAREA\")\n )\n }\n}\n","/**\n * Reads the data the server rendered into a JSON script tag\n * @param {string} id\n * @returns {any|undefined} undefined if no such script tag is present\n */\nexport function readJSONScript(id) {\n const script = document.getElementById(id)\n\n if (!script) return undefined\n\n return JSON.parse(script.textContent)\n}\n","import { readJSONScript } from \"alchemy_admin/utils/json_script\"\n\n// Member routes are rendered with 1 as the record id and become functions\n// that put the given id in its place.\nfunction buildRoutes({ collection = {}, member = {} } = {}) {\n const routes = { ...collection }\n\n Object.entries(member).forEach(([name, path]) => {\n routes[name] =\n typeof path === \"string\"\n ? (id) => path.replace(/1/, id)\n : buildRoutes({ member: path })\n })\n\n return routes\n}\n\nexport const routes = buildRoutes(readJSONScript(\"alchemy_routes\"))\n","import { translate } from \"alchemy_admin/i18n\"\nimport { Dialog } from \"alchemy_admin/dialog\"\nimport { routes } from \"alchemy_admin/routes\"\n\n// Matches a URL fragment (#anchor) at the end of a string.\n// Covers RFC 3986 unreserved characters (ALPHA, DIGIT, \"-\", \".\", \"_\", \"~\")\n// which are the characters valid in URL fragments and common in DOM element IDs.\nconst ANCHOR_REGEX = /#[\\w.~-]+$/\n\n// The server renders the expression as a JavaScript literal, so that whatever\n// a host application configures keeps the meaning it had as an inline script.\n// A replaced external tab may not render it, the server validates urls anyway.\nfunction isLinkUrl(url) {\n const literal =\n document.getElementById(\"external_link\")?.dataset.linkUrlRegexp\n if (!literal) return true\n\n const end = literal.lastIndexOf(\"/\")\n return new RegExp(literal.slice(1, end), literal.slice(end + 1)).test(url)\n}\n\n// Represents the link Dialog that appears, if a user clicks the link buttons\n// in TinyMCE or on an Ingredient that has links enabled (e.g. Picture)\n//\nexport class LinkDialog extends Dialog {\n #onCreateLink\n\n constructor(link) {\n const url = new URL(routes.link_admin_pages_path, window.location)\n const parameterMapping = {\n url: link.url,\n selected_tab: link.type,\n link_title: link.title,\n link_target: link.target\n }\n\n // searchParams.set would also add undefined values\n Object.keys(parameterMapping).forEach((key) => {\n if (parameterMapping[key]) {\n url.searchParams.set(key, parameterMapping[key])\n }\n })\n\n super(url.href, {\n size: \"600x320\",\n title: translate(\"Link\")\n })\n }\n\n /**\n * Called from Dialog class after the url was loaded\n */\n replace(data) {\n // let Dialog class handle the content replacement\n super.replace(data)\n this.#attachEvents()\n }\n\n /**\n * make the open method a promise\n * maybe in a future version the whole Dialog will respond with a promise result if the dialog is closing\n * @returns {Promise<unknown>}\n */\n open() {\n super.open()\n return new Promise((resolve) => (this.#onCreateLink = resolve))\n }\n\n /**\n * Attaches click events to forms in the link dialog.\n */\n #attachEvents() {\n // enable the dom selection in internal link tab\n const internalForm = document.querySelector(\n '[data-link-form-type=\"internal\"]'\n )\n const attachmentSelect = document.querySelector(\n '[data-link-form-type=\"file\"] alchemy-attachment-select'\n )\n\n internalForm.addEventListener(\"Alchemy.RemoteSelect.Change\", (e) => {\n this.#updatePage(e.detail.added)\n })\n\n attachmentSelect.addEventListener(\"Alchemy.RemoteSelect.Change\", (e) => {\n const attachment = e.detail.added\n document.getElementById(\"file_link\").value = attachment\n ? attachment.url\n : \"\"\n })\n\n document.querySelectorAll(\"[data-link-form-type]\").forEach((form) => {\n form.addEventListener(\"submit\", (e) => {\n e.preventDefault()\n this.#submitForm(e.target.dataset.linkFormType)\n })\n })\n }\n\n /**\n * update page select and set anchor select\n * @param page\n */\n #updatePage(page = null) {\n const internalLink = document.getElementById(\"internal_link\")\n const domIdSelect = document.querySelector(\n '[data-link-form-type=\"internal\"] alchemy-dom-id-api-select'\n )\n\n internalLink.value = page ? page.url_path : \"\"\n domIdSelect.page = page ? page.id : undefined\n }\n\n /**\n * submit the form itself\n * @param linkType\n */\n #submitForm(linkType) {\n const elementAnchor = document.getElementById(\"element_anchor\")\n let url = document.getElementById(`${linkType}_link`).value\n\n if (linkType === \"internal\" && elementAnchor.value !== \"\") {\n // remove possible fragments on the url and attach the fragment (which contains the #)\n url = url.replace(ANCHOR_REGEX, \"\") + elementAnchor.value\n } else if (linkType === \"external\" && !isLinkUrl(url)) {\n // show validation error and prevent link creation\n this.#showValidationError()\n return\n }\n\n // Create the link\n this.#onCreateLink({\n url: url.trim(),\n title: document.getElementById(`${linkType}_link_title`).value,\n target: document.getElementById(`${linkType}_link_target`)?.value,\n type: linkType\n })\n this.close()\n }\n\n /**\n * Shows validation errors\n */\n #showValidationError() {\n const errors = document.getElementById(\"errors\")\n errors.querySelector(\"ul\").innerHTML =\n `<li>${Alchemy.t(\"url_validation_failed\")}</li>`\n errors.style.display = \"block\"\n }\n}\n","import { growl } from \"alchemy_admin/growler\"\nimport { translate } from \"alchemy_admin/i18n\"\n\nclass PreviewWindow extends HTMLIFrameElement {\n #afterLoad\n #reloadIcon\n #loadTimeout\n #previewOrigin = null\n\n handleEvent(evt) {\n if (evt.type === \"load\") {\n this.#clearLoadTimeout()\n this.#stopSpinner()\n this.#afterLoad?.call(this, evt)\n }\n }\n\n #onPreviewReady = (event) => {\n if (event.source !== this.contentWindow) return\n\n if (event.data.message === \"Alchemy.previewReady\") {\n this.#previewOrigin = event.origin\n this.#clearLoadTimeout()\n this.#stopSpinner()\n // Deliberately does not resolve #afterLoad: previewReady fires before the\n // images are loaded, so the page is not yet tall enough to scroll and a\n // scrollIntoView triggered now (e.g. focusing the saved element) is a\n // no-op. The load event handler resolves it once the layout is final.\n }\n }\n\n connectedCallback() {\n let url = this.url\n\n this.addEventListener(\"load\", this)\n this.#attachEvents()\n window.addEventListener(\"message\", this.#onPreviewReady)\n\n if (window.localStorage.getItem(\"alchemy-preview-url\")) {\n url = window.localStorage.getItem(\"alchemy-preview-url\")\n this.previewUrlSelect.value = url\n }\n\n this.refresh(url)\n }\n\n disconnectedCallback() {\n this.removeEventListener(\"load\", this)\n key.unbind(\"alt+r\")\n window.removeEventListener(\"message\", this.#onPreviewReady)\n }\n\n postMessage(data) {\n if (this.#previewOrigin) {\n this.contentWindow.postMessage(data, this.#previewOrigin)\n }\n }\n\n resize(width) {\n this.style.width = `${width}px`\n }\n\n refresh(url) {\n this.#previewOrigin = null\n this.#startSpinner()\n\n if (url) {\n this.src = url\n } else {\n this.src = this.url\n }\n\n // Set timeout as fallback - if iframe doesn't load, stop spinner anyway\n this.#clearLoadTimeout()\n this.#loadTimeout = setTimeout(() => {\n this.#stopSpinner()\n growl(translate(\"Preview failed to load\"), \"warning\")\n }, this.loadTimeout)\n\n return new Promise((resolve) => {\n this.#afterLoad = resolve\n })\n }\n\n set isDragged(dragged) {\n this.style.transitionProperty = dragged ? \"none\" : null\n this.style.pointerEvents = dragged ? \"none\" : null\n }\n\n #attachEvents() {\n this.reloadButton?.addEventListener(\"click\", (evt) => {\n evt.preventDefault()\n this.refresh()\n })\n\n key(\"alt+r\", () => this.refresh())\n\n this.sizeSelect.addEventListener(\"change\", (evt) => {\n const select = evt.target\n const width = select.value\n\n if (width === \"\") {\n this.style.width = null\n } else {\n this.resize(width)\n }\n })\n\n this.previewUrlSelect?.addEventListener(\"change\", (evt) => {\n const url = evt.target.value\n window.localStorage.setItem(\"alchemy-preview-url\", url)\n this.refresh(url)\n })\n }\n\n #startSpinner() {\n // Only save the reload icon if we're not already showing a spinner\n if (!this.reloadButton.innerHTML.includes(\"alchemy-spinner\")) {\n this.#reloadIcon = this.reloadButton.innerHTML\n }\n this.reloadButton.innerHTML = `<alchemy-spinner size=\"small\"></alchemy-spinner>`\n }\n\n #stopSpinner() {\n this.reloadButton.innerHTML = this.#reloadIcon\n }\n\n #clearLoadTimeout() {\n if (this.#loadTimeout) {\n clearTimeout(this.#loadTimeout)\n this.#loadTimeout = null\n }\n }\n\n get url() {\n return this.getAttribute(\"url\")\n }\n\n get loadTimeout() {\n const seconds = parseInt(this.getAttribute(\"load-timeout\"), 10)\n return (Number.isNaN(seconds) ? 5 : seconds) * 1000\n }\n\n get sizeSelect() {\n return document.querySelector(\"select#preview_size\")\n }\n\n get previewUrlSelect() {\n return document.querySelector(\"select#preview_url\")\n }\n\n get reloadButton() {\n return document.querySelector(\"#reload_preview_button\")\n }\n}\n\ncustomElements.define(\"alchemy-preview-window\", PreviewWindow, {\n extends: \"iframe\"\n})\n\nexport function reloadPreview() {\n const previewWindow = document.getElementById(\"alchemy_preview_window\")\n previewWindow.refresh()\n}\n","import { createHtmlElement } from \"alchemy_admin/utils/dom_helpers\"\nimport { currentDialog } from \"alchemy_admin/dialog\"\nimport { translate } from \"alchemy_admin/i18n\"\n\nconst getDefaults = () => ({\n // The default size of the dialog\n size: \"300x100\",\n title: translate(\"Please confirm\"),\n ok_label: translate(\"Yes\"),\n cancel_label: translate(\"No\"),\n on_ok() {}\n})\n\nclass ConfirmDialog {\n constructor(message, options = {}) {\n this.message = message\n this.options = { ...getDefaults(), ...options }\n this.#build()\n this.#bindEvents()\n }\n\n open() {\n requestAnimationFrame(() => {\n this.dialog.show()\n })\n }\n\n #build() {\n const width = this.options.size.split(\"x\")[0]\n this.dialog = createHtmlElement(`\n <sl-dialog label=\"${this.options.title}\" style=\"--width: ${width}px\">\n ${this.message}\n <button slot=\"footer\" type=\"reset\" class=\"secondary mx-1 my-0\" autofocus>\n ${this.options.cancel_label}\n </button>\n <button slot=\"footer\" type=\"submit\" class=\"mx-1 my-0\">\n ${this.options.ok_label}\n </button>\n </sl-dialog>\n `)\n // A native <dialog> opened with showModal() sits in the top layer and makes\n // everything outside its subtree inert. Append the confirm into the open\n // dialog (if any) so it stays visible and interactable above it, instead of\n // rendering below and unclickable on the body.\n const container = currentDialog()?.dialog_container ?? document.body\n container.append(this.dialog)\n }\n\n #bindEvents() {\n this.cancelButton.addEventListener(\"click\", (evt) => {\n evt.preventDefault()\n this.options.on_cancel()\n this.dialog.hide()\n })\n this.okButton.addEventListener(\"click\", (evt) => {\n evt.preventDefault()\n this.options.on_ok()\n this.dialog.hide()\n })\n // Prevent the dialog from closing when the user clicks on the overlay\n this.dialog.addEventListener(\"sl-request-close\", (event) => {\n if (event.detail.source === \"overlay\") {\n this.options.on_cancel()\n event.preventDefault()\n }\n })\n // Remove the dialog from the DOM after it has been hidden\n this.dialog.addEventListener(\"sl-after-hide\", () => {\n this.dialog.remove()\n })\n }\n\n get cancelButton() {\n return this.dialog.querySelector(\"button[type=reset]\")\n }\n\n get okButton() {\n return this.dialog.querySelector(\"button[type=submit]\")\n }\n}\n\n/* Opens a confirm dialog\n *\n * @param {string} message - The message that will be displayed to the user\n * @param {Object} [options={}] - Configuration options for the dialog\n * @param {string} [options.title=\"Please confirm\"] - The title of the overlay window\n * @param {string} [options.cancel_label=\"No\"] - The label of the cancel button\n * @param {string} [options.ok_label=\"Yes\"] - The label of the ok button\n *\n * @returns {Promise<void>} A promise that resolves to true when the OK button is clicked and\n * resolves to false when the cancel button is clicked. Works as confirm dialog replacement\n * for Turbo.confirm.\n */\nexport function openConfirmDialog(message, options = {}) {\n return new Promise((resolve) => {\n const dialog = new ConfirmDialog(message, {\n ...options,\n on_ok() {\n resolve(true)\n },\n on_cancel() {\n resolve(false)\n }\n })\n dialog.open()\n })\n}\n","export default class IngredientAnchorLink {\n static updateIcon(ingredientId, active = false) {\n const ingredientEditor = document.querySelector(\n `[data-ingredient-id=\"${ingredientId}\"]`\n )\n if (ingredientEditor) {\n const icon = ingredientEditor.querySelector(\n \".edit-ingredient-anchor-link alchemy-icon\"\n )\n icon.setAttribute(\"icon-style\", active ? \"fill\" : \"line\")\n }\n }\n}\n","import { Turbo } from \"@hotwired/turbo-rails\"\nimport { closeCurrentDialog } from \"alchemy_admin/dialog\"\nimport { reloadPreview } from \"alchemy_admin/components/preview_window\"\nimport { removeTab } from \"alchemy_admin/fixed_elements\"\nimport IngredientAnchorLink from \"alchemy_admin/ingredient_anchor_link\"\nimport pleaseWaitOverlay from \"alchemy_admin/please_wait_overlay\"\n\n// A dialog form submits into the dialog frame, so a redirect would be followed\n// inside the dialog and render the target page in it. The server answers a\n// successful submission with this action instead: close the dialog, then visit\n// the destination the action would have redirected to.\nTurbo.StreamActions.dialog_visit = function () {\n const url = this.getAttribute(\"url\")\n\n if (!closeCurrentDialog(() => Turbo.visit(url))) {\n Turbo.visit(url)\n }\n}\n\n// The editors observe their form field for mutations, so it must not be replaced.\n\nTurbo.StreamActions.assign_picture = function () {\n const [formField] = this.targetElements\n\n if (!formField) return\n\n formField.value = this.getAttribute(\"picture-id\")\n formField.setAttribute(\n \"data-image-file-width\",\n this.getAttribute(\"image-file-width\")\n )\n formField.setAttribute(\n \"data-image-file-height\",\n this.getAttribute(\"image-file-height\")\n )\n\n closeCurrentDialog(() => {\n formField.closest(\"alchemy-element-editor\")?.setDirty()\n })\n}\n\nTurbo.StreamActions.assign_attachment = function () {\n const [formField] = this.targetElements\n\n if (!formField) return\n\n const fileEditor = formField.parentElement\n\n formField.value = this.getAttribute(\"attachment-id\")\n fileEditor.querySelector(\":scope > .file_name\").textContent =\n this.getAttribute(\"attachment-name\")\n fileEditor\n .querySelector(\":scope > .file_icon\")\n .replaceChildren(this.templateContent)\n fileEditor\n .querySelector(\":scope > .remove_file_link\")\n .classList.remove(\"hidden\")\n\n closeCurrentDialog(() => {\n formField.closest(\"alchemy-element-editor\")?.setDirty(formField)\n })\n}\n\n// An intermediate action that closes the current dialog. This will be gone once\n// all dialogs resolve through a promise and no longer have to be closed\n// implicitly.\nTurbo.StreamActions.close_current_dialog = function () {\n closeCurrentDialog()\n}\n\nTurbo.StreamActions.reload_preview = function () {\n reloadPreview()\n}\n\nTurbo.StreamActions.remove_fixed_element = function () {\n removeTab(this.getAttribute(\"element-id\"))\n}\n\nTurbo.StreamActions.update_anchor_icon = function () {\n IngredientAnchorLink.updateIcon(\n this.getAttribute(\"ingredient-id\"),\n this.getAttribute(\"active\") === \"true\"\n )\n}\n\nTurbo.StreamActions.hide_please_wait_overlay = function () {\n pleaseWaitOverlay(false)\n}\n","class Icon extends HTMLElement {\n static get observedAttributes() {\n return [\"name\", \"size\", \"icon-style\"]\n }\n\n constructor() {\n super()\n this.spriteUrl =\n document\n .querySelector('link[rel=\"preload\"][as=\"image\"]')\n ?.getAttribute(\"href\") ?? \"\"\n }\n\n connectedCallback() {\n this.render()\n }\n\n attributeChangedCallback() {\n this.render()\n }\n\n render() {\n const sizeClass = this.size ? ` icon--${this.size}` : \"\"\n this.innerHTML = `<svg class=\"icon${sizeClass}\"><use href=\"${this.spriteUrl}#ri-${this.iconName}${this.style}\" /></svg>`\n }\n\n set name(value) {\n this.setAttribute(\"name\", value)\n }\n\n get iconName() {\n return this.getAttribute(\"name\")\n }\n\n get size() {\n return this.getAttribute(\"size\")\n }\n\n get style() {\n const value = this.getAttribute(\"icon-style\")\n switch (value) {\n case \"none\":\n return \"\"\n case null:\n return \"-line\"\n default:\n return `-${value}`\n }\n }\n}\n\n// Standalone components import this module next to the admin bundle, which\n// already defined the element from its own copy.\nif (!customElements.get(\"alchemy-icon\")) {\n customElements.define(\"alchemy-icon\", Icon)\n}\n","/**\n * Converts a scalar to its best string representation\n * for hash keys and HTML attribute values.\n *\n * Transformations:\n * 'str' -> 'str'\n * null -> ''\n * undefined -> ''\n * true -> '1'\n * false -> '0'\n * 0 -> '0'\n * 1 -> '1'\n *\n */\nexport const hash_key = (value) => {\n if (typeof value === 'undefined' || value === null)\n return null;\n return get_hash(value);\n};\nexport const get_hash = (value) => {\n if (typeof value === 'boolean')\n return value ? '1' : '0';\n return value + '';\n};\n/**\n * Escapes a string for use within HTML.\n *\n */\nexport const escape_html = (str) => {\n return (str + '')\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"');\n};\n/**\n * use setTimeout if timeout > 0\n */\nexport const timeout = (fn, timeout) => {\n if (timeout > 0) {\n return window.setTimeout(fn, timeout);\n }\n fn.call(null);\n return null;\n};\n/**\n * Debounce the user provided load function\n *\n */\nexport const loadDebounce = (fn, delay) => {\n var timeout;\n return function (value, callback) {\n var self = this;\n if (timeout) {\n self.loading = Math.max(self.loading - 1, 0);\n clearTimeout(timeout);\n }\n timeout = setTimeout(function () {\n timeout = null;\n self.loadedSearches[value] = true;\n fn.call(self, value, callback);\n }, delay);\n };\n};\n/**\n * Debounce all fired events types listed in `types`\n * while executing the provided `fn`.\n *\n */\nexport const debounce_events = (self, types, fn) => {\n var type;\n var trigger = self.trigger;\n var event_args = {};\n // override trigger method\n self.trigger = function () {\n var type = arguments[0];\n if (types.indexOf(type) !== -1) {\n event_args[type] = arguments;\n }\n else {\n return trigger.apply(self, arguments);\n }\n };\n // invoke provided function\n fn.apply(self, []);\n self.trigger = trigger;\n // trigger queued events\n for (type of types) {\n if (type in event_args) {\n trigger.apply(self, event_args[type]);\n }\n }\n};\n/**\n * Determines the current selection within a text input control.\n * Returns an object containing:\n * - start\n * - length\n *\n * Note: \"selectionStart, selectionEnd ... apply only to inputs of types text, search, URL, tel and password\"\n * \t- https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement/setSelectionRange\n */\nexport const getSelection = (input) => {\n return {\n start: input.selectionStart || 0,\n length: (input.selectionEnd || 0) - (input.selectionStart || 0),\n };\n};\n/**\n * Prevent default\n *\n */\nexport const preventDefault = (evt, stop = false) => {\n if (evt) {\n evt.preventDefault();\n if (stop) {\n evt.stopPropagation();\n }\n }\n};\n/**\n * Add event helper\n *\n */\nexport const addEvent = (target, type, callback, options) => {\n target.addEventListener(type, callback, options);\n};\n/**\n * Return true if the requested key is down\n * Will return false if more than one control character is pressed ( when [ctrl+shift+a] != [ctrl+a] )\n * The current evt may not always set ( eg calling advanceSelection() )\n *\n */\nexport const isKeyDown = (key_name, evt) => {\n if (!evt) {\n return false;\n }\n if (!evt[key_name]) {\n return false;\n }\n var count = (evt.altKey ? 1 : 0) + (evt.ctrlKey ? 1 : 0) + (evt.shiftKey ? 1 : 0) + (evt.metaKey ? 1 : 0);\n if (count === 1) {\n return true;\n }\n return false;\n};\n/**\n * Get the id of an element\n * If the id attribute is not set, set the attribute with the given id\n *\n */\nexport const getId = (el, id) => {\n const existing_id = el.getAttribute('id');\n if (existing_id) {\n return existing_id;\n }\n el.setAttribute('id', id);\n return id;\n};\n/**\n * Returns a string with backslashes added before characters that need to be escaped.\n */\nexport const addSlashes = (str) => {\n return str.replace(/[\\\\\"']/g, '\\\\$&');\n};\n/**\n *\n */\nexport const append = (parent, node) => {\n if (node)\n parent.append(node);\n};\n/**\n * Iterates over arrays and hashes.\n *\n * ```\n * iterate(this.items, function(item, id) {\n * // invoked for each item\n * });\n * ```\n *\n */\nexport const iterate = (object, callback) => {\n if (Array.isArray(object)) {\n object.forEach(callback);\n }\n else {\n for (var key in object) {\n if (object.hasOwnProperty(key)) {\n callback(object[key], key);\n }\n }\n }\n};\n//# sourceMappingURL=utils.js.map","import { translate } from \"alchemy_admin/i18n\"\nimport {\n autoUpdate,\n computePosition,\n flip,\n offset,\n shift,\n size\n} from \"@floating-ui/dom\"\n\nconst DROPDOWN_WINDOW_MARGIN = 16\nconst DROPDOWN_MIN_HEIGHT = 120\nconst DROPDOWN_MAX_WIDTH = 480\n\n// Tom Select hides the native element it enhances, so the element can not take\n// focus itself. Focus the Tom Select control instead, falling back to the\n// native focus while Tom Select has not initialized yet. Tom Select based custom\n// elements use this for their focus() override, so acting on an autofocus\n// attribute (e.g. from a dialog) lands on the visible control.\nexport function focusTomSelect(tomSelect, focusFallback) {\n if (tomSelect) {\n tomSelect.focus()\n } else {\n focusFallback()\n }\n}\n\n// Tom Select's `destroy()` removes the dropdown, but it never closes it, so the\n// `dropdown_close` handler below would not run. Close first, so a select that is\n// torn down while its dropdown is open (e.g. because a Turbo render replaces the\n// form) does not leave the full window dropdown mask behind, swallowing every\n// click.\nexport function teardownTomSelect(tomSelect) {\n tomSelect?.close(false)\n tomSelect?.destroy()\n}\n\n// Dropdown positioning shared by the Tom Select based components. It appends the\n// dropdown to the body and keeps it positioned with Floating UI so it is not\n// clipped inside dialogs or scrollable panels. The returned handlers are used as\n// Tom Select `onDropdownOpen`/`onDropdownClose` callbacks and are therefore\n// invoked with the Tom Select instance as `this`.\nexport function createDropdownPositioning() {\n const dropdownMask = document.createElement(\"div\")\n dropdownMask.className = \"ts-dropdown-mask\"\n\n let removeAutoUpdater = () => {}\n\n return {\n async onDropdownOpen() {\n // Make the dropdown at least as wide as the control.\n const styles = {\n minWidth: `${this.control.offsetWidth}px`\n }\n // If the select is inside a dialog, we need to ensure the dropdown appears above it.\n if (this.control.closest(\".alchemy-dialog-body, .alchemy-popover\")) {\n styles.zIndex = \"101\"\n }\n Object.assign(this.dropdown.style, styles)\n // Append the dropdown to the body to avoid overflow issues. Inside a\n // native modal dialog it has to go into the dialog element instead,\n // because content outside of the top layer would be inert and\n // unreachable.\n const dropdownRoot = this.control.closest(\"dialog\") || document.body\n dropdownRoot.append(dropdownMask)\n dropdownRoot.append(this.dropdown)\n // Use Floating UI to position the dropdown relative to the control.\n const updatePosition = async () => {\n // Use Floating UI to calculate the dropdown position\n const { x, y } = await computePosition(this.control, this.dropdown, {\n // Line the dropdown up with the left edge of the control. Without this\n // it is centered on the control and a dropdown grown wide by long\n // entries hangs over into the page left of the field.\n placement: \"bottom-start\",\n middleware: [\n // Make some space between the control and the dropdown to prevent overlap\n offset(2),\n // Flip to the opposite side if there’s not enough space\n flip(),\n // Keep the dropdown inside the window\n shift({ padding: DROPDOWN_WINDOW_MARGIN }),\n // Ensure the dropdown fits within the viewport\n size({\n padding: DROPDOWN_WINDOW_MARGIN,\n apply({ availableWidth, availableHeight, elements }) {\n // Keep long entries from stretching the dropdown across the\n // window, but never make it narrower than the control.\n Object.assign(elements.floating.style, {\n maxWidth: `max(${elements.reference.offsetWidth}px, min(${availableWidth}px, var(--select-dropdown-max-width, ${DROPDOWN_MAX_WIDTH}px)))`\n })\n Object.assign(\n elements.floating.querySelector(\".ts-dropdown-content\").style,\n {\n maxHeight: `${Math.max(DROPDOWN_MIN_HEIGHT, availableHeight - DROPDOWN_WINDOW_MARGIN)}px`\n }\n )\n }\n })\n ]\n })\n // Position the dropdown\n Object.assign(this.dropdown.style, {\n left: `${x}px`,\n top: `${y}px`\n })\n }\n // Update the dropdown position whenever the window resizes or scrolls.\n removeAutoUpdater = autoUpdate(\n this.control,\n this.dropdown,\n updatePosition\n )\n },\n onDropdownClose() {\n this.control_input.classList.remove(\"has-value\")\n this.control_input.blur()\n // Remove the dropdown from DOM when closed.\n this.dropdown.remove()\n dropdownMask.remove()\n // Cleanup the position auto-update when the dropdown is closed.\n removeAutoUpdater()\n }\n }\n}\n\n// Configuration for Tom Select's `remove_button` plugin. The plugin renders its\n// control as a non-semantic element (a `<div role=\"button\">` by default); render\n// a real `<button>` instead so assistive tech and the keyboard treat it as one.\n// The translated title also names the icon-only button via `aria-label`, like the\n// clear button does.\nexport function removeButton() {\n return {\n title: translate(\"Remove\"),\n html(data) {\n const button = document.createElement(\"button\")\n button.type = \"button\"\n button.className = data.className\n button.title = data.title\n button.setAttribute(\"aria-label\", data.title)\n button.tabIndex = -1\n button.innerHTML = `<alchemy-icon name=\"close\" size=\"sm\"></alchemy-icon>`\n return button\n }\n }\n}\n\n// Customize the \"create\" and \"no results\" dropdown messages with i18n. Shared by\n// the Tom Select based components as `render` functions.\nexport const dropdownMessages = {\n option_create(data, escape) {\n return `<div class=\"create\">\n ${translate(\"Add\")} <strong>${escape(data.input)}</strong>…\n </div>`\n },\n // Tom Select prepends this above the results it already has, which would\n // shift them.\n loading() {\n if (Object.keys(this.options).length > 0) return\n return `<div class=\"loading-results\">${translate(\"Please wait, loading\")}…</div>`\n },\n no_results() {\n return `<div class=\"no-results\">${translate(\"No results found\")}</div>`\n },\n // Shown at the bottom of the dropdown while the virtual scroll plugin appends\n // the next page of results.\n loading_more: () =>\n `<div class=\"loading-more\">${translate(\"Loading more results\")}…</div>`,\n // Shown at the bottom of the dropdown once every page has been loaded.\n no_more_results: () =>\n `<div class=\"no-more-results\">${translate(\"No more results\")}</div>`\n}\n","import TomSelect from \"tom-select\"\nimport \"alchemy_admin/components/icon\"\nimport { escape_html } from \"tom-select/utils\"\nimport { growl } from \"alchemy_admin/growler\"\nimport { translate } from \"alchemy_admin/i18n\"\nimport {\n createDropdownPositioning,\n dropdownMessages,\n focusTomSelect,\n teardownTomSelect\n} from \"alchemy_admin/utils/tom_select\"\n\n/**\n * Escapes a string for literal use inside a `RegExp`, so a term with\n * metacharacters (e.g. `(`) matches itself instead of being compiled as a\n * pattern, which could match the wrong text or throw a SyntaxError.\n * @param {string} value\n * @returns {string}\n */\nexport function escapeRegExp(value) {\n return String(value).replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\")\n}\n\nexport function hightlightTerm(name, term) {\n // The result is rendered raw as the option's primary text, so the name must\n // be escaped before the term markup is woven in — otherwise a crafted name\n // (e.g. a menu node title) is a stored XSS sink.\n const escapedName = escape_html(name)\n if (!term) return escapedName\n return escapedName.replace(\n new RegExp(escapeRegExp(term), \"gi\"),\n (match) => `<em>${match}</em>`\n )\n}\n\n/**\n * Serializes a (possibly nested) params object into a Rails/Ransack style query\n * string, e.g. `{ q: { name_cont: \"x\" }, page: 1 }` => `q%5Bname_cont%5D=x&page=1`.\n * @param {object} params\n * @param {string} [prefix]\n * @returns {string}\n */\nexport function serializeParams(params, prefix) {\n return Object.keys(params)\n .map((key) => {\n const value = params[key]\n const scopedKey = prefix ? `${prefix}[${key}]` : key\n if (value === null || value === undefined) return \"\"\n if (Array.isArray(value)) {\n return value\n .map(\n (item) =>\n `${encodeURIComponent(`${scopedKey}[]`)}=${encodeURIComponent(item)}`\n )\n .join(\"&\")\n }\n if (typeof value === \"object\") {\n return serializeParams(value, scopedKey)\n }\n return `${encodeURIComponent(scopedKey)}=${encodeURIComponent(value)}`\n })\n .filter((part) => part !== \"\")\n .join(\"&\")\n}\n\nexport class RemoteSelect extends HTMLElement {\n #tomSelect = null\n // The currently selected item, used to compute the added/removed delta that\n // consumers of the RemoteSelect.Change event rely on.\n #selectedItem = null\n // In multiple mode the same delta is computed against this map of the\n // currently selected records, keyed by their id.\n #selectedItems = new Map()\n\n connectedCallback() {\n const element = this.input\n element.classList.add(\"alchemy_selectbox\")\n if (element instanceof HTMLInputElement) {\n // Preselection is driven by the `selection` attribute, not by the input's\n // value. Clear the value during setup so Tom Select does not turn it into a\n // stray option and does not overwrite it with the selected id — the link\n // dialog renders a URL in this input and reads it back. Restore it afterwards.\n const inputValue = element.value\n element.value = \"\"\n this.#tomSelect = new TomSelect(element, this.tomSelectConfig)\n element.value = inputValue\n } else {\n // A multiple select wraps a <select>, which has no such value to protect.\n this.#tomSelect = new TomSelect(element, this.tomSelectConfig)\n }\n // A spinner shown inside the control while results load. Tom Select toggles\n // the wrapper's `loading` class, which reveals it (see the stylesheet).\n this.#tomSelect.control.append(document.createElement(\"sl-spinner\"))\n // Open the dropdown on click, mirroring the native <alchemy-select> (Tom\n // Select does not open on click on its own once openOnFocus is off).\n this.#tomSelect.control.addEventListener(\n \"click\",\n this.#tomSelect.open.bind(this.#tomSelect)\n )\n }\n\n disconnectedCallback() {\n teardownTomSelect(this.#tomSelect)\n this.#tomSelect = null\n }\n\n focus() {\n focusTomSelect(this.#tomSelect, () => super.focus())\n }\n\n /**\n * Optional on change handler.\n * @param {{added: ?object, removed: ?object}} event\n */\n onChange(event) {\n // Update selection attribute so re-attaching the component to the same\n // input (e.g. after dragndrop) does not reset the selection. In multiple\n // mode it holds the array of the currently selected records.\n if (this.multiple) {\n if (event.added) {\n this.#selectedItems.set(String(event.added.id), event.added)\n }\n if (event.removed) {\n this.#selectedItems.delete(String(event.removed.id))\n }\n this.setAttribute(\n \"selection\",\n JSON.stringify([...this.#selectedItems.values()])\n )\n } else if (event.added) {\n this.setAttribute(\"selection\", JSON.stringify(event.added))\n }\n this.dispatchCustomEvent(\"RemoteSelect.Change\", {\n removed: event.removed,\n added: event.added\n })\n }\n\n /**\n * Optional on open handler, called when the dropdown opens.\n * @param {Event} [event]\n */\n onOpen() {}\n\n /**\n * Dispatches a custom event with given name, namespaced under `Alchemy.`.\n * Subclasses may call this to emit their own events.\n * @param {string} name The name of the custom event\n * @param {object} detail Optional event details\n */\n dispatchCustomEvent(name, detail = {}) {\n this.dispatchEvent(\n new CustomEvent(`Alchemy.${name}`, { bubbles: true, detail })\n )\n }\n\n get allowClear() {\n return this.hasAttribute(\"allow-clear\")\n }\n\n get multiple() {\n return this.hasAttribute(\"multiple\")\n }\n\n get selection() {\n return this.getAttribute(\"selection\")\n }\n\n get placeholder() {\n return this.getAttribute(\"placeholder\") ?? \"\"\n }\n\n get queryParams() {\n return this.getAttribute(\"query-params\") ?? \"{}\"\n }\n\n get url() {\n return this.getAttribute(\"url\") ?? \"\"\n }\n\n get input() {\n // The wrapped control is an <input> in single mode and a <select> in\n // multiple mode. Tom Select keeps the original element in place and inserts\n // its own control after it, so the original always comes first.\n return this.querySelector(\"input, select\")\n }\n\n /**\n * Tom Select configuration.\n * @returns {object}\n */\n get tomSelectConfig() {\n const self = this\n const ajax = this.ajaxConfig\n const plugins = { virtual_scroll: {} }\n\n if (this.multiple) {\n plugins.remove_button = {}\n }\n\n if (this.allowClear) {\n plugins.clear_button = {\n html() {\n return `<button type=\"button\" class=\"clear-button\" aria-label=\"${translate(\n \"Clear selection\"\n )}\">\n <alchemy-icon name=\"close\" size=\"1x\"></alchemy-icon>\n </button>`\n }\n }\n }\n\n const { onDropdownOpen, onDropdownClose } = createDropdownPositioning()\n\n return {\n plugins,\n // The server returns each item with an `id` used as the option value.\n valueField: \"id\",\n // A single item unless the multiple attribute lifts the limit.\n maxItems: this.multiple ? null : 1,\n // Close the dropdown after selecting a single item; in multiple mode keep\n // it open so several items can be picked in a row.\n closeAfterSelect: !this.multiple,\n // Show every item returned by the server, not just the first 50.\n maxOptions: null,\n // Searching is done on the server, so keep every returned option.\n searchField: [],\n // Debounce the server requests while typing.\n loadThrottle: ajax.quietMillis,\n // Refresh the input state (hide the selection, show the loading dropdown)\n // on every keystroke without delay. The server request stays debounced by\n // loadThrottle, so this only affects the immediate UI feedback.\n refreshThrottle: 0,\n placeholder: this.placeholder,\n // Behave like the local <alchemy-select>: focusing neither opens the\n // dropdown nor fires a search. The first page of results is loaded when\n // the dropdown opens (see onDropdownOpen), i.e. on click or typing.\n openOnFocus: false,\n // Load even for an empty search term to show the initial result list.\n shouldLoad: () => true,\n firstUrl: (query) => this.#requestUrl(query, 1),\n load: (query, callback) => this.#load(query, callback),\n onInitialize() {\n if (!self.selection) return\n // The preselection only carries enough data to label an item. Mark each\n // one, so the option template can tell it apart from the complete records\n // the server returns. Loading the record drops the mark again.\n if (self.multiple) {\n const items = JSON.parse(self.selection)\n // Track the preselected items first, so the change handler recognizes\n // them as unchanged. Adding them silently avoids a spurious change\n // event while still rendering the items and marking the options.\n self.#selectedItems = new Map(\n items.map((item) => [String(item.id), item])\n )\n items.forEach((item) => {\n this.addOption({ ...item, $preselection: true })\n this.addItem(item.id, true)\n })\n } else {\n const item = JSON.parse(self.selection)\n // Track the preselected item first, so the change handler recognizes\n // it as unchanged. Adding it silently avoids a spurious change event\n // while still rendering the item and marking the option.\n self.#selectedItem = item\n this.addOption({ ...item, $preselection: true })\n this.addItem(item.id, true)\n }\n },\n onChange(value) {\n self.#onChange(value, this)\n },\n onType(term) {\n // While typing, give the search input an opaque background so it covers\n // the selected item behind it. Without this the previous selection shows\n // through the search box (see the `input-active` styles).\n this.control_input.classList.toggle(\"has-value\", term.length > 0)\n },\n onDropdownOpen() {\n onDropdownOpen.call(this)\n if (!this.loading) {\n this.load(this.lastValue ?? \"\")\n }\n self.onOpen()\n },\n onDropdownClose,\n render: {\n option(item, _escape) {\n // A preselection that the server has not returned yet only carries\n // enough data to label the item, so render it like the control does.\n // Everything else is a complete record and gets the list entry.\n if (item.$preselection) {\n return `<div>${self._renderResult(item)}</div>`\n }\n return self._renderListEntry(item, this.lastValue)\n },\n item(item, _escape) {\n return `<div>${self._renderResult(item)}</div>`\n },\n ...dropdownMessages\n }\n }\n }\n\n /**\n * Ajax configuration. Kept for API compatibility with subclasses that spread\n * `super.ajaxConfig` and augment it (e.g. adding request `params`).\n * @returns {object}\n */\n get ajaxConfig() {\n return {\n url: this.url,\n datatype: \"json\",\n quietMillis: 300,\n data: (term, page) => this._searchQuery(term, page),\n results: (response) => this._parseResponse(response)\n }\n }\n\n /**\n * Builds the request url for a given search term and page.\n * @param {string} term\n * @param {number} page\n * @returns {string}\n * @private\n */\n #requestUrl(term, page) {\n const query = serializeParams(this.ajaxConfig.data(term, page))\n const separator = this.url.includes(\"?\") ? \"&\" : \"?\"\n return query ? `${this.url}${separator}${query}` : this.url\n }\n\n /**\n * Loads results from the server and registers the next page url for the\n * virtual scroll plugin.\n * @param {string} query\n * @param {function} callback\n * @private\n */\n async #load(query, callback) {\n const ajax = this.ajaxConfig\n const url = this.#tomSelect.getUrl(query)\n const page = Number(\n new URL(url, window.location.origin).searchParams.get(\"page\")\n )\n try {\n const response = await fetch(url, {\n headers: { Accept: \"application/json\", ...(ajax.params?.headers ?? {}) }\n })\n // fetch only rejects on a network error, so an error response has to be\n // turned into one. Its body often parses as JSON just fine and would\n // otherwise reach the callback as an empty result set.\n if (!response.ok) {\n throw new Error(`${response.status} ${response.statusText}`)\n }\n const { results, more } = ajax.results(await response.json())\n if (more) {\n this.#tomSelect.setNextUrl(query, this.#requestUrl(query, page + 1))\n }\n callback(results)\n } catch (error) {\n growl(error.message || error, \"error\")\n console.error(`[alchemy] Loading ${url} failed`, error)\n // Tom Select only clears its loading state once the callback ran.\n callback()\n }\n }\n\n /**\n * Computes the added/removed delta from a Tom Select change and forwards it to\n * the `onChange` handler.\n * @param {string} value\n * @param {TomSelect} tomSelect\n * @private\n */\n #onChange(value, tomSelect) {\n if (this.multiple) {\n this.#onChangeMultiple(value, tomSelect)\n return\n }\n const previous = this.#selectedItem\n if (value === \"\" || value == null) {\n if (!previous) return\n this.#selectedItem = null\n this.onChange({ added: null, removed: previous })\n } else if (!previous || String(previous.id) !== String(value)) {\n const added = this.#itemData(tomSelect.options[value])\n this.#selectedItem = added\n this.onChange({ added, removed: previous })\n }\n }\n\n /**\n * Computes the per item added/removed deltas from a multiple Tom Select change\n * (whose value is the array of selected ids) by diffing it against the tracked\n * selection, and forwards each delta to the `onChange` handler.\n * @param {string[]} values\n * @param {TomSelect} tomSelect\n * @private\n */\n #onChangeMultiple(values, tomSelect) {\n const ids = (values ?? []).map(String)\n const previous = new Set(this.#selectedItems.keys())\n const current = new Set(ids)\n previous.forEach((id) => {\n if (!current.has(id)) {\n this.onChange({ added: null, removed: this.#selectedItems.get(id) })\n }\n })\n ids.forEach((id) => {\n if (!previous.has(id)) {\n this.onChange({\n added: this.#itemData(tomSelect.options[id]),\n removed: null\n })\n }\n })\n }\n\n /**\n * An option also carries the bookkeeping Tom Select keeps on it, among it the\n * node it was rendered into. Serializing that into the selection attribute\n * breaks the render cache once a component is attached to it again, so keep\n * the record itself only.\n * @param {?object} option\n * @returns {?object}\n * @private\n */\n #itemData(option) {\n if (!option) return null\n return Object.fromEntries(\n Object.entries(option).filter(([key]) => !key.startsWith(\"$\"))\n )\n }\n\n /**\n * Search query send to server.\n * @param {string} term\n * @param {number} page\n * @returns {object}\n * @private\n */\n _searchQuery(term, page) {\n return {\n q: {\n name_i_cont: term,\n ...JSON.parse(this.queryParams)\n },\n page: page\n }\n }\n\n /**\n * Parses server response into a results object.\n * @param {object} response\n * @returns {{results: Array, more: boolean}}\n * @private\n */\n _parseResponse(response) {\n const meta = response.meta\n return {\n results: response.data,\n more: meta.page * meta.per_page < meta.total_count\n }\n }\n\n /**\n * Renders a dropdown option from the slots a subclass describes in `_entry`.\n * External subclasses that still override `_renderListEntry` directly keep\n * their own markup and never reach this.\n * @param {object} item\n * @param {string} term\n * @returns {string}\n * @private\n */\n _renderListEntry(item, term) {\n return this.#renderEntry(this._entry(item, term))\n }\n\n /**\n * Renders the selected item (shown in the control) from the slots a subclass\n * describes in `_selectedEntry`. External subclasses that still override\n * `_renderResult` directly keep their own markup and never reach this.\n * @param {object} item\n * @returns {string}\n * @private\n */\n _renderResult(item) {\n return this.#renderSelection(this._selectedEntry(item))\n }\n\n /**\n * Describes the slots of a dropdown option. Subclasses override this and\n * return an object with any of `icon`/`media`, `primary`, `aside`,\n * `secondary`, and `secondaryAside`. See `#renderEntry` for the accepted\n * value shapes.\n * @param {object} item\n * @param {string} term\n * @returns {object}\n * @protected\n */\n _entry() {\n throw new Error(\"You need to define an _entry function on your sub class!\")\n }\n\n /**\n * Describes the slots of the selected item shown in the control. Subclasses\n * override this and return an object with `icon`/`media`, `primary`, and\n * `secondary`.\n * @param {object} item\n * @returns {object}\n * @protected\n */\n _selectedEntry() {\n throw new Error(\n \"You need to define a _selectedEntry function on your sub class!\"\n )\n }\n\n /**\n * Builds the two row grid markup of a dropdown option from its slots.\n * @param {object} slots\n * @returns {string}\n * @private\n */\n #renderEntry(slots) {\n return `\n <div class=\"remote-select--entry\">\n ${this.#leadColumn(slots)}\n ${this.#cell(\"remote-select--primary\", slots.primary, { raw: true })}\n ${this.#cell(\"remote-select--aside\", slots.aside)}\n ${this.#cell(\"remote-select--secondary\", slots.secondary)}\n ${this.#cell(\"remote-select--secondary-aside\", slots.secondaryAside)}\n </div>\n `\n }\n\n /**\n * Builds the single row markup of the selected item from its slots. Only the\n * leading column, the primary text, and the (fast shrinking) secondary text\n * are shown; the compact control has no room for more.\n * @param {object} slots\n * @returns {string}\n * @private\n */\n #renderSelection(slots) {\n return `\n <div class=\"remote-select--selection\">\n ${this.#leadColumn(slots)}\n ${this.#cell(\"remote-select--selection-name\", slots.primary, { raw: true })}\n ${this.#cell(\"remote-select--selection-aside\", slots.secondary)}\n </div>\n `\n }\n\n /**\n * Builds the leading column, either an icon or a media thumbnail. The two are\n * mutually exclusive; the icon wins if a subclass passes both.\n * @param {object} slots\n * @returns {string}\n * @private\n */\n #leadColumn(slots) {\n if (slots.icon) {\n return `<alchemy-icon class=\"remote-select--icon\" name=\"${escape_html(slots.icon)}\"></alchemy-icon>`\n }\n if (slots.media) {\n return `<img class=\"remote-select--media\" src=\"${escape_html(slots.media)}\" alt=\"\">`\n }\n return \"\"\n }\n\n /**\n * Renders a single cell from a slot value. Omitted or empty values render\n * nothing. A string is escaped, unless `raw` is set (the primary text is\n * pre-highlighted HTML). `{ text, truncate: \"head\" }` wraps the text in\n * `<bdi>` and truncates it from the head. `{ badge }` renders the pill style.\n * @param {string} className\n * @param {(string|object)} value\n * @param {{raw?: boolean}} [options]\n * @returns {string}\n * @private\n */\n #cell(className, value, { raw = false } = {}) {\n if (value == null || value === \"\") return \"\"\n\n if (typeof value === \"object\") {\n if (value.badge != null && value.badge !== \"\") {\n return `<span class=\"${className} remote-select--badge\">${escape_html(value.badge)}</span>`\n }\n if (value.text == null || value.text === \"\") return \"\"\n const text = raw ? value.text : escape_html(value.text)\n if (value.truncate === \"head\") {\n return `<span class=\"${className} remote-select--truncate-head\"><bdi>${text}</bdi></span>`\n }\n return `<span class=\"${className}\">${text}</span>`\n }\n\n return `<span class=\"${className}\">${raw ? value : escape_html(value)}</span>`\n }\n\n /**\n * hightlighted search term\n * @param {string} name\n * @param {string} term\n * @returns {string}\n * @private\n */\n _hightlightTerm(name, term) {\n return hightlightTerm(name, term)\n }\n}\n","import { RemoteSelect } from \"alchemy_admin/components/remote_select\"\n\nexport class AttachmentSelect extends RemoteSelect {\n _entry(attachment, term) {\n return {\n icon: attachment.icon_css_class,\n primary: this._hightlightTerm(attachment.name, term)\n }\n }\n\n _selectedEntry(attachment) {\n return {\n icon: attachment.icon_css_class,\n primary: attachment.name\n }\n }\n}\n\ncustomElements.define(\"alchemy-attachment-select\", AttachmentSelect)\n","// Dispatch a submit event on change of input or select elements\n// contained in a form, so that Turbo can submit the form.\nclass AutoSubmit extends HTMLElement {\n connectedCallback() {\n this.addEventListener(\"change\", this.#onChange)\n }\n\n disconnectedCallback() {\n this.removeEventListener(\"change\", this.#onChange)\n }\n\n #onChange = (event) => {\n // We need to dispatch a submit event, so that Turbo that listens\n // to it submits the search form us.\n const submitEvent = new Event(\"submit\", {\n bubbles: true,\n cancelable: true\n })\n event.target.form.dispatchEvent(submitEvent)\n return false\n }\n}\n\ncustomElements.define(\"alchemy-auto-submit\", AutoSubmit)\n","import Spinner from \"alchemy_admin/spinner\"\n\nclass Button extends HTMLButtonElement {\n connectedCallback() {\n if (this.form) {\n this.form.addEventListener(\"submit\", this)\n this.form.addEventListener(\"turbo:submit-end\", this)\n } else {\n console.warn(\"No form for button found!\", this)\n }\n }\n\n handleEvent(event) {\n switch (event.type) {\n case \"submit\":\n const isDisabled = this.getAttribute(\"disabled\") === \"disabled\"\n\n if (isDisabled) {\n event.preventDefault()\n event.stopPropagation()\n } else {\n this.disable()\n }\n break\n case \"turbo:submit-end\":\n this.enable()\n break\n }\n }\n\n disable() {\n const spinner = new Spinner(\"small\")\n const rect = this.getBoundingClientRect()\n\n this.dataset.initialButtonText = this.innerHTML\n this.setAttribute(\"disabled\", \"disabled\")\n this.setAttribute(\"tabindex\", \"-1\")\n this.classList.add(\"disabled\")\n this.style.width = `${rect.width}px`\n this.style.height = `${rect.height}px`\n this.innerHTML = \" \"\n\n spinner.spin(this)\n }\n\n enable() {\n this.classList.remove(\"disabled\")\n this.removeAttribute(\"disabled\")\n this.removeAttribute(\"tabindex\")\n this.style.width = null\n this.style.height = null\n this.innerHTML = this.dataset.initialButtonText\n }\n}\n\ncustomElements.define(\"alchemy-button\", Button, { extends: \"button\" })\n","/**\n * Show the character counter below input fields and textareas\n */\nimport { translate } from \"alchemy_admin/i18n\"\n\nclass CharCounter extends HTMLElement {\n connectedCallback() {\n this.translation = translate(\"allowed_chars\", this.maxChars)\n this.formField = this.getFormField()\n\n if (this.formField) {\n this.createDisplayElement()\n this.countCharacters()\n this.formField.addEventListener(\"keyup\", this)\n }\n }\n\n disconnectedCallback() {\n this.formField?.removeEventListener(\"keyup\", this)\n }\n\n handleEvent(event) {\n if (event.type === \"keyup\") this.countCharacters()\n }\n\n getFormField() {\n const formFields = this.querySelectorAll(\"input, textarea\")\n return formFields.length > 0 ? formFields[0] : undefined\n }\n\n createDisplayElement() {\n this.display = this.querySelector(\":scope > .alchemy-char-counter\")\n if (this.display) return\n this.display = document.createElement(\"small\")\n this.display.className = \"alchemy-char-counter\"\n this.formField.after(this.display)\n }\n\n countCharacters() {\n const charLength = this.formField.value.length\n this.display.textContent = `${charLength} ${this.translation}`\n this.display.classList.toggle(\"too-long\", charLength > this.maxChars)\n }\n\n get maxChars() {\n return this.getAttribute(\"max-chars\") ?? 60\n }\n}\n\ncustomElements.define(\"alchemy-char-counter\", CharCounter)\n","class ClearScheduleButton extends HTMLButtonElement {\n connectedCallback() {\n this.addEventListener(\"click\", this)\n }\n\n disconnectedCallback() {\n this.removeEventListener(\"click\", this)\n }\n\n handleEvent() {\n this.form\n ?.querySelectorAll('input[type=\"datetime-local\"]')\n .forEach((input) => {\n input.value = \"\"\n })\n }\n}\n\ncustomElements.define(\"alchemy-clear-schedule-button\", ClearScheduleButton, {\n extends: \"button\"\n})\n","import \"clipboard\"\nimport { growl } from \"alchemy_admin/growler\"\n\nclass ClipboardButton extends HTMLElement {\n connectedCallback() {\n this.innerHTML = '<alchemy-icon name=\"clipboard\"></alchemy-icon>'\n\n this.clipboard = new ClipboardJS(this, {\n text: () => {\n return this.getAttribute(\"content\")\n }\n })\n\n this.clipboard.on(\"success\", () => {\n growl(this.getAttribute(\"success-text\"))\n })\n }\n\n disconnectedCallback() {\n this.clipboard.destroy()\n }\n}\n\ncustomElements.define(\"alchemy-clipboard-button\", ClipboardButton)\n","import TomSelect from \"tom-select\"\nimport \"alchemy_admin/components/icon\"\nimport { translate } from \"alchemy_admin/i18n\"\nimport {\n createDropdownPositioning,\n dropdownMessages,\n focusTomSelect,\n removeButton,\n teardownTomSelect\n} from \"alchemy_admin/utils/tom_select\"\n\nexport class Select extends HTMLSelectElement {\n #tomSelect = null\n\n connectedCallback() {\n this.classList.add(\"alchemy_selectbox\")\n this.#initTomSelect()\n }\n\n disconnectedCallback() {\n this.#destroyTomSelect()\n }\n\n enable() {\n this.removeAttribute(\"disabled\")\n this.#tomSelect?.enable()\n }\n\n disable() {\n this.setAttribute(\"disabled\", \"disabled\")\n this.#tomSelect?.disable()\n }\n\n focus() {\n focusTomSelect(this.#tomSelect, () => super.focus())\n }\n\n setOptions(data, prompt = undefined) {\n const selectedValue = this.value\n\n // Tom Select needs to be rebuilt from the new native options, so tear it\n // down, replace the options and initialize it again.\n this.#destroyTomSelect()\n\n // reset the old options and insert the placeholder(s) first\n this.innerHTML = \"\"\n if (prompt) {\n this.add(new Option(prompt, \"\"))\n }\n\n // add the new options to the select\n data.forEach((item) => {\n this.add(new Option(item.text, item.id, false, item.id === selectedValue))\n })\n\n this.#initTomSelect()\n }\n\n get allowClear() {\n return this.dataset.hasOwnProperty(\"allowClear\") || this.multiple\n }\n\n get placeholder() {\n return this.getAttribute(\"placeholder\")\n }\n\n // Subclasses may return extra Tom Select render functions (e.g. custom\n // `option`/`item` templates) to merge on top of the defaults below.\n get renderers() {\n return {}\n }\n\n #initTomSelect() {\n const plugins = {}\n const hasPlaceholder = !!this.placeholder\n // Capture this before Tom Select initializes, since it rewrites the\n // select's selected option during setup.\n const hasSelectedOption = !!this.querySelector(\"option[selected]\")\n const { onDropdownOpen, onDropdownClose } = createDropdownPositioning()\n\n if (this.multiple) {\n plugins.remove_button = removeButton()\n }\n\n if (this.allowClear) {\n plugins.clear_button = {\n html() {\n return `<button type=\"button\" class=\"clear-button\" aria-label=\"${translate(\n \"Clear selection\"\n )}\">\n <alchemy-icon name=\"close\" size=\"1x\"></alchemy-icon>\n </button>`\n }\n }\n }\n\n const settings = {\n plugins,\n closeAfterSelect: !this.multiple,\n onInitialize: function () {\n if (this.input.autofocus) {\n this.focus()\n }\n // Tom Select auto-selects the first option when none is selected. With\n // a placeholder we want it to start empty instead, but only clear when\n // no option was explicitly marked selected, so a preselected value is\n // preserved.\n if (hasPlaceholder && !hasSelectedOption) {\n this.clear()\n }\n },\n onType(term) {\n this.control_input.classList.toggle(\"has-value\", term.length > 0)\n },\n // remove the transition after selection of option.\n refreshThrottle: 0,\n onDropdownOpen,\n onDropdownClose,\n allowEmptyOption: true,\n openOnFocus: false,\n // Keep options in their original order instead of sorting by value.\n sortField: \"$order\",\n // Show every option, not just the first 50 (e.g. the timezone select).\n maxOptions: null,\n // Customize the \"create\" and \"no results\" dropdown messages with i18n.\n render: {\n ...dropdownMessages,\n ...this.renderers\n }\n }\n\n this.#tomSelect = new TomSelect(this, settings)\n\n // Mimick the native select's click-to-open behavior.\n this.#tomSelect.control.addEventListener(\n \"click\",\n this.#tomSelect.open.bind(this.#tomSelect)\n )\n }\n\n #destroyTomSelect() {\n teardownTomSelect(this.#tomSelect)\n this.#tomSelect = null\n }\n}\n\ncustomElements.define(\"alchemy-select\", Select, { extends: \"select\" })\n","import { Select } from \"alchemy_admin/components/select\"\n\nconst renderColorOption = (data, escape) => {\n const swatch = data.swatch || data.value\n const colorIndicator =\n data.value === \"custom_color\"\n ? `<alchemy-icon name=\"palette\"></alchemy-icon>`\n : `<span class=\"color-indicator\" style=\"--color: ${escape(swatch)}\"></span>`\n\n return `\n <div class=\"select-color-option\">\n ${colorIndicator}\n <span>${escape(data.text)}</span>\n </div>`\n}\n\n// A color options select enhanced by Tom Select. It inherits from the shared\n// alchemy-select component to reuse the Tom Select setup, renders each option\n// and the selected item with a color swatch (or the custom-color icon), and\n// enables the adjacent color picker while a custom color is selected.\nexport class ColorSelect extends Select {\n connectedCallback() {\n super.connectedCallback()\n this.addEventListener(\"change\", this.#onChange)\n }\n\n disconnectedCallback() {\n super.disconnectedCallback()\n this.removeEventListener(\"change\", this.#onChange)\n }\n\n get renderers() {\n return {\n item: renderColorOption,\n option: renderColorOption\n }\n }\n\n #onChange = () => {\n const colorPicker = this.parentElement?.querySelector(\"input[type='color']\")\n if (colorPicker) {\n colorPicker.disabled = this.value !== \"custom_color\"\n }\n }\n}\n\ncustomElements.define(\"alchemy-color-select\", ColorSelect, {\n extends: \"select\"\n})\n\n// Free-form color input used when no color options are configured. Keeps the\n// text field and the native color picker in sync and enables the picker.\nexport class ColorInput extends HTMLElement {\n connectedCallback() {\n this.colorInput?.addEventListener(\"input\", this)\n this.textInput?.addEventListener(\"input\", this)\n if (this.colorInput) {\n this.colorInput.disabled = false\n }\n }\n\n handleEvent(event) {\n switch (event.target) {\n case this.colorInput:\n this.textInput.value = this.colorInput.value\n break\n case this.textInput:\n this.colorInput.value = this.textInput.value\n break\n }\n }\n\n disconnectedCallback() {\n this.colorInput?.removeEventListener(\"input\", this)\n this.textInput?.removeEventListener(\"input\", this)\n }\n\n get colorInput() {\n return this.querySelector(\"input[type='color']\")\n }\n\n get textInput() {\n return this.querySelector(\"input[type='text']\")\n }\n}\n\ncustomElements.define(\"alchemy-color-input\", ColorInput)\n","/**\n * Shows its fields only while the controlling checkbox is checked.\n *\n * Hidden fields are disabled as well, so they are not submitted and the stored\n * value is left untouched while the condition does not apply. A disabled\n * control (a fixed page attribute for example) keeps the fields disabled.\n *\n * <alchemy-conditional-field control=\"page_restricted\">\n * <input type=\"text\" name=\"page[foo]\">\n * </alchemy-conditional-field>\n */\nclass ConditionalField extends HTMLElement {\n connectedCallback() {\n this.control?.addEventListener(\"change\", this)\n this.toggle()\n }\n\n disconnectedCallback() {\n this.control?.removeEventListener(\"change\", this)\n }\n\n handleEvent(event) {\n if (event.type === \"change\") this.toggle()\n }\n\n toggle() {\n const active = !!this.control?.checked && !this.control.disabled\n\n this.hidden = !active\n this.fields.forEach((field) => {\n // Custom elements like alchemy-select need their own API to keep their\n // rendered control in sync, but might not be upgraded yet.\n if (typeof field.disable === \"function\") {\n active ? field.enable() : field.disable()\n } else {\n field.disabled = !active\n }\n })\n }\n\n get control() {\n return document.getElementById(this.getAttribute(\"control\"))\n }\n\n get fields() {\n return this.querySelectorAll(\"input, select, textarea\")\n }\n}\n\ncustomElements.define(\"alchemy-conditional-field\", ConditionalField)\n","import { translate, currentLocale } from \"alchemy_admin/i18n\"\nimport flatpickr from \"flatpickr\"\n\nconst locale = currentLocale()\n\nclass Datepicker extends HTMLElement {\n // Load the locales for flatpickr before setting it up.\n async connectedCallback() {\n // English is the default locale for flatpickr, so we don't need to load it\n if (locale !== \"en\") {\n await import(`flatpickr/${locale}.js`)\n }\n // Bail out if the element was disconnected while the locale was loading.\n // Otherwise flatpickr would leak a calendar onto a detached input.\n if (!this.isConnected) return\n\n this.flatpickr = flatpickr(this.inputField, this.flatpickrOptions)\n }\n\n disconnectedCallback() {\n this.flatpickr?.destroy()\n }\n\n get flatpickrOptions() {\n const enableTime = /time/.test(this.inputType)\n const options = {\n // alchemy_i18n supports `zh_CN` etc., but flatpickr only has two-letter codes (`zh`)\n locale: locale.slice(0, 2),\n altInput: true,\n altFormat: translate(`formats.${this.inputType}`),\n altInputClass: \"flatpickr-input\",\n enableTime,\n noCalendar: this.inputType === \"time\",\n time_24hr: translate(\"formats.time_24hr\"),\n onValueUpdate(_selectedDates, _dateStr, instance) {\n instance.element\n .closest(\"alchemy-element-editor\")\n ?.setDirty(this.inputField)\n }\n }\n\n if (enableTime) {\n options.dateFormat = \"Z\"\n }\n\n return options\n }\n\n get inputField() {\n return this.querySelector(\"input\")\n }\n\n get inputType() {\n return this.getAttribute(\"input-type\") || \"date\"\n }\n}\n\ncustomElements.define(\"alchemy-datepicker\", Datepicker)\n","import { Dialog } from \"alchemy_admin/dialog\"\n\nexport class DialogLink extends HTMLAnchorElement {\n connectedCallback() {\n this.addEventListener(\"click\", this)\n }\n\n disconnectedCallback() {\n this.removeEventListener(\"click\", this)\n }\n\n handleEvent(evt) {\n if (!this.disabled) {\n this.openDialog()\n }\n evt.preventDefault()\n }\n\n openDialog() {\n this.dialog = new Dialog(this.getAttribute(\"href\"), this.dialogOptions)\n this.dialog.open()\n }\n\n get dialogOptions() {\n const options = this.dataset.dialogOptions\n ? JSON.parse(this.dataset.dialogOptions)\n : {}\n return options\n }\n\n get disabled() {\n return this.classList.contains(\"disabled\")\n }\n}\n\ncustomElements.define(\"alchemy-dialog-link\", DialogLink, { extends: \"a\" })\n","import { Turbo } from \"@hotwired/turbo-rails\"\nimport { openConfirmDialog } from \"alchemy_admin/confirm_dialog\"\nimport { translate } from \"alchemy_admin/i18n\"\nimport pleaseWaitOverlay from \"alchemy_admin/please_wait_overlay\"\n\n// Set once the user agreed to leave, so that the navigation they agreed to is\n// not questioned again: a guarded form's redirect comes back through\n// turbo:before-visit.\nlet leaveConfirmed = false\n\n// Warns before a wrapped form takes the user away from a page that has unsaved\n// element changes. Links need no wrapping, turbo:before-visit covers them, but\n// Turbo dispatches that for a form only once its request has returned, which is\n// too late for a form that changes something on the server.\n//\n// Guards in the capture phase, so that a cancelled submit never reaches\n// Turbo's document level handlers, nor any component the guard sits in.\n//\n// Wrap an sl-tooltip, never the element inside it: Shoelace anchors to its\n// first slotted child, and this element has no box of its own to anchor to.\nclass DirtyGuard extends HTMLElement {\n connectedCallback() {\n this.addEventListener(\"submit\", this.#guard, true)\n }\n\n disconnectedCallback() {\n this.removeEventListener(\"submit\", this.#guard, true)\n }\n\n #guard = (event) => {\n if (leaveConfirmed || !isPageDirty()) return\n\n event.preventDefault()\n event.stopPropagation()\n\n const form = event.target\n confirmLeave().then((confirmed) => {\n if (confirmed) {\n pleaseWaitOverlay()\n form.requestSubmit()\n }\n })\n }\n}\n\nfunction isPageDirty() {\n return document.querySelectorAll(\"alchemy-element-editor.dirty\").length > 0\n}\n\nasync function confirmLeave() {\n const confirmed = await openConfirmDialog(translate(\"page_dirty_notice\"), {\n title: translate(\"warning\"),\n ok_label: translate(\"ok\"),\n cancel_label: translate(\"cancel\")\n })\n if (confirmed) {\n leaveConfirmed = true\n window.onbeforeunload = void 0\n }\n return confirmed\n}\n\ndocument.addEventListener(\"turbo:before-visit\", (event) => {\n if (leaveConfirmed || !isPageDirty()) return\n\n event.preventDefault()\n\n confirmLeave().then((confirmed) => {\n if (confirmed) Turbo.visit(event.detail.url)\n })\n})\n\ndocument.addEventListener(\"turbo:load\", () => {\n leaveConfirmed = false\n})\n\ncustomElements.define(\"alchemy-dirty-guard\", DirtyGuard)\n","const JSON_CONTENT_TYPE = \"application/json\"\nconst TURBO_STREAM_CONTENT_TYPE = \"text/vnd.turbo-stream.html\"\n\nfunction isGetRequest(method) {\n return method.toLowerCase() === \"get\"\n}\n\nfunction prepareURL(path, data, method) {\n const url = new URL(window.location.origin + path)\n\n if (data && isGetRequest(method)) {\n new URLSearchParams(data).forEach((value, key) => {\n url.searchParams.set(key, value)\n })\n }\n\n return url.toString()\n}\n\nfunction prepareHeaders(accept) {\n return {\n \"Content-Type\": \"application/json; charset=utf-8\",\n Accept: accept,\n \"X-Requested-With\": \"XMLHttpRequest\",\n \"X-CSRF-Token\": getToken()\n }\n}\n\nfunction prepareOptions(method, data, accept) {\n const headers = prepareHeaders(accept)\n const options = { method, headers }\n\n if (data && !isGetRequest(method)) {\n options.body = JSON.stringify(data)\n }\n\n return options\n}\n\nexport function getToken() {\n const metaTag = document.querySelector('meta[name=\"csrf-token\"]')\n return metaTag.attributes.content.textContent\n}\n\nexport function get(url, params) {\n return ajax(\"GET\", url, params)\n}\n\nexport function patch(url, data, accept) {\n return ajax(\"PATCH\", url, data, accept)\n}\n\nexport function post(url, data, accept = JSON_CONTENT_TYPE) {\n return ajax(\"POST\", url, data, accept)\n}\n\nexport default async function ajax(\n method,\n path,\n data,\n accept = JSON_CONTENT_TYPE\n) {\n const response = await fetch(\n prepareURL(path, data, method),\n prepareOptions(method, data, accept)\n )\n const contentType = response.headers.get(\"content-type\")\n const isJson = contentType?.includes(JSON_CONTENT_TYPE)\n const isTurboStream = contentType?.includes(TURBO_STREAM_CONTENT_TYPE)\n\n let responseData = null\n if (isJson) {\n responseData = await response.json()\n } else if (isTurboStream) {\n responseData = await response.text()\n // Automatically render Turbo Stream if Turbo is available\n if (typeof Turbo !== \"undefined\") {\n Turbo.renderStreamMessage(responseData)\n }\n }\n\n if (response.ok) {\n return { data: responseData, status: response.status }\n }\n\n // The session expired while the tab was open. Send the user to the login\n // page instead of leaving them with an error they cannot act on. Never\n // settle, so callers skip their error handling: the login page is what the\n // user needs to see, not a growl about a request they cannot retry.\n if (response.status === 401 && responseData?.redirect_url) {\n Turbo.visit(responseData.redirect_url)\n return new Promise(() => {})\n }\n\n throw responseData || new Error(\"An error occurred during the transaction\")\n}\n","import { get } from \"alchemy_admin/utils/ajax\"\nimport { translate } from \"alchemy_admin/i18n\"\nimport { growl } from \"alchemy_admin/growler\"\nimport { routes } from \"alchemy_admin/routes\"\n\nclass DomIdSelect extends HTMLElement {\n dataItem(hash) {\n return {\n id: `#${hash}`,\n text: `#${hash}`\n }\n }\n\n get selectElement() {\n return this.querySelector('select[is=\"alchemy-select\"]')\n }\n}\n\nclass DomIdApiSelect extends DomIdSelect {\n #pageId = undefined\n\n connectedCallback() {\n this.page = this.getAttribute(\"page\")\n }\n\n async #fetchDomIds() {\n try {\n const result = await get(routes.api_ingredients_path, {\n page_id: this.#pageId\n })\n const options = result.data.ingredients\n .filter((ingredient) => ingredient.data?.dom_id)\n .map((ingredient) => this.dataItem(ingredient.data.dom_id))\n const prompt =\n options.length > 0 ? translate(\"None\") : translate(\"No anchors found\")\n\n this.selectElement.setOptions(options, prompt)\n this.selectElement.enable()\n } catch (error) {\n growl(error.message || error, \"error\")\n console.error(error)\n }\n }\n\n #reset() {\n // wait a tick to initialize the alchemy-select\n requestAnimationFrame(() => {\n this.selectElement.disable()\n this.selectElement.setOptions([], translate(\"Select a page first\"))\n })\n }\n\n set page(pageId) {\n this.#pageId = pageId\n pageId ? this.#fetchDomIds() : this.#reset()\n }\n}\n\nclass DomIdPreviewSelect extends DomIdSelect {\n connectedCallback() {\n // wait a tick to let the browser initialize the inner select component\n requestAnimationFrame(() => {\n const frame = document.getElementById(\"alchemy_preview_window\")\n const elements = frame.contentDocument?.querySelectorAll(\"[id]\") || []\n if (elements.length > 0) {\n const options = Array.from(elements).map((element) => {\n return this.dataItem(element.id)\n })\n this.selectElement.setOptions(options, translate(\"None\"))\n }\n })\n }\n}\n\ncustomElements.define(\"alchemy-dom-id-api-select\", DomIdApiSelect)\ncustomElements.define(\"alchemy-dom-id-preview-select\", DomIdPreviewSelect)\n","export class PublishElementButton extends HTMLElement {\n #scheduleButtonVariant\n\n connectedCallback() {\n this.#scheduleButtonVariant = this.scheduleButton.getAttribute(\"variant\")\n this.publishButton.addEventListener(\"click\", this)\n this.dropdown.addEventListener(\"sl-show\", this)\n this.dropdown.addEventListener(\"sl-hide\", this)\n }\n\n disconnectedCallback() {\n this.publishButton.removeEventListener(\"click\", this)\n this.dropdown.removeEventListener(\"sl-show\", this)\n this.dropdown.removeEventListener(\"sl-hide\", this)\n }\n\n handleEvent(event) {\n switch (event.type) {\n case \"click\":\n this.publishButton.loading = true\n break\n case \"sl-show\":\n this.scheduleButton.setAttribute(\"variant\", \"primary\")\n break\n case \"sl-hide\":\n this.scheduleButton.setAttribute(\"variant\", this.#scheduleButtonVariant)\n break\n }\n }\n\n get publishButton() {\n return this.querySelector(\"sl-button[type='submit']\")\n }\n\n get dropdown() {\n return this.querySelector(\"sl-dropdown\")\n }\n\n get scheduleButton() {\n return this.querySelector(\"sl-button[slot='trigger']\")\n }\n}\n\ncustomElements.define(\"alchemy-publish-element-button\", PublishElementButton)\n","export function dispatchPageDirtyEvent(data) {\n document.dispatchEvent(\n new CustomEvent(\"alchemy:page-dirty\", {\n detail: { tooltip: data.publishButtonTooltip }\n })\n )\n}\n","import ajax from \"alchemy_admin/utils/ajax\"\nimport { removeTab } from \"alchemy_admin/fixed_elements\"\nimport { growl } from \"alchemy_admin/growler\"\nimport { reloadPreview } from \"alchemy_admin/components/preview_window\"\nimport { openConfirmDialog } from \"alchemy_admin/confirm_dialog\"\nimport { dispatchPageDirtyEvent } from \"alchemy_admin/utils/dispatch_page_dirty_event\"\n\nexport class DeleteElementButton extends HTMLElement {\n connectedCallback() {\n this.button?.addEventListener(\"click\", this)\n }\n\n disconnectedCallback() {\n this.button?.removeEventListener(\"click\", this)\n }\n\n async handleEvent() {\n const confirmed = await openConfirmDialog(this.message)\n if (confirmed) {\n try {\n const response = await ajax(\"DELETE\", this.url)\n this.#removeElement(response.data)\n } catch (error) {\n growl(error.message || error, \"error\")\n console.error(error)\n }\n }\n }\n\n #removeElement(data) {\n const elementEditor = this.closest(\"alchemy-element-editor\")\n elementEditor.addEventListener(\"transitionend\", () => {\n if (elementEditor.fixed) {\n removeTab(elementEditor.elementId)\n }\n elementEditor.remove()\n })\n elementEditor.classList.add(\"dismiss\")\n growl(data.message)\n if (data.pageHasUnpublishedChanges) {\n dispatchPageDirtyEvent(data)\n }\n reloadPreview()\n }\n\n get url() {\n return this.getAttribute(\"href\")\n }\n\n get message() {\n return this.getAttribute(\"message\")\n }\n\n get button() {\n return this.querySelector(\"button\")\n }\n}\n\ncustomElements.define(\"alchemy-delete-element-button\", DeleteElementButton)\n","import { post } from \"alchemy_admin/utils/ajax\"\nimport { growl } from \"alchemy_admin/growler\"\n\nimport \"alchemy_admin/components/element_editor/publish_element_button\"\nimport \"alchemy_admin/components/element_editor/delete_element_button\"\nimport { routes } from \"alchemy_admin/routes\"\n\nexport class ElementEditor extends HTMLElement {\n #form = null\n #header = null\n #toggleButton = null\n\n connectedCallback() {\n // The placeholder while be being dragged is empty.\n if (this.classList.contains(\"ui-sortable-placeholder\")) {\n return\n }\n\n // Add event listeners\n this.addEventListener(\"click\", this)\n // Triggered by child elements\n this.addEventListener(\"alchemy:element-update-title\", this)\n\n this.#form = this.form\n if (this.#form) {\n this.#form.addEventListener(\"change\", this.onChange)\n this.#form.addEventListener(\"turbo:submit-start\", this)\n this.#form.addEventListener(\"turbo:submit-end\", this)\n }\n\n this.#header = this.header\n this.#header?.addEventListener(\"dblclick\", this.#onHeaderDblclick)\n\n this.#toggleButton = this.toggleButton\n this.#toggleButton?.addEventListener(\"click\", this.#onToggleClick)\n\n // When newly created, focus the element and refresh the preview\n if (this.hasAttribute(\"created\")) {\n this.focusElement()\n this.previewWindow?.refresh().then(() => {\n this.focusElementPreview()\n })\n this.removeAttribute(\"created\")\n }\n }\n\n disconnectedCallback() {\n this.removeEventListener(\"click\", this)\n this.removeEventListener(\"alchemy:element-update-title\", this)\n if (this.#form) {\n this.#form.removeEventListener(\"change\", this.onChange)\n this.#form.removeEventListener(\"turbo:submit-start\", this)\n this.#form.removeEventListener(\"turbo:submit-end\", this)\n this.#form = null\n }\n this.#header?.removeEventListener(\"dblclick\", this.#onHeaderDblclick)\n this.#header = null\n this.#toggleButton?.removeEventListener(\"click\", this.#onToggleClick)\n this.#toggleButton = null\n }\n\n handleEvent(event) {\n switch (event.type) {\n case \"click\":\n const elementEditor = event.target.closest(\"alchemy-element-editor\")\n if (elementEditor === this) {\n this.onClickElement()\n }\n break\n case \"turbo:submit-start\":\n this.setClean()\n break\n case \"turbo:submit-end\":\n this.onSaveElement(event.detail.success)\n break\n case \"alchemy:element-update-title\":\n if (!this.hasEditors && event.target == this.firstChild) {\n this.setTitle(event.detail.title)\n }\n break\n }\n }\n\n onChange = (event) => {\n const target = event.target\n // SortableJS fires a native change event :/\n // and we do not want to set the element editor dirty\n // when this happens\n if (target.classList.contains(\"nested-elements\")) {\n return\n }\n this.setDirty(target)\n event.stopPropagation()\n return false\n }\n\n /**\n * Scrolls to and highlights element\n * Expands if collapsed\n * Also chooses the right fixed elements tab, if necessary.\n * Can be triggered through custom event 'FocusElementEditor.Alchemy'\n * Used by the elements on click events in the preview frame.\n */\n async focusElement() {\n // Select tab if necessary\n if (document.querySelector(\"#fixed-elements\")) {\n await this.selectTabForElement()\n }\n // Expand if necessary\n await this.expand()\n this.selectElement(true)\n }\n\n focusElementPreview() {\n this.previewWindow?.postMessage({\n message: \"Alchemy.focusElement\",\n element_id: this.elementId\n })\n }\n\n onClickElement() {\n this.selectElement()\n this.focusElementPreview()\n }\n\n /**\n * Applies the client-side effects of a save once the turbo stream rendered.\n *\n * The notice, error markup, header title, anchor icons and publish button are\n * server-rendered turbo streams. Only the preview refresh (which must focus\n * this element afterwards) and the error box toggle remain client-side.\n * @argument {boolean} success\n */\n onSaveElement(success) {\n if (success) {\n this.previewWindow?.refresh().then(() => {\n this.focusElementPreview()\n })\n } else {\n this.elementErrors.classList.remove(\"hidden\")\n }\n }\n\n /**\n * Smoothly scrolls to element\n */\n scrollToElement() {\n // The timeout gives the browser some time to calculate the position\n // of nested elements correctly\n setTimeout(() => {\n this.scrollIntoView({\n behavior: \"smooth\"\n })\n }, 50)\n }\n\n /**\n * Highlight element and optionally scroll into view\n * @param {boolean} scroll smoothly scroll element into view. Default (false)\n */\n selectElement(scroll = false) {\n document\n .querySelectorAll(\"alchemy-element-editor.selected\")\n .forEach((el) => {\n el.classList.remove(\"selected\")\n })\n window.requestAnimationFrame(() => {\n this.classList.add(\"selected\")\n })\n if (scroll) this.scrollToElement()\n }\n\n /**\n * Selects tab for given element\n * Resolves the promise if this is done.\n * @returns {Promise}\n */\n selectTabForElement() {\n return new Promise((resolve, reject) => {\n const tabs = document.querySelector(\"#fixed-elements\")\n const panel = this.closest(\"sl-tab-panel\")\n if (tabs && panel) {\n tabs.show(panel.getAttribute(\"name\"))\n resolve()\n } else {\n reject(new Error(\"No tabs present\"))\n }\n })\n }\n\n /**\n * Sets the element into clean (safed) state\n */\n setClean() {\n this.dirty = false\n window.onbeforeunload = null\n this.elementErrors.classList.add(\"hidden\")\n\n if (this.hasEditors) {\n this.body.querySelectorAll(\".ingredient-editor\").forEach((el) => {\n el.classList.remove(\"dirty\", \"validation_failed\")\n el.querySelectorAll(\"small.error\").forEach((e) => e.remove())\n })\n }\n }\n\n /**\n * Sets the element into dirty (unsafed) state\n * @param {HTMLElement} editor\n */\n setDirty(editor) {\n if (this.hasEditors) {\n this.dirty = true\n\n if (!window.onbeforeunload) {\n window.onbeforeunload = (event) => event.preventDefault()\n }\n\n editor?.closest(\".ingredient-editor\")?.classList.add(\"dirty\")\n }\n }\n\n /**\n * Sets the title quote\n * @param {string} title\n */\n setTitle(title) {\n const quote = this.querySelector(\".element-header .preview_text_quote\")\n // Fixed elements have no header, so there is no quote to update.\n if (quote) {\n quote.textContent = title\n }\n }\n\n /**\n * Expands or collapses element editor\n * If the element is dirty (has unsaved changes) it displays a confirm first.\n */\n async toggle() {\n if (this.collapsed) {\n await this.expand()\n } else {\n await this.collapse()\n }\n }\n\n /**\n * Collapses the element editor and persists the state on the server\n * @returns {Promise}\n */\n collapse() {\n if (this.collapsed || this.compact || this.fixed) {\n return Promise.resolve(\"Element is already collapsed.\")\n }\n\n const spinner = new Alchemy.Spinner(\"small\")\n spinner.spin(this.toggleButton)\n this.toggleIcon?.classList?.add(\"hidden\")\n return post(routes.collapse_admin_element_path(this.elementId))\n .then((response) => {\n const data = response.data\n\n this.collapsed = true\n this.toggleButton?.setAttribute(\"title\", data.title)\n\n // Collapse all nested elements if necessarry\n if (data.nestedElementIds.length) {\n const selector = data.nestedElementIds\n .map((id) => `#element_${id}`)\n .join(\", \")\n this.querySelectorAll(selector).forEach((nestedElement) => {\n nestedElement.collapsed = true\n nestedElement.toggleButton?.setAttribute(\"title\", data.title)\n })\n }\n })\n .catch((error) => {\n growl(error.message, \"error\")\n console.error(error)\n })\n .finally(() => {\n this.toggleIcon?.classList?.remove(\"hidden\")\n spinner.stop()\n })\n }\n\n /**\n * Collapses the element editor and persists the state on the server\n * @* @returns {Promise}\n */\n expand() {\n if (this.expanded && !this.compact) {\n return Promise.resolve(\"Element is already expanded.\")\n }\n\n if (this.compact && this.parentElementEditor) {\n return this.parentElementEditor.expand()\n } else {\n const spinner = new Alchemy.Spinner(\"small\")\n spinner.spin(this.toggleButton)\n this.toggleIcon?.classList.add(\"hidden\")\n\n return new Promise((resolve, reject) => {\n post(routes.expand_admin_element_path(this.elementId))\n .then((response) => {\n const data = response.data\n\n // First expand all parent elements if necessary\n if (data.parentElementIds.length) {\n const selector = data.parentElementIds\n .map((id) => `#element_${id}`)\n .join(\", \")\n document.querySelectorAll(selector).forEach((parentElement) => {\n parentElement.collapsed = false\n parentElement.toggleButton?.setAttribute(\"title\", data.title)\n })\n }\n // Finally expand ourselve\n this.collapsed = false\n this.toggleButton?.setAttribute(\"title\", data.title)\n // Resolve the promise that scrolls to the element very last\n resolve()\n })\n .catch((error) => {\n growl(error.message, \"error\")\n console.error(error)\n reject(error)\n })\n .finally(() => {\n this.toggleIcon?.classList?.remove(\"hidden\")\n spinner.stop()\n })\n })\n }\n }\n\n /**\n * Updates the quote in the element header and dispatches event\n * to parent elements\n * @param {string} title\n */\n updateTitle(title) {\n this.setTitle(title)\n this.dispatchEvent(\n new CustomEvent(\"alchemy:element-update-title\", {\n bubbles: true,\n detail: { title }\n })\n )\n }\n\n /**\n * Sets element published or hidden\n * @param {boolean}\n */\n set published(isPublished) {\n if (isPublished) {\n this.classList.remove(\"element-hidden\")\n } else {\n this.classList.add(\"element-hidden\")\n }\n }\n\n /**\n * Is element published or hidden\n * @returns {boolean}\n */\n get published() {\n return !this.classList.contains(\"hidden\")\n }\n\n /**\n * @returns {boolean}\n */\n get compact() {\n return this.getAttribute(\"compact\") !== null\n }\n\n /**\n * @returns {boolean}\n */\n get fixed() {\n return this.getAttribute(\"fixed\") !== null\n }\n\n /**\n * @param {boolean} value\n */\n set collapsed(value) {\n this.classList.toggle(\"folded\", value)\n this.classList.toggle(\"expanded\", !value)\n this.toggleIcon &&\n (this.toggleIcon.name = value ? \"arrow-left-s\" : \"arrow-down-s\")\n }\n\n /**\n * @returns {boolean}\n */\n get collapsed() {\n return this.classList.contains(\"folded\")\n }\n\n /**\n * @returns {boolean}\n */\n get expanded() {\n return !this.collapsed\n }\n\n /**\n * Toggles the dirty class\n *\n * @param {boolean} value\n */\n set dirty(value) {\n this.classList.toggle(\"dirty\", value)\n }\n\n /**\n * Returns the dirty state of this element\n *\n * @returns {boolean}\n */\n get dirty() {\n return this.classList.contains(\"dirty\")\n }\n\n /**\n * Returns the element header\n *\n * @returns {HTMLElement|undefined}\n */\n get header() {\n return this.querySelector(`.element-header`)\n }\n\n /**\n * Returns the immediate body container of this element if present\n *\n * Makes sure it does not return a nested elements body\n * by scoping the selector to this elements id.\n *\n * @returns {HTMLElement|undefined}\n */\n get body() {\n return this.querySelector(this.bodySelector)\n }\n\n get bodySelector() {\n return `#${this.id} > .element-body`\n }\n\n /**\n * Returns the immediate footer container of this element if present\n *\n * Makes sure it does not return a nested elements footer\n * by scoping the selector to this elements id.\n *\n * @returns {HTMLElement|undefined}\n */\n get footer() {\n return this.querySelector(`#${this.id} > .element-footer`)\n }\n\n /**\n * The collapse/expand toggle button\n *\n * @returns {HTMLButtonElement|undefined}\n */\n get toggleButton() {\n return this.querySelector(\".element-toggle\")\n }\n\n /**\n * The collapse/expand toggle buttons icon\n *\n * @returns {HTMLElement|undefined}\n */\n get toggleIcon() {\n return this.toggleButton?.querySelector(\"alchemy-icon\")\n }\n\n /**\n * The validation messages list container\n *\n * @returns {HTMLElement}\n */\n get elementErrors() {\n return this.body.querySelector(\".element_errors\")\n }\n\n /**\n * The element database id\n *\n * @returns {string}\n */\n get elementId() {\n return this.dataset.elementId\n }\n\n /**\n * The element defintion name\n *\n * @returns {string}\n */\n get elementName() {\n return this.dataset.elementName\n }\n\n /**\n * Does this element have ingredient editor fields?\n *\n * @returns {boolean}\n */\n get hasEditors() {\n return !!this.body?.querySelector(\".element-ingredient-editors\")\n }\n\n /**\n * Does this element have nested elements?\n *\n * @returns {boolean}\n */\n get hasChildren() {\n return !!this.querySelector(\".nested-elements\")\n }\n\n /**\n * The first child element editor if present\n *\n * @returns {HTMLButtonElement|undefined}\n */\n get firstChild() {\n return this.querySelector(\"alchemy-element-editor\")\n }\n\n /**\n * The form element if present\n *\n * Scoped to this element's own form so a wrapper element without ingredients\n * does not return a nested child's form.\n *\n * @returns {HTMLFormElement|undefined}\n */\n get form() {\n return this.querySelector(`#${this.id} > form.element-body`)\n }\n\n /**\n * The parent element editor if present\n *\n * @returns {ElementEditor|undefined}\n */\n get parentElementEditor() {\n return this.parentElement?.closest(\"alchemy-element-editor\")\n }\n\n get previewWindow() {\n return document.getElementById(\"alchemy_preview_window\")\n }\n\n #onHeaderDblclick = () => {\n this.toggle()\n }\n\n #onToggleClick = (evt) => {\n const elementEditor = evt.target.closest(\"alchemy-element-editor\")\n if (elementEditor === this) {\n this.toggle()\n }\n }\n}\n\ncustomElements.define(\"alchemy-element-editor\", ElementEditor)\n","import { Select } from \"alchemy_admin/components/select\"\n\nconst renderName = (icon, text) => `\n <div class=\"element-select-name\">\n ${icon}<span>${text}</span>\n </div>\n`\n\nexport class ElementSelect extends Select {\n get renderers() {\n return {\n item: (data, escape) => renderName(data.icon, escape(data.text)),\n option: (data, escape) => {\n const description = data.hint\n ? `<div class=\"element-select-description\">${escape(data.hint)}</div>`\n : \"\"\n return `\n <div class=\"element-select-item\">\n ${renderName(data.icon, escape(data.text))}\n ${description}\n </div>\n `\n }\n }\n }\n}\n\ncustomElements.define(\"alchemy-element-select\", ElementSelect, {\n extends: \"select\"\n})\n","import { ElementEditor } from \"alchemy_admin/components/element_editor\"\n\nclass ElementsWindow extends HTMLElement {\n #visible = true\n #turboFrame = null\n\n connectedCallback() {\n this.toggleButton?.addEventListener(\"click\", this.#onToggleClick)\n this.collapseButton?.addEventListener(\"click\", this.#onCollapseClick)\n window.addEventListener(\"message\", this.#onWindowMessage)\n document.body.addEventListener(\"click\", this.#onBodyClick)\n if (window.location.hash) {\n this.focusElementEditor(window.location.hash)\n }\n this.resize()\n }\n\n disconnectedCallback() {\n this.toggleButton?.removeEventListener(\"click\", this.#onToggleClick)\n this.collapseButton?.removeEventListener(\"click\", this.#onCollapseClick)\n window.removeEventListener(\"message\", this.#onWindowMessage)\n document.body.removeEventListener(\"click\", this.#onBodyClick)\n }\n\n #onToggleClick = (evt) => {\n evt.preventDefault()\n this.toggle()\n }\n\n #onCollapseClick = () => {\n this.collapseAllElements()\n }\n\n #onWindowMessage = (event) => {\n if (event.source !== this.previewWindow?.contentWindow) return\n\n const data = event.data\n if (data?.message == \"Alchemy.focusElementEditor\") {\n const element = document.getElementById(`element_${data.element_id}`)\n this.show()\n element?.focusElement()\n }\n }\n\n #onBodyClick = (evt) => {\n if (!evt.target.closest(\"alchemy-element-editor\")) {\n this.querySelectorAll(\"alchemy-element-editor\").forEach((editor) => {\n editor.classList.remove(\"selected\")\n })\n this.previewWindow?.postMessage({ message: \"Alchemy.blurElements\" })\n }\n }\n\n collapseAllElements() {\n this.querySelectorAll(\n \"alchemy-element-editor:not([compact]):not([fixed])\"\n ).forEach((editor) => editor.collapse())\n }\n\n toggle() {\n this.#visible ? this.hide() : this.show()\n }\n\n show() {\n document.body.classList.add(\"elements-window-visible\")\n this.#visible = true\n this.toggleButton.closest(\"sl-tooltip\").content = Alchemy.t(\"Hide elements\")\n this.toggleButton\n .querySelector(\"alchemy-icon\")\n .setAttribute(\"name\", \"menu-unfold\")\n this.resize()\n }\n\n hide() {\n document.body.classList.remove(\"elements-window-visible\")\n document.body.style.removeProperty(\"--elements-window-width\")\n this.#visible = false\n this.toggleButton.closest(\"sl-tooltip\").content = Alchemy.t(\"Show elements\")\n this.toggleButton\n .querySelector(\"alchemy-icon\")\n .setAttribute(\"name\", \"menu-fold\")\n }\n\n resize(width) {\n if (width === undefined) {\n width = this.widthFromCookie\n }\n\n if (width) {\n document.body.style.setProperty(\"--elements-window-width\", `${width}px`)\n document.cookie = `alchemy-elements-window-width=${width}; SameSite=Lax; Path=/;`\n }\n }\n\n // Focus element editor and element in preview if URL contains hash to element editor.\n // The link is coming from the assignment view when showing assigned files or pictures.\n focusElementEditor(dom_id) {\n const el = document.querySelector(dom_id)\n\n if (el instanceof ElementEditor) {\n el.focusElement() && el.focusElementPreview()\n }\n }\n\n get collapseButton() {\n return this.querySelector(\"#collapse-all-elements-button\")\n }\n\n get toggleButton() {\n return document.querySelector(\"#element_window_button\")\n }\n\n get previewWindow() {\n return document.getElementById(\"alchemy_preview_window\")\n }\n\n get turboFrame() {\n if (!this.#turboFrame) {\n this.#turboFrame = this.closest(\"turbo-frame\")\n }\n return this.#turboFrame\n }\n\n get widthFromCookie() {\n return document.cookie\n .split(\"; \")\n .find((row) => row.startsWith(\"alchemy-elements-window-width=\"))\n ?.split(\"=\")[1]\n }\n\n set isDragged(dragged) {\n this.turboFrame.style.transitionProperty = dragged ? \"none\" : null\n this.turboFrame.style.pointerEvents = dragged ? \"none\" : null\n }\n}\n\ncustomElements.define(\"alchemy-elements-window\", ElementsWindow)\n","const MIN_WIDTH = 400\nconst MAX_WIDTH = 1000\nclass ElementsWindowHandle extends HTMLElement {\n #dragging = false\n #elementsWindow = null\n #previewWindow = null\n #minWidth = MIN_WIDTH\n #maxWidth = MAX_WIDTH\n\n connectedCallback() {\n this.addEventListener(\"mousedown\", this)\n window.addEventListener(\"mousemove\", this)\n window.addEventListener(\"mouseup\", this)\n }\n\n disconnectedCallback() {\n this.removeEventListener(\"mousedown\", this)\n window.removeEventListener(\"mousemove\", this)\n window.removeEventListener(\"mouseup\", this)\n }\n\n handleEvent(event) {\n switch (event.type) {\n case \"mousedown\":\n event.stopPropagation()\n this.onMouseDown()\n break\n case \"mouseup\":\n this.onMouseUp()\n break\n case \"mousemove\":\n if (this.#dragging) {\n this.onDrag(event.pageX)\n }\n break\n }\n }\n\n onMouseDown() {\n this.#dragging = true\n // Read the resolved min/max width the browser computed from the CSS\n // custom properties (incl. calc() and active media queries) once, so the\n // drag stays clamped to the same bounds the stylesheet defines.\n const styles = getComputedStyle(this.elementsWindow)\n this.#minWidth = parseFloat(styles.minWidth) || MIN_WIDTH\n this.#maxWidth =\n styles.maxWidth === \"none\" ? MAX_WIDTH : parseFloat(styles.maxWidth)\n this.elementsWindow.isDragged = true\n this.previewWindow.isDragged = true\n this.classList.add(\"is-dragged\")\n }\n\n onMouseUp() {\n this.#dragging = false\n this.elementsWindow.isDragged = false\n this.previewWindow.isDragged = false\n this.classList.remove(\"is-dragged\")\n }\n\n onDrag(pageX) {\n const elementWindowWidth = window.innerWidth - pageX\n const width = Math.min(\n Math.max(elementWindowWidth, this.#minWidth),\n this.#maxWidth\n )\n this.elementsWindow.resize(width)\n }\n\n get elementsWindow() {\n if (!this.#elementsWindow) {\n this.#elementsWindow = document.querySelector(\"alchemy-elements-window\")\n }\n return this.#elementsWindow\n }\n\n get previewWindow() {\n if (!this.#previewWindow) {\n this.#previewWindow = document.getElementById(\"alchemy_preview_window\")\n }\n return this.#previewWindow\n }\n}\n\ncustomElements.define(\"alchemy-elements-window-handle\", ElementsWindowHandle)\n","class FileEditor extends HTMLElement {\n connectedCallback() {\n this.deleteLink = this.querySelector(\".remove_file_link\")\n this.fileIcon = this.querySelector(\".file_icon\")\n this.fileName = this.querySelector(\".file_name\")\n this.formFieldId = this.deleteLink?.dataset.formFieldId\n this.formField = this.querySelector(`#${this.formFieldId}`)\n this.deleteLink?.addEventListener(\"click\", this)\n }\n\n disconnectedCallback() {\n this.deleteLink?.removeEventListener(\"click\", this)\n }\n\n handleEvent(event) {\n if (event.type === \"click\") this.removeFile()\n event.stopPropagation()\n }\n\n removeFile() {\n this.formField.value = \"\"\n this.fileIcon.innerHTML = \"\"\n this.fileName.innerHTML = \"\"\n this.deleteLink?.classList.add(\"hidden\")\n this.closest(\"alchemy-element-editor\").setDirty(this.formField)\n }\n}\n\ncustomElements.define(\"alchemy-file-editor\", FileEditor)\n","const DEFAULT_DEBOUNCE_TIME = 150\n\nclass ListFilter extends HTMLElement {\n #debounceTimer\n #filterField = null\n #clearButton = null\n\n connectedCallback() {\n if (this.hotkey) {\n key(this.hotkey, this.#onHotkey)\n }\n this.#filterField = this.filterField\n this.#filterField.addEventListener(\"keyup\", this.#onKeyup)\n this.#filterField.addEventListener(\"focus\", this.#onFocus)\n\n this.#clearButton = this.clearButton\n this.#clearButton.addEventListener(\"click\", this.#onClearClick)\n\n key(\"esc\", \"list_filter\", this.#onEscape)\n }\n\n disconnectedCallback() {\n clearTimeout(this.#debounceTimer)\n this.#filterField?.removeEventListener(\"keyup\", this.#onKeyup)\n this.#filterField?.removeEventListener(\"focus\", this.#onFocus)\n this.#filterField = null\n this.#clearButton?.removeEventListener(\"click\", this.#onClearClick)\n this.#clearButton = null\n if (this.hotkey) {\n key.unbind(this.hotkey)\n }\n key.unbind(\"esc\", \"list_filter\")\n }\n\n filter(term) {\n if (term === \"\") {\n this.clearButton.style.visibility = \"hidden\"\n }\n\n const matchedItems = []\n const itemsToShow = new Set()\n const lowerTerm = term.toLowerCase()\n\n // First pass: find matching items and mark their ancestors as visible too\n this.items.forEach((item) => {\n const name = item.getAttribute(this.nameAttribute)?.toLowerCase()\n // indexOf is much faster then match()\n if (name.indexOf(lowerTerm) !== -1) {\n matchedItems.push(item)\n itemsToShow.add(item)\n // Mark ancestor items as visible so nested matches stay visible\n let ancestor = item.parentElement?.closest(this.itemsSelector)\n while (ancestor) {\n itemsToShow.add(ancestor)\n ancestor = ancestor.parentElement?.closest(this.itemsSelector)\n }\n }\n })\n\n // Second pass: apply visibility\n this.items.forEach((item) => {\n item.classList.toggle(\"hidden\", !itemsToShow.has(item))\n })\n\n // Scroll into view if only one match\n if (matchedItems.length === 1) {\n matchedItems[0].scrollIntoView({ behavior: \"smooth\", block: \"nearest\" })\n }\n }\n\n clear() {\n this.filterField.value = \"\"\n this.clearButton.style.visibility = \"hidden\"\n this.items.forEach((item) => item.classList.remove(\"hidden\"))\n }\n\n get nameAttribute() {\n return this.getAttribute(\"name-attribute\") || \"name\"\n }\n\n get clearButton() {\n return this.querySelector('button[type=\"button\"]')\n }\n\n get filterField() {\n return this.querySelector('input[type=\"text\"]')\n }\n\n get items() {\n return document.querySelectorAll(this.itemsSelector)\n }\n\n get itemsSelector() {\n return this.getAttribute(\"items-selector\")\n }\n\n get debounceTime() {\n return parseInt(this.getAttribute(\"debounce-time\")) || DEFAULT_DEBOUNCE_TIME\n }\n\n get hotkey() {\n return this.getAttribute(\"hotkey\")\n }\n\n #onHotkey = () => {\n this.filterField.focus()\n return false\n }\n\n #onKeyup = () => {\n clearTimeout(this.#debounceTimer)\n this.#debounceTimer = setTimeout(() => {\n const term = this.filterField.value\n this.clearButton.style.visibility = term ? \"visible\" : \"hidden\"\n this.filter(term)\n }, this.debounceTime)\n }\n\n #onFocus = () => key.setScope(\"list_filter\")\n\n #onClearClick = (e) => {\n e.preventDefault()\n this.clear()\n }\n\n #onEscape = () => {\n this.clear()\n this.filterField.blur()\n }\n}\n\ncustomElements.define(\"alchemy-list-filter\", ListFilter)\n","import { autoUpdate, computePosition, flip } from \"@floating-ui/dom\"\n\nconst SCREEN_PADDING = 8\n\n// A main navigation entry lives inside the fixed, vertically scrollable menu\n// column. A scroll container clips its content in both axes, so the hover\n// flyout (this entry's label or its sub navigation) that reaches out to the\n// right of the column would be cut off. While the flyout is open we lift it out\n// of the clipping context by fixing it to the viewport and let Floating UI keep\n// it anchored to the entry.\nclass MainNaviEntry extends HTMLElement {\n #flyout = null\n #stopAutoUpdate = null\n\n connectedCallback() {\n this.addEventListener(\"mouseenter\", this.#open)\n this.addEventListener(\"mouseleave\", this.#close)\n }\n\n disconnectedCallback() {\n this.removeEventListener(\"mouseenter\", this.#open)\n this.removeEventListener(\"mouseleave\", this.#close)\n this.#close()\n }\n\n #open = () => {\n const flyout = this.#flyoutElement()\n if (!flyout) return\n\n this.#flyout = flyout\n this.#stopAutoUpdate = autoUpdate(this, flyout, () => {\n computePosition(this, flyout, {\n strategy: \"fixed\",\n placement: \"right-start\",\n // Keep the flyout anchored to the entry: when there is no room below,\n // flip to bottom alignment instead of drifting away from the entry.\n middleware: [\n flip({ fallbackPlacements: [\"right-end\"], padding: SCREEN_PADDING })\n ]\n }).then(({ x, y }) => {\n Object.assign(flyout.style, {\n position: \"fixed\",\n left: `${Math.round(x)}px`,\n top: `${Math.round(y)}px`\n })\n })\n })\n }\n\n #close = () => {\n this.#stopAutoUpdate?.()\n this.#stopAutoUpdate = null\n\n if (this.#flyout) {\n this.#flyout.style.position = \"\"\n this.#flyout.style.left = \"\"\n this.#flyout.style.top = \"\"\n this.#flyout = null\n }\n }\n\n // The entry's flyout element, but only when it is actually rendered as a\n // flyout. In the expanded menu the label and sub navigation sit in the normal\n // document flow (position: static) and must be left untouched.\n #flyoutElement() {\n const flyout =\n this.querySelector(\".sub_navigation\") || this.querySelector(\"label\")\n if (!flyout || getComputedStyle(flyout).position === \"static\") return null\n return flyout\n }\n}\n\ncustomElements.define(\"alchemy-main-navi-entry\", MainNaviEntry)\n\nexport default MainNaviEntry\n","import { patch } from \"alchemy_admin/utils/ajax\"\nimport { growl } from \"alchemy_admin/growler\"\nimport { routes } from \"alchemy_admin/routes\"\n\n/**\n * Custom element for menu nodes in the nodes tree\n * Handles folding and unfolding of its children\n */\nexport class AlchemyMenuNode extends HTMLElement {\n connectedCallback() {\n this.addEventListener(\"click\", this)\n }\n\n disconnectedCallback() {\n this.removeEventListener(\"click\", this)\n }\n\n handleEvent(event) {\n // Clicks of nested menu nodes bubble up here as well\n if (event.target.closest(\"alchemy-node-folder\") === this.folder) {\n this.toggleFolded(event)\n }\n }\n\n async toggleFolded(event) {\n event.preventDefault()\n\n try {\n await patch(routes.node.toggle_folded_api_path(this.nodeId))\n this.folded = !this.folded\n } catch (error) {\n growl(error.message || error, \"error\")\n }\n }\n\n /**\n * Shows the fold toggle only for nodes that have something to fold\n */\n updateFolder() {\n this.folder.hidden = !(this.hasChildren || this.folded)\n }\n\n get folded() {\n return this.hasAttribute(\"folded\")\n }\n\n set folded(value) {\n this.toggleAttribute(\"folded\", value)\n this.folder.toggleAttribute(\"folded\", value)\n this.childrenList.classList.toggle(\"folded\", value)\n }\n\n get hasChildren() {\n return (\n this.childrenList.querySelectorAll(\":scope > alchemy-menu-node\").length >\n 0\n )\n }\n\n get nodeId() {\n return this.getAttribute(\"node-id\")\n }\n\n get folder() {\n return this.querySelector(\":scope > li > .sitemap_node alchemy-node-folder\")\n }\n\n get childrenList() {\n return this.querySelector(\":scope > li > ul.children\")\n }\n}\n\ncustomElements.define(\"alchemy-menu-node\", AlchemyMenuNode)\n","const DISMISS_DELAY = 5000\n\nclass Message extends HTMLElement {\n #dismissTimeoutId = null\n\n handleEvent(event) {\n if (event.type === \"click\") {\n this.dismiss()\n }\n }\n\n connectedCallback() {\n if (!this.querySelector(\":scope > alchemy-icon\")) {\n const closeIcon =\n this.dismissable && this.type === \"error\"\n ? '<alchemy-icon name=\"close\"></alchemy-icon>'\n : \"\"\n this.insertAdjacentHTML(\n \"afterbegin\",\n `<alchemy-icon name=\"${this.iconName}\"></alchemy-icon>${closeIcon}`\n )\n }\n if (this.dismissable || this.type === \"error\") {\n this.addEventListener(\"click\", this)\n }\n if (this.dismissable && this.type !== \"error\") {\n this.#dismissTimeoutId = setTimeout(() => {\n this.dismiss()\n }, this.dismissDelay)\n }\n }\n\n disconnectedCallback() {\n if (this.#dismissTimeoutId !== null) {\n clearTimeout(this.#dismissTimeoutId)\n this.#dismissTimeoutId = null\n }\n }\n\n dismiss() {\n this.addEventListener(\"transitionend\", () => this.remove())\n this.classList.add(\"dismissed\")\n }\n\n get dismissable() {\n return this.hasAttribute(\"dismissable\")\n }\n\n get icon() {\n return this.getAttribute(\"icon\")\n }\n\n get type() {\n return this.getAttribute(\"type\") || \"notice\"\n }\n\n get dismissDelay() {\n return parseInt(\n this.noticesWrapper?.dataset.autoDismissDelay || DISMISS_DELAY\n )\n }\n\n get iconName() {\n switch (this.icon || this.type) {\n case \"warning\":\n case \"warn\":\n case \"alert\":\n return \"alert\"\n case \"notice\":\n return \"check\"\n case \"info\":\n case \"hint\":\n return \"information\"\n case \"error\":\n return \"bug\"\n default:\n return this.type\n }\n }\n\n get noticesWrapper() {\n return this.closest(\"#flash_notices\")\n }\n}\n\ncustomElements.define(\"alchemy-message\", Message)\n","import { growl } from \"alchemy_admin/growler\"\n\nclass Growl extends HTMLElement {\n connectedCallback() {\n growl(this.message, this.getAttribute(\"type\") || \"notice\")\n this.remove()\n }\n\n get message() {\n return this.getAttribute(\"message\") || this.innerHTML\n }\n}\n\ncustomElements.define(\"alchemy-growl\", Growl)\n","import Cropper from \"cropperjs\"\nimport { currentDialog } from \"alchemy_admin/dialog\"\n\nexport class ImageCropper extends HTMLElement {\n #cropper = null\n #cropFromField = null\n #cropSizeField = null\n\n connectedCallback() {\n this.image = this.querySelector(\"img\")\n this.form = this.querySelector(\"form\")\n this.#cropFromField = document.getElementById(\n this.getAttribute(\"crop-from-field-id\")\n )\n this.#cropSizeField = document.getElementById(\n this.getAttribute(\"crop-size-field-id\")\n )\n this.elementEditor = document.querySelector(\n `[data-element-id='${this.getAttribute(\"element-id\")}']`\n )\n this.form.addEventListener(\"submit\", this.#onSubmit)\n this.form.addEventListener(\"reset\", this.#onReset)\n this.#cropper = new Cropper(this.image, this.cropperOptions)\n }\n\n disconnectedCallback() {\n this.#cropper?.destroy()\n }\n\n get cropperOptions() {\n return {\n aspectRatio: this.aspectRatio,\n viewMode: 1,\n zoomable: false,\n checkCrossOrigin: false, // Prevent CORS issues\n checkOrientation: false, // Prevent loading the image via AJAX which can cause CORS issues\n data: this.box\n }\n }\n\n get aspectRatio() {\n const ratio = this.getAttribute(\"ratio\")\n // NaN lets cropperjs use a free aspect ratio.\n return ratio ? parseFloat(ratio) : NaN\n }\n\n get cropFrom() {\n if (this.#cropFromField?.value) {\n return this.#cropFromField.value.split(\"x\").map((v) => parseInt(v))\n }\n }\n\n get cropSize() {\n if (this.#cropSizeField?.value) {\n return this.#cropSizeField.value.split(\"x\").map((v) => parseInt(v))\n }\n }\n\n get box() {\n if (this.cropFrom && this.cropSize) {\n return {\n x: this.cropFrom[0],\n y: this.cropFrom[1],\n width: this.cropSize[0],\n height: this.cropSize[1]\n }\n } else {\n return this.defaultBox\n }\n }\n\n get defaultBox() {\n const box = JSON.parse(this.getAttribute(\"default-box\"))\n return { x: box[0], y: box[1], width: box[2], height: box[3] }\n }\n\n update(coords) {\n this.#cropFromField.value = `${coords.x}x${coords.y}`\n this.#cropFromField.dispatchEvent(new Event(\"change\"))\n this.#cropSizeField.value = `${coords.width}x${coords.height}`\n this.#cropSizeField.dispatchEvent(new Event(\"change\"))\n }\n\n reset() {\n const cropper = this.#cropper\n // Apply the default size. cropperjs clamps the crop box to its maximum size.\n cropper.setData(this.defaultBox)\n // When the default box sits at the maximum crop box size, sub-pixel rounding\n // makes cropperjs treat setData's box as oversized and revert its position\n // (renderCropBox resets top/left to their old values). Re-apply the position\n // in canvas coordinates afterwards – that does not touch the size, so it is\n // not reverted and the mask actually moves to the default box.\n const canvas = cropper.getCanvasData()\n const scale = canvas.width / canvas.naturalWidth\n cropper.setCropBoxData({\n left: canvas.left + this.defaultBox.x * scale,\n top: canvas.top + this.defaultBox.y * scale\n })\n this.update(this.defaultBox)\n }\n\n #onSubmit = (event) => {\n event.preventDefault()\n this.update(this.#cropper.getData(true))\n this.elementEditor?.setDirty()\n currentDialog()?.close()\n }\n\n #onReset = (event) => {\n event.preventDefault()\n this.reset()\n }\n}\n\ncustomElements.define(\"alchemy-image-cropper\", ImageCropper)\n","import { Dialog } from \"alchemy_admin/dialog\"\nimport { createHtmlElement } from \"alchemy_admin/utils/dom_helpers\"\n\nexport default class ImageOverlay extends Dialog {\n // The picture is rendered in a Turbo frame that replaces its content on every\n // navigation, so the picture elements are looked up on demand instead of being\n // bound once.\n #clickHandler = (e) => {\n if (e.target.closest(\".picture-overlay-handle\")) {\n e.preventDefault()\n this.dialog.classList.toggle(\"hide-form\")\n return\n }\n if (e.target.closest(\".zoomed-picture-background\")) {\n e.stopPropagation()\n if (e.target.nodeName === \"IMG\") {\n return\n }\n this.close()\n }\n }\n\n #keydownHandler = (e) => {\n if (e.target.nodeName === \"INPUT\" || e.target.nodeName === \"TEXTAREA\") {\n return\n }\n switch (e.key) {\n case \"ArrowLeft\":\n this.previous()\n e.preventDefault()\n break\n case \"ArrowRight\":\n this.next()\n e.preventDefault()\n break\n }\n }\n\n // The overlay always fills the viewport via its stylesheet, so the inline\n // dimensions the generic dialog applies would only cap it.\n resize() {}\n\n init() {\n this.dialog_body.removeEventListener(\"click\", this.#clickHandler)\n this.dialog_body.addEventListener(\"click\", this.#clickHandler)\n this.#initKeyboardNavigation()\n super.init()\n }\n\n previous() {\n this.dialog_body.querySelector(\".previous-picture\")?.click()\n }\n\n next() {\n this.dialog_body.querySelector(\".next-picture\")?.click()\n }\n\n close() {\n document.removeEventListener(\"keydown\", this.#keydownHandler)\n return super.close()\n }\n\n build() {\n this.dialog_container = createHtmlElement(\n '<dialog class=\"alchemy-image-overlay-container\"></dialog>'\n )\n this.dialog = createHtmlElement(\n '<div class=\"alchemy-image-overlay-dialog\"></div>'\n )\n this.dialog_body = createHtmlElement(\n '<div class=\"alchemy-image-overlay-body\"></div>'\n )\n this.close_button =\n createHtmlElement(`<a class=\"alchemy-image-overlay-close\">\n <alchemy-icon name=\"close\" size=\"xl\"></alchemy-icon>\n </a>`)\n this.dialog.append(this.close_button)\n this.dialog.append(this.dialog_body)\n this.dialog_container.append(this.dialog)\n document.body.append(this.dialog_container)\n }\n\n #initKeyboardNavigation() {\n document.removeEventListener(\"keydown\", this.#keydownHandler)\n document.addEventListener(\"keydown\", this.#keydownHandler)\n }\n}\n","import { DialogLink } from \"alchemy_admin/components/dialog_link\"\nimport ImageOverlay from \"alchemy_admin/image_overlay\"\n\n// Opens the picture in a full screen image overlay instead of navigating to it\nexport class ImageOverlayLink extends DialogLink {\n openDialog() {\n this.dialog = new ImageOverlay(this.href, { padding: false })\n this.dialog.open()\n }\n}\n\ncustomElements.define(\"alchemy-image-overlay-link\", ImageOverlayLink, {\n extends: \"a\"\n})\n","export class IngredientGroup extends HTMLDetailsElement {\n #localStorageKey = \"Alchemy.expanded_ingredient_groups\"\n\n connectedCallback() {\n this.addEventListener(\"toggle\", this)\n\n if (this.isInLocalStorage) {\n this.open = true\n }\n }\n\n disconnectedCallback() {\n this.removeEventListener(\"toggle\", this)\n }\n\n /**\n * Toggle visibility of the ingredient fields in this group\n */\n handleEvent() {\n let expanded_ingredient_groups = this.localStorageItem\n\n if (this.open) {\n this.toggleIcon.name = \"arrow-down-s\"\n if (!this.isInLocalStorage) expanded_ingredient_groups.push(this.id)\n } else {\n this.toggleIcon.name = \"arrow-right-s\"\n expanded_ingredient_groups = expanded_ingredient_groups.filter(\n (value) => value !== this.id\n )\n }\n\n localStorage.setItem(\n this.#localStorageKey,\n JSON.stringify(expanded_ingredient_groups)\n )\n }\n\n get isInLocalStorage() {\n return this.localStorageItem.includes(this.id)\n }\n\n get localStorageItem() {\n const item = localStorage.getItem(this.#localStorageKey)\n\n if (!item) return []\n\n try {\n return JSON.parse(item)\n } catch (error) {\n console.error(error)\n return []\n }\n }\n\n get toggleIcon() {\n return this.querySelector(\"alchemy-icon\")\n }\n}\n\ncustomElements.define(\"alchemy-ingredient-group\", IngredientGroup, {\n extends: \"details\"\n})\n","class LinkButton extends HTMLButtonElement {\n connectedCallback() {\n this.addEventListener(\"click\", this)\n this.classList.add(\"icon_button\")\n // Prevent accidental form submits if this component is wrapped inside a form\n this.setAttribute(\"type\", \"button\")\n this.innerHTML = '<alchemy-icon name=\"link\" icon-style=\"m\"></alchemy-icon>'\n }\n\n handleEvent(event) {\n const dialog = new Alchemy.LinkDialog({\n url: this.linkUrl,\n title: this.linkTitle,\n target: this.linkTarget,\n type: this.linkClass\n })\n dialog.open().then((link) => this.setLink(link))\n event.preventDefault()\n }\n\n setLink(link) {\n if (link.url === \"\") {\n this.classList.remove(\"linked\")\n this.dispatchEvent(new CustomEvent(\"alchemy:unlink\", { bubbles: true }))\n } else {\n this.classList.add(\"linked\")\n this.dispatchEvent(\n new CustomEvent(\"alchemy:link\", {\n bubbles: true,\n detail: link\n })\n )\n }\n }\n\n get linkUrl() {\n return this.linkButtons.linkUrlField.value\n }\n\n get linkTitle() {\n return this.linkButtons.linkTitleField.value\n }\n\n get linkTarget() {\n return this.linkButtons.linkTargetField.value\n }\n\n get linkClass() {\n return this.linkButtons.linkClassField.value\n }\n\n get linkButtons() {\n return this.closest(\"alchemy-link-buttons\")\n }\n}\n\ncustomElements.define(\"alchemy-link-button\", LinkButton, { extends: \"button\" })\n","class UnlinkButton extends HTMLButtonElement {\n connectedCallback() {\n this.addEventListener(\"click\", this)\n this.classList.add(\"icon_button\")\n // Prevent accidental form submits if this component is wrapped inside a form\n this.setAttribute(\"type\", \"button\")\n this.linked = this.linked\n this.innerHTML =\n '<alchemy-icon name=\"link-unlink\" icon-style=\"m\"></alchemy-icon>'\n }\n\n handleEvent(event) {\n if (this.linked) {\n this.linked = false\n this.blur()\n this.dispatchEvent(new CustomEvent(\"alchemy:unlink\", { bubbles: true }))\n }\n event.preventDefault()\n }\n\n set linked(isLinked) {\n if (isLinked) {\n this.classList.replace(\"disabled\", \"linked\")\n this.removeAttribute(\"tabindex\")\n } else {\n this.classList.replace(\"linked\", \"disabled\")\n this.setAttribute(\"tabindex\", \"-1\")\n }\n }\n\n get linked() {\n return this.classList.contains(\"linked\")\n }\n}\n\ncustomElements.define(\"alchemy-unlink-button\", UnlinkButton, {\n extends: \"button\"\n})\n","import \"alchemy_admin/components/link_buttons/link_button\"\nimport \"alchemy_admin/components/link_buttons/unlink_button\"\n\nclass LinkButtons extends HTMLElement {\n connectedCallback() {\n this.addEventListener(\"alchemy:link\", this)\n this.addEventListener(\"alchemy:unlink\", this)\n }\n\n disconnectedCallback() {\n this.removeEventListener(\"alchemy:link\", this)\n this.removeEventListener(\"alchemy:unlink\", this)\n }\n\n handleEvent(event) {\n switch (event.type) {\n case \"alchemy:link\":\n this.setLink(event.detail)\n break\n case \"alchemy:unlink\":\n this.removeLink()\n }\n event.stopPropagation()\n }\n\n setLink(data) {\n this.linkUrlField.value = data.url\n this.linkUrlField.dispatchEvent(new Event(\"change\"))\n this.linkTitleField.value = data.title\n this.linkClassField.value = data.type\n this.linkTargetField.value = data.target\n\n this.unlinkButton.linked = true\n this.setElementDirty()\n }\n\n removeLink() {\n this.linkUrlField.value = \"\"\n this.linkUrlField.dispatchEvent(new Event(\"change\"))\n this.linkTitleField.value = \"\"\n this.linkClassField.value = \"\"\n this.linkTargetField.value = \"\"\n\n this.linkButton.classList.remove(\"linked\")\n this.unlinkButton.linked = false\n\n this.setElementDirty()\n }\n\n setElementDirty() {\n this.elementEditor.setDirty(this)\n }\n\n get linkButton() {\n return this.querySelector('[is=\"alchemy-link-button\"]')\n }\n\n get unlinkButton() {\n return this.querySelector('[is=\"alchemy-unlink-button\"]')\n }\n\n get ingredientEditor() {\n const ingredientId = this.dataset.ingredientId\n return this.parentElement.closest(`[data-ingredient-id='${ingredientId}']`)\n }\n\n get elementEditor() {\n return this.closest(\"alchemy-element-editor\")\n }\n\n get linkUrlField() {\n return this.ingredientEditor.querySelector(\"[data-link-value]\")\n }\n\n get linkTitleField() {\n return this.ingredientEditor.querySelector(\"[data-link-title]\")\n }\n\n get linkTargetField() {\n return this.ingredientEditor.querySelector(\"[data-link-target]\")\n }\n\n get linkClassField() {\n return this.ingredientEditor.querySelector(\"[data-link-class]\")\n }\n}\n\ncustomElements.define(\"alchemy-link-buttons\", LinkButtons)\n","/**\n * Auto-rotating carousel for the dashboard news widget.\n *\n * Paging is handled by CSS scroll snap on `.news-reader--track`; this component\n * scrolls to the next slide on a timer (pausing while hovered), lets the user\n * swipe/scroll manually, and keeps the dot indicators in sync with the scroll\n * position.\n */\nclass NewsReader extends HTMLElement {\n connectedCallback() {\n this.track = this.querySelector(\".news-reader--track\")\n this.slides = Array.from(this.querySelectorAll(\".news-reader--item\"))\n if (!this.track || this.slides.length < 2) return\n\n this.currentIndex = 0\n this.createDots()\n this.setActive(0)\n this.startRotation()\n\n this.addEventListener(\"mouseenter\", this)\n this.addEventListener(\"mouseleave\", this)\n this.track.addEventListener(\"scroll\", this, { passive: true })\n }\n\n disconnectedCallback() {\n this.stopRotation()\n }\n\n handleEvent(event) {\n switch (event.type) {\n case \"mouseenter\":\n this.stopRotation()\n break\n case \"mouseleave\":\n this.startRotation()\n break\n case \"scroll\":\n this.syncActiveToScroll()\n break\n }\n }\n\n createDots() {\n this.dotList = document.createElement(\"div\")\n this.dotList.className = \"news-reader--dots\"\n this.dots = this.slides.map((_, index) => {\n const dot = document.createElement(\"button\")\n dot.type = \"button\"\n dot.className = \"news-reader--dot\"\n dot.setAttribute(\"aria-label\", `${index + 1}`)\n dot.addEventListener(\"click\", () => {\n this.goTo(index)\n this.startRotation()\n })\n this.dotList.append(dot)\n return dot\n })\n this.append(this.dotList)\n }\n\n goTo(index) {\n this.currentIndex = index\n this.slides[index].scrollIntoView({\n behavior: \"smooth\",\n inline: \"start\",\n block: \"nearest\"\n })\n this.setActive(index)\n }\n\n next() {\n this.goTo((this.currentIndex + 1) % this.slides.length)\n }\n\n // Keep the active dot in sync when the user scrolls the track manually.\n syncActiveToScroll() {\n const width = this.track.clientWidth\n if (!width) return\n const index = Math.round(this.track.scrollLeft / width)\n if (index !== this.currentIndex && this.slides[index]) {\n this.currentIndex = index\n this.setActive(index)\n }\n }\n\n setActive(index) {\n this.dots.forEach((dot, i) => {\n dot.classList.toggle(\"is-active\", i === index)\n })\n }\n\n startRotation() {\n this.stopRotation()\n this.timer = window.setInterval(() => this.next(), this.interval)\n }\n\n stopRotation() {\n if (this.timer) window.clearInterval(this.timer)\n this.timer = null\n }\n\n get interval() {\n return parseInt(this.getAttribute(\"interval\"), 10) || 8000\n }\n}\n\ncustomElements.define(\"alchemy-news-reader\", NewsReader)\n\nexport default NewsReader\n","/**\n * Custom element for the fold toggle of a menu node\n * Renders the fold arrow and keeps it in sync with the folded attribute\n */\nexport class AlchemyNodeFolder extends HTMLElement {\n static get observedAttributes() {\n return [\"folded\"]\n }\n\n connectedCallback() {\n this.innerHTML = `\n <button class=\"node_folder icon_button\">\n <alchemy-icon name=\"${this.iconName}\"></alchemy-icon>\n </button>\n `\n }\n\n attributeChangedCallback() {\n // Fires before connectedCallback for attributes present in the markup\n this.icon?.setAttribute(\"name\", this.iconName)\n }\n\n get icon() {\n return this.querySelector(\"alchemy-icon\")\n }\n\n get iconName() {\n return this.folded ? \"arrow-right-s\" : \"arrow-down-s\"\n }\n\n get folded() {\n return this.hasAttribute(\"folded\")\n }\n}\n\ncustomElements.define(\"alchemy-node-folder\", AlchemyNodeFolder)\n","// Syncs the node name and url fields with the selected page\nexport class NodeForm extends HTMLElement {\n connectedCallback() {\n this.addEventListener(\"Alchemy.RemoteSelect.Change\", this.#onPageSelect)\n }\n\n disconnectedCallback() {\n this.removeEventListener(\"Alchemy.RemoteSelect.Change\", this.#onPageSelect)\n }\n\n #onPageSelect = (event) => {\n const page = event.detail.added\n\n if (page) {\n this.nameField.setAttribute(\"placeholder\", page.name)\n this.urlField.value = page.url_path\n this.urlField.setAttribute(\"disabled\", \"disabled\")\n } else {\n this.nameField.removeAttribute(\"placeholder\")\n this.urlField.value = \"\"\n this.urlField.removeAttribute(\"disabled\")\n }\n }\n\n get nameField() {\n return this.querySelector(\"#node_name\")\n }\n\n get urlField() {\n return this.querySelector(\"#node_url\")\n }\n}\n\ncustomElements.define(\"alchemy-node-form\", NodeForm)\n","import { RemoteSelect } from \"alchemy_admin/components/remote_select\"\nimport { escape_html } from \"tom-select/utils\"\n\nclass NodeSelect extends RemoteSelect {\n _searchQuery(term, page) {\n return {\n filter: {\n name_or_page_name_i_cont: term,\n ...JSON.parse(this.queryParams)\n },\n page: page\n }\n }\n\n _entry(node, term) {\n return {\n icon: \"menu-2\",\n primary: this.#breadcrumb(node) + this._hightlightTerm(node.name, term),\n secondary: { text: node.url, truncate: \"head\" }\n }\n }\n\n _selectedEntry(node) {\n return {\n icon: \"menu-2\",\n primary: this.#breadcrumb(node) + escape_html(node.name)\n }\n }\n\n /**\n * Renders the node's ancestors as a breadcrumb that precedes its name. The\n * ancestors place the node in the tree, so a deeply nested node reads clearly.\n * @param {object} node\n * @returns {string}\n */\n #breadcrumb(node) {\n const ancestors = node.ancestors.map((ancestor) => ancestor.name)\n if (ancestors.length === 0) return \"\"\n const separator = `<alchemy-icon name=\"arrow-right-s\" class=\"node-select--separator\"></alchemy-icon>`\n return `<span class=\"node-select--ancestors\">${ancestors\n .map(escape_html)\n .join(separator)}</span>${separator}`\n }\n}\n\ncustomElements.define(\"alchemy-node-select\", NodeSelect)\n","import Sortable from \"sortablejs\"\nimport { patch } from \"alchemy_admin/utils/ajax\"\nimport { growl } from \"alchemy_admin/growler\"\nimport { translate } from \"alchemy_admin/i18n\"\nimport { routes } from \"alchemy_admin/routes\"\n\n/**\n * Custom element for the nodes tree container\n * Handles drag and drop sorting of its menu nodes\n */\nexport class AlchemyNodeTree extends HTMLElement {\n connectedCallback() {\n this.querySelectorAll(\"ul.children\").forEach((list) => {\n new Sortable(list, {\n group: \"nodes\",\n animation: 150,\n fallbackOnBody: true,\n swapThreshold: 0.65,\n handle: \".node_name\",\n invertSwap: true,\n draggable: \"alchemy-menu-node\",\n onEnd: (event) => this.handleSort(event)\n })\n })\n }\n\n async handleSort(event) {\n if (event.from === event.to && event.oldIndex === event.newIndex) {\n return\n }\n\n const url = routes.node.move_api_path(event.item.nodeId)\n const data = {\n target_parent_id: event.to.dataset.recordId,\n new_position: event.newIndex\n }\n\n try {\n await patch(url, data)\n this.updateFolders(event.from, event.to)\n growl(translate(\"Successfully moved menu item\"))\n } catch (error) {\n growl(error.message || error, \"error\")\n }\n }\n\n updateFolders(from, to) {\n from.closest(\"alchemy-menu-node\")?.updateFolder()\n\n if (from !== to) {\n to.closest(\"alchemy-menu-node\")?.updateFolder()\n }\n }\n}\n\ncustomElements.define(\"alchemy-node-tree\", AlchemyNodeTree)\n","export function formatFileSize(bytes) {\n let exponent = bytes === 0 ? 0 : Math.floor(Math.log(bytes) / Math.log(1024))\n\n // prevent format higher than GB\n if (exponent > 3) {\n exponent = 3\n }\n\n let value = (bytes / Math.pow(1024, exponent)).toFixed(2)\n return value + \" \" + [\"B\", \"kB\", \"MB\", \"GB\"][exponent]\n}\n","import { formatFileSize } from \"alchemy_admin/utils/format\"\nimport { translate } from \"alchemy_admin/i18n\"\nimport { growl } from \"alchemy_admin/growler\"\nimport { readJSONScript } from \"alchemy_admin/utils/json_script\"\n\nexport class FileUpload extends HTMLElement {\n // public — used by callers (Uploader, Progress, tests)\n file = null\n request = null\n progressEventLoaded = 0\n progressEventTotal = 0\n\n // private — backing state for getters/setters\n #valid = true\n #value = 0\n #status = undefined\n #errorMessage = \"\"\n\n connectedCallback() {\n this.innerHTML = `\n <sl-progress-bar value=\"${this.value}\"></sl-progress-bar>\n <div class=\"description\">\n <span class=\"file-name\">${this.file?.name}</span>\n <span class=\"loaded-size\">${this.loadedSize}</span>\n <span class=\"error-message\">${this.errorMessage}</span>\n </div>\n <sl-tooltip content=\"${translate(\"Abort upload\")}\">\n <button class=\"icon_button\" aria-label=\"${translate(\"Abort upload\")}\">\n <alchemy-icon name=\"close\"></alchemy-icon>\n </button>\n </sl-tooltip>\n `\n\n this.querySelector(\"button\").addEventListener(\"click\", () => this.cancel())\n\n if (this.file?.type.includes(\"image\")) {\n const reader = new FileReader()\n reader.readAsDataURL(this.file)\n reader.addEventListener(\"load\", () => {\n const image = new Image()\n image.src = reader.result\n this.prepend(image)\n })\n }\n }\n\n /**\n * Initialize the component with file and request\n * @param {File} file\n * @param {XMLHttpRequest} request\n */\n initialize(file, request) {\n this.file = file\n this.request = request\n this.progressEventTotal = file ? file.size : 0\n this.status = \"in-progress\"\n\n this.#validateFile()\n this.#addRequestEventListener()\n }\n\n /**\n * cancel the upload\n */\n cancel() {\n if (!this.finished) {\n this.status = \"canceled\"\n this.request?.abort()\n this.dispatchCustomEvent(\"FileUpload.Change\")\n }\n }\n\n /**\n * Dispatches a custom event with given name, namespaced under `Alchemy.`.\n * @param {string} name The name of the custom event\n */\n dispatchCustomEvent(name) {\n this.dispatchEvent(new CustomEvent(`Alchemy.${name}`, { bubbles: true }))\n }\n\n /**\n * validate given file with the uploader configuration\n */\n #validateFile() {\n const config = readJSONScript(\"alchemy_uploader_defaults\")\n const maxFileSize = config.file_size_limit * Math.pow(1024, 2) // in Byte\n let errorMessage = undefined\n\n if (this.file?.size > maxFileSize) {\n errorMessage = translate(\"Uploaded bytes exceed file size\")\n }\n\n const allowedFiletypes = this.file?.type.includes(\"image\")\n ? config.allowed_filetypes.alchemy_pictures\n : config.allowed_filetypes.alchemy_attachments\n\n // The allowlist holds filename extensions, so we cannot derive the format\n // from the MIME type: `application/msword` is not `doc` and every Office\n // Open XML type only yields `vnd`.\n const extension = this.file?.name.match(/\\.([^.]+)$/)?.[1].toLowerCase()\n\n const isFileFormatSupported =\n allowedFiletypes.includes(\"*\") ||\n allowedFiletypes.some((type) => type.toLowerCase() === extension)\n\n if (!isFileFormatSupported) {\n errorMessage = translate(\"File type not allowed\")\n }\n\n if (errorMessage) {\n this.valid = false\n this.errorMessage = errorMessage\n }\n }\n\n /**\n * register event listeners to react on request changes\n */\n #addRequestEventListener() {\n // prevent errors if the component will be called without a request - object\n if (!this.request) {\n return\n }\n\n // update the progress bar and currently loaded size information\n this.request.upload.onprogress = (progressEvent) => {\n this.progressEvent = progressEvent\n }\n\n // triggers, when the upload is done\n this.request.onload = () => {\n if (this.request.status < 400) {\n this.status = \"successful\"\n growl(this.responseMessage)\n } else {\n this.status = \"failed\"\n this.errorMessage = this.responseMessage\n }\n this.dispatchCustomEvent(\"FileUpload.Change\")\n }\n\n // catch request errors\n this.request.onerror = () => {\n this.errorMessage = translate(\"An error occurred during the transaction\")\n }\n }\n\n /**\n * @returns {boolean}\n */\n get active() {\n return this.valid && this.status !== \"canceled\"\n }\n\n /**\n * @returns {string}\n */\n get errorMessage() {\n return this.#errorMessage || \"\"\n }\n\n /**\n * @param {string} message\n */\n set errorMessage(message) {\n this.#errorMessage = message\n const errorMessageContainer = this.querySelector(\".error-message\")\n if (errorMessageContainer) {\n errorMessageContainer.textContent = message\n }\n growl(message, \"error\")\n }\n\n /**\n * @returns {boolean}\n */\n get finished() {\n return [\"canceled\", \"successful\", \"failed\"].includes(this.status)\n }\n\n /**\n * format the loaded and total size and present that as a string\n * @returns {string}\n */\n get loadedSize() {\n return `${formatFileSize(this.progressEventLoaded)} / ${formatFileSize(\n this.progressEventTotal\n )}`\n }\n\n /**\n * @returns {HTMLProgressElement|undefined}\n */\n get progressElement() {\n return this.querySelector(\"sl-progress-bar\")\n }\n\n /**\n * @param {ProgressEvent} progressEvent\n */\n set progressEvent(progressEvent) {\n this.progressEventLoaded = progressEvent.loaded\n this.progressEventTotal = progressEvent.total\n\n this.value = Math.round((progressEvent.loaded / progressEvent.total) * 100)\n this.querySelector(\".loaded-size\").textContent = this.loadedSize\n }\n\n /**\n * @returns {string}\n */\n get responseMessage() {\n try {\n const response = JSON.parse(this.request.responseText)\n return response[\"message\"]\n } catch (error) {\n return `${this.request.status}: ${this.request.statusText}`\n }\n }\n\n /**\n * @returns {string}\n */\n get status() {\n return this.#status\n }\n\n /**\n * @param {string} status\n */\n set status(status) {\n this.#status = status\n this.className = status\n\n this.progressElement?.toggleAttribute(\n \"indeterminate\",\n status === \"upload-finished\"\n )\n }\n\n /**\n * @returns {boolean}\n */\n get valid() {\n return this.#valid\n }\n\n /**\n * @param {boolean} isValid\n */\n set valid(isValid) {\n this.#valid = isValid\n this.classList.toggle(\"invalid\", !isValid)\n }\n\n /**\n * get the progress value of the current file\n * @returns {number}\n */\n get value() {\n return this.#value\n }\n\n /**\n * @param {number} value\n */\n set value(value) {\n this.#value = value\n if (this.progressElement) {\n this.progressElement.value = value\n }\n\n if (value === 100) {\n this.status = \"upload-finished\"\n }\n\n this.dispatchCustomEvent(\"FileUpload.Change\")\n }\n}\n\ncustomElements.define(\"alchemy-file-upload\", FileUpload)\n","import { FileUpload } from \"alchemy_admin/components/uploader/file_upload\"\nimport { formatFileSize } from \"alchemy_admin/utils/format\"\nimport { translate } from \"alchemy_admin/i18n\"\n\nconst template = (buttonLabel, fileCount) => `\n <sl-progress-bar value=\"0\"></sl-progress-bar>\n <div class=\"overall-progress-value\">\n <span class=\"value-text\"></span>\n\n <sl-tooltip content=\"${buttonLabel}\">\n <button class=\"icon_button\" aria-label=\"${buttonLabel}\">\n <alchemy-icon name=\"close\"></alchemy-icon>\n </button>\n </sl-tooltip>\n </div>\n <div class=\"single-uploads\" style=\"--progress-columns: ${\n fileCount > 3 ? 3 : fileCount\n }\"></div>\n <div class=\"overall-upload-value value-text\"></div>\n`\n\nexport class Progress extends HTMLElement {\n // public — accessed by Uploader and tests\n fileCount = 0\n\n // private — backing state and internals\n #fileUploads = []\n #buttonLabel = translate(\"Cancel all uploads\")\n #actionButton = null\n #visible = false\n #handleFileChange = () => this.#updateView()\n\n connectedCallback() {\n this.innerHTML = template(this.#buttonLabel, this.fileCount)\n this.visible = true\n\n this.#actionButton = this.querySelector(\"button\")\n this.#actionButton.addEventListener(\"click\", () => {\n if (this.finished) {\n this.onComplete(this.status)\n } else {\n this.cancel()\n }\n })\n\n this.#fileUploads.forEach((fileUpload) => {\n this.querySelector(\".single-uploads\").append(fileUpload)\n })\n\n this.#updateView()\n this.addEventListener(\"Alchemy.FileUpload.Change\", this.#handleFileChange)\n }\n\n disconnectedCallback() {\n this.removeEventListener(\n \"Alchemy.FileUpload.Change\",\n this.#handleFileChange\n )\n }\n\n /**\n * Initialize the component with file uploads\n * @param {FileUpload[]} fileUploads\n */\n initialize(fileUploads = []) {\n this.#fileUploads = fileUploads\n this.fileCount = fileUploads.length\n }\n\n /**\n * cancel requests in all remaining uploads\n */\n cancel() {\n this.#activeUploads().forEach((upload) => {\n upload.cancel()\n })\n this.#setupCloseButton()\n }\n\n /**\n * a complete hook to allow the uploader to react and trigger an event\n * it would be possible to trigger the event here, but the dispatching would happen\n * in the scope of that component and can't be cached o uploader - component level\n */\n onComplete(_status) {}\n\n /**\n * get all active upload components\n * @returns {FileUpload[]}\n */\n #activeUploads() {\n return this.#fileUploads.filter((upload) => upload.active)\n }\n\n /**\n * replace cancel button to be the close button\n */\n #setupCloseButton() {\n this.#buttonLabel = translate(\"Close\")\n this.#actionButton.ariaLabel = this.#buttonLabel\n this.#actionButton.parentElement.content = this.#buttonLabel // update tooltip content\n }\n\n /**\n * @param {string} field\n * @returns {number}\n */\n #sumFileProgresses(field) {\n return this.#activeUploads().reduce(\n (accumulator, upload) => upload[field] + accumulator,\n 0\n )\n }\n\n /**\n * don't render the whole element new, because it would prevent selecting buttons\n */\n #updateView() {\n const status = this.status\n this.className = status\n\n // update progress bar\n this.progressElement.value = this.totalProgress\n this.progressElement.toggleAttribute(\n \"indeterminate\",\n status === \"upload-finished\"\n )\n\n // show progress in file size and percentage\n this.querySelector(`.overall-progress-value > span`).textContent =\n this.overallProgressValue\n this.querySelector(`.overall-upload-value`).textContent =\n this.overallUploadSize\n\n if (this.finished) {\n this.#setupCloseButton()\n this.onComplete(status)\n } else {\n this.visible = true\n }\n }\n\n /**\n * @returns {boolean}\n */\n get finished() {\n return this.#activeUploads().every((entry) => entry.finished)\n }\n\n /**\n * @returns {string}\n */\n get overallUploadSize() {\n const uploadedFileCount = this.#activeUploads().filter(\n (fileProgress) => fileProgress.value >= 100\n ).length\n const overallProgressValue = `${\n this.totalProgress\n }% (${uploadedFileCount} / ${this.#activeUploads().length})`\n\n return `${formatFileSize(\n this.#sumFileProgresses(\"progressEventLoaded\")\n )} / ${formatFileSize(this.#sumFileProgresses(\"progressEventTotal\"))}`\n }\n\n /**\n * @returns {string}\n */\n get overallProgressValue() {\n const uploadedFileCount = this.#activeUploads().filter(\n (fileProgress) => fileProgress.value >= 100\n ).length\n return `${this.totalProgress}% (${uploadedFileCount} / ${\n this.#activeUploads().length\n })`\n }\n\n /**\n * @returns {HTMLProgressElement|undefined}\n */\n get progressElement() {\n return this.querySelector(\"sl-progress-bar\")\n }\n\n /**\n * get status of file progresses and accumulate the overall status\n * @returns {string}\n */\n get status() {\n const uploadsStatuses = this.#activeUploads().map(\n (upload) => upload.className\n )\n\n // mark as failed, if any upload failed\n if (uploadsStatuses.includes(\"failed\")) {\n return \"failed\"\n }\n\n // no active upload means that every upload was canceled\n if (uploadsStatuses.length === 0) {\n return \"canceled\"\n }\n\n // all uploads are successful or upload-finished or in-progress\n if (uploadsStatuses.every((entry) => entry === uploadsStatuses[0])) {\n return uploadsStatuses[0]\n }\n\n return \"in-progress\"\n }\n\n /**\n * @returns {number}\n */\n get totalProgress() {\n const totalSize = this.#activeUploads().reduce(\n (accumulator, upload) => accumulator + upload.file.size,\n 0\n )\n let totalProgress = Math.ceil(\n this.#activeUploads().reduce((accumulator, upload) => {\n const weight = upload.file.size / totalSize\n return upload.value * weight + accumulator\n }, 0)\n )\n // prevent rounding errors\n if (totalProgress > 100) {\n totalProgress = 100\n }\n return totalProgress\n }\n\n /**\n * @returns {boolean}\n */\n get visible() {\n return this.#visible\n }\n\n /**\n * @param {boolean} visible\n */\n set visible(visible) {\n this.classList.toggle(\"visible\", visible)\n this.#visible = visible\n }\n}\n\ncustomElements.define(\"alchemy-upload-progress\", Progress)\n","/**\n * @typedef {object} PersistedFile\n * @property {string} name\n * @property {number} size\n */\nimport { Progress } from \"alchemy_admin/components/uploader/progress\"\nimport { FileUpload } from \"alchemy_admin/components/uploader/file_upload\"\nimport { translate } from \"alchemy_admin/i18n\"\nimport { readJSONScript } from \"alchemy_admin/utils/json_script\"\nimport { getToken } from \"alchemy_admin/utils/ajax\"\n\nexport class Uploader extends HTMLElement {\n #dropzoneElement = null\n #isDraggedOver = false\n\n connectedCallback() {\n this.fileInput.addEventListener(\"change\", this.#onFileInputChange)\n if (this.dropzone) {\n this.#setupDropZone()\n }\n this.addEventListener(\"Alchemy.upload.successful\", this)\n }\n\n disconnectedCallback() {\n this.fileInput?.removeEventListener(\"change\", this.#onFileInputChange)\n if (this.#dropzoneElement) {\n this.#dropzoneElement.removeEventListener(\n \"dragleave\",\n this.#onDropzoneDragleave\n )\n this.#dropzoneElement.removeEventListener(\"drop\", this.#onDropzoneDrop)\n this.#dropzoneElement.removeEventListener(\n \"dragover\",\n this.#onDropzoneDragover\n )\n this.#dropzoneElement = null\n }\n }\n\n handleEvent(evt) {\n switch (evt.type) {\n case \"Alchemy.upload.successful\":\n this.#handleUploadComplete()\n break\n }\n }\n\n #onFileInputChange = (event) => {\n this.uploadFiles(Array.from(event.target.files))\n }\n\n #toggleDropzoneClass = (enabled) => {\n if (this.#isDraggedOver !== enabled) {\n this.#isDraggedOver = enabled\n this.#dropzoneElement.classList.toggle(\"dragover\")\n }\n }\n\n #onDropzoneDragleave = () => this.#toggleDropzoneClass(false)\n\n #onDropzoneDrop = async (event) => {\n event.preventDefault()\n this.#toggleDropzoneClass(false)\n\n const files = [...event.dataTransfer.items].map((item) => item.getAsFile())\n\n this.uploadFiles(files)\n }\n\n #onDropzoneDragover = (event) => {\n event.preventDefault() // dragover has to be disabled to use the custom drop event\n this.#toggleDropzoneClass(true)\n }\n\n #handleUploadComplete() {\n setTimeout(() => {\n const url = this.redirectUrl\n const turboFrame = this.closest(\"turbo-frame\")\n this.uploadProgress.visible = false\n\n if (!url) return\n\n if (turboFrame) {\n turboFrame.setAttribute(\"src\", url)\n turboFrame.reload()\n } else {\n Turbo.visit(url)\n }\n }, 750)\n }\n\n /**\n * add dragover class to indicate, if the file is draggable\n * @private\n */\n #setupDropZone() {\n this.#dropzoneElement = document.querySelector(this.dropzone)\n if (!this.#dropzoneElement) return\n\n this.#dropzoneElement.addEventListener(\n \"dragleave\",\n this.#onDropzoneDragleave\n )\n this.#dropzoneElement.addEventListener(\"drop\", this.#onDropzoneDrop)\n this.#dropzoneElement.addEventListener(\"dragover\", this.#onDropzoneDragover)\n }\n\n /**\n * @param {File[]} files\n */\n uploadFiles(files) {\n // prepare file progress bars and server request\n let fileUploadCount = 0\n const config = readJSONScript(\"alchemy_uploader_defaults\")\n\n const fileUploads = files.map((file) => {\n const request = new XMLHttpRequest()\n const fileUpload = new FileUpload()\n fileUpload.initialize(file, request)\n\n if (config.upload_limit - 1 < fileUploadCount) {\n fileUpload.valid = false\n fileUpload.errorMessage = translate(\"Maximum number of files exceeded\")\n } else if (fileUpload.valid) {\n fileUploadCount++\n this.#submitFile(request, file)\n }\n\n return fileUpload\n })\n\n this.#createProgress(fileUploads)\n }\n\n /**\n * @param {XMLHttpRequest} request\n * @param {File} file\n * @private\n */\n #submitFile(request, file) {\n const form = this.querySelector(\"form\")\n const formData = new FormData(form)\n formData.set(this.fileInput.name, file)\n request.open(\"POST\", form.action)\n request.setRequestHeader(\"X-CSRF-Token\", getToken())\n request.setRequestHeader(\"X-Requested-With\", \"XMLHttpRequest\")\n request.setRequestHeader(\"Accept\", \"application/json\")\n request.send(formData)\n }\n\n /**\n * create (and maybe remove the old) progress bar - component\n * @param {FileUpload[]} fileUploads\n * @private\n */\n #createProgress(fileUploads) {\n if (this.uploadProgress) {\n this.uploadProgress.cancel()\n this.uploadProgress.remove()\n }\n this.uploadProgress = new Progress()\n this.uploadProgress.initialize(fileUploads)\n this.uploadProgress.setAttribute(\"layout\", this.layout)\n this.uploadProgress.onComplete = (status) => {\n this.dispatchEvent(\n new CustomEvent(`Alchemy.upload.${status}`, { bubbles: true })\n )\n }\n\n this.#progressContainer.append(this.uploadProgress)\n }\n\n // The dropzone doubles as the progress container so the upload indicators\n // render inside the archive (and inside dialogs). Falls back to the body for\n // uploaders without a dropzone (e.g. the attachment replace button).\n get #progressContainer() {\n return this.#dropzoneElement ?? document.body\n }\n\n get dropzone() {\n return this.getAttribute(\"dropzone\")\n }\n\n // Shape of the upload indicators. Defaults to \"row\" so uploads fit naturally\n // into resource table views; set layout=\"card\" for the thumbnail grid.\n get layout() {\n return this.getAttribute(\"layout\") || \"row\"\n }\n\n /**\n * @returns {HTMLInputElement}\n */\n get fileInput() {\n return this.querySelector(\"input[type='file']\")\n }\n\n get redirectUrl() {\n return this.getAttribute(\"redirect-url\")\n }\n}\n\ncustomElements.define(\"alchemy-uploader\", Uploader)\n","class Overlay extends HTMLElement {\n connectedCallback() {\n this.innerHTML = `\n <alchemy-spinner></alchemy-spinner>\n <div id=\"overlay_text_box\">\n <span id=\"overlay_text\">${this.getAttribute(\"text\") ?? \"\"}</span>\n </div>\n `\n }\n\n set show(value) {\n this.classList.toggle(\"visible\", value)\n }\n}\n\ncustomElements.define(\"alchemy-overlay\", Overlay)\n","import { patch } from \"alchemy_admin/utils/ajax\"\nimport { growl } from \"alchemy_admin/growler\"\nimport Spinner from \"alchemy_admin/spinner\"\nimport { routes } from \"alchemy_admin/routes\"\n\nconst BUTTON = \"BUTTON\"\nconst SPAN = \"SPAN\"\n\n/**\n * Custom element for page nodes in the sitemap tree\n * Handles folding/unfolding of page children\n */\nexport class AlchemyPageNode extends HTMLElement {\n connectedCallback() {\n this.pageId = this.getAttribute(\"page-id\")\n this.folded = this.hasAttribute(\"folded\")\n\n this.folderButton?.addEventListener(\"click\", this)\n }\n\n disconnectedCallback() {\n this.folderButton?.removeEventListener(\"click\", this)\n }\n\n async handleEvent(event) {\n if (event.type === \"click\") {\n await this.handleFolderClick(event)\n }\n }\n\n async handleFolderClick(event) {\n event.preventDefault()\n event.stopPropagation()\n\n const folderButton = event.currentTarget\n folderButton.innerHTML = \"\"\n const spinner = new Spinner(\"small\")\n spinner.spin(folderButton)\n\n try {\n await patch(\n routes.fold_admin_page_path(this.pageId),\n null,\n \"text/vnd.turbo-stream.html\"\n )\n\n this.folded = !this.folded\n this.toggleAttribute(\"folded\", this.folded)\n this.toggleChildren()\n this.updateFolderIcon()\n } catch (error) {\n growl(error.message || error, \"error\")\n this.updateFolderIcon()\n } finally {\n spinner.stop()\n }\n }\n\n toggleChildren() {\n const childrenContainer = this.querySelector(\n `#page_${this.pageId}_children`\n )\n if (childrenContainer) {\n childrenContainer.classList.toggle(\"hidden\", this.folded)\n }\n }\n\n updateFolderIcon() {\n if (this.folderButton) {\n const iconName = this.folded ? \"arrow-right-s\" : \"arrow-down-s\"\n this.folderButton.innerHTML = `<alchemy-icon name=\"${iconName}\"></alchemy-icon>`\n }\n }\n\n /**\n * Updates the folder button state based on whether the node has children\n * Converts between button and span as needed\n */\n updateFolderButton() {\n const folderElement = this.querySelector(\".page_folder\")\n if (!folderElement) return\n\n const shouldShowButton = this.hasChildren || this.folded\n\n if (shouldShowButton && folderElement.tagName === SPAN) {\n // Convert span to button with icon\n const iconName = this.folded ? \"arrow-right-s\" : \"arrow-down-s\"\n folderElement.outerHTML = `<button class=\"page_folder icon_button\">\n <alchemy-icon name=\"${iconName}\"></alchemy-icon>\n </button>`\n\n // Re-attach event listener to the new button element\n this.folderButton?.addEventListener(\"click\", this)\n } else if (!shouldShowButton && folderElement.tagName === BUTTON) {\n // Convert button to empty span (no children and not folded)\n folderElement.outerHTML = '<span class=\"page_folder\"></span>'\n } else if (shouldShowButton && folderElement.tagName === BUTTON) {\n // Button exists, just update the icon direction\n this.updateFolderIcon()\n }\n }\n\n get hasChildren() {\n const childrenContainer = this.querySelector(\n `#page_${this.pageId}_children`\n )\n if (!childrenContainer) return false\n\n return (\n childrenContainer.querySelectorAll(\":scope > alchemy-page-node\").length >\n 0\n )\n }\n\n get folderButton() {\n return this.querySelector(\"button.page_folder\")\n }\n}\n\ncustomElements.define(\"alchemy-page-node\", AlchemyPageNode)\n","// Handles the page publication date fields\nexport class PagePublicationFields extends HTMLElement {\n connectedCallback() {\n this.publicField?.addEventListener(\"click\", this.#onClick)\n }\n\n disconnectedCallback() {\n this.publicField?.removeEventListener(\"click\", this.#onClick)\n }\n\n #onClick = (evt) => {\n const checkbox = evt.target\n const date = new Date()\n const now = new Date(\n date.getTime() - date.getTimezoneOffset() * 60000\n ).toISOString()\n\n if (checkbox.checked) {\n this.publicationDateFields.classList.remove(\"hidden\")\n this.publicOnPicker.value = now.substring(0, now.indexOf(\"T\") + 6)\n } else {\n this.publicationDateFields.classList.add(\"hidden\")\n this.publicOnPicker.value = \"\"\n }\n this.publicUntilPicker.value = \"\"\n }\n\n get publicField() {\n return this.querySelector(\"#page_public\")\n }\n\n get publicOnPicker() {\n return this.querySelector(\"input#page_public_on\")\n }\n\n get publicUntilPicker() {\n return this.querySelector(\"input#page_public_until\")\n }\n\n get publicationDateFields() {\n return this.querySelector(\".page-publication-date-fields\")\n }\n}\n\ncustomElements.define(\"alchemy-page-publication-fields\", PagePublicationFields)\n","import { RemoteSelect } from \"alchemy_admin/components/remote_select\"\n\nexport class PageSelect extends RemoteSelect {\n get pageId() {\n return this.selection ? JSON.parse(this.selection)[\"id\"] : undefined\n }\n\n _searchQuery(term, page) {\n return {\n q: {\n name_i_cont: term,\n ...JSON.parse(this.queryParams)\n },\n page: page\n }\n }\n\n _parseResponse(response) {\n const meta = response.meta\n return {\n results: response.pages,\n more: meta.page * meta.per_page < meta.total_count\n }\n }\n\n _entry(page, term) {\n return {\n icon: \"file-3\",\n primary: this._hightlightTerm(page.name, term),\n aside: page.site?.name,\n secondary: { text: page.url_path, truncate: \"head\" },\n secondaryAside: { badge: page.language_code }\n }\n }\n\n _selectedEntry(page) {\n return {\n icon: \"file-3\",\n primary: page.text || page.name,\n secondary: { text: page.url_path, truncate: \"head\" }\n }\n }\n}\n\ncustomElements.define(\"alchemy-page-select\", PageSelect)\n","import { openDialog } from \"alchemy_admin/dialog\"\n\n// Multiple picture select handler for the picture archive.\nexport class PictureArchive extends HTMLElement {\n #selectAllButton\n\n connectedCallback() {\n this.addEventListener(\"change\", this.#onCheckboxChange)\n this.addEventListener(\"click\", this.#onEditMultipleClick)\n // The select all button lives in the toolbar, outside of this element.\n this.#selectAllButton = document.querySelector(\"#select_all_pictures\")\n this.#selectAllButton?.addEventListener(\"click\", this.#onSelectAllClick)\n }\n\n disconnectedCallback() {\n this.removeEventListener(\"change\", this.#onCheckboxChange)\n this.removeEventListener(\"click\", this.#onEditMultipleClick)\n this.#selectAllButton?.removeEventListener(\"click\", this.#onSelectAllClick)\n }\n\n #onSelectAllClick = (event) => {\n event.preventDefault()\n\n this.#selectAllButton.classList.toggle(\"active\")\n\n const state = this.#selectAllButton.classList.contains(\"active\")\n\n this.#toggleCheckboxes(state)\n\n this.selectedItemTools.classList.toggle(\"hidden\", !state)\n }\n\n // make the item toolbar visible and show the checkbox also if it is not hovered anymore\n #onCheckboxChange = (event) => {\n const checkbox = event.target.closest(\".picture_tool.select input\")\n\n if (!checkbox) {\n return\n }\n\n this.selectedItemTools.classList.toggle(\n \"hidden\",\n this.checkedInputs.length === 0\n )\n checkbox.parentElement.classList.toggle(\"visible\", checkbox.checked)\n }\n\n // open the edit view in a dialog modal\n #onEditMultipleClick = (event) => {\n const link = event.target.closest(\"a#edit_multiple_pictures\")\n\n if (!link) {\n return\n }\n\n event.preventDefault()\n\n openDialog(this.#editMultiplePicturesUrl(link.href), {\n title: link.title,\n size: \"500x242\"\n })\n }\n\n #toggleCheckboxes(state) {\n this.querySelectorAll(\n \".picture_tool.select input[type='checkbox']\"\n ).forEach((checkbox) => {\n checkbox.checked = state\n checkbox.closest(\".picture_thumbnail\").classList.toggle(\"active\", state)\n })\n }\n\n #editMultiplePicturesUrl(href) {\n const url = new URL(href)\n\n this.checkedInputs.forEach((entry) =>\n url.searchParams.append(entry.name, entry.value)\n )\n\n return url.toString()\n }\n\n get checkedInputs() {\n return this.querySelectorAll(\"input:checked\")\n }\n\n get selectedItemTools() {\n return this.querySelector(\".selected_item_tools\")\n }\n}\n\ncustomElements.define(\"alchemy-picture-archive\", PictureArchive)\n","class PictureDescriptionSelect extends HTMLElement {\n connectedCallback() {\n this.addEventListener(\"change\", this)\n }\n\n disconnectedCallback() {\n this.removeEventListener(\"change\", this)\n }\n\n handleEvent(event) {\n switch (event.type) {\n case \"change\":\n this.onChange()\n break\n }\n }\n\n onChange() {\n const url = new URL(this.getAttribute(\"url\"))\n const select = this.querySelector(\"select\")\n url.searchParams.set(\"language_id\", select.value)\n Turbo.visit(url, { frame: \"picture_descriptions\" })\n }\n}\n\ncustomElements.define(\n \"alchemy-picture-description-select\",\n PictureDescriptionSelect\n)\n","import debounce from \"alchemy_admin/utils/debounce\"\nimport max from \"alchemy_admin/utils/max\"\nimport { get } from \"alchemy_admin/utils/ajax\"\nimport { growl } from \"alchemy_admin/growler\"\nimport { routes } from \"alchemy_admin/routes\"\n\nconst UPDATE_DELAY = 125\nconst IMAGE_PLACEHOLDER = '<alchemy-icon name=\"image\" size=\"xl\"></alchemy-icon>'\nconst THUMBNAIL_SIZE = \"160x120\"\n\nexport class PictureEditor extends HTMLElement {\n connectedCallback() {\n this.cropFromField = this.querySelector(\"[data-crop-from]\")\n this.cropSizeField = this.querySelector(\"[data-crop-size]\")\n this.pictureIdField = this.querySelector(\"[data-picture-id]\")\n this.targetSizeField = this.querySelector(\"[data-target-size]\")\n this.imageCropperField = this.querySelector(\"[data-image-cropper]\")\n this.image = this.querySelector(\"img\")\n this.pictureThumbnail = this.querySelector(\"alchemy-picture-thumbnail\")\n this.deleteButton = this.querySelector(\".picture_tool.delete\")\n this.cropLink = this.querySelector(\".crop_link\")\n\n this.targetSize = this.targetSizeField.dataset.targetSize\n this.pictureId = this.pictureIdField.value\n\n // The mutation observer is observing multiple fields that all get updated\n // simultaneously. We only want to update the image once, so we debounce.\n this.update = debounce(() => {\n this.updateImage()\n this.updateCropLink()\n }, UPDATE_DELAY)\n\n this.deleteButton?.addEventListener(\"click\", this.removeImage)\n\n this.observer = new MutationObserver(this.mutationCallback)\n this.observer.observe(this.cropFromField, { attributes: true })\n this.observer.observe(this.cropSizeField, { attributes: true })\n this.observer.observe(this.pictureIdField, { attributes: true })\n }\n\n disconnectedCallback() {\n this.observer.disconnect()\n }\n\n mutationCallback = (mutationsList) => {\n for (const mutation of mutationsList) {\n if (\"pictureId\" in mutation.target.dataset) {\n this.cropFromField.value = \"\"\n this.cropSizeField.value = \"\"\n this.pictureId = mutation.target.value\n }\n this.update()\n }\n }\n\n updateImage() {\n if (!this.pictureId) return\n\n this.pictureThumbnail.loading = true\n get(routes.url_admin_picture_path(this.pictureId), {\n crop: this.imageCropperEnabled,\n crop_from: this.cropFrom,\n crop_size: this.cropSize,\n flatten: true,\n size: THUMBNAIL_SIZE\n })\n .then(({ data }) => {\n this.pictureThumbnail.src = data.url\n this.pictureThumbnail.image.alt = data.alt\n this.pictureThumbnail.image.title = data.title\n this.setElementDirty()\n })\n .catch((error) => {\n console.error(error.message || error)\n growl(error.message || error, \"error\")\n })\n }\n\n removeImage = () => {\n this.pictureThumbnail.innerHTML = IMAGE_PLACEHOLDER\n this.pictureIdField.value = \"\"\n this.image = null\n this.cropLink.classList.add(\"disabled\")\n this.setElementDirty()\n }\n\n setElementDirty() {\n this.closest(\".element-editor\").setDirty(this)\n }\n\n updateCropLink() {\n if (!this.pictureId || !this.imageCropperEnabled) return\n\n this.cropLink.classList.remove(\"disabled\")\n\n if (this.cropLink.href.match(/(picture_id=)\\d+/)) {\n this.cropLink.href = this.cropLink.href.replace(\n /(picture_id=)\\d+/,\n \"$1\" + this.pictureId\n )\n } else {\n this.cropLink.href = this.cropLink.href + `&picture_id=${this.pictureId}`\n }\n }\n\n get cropFrom() {\n if (this.cropFromField.value === \"\") {\n return this.defaultCropFrom.join(\"x\")\n }\n return this.cropFromField.value\n }\n\n get cropSize() {\n if (this.cropSizeField.value === \"\") {\n return this.defaultCropSize.join(\"x\")\n }\n return this.cropSizeField.value\n }\n\n get defaultCropSize() {\n if (!this.imageCropperEnabled) return []\n\n const mask = this.targetSize.split(\"x\").map((n) => parseInt(n))\n const zoom = max(\n mask[0] / this.imageFileWidth,\n mask[1] / this.imageFileHeight\n )\n\n return [Math.round(mask[0] / zoom), Math.round(mask[1] / zoom)]\n }\n\n get defaultCropFrom() {\n if (!this.imageCropperEnabled) return []\n\n const dimensions = this.defaultCropSize\n\n return [\n Math.round((this.imageFileWidth - dimensions[0]) / 2),\n Math.round((this.imageFileHeight - dimensions[1]) / 2)\n ]\n }\n\n get imageFileWidth() {\n return parseInt(this.pictureIdField.dataset.imageFileWidth)\n }\n\n get imageFileHeight() {\n return parseInt(this.pictureIdField.dataset.imageFileHeight)\n }\n\n get imageCropperEnabled() {\n return this.targetSizeField.dataset.imageCropper === \"true\"\n }\n}\n\ncustomElements.define(\"alchemy-picture-editor\", PictureEditor)\n","export default function (func, delay) {\n let timeout\n\n return function (...args) {\n const that = this\n\n clearTimeout(timeout)\n timeout = setTimeout(() => func.apply(that, args), delay)\n }\n}\n","export default function (a, b) {\n return a >= b ? a : b\n}\n","// Shows spinner while loading images and\n// fades the image after its been loaded\n\nimport Spinner from \"alchemy_admin/spinner\"\n\nexport default class PictureThumbnail extends HTMLElement {\n constructor() {\n super()\n\n this.spinner = new Spinner(\"small\")\n }\n\n handleEvent(evt) {\n switch (evt.type) {\n case \"load\":\n this.#onLoaded()\n break\n case \"error\":\n this.#onError(evt)\n break\n default:\n break\n }\n }\n\n connectedCallback() {\n this.classList.add(\"thumbnail_background\")\n if (this.src && !this.image) {\n this.start()\n }\n this.#setImage()\n }\n\n disconnectedCallback() {\n this.image?.removeEventListener(\"load\", this)\n this.image?.removeEventListener(\"error\", this)\n this.stop()\n }\n\n createImage(src = this.src, alt = this.name) {\n this.image = new Image()\n this.image.src = src\n if (alt) {\n this.image.alt = alt\n }\n }\n\n start(src) {\n this.createImage(src)\n this.image.addEventListener(\"load\", this)\n this.image.addEventListener(\"error\", this)\n this.load()\n }\n\n load() {\n if (this.image?.complete) {\n return\n }\n this.setAttribute(\"loading\", \"loading\")\n this.innerHTML = \"\"\n this.spinner.spin(this)\n }\n\n stop() {\n this.classList.remove(\"loading\")\n this.spinner.stop()\n }\n\n #onLoaded() {\n this.spinner.stop()\n this.removeAttribute(\"loading\")\n }\n\n #onError(evt) {\n const message = `Could not load ${this.image.src}`\n const hoist = this.closest(\".ingredient-editor\")\n this.spinner.stop()\n this.innerHTML = `\n <sl-tooltip content=\"${message}\" ${hoist ? \"hoist\" : \"\"}>\n <alchemy-icon name=\"alert\" class=\"error\"></alchemy-icon>\n </sl-tooltip>\n `\n console.error(message, evt)\n }\n\n #setImage() {\n if (this.image?.complete) {\n this.replaceChildren(this.image)\n } else if (this.image) {\n this.append(this.image)\n }\n }\n\n set loading(value) {\n value ? this.load() : this.stop()\n }\n\n set src(src) {\n this.start(src)\n this.#setImage()\n }\n\n get name() {\n return this.getAttribute(\"name\")\n }\n\n get src() {\n return this.getAttribute(\"src\")\n }\n}\n\ncustomElements.define(\"alchemy-picture-thumbnail\", PictureThumbnail)\n","class PublishPageButton extends HTMLElement {\n connectedCallback() {\n this.addEventListener(\"submit\", this)\n document.addEventListener(\"alchemy:page-dirty\", this)\n }\n\n disconnectedCallback() {\n this.removeEventListener(\"submit\", this)\n document.removeEventListener(\"alchemy:page-dirty\", this)\n }\n\n handleEvent(event) {\n switch (event.type) {\n case \"alchemy:page-dirty\":\n this.markDirty(event.detail)\n break\n case \"submit\":\n this.button.loading = true\n break\n }\n }\n\n markDirty(detail) {\n this.button.variant = \"primary\"\n this.button.disabled = false\n this.tooltip.content = detail.tooltip\n }\n\n get button() {\n return this.querySelector(\"sl-button\")\n }\n\n get tooltip() {\n return this.querySelector(\"sl-tooltip\")\n }\n}\n\ncustomElements.define(\"alchemy-publish-page-button\", PublishPageButton)\n","import Sortable from \"sortablejs\"\nimport { growl } from \"alchemy_admin/growler\"\nimport { patch } from \"alchemy_admin/utils/ajax\"\nimport { translate } from \"alchemy_admin/i18n\"\nimport pleaseWaitOverlay from \"alchemy_admin/please_wait_overlay\"\nimport { routes } from \"alchemy_admin/routes\"\n\n/**\n * Custom element for the sitemap container\n * Handles search/filter functionality and drag-and-drop sorting\n */\nexport class AlchemySitemap extends HTMLElement {\n connectedCallback() {\n this.searchInput = document.querySelector(\".search_input_field\")\n this.clearButton = document.querySelector(\"#search_field_clear\")\n this.resultCounter = document.querySelector(\"#page_filter_result\")\n\n this.setupSearch()\n\n // Wait for child custom elements to be defined before setting up sortables\n requestAnimationFrame(() => {\n this.setupSortables()\n })\n\n // Set up MutationObserver to re-initialize sortables when children containers are added\n this.observer = new MutationObserver((mutations) => {\n mutations.forEach((mutation) => {\n mutation.addedNodes.forEach((node) => {\n if (node.nodeType !== Node.ELEMENT_NODE) return\n\n // If the added node itself is a children container, initialize it\n if (node.classList?.contains(\"children\")) {\n this.setupSortable(node)\n }\n\n // Also check for children containers nested within the added node\n // This handles cases where a parent element with nested children is added at once\n node\n .querySelectorAll(\".children\")\n .forEach((el) => this.setupSortable(el))\n })\n })\n })\n\n // Observe the sitemap for added nodes\n this.observer.observe(this, {\n childList: true,\n subtree: true\n })\n }\n\n disconnectedCallback() {\n this.teardownSearch()\n this.observer?.disconnect()\n }\n\n setupSearch() {\n this.searchInput?.addEventListener(\"input\", this)\n this.clearButton?.addEventListener(\"click\", this)\n }\n\n teardownSearch() {\n this.searchInput?.removeEventListener(\"input\", this)\n this.clearButton?.removeEventListener(\"click\", this)\n }\n\n handleEvent(event) {\n if (event.type === \"input\" && event.target === this.searchInput) {\n this.handleSearch(event)\n } else if (event.type === \"click\" && event.target === this.clearButton) {\n this.handleClearSearch(event)\n }\n }\n\n handleSearch(event) {\n const term = event.target.value.toLowerCase().trim()\n\n if (term === \"\") {\n this.clearFilter()\n return\n }\n\n this.filterPages(term)\n }\n\n filterPages(term) {\n const allPages = this.querySelectorAll(\".sitemap_page\")\n let matchCount = 0\n let firstMatch = null\n\n allPages.forEach((pageElement) => {\n const pageName = pageElement.getAttribute(\"name\") || \"\"\n\n if (pageName.toLowerCase().includes(term)) {\n pageElement.classList.add(\"highlight\")\n pageElement.classList.remove(\"no-match\")\n matchCount++\n if (!firstMatch) firstMatch = pageElement\n } else {\n pageElement.classList.remove(\"highlight\")\n pageElement.classList.add(\"no-match\")\n }\n })\n\n // Update result counter\n\n if (matchCount === 1) {\n this.resultCounter.textContent = `1 ${translate(\"page_found\")}`\n this.resultCounter.style.display = \"block\"\n } else if (matchCount > 1) {\n this.resultCounter.textContent = `${matchCount} ${translate(\"pages_found\")}`\n this.resultCounter.style.display = \"block\"\n } else {\n this.resultCounter.style.display = \"none\"\n }\n\n // Scroll first match into view\n if (firstMatch) {\n firstMatch.scrollIntoView({ behavior: \"smooth\", block: \"center\" })\n }\n }\n\n clearFilter() {\n const allPages = this.querySelectorAll(\".sitemap_page\")\n allPages.forEach((pageElement) => {\n pageElement.classList.remove(\"highlight\", \"no-match\")\n })\n\n this.resultCounter.style.display = \"none\"\n }\n\n handleClearSearch(event) {\n event.preventDefault()\n this.searchInput.value = \"\"\n this.clearFilter()\n }\n\n setupSortable(container) {\n new Sortable(container, {\n group: \"pages\",\n animation: 150,\n fallbackOnBody: true,\n swapThreshold: 0.65,\n handle: \".page-icon.handle\",\n draggable: \"alchemy-page-node\",\n onEnd: (evt) => this.handleSort(evt)\n })\n }\n\n setupSortables() {\n const sortables = this.querySelectorAll(\".children\")\n sortables.forEach((el) => this.setupSortable(el))\n }\n\n async handleSort(evt) {\n // Only proceed if actually moved to different position/container\n if (evt.from === evt.to && evt.oldIndex === evt.newIndex) {\n return\n }\n\n // evt.item is the <alchemy-page-node> element being dragged\n const pageNode = evt.item\n const pageId = pageNode.pageId\n const url = routes.move_admin_page_path(pageId)\n const data = {\n target_parent_id: evt.to.dataset.parentId,\n new_position: evt.newIndex\n }\n\n pleaseWaitOverlay(true)\n\n try {\n const response = await patch(url, data)\n const pageData = await response.data\n\n // Update the URL path of the moved page\n const pageEl = pageNode.querySelector(`#page_${pageId}`)\n if (pageEl) {\n const urlPathEl = pageEl.querySelector(\".sitemap_url\")\n if (urlPathEl && pageData.url_path) {\n urlPathEl.textContent = pageData.url_path\n }\n }\n\n // Update folder icons for affected parent pages\n this.updateFolderIcons(evt.from, evt.to)\n\n growl(translate(\"Successfully moved page\"))\n } catch (error) {\n growl(error.message || error, \"error\")\n // Revert the DOM change by reloading on error\n window.location.reload()\n } finally {\n pleaseWaitOverlay(false)\n }\n }\n\n updateFolderIcons(fromContainer, toContainer) {\n // Update folder icon for source parent (might now have no children)\n const fromParent = fromContainer.closest(\"alchemy-page-node\")\n fromParent?.updateFolderButton()\n\n // Update folder icon for destination parent (now definitely has children)\n if (fromContainer !== toContainer) {\n const toParent = toContainer.closest(\"alchemy-page-node\")\n toParent?.updateFolderButton()\n }\n }\n}\n\ncustomElements.define(\"alchemy-sitemap\", AlchemySitemap)\n","import Sortable from \"sortablejs\"\nimport { growl } from \"alchemy_admin/growler\"\nimport { post } from \"alchemy_admin/utils/ajax\"\nimport { reloadPreview } from \"alchemy_admin/components/preview_window\"\nimport { dispatchPageDirtyEvent } from \"alchemy_admin/utils/dispatch_page_dirty_event\"\nimport { routes } from \"alchemy_admin/routes\"\n\nconst SORTABLE_OPTIONS = {\n draggable: \".element-editor\",\n handle: \".element-handle.draggable\",\n ghostClass: \"dragged\",\n animation: 150,\n swapThreshold: 0.65,\n easing: \"cubic-bezier(1, 0, 0, 1)\"\n}\n\nfunction onStart(event) {\n const name = event.item.dataset.elementName\n document\n .querySelectorAll(`[data-droppable-elements~=\"${name}\"]`)\n .forEach((dropzone) => dropzone.classList.add(\"droppable-elements\"))\n}\n\nfunction onSort(event) {\n const item = event.item\n const parentElement = event.to.parentElement.closest(\".element-editor\")\n const params = {\n element_id: item.dataset.elementId,\n position: event.newIndex + 1\n }\n\n if (parentElement) {\n params.parent_element_id = parentElement.dataset.elementId\n }\n\n // Only send the request if the item was moved to a different container\n // or sorted in the same list. Not on the old list in order to avoid incrementing\n // the position of the other elements.\n if (event.target === event.to) {\n post(routes.order_admin_elements_path, params).then((response) => {\n const data = response.data\n growl(data.message)\n if (data.pageHasUnpublishedChanges) {\n dispatchPageDirtyEvent(data)\n }\n reloadPreview()\n item.updateTitle(data.preview_text)\n })\n }\n}\n\nfunction onEnd() {\n const dropzones = document.querySelectorAll(\"[data-droppable-elements]\")\n dropzones.forEach((dropzone) =>\n dropzone.classList.remove(\"droppable-elements\")\n )\n}\n\nclass SortableElements extends HTMLElement {\n connectedCallback() {\n const group = {\n name: this.dataset.elementName,\n put(to, _from, item) {\n return to.el.dataset.droppableElements\n .split(\" \")\n .includes(item.dataset.elementName)\n }\n }\n new Sortable(this, {\n ...SORTABLE_OPTIONS,\n onStart,\n onSort,\n onEnd,\n group\n })\n }\n}\n\ncustomElements.define(\"alchemy-sortable-elements\", SortableElements)\n","class Spinner extends HTMLElement {\n connectedCallback() {\n this.className = `spinner spinner--${this.size}`\n this.innerHTML = `\n <svg width=\"100%\" viewBox=\"0 0 28 28\" style=\"--spinner-color: ${this.color}\">\n <path\n class=\"hex1\"\n d=\"M5.938,18.07l-5.878-5.9l2.154-8.058l8.024-2.161l5.87,5.9l-2.144,8.058L5.938,18.07z\"\n />\n <path\n class=\"hex2\"\n d=\"M19.686,20.785l-4.731-4.754l1.725-6.487l6.468-1.742l4.733,4.754l-1.734,6.487L19.686,20.785z\"\n />\n <path\n class=\"hex3\"\n d=\"M11.708,26.294l-3.47-3.485l1.276-4.758l4.74-1.276l3.468,3.485l-1.265,4.758L11.708,26.294z\"\n />\n </svg>\n `\n }\n\n get size() {\n return this.getAttribute(\"size\") || \"medium\"\n }\n\n get color() {\n return this.getAttribute(\"color\") || \"currentColor\"\n }\n}\n\ncustomElements.define(\"alchemy-spinner\", Spinner)\n","import TomSelect from \"tom-select\"\nimport \"alchemy_admin/components/icon\"\nimport { get } from \"alchemy_admin/utils/ajax\"\nimport { growl } from \"alchemy_admin/growler\"\nimport {\n createDropdownPositioning,\n dropdownMessages,\n focusTomSelect,\n removeButton,\n teardownTomSelect\n} from \"alchemy_admin/utils/tom_select\"\n\nexport class TagsAutocomplete extends HTMLElement {\n #tomSelect = null\n\n connectedCallback() {\n this.classList.add(\"autocomplete_tag_list\")\n // Reuse the shared Tom Select styling by tagging the source input, whose\n // class Tom Select copies onto the generated wrapper.\n this.input.classList.add(\"alchemy_selectbox\")\n this.#tomSelect = new TomSelect(this.input, this.settings)\n }\n\n disconnectedCallback() {\n teardownTomSelect(this.#tomSelect)\n this.#tomSelect = null\n }\n\n focus() {\n focusTomSelect(this.#tomSelect, () => super.focus())\n }\n\n get input() {\n return this.getElementsByTagName(\"input\")[0]\n }\n\n get settings() {\n return {\n plugins: {\n remove_button: removeButton()\n },\n // The autocomplete endpoint returns each tag as `{ id, text }`.\n valueField: \"id\",\n // Tags are entered and stored as a comma separated list.\n delimiter: \",\",\n // Allow free tagging.\n create: true,\n // Hide the \"Add …\" create option while suggestions are still loading, so\n // it doesn't flash and then disappear once a matching tag arrives from\n // the server. When idle, keep Tom Select's default of not offering to\n // create a tag that already exists.\n createFilter(input) {\n if (this.loading) return false\n return this.settings.duplicates || !this.options[input]\n },\n // Do not keep a removed tag around as a lingering suggestion.\n persist: false,\n // Clear the typed text once a tag is added, so it doesn't linger in the\n // input after selecting an existing tag.\n clearAfterSelect: true,\n // Show every matching tag returned by the server.\n maxOptions: null,\n openOnFocus: false,\n // Apply the placeholder set on the wrapper element.\n placeholder: this.getAttribute(\"placeholder\"),\n // Load matching tags from the server as the user types.\n load: (query, callback) => {\n get(this.getAttribute(\"url\"), { term: query })\n .then((response) => callback(response.data))\n .catch((error) => {\n growl(error.message || error, \"error\")\n // Tom Select stays in loading state until the callback runs.\n callback()\n })\n },\n ...createDropdownPositioning(),\n render: {\n ...dropdownMessages\n }\n }\n }\n}\n\ncustomElements.define(\"alchemy-tags-autocomplete\", TagsAutocomplete)\n","import \"tinymce\"\nimport { currentLocale } from \"alchemy_admin/i18n\"\nimport { readJSONScript } from \"alchemy_admin/utils/json_script\"\n\nconst DARK_THEME = \"alchemy-dark\"\nconst LIGHT_THEME = \"alchemy\"\n\nexport class Tinymce extends HTMLElement {\n #min_height = null\n\n /**\n * the observer will initialize Tinymce if the textarea becomes visible\n */\n connectedCallback() {\n this.className = \"tinymce_container\"\n\n // Append the spinner if not already present (idempotent on reconnect/clone)\n if (!this.querySelector(\":scope > alchemy-spinner\")) {\n this.insertAdjacentHTML(\n \"beforeend\",\n `<alchemy-spinner size=\"small\"></alchemy-spinner>`\n )\n }\n\n // hide the textarea until TinyMCE is ready to show the editor\n this.style.minHeight = `${this.minHeight}px`\n this.editor.style.display = \"none\"\n\n const observerCallback = (entries, observer) => {\n entries.forEach((entry) => {\n if (entry.intersectionRatio > 0) {\n this._initTinymceEditor()\n // disable observer after the Tinymce was initialized\n observer.unobserve(entry.target)\n }\n })\n }\n\n const options = {\n root: document.getElementById(\"element_area\"),\n rootMargin: \"0px\",\n threshold: [0.05]\n }\n\n this.tinymceIntersectionObserver = new IntersectionObserver(\n observerCallback,\n options\n )\n this.tinymceIntersectionObserver.observe(this)\n\n // Set up theme change listener\n this._setupThemeChangeListener()\n }\n\n /**\n * disconnect intersection observer and remove Tinymce editor if the web components get destroyed\n */\n disconnectedCallback() {\n this.tinymceIntersectionObserver?.disconnect()\n\n // Remove theme change listener\n this._removeThemeChangeListener()\n\n tinymce.get(this.editorId)?.remove(this.editorId)\n }\n\n /**\n * initialize Richtext area after the Intersection observer triggered\n * @private\n */\n _initTinymceEditor() {\n tinymce.init(this.configuration).then((editors) => {\n editors.forEach((editor) => this._setupEditor(editor))\n })\n }\n\n /**\n * Setup editor after initialization\n * @param {Object} editor - The TinyMCE editor instance\n * @private\n */\n _setupEditor(editor) {\n // mark the editor container as visible\n // without these correction the editor remains hidden\n // after a drag and drop action\n editor.show()\n\n // remove the spinner after the Tinymce initialized (only on first init)\n const spinner = this.getElementsByTagName(\"alchemy-spinner\")[0]\n if (spinner) {\n spinner.remove()\n }\n\n // event listener to mark the editor as dirty\n if (this.elementEditor) {\n editor.on(\"dirty\", (evt) => {\n this.elementEditor.setDirty(evt.target.editorContainer)\n })\n editor.on(\"click\", () => this.elementEditor.onClickElement(false))\n }\n }\n\n /**\n * Set up listener for OS theme changes\n * @private\n */\n _setupThemeChangeListener() {\n this.darkModeMediaQuery = window.matchMedia(\"(prefers-color-scheme: dark)\")\n this.themeChangeHandler = (event) => this._handleThemeChange(event)\n this.darkModeMediaQuery.addEventListener(\"change\", this.themeChangeHandler)\n }\n\n /**\n * Remove theme change listener\n * @private\n */\n _removeThemeChangeListener() {\n if (this.darkModeMediaQuery && this.themeChangeHandler) {\n this.darkModeMediaQuery.removeEventListener(\n \"change\",\n this.themeChangeHandler\n )\n }\n }\n\n /**\n * Handle OS theme change and update TinyMCE skin\n * @param {MediaQueryListEvent} event - The media query change event\n * @private\n */\n _handleThemeChange(event) {\n const editor = tinymce.get(this.editorId)\n if (editor) {\n const skin = event.matches ? DARK_THEME : LIGHT_THEME\n const content_css = event.matches ? DARK_THEME : LIGHT_THEME\n\n // Update the skin by reinitializing the editor with new configuration\n editor.remove()\n tinymce\n .init({\n content_css,\n ...this.configuration,\n skin\n })\n .then((editors) => {\n editors.forEach((editor) => this._setupEditor(editor))\n })\n }\n }\n\n get configuration() {\n const customConfig = {}\n\n // read the attributes on the component and add them as custom configuration\n this.getAttributeNames().forEach((attributeName) => {\n if (![\"class\", \"id\", \"is\", \"name\", \"style\"].includes(attributeName)) {\n const config = this.getAttribute(attributeName)\n const key = attributeName.replaceAll(\"-\", \"_\")\n\n // Handle boolean HTML attributes (e.g., readonly=\"readonly\" or readonly=\"\")\n if (config === attributeName || config === \"\") {\n customConfig[key] = true\n } else {\n try {\n customConfig[key] = JSON.parse(config)\n } catch (e) {\n // also string values as parameter\n customConfig[key] = config\n }\n }\n }\n })\n\n const config = {\n content_css: this.preferredTheme,\n ...readJSONScript(\"alchemy_tinymce_defaults\"),\n ...customConfig,\n language: currentLocale(),\n selector: `#${this.editorId}`,\n skin: this.preferredTheme\n }\n\n // Tinymce has a height of 400px by default\n // if the element has a min_height set, we use this value for the height as well\n // so we do not need to set both values in the element configuration\n config.height = config.min_height\n\n return config\n }\n\n get preferredTheme() {\n return window.matchMedia(\"(prefers-color-scheme: dark)\").matches\n ? DARK_THEME\n : LIGHT_THEME\n }\n\n get editorId() {\n return this.editor.id\n }\n\n get editor() {\n return this.getElementsByTagName(\"textarea\")[0]\n }\n\n get elementEditor() {\n return document\n .getElementById(this.editorId)\n .closest(\"alchemy-element-editor\")\n }\n\n get minHeight() {\n return this.#min_height || this.configuration.min_height\n }\n\n set minHeight(value) {\n this.#min_height = value\n }\n}\n\ncustomElements.define(\"alchemy-tinymce\", Tinymce)\n","import Spinner from \"alchemy_admin/spinner\"\n\nclass UpdateCheck extends HTMLElement {\n async connectedCallback() {\n const spinner = new Spinner(\"small\")\n spinner.spin(this)\n\n try {\n const response = await fetch(this.url, { credentials: \"include\" })\n const responseJSON = await response.json()\n\n if (response.ok) {\n this.showStatus(responseJSON)\n } else {\n this.showError(response)\n }\n } catch (error) {\n this.showError(error)\n } finally {\n spinner.stop()\n }\n }\n\n get url() {\n return this.getAttribute(\"url\")\n }\n\n showStatus(responseJSON) {\n if (responseJSON[\"status\"] == \"true\") {\n this.querySelector(\".update_available\").classList.remove(\"hidden\")\n } else {\n this.querySelector(\".up_to_date\").classList.remove(\"hidden\")\n }\n }\n\n showError(error) {\n this.querySelector(\".error\").classList.remove(\"hidden\")\n console.error(\"[alchemy] Error fetching update status\", error)\n }\n}\n\ncustomElements.define(\"alchemy-update-check\", UpdateCheck)\n","import { registerIconLibrary, setDefaultAnimation } from \"shoelace\"\n\n// Change the default animation for all tooltips\nsetDefaultAnimation(\"tooltip.show\", {\n keyframes: [\n { transform: \"translateY(10px)\", opacity: \"0\" },\n { transform: \"translateY(0)\", opacity: \"1\" }\n ],\n options: {\n duration: 100\n }\n})\n\nsetDefaultAnimation(\"tooltip.hide\", {\n keyframes: [\n { transform: \"translateY(0)\", opacity: \"1\" },\n { transform: \"translateY(10px)\", opacity: \"0\" }\n ],\n options: {\n duration: 100\n }\n})\n\n// Change the default animation for all dialogs\nsetDefaultAnimation(\"dialog.show\", {\n keyframes: [\n { transform: \"scale(0.98)\", opacity: \"0\" },\n { transform: \"scale(1)\", opacity: \"1\" }\n ],\n options: {\n duration: 150\n }\n})\n\nsetDefaultAnimation(\"dialog.hide\", {\n keyframes: [\n { transform: \"scale(1)\", opacity: \"1\" },\n { transform: \"scale(0.98)\", opacity: \"0\" }\n ],\n options: {\n duration: 150\n }\n})\n\nconst spriteUrl = document\n .querySelector('link[rel=\"preload\"][as=\"image\"]')\n .getAttribute(\"href\")\n\nconst iconMap = {\n \"x-lg\": \"close\",\n caret: \"arrow-down-s\"\n}\n\nconst options = {\n resolver: (name) => `${spriteUrl}#ri-${iconMap[name] || name}-line`,\n mutator: (svg) => {\n svg.setAttribute(\"fill\", \"currentColor\")\n svg.setAttribute(\"viewBox\", \"0 0 24 24\")\n },\n spriteSheet: true\n}\n\nregisterIconLibrary(\"default\", options)\nregisterIconLibrary(\"system\", options)\n","import \"@ungap/custom-elements\"\nimport { Turbo } from \"@hotwired/turbo-rails\"\n\nimport { translate } from \"alchemy_admin/i18n\"\nimport { currentDialog, closeCurrentDialog } from \"alchemy_admin/dialog\"\nimport * as FixedElements from \"alchemy_admin/fixed_elements\"\nimport { growl } from \"alchemy_admin/growler\"\nimport Initializer from \"alchemy_admin/initializer\"\nimport { LinkDialog } from \"alchemy_admin/link_dialog\"\nimport pleaseWaitOverlay from \"alchemy_admin/please_wait_overlay\"\nimport Spinner from \"alchemy_admin/spinner\"\nimport { reloadPreview } from \"alchemy_admin/components/preview_window\"\nimport { openConfirmDialog } from \"alchemy_admin/confirm_dialog\"\n\nimport \"alchemy_admin/turbo_stream_actions\"\n\n// Web Components\nimport \"alchemy_admin/components\"\n\n// Shoelace Setup\nimport \"alchemy_admin/shoelace_theme\"\n\n// Global Alchemy object\nif (typeof window.Alchemy === \"undefined\") {\n window.Alchemy = {}\n}\n\n// Enhance the global Alchemy object with imported features\nObject.assign(Alchemy, {\n closeCurrentDialog,\n currentDialog,\n t: translate, // Global utility method for translating a given string\n FixedElements,\n growl,\n LinkDialog,\n pleaseWaitOverlay,\n Spinner,\n reloadPreview\n})\nTurbo.config.forms.confirm = openConfirmDialog\ndocument.addEventListener(\"turbo:load\", Initializer)\n\n// Public API for extensions\nexport { RemoteSelect } from \"alchemy_admin/components/remote_select\"\nexport { PageSelect } from \"alchemy_admin/components/page_select\"\nexport { AttachmentSelect } from \"alchemy_admin/components/attachment_select\"\nexport { ElementSelect } from \"alchemy_admin/components/element_select\"\nexport { TagsAutocomplete } from \"alchemy_admin/components/tags_autocomplete\"\nexport { Tinymce } from \"alchemy_admin/components/tinymce\"\nexport { on } from \"alchemy_admin/utils/events\"\n"],"names":["KEY_SEPARATOR","getTranslation","key","locale","currentLocale","translations","window","Alchemy","test","keys","split","group","nestedTranslation","console","warn","document","documentElement","lang","translate","replacement","undefined","translation","replace","bindedHotkeys","Hotkeys","scope","forEach","hotkey","unbind","search_fields","querySelectorAll","search_fields_clear","setScope","el","focus","focusVisible","push","click","blur","dataset","alchemyHotkey","createHtmlElement","text","element","createElement","innerHTML","content","children","Spinner$1","constructor","size","color","this","spinner","spin","parent","body","append","stop","remove","dispatchCustomEvent","name","detail","dispatchEvent","CustomEvent","bubbles","on","eventName","baseSelector","targetSelector","callback","baseNode","addEventListener","evt","targets","Array","from","currentNode","target","includes","call","parentElement","currentDialogs","DEFAULTS","header_height","padding","title","modal","overflow","ready","closed","Dialog","previousFlashParent","closing","url","options","width","parseInt","height","build","resize","open","dialog","bind_close_events","dialog_container","showModal","adoptFlashNotices","show","requestAnimationFrame","classList","add","load","close","releaseFlashNotices","pop","length","once","flashNotices","getElementById","clearTransientNotices","message","show_spinner","fetch","headers","then","async","response","responseText","get","Turbo","renderStreamMessage","ok","show_error","status","statusText","catch","reload","dialog_body","data","remove_spinner","style","display","init","Spinner","focusInitialElement","attempts","querySelector","activeElement","close_button","xhr","error_body","error_header","error_type","error_messages","error","e","preventDefault","dialog_header","dialog_title","textContent","getSize","minHeight","doc_width","innerWidth","doc_height","innerHeight","currentDialog","closeCurrentDialog","openDialog","removeTab","element_id","fixed_elements","panel_name","label","tab","panel","growl","flashType","flashMessage","pleaseWaitOverlay","showHelp","matches","String","fromCharCode","which","t","readJSONScript","id","script","JSON","parse","routes","buildRoutes","collection","member","Object","entries","path","ANCHOR_REGEX","PreviewWindow","HTMLIFrameElement","afterLoad","reloadIcon","loadTimeout","previewOrigin","handleEvent","type","clearLoadTimeout","stopSpinner","onPreviewReady","event","source","contentWindow","origin","connectedCallback","attachEvents","localStorage","getItem","previewUrlSelect","value","refresh","disconnectedCallback","removeEventListener","postMessage","startSpinner","src","setTimeout","Promise","resolve","isDragged","dragged","transitionProperty","pointerEvents","reloadButton","sizeSelect","setItem","clearTimeout","getAttribute","seconds","Number","isNaN","reloadPreview","customElements","define","extends","ConfirmDialog","ok_label","cancel_label","on_ok","bindEvents","cancelButton","on_cancel","hide","okButton","openConfirmDialog","IngredientAnchorLink","updateIcon","ingredientId","active","ingredientEditor","setAttribute","StreamActions","dialog_visit","visit","assign_picture","formField","targetElements","closest","setDirty","assign_attachment","fileEditor","replaceChildren","templateContent","close_current_dialog","reload_preview","remove_fixed_element","update_anchor_icon","hide_please_wait_overlay","Icon","HTMLElement","observedAttributes","super","spriteUrl","render","attributeChangedCallback","sizeClass","iconName","escape_html","str","focusTomSelect","tomSelect","focusFallback","teardownTomSelect","destroy","createDropdownPositioning","dropdownMask","className","removeAutoUpdater","onDropdownOpen","styles","minWidth","control","offsetWidth","zIndex","assign","dropdown","dropdownRoot","autoUpdate","x","y","computePosition","placement","middleware","offset","flip","shift","apply","availableWidth","availableHeight","elements","floating","maxWidth","reference","maxHeight","Math","max","left","top","onDropdownClose","control_input","removeButton","html","button","tabIndex","dropdownMessages","option_create","escape","input","loading","no_results","loading_more","no_more_results","hightlightTerm","term","escapedName","RegExp","match","serializeParams","params","prefix","map","scopedKey","isArray","item","encodeURIComponent","join","filter","part","RemoteSelect","selectedItem","selectedItems","Map","HTMLInputElement","inputValue","TomSelect","tomSelectConfig","bind","onChange","multiple","added","set","removed","delete","stringify","values","onOpen","allowClear","hasAttribute","selection","placeholder","queryParams","self","ajax","ajaxConfig","plugins","virtual_scroll","remove_button","clear_button","valueField","maxItems","closeAfterSelect","maxOptions","searchField","loadThrottle","quietMillis","refreshThrottle","openOnFocus","shouldLoad","firstUrl","query","requestUrl","onInitialize","items","addOption","$preselection","addItem","onType","toggle","lastValue","option","_escape","_renderResult","_renderListEntry","datatype","page","_searchQuery","results","_parseResponse","separator","getUrl","URL","location","searchParams","Accept","Error","more","json","setNextUrl","onChangeMultiple","previous","itemData","ids","Set","current","has","fromEntries","startsWith","q","name_i_cont","meta","per_page","total_count","renderEntry","_entry","renderSelection","_selectedEntry","slots","leadColumn","cell","primary","raw","aside","secondary","secondaryAside","icon","media","badge","truncate","_hightlightTerm","AttachmentSelect","attachment","icon_css_class","AutoSubmit","submitEvent","Event","cancelable","form","Button","HTMLButtonElement","stopPropagation","disable","enable","rect","getBoundingClientRect","initialButtonText","removeAttribute","CharCounter","maxChars","getFormField","createDisplayElement","countCharacters","formFields","after","charLength","ClearScheduleButton","ClipboardButton","clipboard","ClipboardJS","Select","HTMLSelectElement","initTomSelect","destroyTomSelect","setOptions","prompt","selectedValue","Option","hasOwnProperty","renderers","hasPlaceholder","hasSelectedOption","settings","autofocus","clear","allowEmptyOption","sortField","renderColorOption","swatch","colorPicker","disabled","ColorInput","colorInput","textInput","ConditionalField","checked","hidden","fields","field","Datepicker","import","isConnected","flatpickr","inputField","flatpickrOptions","enableTime","inputType","slice","altInput","altFormat","altInputClass","noCalendar","time_24hr","onValueUpdate","_selectedDates","_dateStr","instance","dateFormat","DialogLink","HTMLAnchorElement","dialogOptions","contains","leaveConfirmed","DirtyGuard","guard","isPageDirty","confirmLeave","confirmed","requestSubmit","onbeforeunload","JSON_CONTENT_TYPE","isGetRequest","method","toLowerCase","prepareOptions","accept","getToken","prepareHeaders","attributes","patch","post","URLSearchParams","toString","prepareURL","contentType","isJson","isTurboStream","responseData","redirect_url","DomIdSelect","dataItem","hash","selectElement","pageId","fetchDomIds","api_ingredients_path","page_id","ingredients","ingredient","dom_id","reset","frame","contentDocument","PublishElementButton","scheduleButtonVariant","scheduleButton","publishButton","dispatchPageDirtyEvent","tooltip","publishButtonTooltip","DeleteElementButton","removeElement","elementEditor","fixed","elementId","pageHasUnpublishedChanges","ElementEditor","header","toggleButton","onHeaderDblclick","onToggleClick","focusElement","previewWindow","focusElementPreview","onClickElement","setClean","onSaveElement","success","hasEditors","firstChild","setTitle","selectTabForElement","expand","elementErrors","scrollToElement","scrollIntoView","behavior","scroll","reject","tabs","dirty","editor","quote","collapsed","collapse","compact","toggleIcon","collapse_admin_element_path","nestedElementIds","selector","nestedElement","finally","expanded","parentElementEditor","expand_admin_element_path","parentElementIds","updateTitle","published","isPublished","bodySelector","footer","elementName","hasChildren","renderName","ElementSelect","description","hint","ElementsWindow","visible","turboFrame","collapseButton","onCollapseClick","onWindowMessage","onBodyClick","focusElementEditor","collapseAllElements","removeProperty","widthFromCookie","setProperty","cookie","find","row","ElementsWindowHandle","dragging","elementsWindow","onMouseDown","onMouseUp","onDrag","pageX","getComputedStyle","parseFloat","elementWindowWidth","min","FileEditor","deleteLink","fileIcon","fileName","formFieldId","removeFile","ListFilter","debounceTimer","filterField","clearButton","onHotkey","onKeyup","onFocus","onClearClick","onEscape","visibility","matchedItems","itemsToShow","lowerTerm","nameAttribute","indexOf","ancestor","itemsSelector","block","debounceTime","MainNaviEntry","flyout","stopAutoUpdate","flyoutElement","strategy","fallbackPlacements","position","round","AlchemyMenuNode","folder","toggleFolded","node","toggle_folded_api_path","nodeId","folded","updateFolder","toggleAttribute","childrenList","Message","dismissTimeoutId","dismiss","closeIcon","dismissable","insertAdjacentHTML","dismissDelay","noticesWrapper","autoDismissDelay","Growl","ImageCropper","cropper","cropFromField","cropSizeField","image","onSubmit","onReset","Cropper","cropperOptions","aspectRatio","viewMode","zoomable","checkCrossOrigin","checkOrientation","box","ratio","NaN","cropFrom","v","cropSize","defaultBox","update","coords","setData","canvas","getCanvasData","scale","naturalWidth","setCropBoxData","getData","ImageOverlay","clickHandler","nodeName","keydownHandler","next","initKeyboardNavigation","href","IngredientGroup","HTMLDetailsElement","localStorageKey","isInLocalStorage","expanded_ingredient_groups","localStorageItem","LinkButton","LinkDialog","linkUrl","linkTitle","linkTarget","linkClass","link","setLink","linkButtons","linkUrlField","linkTitleField","linkTargetField","linkClassField","UnlinkButton","linked","isLinked","LinkButtons","removeLink","unlinkButton","setElementDirty","linkButton","NewsReader","track","slides","currentIndex","createDots","setActive","startRotation","passive","stopRotation","syncActiveToScroll","dotList","dots","_","index","dot","goTo","inline","clientWidth","scrollLeft","i","timer","setInterval","interval","clearInterval","AlchemyNodeFolder","NodeForm","onPageSelect","nameField","urlField","url_path","name_or_page_name_i_cont","breadcrumb","ancestors","AlchemyNodeTree","list","Sortable","animation","fallbackOnBody","swapThreshold","handle","invertSwap","draggable","onEnd","handleSort","to","oldIndex","newIndex","move_api_path","target_parent_id","recordId","new_position","updateFolders","formatFileSize","bytes","exponent","floor","log","pow","toFixed","FileUpload","file","request","progressEventLoaded","progressEventTotal","valid","errorMessage","loadedSize","cancel","reader","FileReader","readAsDataURL","Image","result","prepend","initialize","validateFile","addRequestEventListener","finished","abort","config","maxFileSize","file_size_limit","allowedFiletypes","allowed_filetypes","alchemy_pictures","alchemy_attachments","extension","some","upload","onprogress","progressEvent","onload","responseMessage","onerror","errorMessageContainer","progressElement","loaded","total","isValid","Progress","fileCount","fileUploads","buttonLabel","actionButton","handleFileChange","updateView","onComplete","fileUpload","activeUploads","setupCloseButton","_status","ariaLabel","sumFileProgresses","reduce","accumulator","totalProgress","overallProgressValue","overallUploadSize","every","entry","fileProgress","uploadedFileCount","uploadsStatuses","totalSize","ceil","weight","Uploader","dropzoneElement","isDraggedOver","fileInput","onFileInputChange","dropzone","setupDropZone","onDropzoneDragleave","onDropzoneDrop","onDropzoneDragover","handleUploadComplete","uploadFiles","files","toggleDropzoneClass","enabled","dataTransfer","getAsFile","redirectUrl","uploadProgress","fileUploadCount","XMLHttpRequest","upload_limit","submitFile","createProgress","formData","FormData","action","setRequestHeader","send","layout","progressContainer","Overlay","BUTTON","AlchemyPageNode","folderButton","handleFolderClick","currentTarget","fold_admin_page_path","toggleChildren","updateFolderIcon","childrenContainer","updateFolderButton","folderElement","shouldShowButton","tagName","outerHTML","PagePublicationFields","publicField","onClick","checkbox","date","Date","now","getTime","getTimezoneOffset","toISOString","publicationDateFields","publicOnPicker","substring","publicUntilPicker","PageSelect","pages","site","language_code","PictureArchive","selectAllButton","onCheckboxChange","onEditMultipleClick","onSelectAllClick","state","toggleCheckboxes","selectedItemTools","checkedInputs","editMultiplePicturesUrl","PictureDescriptionSelect","select","PictureEditor","pictureIdField","targetSizeField","imageCropperField","pictureThumbnail","deleteButton","cropLink","targetSize","pictureId","func","delay","timeout","args","that","debounce","updateImage","updateCropLink","removeImage","observer","MutationObserver","mutationCallback","observe","disconnect","mutationsList","mutation","url_admin_picture_path","crop","imageCropperEnabled","crop_from","crop_size","flatten","alt","defaultCropFrom","defaultCropSize","mask","n","zoom","a","imageFileWidth","b","imageFileHeight","dimensions","imageCropper","PictureThumbnail","onLoaded","onError","start","setImage","createImage","complete","hoist","PublishPageButton","markDirty","variant","AlchemySitemap","searchInput","resultCounter","setupSearch","setupSortables","mutations","addedNodes","nodeType","Node","ELEMENT_NODE","setupSortable","childList","subtree","teardownSearch","handleSearch","handleClearSearch","trim","filterPages","clearFilter","allPages","matchCount","firstMatch","pageElement","container","pageNode","move_admin_page_path","parentId","pageData","pageEl","urlPathEl","updateFolderIcons","fromContainer","toContainer","fromParent","toParent","SORTABLE_OPTIONS","ghostClass","easing","onStart","onSort","parent_element_id","order_admin_elements_path","preview_text","SortableElements","put","_from","droppableElements","TagsAutocomplete","getElementsByTagName","delimiter","create","createFilter","duplicates","persist","clearAfterSelect","DARK_THEME","LIGHT_THEME","Tinymce","min_height","root","rootMargin","threshold","tinymceIntersectionObserver","IntersectionObserver","intersectionRatio","_initTinymceEditor","unobserve","_setupThemeChangeListener","_removeThemeChangeListener","tinymce","editorId","configuration","editors","_setupEditor","editorContainer","darkModeMediaQuery","matchMedia","themeChangeHandler","_handleThemeChange","skin","content_css","customConfig","getAttributeNames","attributeName","replaceAll","preferredTheme","language","UpdateCheck","credentials","responseJSON","showStatus","showError","setDefaultAnimation","keyframes","transform","opacity","duration","iconMap","caret","resolver","mutator","svg","spriteSheet","registerIconLibrary","FixedElements","onCreateLink","link_admin_pages_path","parameterMapping","selected_tab","link_title","link_target","internalForm","attachmentSelect","updatePage","submitForm","linkFormType","internalLink","domIdSelect","linkType","elementAnchor","literal","linkUrlRegexp","end","lastIndexOf","isLinkUrl","showValidationError","errors","forms","confirm","srcElement","isPressed"],"mappings":"6ZAAA,MAAMA,EAAgB,KAWtB,SAASC,EAAeC,GACtB,MAAMC,EAASC,IAGTC,EAAeC,OAAOC,SAASF,aAErC,OAAKA,EAKDL,EAAcQ,KAAKN,GApBzB,SAA2BG,EAAcH,GACvC,MAAMO,EAAOP,EAAIQ,MAAMV,GACjBW,EAAQN,EAAaI,EAAK,IAChC,OAAIE,GACKA,EAAMF,EAAK,KAEbP,CACT,CAcWU,CAAkBP,EAAcH,GAElCG,EAAaH,IAAQA,GAP1BW,QAAQC,KAAK,2BAA2BX,gBACjCD,EAOX,CAEO,SAASE,IACd,OAAIW,SAASC,gBAAgBC,KACpBF,SAASC,gBAAgBC,KAE3B,IACT,CAEO,SAASC,EAAUhB,EAAKiB,OAAcC,GAC3C,IAAIC,EAAcpB,EAAeC,GAEjC,OAAIiB,EACKE,EAAYC,QAAQ,UAAWH,GAEjCE,CACT,CCxCA,MAAME,EAAgB,GAEP,SAAAC,EAAUC,EAAQV,UAE3BU,IAAUV,UACZQ,EAAcG,QAASC,GAAWzB,IAAI0B,OAAOD,IAI/C,MAAME,EAAgBJ,EAAMK,iBAAiB,uBACvCC,EAAsBN,EAAMK,iBAChC,+CAEF5B,IAAI,QAAS,WAGX,OAFAA,IAAI8B,SAAS,UACbH,EAAcH,QAASO,GAAOA,EAAGC,MAAM,CAAEC,cAAc,MAChD,CACT,GACAZ,EAAca,KAAK,SACnBlC,IAAI,MAAO,SAAU,WACnB6B,EAAoBL,QAASO,GAAOA,EAAGI,SACvCR,EAAcH,QAASO,GAAOA,EAAGK,OACnC,GACAf,EAAca,KAAK,OAOnBX,EAAMK,iBAAiB,yBAAyBJ,QAAQ,SAAUO,GAChE,MAAMN,EAASM,EAAGM,QAAQC,cAC1BtC,IAAIyB,EAAQ,IAAMM,EAAGI,SACrBd,EAAca,KAAKT,EACrB,EACF,CChCO,SAASc,EAAkBC,GAChC,MAAMC,EAAU5B,SAAS6B,cAAc,YAEvC,OADAD,EAAQE,UAAYH,EACbC,EAAQG,QAAQC,SAAS,EAClC,CCPe,IAAAC,EAAA,MACb,WAAAC,CAAYC,EAAMC,EAAQ,gBACxBC,KAAKF,KAAOA,EACZE,KAAKD,MAAQA,EACbC,KAAKC,aAAUjC,CACjB,CAKA,MAAIa,GACF,OAAOmB,KAAKC,OACd,CAIA,IAAAC,CAAKC,GAQH,YAPsB,IAAXA,IACTA,EAASxC,SAASyC,MAEpBJ,KAAKC,QAAUZ,EACb,0BAA0BW,KAAKF,gBAAgBE,KAAKD,6BAEtDI,EAAOE,OAAOL,KAAKC,SACZD,IACT,CAEA,IAAAM,GACMN,KAAKC,UACPD,KAAKC,QAAQM,SACbP,KAAKC,aAAUjC,EAEnB,GCjCK,SAASwC,EAAoBjB,EAASkB,EAAMC,EAAS,CAAA,GAC1DnB,EAAQoB,cAAc,IAAIC,YAAYH,EAAM,CAAEI,SAAS,EAAMH,WAC/D,CAEO,SAASI,EAAGC,EAAWC,EAAcC,EAAgBC,GAC1DvD,SAASe,iBAAiBsC,GAAc1C,QAAS6C,IAC/CA,EAASC,iBAAiBL,EAAYM,IACpC,MAAMC,EAAUC,MAAMC,KAAKL,EAASzC,iBAAiBuC,IACrD,IAAIQ,EAAcJ,EAAIK,OAEtB,KAAOD,IAAgBN,GAAU,CAC/B,GAAIG,EAAQK,SAASF,GAEnB,YADAP,EAASU,KAAKH,EAAaJ,GAG7BI,EAAcA,EAAYI,aAC5B,KAGN,CCbA,MAAMC,EAAiB,GAQjBC,EAAW,CACfC,cAAe,GACflC,KAAM,UACNmC,SAAS,EACTC,MAAO,GACPC,OAAO,EACPC,SAAU,UACVC,MAAO,OACPC,OAAQ,QAGH,MAAMC,EACXC,GAAuB,KACvBC,IAAW,EAOX,WAAA5C,CAAY6C,EAAKC,EAAU,IACzB3C,KAAK0C,IAAMA,EACX1C,KAAK2C,QAAU,IAAKZ,KAAaY,GACjC,MAAM7C,EAAOE,KAAK2C,QAAQ7C,KAAKxC,MAAM,KACrC0C,KAAK4C,MAAQC,SAAS/C,EAAK,GAAI,IAC/BE,KAAK8C,OAASD,SAAS/C,EAAK,GAAI,IAChCE,KAAK+C,QACL/C,KAAKgD,QACP,CAGA,IAAAC,GACEzC,EAAoBR,KAAKkD,OAAQ,sBACjClD,KAAKmD,oBACDnD,KAAK2C,QAAQR,OACfnC,KAAKoD,iBAAiBC,YACtBrD,MAAKsD,KAELtD,KAAKoD,iBAAiBG,OAExBrG,OAAOsG,sBAAsB,KAC3BxD,KAAKoD,iBAAiBK,UAAUC,IAAI,UAEtC/F,SAASyC,KAAKqD,UAAUC,IAAI,qBAC5B5B,EAAe9C,KAAKgB,MACpBA,KAAK2D,MACP,CAGA,KAAAC,GAKE,OAAI5D,MAAKyC,IAGTzC,MAAKyC,GAAW,EAChBjC,EAAoBR,KAAKkD,OAAQ,uBACjClD,KAAKoD,iBAAiBK,UAAUlD,OAAO,QACvCP,KAAKoD,iBAAiBhC,iBACpB,gBACA,KAGEpB,MAAK6D,IACL7D,KAAKoD,iBAAiBQ,QACtB5D,KAAKoD,iBAAiB7C,SACtBuB,EAAegC,MACe,IAA1BhC,EAAeiC,QACjBpG,SAASyC,KAAKqD,UAAUlD,OAAO,qBAEN,MAAvBP,KAAK2C,QAAQL,QACftC,KAAK2C,QAAQL,UAGjB,CAAE0B,MAAM,MArBD,CAwBX,CAMA,EAAAV,GACE,MAAMW,EAAetG,SAASuG,eAAe,iBACzCD,IACFjE,MAAKwC,EAAuByB,EAAapC,cACzC7B,KAAKoD,iBAAiB/C,OAAO4D,GAC7BjE,MAAKmE,EAAuBF,GAEhC,CAMA,EAAAJ,GACE,MAAMI,EAAetG,SAASuG,eAAe,iBACzCD,GAAgBjE,MAAKwC,GACvBxC,MAAKwC,EAAqBnC,OAAO4D,EAErC,CAQA,EAAAE,CAAuBF,GACrBA,EACGvF,iBAAiB,oDACjBJ,QAAS8F,GAAYA,EAAQ7D,SAClC,CAOA,IAAAoD,GACE3D,KAAKqE,eACLC,MAAMtE,KAAK0C,IAAK,CACd6B,QAAS,CAAE,cAjIc,0BAmIxBC,KAAKC,MAAOC,IACX,MAAMC,QAAqBD,EAASpF,OAMlCoF,EAASH,QACNK,IAAI,iBACHjD,SA1IoB,8BA4IxBkD,EAAMC,oBAAoBH,GACjBD,EAASK,GAClB/E,KAAK9B,QAAQyG,GAEb3E,KAAKgF,WAAW,CACdC,OAAQP,EAASO,OACjBC,WAAYR,EAASQ,WACrBP,mBAILQ,MAAM,KACLnF,KAAKgF,WAAW,CAAEC,OAAQ,KAEhC,CAGA,MAAAG,GACEpF,KAAKqF,YAAY5F,UAAY,GAC7BO,KAAK2D,MACP,CAGA,OAAAzF,CAAQoH,GACNtF,KAAKuF,iBACLvF,KAAKqF,YAAYG,MAAMC,QAAU,OACjCzF,KAAKqF,YAAY5F,UAAY6F,EAC7BtF,KAAK0F,OACLlF,EAAoBR,KAAKkD,OAAQ,sBAAuB,CACtD9C,KAAMJ,KAAKqF,cAEa,MAAtBrF,KAAK2C,QAAQN,OACfrC,KAAK2C,QAAQN,MAAMrC,KAAKqF,aAE1BrF,KAAKqF,YAAYG,MAAMC,QAAU,EACnC,CAGA,YAAApB,GACErE,KAAKC,QAAU,IAAI0F,EAAQ,UAC3B3F,KAAKC,QAAQC,KAAKF,KAAKqF,YACzB,CAGA,cAAAE,GACEvF,KAAKC,QAAQK,MACf,CAGA,IAAAoF,GACEtH,EAAQ4B,KAAKqF,aACbnI,OAAOsG,sBAAsB,IAAMxD,MAAK4F,IAC1C,CAIA,EAAAA,CAAqBC,EAAW,GAC9B,MAAMnE,EACJ1B,KAAKqF,YAAYS,cAAc,gBAC/B9F,KAAKqF,YAAYS,cAAc,wBACjCpE,GAAQ5C,QACJnB,SAASoI,gBAAkB/F,KAAKgG,cAAgBH,EAAW,IAC7D3I,OAAOsG,sBAAsB,IAC3BxD,MAAK4F,EAAqBC,EAAW,GAG3C,CAGA,UAAAb,CAAWiB,EAAKf,GACd,GAAmB,MAAfe,EAAIhB,OAGN,OAFAjF,KAAKqF,YAAY5F,UAAYwG,EAAItB,kBACjC3E,KAAK0F,OAIP,MAAMQ,WAAEA,EAAUC,aAAEA,EAAYC,WAAEA,GAAepG,KAAKqG,eACpDJ,EACAf,GAGFlF,KAAKqF,YAAY5F,UAAY,0BAA0B2G,kBAC/CD,oBACDD,+BAET,CAGA,cAAAG,CAAeJ,EAAKf,GAClB,IAAIgB,EACFC,EACAC,EAAa,UAEf,OAAQH,EAAIhB,QACV,KAAK,EACHkB,EAAe,+BACfD,EAAa,qCACb,MACF,KAAK,IACHC,EAAe,0BACfD,EAAa,4BACb,MACF,QACEE,EAAa,QACTlB,GACFiB,EAAejB,EACfzH,QAAQ6I,MAAML,EAAItB,eAElBwB,EAAe,GAAGF,EAAIf,eAAee,EAAIhB,UAE3CiB,EAAa,kCAGjB,MAAO,CAAEC,eAAcD,aAAYE,aACrC,CAMA,iBAAAjD,GACEnD,KAAKgG,aAAa5E,iBAAiB,QAAUmF,IAC3CA,EAAEC,iBACFxG,KAAK4D,UAEP5D,KAAKoD,iBAAiBhC,iBAAiB,QAAUmF,IAC/CA,EAAEC,iBACFxG,KAAK4D,UAEP5D,KAAKoD,iBAAiBK,UAAUC,IAAI,YAKpC1D,KAAKoD,iBAAiBhC,iBAAiB,cAAgBmF,IACjDA,EAAE7E,SAAW1B,KAAKoD,kBACpBpD,KAAK4D,UAGT5D,KAAKoD,iBAAiBhC,iBAAiB,SAAWmF,IAI5CA,EAAE7E,SAAW1B,KAAKoD,mBAGtBmD,EAAEC,iBACFxG,KAAK4D,UAET,CAGA,KAAAb,GACE/C,KAAKoD,iBAAmB/D,EACtB,sDAEFW,KAAKkD,OAAS7D,EAAkB,sCAChCW,KAAKqF,YAAchG,EACjB,2CAEFW,KAAKyG,cAAgBpH,EACnB,6CAEFW,KAAK0G,aAAerH,EAClB,4CAEFW,KAAKgG,aAAe3G,EAClB,4FAEFW,KAAK0G,aAAaC,YAAc3G,KAAK2C,QAAQT,MAC7ClC,KAAKyG,cAAcpG,OAAOL,KAAK0G,cAC/B1G,KAAKyG,cAAcpG,OAAOL,KAAKgG,cAC/BhG,KAAKkD,OAAO7C,OAAOL,KAAKyG,eACxBzG,KAAKkD,OAAO7C,OAAOL,KAAKqF,aACxBrF,KAAKoD,iBAAiB/C,OAAOL,KAAKkD,QAC9BlD,KAAK2C,QAAQR,OACfnC,KAAKkD,OAAOO,UAAUC,IAAI,SAExB1D,KAAK2C,QAAQV,SACfjC,KAAKqF,YAAY5B,UAAUC,IAAI,UAEjC/F,SAASyC,KAAKC,OAAOL,KAAKoD,iBAC5B,CAIA,MAAAJ,GACE,MAAMJ,MAAEA,EAAKE,OAAEA,GAAW9C,KAAK4G,UAE/B5G,KAAKkD,OAAOsC,MAAM5C,MAAQ,GAAGA,MAC7B5C,KAAKkD,OAAOsC,MAAMqB,UAAY,GAAG/D,MACjC9C,KAAKkD,OAAOsC,MAAMpD,SAAWpC,KAAK2C,QAAQP,SAEZ,WAA1BpC,KAAK2C,QAAQP,UACfpC,KAAKqF,YAAYG,MAAM1C,OAAS,GAAGA,MACnC9C,KAAKqF,YAAYG,MAAMpD,SAAW,SAElCpC,KAAKqF,YAAYG,MAAMqB,UAAY,GAAG/D,MACtC9C,KAAKqF,YAAYG,MAAMpD,SAAW,UAEtC,CAEA,OAAAwE,GACE,MAAM3E,EAAUjC,KAAK2C,QAAQV,QAAU,GAAK,EACtC6E,EAAY5J,OAAO6J,WACnBC,EAAa9J,OAAO+J,YAE1B,IAAIrE,EAAQ5C,KAAK4C,MACbE,EAAS9C,KAAK8C,OAUlB,OARIF,GAASkE,IACXlE,EAAQkE,EAAY7E,GAGlBa,GAAUkE,IACZlE,EAASkE,EAAa/E,EAAUF,EAASC,eAGpC,CAAEY,QAAOE,SAClB,EAIK,SAASoE,IACd,MAAMnD,OAAEA,GAAWjC,EACnB,GAAe,IAAXiC,EAGJ,OAAOjC,EAAeiC,EAAS,EACjC,CAMO,SAASoD,EAAmBjG,GACjC,MAAMgC,EAASgE,IACf,GAAc,MAAVhE,EAEF,OADAA,EAAOP,QAAQL,OAASpB,EACjBgC,EAAOU,OAElB,CAGO,SAASwD,EAAW1E,EAAKC,GAC9B,IAAKD,EACH,KAAM,uCAEO,IAAIH,EAAOG,EAAKC,GACxBM,MACT,CCpYO,SAASoE,EAAUC,GACxB,MAAMC,EAAiB5J,SAASuG,eAAe,kBACzCsD,EAAa,iBAAiBF,IAEpCC,EAAezB,cAAc,iBAAiB0B,OAAgBjH,SAC9DgH,EAAezB,cAAc,sBAAsB0B,OAAgBjH,SAEnEgH,EAAehE,KAAK,wBACtB,+CAtBO,SAAmB+D,EAAYG,GACpC,MAAMF,EAAiB5J,SAASuG,eAAe,kBACzCsD,EAAa,iBAAiBF,IAE9BI,EAAM,6BAA6BF,MAAeC,aAClDE,EAAQ,uBAAuBH,6BAErCD,EAAe9H,WAAaiI,EAAMC,EAElCzK,OAAOsG,sBAAsB,WAC3B+D,EAAehE,KAAKiE,EACtB,EACF,gBCDO,SAASI,EAAMxD,EAASoB,EAAQ,WAVvC,SAAepB,EAASyD,GACtB,MAAM5D,EAAetG,SAASuG,eAAe,iBACvC4D,EAAezI,EAAkB,gCACZwI,0BACrBzD,iCAGNH,EAAa5D,OAAOyH,EACtB,CAGE/E,CAAMqB,EAASoB,EACjB,CCTe,SAASuC,EAAkBxE,GAAO,GAC/C5F,SAASmI,cAAc,mBAAmBvC,OAASA,CACrD,CCFA,SAASyE,EAAS3G,GAChB,SACGA,EAAIK,OAAOuG,QAAQ,oBACe,MAAnCC,OAAOC,aAAa9G,EAAI+G,UAExBhB,EAAW,cAAe,CACxBlF,MAAO/E,QAAQkL,EAAE,QACjBvI,KAAM,aAED,EAIX,CCbO,SAASwI,EAAeC,GAC7B,MAAMC,EAAS7K,SAASuG,eAAeqE,GAEvC,GAAKC,EAEL,OAAOC,KAAKC,MAAMF,EAAO7B,YAC3B,CCMO,MAAMgC,EAbb,SAASC,GAAYC,WAAEA,EAAa,CAAA,EAAEC,OAAEA,EAAS,CAAA,GAAO,IACtD,MAAMH,EAAS,IAAKE,GASpB,OAPAE,OAAOC,QAAQF,GAAQxK,QAAQ,EAAEmC,EAAMwI,MACrCN,EAAOlI,GACW,iBAATwI,EACFV,GAAOU,EAAK/K,QAAQ,IAAKqK,GAC1BK,EAAY,CAAEE,OAAQG,MAGvBN,CACT,CAEsBC,CAAYN,EAAe,mBCV3CY,EAAe,aCJrB,MAAMC,UAAsBC,kBAC1BC,GACAC,GACAC,GACAC,GAAiB,KAEjB,WAAAC,CAAYpI,GACO,SAAbA,EAAIqI,OACN1J,MAAK2J,IACL3J,MAAK4J,IACL5J,MAAKqJ,GAAYzH,KAAK5B,KAAMqB,GAEhC,CAEAwI,GAAmBC,IACbA,EAAMC,SAAW/J,KAAKgK,eAEC,yBAAvBF,EAAMxE,KAAKlB,UACbpE,MAAKwJ,EAAiBM,EAAMG,OAC5BjK,MAAK2J,IACL3J,MAAK4J,MAQT,iBAAAM,GACE,IAAIxH,EAAM1C,KAAK0C,IAEf1C,KAAKoB,iBAAiB,OAAQpB,MAC9BA,MAAKmK,IACLjN,OAAOkE,iBAAiB,UAAWpB,MAAK6J,GAEpC3M,OAAOkN,aAAaC,QAAQ,yBAC9B3H,EAAMxF,OAAOkN,aAAaC,QAAQ,uBAClCrK,KAAKsK,iBAAiBC,MAAQ7H,GAGhC1C,KAAKwK,QAAQ9H,EACf,CAEA,oBAAA+H,GACEzK,KAAK0K,oBAAoB,OAAQ1K,MACjClD,IAAI0B,OAAO,SACXtB,OAAOwN,oBAAoB,UAAW1K,MAAK6J,EAC7C,CAEA,WAAAc,CAAYrF,GACNtF,MAAKwJ,GACPxJ,KAAKgK,cAAcW,YAAYrF,EAAMtF,MAAKwJ,EAE9C,CAEA,MAAAxG,CAAOJ,GACL5C,KAAKwF,MAAM5C,MAAQ,GAAGA,KACxB,CAEA,OAAA4H,CAAQ9H,GAiBN,OAhBA1C,MAAKwJ,EAAiB,KACtBxJ,MAAK4K,IAGH5K,KAAK6K,IADHnI,GAGS1C,KAAK0C,IAIlB1C,MAAK2J,IACL3J,MAAKuJ,EAAeuB,WAAW,KAC7B9K,MAAK4J,IACLhC,EAAM9J,EAAU,0BAA2B,YAC1CkC,KAAKuJ,aAED,IAAIwB,QAASC,IAClBhL,MAAKqJ,EAAa2B,GAEtB,CAEA,aAAIC,CAAUC,GACZlL,KAAKwF,MAAM2F,mBAAqBD,EAAU,OAAS,KACnDlL,KAAKwF,MAAM4F,cAAgBF,EAAU,OAAS,IAChD,CAEA,EAAAf,GACEnK,KAAKqL,cAAcjK,iBAAiB,QAAUC,IAC5CA,EAAImF,iBACJxG,KAAKwK,YAGP1N,IAAI,QAAS,IAAMkD,KAAKwK,WAExBxK,KAAKsL,WAAWlK,iBAAiB,SAAWC,IAC1C,MACMuB,EADSvB,EAAIK,OACE6I,MAEP,KAAV3H,EACF5C,KAAKwF,MAAM5C,MAAQ,KAEnB5C,KAAKgD,OAAOJ,KAIhB5C,KAAKsK,kBAAkBlJ,iBAAiB,SAAWC,IACjD,MAAMqB,EAAMrB,EAAIK,OAAO6I,MACvBrN,OAAOkN,aAAamB,QAAQ,sBAAuB7I,GACnD1C,KAAKwK,QAAQ9H,IAEjB,CAEA,EAAAkI,GAEO5K,KAAKqL,aAAa5L,UAAUkC,SAAS,qBACxC3B,MAAKsJ,EAActJ,KAAKqL,aAAa5L,WAEvCO,KAAKqL,aAAa5L,UAAY,kDAChC,CAEA,EAAAmK,GACE5J,KAAKqL,aAAa5L,UAAYO,MAAKsJ,CACrC,CAEA,EAAAK,GACM3J,MAAKuJ,IACPiC,aAAaxL,MAAKuJ,GAClBvJ,MAAKuJ,EAAe,KAExB,CAEA,OAAI7G,GACF,OAAO1C,KAAKyL,aAAa,MAC3B,CAEA,eAAIlC,GACF,MAAMmC,EAAU7I,SAAS7C,KAAKyL,aAAa,gBAAiB,IAC5D,OAA+C,KAAvCE,OAAOC,MAAMF,GAAW,EAAIA,EACtC,CAEA,cAAIJ,GACF,OAAO3N,SAASmI,cAAc,sBAChC,CAEA,oBAAIwE,GACF,OAAO3M,SAASmI,cAAc,qBAChC,CAEA,gBAAIuF,GACF,OAAO1N,SAASmI,cAAc,yBAChC,EAOK,SAAS+F,IACQlO,SAASuG,eAAe,0BAChCsG,SAChB,CAPAsB,eAAeC,OAAO,yBAA0B5C,EAAe,CAC7D6C,QAAS,WChJX,MAAMC,EACJ,WAAApM,CAAYuE,EAASzB,EAAU,IAC7B3C,KAAKoE,QAAUA,EACfpE,KAAK2C,QAAU,IAZC,CAElB7C,KAAM,UACNoC,MAAOpE,EAAU,kBACjBoO,SAAUpO,EAAU,OACpBqO,aAAcrO,EAAU,MACxB,KAAAsO,GAAS,MAM+BzJ,GACtC3C,MAAK+C,IACL/C,MAAKqM,GACP,CAEA,IAAApJ,GACEO,sBAAsB,KACpBxD,KAAKkD,OAAOK,QAEhB,CAEA,EAAAR,GACE,MAAMH,EAAQ5C,KAAK2C,QAAQ7C,KAAKxC,MAAM,KAAK,GAC3C0C,KAAKkD,OAAS7D,EAAkB,6BACVW,KAAK2C,QAAQT,0BAA0BU,kBACvD5C,KAAKoE,yGAEHpE,KAAK2C,QAAQwJ,8GAGbnM,KAAK2C,QAAQuJ,0DAQHhF,KAAiB9D,kBAAoBzF,SAASyC,MACtDC,OAAOL,KAAKkD,OACxB,CAEA,EAAAmJ,GACErM,KAAKsM,aAAalL,iBAAiB,QAAUC,IAC3CA,EAAImF,iBACJxG,KAAK2C,QAAQ4J,YACbvM,KAAKkD,OAAOsJ,SAEdxM,KAAKyM,SAASrL,iBAAiB,QAAUC,IACvCA,EAAImF,iBACJxG,KAAK2C,QAAQyJ,QACbpM,KAAKkD,OAAOsJ,SAGdxM,KAAKkD,OAAO9B,iBAAiB,mBAAqB0I,IACpB,YAAxBA,EAAMpJ,OAAOqJ,SACf/J,KAAK2C,QAAQ4J,YACbzC,EAAMtD,oBAIVxG,KAAKkD,OAAO9B,iBAAiB,gBAAiB,KAC5CpB,KAAKkD,OAAO3C,UAEhB,CAEA,gBAAI+L,GACF,OAAOtM,KAAKkD,OAAO4C,cAAc,qBACnC,CAEA,YAAI2G,GACF,OAAOzM,KAAKkD,OAAO4C,cAAc,sBACnC,EAeK,SAAS4G,EAAkBtI,EAASzB,EAAU,IACnD,OAAO,IAAIoI,QAASC,IACH,IAAIiB,EAAc7H,EAAS,IACrCzB,EACH,KAAAyJ,GACEpB,GAAQ,EACV,EACA,SAAAuB,GACEvB,GAAQ,EACV,IAEK/H,QAEX,CC1Ge,MAAM0J,EACnB,iBAAOC,CAAWC,EAAcC,GAAS,GACvC,MAAMC,EAAmBpP,SAASmI,cAChC,wBAAwB+G,OAE1B,GAAIE,EAAkB,CACPA,EAAiBjH,cAC5B,6CAEGkH,aAAa,aAAcF,EAAS,OAAS,OACpD,CACF,ECAFjI,EAAMoI,cAAcC,aAAe,WACjC,MAAMxK,EAAM1C,KAAKyL,aAAa,OAEzBtE,EAAmB,IAAMtC,EAAMsI,MAAMzK,KACxCmC,EAAMsI,MAAMzK,EAEhB,EAIAmC,EAAMoI,cAAcG,eAAiB,WACnC,MAAOC,GAAarN,KAAKsN,eAEpBD,IAELA,EAAU9C,MAAQvK,KAAKyL,aAAa,cACpC4B,EAAUL,aACR,wBACAhN,KAAKyL,aAAa,qBAEpB4B,EAAUL,aACR,yBACAhN,KAAKyL,aAAa,sBAGpBtE,EAAmB,KACjBkG,EAAUE,QAAQ,2BAA2BC,aAEjD,EAEA3I,EAAMoI,cAAcQ,kBAAoB,WACtC,MAAOJ,GAAarN,KAAKsN,eAEzB,IAAKD,EAAW,OAEhB,MAAMK,EAAaL,EAAUxL,cAE7BwL,EAAU9C,MAAQvK,KAAKyL,aAAa,iBACpCiC,EAAW5H,cAAc,uBAAuBa,YAC9C3G,KAAKyL,aAAa,mBACpBiC,EACG5H,cAAc,uBACd6H,gBAAgB3N,KAAK4N,iBACxBF,EACG5H,cAAc,8BACdrC,UAAUlD,OAAO,UAEpB4G,EAAmB,KACjBkG,EAAUE,QAAQ,2BAA2BC,SAASH,IAE1D,EAKAxI,EAAMoI,cAAcY,qBAAuB,WACzC1G,GACF,EAEAtC,EAAMoI,cAAca,eAAiB,WACnCjC,GACF,EAEAhH,EAAMoI,cAAcc,qBAAuB,WACzC1G,EAAUrH,KAAKyL,aAAa,cAC9B,EAEA5G,EAAMoI,cAAce,mBAAqB,WACvCrB,EAAqBC,WACnB5M,KAAKyL,aAAa,iBACc,SAAhCzL,KAAKyL,aAAa,UAEtB,EAEA5G,EAAMoI,cAAcgB,yBAA2B,WAC7ClG,GAAkB,EACpB,ECvFA,MAAMmG,UAAaC,YACjB,6BAAWC,GACT,MAAO,CAAC,OAAQ,OAAQ,aAC1B,CAEA,WAAAvO,GACEwO,QACArO,KAAKsO,UACH3Q,SACGmI,cAAc,oCACb2F,aAAa,SAAW,EAChC,CAEA,iBAAAvB,GACElK,KAAKuO,QACP,CAEA,wBAAAC,GACExO,KAAKuO,QACP,CAEA,MAAAA,GACE,MAAME,EAAYzO,KAAKF,KAAO,UAAUE,KAAKF,OAAS,GACtDE,KAAKP,UAAY,mBAAmBgP,iBAAyBzO,KAAKsO,gBAAgBtO,KAAK0O,WAAW1O,KAAKwF,iBACzG,CAEA,QAAI/E,CAAK8J,GACPvK,KAAKgN,aAAa,OAAQzC,EAC5B,CAEA,YAAImE,GACF,OAAO1O,KAAKyL,aAAa,OAC3B,CAEA,QAAI3L,GACF,OAAOE,KAAKyL,aAAa,OAC3B,CAEA,SAAIjG,GACF,MAAM+E,EAAQvK,KAAKyL,aAAa,cAChC,OAAQlB,GACN,IAAK,OACH,MAAO,GACT,KAAK,KACH,MAAO,QACT,QACE,MAAO,IAAIA,IAEjB,EAKGuB,eAAelH,IAAI,iBACtBkH,eAAeC,OAAO,eAAgBmC,GC1BjC,MAAMS,EAAeC,IAChBA,EAAM,IACT1Q,QAAQ,KAAM,SACdA,QAAQ,KAAM,QACdA,QAAQ,KAAM,QACdA,QAAQ,KAAM,UCdhB,SAAS2Q,EAAeC,EAAWC,GACpCD,EACFA,EAAUhQ,QAEViQ,GAEJ,CAOO,SAASC,EAAkBF,GAChCA,GAAWlL,OAAM,GACjBkL,GAAWG,SACb,CAOO,SAASC,IACd,MAAMC,EAAexR,SAAS6B,cAAc,OAC5C2P,EAAaC,UAAY,mBAEzB,IAAIC,EAAoB,OAExB,MAAO,CACL,oBAAMC,GAEJ,MAAMC,EAAS,CACbC,SAAU,GAAGxP,KAAKyP,QAAQC,iBAGxB1P,KAAKyP,QAAQlC,QAAQ,4CACvBgC,EAAOI,OAAS,OAElB5G,OAAO6G,OAAO5P,KAAK6P,SAASrK,MAAO+J,GAKnC,MAAMO,EAAe9P,KAAKyP,QAAQlC,QAAQ,WAAa5P,SAASyC,KAChE0P,EAAazP,OAAO8O,GACpBW,EAAazP,OAAOL,KAAK6P,UA0CzBR,EAAoBU,EAClB/P,KAAKyP,QACLzP,KAAK6P,SA1CgBpL,UAErB,MAAMuL,EAAEA,EAACC,EAAEA,SAAYC,EAAgBlQ,KAAKyP,QAASzP,KAAK6P,SAAU,CAIlEM,UAAW,eACXC,WAAY,CAEVC,EAAO,GAEPC,IAEAC,EAAM,CAAEtO,QAtEW,KAwEnBnC,EAAK,CACHmC,QAzEiB,GA0EjB,KAAAuO,EAAMC,eAAEA,EAAcC,gBAAEA,EAAeC,SAAEA,IAGvC5H,OAAO6G,OAAOe,EAASC,SAASpL,MAAO,CACrCqL,SAAU,OAAOF,EAASG,UAAUpB,sBAAsBe,mDAE5D1H,OAAO6G,OACLe,EAASC,SAAS9K,cAAc,wBAAwBN,MACxD,CACEuL,UAAW,GAAGC,KAAKC,IAlFX,IAkFoCP,EAnFjC,SAsFjB,OAKN3H,OAAO6G,OAAO5P,KAAK6P,SAASrK,MAAO,CACjC0L,KAAM,GAAGlB,MACTmB,IAAK,GAAGlB,SASd,EACA,eAAAmB,GACEpR,KAAKqR,cAAc5N,UAAUlD,OAAO,aACpCP,KAAKqR,cAAcnS,OAEnBc,KAAK6P,SAAStP,SACd4O,EAAa5O,SAEb8O,GACF,EAEJ,CAOO,SAASiC,IACd,MAAO,CACLpP,MAAOpE,EAAU,UACjB,IAAAyT,CAAKjM,GACH,MAAMkM,EAAS7T,SAAS6B,cAAc,UAOtC,OANAgS,EAAO9H,KAAO,SACd8H,EAAOpC,UAAY9J,EAAK8J,UACxBoC,EAAOtP,MAAQoD,EAAKpD,MACpBsP,EAAOxE,aAAa,aAAc1H,EAAKpD,OACvCsP,EAAOC,UAAW,EAClBD,EAAO/R,UAAY,uDACZ+R,CACT,EAEJ,CAIO,MAAME,EAAmB,CAC9BC,cAAa,CAACrM,EAAMsM,IACX,+BACH9T,EAAU,kBAAkB8T,EAAOtM,EAAKuM,sCAK9C,OAAAC,GACE,KAAI/I,OAAO1L,KAAK2C,KAAK2C,SAASoB,OAAS,GACvC,MAAO,gCAAgCjG,EAAU,uCACnD,EACAiU,WAAU,IACD,2BAA2BjU,EAAU,4BAI9CkU,aAAc,IACZ,6BAA6BlU,EAAU,wCAEzCmU,gBAAiB,IACf,gCAAgCnU,EAAU,4BClJvC,SAASoU,EAAezR,EAAM0R,GAInC,MAAMC,EAAczD,EAAYlO,GAChC,OAAK0R,EACEC,EAAYlU,QACjB,IAAImU,OAVCnK,OAUmBiK,GAVLjU,QAAQ,sBAAuB,QAUnB,MAC9BoU,GAAU,OAAOA,UAHFF,CAKpB,CASO,SAASG,EAAgBC,EAAQC,GACtC,OAAO1J,OAAO1L,KAAKmV,GAChBE,IAAK5V,IACJ,MAAMyN,EAAQiI,EAAO1V,GACf6V,EAAYF,EAAS,GAAGA,KAAU3V,KAASA,EACjD,OAAIyN,QAA8C,GAC9ChJ,MAAMqR,QAAQrI,GACTA,EACJmI,IACEG,GACC,GAAGC,mBAAmB,GAAGH,UAAkBG,mBAAmBD,MAEjEE,KAAK,KAEW,iBAAVxI,EACFgI,EAAgBhI,EAAOoI,GAEzB,GAAGG,mBAAmBH,MAAcG,mBAAmBvI,OAE/DyI,OAAQC,GAAkB,KAATA,GACjBF,KAAK,IACV,CAEO,MAAMG,UAAqB/E,YAChCW,GAAa,KAGbqE,GAAgB,KAGhBC,GAAiB,IAAIC,IAErB,iBAAAnJ,GACE,MAAM3K,EAAUS,KAAK6R,MAErB,GADAtS,EAAQkE,UAAUC,IAAI,qBAClBnE,aAAmB+T,iBAAkB,CAKvC,MAAMC,EAAahU,EAAQgL,MAC3BhL,EAAQgL,MAAQ,GAChBvK,MAAK8O,EAAa,IAAI0E,EAAUjU,EAASS,KAAKyT,iBAC9ClU,EAAQgL,MAAQgJ,CAClB,MAEEvT,MAAK8O,EAAa,IAAI0E,EAAUjU,EAASS,KAAKyT,iBAIhDzT,MAAK8O,EAAWW,QAAQpP,OAAO1C,SAAS6B,cAAc,eAGtDQ,MAAK8O,EAAWW,QAAQrO,iBACtB,QACApB,MAAK8O,EAAW7L,KAAKyQ,KAAK1T,MAAK8O,GAEnC,CAEA,oBAAArE,GACEuE,EAAkBhP,MAAK8O,GACvB9O,MAAK8O,EAAa,IACpB,CAEA,KAAAhQ,GACE+P,EAAe7O,MAAK8O,EAAY,IAAMT,MAAMvP,QAC9C,CAMA,QAAA6U,CAAS7J,GAIH9J,KAAK4T,UACH9J,EAAM+J,OACR7T,MAAKoT,EAAeU,IAAI5L,OAAO4B,EAAM+J,MAAMtL,IAAKuB,EAAM+J,OAEpD/J,EAAMiK,SACR/T,MAAKoT,EAAeY,OAAO9L,OAAO4B,EAAMiK,QAAQxL,KAElDvI,KAAKgN,aACH,YACAvE,KAAKwL,UAAU,IAAIjU,MAAKoT,EAAec,aAEhCpK,EAAM+J,OACf7T,KAAKgN,aAAa,YAAavE,KAAKwL,UAAUnK,EAAM+J,QAEtD7T,KAAKQ,oBAAoB,sBAAuB,CAC9CuT,QAASjK,EAAMiK,QACfF,MAAO/J,EAAM+J,OAEjB,CAMA,MAAAM,GAAU,CAQV,mBAAA3T,CAAoBC,EAAMC,EAAS,IACjCV,KAAKW,cACH,IAAIC,YAAY,WAAWH,IAAQ,CAAEI,SAAS,EAAMH,WAExD,CAEA,cAAI0T,GACF,OAAOpU,KAAKqU,aAAa,cAC3B,CAEA,YAAIT,GACF,OAAO5T,KAAKqU,aAAa,WAC3B,CAEA,aAAIC,GACF,OAAOtU,KAAKyL,aAAa,YAC3B,CAEA,eAAI8I,GACF,OAAOvU,KAAKyL,aAAa,gBAAkB,EAC7C,CAEA,eAAI+I,GACF,OAAOxU,KAAKyL,aAAa,iBAAmB,IAC9C,CAEA,OAAI/I,GACF,OAAO1C,KAAKyL,aAAa,QAAU,EACrC,CAEA,SAAIoG,GAIF,OAAO7R,KAAK8F,cAAc,gBAC5B,CAMA,mBAAI2N,GACF,MAAMgB,EAAOzU,KACP0U,EAAO1U,KAAK2U,WACZC,EAAU,CAAEC,eAAgB,CAAA,GAE9B7U,KAAK4T,WACPgB,EAAQE,cAAgB,CAAA,GAGtB9U,KAAKoU,aACPQ,EAAQG,aAAe,CACrBxD,KAAI,IACK,0DAA0DzT,EAC/D,gHAQR,MAAMwR,eAAEA,EAAc8B,gBAAEA,GAAoBlC,IAE5C,MAAO,CACL0F,UAEAI,WAAY,KAEZC,SAAUjV,KAAK4T,SAAW,KAAO,EAGjCsB,kBAAmBlV,KAAK4T,SAExBuB,WAAY,KAEZC,YAAa,GAEbC,aAAcX,EAAKY,YAInBC,gBAAiB,EACjBhB,YAAavU,KAAKuU,YAIlBiB,aAAa,EAEbC,WAAY,KAAM,EAClBC,SAAWC,GAAU3V,MAAK4V,EAAYD,EAAO,GAC7ChS,KAAM,CAACgS,EAAOzU,IAAalB,MAAK2D,EAAMgS,EAAOzU,GAC7C,YAAA2U,GACE,GAAKpB,EAAKH,UAIV,GAAIG,EAAKb,SAAU,CACjB,MAAMkC,EAAQrN,KAAKC,MAAM+L,EAAKH,WAI9BG,GAAKrB,EAAiB,IAAIC,IACxByC,EAAMpD,IAAKG,GAAS,CAAC3K,OAAO2K,EAAKtK,IAAKsK,KAExCiD,EAAMxX,QAASuU,IACb7S,KAAK+V,UAAU,IAAKlD,EAAMmD,eAAe,IACzChW,KAAKiW,QAAQpD,EAAKtK,IAAI,IAE1B,KAAO,CACL,MAAMsK,EAAOpK,KAAKC,MAAM+L,EAAKH,WAI7BG,GAAKtB,EAAgBN,EACrB7S,KAAK+V,UAAU,IAAKlD,EAAMmD,eAAe,IACzChW,KAAKiW,QAAQpD,EAAKtK,IAAI,EACxB,CACF,EACA,QAAAoL,CAASpJ,GACPkK,GAAKd,EAAUpJ,EAAOvK,KACxB,EACA,MAAAkW,CAAO/D,GAILnS,KAAKqR,cAAc5N,UAAU0S,OAAO,YAAahE,EAAKpO,OAAS,EACjE,EACA,cAAAuL,GACEA,EAAe1N,KAAK5B,MACfA,KAAK8R,SACR9R,KAAK2D,KAAK3D,KAAKoW,WAAa,IAE9B3B,EAAKN,QACP,EACA/C,kBACA7C,OAAQ,CACN,MAAA8H,CAAOxD,EAAMyD,GAIX,OAAIzD,EAAKmD,cACA,QAAQvB,EAAK8B,cAAc1D,WAE7B4B,EAAK+B,iBAAiB3D,EAAM7S,KAAKoW,UAC1C,EACAvD,KAAI,CAACA,EAAMyD,IACF,QAAQ7B,EAAK8B,cAAc1D,cAEjCnB,GAGT,CAOA,cAAIiD,GACF,MAAO,CACLjS,IAAK1C,KAAK0C,IACV+T,SAAU,OACVnB,YAAa,IACbhQ,KAAM,CAAC6M,EAAMuE,IAAS1W,KAAK2W,aAAaxE,EAAMuE,GAC9CE,QAAUlS,GAAa1E,KAAK6W,eAAenS,GAE/C,CASA,EAAAkR,CAAYzD,EAAMuE,GAChB,MAAMf,EAAQpD,EAAgBvS,KAAK2U,WAAWrP,KAAK6M,EAAMuE,IACnDI,EAAY9W,KAAK0C,IAAIf,SAAS,KAAO,IAAM,IACjD,OAAOgU,EAAQ,GAAG3V,KAAK0C,MAAMoU,IAAYnB,IAAU3V,KAAK0C,GAC1D,CASA,OAAMiB,CAAMgS,EAAOzU,GACjB,MAAMwT,EAAO1U,KAAK2U,WACZjS,EAAM1C,MAAK8O,EAAWiI,OAAOpB,GAC7Be,EAAO/K,OACX,IAAIqL,IAAItU,EAAKxF,OAAO+Z,SAAShN,QAAQiN,aAAatS,IAAI,SAExD,IACE,MAAMF,QAAiBJ,MAAM5B,EAAK,CAChC6B,QAAS,CAAE4S,OAAQ,sBAAwBzC,EAAKlC,QAAQjO,SAAW,MAKrE,IAAKG,EAASK,GACZ,MAAM,IAAIqS,MAAM,GAAG1S,EAASO,UAAUP,EAASQ,cAEjD,MAAM0R,QAAEA,EAAOS,KAAEA,GAAS3C,EAAKkC,cAAclS,EAAS4S,QAClDD,GACFrX,MAAK8O,EAAWyI,WAAW5B,EAAO3V,MAAK4V,EAAYD,EAAOe,EAAO,IAEnExV,EAAS0V,EACX,CAAE,MAAOtQ,GACPsB,EAAMtB,EAAMlC,SAAWkC,EAAO,SAC9B7I,QAAQ6I,MAAM,qBAAqB5D,WAAc4D,GAEjDpF,GACF,CACF,CASA,EAAAyS,CAAUpJ,EAAOuE,GACf,GAAI9O,KAAK4T,SAEP,YADA5T,MAAKwX,EAAkBjN,EAAOuE,GAGhC,MAAM2I,EAAWzX,MAAKmT,EACtB,GAAc,KAAV5I,GAAyB,MAATA,EAAe,CACjC,IAAKkN,EAAU,OACfzX,MAAKmT,EAAgB,KACrBnT,KAAK2T,SAAS,CAAEE,MAAO,KAAME,QAAS0D,GACxC,MAAO,IAAKA,GAAYvP,OAAOuP,EAASlP,MAAQL,OAAOqC,GAAQ,CAC7D,MAAMsJ,EAAQ7T,MAAK0X,EAAU5I,EAAUnM,QAAQ4H,IAC/CvK,MAAKmT,EAAgBU,EACrB7T,KAAK2T,SAAS,CAAEE,QAAOE,QAAS0D,GAClC,CACF,CAUA,EAAAD,CAAkBtD,EAAQpF,GACxB,MAAM6I,GAAOzD,GAAU,IAAIxB,IAAIxK,QACzBuP,EAAW,IAAIG,IAAI5X,MAAKoT,EAAe/V,QACvCwa,EAAU,IAAID,IAAID,GACxBF,EAASnZ,QAASiK,IACXsP,EAAQC,IAAIvP,IACfvI,KAAK2T,SAAS,CAAEE,MAAO,KAAME,QAAS/T,MAAKoT,EAAexO,IAAI2D,OAGlEoP,EAAIrZ,QAASiK,IACNkP,EAASK,IAAIvP,IAChBvI,KAAK2T,SAAS,CACZE,MAAO7T,MAAK0X,EAAU5I,EAAUnM,QAAQ4F,IACxCwL,QAAS,QAIjB,CAWA,EAAA2D,CAAUrB,GACR,OAAKA,EACEtN,OAAOgP,YACZhP,OAAOC,QAAQqN,GAAQrD,OAAO,EAAElW,MAAUA,EAAIkb,WAAW,OAFvC,IAItB,CASA,YAAArB,CAAaxE,EAAMuE,GACjB,MAAO,CACLuB,EAAG,CACDC,YAAa/F,KACV1J,KAAKC,MAAM1I,KAAKwU,cAErBkC,KAAMA,EAEV,CAQA,cAAAG,CAAenS,GACb,MAAMyT,EAAOzT,EAASyT,KACtB,MAAO,CACLvB,QAASlS,EAASY,KAClB+R,KAAMc,EAAKzB,KAAOyB,EAAKC,SAAWD,EAAKE,YAE3C,CAWA,gBAAA7B,CAAiB3D,EAAMV,GACrB,OAAOnS,MAAKsY,EAAatY,KAAKuY,OAAO1F,EAAMV,GAC7C,CAUA,aAAAoE,CAAc1D,GACZ,OAAO7S,MAAKwY,EAAiBxY,KAAKyY,eAAe5F,GACnD,CAYA,MAAA0F,GACE,MAAM,IAAInB,MAAM,2DAClB,CAUA,cAAAqB,GACE,MAAM,IAAIrB,MACR,kEAEJ,CAQA,EAAAkB,CAAaI,GACX,MAAO,uDAED1Y,MAAK2Y,EAAYD,eACjB1Y,MAAK4Y,EAAM,yBAA0BF,EAAMG,QAAS,CAAEC,KAAK,gBAC3D9Y,MAAK4Y,EAAM,uBAAwBF,EAAMK,mBACzC/Y,MAAK4Y,EAAM,2BAA4BF,EAAMM,uBAC7ChZ,MAAK4Y,EAAM,iCAAkCF,EAAMO,qCAG3D,CAUA,EAAAT,CAAiBE,GACf,MAAO,2DAED1Y,MAAK2Y,EAAYD,eACjB1Y,MAAK4Y,EAAM,gCAAiCF,EAAMG,QAAS,CAAEC,KAAK,gBAClE9Y,MAAK4Y,EAAM,iCAAkCF,EAAMM,gCAG3D,CASA,EAAAL,CAAYD,GACV,OAAIA,EAAMQ,KACD,mDAAmDvK,EAAY+J,EAAMQ,yBAE1ER,EAAMS,MACD,0CAA0CxK,EAAY+J,EAAMS,kBAE9D,EACT,CAaA,EAAAP,CAAMxJ,EAAW7E,GAAOuO,IAAEA,GAAM,GAAU,IACxC,GAAa,MAATvO,GAA2B,KAAVA,EAAc,MAAO,GAE1C,GAAqB,iBAAVA,EAAoB,CAC7B,GAAmB,MAAfA,EAAM6O,OAAiC,KAAhB7O,EAAM6O,MAC/B,MAAO,gBAAgBhK,2BAAmCT,EAAYpE,EAAM6O,gBAE9E,GAAkB,MAAd7O,EAAMjL,MAA+B,KAAfiL,EAAMjL,KAAa,MAAO,GACpD,MAAMA,EAAOwZ,EAAMvO,EAAMjL,KAAOqP,EAAYpE,EAAMjL,MAClD,MAAuB,SAAnBiL,EAAM8O,SACD,gBAAgBjK,wCAAgD9P,iBAElE,gBAAgB8P,MAAc9P,UACvC,CAEA,MAAO,gBAAgB8P,MAAc0J,EAAMvO,EAAQoE,EAAYpE,WACjE,CASA,eAAA+O,CAAgB7Y,EAAM0R,GACpB,OAAOD,EAAezR,EAAM0R,EAC9B,EChmBK,MAAMoH,UAAyBrG,EACpC,MAAAqF,CAAOiB,EAAYrH,GACjB,MAAO,CACL+G,KAAMM,EAAWC,eACjBZ,QAAS7Y,KAAKsZ,gBAAgBE,EAAW/Y,KAAM0R,GAEnD,CAEA,cAAAsG,CAAee,GACb,MAAO,CACLN,KAAMM,EAAWC,eACjBZ,QAASW,EAAW/Y,KAExB,EAGFqL,eAAeC,OAAO,4BAA6BwN,GChBnD,MAAMG,UAAmBvL,YACvB,iBAAAjE,GACElK,KAAKoB,iBAAiB,SAAUpB,MAAK2T,EACvC,CAEA,oBAAAlJ,GACEzK,KAAK0K,oBAAoB,SAAU1K,MAAK2T,EAC1C,CAEAA,GAAa7J,IAGX,MAAM6P,EAAc,IAAIC,MAAM,SAAU,CACtC/Y,SAAS,EACTgZ,YAAY,IAGd,OADA/P,EAAMpI,OAAOoY,KAAKnZ,cAAcgZ,IACzB,GAIX7N,eAAeC,OAAO,sBAAuB2N,GCrB7C,MAAMK,WAAeC,kBACnB,iBAAA9P,GACMlK,KAAK8Z,MACP9Z,KAAK8Z,KAAK1Y,iBAAiB,SAAUpB,MACrCA,KAAK8Z,KAAK1Y,iBAAiB,mBAAoBpB,OAE/CvC,QAAQC,KAAK,4BAA6BsC,KAE9C,CAEA,WAAAyJ,CAAYK,GACV,OAAQA,EAAMJ,MACZ,IAAK,SACkD,aAAlC1J,KAAKyL,aAAa,aAGnC3B,EAAMtD,iBACNsD,EAAMmQ,mBAENja,KAAKka,UAEP,MACF,IAAK,mBACHla,KAAKma,SAGX,CAEA,OAAAD,GACE,MAAMja,EAAU,IAAI0F,EAAQ,SACtByU,EAAOpa,KAAKqa,wBAElBra,KAAKb,QAAQmb,kBAAoBta,KAAKP,UACtCO,KAAKgN,aAAa,WAAY,YAC9BhN,KAAKgN,aAAa,WAAY,MAC9BhN,KAAKyD,UAAUC,IAAI,YACnB1D,KAAKwF,MAAM5C,MAAQ,GAAGwX,EAAKxX,UAC3B5C,KAAKwF,MAAM1C,OAAS,GAAGsX,EAAKtX,WAC5B9C,KAAKP,UAAY,SAEjBQ,EAAQC,KAAKF,KACf,CAEA,MAAAma,GACEna,KAAKyD,UAAUlD,OAAO,YACtBP,KAAKua,gBAAgB,YACrBva,KAAKua,gBAAgB,YACrBva,KAAKwF,MAAM5C,MAAQ,KACnB5C,KAAKwF,MAAM1C,OAAS,KACpB9C,KAAKP,UAAYO,KAAKb,QAAQmb,iBAChC,EAGFxO,eAAeC,OAAO,iBAAkBgO,GAAQ,CAAE/N,QAAS,WClD3D,MAAMwO,WAAoBrM,YACxB,iBAAAjE,GACElK,KAAK/B,YAAcH,EAAU,gBAAiBkC,KAAKya,UACnDza,KAAKqN,UAAYrN,KAAK0a,eAElB1a,KAAKqN,YACPrN,KAAK2a,uBACL3a,KAAK4a,kBACL5a,KAAKqN,UAAUjM,iBAAiB,QAASpB,MAE7C,CAEA,oBAAAyK,GACEzK,KAAKqN,WAAW3C,oBAAoB,QAAS1K,KAC/C,CAEA,WAAAyJ,CAAYK,GACS,UAAfA,EAAMJ,MAAkB1J,KAAK4a,iBACnC,CAEA,YAAAF,GACE,MAAMG,EAAa7a,KAAKtB,iBAAiB,mBACzC,OAAOmc,EAAW9W,OAAS,EAAI8W,EAAW,QAAK7c,CACjD,CAEA,oBAAA2c,GACE3a,KAAKyF,QAAUzF,KAAK8F,cAAc,kCAC9B9F,KAAKyF,UACTzF,KAAKyF,QAAU9H,SAAS6B,cAAc,SACtCQ,KAAKyF,QAAQ2J,UAAY,uBACzBpP,KAAKqN,UAAUyN,MAAM9a,KAAKyF,SAC5B,CAEA,eAAAmV,GACE,MAAMG,EAAa/a,KAAKqN,UAAU9C,MAAMxG,OACxC/D,KAAKyF,QAAQkB,YAAc,GAAGoU,KAAc/a,KAAK/B,cACjD+B,KAAKyF,QAAQhC,UAAU0S,OAAO,WAAY4E,EAAa/a,KAAKya,SAC9D,CAEA,YAAIA,GACF,OAAOza,KAAKyL,aAAa,cAAgB,EAC3C,EAGFK,eAAeC,OAAO,uBAAwByO,ICjD9C,MAAMQ,WAA4BhB,kBAChC,iBAAA9P,GACElK,KAAKoB,iBAAiB,QAASpB,KACjC,CAEA,oBAAAyK,GACEzK,KAAK0K,oBAAoB,QAAS1K,KACpC,CAEA,WAAAyJ,GACEzJ,KAAK8Z,MACDpb,iBAAiB,gCAClBJ,QAASuT,IACRA,EAAMtH,MAAQ,IAEpB,EAGFuB,eAAeC,OAAO,gCAAiCiP,GAAqB,CAC1EhP,QAAS,WChBX,MAAMiP,WAAwB9M,YAC5B,iBAAAjE,GACElK,KAAKP,UAAY,iDAEjBO,KAAKkb,UAAY,IAAIC,YAAYnb,KAAM,CACrCV,KAAM,IACGU,KAAKyL,aAAa,aAI7BzL,KAAKkb,UAAUpa,GAAG,UAAW,KAC3B8G,EAAM5H,KAAKyL,aAAa,kBAE5B,CAEA,oBAAAhB,GACEzK,KAAKkb,UAAUjM,SACjB,EAGFnD,eAAeC,OAAO,2BAA4BkP,ICZ3C,MAAMG,WAAeC,kBAC1BvM,GAAa,KAEb,iBAAA5E,GACElK,KAAKyD,UAAUC,IAAI,qBACnB1D,MAAKsb,GACP,CAEA,oBAAA7Q,GACEzK,MAAKub,GACP,CAEA,MAAApB,GACEna,KAAKua,gBAAgB,YACrBva,MAAK8O,GAAYqL,QACnB,CAEA,OAAAD,GACEla,KAAKgN,aAAa,WAAY,YAC9BhN,MAAK8O,GAAYoL,SACnB,CAEA,KAAApb,GACE+P,EAAe7O,MAAK8O,EAAY,IAAMT,MAAMvP,QAC9C,CAEA,UAAA0c,CAAWlW,EAAMmW,OAASzd,GACxB,MAAM0d,EAAgB1b,KAAKuK,MAI3BvK,MAAKub,IAGLvb,KAAKP,UAAY,GACbgc,GACFzb,KAAK0D,IAAI,IAAIiY,OAAOF,EAAQ,KAI9BnW,EAAKhH,QAASuU,IACZ7S,KAAK0D,IAAI,IAAIiY,OAAO9I,EAAKvT,KAAMuT,EAAKtK,IAAI,EAAOsK,EAAKtK,KAAOmT,MAG7D1b,MAAKsb,GACP,CAEA,cAAIlH,GACF,OAAOpU,KAAKb,QAAQyc,eAAe,eAAiB5b,KAAK4T,QAC3D,CAEA,eAAIW,GACF,OAAOvU,KAAKyL,aAAa,cAC3B,CAIA,aAAIoQ,GACF,MAAO,CAAA,CACT,CAEA,EAAAP,GACE,MAAM1G,EAAU,CAAA,EACVkH,IAAmB9b,KAAKuU,YAGxBwH,IAAsB/b,KAAK8F,cAAc,qBACzCwJ,eAAEA,EAAc8B,gBAAEA,GAAoBlC,IAExClP,KAAK4T,WACPgB,EAAQE,cAAgBxD,KAGtBtR,KAAKoU,aACPQ,EAAQG,aAAe,CACrBxD,KAAI,IACK,0DAA0DzT,EAC/D,gHAQR,MAAMke,EAAW,CACfpH,UACAM,kBAAmBlV,KAAK4T,SACxBiC,aAAc,WACR7V,KAAK6R,MAAMoK,WACbjc,KAAKlB,QAMHgd,IAAmBC,GACrB/b,KAAKkc,OAET,EACA,MAAAhG,CAAO/D,GACLnS,KAAKqR,cAAc5N,UAAU0S,OAAO,YAAahE,EAAKpO,OAAS,EACjE,EAEAwR,gBAAiB,EACjBjG,iBACA8B,kBACA+K,kBAAkB,EAClB3G,aAAa,EAEb4G,UAAW,SAEXjH,WAAY,KAEZ5G,OAAQ,IACHmD,KACA1R,KAAK6b,YAIZ7b,MAAK8O,EAAa,IAAI0E,EAAUxT,KAAMgc,GAGtChc,MAAK8O,EAAWW,QAAQrO,iBACtB,QACApB,MAAK8O,EAAW7L,KAAKyQ,KAAK1T,MAAK8O,GAEnC,CAEA,EAAAyM,GACEvM,EAAkBhP,MAAK8O,GACvB9O,MAAK8O,EAAa,IACpB,EAGFhD,eAAeC,OAAO,iBAAkBqP,GAAQ,CAAEpP,QAAS,WChJ3D,MAAMqQ,GAAoB,CAAC/W,EAAMsM,KAC/B,MAAM0K,EAAShX,EAAKgX,QAAUhX,EAAKiF,MAMnC,MAAO,kDAJU,iBAAfjF,EAAKiF,MACD,+CACA,iDAAiDqH,EAAO0K,8BAKlD1K,EAAOtM,EAAKhG,4BAkC1BwM,eAAeC,OAAO,uBA1Bf,cAA0BqP,GAC/B,iBAAAlR,GACEmE,MAAMnE,oBACNlK,KAAKoB,iBAAiB,SAAUpB,MAAK2T,EACvC,CAEA,oBAAAlJ,GACE4D,MAAM5D,uBACNzK,KAAK0K,oBAAoB,SAAU1K,MAAK2T,EAC1C,CAEA,aAAIkI,GACF,MAAO,CACLhJ,KAAMwJ,GACNhG,OAAQgG,GAEZ,CAEA1I,GAAY,KACV,MAAM4I,EAAcvc,KAAK6B,eAAeiE,cAAc,uBAClDyW,IACFA,EAAYC,SAA0B,iBAAfxc,KAAKuK,SAKyB,CACzDyB,QAAS,WAKJ,MAAMyQ,WAAmBtO,YAC9B,iBAAAjE,GACElK,KAAK0c,YAAYtb,iBAAiB,QAASpB,MAC3CA,KAAK2c,WAAWvb,iBAAiB,QAASpB,MACtCA,KAAK0c,aACP1c,KAAK0c,WAAWF,UAAW,EAE/B,CAEA,WAAA/S,CAAYK,GACV,OAAQA,EAAMpI,QACZ,KAAK1B,KAAK0c,WACR1c,KAAK2c,UAAUpS,MAAQvK,KAAK0c,WAAWnS,MACvC,MACF,KAAKvK,KAAK2c,UACR3c,KAAK0c,WAAWnS,MAAQvK,KAAK2c,UAAUpS,MAG7C,CAEA,oBAAAE,GACEzK,KAAK0c,YAAYhS,oBAAoB,QAAS1K,MAC9CA,KAAK2c,WAAWjS,oBAAoB,QAAS1K,KAC/C,CAEA,cAAI0c,GACF,OAAO1c,KAAK8F,cAAc,sBAC5B,CAEA,aAAI6W,GACF,OAAO3c,KAAK8F,cAAc,qBAC5B,EAGFgG,eAAeC,OAAO,sBAAuB0Q,IC3E7C,MAAMG,WAAyBzO,YAC7B,iBAAAjE,GACElK,KAAKyP,SAASrO,iBAAiB,SAAUpB,MACzCA,KAAKmW,QACP,CAEA,oBAAA1L,GACEzK,KAAKyP,SAAS/E,oBAAoB,SAAU1K,KAC9C,CAEA,WAAAyJ,CAAYK,GACS,WAAfA,EAAMJ,MAAmB1J,KAAKmW,QACpC,CAEA,MAAAA,GACE,MAAMrJ,IAAW9M,KAAKyP,SAASoN,UAAY7c,KAAKyP,QAAQ+M,SAExDxc,KAAK8c,QAAUhQ,EACf9M,KAAK+c,OAAOze,QAAS0e,IAGU,mBAAlBA,EAAM9C,QACfpN,EAASkQ,EAAM7C,SAAW6C,EAAM9C,UAEhC8C,EAAMR,UAAY1P,GAGxB,CAEA,WAAI2C,GACF,OAAO9R,SAASuG,eAAelE,KAAKyL,aAAa,WACnD,CAEA,UAAIsR,GACF,OAAO/c,KAAKtB,iBAAiB,0BAC/B,EAGFoN,eAAeC,OAAO,4BAA6B6Q,IC9CnD,MAAM7f,GAASC,IAEf,MAAMigB,WAAmB9O,YAEvB,uBAAMjE,GAEW,OAAXnN,UACImgB,OAAO,aAAangB,SAIvBiD,KAAKmd,cAEVnd,KAAKod,UAAYA,EAAUpd,KAAKqd,WAAYrd,KAAKsd,kBACnD,CAEA,oBAAA7S,GACEzK,KAAKod,WAAWnO,SAClB,CAEA,oBAAIqO,GACF,MAAMC,EAAa,OAAOngB,KAAK4C,KAAKwd,WAC9B7a,EAAU,CAEd5F,OAAQA,GAAO0gB,MAAM,EAAG,GACxBC,UAAU,EACVC,UAAW7f,EAAU,WAAWkC,KAAKwd,aACrCI,cAAe,kBACfL,aACAM,WAA+B,SAAnB7d,KAAKwd,UACjBM,UAAWhgB,EAAU,qBACrB,aAAAigB,CAAcC,EAAgBC,EAAUC,GACtCA,EAAS3e,QACNgO,QAAQ,2BACPC,SAASxN,KAAKqd,WACpB,GAOF,OAJIE,IACF5a,EAAQwb,WAAa,KAGhBxb,CACT,CAEA,cAAI0a,GACF,OAAOrd,KAAK8F,cAAc,QAC5B,CAEA,aAAI0X,GACF,OAAOxd,KAAKyL,aAAa,eAAiB,MAC5C,EAGFK,eAAeC,OAAO,qBAAsBkR,ICvDrC,MAAMmB,WAAmBC,kBAC9B,iBAAAnU,GACElK,KAAKoB,iBAAiB,QAASpB,KACjC,CAEA,oBAAAyK,GACEzK,KAAK0K,oBAAoB,QAAS1K,KACpC,CAEA,WAAAyJ,CAAYpI,GACLrB,KAAKwc,UACRxc,KAAKoH,aAEP/F,EAAImF,gBACN,CAEA,UAAAY,GACEpH,KAAKkD,OAAS,IAAIX,EAAOvC,KAAKyL,aAAa,QAASzL,KAAKse,eACzDte,KAAKkD,OAAOD,MACd,CAEA,iBAAIqb,GAIF,OAHgBte,KAAKb,QAAQmf,cACzB7V,KAAKC,MAAM1I,KAAKb,QAAQmf,eACxB,CAAA,CAEN,CAEA,YAAI9B,GACF,OAAOxc,KAAKyD,UAAU8a,SAAS,WACjC,EAGFzS,eAAeC,OAAO,sBAAuBqS,GAAY,CAAEpS,QAAS,MC3BpE,IAAIwS,IAAiB,EAYrB,MAAMC,WAAmBtQ,YACvB,iBAAAjE,GACElK,KAAKoB,iBAAiB,SAAUpB,MAAK0e,GAAQ,EAC/C,CAEA,oBAAAjU,GACEzK,KAAK0K,oBAAoB,SAAU1K,MAAK0e,GAAQ,EAClD,CAEAA,GAAU5U,IACR,GAAI0U,KAAmBG,KAAe,OAEtC7U,EAAMtD,iBACNsD,EAAMmQ,kBAEN,MAAMH,EAAOhQ,EAAMpI,OACnBkd,KAAepa,KAAMqa,IACfA,IACF9W,IACA+R,EAAKgF,oBAMb,SAASH,KACP,OAAOhhB,SAASe,iBAAiB,gCAAgCqF,OAAS,CAC5E,CAEAU,eAAema,KACb,MAAMC,QAAkBnS,EAAkB5O,EAAU,qBAAsB,CACxEoE,MAAOpE,EAAU,WACjBoO,SAAUpO,EAAU,MACpBqO,aAAcrO,EAAU,YAM1B,OAJI+gB,IACFL,IAAiB,EACjBthB,OAAO6hB,oBAAiB,GAEnBF,CACT,CAEAlhB,SAASyD,iBAAiB,qBAAuB0I,KAC3C0U,IAAmBG,OAEvB7U,EAAMtD,iBAENoY,KAAepa,KAAMqa,IACfA,GAAWha,EAAMsI,MAAMrD,EAAMpJ,OAAOgC,UAI5C/E,SAASyD,iBAAiB,aAAc,KACtCod,IAAiB,IAGnB1S,eAAeC,OAAO,sBAAuB0S,IC5E7C,MAAMO,GAAoB,mBAG1B,SAASC,GAAaC,GACpB,MAAgC,QAAzBA,EAAOC,aAChB,CAuBA,SAASC,GAAeF,EAAQ5Z,EAAM+Z,GACpC,MAAM9a,EAVR,SAAwB8a,GACtB,MAAO,CACL,eAAgB,kCAChBlI,OAAQkI,EACR,mBAAoB,iBACpB,eAAgBC,KAEpB,CAGkBC,CAAeF,GACzB1c,EAAU,CAAEuc,SAAQ3a,WAM1B,OAJIe,IAAS2Z,GAAaC,KACxBvc,EAAQvC,KAAOqI,KAAKwL,UAAU3O,IAGzB3C,CACT,CAEO,SAAS2c,KAEd,OADgB3hB,SAASmI,cAAc,2BACxB0Z,WAAW9f,QAAQiH,WACpC,CAEO,SAAS/B,GAAIlC,EAAK8P,GACvB,OAAOkC,GAAK,MAAOhS,EAAK8P,EAC1B,CAEO,SAASiN,GAAM/c,EAAK4C,EAAM+Z,GAC/B,OAAO3K,GAAK,QAAShS,EAAK4C,EAAM+Z,EAClC,CAEO,SAASK,GAAKhd,EAAK4C,EAAM+Z,EAASL,IACvC,OAAOtK,GAAK,OAAQhS,EAAK4C,EAAM+Z,EACjC,CAEe5a,eAAeiQ,GAC5BwK,EACAjW,EACA3D,EACA+Z,EAASL,IAET,MAAMta,QAAiBJ,MAvDzB,SAAoB2E,EAAM3D,EAAM4Z,GAC9B,MAAMxc,EAAM,IAAIsU,IAAI9Z,OAAO+Z,SAAShN,OAAShB,GAQ7C,OANI3D,GAAQ2Z,GAAaC,IACvB,IAAIS,gBAAgBra,GAAMhH,QAAQ,CAACiM,EAAOzN,KACxC4F,EAAIwU,aAAapD,IAAIhX,EAAKyN,KAIvB7H,EAAIkd,UACb,CA8CIC,CAAW5W,EAAM3D,EAAM4Z,GACvBE,GAAeF,EAAQ5Z,EAAM+Z,IAEzBS,EAAcpb,EAASH,QAAQK,IAAI,gBACnCmb,EAASD,GAAane,SAASqd,IAC/BgB,EAAgBF,GAAane,SAnEH,8BAqEhC,IAAIse,EAAe,KAWnB,GAVIF,EACFE,QAAqBvb,EAAS4S,OACrB0I,IACTC,QAAqBvb,EAASpF,OAET,oBAAVuF,OACTA,MAAMC,oBAAoBmb,IAI1Bvb,EAASK,GACX,MAAO,CAAEO,KAAM2a,EAAchb,OAAQP,EAASO,QAOhD,GAAwB,MAApBP,EAASO,QAAkBgb,GAAcC,aAE3C,OADArb,MAAMsI,MAAM8S,EAAaC,cAClB,IAAInV,QAAQ,QAGrB,MAAMkV,GAAgB,IAAI7I,MAAM,2CAClC,CC1FA,MAAM+I,WAAoBhS,YACxB,QAAAiS,CAASC,GACP,MAAO,CACL9X,GAAI,IAAI8X,IACR/gB,KAAM,IAAI+gB,IAEd,CAEA,iBAAIC,GACF,OAAOtgB,KAAK8F,cAAc,8BAC5B,EA2DFgG,eAAeC,OAAO,4BAxDtB,cAA6BoU,GAC3BI,QAAUviB,EAEV,iBAAAkM,GACElK,KAAK0W,KAAO1W,KAAKyL,aAAa,OAChC,CAEA,OAAM+U,GACJ,IACE,MAGM7d,SAHeiC,GAAI+D,EAAO8X,qBAAsB,CACpDC,QAAS1gB,MAAKugB,KAEOjb,KAAKqb,YACzB3N,OAAQ4N,GAAeA,EAAWtb,MAAMub,QACxCnO,IAAKkO,GAAe5gB,KAAKogB,SAASQ,EAAWtb,KAAKub,SAC/CpF,EACJ9Y,EAAQoB,OAAS,EAAIjG,EAAU,QAAUA,EAAU,oBAErDkC,KAAKsgB,cAAc9E,WAAW7Y,EAAS8Y,GACvCzb,KAAKsgB,cAAcnG,QACrB,CAAE,MAAO7T,GACPsB,EAAMtB,EAAMlC,SAAWkC,EAAO,SAC9B7I,QAAQ6I,MAAMA,EAChB,CACF,CAEA,EAAAwa,GAEEtd,sBAAsB,KACpBxD,KAAKsgB,cAAcpG,UACnBla,KAAKsgB,cAAc9E,WAAW,GAAI1d,EAAU,yBAEhD,CAEA,QAAI4Y,CAAK6J,GACPvgB,MAAKugB,EAAUA,EACfA,EAASvgB,MAAKwgB,IAAiBxgB,MAAK8gB,GACtC,IAoBFhV,eAAeC,OAAO,gCAjBtB,cAAiCoU,GAC/B,iBAAAjW,GAEE1G,sBAAsB,KACpB,MAAMud,EAAQpjB,SAASuG,eAAe,0BAChCyM,EAAWoQ,EAAMC,iBAAiBtiB,iBAAiB,SAAW,GACpE,GAAIiS,EAAS5M,OAAS,EAAG,CACvB,MAAMpB,EAAUpB,MAAMC,KAAKmP,GAAU+B,IAAKnT,GACjCS,KAAKogB,SAAS7gB,EAAQgJ,KAE/BvI,KAAKsgB,cAAc9E,WAAW7Y,EAAS7E,EAAU,QACnD,GAEJ,ICvEK,MAAMmjB,WAA6B9S,YACxC+S,GAEA,iBAAAhX,GACElK,MAAKkhB,EAAyBlhB,KAAKmhB,eAAe1V,aAAa,WAC/DzL,KAAKohB,cAAchgB,iBAAiB,QAASpB,MAC7CA,KAAK6P,SAASzO,iBAAiB,UAAWpB,MAC1CA,KAAK6P,SAASzO,iBAAiB,UAAWpB,KAC5C,CAEA,oBAAAyK,GACEzK,KAAKohB,cAAc1W,oBAAoB,QAAS1K,MAChDA,KAAK6P,SAASnF,oBAAoB,UAAW1K,MAC7CA,KAAK6P,SAASnF,oBAAoB,UAAW1K,KAC/C,CAEA,WAAAyJ,CAAYK,GACV,OAAQA,EAAMJ,MACZ,IAAK,QACH1J,KAAKohB,cAActP,SAAU,EAC7B,MACF,IAAK,UACH9R,KAAKmhB,eAAenU,aAAa,UAAW,WAC5C,MACF,IAAK,UACHhN,KAAKmhB,eAAenU,aAAa,UAAWhN,MAAKkhB,GAGvD,CAEA,iBAAIE,GACF,OAAOphB,KAAK8F,cAAc,2BAC5B,CAEA,YAAI+J,GACF,OAAO7P,KAAK8F,cAAc,cAC5B,CAEA,kBAAIqb,GACF,OAAOnhB,KAAK8F,cAAc,4BAC5B,ECxCK,SAASub,GAAuB/b,GACrC3H,SAASgD,cACP,IAAIC,YAAY,qBAAsB,CACpCF,OAAQ,CAAE4gB,QAAShc,EAAKic,wBAG9B,CDqCAzV,eAAeC,OAAO,iCAAkCkV,IEpCjD,MAAMO,WAA4BrT,YACvC,iBAAAjE,GACElK,KAAKwR,QAAQpQ,iBAAiB,QAASpB,KACzC,CAEA,oBAAAyK,GACEzK,KAAKwR,QAAQ9G,oBAAoB,QAAS1K,KAC5C,CAEA,iBAAMyJ,GAEJ,SADwBiD,EAAkB1M,KAAKoE,SAE7C,IACE,MAAMM,QAAiBgQ,GAAK,SAAU1U,KAAK0C,KAC3C1C,MAAKyhB,EAAe/c,EAASY,KAC/B,CAAE,MAAOgB,GACPsB,EAAMtB,EAAMlC,SAAWkC,EAAO,SAC9B7I,QAAQ6I,MAAMA,EAChB,CAEJ,CAEA,EAAAmb,CAAenc,GACb,MAAMoc,EAAgB1hB,KAAKuN,QAAQ,0BACnCmU,EAActgB,iBAAiB,gBAAiB,KAC1CsgB,EAAcC,OAChBta,EAAUqa,EAAcE,WAE1BF,EAAcnhB,WAEhBmhB,EAAcje,UAAUC,IAAI,WAC5BkE,EAAMtC,EAAKlB,SACPkB,EAAKuc,2BACPR,GAAuB/b,GAEzBuG,GACF,CAEA,OAAInJ,GACF,OAAO1C,KAAKyL,aAAa,OAC3B,CAEA,WAAIrH,GACF,OAAOpE,KAAKyL,aAAa,UAC3B,CAEA,UAAI+F,GACF,OAAOxR,KAAK8F,cAAc,SAC5B,EAGFgG,eAAeC,OAAO,gCAAiCyV,ICnDhD,MAAMM,WAAsB3T,YACjC2L,GAAQ,KACRiI,GAAU,KACVC,GAAgB,KAEhB,iBAAA9X,GAEMlK,KAAKyD,UAAU8a,SAAS,6BAK5Bve,KAAKoB,iBAAiB,QAASpB,MAE/BA,KAAKoB,iBAAiB,+BAAgCpB,MAEtDA,MAAK8Z,EAAQ9Z,KAAK8Z,KACd9Z,MAAK8Z,IACP9Z,MAAK8Z,EAAM1Y,iBAAiB,SAAUpB,KAAK2T,UAC3C3T,MAAK8Z,EAAM1Y,iBAAiB,qBAAsBpB,MAClDA,MAAK8Z,EAAM1Y,iBAAiB,mBAAoBpB,OAGlDA,MAAK+hB,EAAU/hB,KAAK+hB,OACpB/hB,MAAK+hB,GAAS3gB,iBAAiB,WAAYpB,MAAKiiB,GAEhDjiB,MAAKgiB,EAAgBhiB,KAAKgiB,aAC1BhiB,MAAKgiB,GAAe5gB,iBAAiB,QAASpB,MAAKkiB,GAG/CliB,KAAKqU,aAAa,aACpBrU,KAAKmiB,eACLniB,KAAKoiB,eAAe5X,UAAUhG,KAAK,KACjCxE,KAAKqiB,wBAEPriB,KAAKua,gBAAgB,YAEzB,CAEA,oBAAA9P,GACEzK,KAAK0K,oBAAoB,QAAS1K,MAClCA,KAAK0K,oBAAoB,+BAAgC1K,MACrDA,MAAK8Z,IACP9Z,MAAK8Z,EAAMpP,oBAAoB,SAAU1K,KAAK2T,UAC9C3T,MAAK8Z,EAAMpP,oBAAoB,qBAAsB1K,MACrDA,MAAK8Z,EAAMpP,oBAAoB,mBAAoB1K,MACnDA,MAAK8Z,EAAQ,MAEf9Z,MAAK+hB,GAASrX,oBAAoB,WAAY1K,MAAKiiB,GACnDjiB,MAAK+hB,EAAU,KACf/hB,MAAKgiB,GAAetX,oBAAoB,QAAS1K,MAAKkiB,GACtDliB,MAAKgiB,EAAgB,IACvB,CAEA,WAAAvY,CAAYK,GACV,OAAQA,EAAMJ,MACZ,IAAK,QACmBI,EAAMpI,OAAO6L,QAAQ,4BACrBvN,MACpBA,KAAKsiB,iBAEP,MACF,IAAK,qBACHtiB,KAAKuiB,WACL,MACF,IAAK,mBACHviB,KAAKwiB,cAAc1Y,EAAMpJ,OAAO+hB,SAChC,MACF,IAAK,+BACEziB,KAAK0iB,YAAc5Y,EAAMpI,QAAU1B,KAAK2iB,YAC3C3iB,KAAK4iB,SAAS9Y,EAAMpJ,OAAOwB,OAInC,CAEAyR,SAAY7J,IACV,MAAMpI,EAASoI,EAAMpI,OAIrB,IAAIA,EAAO+B,UAAU8a,SAAS,mBAK9B,OAFAve,KAAKwN,SAAS9L,GACdoI,EAAMmQ,mBACC,GAUT,kBAAMkI,GAEAxkB,SAASmI,cAAc,0BACnB9F,KAAK6iB,4BAGP7iB,KAAK8iB,SACX9iB,KAAKsgB,eAAc,EACrB,CAEA,mBAAA+B,GACEriB,KAAKoiB,eAAezX,YAAY,CAC9BvG,QAAS,uBACTkD,WAAYtH,KAAK4hB,WAErB,CAEA,cAAAU,GACEtiB,KAAKsgB,gBACLtgB,KAAKqiB,qBACP,CAUA,aAAAG,CAAcC,GACRA,EACFziB,KAAKoiB,eAAe5X,UAAUhG,KAAK,KACjCxE,KAAKqiB,wBAGPriB,KAAK+iB,cAActf,UAAUlD,OAAO,SAExC,CAKA,eAAAyiB,GAGElY,WAAW,KACT9K,KAAKijB,eAAe,CAClBC,SAAU,YAEX,GACL,CAMA,aAAA5C,CAAc6C,GAAS,GACrBxlB,SACGe,iBAAiB,mCACjBJ,QAASO,IACRA,EAAG4E,UAAUlD,OAAO,cAExBrD,OAAOsG,sBAAsB,KAC3BxD,KAAKyD,UAAUC,IAAI,cAEjByf,GAAQnjB,KAAKgjB,iBACnB,CAOA,mBAAAH,GACE,OAAO,IAAI9X,QAAQ,CAACC,EAASoY,KAC3B,MAAMC,EAAO1lB,SAASmI,cAAc,mBAC9B6B,EAAQ3H,KAAKuN,QAAQ,gBACvB8V,GAAQ1b,GACV0b,EAAK9f,KAAKoE,EAAM8D,aAAa,SAC7BT,KAEAoY,EAAO,IAAIhM,MAAM,qBAGvB,CAKA,QAAAmL,GACEviB,KAAKsjB,OAAQ,EACbpmB,OAAO6hB,eAAiB,KACxB/e,KAAK+iB,cAActf,UAAUC,IAAI,UAE7B1D,KAAK0iB,YACP1iB,KAAKI,KAAK1B,iBAAiB,sBAAsBJ,QAASO,IACxDA,EAAG4E,UAAUlD,OAAO,QAAS,qBAC7B1B,EAAGH,iBAAiB,eAAeJ,QAASiI,GAAMA,EAAEhG,WAG1D,CAMA,QAAAiN,CAAS+V,GACHvjB,KAAK0iB,aACP1iB,KAAKsjB,OAAQ,EAERpmB,OAAO6hB,iBACV7hB,OAAO6hB,eAAkBjV,GAAUA,EAAMtD,kBAG3C+c,GAAQhW,QAAQ,uBAAuB9J,UAAUC,IAAI,SAEzD,CAMA,QAAAkf,CAAS1gB,GACP,MAAMshB,EAAQxjB,KAAK8F,cAAc,uCAE7B0d,IACFA,EAAM7c,YAAczE,EAExB,CAMA,YAAMiU,GACAnW,KAAKyjB,gBACDzjB,KAAK8iB,eAEL9iB,KAAK0jB,UAEf,CAMA,QAAAA,GACE,GAAI1jB,KAAKyjB,WAAazjB,KAAK2jB,SAAW3jB,KAAK2hB,MACzC,OAAO5W,QAAQC,QAAQ,iCAGzB,MAAM/K,EAAU,IAAI9C,QAAQwI,QAAQ,SAGpC,OAFA1F,EAAQC,KAAKF,KAAKgiB,cAClBhiB,KAAK4jB,YAAYngB,WAAWC,IAAI,UACzBgc,GAAK/W,EAAOkb,4BAA4B7jB,KAAK4hB,YACjDpd,KAAME,IACL,MAAMY,EAAOZ,EAASY,KAMtB,GAJAtF,KAAKyjB,WAAY,EACjBzjB,KAAKgiB,cAAchV,aAAa,QAAS1H,EAAKpD,OAG1CoD,EAAKwe,iBAAiB/f,OAAQ,CAChC,MAAMggB,EAAWze,EAAKwe,iBACnBpR,IAAKnK,GAAO,YAAYA,KACxBwK,KAAK,MACR/S,KAAKtB,iBAAiBqlB,GAAUzlB,QAAS0lB,IACvCA,EAAcP,WAAY,EAC1BO,EAAchC,cAAchV,aAAa,QAAS1H,EAAKpD,QAE3D,IAEDiD,MAAOmB,IACNsB,EAAMtB,EAAMlC,QAAS,SACrB3G,QAAQ6I,MAAMA,KAEf2d,QAAQ,KACPjkB,KAAK4jB,YAAYngB,WAAWlD,OAAO,UACnCN,EAAQK,QAEd,CAMA,MAAAwiB,GACE,GAAI9iB,KAAKkkB,WAAalkB,KAAK2jB,QACzB,OAAO5Y,QAAQC,QAAQ,gCAGzB,GAAIhL,KAAK2jB,SAAW3jB,KAAKmkB,oBACvB,OAAOnkB,KAAKmkB,oBAAoBrB,SAC3B,CACL,MAAM7iB,EAAU,IAAI9C,QAAQwI,QAAQ,SAIpC,OAHA1F,EAAQC,KAAKF,KAAKgiB,cAClBhiB,KAAK4jB,YAAYngB,UAAUC,IAAI,UAExB,IAAIqH,QAAQ,CAACC,EAASoY,KAC3B1D,GAAK/W,EAAOyb,0BAA0BpkB,KAAK4hB,YACxCpd,KAAME,IACL,MAAMY,EAAOZ,EAASY,KAGtB,GAAIA,EAAK+e,iBAAiBtgB,OAAQ,CAChC,MAAMggB,EAAWze,EAAK+e,iBACnB3R,IAAKnK,GAAO,YAAYA,KACxBwK,KAAK,MACRpV,SAASe,iBAAiBqlB,GAAUzlB,QAASuD,IAC3CA,EAAc4hB,WAAY,EAC1B5hB,EAAcmgB,cAAchV,aAAa,QAAS1H,EAAKpD,QAE3D,CAEAlC,KAAKyjB,WAAY,EACjBzjB,KAAKgiB,cAAchV,aAAa,QAAS1H,EAAKpD,OAE9C8I,MAED7F,MAAOmB,IACNsB,EAAMtB,EAAMlC,QAAS,SACrB3G,QAAQ6I,MAAMA,GACd8c,EAAO9c,KAER2d,QAAQ,KACPjkB,KAAK4jB,YAAYngB,WAAWlD,OAAO,UACnCN,EAAQK,UAGhB,CACF,CAOA,WAAAgkB,CAAYpiB,GACVlC,KAAK4iB,SAAS1gB,GACdlC,KAAKW,cACH,IAAIC,YAAY,+BAAgC,CAC9CC,SAAS,EACTH,OAAQ,CAAEwB,WAGhB,CAMA,aAAIqiB,CAAUC,GACRA,EACFxkB,KAAKyD,UAAUlD,OAAO,kBAEtBP,KAAKyD,UAAUC,IAAI,iBAEvB,CAMA,aAAI6gB,GACF,OAAQvkB,KAAKyD,UAAU8a,SAAS,SAClC,CAKA,WAAIoF,GACF,OAAwC,OAAjC3jB,KAAKyL,aAAa,UAC3B,CAKA,SAAIkW,GACF,OAAsC,OAA/B3hB,KAAKyL,aAAa,QAC3B,CAKA,aAAIgY,CAAUlZ,GACZvK,KAAKyD,UAAU0S,OAAO,SAAU5L,GAChCvK,KAAKyD,UAAU0S,OAAO,YAAa5L,GACnCvK,KAAK4jB,aACF5jB,KAAK4jB,WAAWnjB,KAAO8J,EAAQ,eAAiB,eACrD,CAKA,aAAIkZ,GACF,OAAOzjB,KAAKyD,UAAU8a,SAAS,SACjC,CAKA,YAAI2F,GACF,OAAQlkB,KAAKyjB,SACf,CAOA,SAAIH,CAAM/Y,GACRvK,KAAKyD,UAAU0S,OAAO,QAAS5L,EACjC,CAOA,SAAI+Y,GACF,OAAOtjB,KAAKyD,UAAU8a,SAAS,QACjC,CAOA,UAAIwD,GACF,OAAO/hB,KAAK8F,cAAc,kBAC5B,CAUA,QAAI1F,GACF,OAAOJ,KAAK8F,cAAc9F,KAAKykB,aACjC,CAEA,gBAAIA,GACF,MAAO,IAAIzkB,KAAKuI,oBAClB,CAUA,UAAImc,GACF,OAAO1kB,KAAK8F,cAAc,IAAI9F,KAAKuI,uBACrC,CAOA,gBAAIyZ,GACF,OAAOhiB,KAAK8F,cAAc,kBAC5B,CAOA,cAAI8d,GACF,OAAO5jB,KAAKgiB,cAAclc,cAAc,eAC1C,CAOA,iBAAIid,GACF,OAAO/iB,KAAKI,KAAK0F,cAAc,kBACjC,CAOA,aAAI8b,GACF,OAAO5hB,KAAKb,QAAQyiB,SACtB,CAOA,eAAI+C,GACF,OAAO3kB,KAAKb,QAAQwlB,WACtB,CAOA,cAAIjC,GACF,QAAS1iB,KAAKI,MAAM0F,cAAc,8BACpC,CAOA,eAAI8e,GACF,QAAS5kB,KAAK8F,cAAc,mBAC9B,CAOA,cAAI6c,GACF,OAAO3iB,KAAK8F,cAAc,yBAC5B,CAUA,QAAIgU,GACF,OAAO9Z,KAAK8F,cAAc,IAAI9F,KAAKuI,yBACrC,CAOA,uBAAI4b,GACF,OAAOnkB,KAAK6B,eAAe0L,QAAQ,yBACrC,CAEA,iBAAI6U,GACF,OAAOzkB,SAASuG,eAAe,yBACjC,CAEA+d,GAAoB,KAClBjiB,KAAKmW,UAGP+L,GAAkB7gB,IACMA,EAAIK,OAAO6L,QAAQ,4BACnBvN,MACpBA,KAAKmW,UAKXrK,eAAeC,OAAO,yBAA0B+V,IC3jBhD,MAAM+C,GAAa,CAAC3L,EAAM5Z,IAAS,8CAE7B4Z,UAAa5Z,uBAIZ,MAAMwlB,WAAsB1J,GACjC,aAAIS,GACF,MAAO,CACLhJ,KAAM,CAACvN,EAAMsM,IAAWiT,GAAWvf,EAAK4T,KAAMtH,EAAOtM,EAAKhG,OAC1D+W,OAAQ,CAAC/Q,EAAMsM,KACb,MAAMmT,EAAczf,EAAK0f,KACrB,2CAA2CpT,EAAOtM,EAAK0f,cACvD,GACJ,MAAO,8DAEDH,GAAWvf,EAAK4T,KAAMtH,EAAOtM,EAAKhG,uBAClCylB,iCAKZ,EAGFjZ,eAAeC,OAAO,yBAA0B+Y,GAAe,CAC7D9Y,QAAS,WC1BX,MAAMiZ,WAAuB9W,YAC3B+W,IAAW,EACXC,GAAc,KAEd,iBAAAjb,GACElK,KAAKgiB,cAAc5gB,iBAAiB,QAASpB,MAAKkiB,GAClDliB,KAAKolB,gBAAgBhkB,iBAAiB,QAASpB,MAAKqlB,GACpDnoB,OAAOkE,iBAAiB,UAAWpB,MAAKslB,GACxC3nB,SAASyC,KAAKgB,iBAAiB,QAASpB,MAAKulB,GACzCroB,OAAO+Z,SAASoJ,MAClBrgB,KAAKwlB,mBAAmBtoB,OAAO+Z,SAASoJ,MAE1CrgB,KAAKgD,QACP,CAEA,oBAAAyH,GACEzK,KAAKgiB,cAActX,oBAAoB,QAAS1K,MAAKkiB,GACrDliB,KAAKolB,gBAAgB1a,oBAAoB,QAAS1K,MAAKqlB,GACvDnoB,OAAOwN,oBAAoB,UAAW1K,MAAKslB,GAC3C3nB,SAASyC,KAAKsK,oBAAoB,QAAS1K,MAAKulB,EAClD,CAEArD,GAAkB7gB,IAChBA,EAAImF,iBACJxG,KAAKmW,UAGPkP,GAAmB,KACjBrlB,KAAKylB,uBAGPH,GAAoBxb,IAClB,GAAIA,EAAMC,SAAW/J,KAAKoiB,eAAepY,cAAe,OAExD,MAAM1E,EAAOwE,EAAMxE,KACnB,GAAqB,8BAAjBA,GAAMlB,QAAyC,CACjD,MAAM7E,EAAU5B,SAASuG,eAAe,WAAWoB,EAAKgC,cACxDtH,KAAKuD,OACLhE,GAAS4iB,cACX,GAGFoD,GAAgBlkB,IACTA,EAAIK,OAAO6L,QAAQ,4BACtBvN,KAAKtB,iBAAiB,0BAA0BJ,QAASilB,IACvDA,EAAO9f,UAAUlD,OAAO,cAE1BP,KAAKoiB,eAAezX,YAAY,CAAEvG,QAAS,2BAI/C,mBAAAqhB,GACEzlB,KAAKtB,iBACH,sDACAJ,QAASilB,GAAWA,EAAOG,WAC/B,CAEA,MAAAvN,GACEnW,MAAKklB,EAAWllB,KAAKwM,OAASxM,KAAKuD,MACrC,CAEA,IAAAA,GACE5F,SAASyC,KAAKqD,UAAUC,IAAI,2BAC5B1D,MAAKklB,GAAW,EAChBllB,KAAKgiB,aAAazU,QAAQ,cAAc7N,QAAUvC,QAAQkL,EAAE,iBAC5DrI,KAAKgiB,aACFlc,cAAc,gBACdkH,aAAa,OAAQ,eACxBhN,KAAKgD,QACP,CAEA,IAAAwJ,GACE7O,SAASyC,KAAKqD,UAAUlD,OAAO,2BAC/B5C,SAASyC,KAAKoF,MAAMkgB,eAAe,2BACnC1lB,MAAKklB,GAAW,EAChBllB,KAAKgiB,aAAazU,QAAQ,cAAc7N,QAAUvC,QAAQkL,EAAE,iBAC5DrI,KAAKgiB,aACFlc,cAAc,gBACdkH,aAAa,OAAQ,YAC1B,CAEA,MAAAhK,CAAOJ,QACS5E,IAAV4E,IACFA,EAAQ5C,KAAK2lB,iBAGX/iB,IACFjF,SAASyC,KAAKoF,MAAMogB,YAAY,0BAA2B,GAAGhjB,OAC9DjF,SAASkoB,OAAS,iCAAiCjjB,2BAEvD,CAIA,kBAAA4iB,CAAmB3E,GACjB,MAAMhiB,EAAKlB,SAASmI,cAAc+a,GAE9BhiB,aAAcijB,IAChBjjB,EAAGsjB,gBAAkBtjB,EAAGwjB,qBAE5B,CAEA,kBAAI+C,GACF,OAAOplB,KAAK8F,cAAc,gCAC5B,CAEA,gBAAIkc,GACF,OAAOrkB,SAASmI,cAAc,yBAChC,CAEA,iBAAIsc,GACF,OAAOzkB,SAASuG,eAAe,yBACjC,CAEA,cAAIihB,GAIF,OAHKnlB,MAAKmlB,IACRnlB,MAAKmlB,EAAcnlB,KAAKuN,QAAQ,gBAE3BvN,MAAKmlB,CACd,CAEA,mBAAIQ,GACF,OAAOhoB,SAASkoB,OACbvoB,MAAM,MACNwoB,KAAMC,GAAQA,EAAI/N,WAAW,oCAC5B1a,MAAM,KAAK,EACjB,CAEA,aAAI2N,CAAUC,GACZlL,KAAKmlB,WAAW3f,MAAM2F,mBAAqBD,EAAU,OAAS,KAC9DlL,KAAKmlB,WAAW3f,MAAM4F,cAAgBF,EAAU,OAAS,IAC3D,EAGFY,eAAeC,OAAO,0BAA2BkZ,ICtIjD,MAAMe,WAA6B7X,YACjC8X,IAAY,EACZC,GAAkB,KAClB9D,GAAiB,KACjB5S,GANgB,IAOhBqB,GANgB,IAQhB,iBAAA3G,GACElK,KAAKoB,iBAAiB,YAAapB,MACnC9C,OAAOkE,iBAAiB,YAAapB,MACrC9C,OAAOkE,iBAAiB,UAAWpB,KACrC,CAEA,oBAAAyK,GACEzK,KAAK0K,oBAAoB,YAAa1K,MACtC9C,OAAOwN,oBAAoB,YAAa1K,MACxC9C,OAAOwN,oBAAoB,UAAW1K,KACxC,CAEA,WAAAyJ,CAAYK,GACV,OAAQA,EAAMJ,MACZ,IAAK,YACHI,EAAMmQ,kBACNja,KAAKmmB,cACL,MACF,IAAK,UACHnmB,KAAKomB,YACL,MACF,IAAK,YACCpmB,MAAKimB,GACPjmB,KAAKqmB,OAAOvc,EAAMwc,OAI1B,CAEA,WAAAH,GACEnmB,MAAKimB,GAAY,EAIjB,MAAM1W,EAASgX,iBAAiBvmB,KAAKkmB,gBACrClmB,MAAKwP,EAAYgX,WAAWjX,EAAOC,WA5CrB,IA6CdxP,MAAK6Q,EACiB,SAApBtB,EAAOsB,SA7CK,IA6C6B2V,WAAWjX,EAAOsB,UAC7D7Q,KAAKkmB,eAAejb,WAAY,EAChCjL,KAAKoiB,cAAcnX,WAAY,EAC/BjL,KAAKyD,UAAUC,IAAI,aACrB,CAEA,SAAA0iB,GACEpmB,MAAKimB,GAAY,EACjBjmB,KAAKkmB,eAAejb,WAAY,EAChCjL,KAAKoiB,cAAcnX,WAAY,EAC/BjL,KAAKyD,UAAUlD,OAAO,aACxB,CAEA,MAAA8lB,CAAOC,GACL,MAAMG,EAAqBvpB,OAAO6J,WAAauf,EACzC1jB,EAAQoO,KAAK0V,IACjB1V,KAAKC,IAAIwV,EAAoBzmB,MAAKwP,GAClCxP,MAAK6Q,GAEP7Q,KAAKkmB,eAAeljB,OAAOJ,EAC7B,CAEA,kBAAIsjB,GAIF,OAHKlmB,MAAKkmB,IACRlmB,MAAKkmB,EAAkBvoB,SAASmI,cAAc,4BAEzC9F,MAAKkmB,CACd,CAEA,iBAAI9D,GAIF,OAHKpiB,MAAKoiB,IACRpiB,MAAKoiB,EAAiBzkB,SAASuG,eAAe,2BAEzClE,MAAKoiB,CACd,EAGFtW,eAAeC,OAAO,iCAAkCia,ICnFxD,MAAMW,WAAmBxY,YACvB,iBAAAjE,GACElK,KAAK4mB,WAAa5mB,KAAK8F,cAAc,qBACrC9F,KAAK6mB,SAAW7mB,KAAK8F,cAAc,cACnC9F,KAAK8mB,SAAW9mB,KAAK8F,cAAc,cACnC9F,KAAK+mB,YAAc/mB,KAAK4mB,YAAYznB,QAAQ4nB,YAC5C/mB,KAAKqN,UAAYrN,KAAK8F,cAAc,IAAI9F,KAAK+mB,eAC7C/mB,KAAK4mB,YAAYxlB,iBAAiB,QAASpB,KAC7C,CAEA,oBAAAyK,GACEzK,KAAK4mB,YAAYlc,oBAAoB,QAAS1K,KAChD,CAEA,WAAAyJ,CAAYK,GACS,UAAfA,EAAMJ,MAAkB1J,KAAKgnB,aACjCld,EAAMmQ,iBACR,CAEA,UAAA+M,GACEhnB,KAAKqN,UAAU9C,MAAQ,GACvBvK,KAAK6mB,SAASpnB,UAAY,GAC1BO,KAAK8mB,SAASrnB,UAAY,GAC1BO,KAAK4mB,YAAYnjB,UAAUC,IAAI,UAC/B1D,KAAKuN,QAAQ,0BAA0BC,SAASxN,KAAKqN,UACvD,EAGFvB,eAAeC,OAAO,sBAAuB4a,IC1B7C,MAAMM,WAAmB9Y,YACvB+Y,GACAC,GAAe,KACfC,IAAe,KAEf,iBAAAld,GACMlK,KAAKzB,QACPzB,IAAIkD,KAAKzB,OAAQyB,MAAKqnB,IAExBrnB,MAAKmnB,EAAennB,KAAKmnB,YACzBnnB,MAAKmnB,EAAa/lB,iBAAiB,QAASpB,MAAKsnB,IACjDtnB,MAAKmnB,EAAa/lB,iBAAiB,QAASpB,MAAKunB,IAEjDvnB,MAAKonB,GAAepnB,KAAKonB,YACzBpnB,MAAKonB,GAAahmB,iBAAiB,QAASpB,MAAKwnB,IAEjD1qB,IAAI,MAAO,cAAekD,MAAKynB,GACjC,CAEA,oBAAAhd,GACEe,aAAaxL,MAAKknB,GAClBlnB,MAAKmnB,GAAczc,oBAAoB,QAAS1K,MAAKsnB,IACrDtnB,MAAKmnB,GAAczc,oBAAoB,QAAS1K,MAAKunB,IACrDvnB,MAAKmnB,EAAe,KACpBnnB,MAAKonB,IAAc1c,oBAAoB,QAAS1K,MAAKwnB,IACrDxnB,MAAKonB,GAAe,KAChBpnB,KAAKzB,QACPzB,IAAI0B,OAAOwB,KAAKzB,QAElBzB,IAAI0B,OAAO,MAAO,cACpB,CAEA,MAAAwU,CAAOb,GACQ,KAATA,IACFnS,KAAKonB,YAAY5hB,MAAMkiB,WAAa,UAGtC,MAAMC,EAAe,GACfC,EAAc,IAAIhQ,IAClBiQ,EAAY1V,EAAKgN,cAGvBnf,KAAK8V,MAAMxX,QAASuU,IAClB,MAAMpS,EAAOoS,EAAKpH,aAAazL,KAAK8nB,gBAAgB3I,cAEpD,IAAgC,IAA5B1e,EAAKsnB,QAAQF,GAAmB,CAClCF,EAAa3oB,KAAK6T,GAClB+U,EAAYlkB,IAAImP,GAEhB,IAAImV,EAAWnV,EAAKhR,eAAe0L,QAAQvN,KAAKioB,eAChD,KAAOD,GACLJ,EAAYlkB,IAAIskB,GAChBA,EAAWA,EAASnmB,eAAe0L,QAAQvN,KAAKioB,cAEpD,IAIFjoB,KAAK8V,MAAMxX,QAASuU,IAClBA,EAAKpP,UAAU0S,OAAO,UAAWyR,EAAY9P,IAAIjF,MAIvB,IAAxB8U,EAAa5jB,QACf4jB,EAAa,GAAG1E,eAAe,CAAEC,SAAU,SAAUgF,MAAO,WAEhE,CAEA,KAAAhM,GACElc,KAAKmnB,YAAY5c,MAAQ,GACzBvK,KAAKonB,YAAY5hB,MAAMkiB,WAAa,SACpC1nB,KAAK8V,MAAMxX,QAASuU,GAASA,EAAKpP,UAAUlD,OAAO,UACrD,CAEA,iBAAIunB,GACF,OAAO9nB,KAAKyL,aAAa,mBAAqB,MAChD,CAEA,eAAI2b,GACF,OAAOpnB,KAAK8F,cAAc,wBAC5B,CAEA,eAAIqhB,GACF,OAAOnnB,KAAK8F,cAAc,qBAC5B,CAEA,SAAIgQ,GACF,OAAOnY,SAASe,iBAAiBsB,KAAKioB,cACxC,CAEA,iBAAIA,GACF,OAAOjoB,KAAKyL,aAAa,iBAC3B,CAEA,gBAAI0c,GACF,OAAOtlB,SAAS7C,KAAKyL,aAAa,mBAjGR,GAkG5B,CAEA,UAAIlN,GACF,OAAOyB,KAAKyL,aAAa,SAC3B,CAEA4b,IAAY,KACVrnB,KAAKmnB,YAAYroB,SACV,GAGTwoB,IAAW,KACT9b,aAAaxL,MAAKknB,GAClBlnB,MAAKknB,EAAiBpc,WAAW,KAC/B,MAAMqH,EAAOnS,KAAKmnB,YAAY5c,MAC9BvK,KAAKonB,YAAY5hB,MAAMkiB,WAAavV,EAAO,UAAY,SACvDnS,KAAKgT,OAAOb,IACXnS,KAAKmoB,eAGVZ,IAAW,IAAMzqB,IAAI8B,SAAS,eAE9B4oB,IAAiBjhB,IACfA,EAAEC,iBACFxG,KAAKkc,SAGPuL,IAAY,KACVznB,KAAKkc,QACLlc,KAAKmnB,YAAYjoB,QAIrB4M,eAAeC,OAAO,sBAAuBkb,ICzH7C,MAAMmB,WAAsBja,YAC1Bka,IAAU,KACVC,IAAkB,KAElB,iBAAApe,GACElK,KAAKoB,iBAAiB,aAAcpB,MAAKiD,IACzCjD,KAAKoB,iBAAiB,aAAcpB,MAAK4D,GAC3C,CAEA,oBAAA6G,GACEzK,KAAK0K,oBAAoB,aAAc1K,MAAKiD,IAC5CjD,KAAK0K,oBAAoB,aAAc1K,MAAK4D,IAC5C5D,MAAK4D,IACP,CAEAX,IAAQ,KACN,MAAMolB,EAASroB,MAAKuoB,KACfF,IAELroB,MAAKqoB,GAAUA,EACfroB,MAAKsoB,GAAkBvY,EAAW/P,KAAMqoB,EAAQ,KAC9CnY,EAAgBlQ,KAAMqoB,EAAQ,CAC5BG,SAAU,QACVrY,UAAW,cAGXC,WAAY,CACVE,EAAK,CAAEmY,mBAAoB,CAAC,aAAcxmB,QAnC7B,OAqCduC,KAAK,EAAGwL,IAAGC,QACZlH,OAAO6G,OAAOyY,EAAO7iB,MAAO,CAC1BkjB,SAAU,QACVxX,KAAM,GAAGF,KAAK2X,MAAM3Y,OACpBmB,IAAK,GAAGH,KAAK2X,MAAM1Y,eAM3BrM,IAAS,KACP5D,MAAKsoB,OACLtoB,MAAKsoB,GAAkB,KAEnBtoB,MAAKqoB,KACProB,MAAKqoB,GAAQ7iB,MAAMkjB,SAAW,GAC9B1oB,MAAKqoB,GAAQ7iB,MAAM0L,KAAO,GAC1BlR,MAAKqoB,GAAQ7iB,MAAM2L,IAAM,GACzBnR,MAAKqoB,GAAU,OAOnB,GAAAE,GACE,MAAMF,EACJroB,KAAK8F,cAAc,oBAAsB9F,KAAK8F,cAAc,SAC9D,OAAKuiB,GAAgD,WAAtC9B,iBAAiB8B,GAAQK,SACjCL,EAD+D,IAExE,EAGFvc,eAAeC,OAAO,0BAA2Bqc,IChE1C,MAAMQ,WAAwBza,YACnC,iBAAAjE,GACElK,KAAKoB,iBAAiB,QAASpB,KACjC,CAEA,oBAAAyK,GACEzK,KAAK0K,oBAAoB,QAAS1K,KACpC,CAEA,WAAAyJ,CAAYK,GAENA,EAAMpI,OAAO6L,QAAQ,yBAA2BvN,KAAK6oB,QACvD7oB,KAAK8oB,aAAahf,EAEtB,CAEA,kBAAMgf,CAAahf,GACjBA,EAAMtD,iBAEN,UACQiZ,GAAM9W,EAAOogB,KAAKC,uBAAuBhpB,KAAKipB,SACpDjpB,KAAKkpB,QAAUlpB,KAAKkpB,MACtB,CAAE,MAAO5iB,GACPsB,EAAMtB,EAAMlC,SAAWkC,EAAO,QAChC,CACF,CAKA,YAAA6iB,GACEnpB,KAAK6oB,OAAO/L,SAAW9c,KAAK4kB,aAAe5kB,KAAKkpB,OAClD,CAEA,UAAIA,GACF,OAAOlpB,KAAKqU,aAAa,SAC3B,CAEA,UAAI6U,CAAO3e,GACTvK,KAAKopB,gBAAgB,SAAU7e,GAC/BvK,KAAK6oB,OAAOO,gBAAgB,SAAU7e,GACtCvK,KAAKqpB,aAAa5lB,UAAU0S,OAAO,SAAU5L,EAC/C,CAEA,eAAIqa,GACF,OACE5kB,KAAKqpB,aAAa3qB,iBAAiB,8BAA8BqF,OACjE,CAEJ,CAEA,UAAIklB,GACF,OAAOjpB,KAAKyL,aAAa,UAC3B,CAEA,UAAIod,GACF,OAAO7oB,KAAK8F,cAAc,kDAC5B,CAEA,gBAAIujB,GACF,OAAOrpB,KAAK8F,cAAc,4BAC5B,EAGFgG,eAAeC,OAAO,oBAAqB6c,ICtE3C,MAAMU,WAAgBnb,YACpBob,IAAoB,KAEpB,WAAA9f,CAAYK,GACS,UAAfA,EAAMJ,MACR1J,KAAKwpB,SAET,CAEA,iBAAAtf,GACE,IAAKlK,KAAK8F,cAAc,yBAA0B,CAChD,MAAM2jB,EACJzpB,KAAK0pB,aAA6B,UAAd1pB,KAAK0J,KACrB,6CACA,GACN1J,KAAK2pB,mBACH,aACA,uBAAuB3pB,KAAK0O,4BAA4B+a,IAE5D,EACIzpB,KAAK0pB,aAA6B,UAAd1pB,KAAK0J,OAC3B1J,KAAKoB,iBAAiB,QAASpB,MAE7BA,KAAK0pB,aAA6B,UAAd1pB,KAAK0J,OAC3B1J,MAAKupB,GAAoBze,WAAW,KAClC9K,KAAKwpB,WACJxpB,KAAK4pB,cAEZ,CAEA,oBAAAnf,GACiC,OAA3BzK,MAAKupB,KACP/d,aAAaxL,MAAKupB,IAClBvpB,MAAKupB,GAAoB,KAE7B,CAEA,OAAAC,GACExpB,KAAKoB,iBAAiB,gBAAiB,IAAMpB,KAAKO,UAClDP,KAAKyD,UAAUC,IAAI,YACrB,CAEA,eAAIgmB,GACF,OAAO1pB,KAAKqU,aAAa,cAC3B,CAEA,QAAI6E,GACF,OAAOlZ,KAAKyL,aAAa,OAC3B,CAEA,QAAI/B,GACF,OAAO1J,KAAKyL,aAAa,SAAW,QACtC,CAEA,gBAAIme,GACF,OAAO/mB,SACL7C,KAAK6pB,gBAAgB1qB,QAAQ2qB,kBA1Db,IA4DpB,CAEA,YAAIpb,GACF,OAAQ1O,KAAKkZ,MAAQlZ,KAAK0J,MACxB,IAAK,UACL,IAAK,OACL,IAAK,QACH,MAAO,QACT,IAAK,SACH,MAAO,QACT,IAAK,OACL,IAAK,OACH,MAAO,cACT,IAAK,QACH,MAAO,MACT,QACE,OAAO1J,KAAK0J,KAElB,CAEA,kBAAImgB,GACF,OAAO7pB,KAAKuN,QAAQ,iBACtB,EAGFzB,eAAeC,OAAO,kBAAmBud,ICnFzC,MAAMS,WAAc5b,YAClB,iBAAAjE,GACEtC,EAAM5H,KAAKoE,QAASpE,KAAKyL,aAAa,SAAW,UACjDzL,KAAKO,QACP,CAEA,WAAI6D,GACF,OAAOpE,KAAKyL,aAAa,YAAczL,KAAKP,SAC9C,EAGFqM,eAAeC,OAAO,gBAAiBge,ICVhC,MAAMC,WAAqB7b,YAChC8b,IAAW,KACXC,IAAiB,KACjBC,IAAiB,KAEjB,iBAAAjgB,GACElK,KAAKoqB,MAAQpqB,KAAK8F,cAAc,OAChC9F,KAAK8Z,KAAO9Z,KAAK8F,cAAc,QAC/B9F,MAAKkqB,GAAiBvsB,SAASuG,eAC7BlE,KAAKyL,aAAa,uBAEpBzL,MAAKmqB,GAAiBxsB,SAASuG,eAC7BlE,KAAKyL,aAAa,uBAEpBzL,KAAK0hB,cAAgB/jB,SAASmI,cAC5B,qBAAqB9F,KAAKyL,aAAa,mBAEzCzL,KAAK8Z,KAAK1Y,iBAAiB,SAAUpB,MAAKqqB,IAC1CrqB,KAAK8Z,KAAK1Y,iBAAiB,QAASpB,MAAKsqB,IACzCtqB,MAAKiqB,GAAW,IAAIM,EAAQvqB,KAAKoqB,MAAOpqB,KAAKwqB,eAC/C,CAEA,oBAAA/f,GACEzK,MAAKiqB,IAAUhb,SACjB,CAEA,kBAAIub,GACF,MAAO,CACLC,YAAazqB,KAAKyqB,YAClBC,SAAU,EACVC,UAAU,EACVC,kBAAkB,EAClBC,kBAAkB,EAClBvlB,KAAMtF,KAAK8qB,IAEf,CAEA,eAAIL,GACF,MAAMM,EAAQ/qB,KAAKyL,aAAa,SAEhC,OAAOsf,EAAQvE,WAAWuE,GAASC,GACrC,CAEA,YAAIC,GACF,GAAIjrB,MAAKkqB,IAAgB3f,MACvB,OAAOvK,MAAKkqB,GAAe3f,MAAMjN,MAAM,KAAKoV,IAAKwY,GAAMroB,SAASqoB,GAEpE,CAEA,YAAIC,GACF,GAAInrB,MAAKmqB,IAAgB5f,MACvB,OAAOvK,MAAKmqB,GAAe5f,MAAMjN,MAAM,KAAKoV,IAAKwY,GAAMroB,SAASqoB,GAEpE,CAEA,OAAIJ,GACF,OAAI9qB,KAAKirB,UAAYjrB,KAAKmrB,SACjB,CACLnb,EAAGhQ,KAAKirB,SAAS,GACjBhb,EAAGjQ,KAAKirB,SAAS,GACjBroB,MAAO5C,KAAKmrB,SAAS,GACrBroB,OAAQ9C,KAAKmrB,SAAS,IAGjBnrB,KAAKorB,UAEhB,CAEA,cAAIA,GACF,MAAMN,EAAMriB,KAAKC,MAAM1I,KAAKyL,aAAa,gBACzC,MAAO,CAAEuE,EAAG8a,EAAI,GAAI7a,EAAG6a,EAAI,GAAIloB,MAAOkoB,EAAI,GAAIhoB,OAAQgoB,EAAI,GAC5D,CAEA,MAAAO,CAAOC,GACLtrB,MAAKkqB,GAAe3f,MAAQ,GAAG+gB,EAAOtb,KAAKsb,EAAOrb,IAClDjQ,MAAKkqB,GAAevpB,cAAc,IAAIiZ,MAAM,WAC5C5Z,MAAKmqB,GAAe5f,MAAQ,GAAG+gB,EAAO1oB,SAAS0oB,EAAOxoB,SACtD9C,MAAKmqB,GAAexpB,cAAc,IAAIiZ,MAAM,UAC9C,CAEA,KAAAkH,GACE,MAAMmJ,EAAUjqB,MAAKiqB,GAErBA,EAAQsB,QAAQvrB,KAAKorB,YAMrB,MAAMI,EAASvB,EAAQwB,gBACjBC,EAAQF,EAAO5oB,MAAQ4oB,EAAOG,aACpC1B,EAAQ2B,eAAe,CACrB1a,KAAMsa,EAAOta,KAAOlR,KAAKorB,WAAWpb,EAAI0b,EACxCva,IAAKqa,EAAOra,IAAMnR,KAAKorB,WAAWnb,EAAIyb,IAExC1rB,KAAKqrB,OAAOrrB,KAAKorB,WACnB,CAEAf,IAAavgB,IACXA,EAAMtD,iBACNxG,KAAKqrB,OAAOrrB,MAAKiqB,GAAS4B,SAAQ,IAClC7rB,KAAK0hB,eAAelU,WACpBtG,KAAiBtD,SAGnB0mB,IAAYxgB,IACVA,EAAMtD,iBACNxG,KAAK8gB,SAIThV,eAAeC,OAAO,wBAAyBie,IC/GhC,MAAM8B,WAAqBvpB,EAIxCwpB,IAAiBxlB,IACf,GAAIA,EAAE7E,OAAO6L,QAAQ,2BAGnB,OAFAhH,EAAEC,sBACFxG,KAAKkD,OAAOO,UAAU0S,OAAO,aAG/B,GAAI5P,EAAE7E,OAAO6L,QAAQ,8BAA+B,CAElD,GADAhH,EAAE0T,kBACwB,QAAtB1T,EAAE7E,OAAOsqB,SACX,OAEFhsB,KAAK4D,OACP,GAGFqoB,IAAmB1lB,IACjB,GAA0B,UAAtBA,EAAE7E,OAAOsqB,UAA8C,aAAtBzlB,EAAE7E,OAAOsqB,SAG9C,OAAQzlB,EAAEzJ,KACR,IAAK,YACHkD,KAAKyX,WACLlR,EAAEC,iBACF,MACF,IAAK,aACHxG,KAAKksB,OACL3lB,EAAEC,mBAOR,MAAAxD,GAAU,CAEV,IAAA0C,GACE1F,KAAKqF,YAAYqF,oBAAoB,QAAS1K,MAAK+rB,IACnD/rB,KAAKqF,YAAYjE,iBAAiB,QAASpB,MAAK+rB,IAChD/rB,MAAKmsB,KACL9d,MAAM3I,MACR,CAEA,QAAA+R,GACEzX,KAAKqF,YAAYS,cAAc,sBAAsB7G,OACvD,CAEA,IAAAitB,GACElsB,KAAKqF,YAAYS,cAAc,kBAAkB7G,OACnD,CAEA,KAAA2E,GAEE,OADAjG,SAAS+M,oBAAoB,UAAW1K,MAAKisB,IACtC5d,MAAMzK,OACf,CAEA,KAAAb,GACE/C,KAAKoD,iBAAmB/D,EACtB,6DAEFW,KAAKkD,OAAS7D,EACZ,oDAEFW,KAAKqF,YAAchG,EACjB,kDAEFW,KAAKgG,aACH3G,EAAkB,iHAGpBW,KAAKkD,OAAO7C,OAAOL,KAAKgG,cACxBhG,KAAKkD,OAAO7C,OAAOL,KAAKqF,aACxBrF,KAAKoD,iBAAiB/C,OAAOL,KAAKkD,QAClCvF,SAASyC,KAAKC,OAAOL,KAAKoD,iBAC5B,CAEA,GAAA+oB,GACExuB,SAAS+M,oBAAoB,UAAW1K,MAAKisB,IAC7CtuB,SAASyD,iBAAiB,UAAWpB,MAAKisB,GAC5C,EC1EFngB,eAAeC,OAAO,6BAPf,cAA+BqS,GACpC,UAAAhX,GACEpH,KAAKkD,OAAS,IAAI4oB,GAAa9rB,KAAKosB,KAAM,CAAEnqB,SAAS,IACrDjC,KAAKkD,OAAOD,MACd,GAGoE,CACpE+I,QAAS,MCZJ,MAAMqgB,WAAwBC,mBACnCC,IAAmB,qCAEnB,iBAAAriB,GACElK,KAAKoB,iBAAiB,SAAUpB,MAE5BA,KAAKwsB,mBACPxsB,KAAKiD,MAAO,EAEhB,CAEA,oBAAAwH,GACEzK,KAAK0K,oBAAoB,SAAU1K,KACrC,CAKA,WAAAyJ,GACE,IAAIgjB,EAA6BzsB,KAAK0sB,iBAElC1sB,KAAKiD,MACPjD,KAAK4jB,WAAWnjB,KAAO,eAClBT,KAAKwsB,kBAAkBC,EAA2BztB,KAAKgB,KAAKuI,MAEjEvI,KAAK4jB,WAAWnjB,KAAO,gBACvBgsB,EAA6BA,EAA2BzZ,OACrDzI,GAAUA,IAAUvK,KAAKuI,KAI9B6B,aAAamB,QACXvL,MAAKusB,GACL9jB,KAAKwL,UAAUwY,GAEnB,CAEA,oBAAID,GACF,OAAOxsB,KAAK0sB,iBAAiB/qB,SAAS3B,KAAKuI,GAC7C,CAEA,oBAAImkB,GACF,MAAM7Z,EAAOzI,aAAaC,QAAQrK,MAAKusB,IAEvC,IAAK1Z,EAAM,MAAO,GAElB,IACE,OAAOpK,KAAKC,MAAMmK,EACpB,CAAE,MAAOvM,GAEP,OADA7I,QAAQ6I,MAAMA,GACP,EACT,CACF,CAEA,cAAIsd,GACF,OAAO5jB,KAAK8F,cAAc,eAC5B,EAGFgG,eAAeC,OAAO,2BAA4BsgB,GAAiB,CACjErgB,QAAS,YC5DX,MAAM2gB,WAAmB3S,kBACvB,iBAAA9P,GACElK,KAAKoB,iBAAiB,QAASpB,MAC/BA,KAAKyD,UAAUC,IAAI,eAEnB1D,KAAKgN,aAAa,OAAQ,UAC1BhN,KAAKP,UAAY,0DACnB,CAEA,WAAAgK,CAAYK,GACK,IAAI3M,QAAQyvB,WAAW,CACpClqB,IAAK1C,KAAK6sB,QACV3qB,MAAOlC,KAAK8sB,UACZprB,OAAQ1B,KAAK+sB,WACbrjB,KAAM1J,KAAKgtB,YAEN/pB,OAAOuB,KAAMyoB,GAASjtB,KAAKktB,QAAQD,IAC1CnjB,EAAMtD,gBACR,CAEA,OAAA0mB,CAAQD,GACW,KAAbA,EAAKvqB,KACP1C,KAAKyD,UAAUlD,OAAO,UACtBP,KAAKW,cAAc,IAAIC,YAAY,iBAAkB,CAAEC,SAAS,OAEhEb,KAAKyD,UAAUC,IAAI,UACnB1D,KAAKW,cACH,IAAIC,YAAY,eAAgB,CAC9BC,SAAS,EACTH,OAAQusB,KAIhB,CAEA,WAAIJ,GACF,OAAO7sB,KAAKmtB,YAAYC,aAAa7iB,KACvC,CAEA,aAAIuiB,GACF,OAAO9sB,KAAKmtB,YAAYE,eAAe9iB,KACzC,CAEA,cAAIwiB,GACF,OAAO/sB,KAAKmtB,YAAYG,gBAAgB/iB,KAC1C,CAEA,aAAIyiB,GACF,OAAOhtB,KAAKmtB,YAAYI,eAAehjB,KACzC,CAEA,eAAI4iB,GACF,OAAOntB,KAAKuN,QAAQ,uBACtB,EAGFzB,eAAeC,OAAO,sBAAuB4gB,GAAY,CAAE3gB,QAAS,WCxDpE,MAAMwhB,WAAqBxT,kBACzB,iBAAA9P,GACElK,KAAKoB,iBAAiB,QAASpB,MAC/BA,KAAKyD,UAAUC,IAAI,eAEnB1D,KAAKgN,aAAa,OAAQ,UAC1BhN,KAAKytB,OAASztB,KAAKytB,OACnBztB,KAAKP,UACH,iEACJ,CAEA,WAAAgK,CAAYK,GACN9J,KAAKytB,SACPztB,KAAKytB,QAAS,EACdztB,KAAKd,OACLc,KAAKW,cAAc,IAAIC,YAAY,iBAAkB,CAAEC,SAAS,MAElEiJ,EAAMtD,gBACR,CAEA,UAAIinB,CAAOC,GACLA,GACF1tB,KAAKyD,UAAUvF,QAAQ,WAAY,UACnC8B,KAAKua,gBAAgB,cAErBva,KAAKyD,UAAUvF,QAAQ,SAAU,YACjC8B,KAAKgN,aAAa,WAAY,MAElC,CAEA,UAAIygB,GACF,OAAOztB,KAAKyD,UAAU8a,SAAS,SACjC,EAGFzS,eAAeC,OAAO,wBAAyByhB,GAAc,CAC3DxhB,QAAS,WCjCX,MAAM2hB,WAAoBxf,YACxB,iBAAAjE,GACElK,KAAKoB,iBAAiB,eAAgBpB,MACtCA,KAAKoB,iBAAiB,iBAAkBpB,KAC1C,CAEA,oBAAAyK,GACEzK,KAAK0K,oBAAoB,eAAgB1K,MACzCA,KAAK0K,oBAAoB,iBAAkB1K,KAC7C,CAEA,WAAAyJ,CAAYK,GACV,OAAQA,EAAMJ,MACZ,IAAK,eACH1J,KAAKktB,QAAQpjB,EAAMpJ,QACnB,MACF,IAAK,iBACHV,KAAK4tB,aAET9jB,EAAMmQ,iBACR,CAEA,OAAAiT,CAAQ5nB,GACNtF,KAAKotB,aAAa7iB,MAAQjF,EAAK5C,IAC/B1C,KAAKotB,aAAazsB,cAAc,IAAIiZ,MAAM,WAC1C5Z,KAAKqtB,eAAe9iB,MAAQjF,EAAKpD,MACjClC,KAAKutB,eAAehjB,MAAQjF,EAAKoE,KACjC1J,KAAKstB,gBAAgB/iB,MAAQjF,EAAK5D,OAElC1B,KAAK6tB,aAAaJ,QAAS,EAC3BztB,KAAK8tB,iBACP,CAEA,UAAAF,GACE5tB,KAAKotB,aAAa7iB,MAAQ,GAC1BvK,KAAKotB,aAAazsB,cAAc,IAAIiZ,MAAM,WAC1C5Z,KAAKqtB,eAAe9iB,MAAQ,GAC5BvK,KAAKutB,eAAehjB,MAAQ,GAC5BvK,KAAKstB,gBAAgB/iB,MAAQ,GAE7BvK,KAAK+tB,WAAWtqB,UAAUlD,OAAO,UACjCP,KAAK6tB,aAAaJ,QAAS,EAE3BztB,KAAK8tB,iBACP,CAEA,eAAAA,GACE9tB,KAAK0hB,cAAclU,SAASxN,KAC9B,CAEA,cAAI+tB,GACF,OAAO/tB,KAAK8F,cAAc,6BAC5B,CAEA,gBAAI+nB,GACF,OAAO7tB,KAAK8F,cAAc,+BAC5B,CAEA,oBAAIiH,GACF,MAAMF,EAAe7M,KAAKb,QAAQ0N,aAClC,OAAO7M,KAAK6B,cAAc0L,QAAQ,wBAAwBV,MAC5D,CAEA,iBAAI6U,GACF,OAAO1hB,KAAKuN,QAAQ,yBACtB,CAEA,gBAAI6f,GACF,OAAOptB,KAAK+M,iBAAiBjH,cAAc,oBAC7C,CAEA,kBAAIunB,GACF,OAAOrtB,KAAK+M,iBAAiBjH,cAAc,oBAC7C,CAEA,mBAAIwnB,GACF,OAAOttB,KAAK+M,iBAAiBjH,cAAc,qBAC7C,CAEA,kBAAIynB,GACF,OAAOvtB,KAAK+M,iBAAiBjH,cAAc,oBAC7C,EAGFgG,eAAeC,OAAO,uBAAwB4hB,IC/E9C,MAAMK,WAAmB7f,YACvB,iBAAAjE,GACElK,KAAKiuB,MAAQjuB,KAAK8F,cAAc,uBAChC9F,KAAKkuB,OAAS3sB,MAAMC,KAAKxB,KAAKtB,iBAAiB,wBAC1CsB,KAAKiuB,OAASjuB,KAAKkuB,OAAOnqB,OAAS,IAExC/D,KAAKmuB,aAAe,EACpBnuB,KAAKouB,aACLpuB,KAAKquB,UAAU,GACfruB,KAAKsuB,gBAELtuB,KAAKoB,iBAAiB,aAAcpB,MACpCA,KAAKoB,iBAAiB,aAAcpB,MACpCA,KAAKiuB,MAAM7sB,iBAAiB,SAAUpB,KAAM,CAAEuuB,SAAS,IACzD,CAEA,oBAAA9jB,GACEzK,KAAKwuB,cACP,CAEA,WAAA/kB,CAAYK,GACV,OAAQA,EAAMJ,MACZ,IAAK,aACH1J,KAAKwuB,eACL,MACF,IAAK,aACHxuB,KAAKsuB,gBACL,MACF,IAAK,SACHtuB,KAAKyuB,qBAGX,CAEA,UAAAL,GACEpuB,KAAK0uB,QAAU/wB,SAAS6B,cAAc,OACtCQ,KAAK0uB,QAAQtf,UAAY,oBACzBpP,KAAK2uB,KAAO3uB,KAAKkuB,OAAOxb,IAAI,CAACkc,EAAGC,KAC9B,MAAMC,EAAMnxB,SAAS6B,cAAc,UASnC,OARAsvB,EAAIplB,KAAO,SACXolB,EAAI1f,UAAY,mBAChB0f,EAAI9hB,aAAa,aAAc,GAAG6hB,EAAQ,KAC1CC,EAAI1tB,iBAAiB,QAAS,KAC5BpB,KAAK+uB,KAAKF,GACV7uB,KAAKsuB,kBAEPtuB,KAAK0uB,QAAQruB,OAAOyuB,GACbA,IAET9uB,KAAKK,OAAOL,KAAK0uB,QACnB,CAEA,IAAAK,CAAKF,GACH7uB,KAAKmuB,aAAeU,EACpB7uB,KAAKkuB,OAAOW,GAAO5L,eAAe,CAChCC,SAAU,SACV8L,OAAQ,QACR9G,MAAO,YAETloB,KAAKquB,UAAUQ,EACjB,CAEA,IAAA3C,GACElsB,KAAK+uB,MAAM/uB,KAAKmuB,aAAe,GAAKnuB,KAAKkuB,OAAOnqB,OAClD,CAGA,kBAAA0qB,GACE,MAAM7rB,EAAQ5C,KAAKiuB,MAAMgB,YACzB,IAAKrsB,EAAO,OACZ,MAAMisB,EAAQ7d,KAAK2X,MAAM3oB,KAAKiuB,MAAMiB,WAAatsB,GAC7CisB,IAAU7uB,KAAKmuB,cAAgBnuB,KAAKkuB,OAAOW,KAC7C7uB,KAAKmuB,aAAeU,EACpB7uB,KAAKquB,UAAUQ,GAEnB,CAEA,SAAAR,CAAUQ,GACR7uB,KAAK2uB,KAAKrwB,QAAQ,CAACwwB,EAAKK,KACtBL,EAAIrrB,UAAU0S,OAAO,YAAagZ,IAAMN,IAE5C,CAEA,aAAAP,GACEtuB,KAAKwuB,eACLxuB,KAAKovB,MAAQlyB,OAAOmyB,YAAY,IAAMrvB,KAAKksB,OAAQlsB,KAAKsvB,SAC1D,CAEA,YAAAd,GACMxuB,KAAKovB,OAAOlyB,OAAOqyB,cAAcvvB,KAAKovB,OAC1CpvB,KAAKovB,MAAQ,IACf,CAEA,YAAIE,GACF,OAAOzsB,SAAS7C,KAAKyL,aAAa,YAAa,KAAO,GACxD,EAGFK,eAAeC,OAAO,sBAAuBiiB,ICtGtC,MAAMwB,WAA0BrhB,YACrC,6BAAWC,GACT,MAAO,CAAC,SACV,CAEA,iBAAAlE,GACElK,KAAKP,UAAY,iFAESO,KAAK0O,kDAGjC,CAEA,wBAAAF,GAEExO,KAAKkZ,MAAMlM,aAAa,OAAQhN,KAAK0O,SACvC,CAEA,QAAIwK,GACF,OAAOlZ,KAAK8F,cAAc,eAC5B,CAEA,YAAI4I,GACF,OAAO1O,KAAKkpB,OAAS,gBAAkB,cACzC,CAEA,UAAIA,GACF,OAAOlpB,KAAKqU,aAAa,SAC3B,EAGFvI,eAAeC,OAAO,sBAAuByjB,IClCtC,MAAMC,WAAiBthB,YAC5B,iBAAAjE,GACElK,KAAKoB,iBAAiB,8BAA+BpB,MAAK0vB,GAC5D,CAEA,oBAAAjlB,GACEzK,KAAK0K,oBAAoB,8BAA+B1K,MAAK0vB,GAC/D,CAEAA,IAAiB5lB,IACf,MAAM4M,EAAO5M,EAAMpJ,OAAOmT,MAEtB6C,GACF1W,KAAK2vB,UAAU3iB,aAAa,cAAe0J,EAAKjW,MAChDT,KAAK4vB,SAASrlB,MAAQmM,EAAKmZ,SAC3B7vB,KAAK4vB,SAAS5iB,aAAa,WAAY,cAEvChN,KAAK2vB,UAAUpV,gBAAgB,eAC/Bva,KAAK4vB,SAASrlB,MAAQ,GACtBvK,KAAK4vB,SAASrV,gBAAgB,cAIlC,aAAIoV,GACF,OAAO3vB,KAAK8F,cAAc,aAC5B,CAEA,YAAI8pB,GACF,OAAO5vB,KAAK8F,cAAc,YAC5B,EAGFgG,eAAeC,OAAO,oBAAqB0jB,ICY3C3jB,eAAeC,OAAO,sBA1CtB,cAAyBmH,EACvB,YAAAyD,CAAaxE,EAAMuE,GACjB,MAAO,CACL1D,OAAQ,CACN8c,yBAA0B3d,KACvB1J,KAAKC,MAAM1I,KAAKwU,cAErBkC,KAAMA,EAEV,CAEA,MAAA6B,CAAOwQ,EAAM5W,GACX,MAAO,CACL+G,KAAM,SACNL,QAAS7Y,MAAK+vB,GAAYhH,GAAQ/oB,KAAKsZ,gBAAgByP,EAAKtoB,KAAM0R,GAClE6G,UAAW,CAAE1Z,KAAMypB,EAAKrmB,IAAK2W,SAAU,QAE3C,CAEA,cAAAZ,CAAesQ,GACb,MAAO,CACL7P,KAAM,SACNL,QAAS7Y,MAAK+vB,GAAYhH,GAAQpa,EAAYoa,EAAKtoB,MAEvD,CAQA,GAAAsvB,CAAYhH,GACV,MAAMiH,EAAYjH,EAAKiH,UAAUtd,IAAKsV,GAAaA,EAASvnB,MAC5D,GAAyB,IAArBuvB,EAAUjsB,OAAc,MAAO,GACnC,MAAM+S,EAAY,oFAClB,MAAO,wCAAwCkZ,EAC5Ctd,IAAI/D,GACJoE,KAAK+D,YAAoBA,GAC9B,IChCK,MAAMmZ,WAAwB9hB,YACnC,iBAAAjE,GACElK,KAAKtB,iBAAiB,eAAeJ,QAAS4xB,IAC5C,IAAIC,EAASD,EAAM,CACjB3yB,MAAO,QACP6yB,UAAW,IACXC,gBAAgB,EAChBC,cAAe,IACfC,OAAQ,aACRC,YAAY,EACZC,UAAW,oBACXC,MAAQ5mB,GAAU9J,KAAK2wB,WAAW7mB,MAGxC,CAEA,gBAAM6mB,CAAW7mB,GACf,GAAIA,EAAMtI,OAASsI,EAAM8mB,IAAM9mB,EAAM+mB,WAAa/mB,EAAMgnB,SACtD,OAGF,MAAMpuB,EAAMiG,EAAOogB,KAAKgI,cAAcjnB,EAAM+I,KAAKoW,QAC3C3jB,EAAO,CACX0rB,iBAAkBlnB,EAAM8mB,GAAGzxB,QAAQ8xB,SACnCC,aAAcpnB,EAAMgnB,UAGtB,UACQrR,GAAM/c,EAAK4C,GACjBtF,KAAKmxB,cAAcrnB,EAAMtI,KAAMsI,EAAM8mB,IACrChpB,EAAM9J,EAAU,gCAClB,CAAE,MAAOwI,GACPsB,EAAMtB,EAAMlC,SAAWkC,EAAO,QAChC,CACF,CAEA,aAAA6qB,CAAc3vB,EAAMovB,GAClBpvB,EAAK+L,QAAQ,sBAAsB4b,eAE/B3nB,IAASovB,GACXA,EAAGrjB,QAAQ,sBAAsB4b,cAErC,ECpDK,SAASiI,GAAeC,GAC7B,IAAIC,EAAqB,IAAVD,EAAc,EAAIrgB,KAAKugB,MAAMvgB,KAAKwgB,IAAIH,GAASrgB,KAAKwgB,IAAI,OAQvE,OALIF,EAAW,IACbA,EAAW,IAGAD,EAAQrgB,KAAKygB,IAAI,KAAMH,IAAWI,QAAQ,GACxC,IAAM,CAAC,IAAK,KAAM,KAAM,MAAMJ,EAC/C,CD6CAxlB,eAAeC,OAAO,oBAAqBkkB,IElDpC,MAAM0B,WAAmBxjB,YAE9ByjB,KAAO,KACPC,QAAU,KACVC,oBAAsB,EACtBC,mBAAqB,EAGrBC,KAAS,EACTznB,IAAS,EACTtF,SAAUjH,EACVi0B,IAAgB,GAEhB,iBAAA/nB,GAiBE,GAhBAlK,KAAKP,UAAY,mCACWO,KAAKuK,+FAEHvK,KAAK4xB,MAAMnxB,kDACTT,KAAKkyB,0DACHlyB,KAAKiyB,iEAEdn0B,EAAU,sEACWA,EAAU,wHAMxDkC,KAAK8F,cAAc,UAAU1E,iBAAiB,QAAS,IAAMpB,KAAKmyB,UAE9DnyB,KAAK4xB,MAAMloB,KAAK/H,SAAS,SAAU,CACrC,MAAMywB,EAAS,IAAIC,WACnBD,EAAOE,cAActyB,KAAK4xB,MAC1BQ,EAAOhxB,iBAAiB,OAAQ,KAC9B,MAAMgpB,EAAQ,IAAImI,MAClBnI,EAAMvf,IAAMunB,EAAOI,OACnBxyB,KAAKyyB,QAAQrI,IAEjB,CACF,CAOA,UAAAsI,CAAWd,EAAMC,GACf7xB,KAAK4xB,KAAOA,EACZ5xB,KAAK6xB,QAAUA,EACf7xB,KAAK+xB,mBAAqBH,EAAOA,EAAK9xB,KAAO,EAC7CE,KAAKiF,OAAS,cAEdjF,MAAK2yB,KACL3yB,MAAK4yB,IACP,CAKA,MAAAT,GACOnyB,KAAK6yB,WACR7yB,KAAKiF,OAAS,WACdjF,KAAK6xB,SAASiB,QACd9yB,KAAKQ,oBAAoB,qBAE7B,CAMA,mBAAAA,CAAoBC,GAClBT,KAAKW,cAAc,IAAIC,YAAY,WAAWH,IAAQ,CAAEI,SAAS,IACnE,CAKA,GAAA8xB,GACE,MAAMI,EAASzqB,EAAe,6BACxB0qB,EAAcD,EAAOE,gBAAkBjiB,KAAKygB,IAAI,KAAM,GAC5D,IAAIQ,EAEAjyB,KAAK4xB,MAAM9xB,KAAOkzB,IACpBf,EAAen0B,EAAU,oCAG3B,MAAMo1B,EAAmBlzB,KAAK4xB,MAAMloB,KAAK/H,SAAS,SAC9CoxB,EAAOI,kBAAkBC,iBACzBL,EAAOI,kBAAkBE,oBAKvBC,EAAYtzB,KAAK4xB,MAAMnxB,KAAK6R,MAAM,gBAAgB,GAAG6M,cAGzD+T,EAAiBvxB,SAAS,MAC1BuxB,EAAiBK,KAAM7pB,GAASA,EAAKyV,gBAAkBmU,KAGvDrB,EAAen0B,EAAU,0BAGvBm0B,IACFjyB,KAAKgyB,OAAQ,EACbhyB,KAAKiyB,aAAeA,EAExB,CAKA,GAAAW,GAEO5yB,KAAK6xB,UAKV7xB,KAAK6xB,QAAQ2B,OAAOC,WAAcC,IAChC1zB,KAAK0zB,cAAgBA,GAIvB1zB,KAAK6xB,QAAQ8B,OAAS,KAChB3zB,KAAK6xB,QAAQ5sB,OAAS,KACxBjF,KAAKiF,OAAS,aACd2C,EAAM5H,KAAK4zB,mBAEX5zB,KAAKiF,OAAS,SACdjF,KAAKiyB,aAAejyB,KAAK4zB,iBAE3B5zB,KAAKQ,oBAAoB,sBAI3BR,KAAK6xB,QAAQgC,QAAU,KACrB7zB,KAAKiyB,aAAen0B,EAAU,6CAElC,CAKA,UAAIgP,GACF,OAAO9M,KAAKgyB,OAAyB,aAAhBhyB,KAAKiF,MAC5B,CAKA,gBAAIgtB,GACF,OAAOjyB,MAAKiyB,IAAiB,EAC/B,CAKA,gBAAIA,CAAa7tB,GACfpE,MAAKiyB,GAAgB7tB,EACrB,MAAM0vB,EAAwB9zB,KAAK8F,cAAc,kBAC7CguB,IACFA,EAAsBntB,YAAcvC,GAEtCwD,EAAMxD,EAAS,QACjB,CAKA,YAAIyuB,GACF,MAAO,CAAC,WAAY,aAAc,UAAUlxB,SAAS3B,KAAKiF,OAC5D,CAMA,cAAIitB,GACF,MAAO,GAAGd,GAAepxB,KAAK8xB,0BAA0BV,GACtDpxB,KAAK+xB,qBAET,CAKA,mBAAIgC,GACF,OAAO/zB,KAAK8F,cAAc,kBAC5B,CAKA,iBAAI4tB,CAAcA,GAChB1zB,KAAK8xB,oBAAsB4B,EAAcM,OACzCh0B,KAAK+xB,mBAAqB2B,EAAcO,MAExCj0B,KAAKuK,MAAQyG,KAAK2X,MAAO+K,EAAcM,OAASN,EAAcO,MAAS,KACvEj0B,KAAK8F,cAAc,gBAAgBa,YAAc3G,KAAKkyB,UACxD,CAKA,mBAAI0B,GACF,IAEE,OADiBnrB,KAAKC,MAAM1I,KAAK6xB,QAAQltB,cAChB,OAC3B,CAAE,MAAO2B,GACP,MAAO,GAAGtG,KAAK6xB,QAAQ5sB,WAAWjF,KAAK6xB,QAAQ3sB,YACjD,CACF,CAKA,UAAID,GACF,OAAOjF,MAAKiF,EACd,CAKA,UAAIA,CAAOA,GACTjF,MAAKiF,GAAUA,EACfjF,KAAKoP,UAAYnK,EAEjBjF,KAAK+zB,iBAAiB3K,gBACpB,gBACW,oBAAXnkB,EAEJ,CAKA,SAAI+sB,GACF,OAAOhyB,MAAKgyB,EACd,CAKA,SAAIA,CAAMkC,GACRl0B,MAAKgyB,GAASkC,EACdl0B,KAAKyD,UAAU0S,OAAO,WAAY+d,EACpC,CAMA,SAAI3pB,GACF,OAAOvK,MAAKuK,EACd,CAKA,SAAIA,CAAMA,GACRvK,MAAKuK,GAASA,EACVvK,KAAK+zB,kBACP/zB,KAAK+zB,gBAAgBxpB,MAAQA,GAGjB,MAAVA,IACFvK,KAAKiF,OAAS,mBAGhBjF,KAAKQ,oBAAoB,oBAC3B,EAGFsL,eAAeC,OAAO,sBAAuB4lB,ICnQtC,MAAMwC,WAAiBhmB,YAE5BimB,UAAY,EAGZC,IAAe,GACfC,IAAex2B,EAAU,sBACzBy2B,IAAgB,KAChBrP,IAAW,EACXsP,IAAoB,IAAMx0B,MAAKy0B,KAE/B,iBAAAvqB,GA5Be,IAACoqB,EAAaF,EA6B3Bp0B,KAAKP,WA7BS60B,EA6BYt0B,MAAKs0B,GA7BJF,EA6BkBp0B,KAAKo0B,UA7BT,+JAKlBE,sDACqBA,mKAM5CF,EAAY,EAAI,EAAIA,sEAkBpBp0B,KAAKklB,SAAU,EAEfllB,MAAKu0B,GAAgBv0B,KAAK8F,cAAc,UACxC9F,MAAKu0B,GAAcnzB,iBAAiB,QAAS,KACvCpB,KAAK6yB,SACP7yB,KAAK00B,WAAW10B,KAAKiF,QAErBjF,KAAKmyB,WAITnyB,MAAKq0B,GAAa/1B,QAASq2B,IACzB30B,KAAK8F,cAAc,mBAAmBzF,OAAOs0B,KAG/C30B,MAAKy0B,KACLz0B,KAAKoB,iBAAiB,4BAA6BpB,MAAKw0B,GAC1D,CAEA,oBAAA/pB,GACEzK,KAAK0K,oBACH,4BACA1K,MAAKw0B,GAET,CAMA,UAAA9B,CAAW2B,EAAc,IACvBr0B,MAAKq0B,GAAeA,EACpBr0B,KAAKo0B,UAAYC,EAAYtwB,MAC/B,CAKA,MAAAouB,GACEnyB,MAAK40B,KAAiBt2B,QAASk1B,IAC7BA,EAAOrB,WAETnyB,MAAK60B,IACP,CAOA,UAAAH,CAAWI,GAAU,CAMrB,GAAAF,GACE,OAAO50B,MAAKq0B,GAAarhB,OAAQwgB,GAAWA,EAAO1mB,OACrD,CAKA,GAAA+nB,GACE70B,MAAKs0B,GAAex2B,EAAU,SAC9BkC,MAAKu0B,GAAcQ,UAAY/0B,MAAKs0B,GACpCt0B,MAAKu0B,GAAc1yB,cAAcnC,QAAUM,MAAKs0B,EAClD,CAMA,GAAAU,CAAmBhY,GACjB,OAAOhd,MAAK40B,KAAiBK,OAC3B,CAACC,EAAa1B,IAAWA,EAAOxW,GAASkY,EACzC,EAEJ,CAKA,GAAAT,GACE,MAAMxvB,EAASjF,KAAKiF,OACpBjF,KAAKoP,UAAYnK,EAGjBjF,KAAK+zB,gBAAgBxpB,MAAQvK,KAAKm1B,cAClCn1B,KAAK+zB,gBAAgB3K,gBACnB,gBACW,oBAAXnkB,GAIFjF,KAAK8F,cAAc,kCAAkCa,YACnD3G,KAAKo1B,qBACPp1B,KAAK8F,cAAc,yBAAyBa,YAC1C3G,KAAKq1B,kBAEHr1B,KAAK6yB,UACP7yB,MAAK60B,KACL70B,KAAK00B,WAAWzvB,IAEhBjF,KAAKklB,SAAU,CAEnB,CAKA,YAAI2N,GACF,OAAO7yB,MAAK40B,KAAiBU,MAAOC,GAAUA,EAAM1C,SACtD,CAKA,qBAAIwC,GACwBr1B,MAAK40B,KAAiB5hB,OAC7CwiB,GAAiBA,EAAajrB,OAAS,KACxCxG,OAKF,OAHE/D,KAAKm1B,cACsBn1B,MAAK40B,KAAiB7wB,OAE5C,GAAGqtB,GACRpxB,MAAKg1B,GAAmB,6BACnB5D,GAAepxB,MAAKg1B,GAAmB,wBAChD,CAKA,wBAAII,GACF,MAAMK,EAAoBz1B,MAAK40B,KAAiB5hB,OAC7CwiB,GAAiBA,EAAajrB,OAAS,KACxCxG,OACF,MAAO,GAAG/D,KAAKm1B,mBAAmBM,OAChCz1B,MAAK40B,KAAiB7wB,SAE1B,CAKA,mBAAIgwB,GACF,OAAO/zB,KAAK8F,cAAc,kBAC5B,CAMA,UAAIb,GACF,MAAMywB,EAAkB11B,MAAK40B,KAAiBliB,IAC3C8gB,GAAWA,EAAOpkB,WAIrB,OAAIsmB,EAAgB/zB,SAAS,UACpB,SAIsB,IAA3B+zB,EAAgB3xB,OACX,WAIL2xB,EAAgBJ,MAAOC,GAAUA,IAAUG,EAAgB,IACtDA,EAAgB,GAGlB,aACT,CAKA,iBAAIP,GACF,MAAMQ,EAAY31B,MAAK40B,KAAiBK,OACtC,CAACC,EAAa1B,IAAW0B,EAAc1B,EAAO5B,KAAK9xB,KACnD,GAEF,IAAIq1B,EAAgBnkB,KAAK4kB,KACvB51B,MAAK40B,KAAiBK,OAAO,CAACC,EAAa1B,KACzC,MAAMqC,EAASrC,EAAO5B,KAAK9xB,KAAO61B,EAClC,OAAOnC,EAAOjpB,MAAQsrB,EAASX,GAC9B,IAML,OAHIC,EAAgB,MAClBA,EAAgB,KAEXA,CACT,CAKA,WAAIjQ,GACF,OAAOllB,MAAKklB,CACd,CAKA,WAAIA,CAAQA,GACVllB,KAAKyD,UAAU0S,OAAO,UAAW+O,GACjCllB,MAAKklB,EAAWA,CAClB,EAGFpZ,eAAeC,OAAO,0BAA2BooB,IC7O1C,MAAM2B,WAAiB3nB,YAC5B4nB,IAAmB,KACnBC,KAAiB,EAEjB,iBAAA9rB,GACElK,KAAKi2B,UAAU70B,iBAAiB,SAAUpB,MAAKk2B,IAC3Cl2B,KAAKm2B,UACPn2B,MAAKo2B,KAEPp2B,KAAKoB,iBAAiB,4BAA6BpB,KACrD,CAEA,oBAAAyK,GACEzK,KAAKi2B,WAAWvrB,oBAAoB,SAAU1K,MAAKk2B,IAC/Cl2B,MAAK+1B,KACP/1B,MAAK+1B,GAAiBrrB,oBACpB,YACA1K,MAAKq2B,IAEPr2B,MAAK+1B,GAAiBrrB,oBAAoB,OAAQ1K,MAAKs2B,IACvDt2B,MAAK+1B,GAAiBrrB,oBACpB,WACA1K,MAAKu2B,IAEPv2B,MAAK+1B,GAAmB,KAE5B,CAEA,WAAAtsB,CAAYpI,GACV,GACO,8BADCA,EAAIqI,KAER1J,MAAKw2B,IAGX,CAEAN,IAAsBpsB,IACpB9J,KAAKy2B,YAAYl1B,MAAMC,KAAKsI,EAAMpI,OAAOg1B,SAG3CC,IAAwBC,IAClB52B,MAAKg2B,KAAmBY,IAC1B52B,MAAKg2B,GAAiBY,EACtB52B,MAAK+1B,GAAiBtyB,UAAU0S,OAAO,cAI3CkgB,IAAuB,IAAMr2B,MAAK22B,IAAqB,GAEvDL,IAAkB7xB,MAAOqF,IACvBA,EAAMtD,iBACNxG,MAAK22B,IAAqB,GAE1B,MAAMD,EAAQ,IAAI5sB,EAAM+sB,aAAa/gB,OAAOpD,IAAKG,GAASA,EAAKikB,aAE/D92B,KAAKy2B,YAAYC,IAGnBH,IAAuBzsB,IACrBA,EAAMtD,iBACNxG,MAAK22B,IAAqB,IAG5B,GAAAH,GACE1rB,WAAW,KACT,MAAMpI,EAAM1C,KAAK+2B,YACX5R,EAAanlB,KAAKuN,QAAQ,eAChCvN,KAAKg3B,eAAe9R,SAAU,EAEzBxiB,IAEDyiB,GACFA,EAAWnY,aAAa,MAAOtK,GAC/ByiB,EAAW/f,UAEXP,MAAMsI,MAAMzK,KAEb,IACL,CAMA,GAAA0zB,GACEp2B,MAAK+1B,GAAmBp4B,SAASmI,cAAc9F,KAAKm2B,UAC/Cn2B,MAAK+1B,KAEV/1B,MAAK+1B,GAAiB30B,iBACpB,YACApB,MAAKq2B,IAEPr2B,MAAK+1B,GAAiB30B,iBAAiB,OAAQpB,MAAKs2B,IACpDt2B,MAAK+1B,GAAiB30B,iBAAiB,WAAYpB,MAAKu2B,IAC1D,CAKA,WAAAE,CAAYC,GAEV,IAAIO,EAAkB,EACtB,MAAMlE,EAASzqB,EAAe,6BAExB+rB,EAAcqC,EAAMhkB,IAAKkf,IAC7B,MAAMC,EAAU,IAAIqF,eACdvC,EAAa,IAAIhD,GAWvB,OAVAgD,EAAWjC,WAAWd,EAAMC,GAExBkB,EAAOoE,aAAe,EAAIF,GAC5BtC,EAAW3C,OAAQ,EACnB2C,EAAW1C,aAAen0B,EAAU,qCAC3B62B,EAAW3C,QACpBiF,IACAj3B,MAAKo3B,GAAYvF,EAASD,IAGrB+C,IAGT30B,MAAKq3B,GAAgBhD,EACvB,CAOA,GAAA+C,CAAYvF,EAASD,GACnB,MAAM9X,EAAO9Z,KAAK8F,cAAc,QAC1BwxB,EAAW,IAAIC,SAASzd,GAC9Bwd,EAASxjB,IAAI9T,KAAKi2B,UAAUx1B,KAAMmxB,GAClCC,EAAQ5uB,KAAK,OAAQ6W,EAAK0d,QAC1B3F,EAAQ4F,iBAAiB,eAAgBnY,MACzCuS,EAAQ4F,iBAAiB,mBAAoB,kBAC7C5F,EAAQ4F,iBAAiB,SAAU,oBACnC5F,EAAQ6F,KAAKJ,EACf,CAOA,GAAAD,CAAgBhD,GACVr0B,KAAKg3B,iBACPh3B,KAAKg3B,eAAe7E,SACpBnyB,KAAKg3B,eAAez2B,UAEtBP,KAAKg3B,eAAiB,IAAI7C,GAC1Bn0B,KAAKg3B,eAAetE,WAAW2B,GAC/Br0B,KAAKg3B,eAAehqB,aAAa,SAAUhN,KAAK23B,QAChD33B,KAAKg3B,eAAetC,WAAczvB,IAChCjF,KAAKW,cACH,IAAIC,YAAY,kBAAkBqE,IAAU,CAAEpE,SAAS,MAI3Db,MAAK43B,GAAmBv3B,OAAOL,KAAKg3B,eACtC,CAKA,MAAIY,GACF,OAAO53B,MAAK+1B,IAAoBp4B,SAASyC,IAC3C,CAEA,YAAI+1B,GACF,OAAOn2B,KAAKyL,aAAa,WAC3B,CAIA,UAAIksB,GACF,OAAO33B,KAAKyL,aAAa,WAAa,KACxC,CAKA,aAAIwqB,GACF,OAAOj2B,KAAK8F,cAAc,qBAC5B,CAEA,eAAIixB,GACF,OAAO/2B,KAAKyL,aAAa,eAC3B,EAGFK,eAAeC,OAAO,mBAAoB+pB,ICzM1C,MAAM+B,WAAgB1pB,YACpB,iBAAAjE,GACElK,KAAKP,UAAY,mHAGaO,KAAKyL,aAAa,SAAW,+BAG7D,CAEA,QAAIlI,CAAKgH,GACPvK,KAAKyD,UAAU0S,OAAO,UAAW5L,EACnC,EAGFuB,eAAeC,OAAO,kBAAmB8rB,ICVzC,MAAMC,GAAS,SAOR,MAAMC,WAAwB5pB,YACnC,iBAAAjE,GACElK,KAAKugB,OAASvgB,KAAKyL,aAAa,WAChCzL,KAAKkpB,OAASlpB,KAAKqU,aAAa,UAEhCrU,KAAKg4B,cAAc52B,iBAAiB,QAASpB,KAC/C,CAEA,oBAAAyK,GACEzK,KAAKg4B,cAActtB,oBAAoB,QAAS1K,KAClD,CAEA,iBAAMyJ,CAAYK,GACG,UAAfA,EAAMJ,YACF1J,KAAKi4B,kBAAkBnuB,EAEjC,CAEA,uBAAMmuB,CAAkBnuB,GACtBA,EAAMtD,iBACNsD,EAAMmQ,kBAEN,MAAM+d,EAAeluB,EAAMouB,cAC3BF,EAAav4B,UAAY,GACzB,MAAMQ,EAAU,IAAI0F,EAAQ,SAC5B1F,EAAQC,KAAK83B,GAEb,UACQvY,GACJ9W,EAAOwvB,qBAAqBn4B,KAAKugB,QACjC,KACA,8BAGFvgB,KAAKkpB,QAAUlpB,KAAKkpB,OACpBlpB,KAAKopB,gBAAgB,SAAUppB,KAAKkpB,QACpClpB,KAAKo4B,iBACLp4B,KAAKq4B,kBACP,CAAE,MAAO/xB,GACPsB,EAAMtB,EAAMlC,SAAWkC,EAAO,SAC9BtG,KAAKq4B,kBACP,CAAC,QACCp4B,EAAQK,MACV,CACF,CAEA,cAAA83B,GACE,MAAME,EAAoBt4B,KAAK8F,cAC7B,SAAS9F,KAAKugB,mBAEZ+X,GACFA,EAAkB70B,UAAU0S,OAAO,SAAUnW,KAAKkpB,OAEtD,CAEA,gBAAAmP,GACE,GAAIr4B,KAAKg4B,aAAc,CACrB,MAAMtpB,EAAW1O,KAAKkpB,OAAS,gBAAkB,eACjDlpB,KAAKg4B,aAAav4B,UAAY,uBAAuBiP,oBACvD,CACF,CAMA,kBAAA6pB,GACE,MAAMC,EAAgBx4B,KAAK8F,cAAc,gBACzC,IAAK0yB,EAAe,OAEpB,MAAMC,EAAmBz4B,KAAK4kB,aAAe5kB,KAAKkpB,OAElD,GAAIuP,GA9EK,SA8EeD,EAAcE,QAAkB,CAEtD,MAAMhqB,EAAW1O,KAAKkpB,OAAS,gBAAkB,eACjDsP,EAAcG,UAAY,yEACFjqB,sCAIxB1O,KAAKg4B,cAAc52B,iBAAiB,QAASpB,KAC/C,MAAYy4B,GAAoBD,EAAcE,UAAYZ,GAG/CW,GAAoBD,EAAcE,UAAYZ,IAEvD93B,KAAKq4B,mBAHLG,EAAcG,UAAY,mCAK9B,CAEA,eAAI/T,GACF,MAAM0T,EAAoBt4B,KAAK8F,cAC7B,SAAS9F,KAAKugB,mBAEhB,QAAK+X,GAGHA,EAAkB55B,iBAAiB,8BAA8BqF,OACjE,CAEJ,CAEA,gBAAIi0B,GACF,OAAOh4B,KAAK8F,cAAc,qBAC5B,EAGFgG,eAAeC,OAAO,oBAAqBgsB,ICtHpC,MAAMa,WAA8BzqB,YACzC,iBAAAjE,GACElK,KAAK64B,aAAaz3B,iBAAiB,QAASpB,MAAK84B,GACnD,CAEA,oBAAAruB,GACEzK,KAAK64B,aAAanuB,oBAAoB,QAAS1K,MAAK84B,GACtD,CAEAA,IAAYz3B,IACV,MAAM03B,EAAW13B,EAAIK,OACfs3B,EAAO,IAAIC,KACXC,EAAM,IAAID,KACdD,EAAKG,UAAuC,IAA3BH,EAAKI,qBACtBC,cAEEN,EAASlc,SACX7c,KAAKs5B,sBAAsB71B,UAAUlD,OAAO,UAC5CP,KAAKu5B,eAAehvB,MAAQ2uB,EAAIM,UAAU,EAAGN,EAAInR,QAAQ,KAAO,KAEhE/nB,KAAKs5B,sBAAsB71B,UAAUC,IAAI,UACzC1D,KAAKu5B,eAAehvB,MAAQ,IAE9BvK,KAAKy5B,kBAAkBlvB,MAAQ,IAGjC,eAAIsuB,GACF,OAAO74B,KAAK8F,cAAc,eAC5B,CAEA,kBAAIyzB,GACF,OAAOv5B,KAAK8F,cAAc,uBAC5B,CAEA,qBAAI2zB,GACF,OAAOz5B,KAAK8F,cAAc,0BAC5B,CAEA,yBAAIwzB,GACF,OAAOt5B,KAAK8F,cAAc,gCAC5B,EAGFgG,eAAeC,OAAO,kCAAmC6sB,IC1ClD,MAAMc,WAAmBxmB,EAC9B,UAAIqN,GACF,OAAOvgB,KAAKsU,UAAY7L,KAAKC,MAAM1I,KAAKsU,WAAe,QAAItW,CAC7D,CAEA,YAAA2Y,CAAaxE,EAAMuE,GACjB,MAAO,CACLuB,EAAG,CACDC,YAAa/F,KACV1J,KAAKC,MAAM1I,KAAKwU,cAErBkC,KAAMA,EAEV,CAEA,cAAAG,CAAenS,GACb,MAAMyT,EAAOzT,EAASyT,KACtB,MAAO,CACLvB,QAASlS,EAASi1B,MAClBtiB,KAAMc,EAAKzB,KAAOyB,EAAKC,SAAWD,EAAKE,YAE3C,CAEA,MAAAE,CAAO7B,EAAMvE,GACX,MAAO,CACL+G,KAAM,SACNL,QAAS7Y,KAAKsZ,gBAAgB5C,EAAKjW,KAAM0R,GACzC4G,MAAOrC,EAAKkjB,MAAMn5B,KAClBuY,UAAW,CAAE1Z,KAAMoX,EAAKmZ,SAAUxW,SAAU,QAC5CJ,eAAgB,CAAEG,MAAO1C,EAAKmjB,eAElC,CAEA,cAAAphB,CAAe/B,GACb,MAAO,CACLwC,KAAM,SACNL,QAASnC,EAAKpX,MAAQoX,EAAKjW,KAC3BuY,UAAW,CAAE1Z,KAAMoX,EAAKmZ,SAAUxW,SAAU,QAEhD,EAGFvN,eAAeC,OAAO,sBAAuB2tB,ICzCtC,MAAMI,WAAuB3rB,YAClC4rB,IAEA,iBAAA7vB,GACElK,KAAKoB,iBAAiB,SAAUpB,MAAKg6B,IACrCh6B,KAAKoB,iBAAiB,QAASpB,MAAKi6B,IAEpCj6B,MAAK+5B,GAAmBp8B,SAASmI,cAAc,wBAC/C9F,MAAK+5B,IAAkB34B,iBAAiB,QAASpB,MAAKk6B,GACxD,CAEA,oBAAAzvB,GACEzK,KAAK0K,oBAAoB,SAAU1K,MAAKg6B,IACxCh6B,KAAK0K,oBAAoB,QAAS1K,MAAKi6B,IACvCj6B,MAAK+5B,IAAkBrvB,oBAAoB,QAAS1K,MAAKk6B,GAC3D,CAEAA,IAAqBpwB,IACnBA,EAAMtD,iBAENxG,MAAK+5B,GAAiBt2B,UAAU0S,OAAO,UAEvC,MAAMgkB,EAAQn6B,MAAK+5B,GAAiBt2B,UAAU8a,SAAS,UAEvDve,MAAKo6B,GAAkBD,GAEvBn6B,KAAKq6B,kBAAkB52B,UAAU0S,OAAO,UAAWgkB,IAIrDH,IAAqBlwB,IACnB,MAAMivB,EAAWjvB,EAAMpI,OAAO6L,QAAQ,8BAEjCwrB,IAIL/4B,KAAKq6B,kBAAkB52B,UAAU0S,OAC/B,SAC8B,IAA9BnW,KAAKs6B,cAAcv2B,QAErBg1B,EAASl3B,cAAc4B,UAAU0S,OAAO,UAAW4iB,EAASlc,WAI9Dod,IAAwBnwB,IACtB,MAAMmjB,EAAOnjB,EAAMpI,OAAO6L,QAAQ,4BAE7B0f,IAILnjB,EAAMtD,iBAENY,EAAWpH,MAAKu6B,GAAyBtN,EAAKb,MAAO,CACnDlqB,MAAO+qB,EAAK/qB,MACZpC,KAAM,cAIV,GAAAs6B,CAAkBD,GAChBn6B,KAAKtB,iBACH,+CACAJ,QAASy6B,IACTA,EAASlc,QAAUsd,EACnBpB,EAASxrB,QAAQ,sBAAsB9J,UAAU0S,OAAO,SAAUgkB,IAEtE,CAEA,GAAAI,CAAyBnO,GACvB,MAAM1pB,EAAM,IAAIsU,IAAIoV,GAMpB,OAJApsB,KAAKs6B,cAAch8B,QAASi3B,GAC1B7yB,EAAIwU,aAAa7W,OAAOk1B,EAAM90B,KAAM80B,EAAMhrB,QAGrC7H,EAAIkd,UACb,CAEA,iBAAI0a,GACF,OAAOt6B,KAAKtB,iBAAiB,gBAC/B,CAEA,qBAAI27B,GACF,OAAOr6B,KAAK8F,cAAc,uBAC5B,EAGFgG,eAAeC,OAAO,0BAA2B+tB,IC3FjD,MAAMU,WAAiCrsB,YACrC,iBAAAjE,GACElK,KAAKoB,iBAAiB,SAAUpB,KAClC,CAEA,oBAAAyK,GACEzK,KAAK0K,oBAAoB,SAAU1K,KACrC,CAEA,WAAAyJ,CAAYK,GACV,GACO,WADCA,EAAMJ,KAEV1J,KAAK2T,UAGX,CAEA,QAAAA,GACE,MAAMjR,EAAM,IAAIsU,IAAIhX,KAAKyL,aAAa,QAChCgvB,EAASz6B,KAAK8F,cAAc,UAClCpD,EAAIwU,aAAapD,IAAI,cAAe2mB,EAAOlwB,OAC3C1F,MAAMsI,MAAMzK,EAAK,CAAEqe,MAAO,wBAC5B,EAGFjV,eAAeC,OACb,qCACAyuB,ICjBK,MAAME,WAAsBvsB,YACjC,iBAAAjE,GACElK,KAAKkqB,cAAgBlqB,KAAK8F,cAAc,oBACxC9F,KAAKmqB,cAAgBnqB,KAAK8F,cAAc,oBACxC9F,KAAK26B,eAAiB36B,KAAK8F,cAAc,qBACzC9F,KAAK46B,gBAAkB56B,KAAK8F,cAAc,sBAC1C9F,KAAK66B,kBAAoB76B,KAAK8F,cAAc,wBAC5C9F,KAAKoqB,MAAQpqB,KAAK8F,cAAc,OAChC9F,KAAK86B,iBAAmB96B,KAAK8F,cAAc,6BAC3C9F,KAAK+6B,aAAe/6B,KAAK8F,cAAc,wBACvC9F,KAAKg7B,SAAWh7B,KAAK8F,cAAc,cAEnC9F,KAAKi7B,WAAaj7B,KAAK46B,gBAAgBz7B,QAAQ87B,WAC/Cj7B,KAAKk7B,UAAYl7B,KAAK26B,eAAepwB,MAIrCvK,KAAKqrB,OC3BM,SAAU8P,EAAMC,GAC7B,IAAIC,EAEJ,OAAO,YAAaC,GAClB,MAAMC,EAAOv7B,KAEbwL,aAAa6vB,GACbA,EAAUvwB,WAAW,IAAMqwB,EAAK3qB,MAAM+qB,EAAMD,GAAOF,EACrD,CACF,CDkBkBI,CAAS,KACrBx7B,KAAKy7B,cACLz7B,KAAK07B,kBAvBU,KA0BjB17B,KAAK+6B,cAAc35B,iBAAiB,QAASpB,KAAK27B,aAElD37B,KAAK47B,SAAW,IAAIC,iBAAiB77B,KAAK87B,kBAC1C97B,KAAK47B,SAASG,QAAQ/7B,KAAKkqB,cAAe,CAAE1K,YAAY,IACxDxf,KAAK47B,SAASG,QAAQ/7B,KAAKmqB,cAAe,CAAE3K,YAAY,IACxDxf,KAAK47B,SAASG,QAAQ/7B,KAAK26B,eAAgB,CAAEnb,YAAY,GAC3D,CAEA,oBAAA/U,GACEzK,KAAK47B,SAASI,YAChB,CAEAF,iBAAoBG,IAClB,IAAK,MAAMC,KAAYD,EACjB,cAAeC,EAASx6B,OAAOvC,UACjCa,KAAKkqB,cAAc3f,MAAQ,GAC3BvK,KAAKmqB,cAAc5f,MAAQ,GAC3BvK,KAAKk7B,UAAYgB,EAASx6B,OAAO6I,OAEnCvK,KAAKqrB,UAIT,WAAAoQ,GACOz7B,KAAKk7B,YAEVl7B,KAAK86B,iBAAiBhpB,SAAU,EAChClN,GAAI+D,EAAOwzB,uBAAuBn8B,KAAKk7B,WAAY,CACjDkB,KAAMp8B,KAAKq8B,oBACXC,UAAWt8B,KAAKirB,SAChBsR,UAAWv8B,KAAKmrB,SAChBqR,SAAS,EACT18B,KAxDiB,YA0DhB0E,KAAK,EAAGc,WACPtF,KAAK86B,iBAAiBjwB,IAAMvF,EAAK5C,IACjC1C,KAAK86B,iBAAiB1Q,MAAMqS,IAAMn3B,EAAKm3B,IACvCz8B,KAAK86B,iBAAiB1Q,MAAMloB,MAAQoD,EAAKpD,MACzClC,KAAK8tB,oBAEN3oB,MAAOmB,IACN7I,QAAQ6I,MAAMA,EAAMlC,SAAWkC,GAC/BsB,EAAMtB,EAAMlC,SAAWkC,EAAO,WAEpC,CAEAq1B,YAAc,KACZ37B,KAAK86B,iBAAiBr7B,UAxEA,uDAyEtBO,KAAK26B,eAAepwB,MAAQ,GAC5BvK,KAAKoqB,MAAQ,KACbpqB,KAAKg7B,SAASv3B,UAAUC,IAAI,YAC5B1D,KAAK8tB,mBAGP,eAAAA,GACE9tB,KAAKuN,QAAQ,mBAAmBC,SAASxN,KAC3C,CAEA,cAAA07B,GACO17B,KAAKk7B,WAAcl7B,KAAKq8B,sBAE7Br8B,KAAKg7B,SAASv3B,UAAUlD,OAAO,YAE3BP,KAAKg7B,SAAS5O,KAAK9Z,MAAM,oBAC3BtS,KAAKg7B,SAAS5O,KAAOpsB,KAAKg7B,SAAS5O,KAAKluB,QACtC,mBACA,KAAO8B,KAAKk7B,WAGdl7B,KAAKg7B,SAAS5O,KAAOpsB,KAAKg7B,SAAS5O,KAAO,eAAepsB,KAAKk7B,YAElE,CAEA,YAAIjQ,GACF,MAAiC,KAA7BjrB,KAAKkqB,cAAc3f,MACdvK,KAAK08B,gBAAgB3pB,KAAK,KAE5B/S,KAAKkqB,cAAc3f,KAC5B,CAEA,YAAI4gB,GACF,MAAiC,KAA7BnrB,KAAKmqB,cAAc5f,MACdvK,KAAK28B,gBAAgB5pB,KAAK,KAE5B/S,KAAKmqB,cAAc5f,KAC5B,CAEA,mBAAIoyB,GACF,IAAK38B,KAAKq8B,oBAAqB,MAAO,GAEtC,MAAMO,EAAO58B,KAAKi7B,WAAW39B,MAAM,KAAKoV,IAAKmqB,GAAMh6B,SAASg6B,IACtDC,GE3HeC,EF4HnBH,EAAK,GAAK58B,KAAKg9B,eE5HOC,EF6HtBL,EAAK,GAAK58B,KAAKk9B,gBE5HZH,GAAKE,EAAIF,EAAIE,GADP,IAAUF,EAAGE,EFgIxB,MAAO,CAACjsB,KAAK2X,MAAMiU,EAAK,GAAKE,GAAO9rB,KAAK2X,MAAMiU,EAAK,GAAKE,GAC3D,CAEA,mBAAIJ,GACF,IAAK18B,KAAKq8B,oBAAqB,MAAO,GAEtC,MAAMc,EAAan9B,KAAK28B,gBAExB,MAAO,CACL3rB,KAAK2X,OAAO3oB,KAAKg9B,eAAiBG,EAAW,IAAM,GACnDnsB,KAAK2X,OAAO3oB,KAAKk9B,gBAAkBC,EAAW,IAAM,GAExD,CAEA,kBAAIH,GACF,OAAOn6B,SAAS7C,KAAK26B,eAAex7B,QAAQ69B,eAC9C,CAEA,mBAAIE,GACF,OAAOr6B,SAAS7C,KAAK26B,eAAex7B,QAAQ+9B,gBAC9C,CAEA,uBAAIb,GACF,MAAqD,SAA9Cr8B,KAAK46B,gBAAgBz7B,QAAQi+B,YACtC,EAGFtxB,eAAeC,OAAO,yBAA0B2uB,IGtJjC,MAAM2C,WAAyBlvB,YAC5C,WAAAtO,GACEwO,QAEArO,KAAKC,QAAU,IAAI0F,EAAQ,QAC7B,CAEA,WAAA8D,CAAYpI,GACV,OAAQA,EAAIqI,MACV,IAAK,OACH1J,MAAKs9B,KACL,MACF,IAAK,QACHt9B,MAAKu9B,GAASl8B,GAKpB,CAEA,iBAAA6I,GACElK,KAAKyD,UAAUC,IAAI,wBACf1D,KAAK6K,MAAQ7K,KAAKoqB,OACpBpqB,KAAKw9B,QAEPx9B,MAAKy9B,IACP,CAEA,oBAAAhzB,GACEzK,KAAKoqB,OAAO1f,oBAAoB,OAAQ1K,MACxCA,KAAKoqB,OAAO1f,oBAAoB,QAAS1K,MACzCA,KAAKM,MACP,CAEA,WAAAo9B,CAAY7yB,EAAM7K,KAAK6K,IAAK4xB,EAAMz8B,KAAKS,MACrCT,KAAKoqB,MAAQ,IAAImI,MACjBvyB,KAAKoqB,MAAMvf,IAAMA,EACb4xB,IACFz8B,KAAKoqB,MAAMqS,IAAMA,EAErB,CAEA,KAAAe,CAAM3yB,GACJ7K,KAAK09B,YAAY7yB,GACjB7K,KAAKoqB,MAAMhpB,iBAAiB,OAAQpB,MACpCA,KAAKoqB,MAAMhpB,iBAAiB,QAASpB,MACrCA,KAAK2D,MACP,CAEA,IAAAA,GACM3D,KAAKoqB,OAAOuT,WAGhB39B,KAAKgN,aAAa,UAAW,WAC7BhN,KAAKP,UAAY,GACjBO,KAAKC,QAAQC,KAAKF,MACpB,CAEA,IAAAM,GACEN,KAAKyD,UAAUlD,OAAO,WACtBP,KAAKC,QAAQK,MACf,CAEA,GAAAg9B,GACEt9B,KAAKC,QAAQK,OACbN,KAAKua,gBAAgB,UACvB,CAEA,GAAAgjB,CAASl8B,GACP,MAAM+C,EAAU,kBAAkBpE,KAAKoqB,MAAMvf,MACvC+yB,EAAQ59B,KAAKuN,QAAQ,sBAC3BvN,KAAKC,QAAQK,OACbN,KAAKP,UAAY,gCACQ2E,MAAYw5B,EAAQ,QAAU,mGAIvDngC,QAAQ6I,MAAMlC,EAAS/C,EACzB,CAEA,GAAAo8B,GACMz9B,KAAKoqB,OAAOuT,SACd39B,KAAK2N,gBAAgB3N,KAAKoqB,OACjBpqB,KAAKoqB,OACdpqB,KAAKK,OAAOL,KAAKoqB,MAErB,CAEA,WAAItY,CAAQvH,GACVA,EAAQvK,KAAK2D,OAAS3D,KAAKM,MAC7B,CAEA,OAAIuK,CAAIA,GACN7K,KAAKw9B,MAAM3yB,GACX7K,MAAKy9B,IACP,CAEA,QAAIh9B,GACF,OAAOT,KAAKyL,aAAa,OAC3B,CAEA,OAAIZ,GACF,OAAO7K,KAAKyL,aAAa,MAC3B,EAGFK,eAAeC,OAAO,4BAA6BsxB,IC/GnD,MAAMQ,WAA0B1vB,YAC9B,iBAAAjE,GACElK,KAAKoB,iBAAiB,SAAUpB,MAChCrC,SAASyD,iBAAiB,qBAAsBpB,KAClD,CAEA,oBAAAyK,GACEzK,KAAK0K,oBAAoB,SAAU1K,MACnCrC,SAAS+M,oBAAoB,qBAAsB1K,KACrD,CAEA,WAAAyJ,CAAYK,GACV,OAAQA,EAAMJ,MACZ,IAAK,qBACH1J,KAAK89B,UAAUh0B,EAAMpJ,QACrB,MACF,IAAK,SACHV,KAAKwR,OAAOM,SAAU,EAG5B,CAEA,SAAAgsB,CAAUp9B,GACRV,KAAKwR,OAAOusB,QAAU,UACtB/9B,KAAKwR,OAAOgL,UAAW,EACvBxc,KAAKshB,QAAQ5hB,QAAUgB,EAAO4gB,OAChC,CAEA,UAAI9P,GACF,OAAOxR,KAAK8F,cAAc,YAC5B,CAEA,WAAIwb,GACF,OAAOthB,KAAK8F,cAAc,aAC5B,EAGFgG,eAAeC,OAAO,8BAA+B8xB,IC1B9C,MAAMG,WAAuB7vB,YAClC,iBAAAjE,GACElK,KAAKi+B,YAActgC,SAASmI,cAAc,uBAC1C9F,KAAKonB,YAAczpB,SAASmI,cAAc,uBAC1C9F,KAAKk+B,cAAgBvgC,SAASmI,cAAc,uBAE5C9F,KAAKm+B,cAGL36B,sBAAsB,KACpBxD,KAAKo+B,mBAIPp+B,KAAK47B,SAAW,IAAIC,iBAAkBwC,IACpCA,EAAU//B,QAAS49B,IACjBA,EAASoC,WAAWhgC,QAASyqB,IACvBA,EAAKwV,WAAaC,KAAKC,eAGvB1V,EAAKtlB,WAAW8a,SAAS,aAC3Bve,KAAK0+B,cAAc3V,GAKrBA,EACGrqB,iBAAiB,aACjBJ,QAASO,GAAOmB,KAAK0+B,cAAc7/B,WAM5CmB,KAAK47B,SAASG,QAAQ/7B,KAAM,CAC1B2+B,WAAW,EACXC,SAAS,GAEb,CAEA,oBAAAn0B,GACEzK,KAAK6+B,iBACL7+B,KAAK47B,UAAUI,YACjB,CAEA,WAAAmC,GACEn+B,KAAKi+B,aAAa78B,iBAAiB,QAASpB,MAC5CA,KAAKonB,aAAahmB,iBAAiB,QAASpB,KAC9C,CAEA,cAAA6+B,GACE7+B,KAAKi+B,aAAavzB,oBAAoB,QAAS1K,MAC/CA,KAAKonB,aAAa1c,oBAAoB,QAAS1K,KACjD,CAEA,WAAAyJ,CAAYK,GACS,UAAfA,EAAMJ,MAAoBI,EAAMpI,SAAW1B,KAAKi+B,YAClDj+B,KAAK8+B,aAAah1B,GACM,UAAfA,EAAMJ,MAAoBI,EAAMpI,SAAW1B,KAAKonB,aACzDpnB,KAAK++B,kBAAkBj1B,EAE3B,CAEA,YAAAg1B,CAAah1B,GACX,MAAMqI,EAAOrI,EAAMpI,OAAO6I,MAAM4U,cAAc6f,OAEjC,KAAT7sB,EAKJnS,KAAKi/B,YAAY9sB,GAJfnS,KAAKk/B,aAKT,CAEA,WAAAD,CAAY9sB,GACV,MAAMgtB,EAAWn/B,KAAKtB,iBAAiB,iBACvC,IAAI0gC,EAAa,EACbC,EAAa,KAEjBF,EAAS7gC,QAASghC,KACCA,EAAY7zB,aAAa,SAAW,IAExC0T,cAAcxd,SAASwQ,IAClCmtB,EAAY77B,UAAUC,IAAI,aAC1B47B,EAAY77B,UAAUlD,OAAO,YAC7B6+B,IACKC,IAAYA,EAAaC,KAE9BA,EAAY77B,UAAUlD,OAAO,aAC7B++B,EAAY77B,UAAUC,IAAI,eAMX,IAAf07B,GACFp/B,KAAKk+B,cAAcv3B,YAAc,KAAK7I,EAAU,gBAChDkC,KAAKk+B,cAAc14B,MAAMC,QAAU,SAC1B25B,EAAa,GACtBp/B,KAAKk+B,cAAcv3B,YAAc,GAAGy4B,KAActhC,EAAU,iBAC5DkC,KAAKk+B,cAAc14B,MAAMC,QAAU,SAEnCzF,KAAKk+B,cAAc14B,MAAMC,QAAU,OAIjC45B,GACFA,EAAWpc,eAAe,CAAEC,SAAU,SAAUgF,MAAO,UAE3D,CAEA,WAAAgX,GACmBl/B,KAAKtB,iBAAiB,iBAC9BJ,QAASghC,IAChBA,EAAY77B,UAAUlD,OAAO,YAAa,cAG5CP,KAAKk+B,cAAc14B,MAAMC,QAAU,MACrC,CAEA,iBAAAs5B,CAAkBj1B,GAChBA,EAAMtD,iBACNxG,KAAKi+B,YAAY1zB,MAAQ,GACzBvK,KAAKk/B,aACP,CAEA,aAAAR,CAAca,GACZ,IAAIpP,EAASoP,EAAW,CACtBhiC,MAAO,QACP6yB,UAAW,IACXC,gBAAgB,EAChBC,cAAe,IACfC,OAAQ,oBACRE,UAAW,oBACXC,MAAQrvB,GAAQrB,KAAK2wB,WAAWtvB,IAEpC,CAEA,cAAA+8B,GACoBp+B,KAAKtB,iBAAiB,aAC9BJ,QAASO,GAAOmB,KAAK0+B,cAAc7/B,GAC/C,CAEA,gBAAM8xB,CAAWtvB,GAEf,GAAIA,EAAIG,OAASH,EAAIuvB,IAAMvvB,EAAIwvB,WAAaxvB,EAAIyvB,SAC9C,OAIF,MAAM0O,EAAWn+B,EAAIwR,KACf0N,EAASif,EAASjf,OAClB7d,EAAMiG,EAAO82B,qBAAqBlf,GAClCjb,EAAO,CACX0rB,iBAAkB3vB,EAAIuvB,GAAGzxB,QAAQugC,SACjCxO,aAAc7vB,EAAIyvB,UAGpB/oB,GAAkB,GAElB,IACE,MAAMrD,QAAiB+a,GAAM/c,EAAK4C,GAC5Bq6B,QAAiBj7B,EAASY,KAG1Bs6B,EAASJ,EAAS15B,cAAc,SAASya,KAC/C,GAAIqf,EAAQ,CACV,MAAMC,EAAYD,EAAO95B,cAAc,gBACnC+5B,GAAaF,EAAS9P,WACxBgQ,EAAUl5B,YAAcg5B,EAAS9P,SAErC,CAGA7vB,KAAK8/B,kBAAkBz+B,EAAIG,KAAMH,EAAIuvB,IAErChpB,EAAM9J,EAAU,2BAClB,CAAE,MAAOwI,GACPsB,EAAMtB,EAAMlC,SAAWkC,EAAO,SAE9BpJ,OAAO+Z,SAAS7R,QAClB,CAAC,QACC2C,GAAkB,EACpB,CACF,CAEA,iBAAA+3B,CAAkBC,EAAeC,GAE/B,MAAMC,EAAaF,EAAcxyB,QAAQ,qBAIzC,GAHA0yB,GAAY1H,qBAGRwH,IAAkBC,EAAa,CACjC,MAAME,EAAWF,EAAYzyB,QAAQ,qBACrC2yB,GAAU3H,oBACZ,CACF,EAGFzsB,eAAeC,OAAO,kBAAmBiyB,IC3MzC,MAAMmC,GAAmB,CACvB1P,UAAW,kBACXF,OAAQ,4BACR6P,WAAY,UACZhQ,UAAW,IACXE,cAAe,IACf+P,OAAQ,4BAGV,SAASC,GAAQx2B,GACf,MAAMrJ,EAAOqJ,EAAM+I,KAAK1T,QAAQwlB,YAChChnB,SACGe,iBAAiB,8BAA8B+B,OAC/CnC,QAAS63B,GAAaA,EAAS1yB,UAAUC,IAAI,sBAClD,CAEA,SAAS68B,GAAOz2B,GACd,MAAM+I,EAAO/I,EAAM+I,KACbhR,EAAgBiI,EAAM8mB,GAAG/uB,cAAc0L,QAAQ,mBAC/CiF,EAAS,CACblL,WAAYuL,EAAK1T,QAAQyiB,UACzB8G,SAAU5e,EAAMgnB,SAAW,GAGzBjvB,IACF2Q,EAAOguB,kBAAoB3+B,EAAc1C,QAAQyiB,WAM/C9X,EAAMpI,SAAWoI,EAAM8mB,IACzBlR,GAAK/W,EAAO83B,0BAA2BjuB,GAAQhO,KAAME,IACnD,MAAMY,EAAOZ,EAASY,KACtBsC,EAAMtC,EAAKlB,SACPkB,EAAKuc,2BACPR,GAAuB/b,GAEzBuG,IACAgH,EAAKyR,YAAYhf,EAAKo7B,eAG5B,CAEA,SAAShQ,KACW/yB,SAASe,iBAAiB,6BAClCJ,QAAS63B,GACjBA,EAAS1yB,UAAUlD,OAAO,sBAE9B,CAEA,MAAMogC,WAAyBxyB,YAC7B,iBAAAjE,GACE,MAAM3M,EAAQ,CACZkD,KAAMT,KAAKb,QAAQwlB,YACnBic,IAAG,CAAChQ,EAAIiQ,EAAOhuB,IACN+d,EAAG/xB,GAAGM,QAAQ2hC,kBAClBxjC,MAAM,KACNqE,SAASkR,EAAK1T,QAAQwlB,cAG7B,IAAIwL,EAASnwB,KAAM,IACdmgC,GACHG,WACAC,UACA7P,SACAnzB,SAEJ,EAGFuO,eAAeC,OAAO,4BAA6B40B,IC9EnD,MAAMh7B,WAAgBwI,YACpB,iBAAAjE,GACElK,KAAKoP,UAAY,oBAAoBpP,KAAKF,OAC1CE,KAAKP,UAAY,yEACiDO,KAAKD,4eAezE,CAEA,QAAID,GACF,OAAOE,KAAKyL,aAAa,SAAW,QACtC,CAEA,SAAI1L,GACF,OAAOC,KAAKyL,aAAa,UAAY,cACvC,EAGFK,eAAeC,OAAO,kBAAmBpG,IClBlC,MAAMo7B,WAAyB5yB,YACpCW,GAAa,KAEb,iBAAA5E,GACElK,KAAKyD,UAAUC,IAAI,yBAGnB1D,KAAK6R,MAAMpO,UAAUC,IAAI,qBACzB1D,MAAK8O,EAAa,IAAI0E,EAAUxT,KAAK6R,MAAO7R,KAAKgc,SACnD,CAEA,oBAAAvR,GACEuE,EAAkBhP,MAAK8O,GACvB9O,MAAK8O,EAAa,IACpB,CAEA,KAAAhQ,GACE+P,EAAe7O,MAAK8O,EAAY,IAAMT,MAAMvP,QAC9C,CAEA,SAAI+S,GACF,OAAO7R,KAAKghC,qBAAqB,SAAS,EAC5C,CAEA,YAAIhlB,GACF,MAAO,CACLpH,QAAS,CACPE,cAAexD,KAGjB0D,WAAY,KAEZisB,UAAW,IAEXC,QAAQ,EAKR,YAAAC,CAAatvB,GACX,OAAI7R,KAAK8R,UACF9R,KAAKgc,SAASolB,aAAephC,KAAK2C,QAAQkP,GACnD,EAEAwvB,SAAS,EAGTC,kBAAkB,EAElBnsB,WAAY,KACZK,aAAa,EAEbjB,YAAavU,KAAKyL,aAAa,eAE/B9H,KAAM,CAACgS,EAAOzU,KACZ0D,GAAI5E,KAAKyL,aAAa,OAAQ,CAAE0G,KAAMwD,IACnCnR,KAAME,GAAaxD,EAASwD,EAASY,OACrCH,MAAOmB,IACNsB,EAAMtB,EAAMlC,SAAWkC,EAAO,SAE9BpF,UAGHgO,IACHX,OAAQ,IACHmD,GAGT,EAGF5F,eAAeC,OAAO,4BAA6Bg1B,IC/EnD,MAAMQ,GAAa,eACbC,GAAc,UAEb,MAAMC,WAAgBtzB,YAC3BuzB,IAAc,KAKd,iBAAAx3B,GACElK,KAAKoP,UAAY,oBAGZpP,KAAK8F,cAAc,6BACtB9F,KAAK2pB,mBACH,YACA,oDAKJ3pB,KAAKwF,MAAMqB,UAAY,GAAG7G,KAAK6G,cAC/B7G,KAAKujB,OAAO/d,MAAMC,QAAU,OAE5B,MAUM9C,EAAU,CACdg/B,KAAMhkC,SAASuG,eAAe,gBAC9B09B,WAAY,MACZC,UAAW,CAAC,MAGd7hC,KAAK8hC,4BAA8B,IAAIC,qBAhBd,CAAC/4B,EAAS4yB,KACjC5yB,EAAQ1K,QAASi3B,IACXA,EAAMyM,kBAAoB,IAC5BhiC,KAAKiiC,qBAELrG,EAASsG,UAAU3M,EAAM7zB,YAa7BiB,GAEF3C,KAAK8hC,4BAA4B/F,QAAQ/7B,MAGzCA,KAAKmiC,2BACP,CAKA,oBAAA13B,GACEzK,KAAK8hC,6BAA6B9F,aAGlCh8B,KAAKoiC,6BAELC,QAAQz9B,IAAI5E,KAAKsiC,WAAW/hC,OAAOP,KAAKsiC,SAC1C,CAMA,kBAAAL,GACEI,QAAQ38B,KAAK1F,KAAKuiC,eAAe/9B,KAAMg+B,IACrCA,EAAQlkC,QAASilB,GAAWvjB,KAAKyiC,aAAalf,KAElD,CAOA,YAAAkf,CAAalf,GAIXA,EAAOhgB,OAGP,MAAMtD,EAAUD,KAAKghC,qBAAqB,mBAAmB,GACzD/gC,GACFA,EAAQM,SAINP,KAAK0hB,gBACP6B,EAAOziB,GAAG,QAAUO,IAClBrB,KAAK0hB,cAAclU,SAASnM,EAAIK,OAAOghC,mBAEzCnf,EAAOziB,GAAG,QAAS,IAAMd,KAAK0hB,cAAcY,gBAAe,IAE/D,CAMA,yBAAA6f,GACEniC,KAAK2iC,mBAAqBzlC,OAAO0lC,WAAW,gCAC5C5iC,KAAK6iC,mBAAsB/4B,GAAU9J,KAAK8iC,mBAAmBh5B,GAC7D9J,KAAK2iC,mBAAmBvhC,iBAAiB,SAAUpB,KAAK6iC,mBAC1D,CAMA,0BAAAT,GACMpiC,KAAK2iC,oBAAsB3iC,KAAK6iC,oBAClC7iC,KAAK2iC,mBAAmBj4B,oBACtB,SACA1K,KAAK6iC,mBAGX,CAOA,kBAAAC,CAAmBh5B,GACjB,MAAMyZ,EAAS8e,QAAQz9B,IAAI5E,KAAKsiC,UAChC,GAAI/e,EAAQ,CACV,MAAMwf,EAAOj5B,EAAM7B,QAAUs5B,GAAaC,GACpCwB,EAAcl5B,EAAM7B,QAAUs5B,GAAaC,GAGjDje,EAAOhjB,SACP8hC,QACG38B,KAAK,CACJs9B,iBACGhjC,KAAKuiC,cACRQ,SAEDv+B,KAAMg+B,IACLA,EAAQlkC,QAASilB,GAAWvjB,KAAKyiC,aAAalf,KAEpD,CACF,CAEA,iBAAIgf,GACF,MAAMU,EAAe,CAAA,EAGrBjjC,KAAKkjC,oBAAoB5kC,QAAS6kC,IAChC,IAAK,CAAC,QAAS,KAAM,KAAM,OAAQ,SAASxhC,SAASwhC,GAAgB,CACnE,MAAMpQ,EAAS/yB,KAAKyL,aAAa03B,GAC3BrmC,EAAMqmC,EAAcC,WAAW,IAAK,KAG1C,GAAIrQ,IAAWoQ,GAA4B,KAAXpQ,EAC9BkQ,EAAanmC,IAAO,OAEpB,IACEmmC,EAAanmC,GAAO2L,KAAKC,MAAMqqB,EACjC,CAAE,MAAOxsB,GAEP08B,EAAanmC,GAAOi2B,CACtB,CAEJ,IAGF,MAAMA,EAAS,CACbiQ,YAAahjC,KAAKqjC,kBACf/6B,EAAe,+BACf26B,EACHK,SAAUtmC,IACV+mB,SAAU,IAAI/jB,KAAKsiC,WACnBS,KAAM/iC,KAAKqjC,gBAQb,OAFAtQ,EAAOjwB,OAASiwB,EAAO2O,WAEhB3O,CACT,CAEA,kBAAIsQ,GACF,OAAOnmC,OAAO0lC,WAAW,gCAAgC36B,QACrDs5B,GACAC,EACN,CAEA,YAAIc,GACF,OAAOtiC,KAAKujB,OAAOhb,EACrB,CAEA,UAAIgb,GACF,OAAOvjB,KAAKghC,qBAAqB,YAAY,EAC/C,CAEA,iBAAItf,GACF,OAAO/jB,SACJuG,eAAelE,KAAKsiC,UACpB/0B,QAAQ,yBACb,CAEA,aAAI1G,GACF,OAAO7G,MAAK0hC,IAAe1hC,KAAKuiC,cAAcb,UAChD,CAEA,aAAI76B,CAAU0D,GACZvK,MAAK0hC,GAAcn3B,CACrB,EAGFuB,eAAeC,OAAO,kBAAmB01B,ICzNzC,MAAM8B,WAAoBp1B,YACxB,uBAAMjE,GACJ,MAAMjK,EAAU,IAAI0F,EAAQ,SAC5B1F,EAAQC,KAAKF,MAEb,IACE,MAAM0E,QAAiBJ,MAAMtE,KAAK0C,IAAK,CAAE8gC,YAAa,YAChDC,QAAqB/+B,EAAS4S,OAEhC5S,EAASK,GACX/E,KAAK0jC,WAAWD,GAEhBzjC,KAAK2jC,UAAUj/B,EAEnB,CAAE,MAAO4B,GACPtG,KAAK2jC,UAAUr9B,EACjB,CAAC,QACCrG,EAAQK,MACV,CACF,CAEA,OAAIoC,GACF,OAAO1C,KAAKyL,aAAa,MAC3B,CAEA,UAAAi4B,CAAWD,GACqB,QAA1BA,EAAqB,OACvBzjC,KAAK8F,cAAc,qBAAqBrC,UAAUlD,OAAO,UAEzDP,KAAK8F,cAAc,eAAerC,UAAUlD,OAAO,SAEvD,CAEA,SAAAojC,CAAUr9B,GACRtG,KAAK8F,cAAc,UAAUrC,UAAUlD,OAAO,UAC9C9C,QAAQ6I,MAAM,yCAA0CA,EAC1D,EAGFwF,eAAeC,OAAO,uBAAwBw3B,ICtC9CK,EAAoB,eAAgB,CAClCC,UAAW,CACT,CAAEC,UAAW,mBAAoBC,QAAS,KAC1C,CAAED,UAAW,gBAAiBC,QAAS,MAEzCphC,QAAS,CACPqhC,SAAU,OAIdJ,EAAoB,eAAgB,CAClCC,UAAW,CACT,CAAEC,UAAW,gBAAiBC,QAAS,KACvC,CAAED,UAAW,mBAAoBC,QAAS,MAE5CphC,QAAS,CACPqhC,SAAU,OAKdJ,EAAoB,cAAe,CACjCC,UAAW,CACT,CAAEC,UAAW,cAAeC,QAAS,KACrC,CAAED,UAAW,WAAYC,QAAS,MAEpCphC,QAAS,CACPqhC,SAAU,OAIdJ,EAAoB,cAAe,CACjCC,UAAW,CACT,CAAEC,UAAW,WAAYC,QAAS,KAClC,CAAED,UAAW,cAAeC,QAAS,MAEvCphC,QAAS,CACPqhC,SAAU,OAId,MAAM11B,GAAY3Q,SACfmI,cAAc,mCACd2F,aAAa,QAEVw4B,GAAU,CACd,OAAQ,QACRC,MAAO,gBAGHvhC,GAAU,CACdwhC,SAAW1jC,GAAS,GAAG6N,SAAgB21B,GAAQxjC,IAASA,SACxD2jC,QAAUC,IACRA,EAAIr3B,aAAa,OAAQ,gBACzBq3B,EAAIr3B,aAAa,UAAW,cAE9Bs3B,aAAa,GAGfC,EAAoB,UAAW5hC,IAC/B4hC,EAAoB,SAAU5hC,SCxCA,IAAnBzF,OAAOC,UAChBD,OAAOC,QAAU,CAAA,GAInB4L,OAAO6G,OAAOzS,QAAS,CACrBgK,qBACAD,gBACAmB,EAAGvK,EACH0mC,gBACA58B,QACAglB,WtEVK,cAAyBrqB,EAC9BkiC,IAEA,WAAA5kC,CAAYotB,GACV,MAAMvqB,EAAM,IAAIsU,IAAIrO,EAAO+7B,sBAAuBxnC,OAAO+Z,UACnD0tB,EAAmB,CACvBjiC,IAAKuqB,EAAKvqB,IACVkiC,aAAc3X,EAAKvjB,KACnBm7B,WAAY5X,EAAK/qB,MACjB4iC,YAAa7X,EAAKvrB,QAIpBqH,OAAO1L,KAAKsnC,GAAkBrmC,QAASxB,IACjC6nC,EAAiB7nC,IACnB4F,EAAIwU,aAAapD,IAAIhX,EAAK6nC,EAAiB7nC,MAI/CuR,MAAM3L,EAAI0pB,KAAM,CACdtsB,KAAM,UACNoC,MAAOpE,EAAU,SAErB,CAKA,OAAAI,CAAQoH,GAEN+I,MAAMnQ,QAAQoH,GACdtF,MAAKmK,GACP,CAOA,IAAAlH,GAEE,OADAoL,MAAMpL,OACC,IAAI8H,QAASC,GAAahL,MAAKykC,GAAgBz5B,EACxD,CAKA,EAAAb,GAEE,MAAM46B,EAAepnC,SAASmI,cAC5B,oCAEIk/B,EAAmBrnC,SAASmI,cAChC,0DAGFi/B,EAAa3jC,iBAAiB,8BAAgCmF,IAC5DvG,MAAKilC,GAAY1+B,EAAE7F,OAAOmT,SAG5BmxB,EAAiB5jC,iBAAiB,8BAAgCmF,IAChE,MAAMiT,EAAajT,EAAE7F,OAAOmT,MAC5BlW,SAASuG,eAAe,aAAaqG,MAAQiP,EACzCA,EAAW9W,IACX,KAGN/E,SAASe,iBAAiB,yBAAyBJ,QAASwb,IAC1DA,EAAK1Y,iBAAiB,SAAWmF,IAC/BA,EAAEC,iBACFxG,MAAKklC,GAAY3+B,EAAE7E,OAAOvC,QAAQgmC,iBAGxC,CAMA,GAAAF,CAAYvuB,EAAO,MACjB,MAAM0uB,EAAeznC,SAASuG,eAAe,iBACvCmhC,EAAc1nC,SAASmI,cAC3B,8DAGFs/B,EAAa76B,MAAQmM,EAAOA,EAAKmZ,SAAW,GAC5CwV,EAAY3uB,KAAOA,EAAOA,EAAKnO,QAAKvK,CACtC,CAMA,GAAAknC,CAAYI,GACV,MAAMC,EAAgB5nC,SAASuG,eAAe,kBAC9C,IAAIxB,EAAM/E,SAASuG,eAAe,GAAGohC,UAAiB/6B,MAEtD,GAAiB,aAAb+6B,GAAmD,KAAxBC,EAAch7B,MAE3C7H,EAAMA,EAAIxE,QAAQgL,EAAc,IAAMq8B,EAAch7B,WAC/C,GAAiB,aAAb+6B,IAhHf,SAAmB5iC,GACjB,MAAM8iC,EACJ7nC,SAASuG,eAAe,kBAAkB/E,QAAQsmC,cACpD,IAAKD,EAAS,OAAO,EAErB,MAAME,EAAMF,EAAQG,YAAY,KAChC,OAAO,IAAItzB,OAAOmzB,EAAQ/nB,MAAM,EAAGioB,GAAMF,EAAQ/nB,MAAMioB,EAAM,IAAItoC,KAAKsF,EACxE,CAyG2CkjC,CAAUljC,GAG/C,YADA1C,MAAK6lC,KAKP7lC,MAAKykC,GAAc,CACjB/hC,IAAKA,EAAIs8B,OACT98B,MAAOvE,SAASuG,eAAe,GAAGohC,gBAAuB/6B,MACzD7I,OAAQ/D,SAASuG,eAAe,GAAGohC,kBAAyB/6B,MAC5Db,KAAM47B,IAERtlC,KAAK4D,OACP,CAKA,GAAAiiC,GACE,MAAMC,EAASnoC,SAASuG,eAAe,UACvC4hC,EAAOhgC,cAAc,MAAMrG,UACzB,OAAOtC,QAAQkL,EAAE,gCACnBy9B,EAAOtgC,MAAMC,QAAU,OACzB,GsEjHAsC,oBACFpC,QAAEA,EACAkG,kBAEFhH,EAAMkuB,OAAOgT,MAAMC,QAAUt5B,EAC7B/O,SAASyD,iBAAiB,azEpBX,WAEbzD,SAASC,gBAAgB6F,UAAUlD,OAAO,SAG1CnC,IAGAT,SAAS+M,oBAAoB,WAAY1C,GACzCrK,SAASyD,iBAAiB,WAAY4G,GAGtCrK,SAASe,iBAAiB,gBAAgBJ,QAASiB,IACjDA,EAAQ6B,iBAAiB,QAAS2G,KAIpCpK,SAASe,iBAAiB,YAAYJ,QAASkT,IAC7CA,EAAOxE,aAAa,WAAY,KAIlClQ,IAAIkW,OAAS,SAAUlJ,GACrB,IAAI4uB,GAAW5uB,EAAMpI,QAAUoI,EAAMm8B,YAAYvN,QACjD,OACE57B,IAAIopC,UAAU,UACA,UAAZxN,GAAmC,WAAZA,GAAoC,aAAZA,EAErD,CACF","x_google_ignoreList":[18]}
|