diff --git a/AGENTS.md b/AGENTS.md index 5041ce9ec1..f3a24817d0 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -188,7 +188,7 @@ The admin interface uses a **hybrid architecture**: **Modern Web Components** (preferred for new features): - Custom elements in `app/javascript/alchemy_admin/components/` -- Base class: `AlchemyHTMLElement` (extends `HTMLElement`) +- Extend `HTMLElement` directly; do all DOM work in `connectedCallback` - Examples: `alchemy-sitemap`, `alchemy-element-editor`, `alchemy-datepicker` - Vanilla JavaScript (no framework dependency) diff --git a/app/assets/builds/alchemy/alchemy_admin.min.js b/app/assets/builds/alchemy/alchemy_admin.min.js index 9200fbff07..f215ff3cd6 100644 --- a/app/assets/builds/alchemy/alchemy_admin.min.js +++ b/app/assets/builds/alchemy/alchemy_admin.min.js @@ -1,2 +1,2 @@ -import"handlebars";import"jquery";import"@ungap/custom-elements";import{Turbo as e}from"@hotwired/turbo-rails";import"select2";import t from"@rails/ujs";import"keymaster";import"clipboard";import s from"flatpickr";import i from"sortablejs";import"tinymce";import{setDefaultAnimation as n,registerIconLibrary as r}from"shoelace";import l from"cropperjs";const o=/\./;function a(e){const t=c(),s=Alchemy.translations;return s?o.test(e)?function(e,t){const s=t.split(o),i=e[s[0]];return i&&i[s[1]]||t}(s,e):s[e]||e:(console.warn(`Translations for locale ${t} not found!`),e)}function c(){return document.documentElement.lang?document.documentElement.lang:"en"}function d(e,t=void 0){let s=a(e);return t?s.replace(/%\{.+\}/,t):s}async function h(){const e=c();"en"!==e&&(await import(`select2/${e}.js`),$.extend($.fn.select2.defaults,$.fn.select2.locales[e]))}const u=[];function m(e){return!(!$(e.target).is("input, textarea")&&"?"===String.fromCharCode(e.which))||(_("/admin/help",{title:Alchemy.t("help"),size:"400x492"}),!1)}function p(e=document){e instanceof jQuery&&(e=e[0]),e===document&&(document.removeEventListener("keypress",m),document.addEventListener("keypress",m),u.forEach((e=>key.unbind(e))));const t=e.querySelectorAll(".search_input_field"),s=e.querySelectorAll(".search_field_clear, .js_filter_field_clear");key("alt+f",(function(){return key.setScope("search"),t.forEach((e=>e.focus({focusVisible:!0}))),!1})),u.push("alt+f"),key("esc","search",(function(){s.forEach((e=>e.click())),t.forEach((e=>e.blur()))})),u.push("esc"),e.querySelectorAll("[data-alchemy-hotkey]").forEach((function(e){const t=e.dataset.alchemyHotkey;key(t,(()=>e.click())),u.push(t)}))}function g(e){const t=document.createElement("template");return t.innerHTML=e,t.content.children[0]}let y=class{constructor(e,t="currentColor"){this.size=e,this.color=t,this.spinner=void 0}get el(){return this.spinner}spin(e){return void 0===e&&(e=document.body),this.spinner=g(``),e.append(this.spinner),this}stop(){this.spinner&&(this.spinner.remove(),this.spinner=void 0)}};const f=[],v={header_height:36,size:"400x300",padding:!0,title:"",modal:!0,overflow:"visible",ready:()=>{},closed:()=>{}};class b{constructor(e,t={}){this.url=e,this.options={...v,...t},this.$document=$(document),this.$window=$(window),this.$body=$("body");const s=this.options.size.split("x");this.width=parseInt(s[0],10),this.height=parseInt(s[1],10),this.build(),this.resize()}open(){this.dialog.trigger("Alchemy.DialogOpen"),this.bind_close_events(),window.requestAnimationFrame((()=>{if(this.dialog_container.addClass("open"),null!=this.overlay)return this.overlay.addClass("open")})),this.$body.addClass("prevent-scrolling"),f.push(this),this.load()}close(){return this.dialog.trigger("DialogClose.Alchemy"),this.$document.off("keydown"),this.dialog_container.removeClass("open"),null!=this.overlay&&this.overlay.removeClass("open"),this.$document.on("webkitTransitionEnd transitionend oTransitionEnd",(()=>{if(this.$document.off("webkitTransitionEnd transitionend oTransitionEnd"),this.dialog_container.remove(),null!=this.overlay&&this.overlay.remove(),this.$body.removeClass("prevent-scrolling"),f.pop(this),null!=this.options.closed)return this.options.closed()})),!0}load(){this.show_spinner(),$.get(this.url,(e=>{this.replace(e)})).fail((e=>{this.show_error(e)}))}reload(){this.dialog_body.empty(),this.load()}replace(e){this.remove_spinner(),this.dialog_body.hide(),this.dialog_body.html(e),this.init(),this.dialog[0].dispatchEvent(new CustomEvent("DialogReady.Alchemy",{bubbles:!0,detail:{body:this.dialog_body[0]}})),null!=this.options.ready&&this.options.ready(this.dialog_body),this.dialog_body.show()}show_spinner(){this.spinner=new y("medium"),this.spinner.spin(this.dialog_body[0])}remove_spinner(){this.spinner.stop()}init(){p(this.dialog_body),this.watch_remote_forms()}watch_remote_forms(){const e=$('[data-remote="true"]',this.dialog_body);e.on("ajax:success",(e=>{const t=e.detail[2];t.getResponseHeader("Content-Type").match(/javascript/)||(this.dialog_body.html(t.responseText),this.init())})),e.on("ajax:error",(e=>{const t=e.detail[1],s=e.detail[2];this.show_error(s,t)}))}show_error(e,t){if(422===e.status)return this.dialog_body.html(e.responseText),void this.init();const{error_body:s,error_header:i,error_type:n}=this.error_messages(e,t),r=$(`\n

${i}

\n

${s}

\n
`);this.dialog_body.html(r)}error_messages(e,t){let s,i,n="warning";switch(e.status){case 0:i="The server does not respond.",s="Please check server and try again.";break;case 403:i="You are not authorized!",s="Please close this window.";break;default:n="error",t?(i=t,console.error(e.responseText)):i=`${e.statusText} (${e.status})`,s="Please check log and try again."}return{error_header:i,error_body:s,error_type:n}}bind_close_events(){this.close_button.on("click",(()=>{this.close()})),this.dialog_container.addClass("closable").on("click",(e=>e.target!==this.dialog_container.get(0)||(this.close(),!1))),this.$document.keydown((e=>27!==e.which||(this.close(),!1)))}build(){this.dialog_container=$('
'),this.dialog=$('
'),this.dialog_body=$('
'),this.dialog_header=$('
'),this.dialog_title=$('
'),this.close_button=$(''),this.dialog_title.text(this.options.title),this.dialog_header.append(this.dialog_title),this.dialog_header.append(this.close_button),this.dialog.append(this.dialog_header),this.dialog.append(this.dialog_body),this.dialog_container.append(this.dialog),this.options.modal&&this.dialog.addClass("modal"),this.options.padding&&this.dialog_body.addClass("padded"),this.options.modal&&(this.overlay=$('
'),this.$body.append(this.overlay)),this.$body.append(this.dialog_container)}resize(){const{width:e,height:t}=this.getSize();this.dialog.css({width:e,"min-height":t,overflow:this.options.overflow}),"hidden"===this.options.overflow?this.dialog_body.css({height:t,overflow:"auto"}):this.dialog_body.css({"min-height":t,overflow:"visible"})}getSize(){const e=this.options.padding?16:0,t=this.$window.width(),s=this.$window.height();let i=this.width,n=this.height;return i>=t&&(i=t-e),n>=s&&(n=s-e-v.header_height),{width:i,height:n}}}function E(){const{length:e}=f;if(0!==e)return f[e-1]}function w(e){const t=E();if(null!=t)return t.options.closed=e,t.close()}function _(e,t){if(!e)throw"No url given! Please provide an url.";new b(e,t).open()}class k{constructor(e,t={}){this.message=e,this.options={...{size:"300x100",title:d("Please confirm"),ok_label:d("Yes"),cancel_label:d("No"),on_ok(){}},...t},this.#e(),this.#t()}open(){requestAnimationFrame((()=>{this.dialog.show()}))}#e(){const e=this.options.size.split("x")[0];this.dialog=g(`\n \n ${this.message}\n \n \n \n `),document.body.append(this.dialog)}#t(){this.cancelButton.addEventListener("click",(e=>{e.preventDefault(),this.options.on_cancel(),this.dialog.hide()})),this.okButton.addEventListener("click",(e=>{e.preventDefault(),this.options.on_ok(),this.dialog.hide()})),this.dialog.addEventListener("sl-request-close",(e=>{"overlay"===e.detail.source&&(this.options.on_cancel(),e.preventDefault())})),this.dialog.addEventListener("sl-after-hide",(()=>{this.dialog.remove()}))}get cancelButton(){return this.dialog.querySelector("button[type=reset]")}get okButton(){return this.dialog.querySelector("button[type=submit]")}}function L(e,t={}){return new Promise((s=>{new k(e,{...t,on_ok(){s(!0)},on_cancel(){s(!1)}}).open()}))}function S(e=!0){document.querySelector("alchemy-overlay").show=!!e}function C(e){let t=()=>{};$(e).is("form")?t=function(){const t=$(`
`);t.append($(e).find("input")),t.appendTo("body"),S(),t.trigger("submit")}:$(e).is("a")&&(t=()=>Turbo.visit(e.pathname));return!(document.querySelectorAll("alchemy-element-editor.dirty").length>0)||(L(d("page_dirty_notice"),{title:d("warning"),ok_label:d("ok"),cancel_label:d("cancel")}).then((e=>{e&&(window.onbeforeunload=void 0,t())})),!1)}var x={checkPageDirtyness:C,PageLeaveObserver:function(){document.querySelectorAll("#main_navi a").forEach((e=>{e.addEventListener("click",(e=>{C(e.currentTarget)||e.preventDefault()}))}))}};function T(e){const t=document.getElementById("fixed-elements"),s=`fixed-element-${e}`;t.querySelector(`sl-tab[panel="${s}"]`).remove(),t.querySelector(`sl-tab-panel[name="${s}"]`).remove(),t.show("main-content-elements")}var A=Object.freeze({__proto__:null,createTab:function(e,t){const s=document.getElementById("fixed-elements"),i=`fixed-element-${e}`,n=`${t}`,r=``;s.innerHTML+=n+r,window.requestAnimationFrame((function(){s.show(i)}))},removeTab:T});function F(e,t="notice"){!function(e,t){const s=document.getElementById("flash_notices"),i=g(`\n \n ${e}\n \n `);s.append(i)}(e,t)}const q=/#[\w.~-]+$/;class I extends HTMLIFrameElement{#s;#i;#n;#r;constructor(){super(),this.addEventListener("load",this),this.#r=this.#l.bind(this)}handleEvent(e){"load"===e.type&&(this.#o(),this.#a(),this.#s?.call(this,e))}#l(e){"Alchemy.previewReady"===e.data.message&&(this.#o(),this.#a(),this.#s?.call(this,e))}connectedCallback(){let e=this.url;this.#c(),window.addEventListener("message",this.#r),window.localStorage.getItem("alchemy-preview-url")&&(e=window.localStorage.getItem("alchemy-preview-url"),this.previewUrlSelect.value=e),this.refresh(e)}disconnectedCallback(){key.unbind("alt+r"),window.removeEventListener("message",this.#r)}postMessage(e){this.contentWindow.postMessage(e,"*")}resize(e){this.style.width=`${e}px`}refresh(e){return this.#d(),this.src=e||this.url,this.#o(),this.#n=setTimeout((()=>{this.#a(),F(d("Preview failed to load"),"warning")}),5e3),new Promise((e=>{this.#s=e}))}set isDragged(e){this.style.transitionProperty=e?"none":null,this.style.pointerEvents=e?"none":null}#c(){this.reloadButton?.addEventListener("click",(e=>{e.preventDefault(),this.refresh()})),key("alt+r",(()=>this.refresh())),this.sizeSelect.addEventListener("change",(e=>{const t=e.target.value;""===t?this.style.width=null:this.resize(t)})),this.previewUrlSelect?.addEventListener("change",(e=>{const t=e.target.value;window.localStorage.setItem("alchemy-preview-url",t),this.refresh(t)}))}#d(){this.reloadButton.innerHTML.includes("alchemy-spinner")||(this.#i=this.reloadButton.innerHTML),this.reloadButton.innerHTML=''}#a(){this.reloadButton.innerHTML=this.#i}#o(){this.#n&&(clearTimeout(this.#n),this.#n=null)}get url(){return this.getAttribute("url")}get sizeSelect(){return document.querySelector("select#preview_size")}get previewUrlSelect(){return document.querySelector("select#preview_url")}get reloadButton(){return document.querySelector("#reload_preview_button")}}function B(){document.getElementById("alchemy_preview_window").refresh()}customElements.define("alchemy-preview-window",I,{extends:"iframe"});class M{static updateIcon(e,t=!1){const s=document.querySelector(`[data-ingredient-id="${e}"]`);if(s){s.querySelector(".edit-ingredient-anchor-link alchemy-icon").setAttribute("icon-style",t?"fill":"line")}}}class z extends HTMLElement{constructor(){super(),this.actions={closeCurrentDialog:w,reloadPreview:B,removeFixedElement:T,updateAnchorIcon:M.updateIcon,hidePleaseWaitOverlay(){S(!1)}}}connectedCallback(){const e=this.actions[this.name];e?e(...this.params):console.error(`Unknown Alchemy action: ${this.name}`),this.remove()}get name(){return this.getAttribute("name")}get params(){return this.hasAttribute("params")?JSON.parse(this.getAttribute("params")):[]}}customElements.define("alchemy-action",z);class H extends HTMLElement{static properties={};static get observedAttributes(){return Object.keys(this.properties)}constructor(e={}){super(),this.options=e,this.changeComponent=!0,this.initialContent=this.innerHTML}async connectedCallback(){Object.keys(this.constructor.properties).forEach((e=>{this[e]=this.options[e]??this.constructor.properties[e].default})),this.getAttributeNames().forEach((e=>this._updateFromAttribute(e))),this._updateComponent(),await this.connected()}disconnectedCallback(){this.disconnected()}attributeChangedCallback(e){this._updateFromAttribute(e),this._updateComponent()}async connected(){}disconnected(){}render(){return this.initialContent}afterRender(){}dispatchCustomEvent(e,t={}){const s=new CustomEvent(`Alchemy.${e}`,{bubbles:!0,detail:t});this.dispatchEvent(s)}_updateComponent(){this.changeComponent&&(this.innerHTML=this.render(),this.changeComponent=!1,this.afterRender())}_updateFromAttribute(e){const t=this.getAttribute(e),s=e.split(/-|_/).reduce(((e,t)=>e+t.charAt(0).toUpperCase()+t.slice(1)));const i=!!(0===t.length||"true"===t)||t;this[s]!==i&&(this[s]=i,this.changeComponent=!0)}}function P(e,t){return e.replace(new RegExp(t,"gi"),(e=>`${e}`))}class D extends H{static properties={allowClear:{default:!1},selection:{default:void 0},placeholder:{default:""},queryParams:{default:"{}"},url:{default:""}};async connected(){await h(),this.input.classList.add("alchemy_selectbox"),$(this.input).select2(this.select2Config).on("select2-open",(e=>{this.onOpen(e)})).on("change",(e=>{this.onChange(e)}))}onChange(e){this.dispatchCustomEvent("RemoteSelect.Change",{removed:e.removed,added:e.added})}onOpen(e){setTimeout((()=>{document.querySelector("#select2-drop .select2-input").focus()}),100)}get input(){return this.getElementsByTagName("input")[0]}get select2Config(){return{placeholder:this.placeholder,allowClear:this.allowClear,initSelection:(e,t)=>{this.selection&&t(JSON.parse(this.selection))},ajax:this.ajaxConfig,formatSelection:e=>this._renderResult(e),formatResult:(e,t,s)=>this._renderListEntry(e,s.term)}}get ajaxConfig(){return{url:this.url,datatype:"json",quietMillis:300,data:(e,t)=>this._searchQuery(e,t),results:e=>this._parseResponse(e)}}_searchQuery(e,t){return{q:{name_cont:e,...JSON.parse(this.queryParams)},page:t}}_parseResponse(e){const t=e.meta;return{results:e.data,more:t.page*t.per_page\n \n ${this._hightlightTerm(e.name,t)}\n
\n `}});class N extends HTMLElement{connectedCallback(){$(this).on("change",(function(e){const t=new Event("submit",{bubbles:!0,cancelable:!0});return e.target.form.dispatchEvent(t),!1}))}}customElements.define("alchemy-auto-submit",N);class O extends HTMLButtonElement{connectedCallback(){this.form?(this.form.addEventListener("submit",this),"true"==this.form.dataset.remote&&this.form.addEventListener("ajax:complete",this),this.form.addEventListener("turbo:submit-end",this)):console.warn("No form for button found!",this)}handleEvent(e){switch(e.type){case"submit":"disabled"===this.getAttribute("disabled")?(e.preventDefault(),e.stopPropagation()):this.disable();break;case"ajax:complete":case"turbo:submit-end":this.enable()}}disable(){const e=new y("small"),t=this.getBoundingClientRect();this.dataset.initialButtonText=this.innerHTML,this.setAttribute("disabled","disabled"),this.setAttribute("tabindex","-1"),this.classList.add("disabled"),this.style.width=`${t.width}px`,this.style.height=`${t.height}px`,this.innerHTML=" ",e.spin(this)}enable(){this.classList.remove("disabled"),this.removeAttribute("disabled"),this.removeAttribute("tabindex"),this.style.width=null,this.style.height=null,this.innerHTML=this.dataset.initialButtonText}}customElements.define("alchemy-button",O,{extends:"button"});customElements.define("alchemy-char-counter",class extends H{static properties={maxChars:{default:60}};connected(){this.translation=d("allowed_chars",this.maxChars),this.formField=this.getFormField(),this.formField&&(this.createDisplayElement(),this.countCharacters(),this.formField.addEventListener("keyup",(()=>this.countCharacters())))}getFormField(){const e=this.querySelectorAll("input, textarea");return e.length>0?e[0]:void 0}createDisplayElement(){this.display=document.createElement("small"),this.display.className="alchemy-char-counter",this.formField.after(this.display)}countCharacters(){const e=this.formField.value.length;this.display.textContent=`${e} ${this.translation}`,this.display.classList.toggle("too-long",e>this.maxChars)}});class R extends HTMLElement{constructor(){super(),this.innerHTML='\n \n ',this.clipboard=new ClipboardJS(this,{text:()=>this.getAttribute("content")}),this.clipboard.on("success",(()=>{F(this.getAttribute("success-text"))}))}disconnectedCallback(){this.clipboard.destroy()}}customElements.define("alchemy-clipboard-button",R);const U=e=>{const t=e.element[0],s=t.dataset.swatch||t.value;return`\n
\n ${"custom_color"===t.value?'':``}\n ${e.text}\n
`};class j extends HTMLElement{connectedCallback(){this.select?(this.#h(),$(this.select).on("change",(e=>this.#u("custom_color"===e.val)))):(this.colorInput?.addEventListener("input",this),this.textInput?.addEventListener("input",this),this.#u(!0))}handleEvent(e){switch(e.target){case this.colorInput:this.textInput.value=this.colorInput.value;break;case this.textInput:this.colorInput.value=this.textInput.value}}disconnectedCallback(){this.colorInput?.removeEventListener("input",this),this.textInput?.removeEventListener("input",this)}#h(){this.select.classList.add("alchemy_selectbox");const e={minimumResultsForSearch:10,formatResult:U,formatSelection:U};$(this.select).select2(e)}#u(e=!0){this.colorInput.disabled=!e}get colorInput(){return this.querySelector("input[type='color']")}get textInput(){return this.querySelector("input[type='text']")}get select(){return this.querySelector("select")}}customElements.define("alchemy-color-select",j);const W=c();customElements.define("alchemy-datepicker",class extends H{static properties={inputType:{default:"date"}};constructor(){super(),this.flatpickr=void 0}async connected(){"en"!==W&&await import(`flatpickr/${W}.js`),this.flatpickr=s(this.inputField,this.flatpickrOptions)}disconnected(){this.flatpickr.destroy()}get flatpickrOptions(){const e=/time/.test(this.inputType),t={locale:W.slice(0,2),altInput:!0,altFormat:d(`formats.${this.inputType}`),altInputClass:"flatpickr-input",enableTime:e,noCalendar:"time"===this.inputType,time_24hr:d("formats.time_24hr"),onValueUpdate(e,t,s){s.element.closest("alchemy-element-editor")?.setDirty(this.inputField)}};return e&&(t.dateFormat="Z"),t}get inputField(){return this.querySelector("input")}});class J extends HTMLAnchorElement{constructor(){super(),this.addEventListener("click",this)}handleEvent(e){this.disabled||this.openDialog(),e.preventDefault()}openDialog(){this.dialog=new b(this.getAttribute("href"),this.dialogOptions),this.dialog.open()}get dialogOptions(){return this.dataset.dialogOptions?JSON.parse(this.dataset.dialogOptions):{}}get disabled(){return this.classList.contains("disabled")}}customElements.define("alchemy-dialog-link",J,{extends:"a"});const V="application/json",X="text/vnd.turbo-stream.html";function Q(e){return"get"===e.toLowerCase()}function Y(e,t,s){const i=function(e){return{"Content-Type":"application/json; charset=utf-8",Accept:e,"X-Requested-With":"XMLHttpRequest","X-CSRF-Token":K()}}(s),n={method:e,headers:i};return t&&!Q(e)&&(n.body=JSON.stringify(t)),n}function K(){return document.querySelector('meta[name="csrf-token"]').attributes.content.textContent}function G(e,t){return te("GET",e,t)}function Z(e,t,s){return te("PATCH",e,t,s)}function ee(e,t,s=V){return te("POST",e,t,s)}async function te(e,t,s,i=V){const n=await fetch(function(e,t,s){const i=new URL(window.location.origin+e);return t&&Q(s)&&(i.search=new URLSearchParams(t).toString()),i.toString()}(t,s,e),Y(e,s,i)),r=n.headers.get("content-type"),l=r?.includes(V),o=r?.includes(X);let a=null;if(l?a=await n.json():o&&(a=await n.text(),"undefined"!=typeof Turbo&&Turbo.renderStreamMessage(a)),n.ok)return{data:a,status:n.status};throw a||new Error("An error occurred during the transaction")}class se extends HTMLElement{dataItem(e){return{id:`#${e}`,text:`#${e}`}}get selectElement(){return this.querySelector('select[is="alchemy-select"]')}}customElements.define("alchemy-dom-id-api-select",class extends se{#m=void 0;connectedCallback(){this.page=this.getAttribute("page")}async#p(){const e=(await G(Alchemy.routes.api_ingredients_path,{page_id:this.#m})).data.ingredients.filter((e=>e.data?.dom_id)).map((e=>this.dataItem(e.data.dom_id))),t=e.length>0?d("None"):d("No anchors found");this.selectElement.setOptions(e,t),this.selectElement.enable()}#g(){requestAnimationFrame((()=>{this.selectElement.disable(),this.selectElement.setOptions([],d("Select a page first"))}))}set page(e){this.#m=e,e?this.#p():this.#g()}}),customElements.define("alchemy-dom-id-preview-select",class extends se{connectedCallback(){requestAnimationFrame((()=>{const e=document.getElementById("alchemy_preview_window"),t=e.contentDocument?.querySelectorAll("[id]")||[];if(t.length>0){const e=Array.from(t).map((e=>this.dataItem(e.id)));this.selectElement.setOptions(e,d("None"))}}))}});class ie extends HTMLElement{#y;connectedCallback(){this.#y=this.scheduleButton.getAttribute("variant"),this.publishButton.addEventListener("click",this),this.dropdown.addEventListener("sl-show",this),this.dropdown.addEventListener("sl-hide",this)}disconnectedCallback(){this.publishButton.removeEventListener("click",this),this.dropdown.removeEventListener("sl-show",this),this.dropdown.removeEventListener("sl-hide",this)}handleEvent(e){switch(e.type){case"click":this.publishButton.loading=!0;break;case"sl-show":this.scheduleButton.setAttribute("variant","primary");break;case"sl-hide":this.scheduleButton.setAttribute("variant",this.#y)}}get publishButton(){return this.querySelector("sl-button[type='submit']")}get dropdown(){return this.querySelector("sl-dropdown")}get scheduleButton(){return this.querySelector("sl-button[slot='trigger']")}}customElements.define("alchemy-publish-element-button",ie);class ne extends HTMLElement{constructor(){super(),this.button?.addEventListener("click",this)}async handleEvent(){if(await L(this.message)){const e=await te("DELETE",this.url);this.#f(e.data)}}#f(e){const t=this.closest("alchemy-element-editor");t.addEventListener("transitionend",(()=>{t.fixed&&T(t.elementId),t.remove()})),t.classList.add("dismiss"),F(e.message),e.pageHasUnpublishedChanges&&re(e),B()}get url(){return this.getAttribute("href")}get message(){return this.getAttribute("message")}get button(){return this.querySelector("button")}}function re(e){document.dispatchEvent(new CustomEvent("alchemy:page-dirty",{detail:{tooltip:e.publishButtonTooltip}}))}customElements.define("alchemy-delete-element-button",ne);class le extends HTMLElement{constructor(){super(),this.addEventListener("click",this),this.addEventListener("alchemy:element-update-title",this),this.addEventListener("ajax:complete",this),$(this.form).on("change",this.onChange),this.header?.addEventListener("dblclick",(()=>{this.toggle()})),this.toggleButton?.addEventListener("click",(e=>{e.target.closest("alchemy-element-editor")===this&&this.toggle()}))}connectedCallback(){this.classList.contains("ui-sortable-placeholder")||this.hasAttribute("created")&&(this.focusElement(),this.previewWindow?.refresh().then((()=>{this.focusElementPreview()})),this.removeAttribute("created"))}handleEvent(e){switch(e.type){case"click":e.target.closest("alchemy-element-editor")===this&&this.onClickElement();break;case"ajax:complete":if(e.target===this.body){const t=e.detail[0];e.stopPropagation(),this.onSaveElement(t)}break;case"alchemy:element-update-title":this.hasEditors||e.target!=this.firstChild||this.setTitle(e.detail.title)}}onChange(e){const t=e.target;if(!t.classList.contains("nested-elements"))return this.closest("alchemy-element-editor").setDirty(t),e.stopPropagation(),!1}async focusElement(){document.querySelector("#fixed-elements")&&await this.selectTabForElement(),await this.expand(),this.selectElement(!0)}focusElementPreview(){this.previewWindow?.postMessage({message:"Alchemy.focusElement",element_id:this.elementId})}onClickElement(){this.selectElement(),this.focusElementPreview()}onSaveElement(e){const t=JSON.parse(e.responseText);if(this.setClean(),422===e.status){const e=t.warning;t.ingredientsWithErrors.forEach((e=>{const t=this.querySelector(`[data-ingredient-id="${e.id}"]`),s=g(`${e.errorMessage}`);t?.appendChild(s),t?.classList.add("validation_failed")})),F(e,"warn"),this.elementErrors.classList.remove("hidden")}else F(t.notice),this.previewWindow?.refresh().then((()=>{this.focusElementPreview()})),this.updateTitle(t.previewText),t.ingredientAnchors.forEach((e=>{M.updateIcon(e.ingredientId,e.active)})),t.pageHasUnpublishedChanges&&re(t)}scrollToElement(){setTimeout((()=>{this.scrollIntoView({behavior:"smooth"})}),50)}selectElement(e=!1){document.querySelectorAll("alchemy-element-editor.selected").forEach((e=>{e.classList.remove("selected")})),window.requestAnimationFrame((()=>{this.classList.add("selected")})),e&&this.scrollToElement()}selectTabForElement(){return new Promise(((e,t)=>{const s=document.querySelector("#fixed-elements"),i=this.closest("sl-tab-panel");s&&i?(s.show(i.getAttribute("name")),e()):t(new Error("No tabs present"))}))}setClean(){this.dirty=!1,window.onbeforeunload=null,this.elementErrors.classList.add("hidden"),this.hasEditors&&this.body.querySelectorAll(".ingredient-editor").forEach((e=>{e.classList.remove("dirty","validation_failed"),e.querySelectorAll("small.error").forEach((e=>e.remove()))}))}setDirty(e){this.hasEditors&&(this.dirty=!0,window.onbeforeunload||(window.onbeforeunload=e=>e.preventDefault()),e?.closest(".ingredient-editor")?.classList.add("dirty"))}setTitle(e){this.querySelector(".element-header .preview_text_quote").textContent=e}async toggle(){this.collapsed?await this.expand():await this.collapse()}collapse(){if(this.collapsed||this.compact||this.fixed)return Promise.resolve("Element is already collapsed.");const e=new Alchemy.Spinner("small");return e.spin(this.toggleButton),this.toggleIcon?.classList?.add("hidden"),ee(Alchemy.routes.collapse_admin_element_path(this.elementId)).then((e=>{const t=e.data;if(this.collapsed=!0,this.toggleButton?.setAttribute("title",t.title),t.nestedElementIds.length){const e=t.nestedElementIds.map((e=>`#element_${e}`)).join(", ");this.querySelectorAll(e).forEach((e=>{e.collapsed=!0,e.toggleButton?.setAttribute("title",t.title)}))}})).catch((e=>{F(e.message,"error"),console.error(e)})).finally((()=>{this.toggleIcon?.classList?.remove("hidden"),e.stop()}))}expand(){if(this.expanded&&!this.compact)return Promise.resolve("Element is already expanded.");if(this.compact&&this.parentElementEditor)return this.parentElementEditor.expand();{const e=new Alchemy.Spinner("small");return e.spin(this.toggleButton),this.toggleIcon?.classList.add("hidden"),new Promise(((t,s)=>{ee(Alchemy.routes.expand_admin_element_path(this.elementId)).then((e=>{const s=e.data;if(s.parentElementIds.length){const e=s.parentElementIds.map((e=>`#element_${e}`)).join(", ");document.querySelectorAll(e).forEach((e=>{e.collapsed=!1,e.toggleButton?.setAttribute("title",s.title)}))}this.collapsed=!1,this.toggleButton?.setAttribute("title",s.title),t()})).catch((e=>{F(e.message,"error"),console.error(e),s(e)})).finally((()=>{this.toggleIcon?.classList?.remove("hidden"),e.stop()}))}))}}updateTitle(e){this.setTitle(e),this.dispatchEvent(new CustomEvent("alchemy:element-update-title",{bubbles:!0,detail:{title:e}}))}set published(e){e?this.classList.remove("element-hidden"):this.classList.add("element-hidden")}get published(){return!this.classList.contains("hidden")}get compact(){return null!==this.getAttribute("compact")}get fixed(){return null!==this.getAttribute("fixed")}set collapsed(e){this.classList.toggle("folded",e),this.classList.toggle("expanded",!e),this.toggleIcon&&(this.toggleIcon.name=e?"arrow-left-s":"arrow-down-s")}get collapsed(){return this.classList.contains("folded")}get expanded(){return!this.collapsed}set dirty(e){this.classList.toggle("dirty",e)}get dirty(){return this.classList.contains("dirty")}get header(){return this.querySelector(".element-header")}get body(){return this.querySelector(this.bodySelector)}get bodySelector(){return`#${this.id} > .element-body`}get footer(){return this.querySelector(`#${this.id} > .element-footer`)}get toggleButton(){return this.querySelector(".element-toggle")}get toggleIcon(){return this.toggleButton?.querySelector("alchemy-icon")}get elementErrors(){return this.body.querySelector(".element_errors")}get elementId(){return this.dataset.elementId}get elementName(){return this.dataset.elementName}get hasEditors(){return!!this.body?.querySelector(".element-ingredient-editors")}get hasChildren(){return!!this.querySelector(".nested-elements")}get firstChild(){return this.querySelector("alchemy-element-editor")}get form(){return this.querySelector("form.element-body")}get parentElementEditor(){return this.parentElement?.closest("alchemy-element-editor")}get previewWindow(){return document.getElementById("alchemy_preview_window")}}customElements.define("alchemy-element-editor",le);const oe=e=>`\n
\n ${e.icon}${e.text}\n
\n `;class ae extends HTMLElement{constructor(){super()}connectedCallback(){const e=this.options,t={minimumResultsForSearch:3,dropdownAutoWidth:!0,data:()=>({results:e}),formatResult:(e,t,s)=>{let i;return""===e.id?e.text:(i=""!==s.term?P(e.text,s.term):e.text,((e,t,s)=>{const i=s?`
${s}
`:"";return`\n
\n ${oe({icon:e,text:t})}\n ${i}\n
\n `})(e.icon,i,e.hint))},formatSelection:oe,placeholder:this.placeholder};$(this.inputField).select2(t)}get options(){return JSON.parse(this.getAttribute("options"))}get placeholder(){return this.getAttribute("placeholder")}get inputField(){return this.querySelector("input")}}customElements.define("alchemy-element-select",ae);class ce extends HTMLElement{#v=!0;#b=null;constructor(){super(),this.#c()}connectedCallback(){this.toggleButton?.addEventListener("click",(e=>{e.preventDefault(),this.toggle()})),window.location.hash&&this.focusElementEditor(window.location.hash),this.resize()}collapseAllElements(){this.querySelectorAll("alchemy-element-editor:not([compact]):not([fixed])").forEach((e=>e.collapse()))}toggle(){this.#v?this.hide():this.show()}show(){document.body.classList.add("elements-window-visible"),this.#v=!0,this.toggleButton.closest("sl-tooltip").content=Alchemy.t("Hide elements"),this.toggleButton.querySelector("alchemy-icon").setAttribute("name","menu-unfold"),this.resize()}hide(){document.body.classList.remove("elements-window-visible"),document.body.style.removeProperty("--elements-window-width"),this.#v=!1,this.toggleButton.closest("sl-tooltip").content=Alchemy.t("Show elements"),this.toggleButton.querySelector("alchemy-icon").setAttribute("name","menu-fold")}resize(e){void 0===e&&(e=this.widthFromCookie),e&&(document.body.style.setProperty("--elements-window-width",`${e}px`),document.cookie=`alchemy-elements-window-width=${e}; SameSite=Lax; Path=/;`)}focusElementEditor(e){const t=document.querySelector(e);t instanceof le&&t.focusElement()&&t.focusElementPreview()}get collapseButton(){return this.querySelector("#collapse-all-elements-button")}get toggleButton(){return document.querySelector("#element_window_button")}get previewWindow(){return document.getElementById("alchemy_preview_window")}get turboFrame(){return this.#b||(this.#b=this.closest("turbo-frame")),this.#b}get widthFromCookie(){return document.cookie.split("; ").find((e=>e.startsWith("alchemy-elements-window-width=")))?.split("=")[1]}set isDragged(e){this.turboFrame.style.transitionProperty=e?"none":null,this.turboFrame.style.pointerEvents=e?"none":null}#c(){this.collapseButton?.addEventListener("click",(()=>{this.collapseAllElements()})),window.addEventListener("message",(e=>{const t=e.data;if("Alchemy.focusElementEditor"==t?.message){const e=document.getElementById(`element_${t.element_id}`);this.show(),e?.focusElement()}})),document.body.addEventListener("click",(e=>{e.target.closest("alchemy-element-editor")||(this.querySelectorAll("alchemy-element-editor").forEach((e=>{e.classList.remove("selected")})),this.previewWindow?.postMessage({message:"Alchemy.blurElements"}))}))}}customElements.define("alchemy-elements-window",ce);class de extends HTMLElement{#E=!1;#w=null;#_=null;constructor(){super(),this.addEventListener("mousedown",this),window.addEventListener("mousemove",this),window.addEventListener("mouseup",this)}handleEvent(e){switch(e.type){case"mousedown":e.stopPropagation(),this.onMouseDown();break;case"mouseup":this.onMouseUp();break;case"mousemove":this.#E&&this.onDrag(e.pageX)}}onMouseDown(){this.#E=!0,this.elementsWindow.isDragged=!0,this.previewWindow.isDragged=!0,this.classList.add("is-dragged")}onMouseUp(){this.#E=!1,this.elementsWindow.isDragged=!1,this.previewWindow.isDragged=!1,this.classList.remove("is-dragged")}onDrag(e){const t=window.innerWidth-e;this.elementsWindow.resize(t)}get elementsWindow(){return this.#w||(this.#w=document.querySelector("alchemy-elements-window")),this.#w}get previewWindow(){return this.#_||(this.#_=document.getElementById("alchemy_preview_window")),this.#_}}customElements.define("alchemy-elements-window-handle",de);class he extends HTMLElement{constructor(){super(),this.deleteLink=this.querySelector(".remove_file_link"),this.fileIcon=this.querySelector(".file_icon"),this.fileName=this.querySelector(".file_name"),this.formFieldId=this.deleteLink?.dataset.formFieldId,this.formField=this.querySelector(`#${this.formFieldId}`),this.deleteLink?.addEventListener("click",this)}handleEvent(e){"click"===e.type&&this.removeFile(),e.stopPropagation()}removeFile(){this.formField.value="",this.fileIcon.innerHTML="",this.fileName.innerHTML="",this.deleteLink?.classList.add("hidden"),this.closest("alchemy-element-editor").setDirty(this.formField)}}customElements.define("alchemy-file-editor",he);class ue extends HTMLElement{#k;constructor(){super(),this.#c()}#c(){this.hotkey&&key(this.hotkey,(()=>(this.filterField.focus(),!1))),this.filterField.addEventListener("keyup",(()=>{clearTimeout(this.#k),this.#k=setTimeout((()=>{const e=this.filterField.value;this.clearButton.style.visibility=e?"visible":"hidden",this.filter(e)}),this.debounceTime)})),this.clearButton.addEventListener("click",(e=>{e.preventDefault(),this.clear()})),this.filterField.addEventListener("focus",(()=>key.setScope("list_filter"))),key("esc","list_filter",(()=>{this.clear(),this.filterField.blur()}))}disconnectedCallback(){this.hotkey&&key.unbind(this.hotkey),key.unbind("esc","list_filter")}filter(e){""===e&&(this.clearButton.style.visibility="hidden");const t=[],s=new Set,i=e.toLowerCase();this.items.forEach((e=>{const n=e.getAttribute(this.nameAttribute)?.toLowerCase();if(-1!==n.indexOf(i)){t.push(e),s.add(e);let i=e.parentElement?.closest(this.itemsSelector);for(;i;)s.add(i),i=i.parentElement?.closest(this.itemsSelector)}})),this.items.forEach((e=>{e.classList.toggle("hidden",!s.has(e))})),1===t.length&&t[0].scrollIntoView({behavior:"smooth",block:"nearest"})}clear(){this.filterField.value="",this.clearButton.style.visibility="hidden",this.items.forEach((e=>e.classList.remove("hidden")))}get nameAttribute(){return this.getAttribute("name-attribute")||"name"}get clearButton(){return this.querySelector('button[type="button"]')}get filterField(){return this.querySelector('input[type="text"]')}get items(){return document.querySelectorAll(this.itemsSelector)}get itemsSelector(){return this.getAttribute("items-selector")}get debounceTime(){return parseInt(this.getAttribute("debounce-time"))||150}get hotkey(){return this.getAttribute("hotkey")}}customElements.define("alchemy-list-filter",ue);class me extends HTMLElement{#L;constructor(){super(),this.#L=this.innerHTML,(this.dismissable||"error"===this.type)&&this.addEventListener("click",this)}handleEvent(e){"click"===e.type&&this.dismiss()}connectedCallback(){this.innerHTML=`\n \n ${this.dismissable&&"error"===this.type?'':""}\n ${this.#L}\n `,this.dismissable&&"error"!==this.type&&setTimeout((()=>{this.dismiss()}),this.dismissDelay)}dismiss(){this.addEventListener("transitionend",(()=>this.remove())),this.classList.add("dismissed")}get dismissable(){return this.hasAttribute("dismissable")}get icon(){return this.getAttribute("icon")}get type(){return this.getAttribute("type")||"notice"}get dismissDelay(){return parseInt(this.noticesWrapper?.dataset.autoDismissDelay||5e3)}get iconName(){switch(this.icon||this.type){case"warning":case"warn":case"alert":return"alert";case"notice":return"check";case"info":case"hint":return"information";case"error":return"bug";default:return this.type}}get noticesWrapper(){return this.closest("#flash_notices")}}customElements.define("alchemy-message",me);class pe extends HTMLElement{connectedCallback(){F(this.message,this.getAttribute("type")||"notice"),this.remove()}get message(){return this.getAttribute("message")||this.innerHTML}}customElements.define("alchemy-growl",pe);class ge extends HTMLElement{static get observedAttributes(){return["name","size","icon-style"]}constructor(){super(),this.spriteUrl=document.querySelector('link[rel="preload"][as="image"]').getAttribute("href")}connectedCallback(){this.render()}attributeChangedCallback(){this.render()}render(){const e=this.size?` icon--${this.size}`:"";this.innerHTML=``}set name(e){this.setAttribute("name",e)}get iconName(){return this.getAttribute("name")}get size(){return this.getAttribute("size")}get style(){const e=this.getAttribute("icon-style");switch(e){case"none":return"";case null:return"-line";default:return`-${e}`}}}customElements.define("alchemy-icon",ge);class ye extends HTMLDetailsElement{#S="Alchemy.expanded_ingredient_groups";constructor(){super(),this.addEventListener("toggle",this),this.isInLocalStorage&&(this.open=!0)}handleEvent(){let e=this.localStorageItem;this.open?(this.toggleIcon.name="arrow-down-s",this.isInLocalStorage||e.push(this.id)):(this.toggleIcon.name="arrow-left-s",e=e.filter((e=>e!==this.id))),localStorage.setItem(this.#S,JSON.stringify(e))}get isInLocalStorage(){return this.localStorageItem.includes(this.id)}get localStorageItem(){const e=localStorage.getItem(this.#S);if(!e)return[];try{return JSON.parse(e)}catch(e){return console.error(e),[]}}get toggleIcon(){return this.querySelector("alchemy-icon")}}customElements.define("alchemy-ingredient-group",ye,{extends:"details"});class fe extends HTMLButtonElement{constructor(){super(),this.addEventListener("click",this),this.classList.add("icon_button"),this.setAttribute("type","button"),this.innerHTML=''}handleEvent(e){new Alchemy.LinkDialog({url:this.linkUrl,title:this.linkTitle,target:this.linkTarget,type:this.linkClass}).open().then((e=>this.setLink(e))),e.preventDefault()}setLink(e){""===e.url?(this.classList.remove("linked"),this.dispatchEvent(new CustomEvent("alchemy:unlink",{bubbles:!0}))):(this.classList.add("linked"),this.dispatchEvent(new CustomEvent("alchemy:link",{bubbles:!0,detail:e})))}get linkUrl(){return this.linkButtons.linkUrlField.value}get linkTitle(){return this.linkButtons.linkTitleField.value}get linkTarget(){return this.linkButtons.linkTargetField.value}get linkClass(){return this.linkButtons.linkClassField.value}get linkButtons(){return this.closest("alchemy-link-buttons")}}customElements.define("alchemy-link-button",fe,{extends:"button"});class ve extends HTMLButtonElement{constructor(){super(),this.addEventListener("click",this),this.classList.add("icon_button"),this.setAttribute("type","button"),this.linked=this.linked,this.innerHTML=''}handleEvent(e){this.linked&&(this.linked=!1,this.blur(),this.dispatchEvent(new CustomEvent("alchemy:unlink",{bubbles:!0}))),e.preventDefault()}set linked(e){e?(this.classList.replace("disabled","linked"),this.removeAttribute("tabindex")):(this.classList.replace("linked","disabled"),this.setAttribute("tabindex","-1"))}get linked(){return this.classList.contains("linked")}}customElements.define("alchemy-unlink-button",ve,{extends:"button"});class be extends HTMLElement{constructor(){super(),this.addEventListener("alchemy:link",this),this.addEventListener("alchemy:unlink",this)}handleEvent(e){switch(e.type){case"alchemy:link":this.setLink(e.detail);break;case"alchemy:unlink":this.removeLink()}e.stopPropagation()}setLink(e){this.linkUrlField.value=e.url,this.linkUrlField.dispatchEvent(new Event("change")),this.linkTitleField.value=e.title,this.linkClassField.value=e.type,this.linkTargetField.value=e.target,this.unlinkButton.linked=!0,this.setElementDirty()}removeLink(){this.linkUrlField.value="",this.linkUrlField.dispatchEvent(new Event("change")),this.linkTitleField.value="",this.linkClassField.value="",this.linkTargetField.value="",this.linkButton.classList.remove("linked"),this.unlinkButton.linked=!1,this.setElementDirty()}setElementDirty(){this.elementEditor.setDirty(this)}get linkButton(){return this.querySelector('[is="alchemy-link-button"]')}get unlinkButton(){return this.querySelector('[is="alchemy-unlink-button"]')}get ingredientEditor(){const e=this.dataset.ingredientId;return this.parentElement.closest(`[data-ingredient-id='${e}']`)}get elementEditor(){return this.closest("alchemy-element-editor")}get linkUrlField(){return this.ingredientEditor.querySelector("[data-link-value]")}get linkTitleField(){return this.ingredientEditor.querySelector("[data-link-title]")}get linkTargetField(){return this.ingredientEditor.querySelector("[data-link-target]")}get linkClassField(){return this.ingredientEditor.querySelector("[data-link-class]")}}customElements.define("alchemy-link-buttons",be);function Ee(e){let t=0===e?0:Math.floor(Math.log(e)/Math.log(1024));return t>3&&(t=3),(e/Math.pow(1024,t)).toFixed(2)+" "+["B","kB","MB","GB"][t]}customElements.define("alchemy-node-select",class extends D{_searchQuery(e,t){return{filter:{name_or_page_name_cont:e,...JSON.parse(this.queryParams)},page:t}}_renderResult(e){return this._renderListEntry(e)}_renderListEntry(e,t){const s=e.ancestors.map((e=>e.name)),i='';return`\n
\n \n
\n \n ${s.length>0?s.join(i)+i:""}\n \n \n ${this._hightlightTerm(e.name,t)}\n \n
\n
\n ${e.url||""}\n
\n
\n `}});class we extends H{constructor(){super(),this.file=null,this.request=null,this.progressEventLoaded=0,this.progressEventTotal=0,this.className="in-progress",this.valid=!0,this.value=0}initialize(e,t){this.file=e,this.request=t,this.progressEventTotal=e?e.size:0,this._validateFile(),this._addRequestEventListener()}render(){return`\n \n
\n ${this.file?.name}\n ${this.loadedSize}\n ${this.errorMessage}\n
\n \n \n \n `}afterRender(){if(this.querySelector("button").addEventListener("click",(()=>this.cancel())),this.file?.type.includes("image")){const e=new FileReader;e.readAsDataURL(this.file),e.addEventListener("load",(()=>{const t=new Image;t.src=e.result,this.prepend(t)}))}}cancel(){this.finished||(this.status="canceled",this.request?.abort(),this.dispatchCustomEvent("FileUpload.Change"))}_validateFile(){const e=Alchemy.uploader_defaults,t=e.file_size_limit*Math.pow(1024,2);let s;this.file?.size>t&&(s=d("Uploaded bytes exceed file size"));const i=this.file?.type.includes("image")?e.allowed_filetypes.alchemy_pictures:e.allowed_filetypes.alchemy_attachments;i.includes("*")||i.includes(this.file?.type.replace(/^\w+\/(\w+)(\+\w+)?/i,"$1"))||(s=d("File type not allowed")),s&&(this.valid=!1,this.errorMessage=s)}_addRequestEventListener(){this.request&&(this.request.upload.onprogress=e=>{this.progressEvent=e},this.request.onload=()=>{this.request.status<400?(this.status="successful",F(this.responseMessage)):(this.status="failed",this.errorMessage=this.responseMessage),this.dispatchCustomEvent("FileUpload.Change")},this.request.onerror=()=>{this.errorMessage=d("An error occurred during the transaction")})}get active(){return this.valid&&"canceled"!==this.status}get errorMessage(){return this._errorMessage||""}set errorMessage(e){this._errorMessage=e;const t=this.querySelector(".error-message");t&&(t.textContent=e),F(e,"error")}get finished(){return["canceled","successful","failed"].includes(this.status)}get loadedSize(){return`${Ee(this.progressEventLoaded)} / ${Ee(this.progressEventTotal)}`}get progressElement(){return this.querySelector("sl-progress-bar")}set progressEvent(e){this.progressEventLoaded=e.loaded,this.progressEventTotal=e.total,this.value=Math.round(e.loaded/e.total*100),this.querySelector(".loaded-size").textContent=this.loadedSize}get responseMessage(){try{return JSON.parse(this.request.responseText).message}catch(e){return`${this.request.status}: ${this.request.statusText}`}}get status(){return this._status}set status(e){this._status=e,this.className=e,this.progressElement?.toggleAttribute("indeterminate","upload-finished"===e)}get valid(){return this._valid}set valid(e){this._valid=e,this.classList.toggle("invalid",!e)}get value(){return this._value}set value(e){this._value=e,this.progressElement&&(this.progressElement.value=e),100===e&&(this.status="upload-finished"),this.dispatchCustomEvent("FileUpload.Change")}}customElements.define("alchemy-file-upload",we);class _e extends H{#v=!1;constructor(){super(),this.buttonLabel=d("Cancel all uploads"),this.fileUploads=[],this.fileCount=0,this.className="in-progress",this.visible=!0,this.handleFileChange=()=>this._updateView()}initialize(e=[]){this.fileUploads=e,this.fileCount=e.length}afterRender(){this.actionButton=this.querySelector("button"),this.actionButton.addEventListener("click",(()=>{this.finished?this.onComplete(this.status):this.cancel()})),this.fileUploads.forEach((e=>{this.querySelector(".single-uploads").append(e)}))}cancel(){this._activeUploads().forEach((e=>{e.cancel()})),this._setupCloseButton()}connected(){this._updateView(),this.addEventListener("Alchemy.FileUpload.Change",this.handleFileChange)}disconnected(){this.removeEventListener("Alchemy.FileUpload.Change",this.handleFileChange)}onComplete(e){}render(){return`\n \n
\n \n\n \n \n \n
\n
\n
\n `}_activeUploads(){return this.fileUploads.filter((e=>e.active))}_setupCloseButton(){this.buttonLabel=d("Close"),this.actionButton.ariaLabel=this.buttonLabel,this.actionButton.parentElement.content=this.buttonLabel}_sumFileProgresses(e){return this._activeUploads().reduce(((t,s)=>s[e]+t),0)}_updateView(){const e=this.status;this.className=e,this.progressElement.value=this.totalProgress,this.progressElement.toggleAttribute("indeterminate","upload-finished"===e),this.querySelector(".overall-progress-value > span").textContent=this.overallProgressValue,this.querySelector(".overall-upload-value").textContent=this.overallUploadSize,this.finished?(this._setupCloseButton(),this.onComplete(e)):this.visible=!0}get finished(){return this._activeUploads().every((e=>e.finished))}get overallUploadSize(){this._activeUploads().filter((e=>e.value>=100)).length;return this.totalProgress,this._activeUploads().length,`${Ee(this._sumFileProgresses("progressEventLoaded"))} / ${Ee(this._sumFileProgresses("progressEventTotal"))}`}get overallProgressValue(){const e=this._activeUploads().filter((e=>e.value>=100)).length;return`${this.totalProgress}% (${e} / ${this._activeUploads().length})`}get progressElement(){return this.querySelector("sl-progress-bar")}get status(){const e=this._activeUploads().map((e=>e.className));return e.includes("failed")?"failed":0===e.length?"canceled":e.every((t=>t===e[0]))?e[0]:"in-progress"}get totalProgress(){const e=this._activeUploads().reduce(((e,t)=>e+t.file.size),0);let t=Math.ceil(this._activeUploads().reduce(((t,s)=>{const i=s.file.size/e;return s.value*i+t}),0));return t>100&&(t=100),t}get visible(){return this.#v}set visible(e){this.classList.toggle("visible",e),this.#v=e}}customElements.define("alchemy-upload-progress",_e);customElements.define("alchemy-uploader",class extends H{static properties={dropzone:{default:!1}};connected(){this.fileInput.addEventListener("change",(e=>{this._uploadFiles(Array.from(e.target.files))})),this.dropzone&&this._dragAndDropBehavior(),this.addEventListener("Alchemy.upload.successful",this)}handleEvent(e){if("Alchemy.upload.successful"===e.type)this._handleUploadComplete()}_handleUploadComplete(){setTimeout((()=>{const e=this.redirectUrl,t=this.closest("turbo-frame");this.uploadProgress.visible=!1,e&&(t?(t.setAttribute("src",e),t.reload()):Turbo.visit(e))}),750)}_dragAndDropBehavior(){const e=document.querySelector(this.dropzone);let t=!1;const s=s=>{t!==s&&(t=s,e.classList.toggle("dragover"))};e.addEventListener("dragleave",(()=>s(!1))),e.addEventListener("drop",(async e=>{e.preventDefault(),s(!1);const t=[...e.dataTransfer.items].map((e=>e.getAsFile()));this._uploadFiles(t)})),e.addEventListener("dragover",(e=>{e.preventDefault(),s(!0)}))}_uploadFiles(e){let t=0;const s=e.map((e=>{const s=new XMLHttpRequest,i=new we;return i.initialize(e,s),Alchemy.uploader_defaults.upload_limit-1{this.dispatchCustomEvent(`upload.${e}`)},document.body.append(this.uploadProgress)}get fileInput(){return this.querySelector("input[type='file']")}get redirectUrl(){return this.getAttribute("redirect-url")}});customElements.define("alchemy-overlay",class extends H{render(){return`\n \n
\n ${this.getAttribute("text")}\n
\n `}set show(e){this.classList.toggle("visible",e)}});const ke="BUTTON";class Le extends HTMLElement{connectedCallback(){this.pageId=this.getAttribute("page-id"),this.folded=this.hasAttribute("folded"),this.folderButton?.addEventListener("click",this)}disconnectedCallback(){this.folderButton?.removeEventListener("click",this)}async handleEvent(e){"click"===e.type&&await this.handleFolderClick(e)}async handleFolderClick(e){e.preventDefault(),e.stopPropagation();const t=e.currentTarget;t.innerHTML="";const s=new y("small");s.spin(t);try{await Z(Alchemy.routes.fold_admin_page_path(this.pageId),null,"text/vnd.turbo-stream.html"),this.folded=!this.folded,this.toggleAttribute("folded",this.folded),this.toggleChildren(),this.updateFolderIcon()}catch(e){F(e.message||e,"error"),this.updateFolderIcon()}finally{s.stop()}}toggleChildren(){const e=this.querySelector(`#page_${this.pageId}_children`);e&&e.classList.toggle("hidden",this.folded)}updateFolderIcon(){if(this.folderButton){const e=this.folded?"arrow-right-s":"arrow-down-s";this.folderButton.innerHTML=``}}updateFolderButton(){const e=this.querySelector(".page_folder");if(!e)return;const t=this.hasChildren||this.folded;if(t&&"SPAN"===e.tagName){const t=this.folded?"arrow-right-s":"arrow-down-s";e.outerHTML=``,this.folderButton?.addEventListener("click",this)}else t||e.tagName!==ke?t&&e.tagName===ke&&this.updateFolderIcon():e.outerHTML=''}get hasChildren(){const e=this.querySelector(`#page_${this.pageId}_children`);return!!e&&e.querySelectorAll(":scope > alchemy-page-node").length>0}get folderButton(){return this.querySelector("button.page_folder")}}customElements.define("alchemy-page-node",Le);class Se extends HTMLElement{connectedCallback(){const e=this.querySelector("alchemy-datepicker:has(#page_public_on)"),t=this.querySelector("alchemy-datepicker:has(#page_public_until)"),s=this.querySelector(".page-publication-date-fields"),i=this.querySelector("#page_public");i&&i.addEventListener("click",(function(i){const n=i.target,r=new Date;n.checked?(s.classList.remove("hidden"),e.flatpickr.setDate(r)):(s.classList.add("hidden"),e.flatpickr.clear()),t.flatpickr?.clear()}))}}customElements.define("alchemy-page-publication-fields",Se);customElements.define("alchemy-page-select",class extends D{get pageId(){return this.selection?JSON.parse(this.selection).id:void 0}_searchQuery(e,t){return{q:{name_cont:e,...JSON.parse(this.queryParams)},page:t}}_parseResponse(e){const t=e.meta;return{results:e.pages,more:t.page*t.per_page\n
\n \n ${this._hightlightTerm(e.name,t)}\n ${e.site.name}\n
\n
\n ${e.url_path}\n ${e.language_code}\n
\n
\n `}});class Ce extends HTMLElement{constructor(){super(),this.addEventListener("change",this)}handleEvent(e){if("change"===e.type)this.onChange()}onChange(){const e=new URL(this.getAttribute("url")),t=this.querySelector("select");e.searchParams.set("language_id",t.value),Turbo.visit(e,{frame:"picture_descriptions"})}}customElements.define("alchemy-picture-description-select",Ce);class xe extends HTMLElement{constructor(){super(),this.cropFromField=this.querySelector("[data-crop-from]"),this.cropSizeField=this.querySelector("[data-crop-size]"),this.pictureIdField=this.querySelector("[data-picture-id]"),this.targetSizeField=this.querySelector("[data-target-size]"),this.imageCropperField=this.querySelector("[data-image-cropper]"),this.image=this.querySelector("img"),this.pictureThumbnail=this.querySelector("alchemy-picture-thumbnail"),this.deleteButton=this.querySelector(".picture_tool.delete"),this.cropLink=this.querySelector(".crop_link"),this.targetSize=this.targetSizeField.dataset.targetSize,this.pictureId=this.pictureIdField.value,this.update=function(e,t){let s;return function(...i){const n=this;clearTimeout(s),s=setTimeout((()=>e.apply(n,i)),t)}}((()=>{this.updateImage(),this.updateCropLink()}),125),this.deleteButton?.addEventListener("click",this.removeImage.bind(this))}connectedCallback(){this.observer=new MutationObserver(this.mutationCallback.bind(this)),this.observer.observe(this.cropFromField,{attributes:!0}),this.observer.observe(this.cropSizeField,{attributes:!0}),this.observer.observe(this.pictureIdField,{attributes:!0})}disconnectedCallback(){this.observer.disconnect()}mutationCallback(e){for(const t of e)"pictureId"in t.target.dataset&&(this.cropFromField.value="",this.cropSizeField.value="",this.pictureId=t.target.value),this.update()}updateImage(){this.pictureId&&(this.pictureThumbnail.loading=!0,G(Alchemy.routes.url_admin_picture_path(this.pictureId),{crop:this.imageCropperEnabled,crop_from:this.cropFrom,crop_size:this.cropSize,flatten:!0,size:"160x120"}).then((({data:e})=>{this.pictureThumbnail.src=e.url,this.pictureThumbnail.image.alt=e.alt,this.pictureThumbnail.image.title=e.title,this.setElementDirty()})).catch((e=>{console.error(e.message||e),F(e.message||e,"error")})))}removeImage(){this.pictureThumbnail.innerHTML='',this.pictureIdField.value="",this.image=null,this.cropLink.classList.add("disabled"),this.setElementDirty()}setElementDirty(){this.closest(".element-editor").setDirty(this)}updateCropLink(){this.pictureId&&this.imageCropperEnabled&&(this.cropLink.classList.remove("disabled"),this.cropLink.href.match(/(picture_id=)\d+/)?this.cropLink.href=this.cropLink.href.replace(/(picture_id=)\d+/,"$1"+this.pictureId):this.cropLink.href=this.cropLink.href+`&picture_id=${this.pictureId}`)}get cropFrom(){return""===this.cropFromField.value?this.defaultCropFrom.join("x"):this.cropFromField.value}get cropSize(){return""===this.cropSizeField.value?this.defaultCropSize.join("x"):this.cropSizeField.value}get defaultCropSize(){if(!this.imageCropperEnabled)return[];const e=this.targetSize.split("x").map((e=>parseInt(e))),t=(s=e[0]/this.imageFileWidth,i=e[1]/this.imageFileHeight,s>=i?s:i);var s,i;return[Math.round(e[0]/t),Math.round(e[1]/t)]}get defaultCropFrom(){if(!this.imageCropperEnabled)return[];const e=this.defaultCropSize;return[Math.round((this.imageFileWidth-e[0])/2),Math.round((this.imageFileHeight-e[1])/2)]}get imageFileWidth(){return parseInt(this.pictureIdField.dataset.imageFileWidth)}get imageFileHeight(){return parseInt(this.pictureIdField.dataset.imageFileHeight)}get imageCropperEnabled(){return"true"===this.targetSizeField.dataset.imageCropper}}customElements.define("alchemy-picture-editor",xe);class Te extends HTMLElement{constructor(){super(),this.classList.add("thumbnail_background"),this.spinner=new y("small"),this.src&&this.start()}handleEvent(e){switch(e.type){case"load":this.#C();break;case"error":this.#x(e)}}connectedCallback(){this.#T()}disconnectedCallback(){this.image?.removeEventListener("load",this),this.image?.removeEventListener("error",this),this.stop()}createImage(e=this.src,t=this.name){this.image=new Image,this.image.src=e,t&&(this.image.alt=t)}start(e){this.createImage(e),this.image.addEventListener("load",this),this.image.addEventListener("error",this),this.load()}load(){this.image?.complete||(this.setAttribute("loading","loading"),this.innerHTML="",this.spinner.spin(this))}stop(){this.classList.remove("loading"),this.spinner.stop()}#C(){this.spinner.stop(),this.removeAttribute("loading")}#x(e){const t=`Could not load ${this.image.src}`,s=this.closest(".ingredient-editor");this.spinner.stop(),this.innerHTML=`\n \n \n \n `,console.error(t,e)}#T(){this.image?.complete?this.replaceChildren(this.image):this.image&&this.append(this.image)}set loading(e){e?this.load():this.stop()}set src(e){this.start(e),this.#T()}get name(){return this.getAttribute("name")}get src(){return this.getAttribute("src")}}customElements.define("alchemy-picture-thumbnail",Te);class Ae extends HTMLElement{constructor(){super(),this.addEventListener("submit",this)}connectedCallback(){document.addEventListener("alchemy:page-dirty",this)}disconnectedCallback(){document.removeEventListener("alchemy:page-dirty",this)}handleEvent(e){switch(e.type){case"alchemy:page-dirty":this.markDirty(e.detail);break;case"submit":this.button.loading=!0}}markDirty(e){this.button.variant="primary",this.button.disabled=!1,this.tooltip.content=e.tooltip}get button(){return this.querySelector("sl-button")}get tooltip(){return this.querySelector("sl-tooltip")}}customElements.define("alchemy-publish-page-button",Ae);class $e extends HTMLSelectElement{#A;connectedCallback(){this.classList.add("alchemy_selectbox"),this.#A=$(this).select2({minimumResultsForSearch:5,dropdownAutoWidth:!0,allowClear:!!this.allowClear}),this.allowClear||this.multiple||this.#A.prev(".select2-container").find(".select2-search-choice-close").remove()}enable(){this.removeAttribute("disabled"),this.#$()}disable(){this.setAttribute("disabled","disabled"),this.#$()}setOptions(e,t=void 0){let s=this.value;this.innerHTML="",t&&this.add(new Option(t,"")),e.forEach((e=>{this.add(new Option(e.text,e.id,!1,e.id===s))})),this.#$()}#$(){this.#A.trigger("change")}get allowClear(){return this.dataset.hasOwnProperty("allowClear")||this.multiple}}customElements.define("alchemy-select",$e,{extends:"select"});class Fe extends HTMLElement{connectedCallback(){this.searchInput=document.querySelector(".search_input_field"),this.clearButton=document.querySelector("#search_field_clear"),this.resultCounter=document.querySelector("#page_filter_result"),this.setupSearch(),requestAnimationFrame((()=>{this.setupSortables()})),this.observer=new MutationObserver((e=>{e.forEach((e=>{e.addedNodes.forEach((e=>{e.nodeType===Node.ELEMENT_NODE&&(e.classList?.contains("children")&&this.setupSortable(e),e.querySelectorAll(".children").forEach((e=>this.setupSortable(e))))}))}))})),this.observer.observe(this,{childList:!0,subtree:!0})}disconnectedCallback(){this.teardownSearch(),this.observer?.disconnect()}setupSearch(){this.searchInput?.addEventListener("input",this),this.clearButton?.addEventListener("click",this)}teardownSearch(){this.searchInput?.removeEventListener("input",this),this.clearButton?.removeEventListener("click",this)}handleEvent(e){"input"===e.type&&e.target===this.searchInput?this.handleSearch(e):"click"===e.type&&e.target===this.clearButton&&this.handleClearSearch(e)}handleSearch(e){const t=e.target.value.toLowerCase().trim();""!==t?this.filterPages(t):this.clearFilter()}filterPages(e){const t=this.querySelectorAll(".sitemap_page");let s=0,i=null;t.forEach((t=>{(t.getAttribute("name")||"").toLowerCase().includes(e)?(t.classList.add("highlight"),t.classList.remove("no-match"),s++,i||(i=t)):(t.classList.remove("highlight"),t.classList.add("no-match"))})),1===s?(this.resultCounter.textContent=`1 ${d("page_found")}`,this.resultCounter.style.display="block"):s>1?(this.resultCounter.textContent=`${s} ${d("pages_found")}`,this.resultCounter.style.display="block"):this.resultCounter.style.display="none",i&&i.scrollIntoView({behavior:"smooth",block:"center"})}clearFilter(){this.querySelectorAll(".sitemap_page").forEach((e=>{e.classList.remove("highlight","no-match")})),this.resultCounter.style.display="none"}handleClearSearch(e){e.preventDefault(),this.searchInput.value="",this.clearFilter()}setupSortable(e){new i(e,{group:"pages",animation:150,fallbackOnBody:!0,swapThreshold:.65,handle:".page-icon.handle",draggable:"alchemy-page-node",onEnd:e=>this.handleSort(e)})}setupSortables(){this.querySelectorAll(".children").forEach((e=>this.setupSortable(e)))}async handleSort(e){if(e.from===e.to&&e.oldIndex===e.newIndex)return;const t=e.item,s=t.pageId,i=Alchemy.routes.move_admin_page_path(s),n={target_parent_id:e.to.dataset.parentId,new_position:e.newIndex};S(!0);try{const r=await Z(i,n),l=await r.data,o=t.querySelector(`#page_${s}`);if(o){const e=o.querySelector(".sitemap_url");e&&l.url_path&&(e.textContent=l.url_path)}this.updateFolderIcons(e.from,e.to),F(d("Successfully moved page"))}catch(e){F(e.message||e,"error"),window.location.reload()}finally{S(!1)}}updateFolderIcons(e,t){const s=e.closest("alchemy-page-node");if(s?.updateFolderButton(),e!==t){const e=t.closest("alchemy-page-node");e?.updateFolderButton()}}}customElements.define("alchemy-sitemap",Fe);const qe={draggable:".element-editor",handle:".element-handle.draggable",ghostClass:"dragged",animation:150,swapThreshold:.65,easing:"cubic-bezier(1, 0, 0, 1)"};function Ie(e){const t=e.item.dataset.elementName;document.querySelectorAll(`[data-droppable-elements~="${t}"]`).forEach((e=>e.classList.add("droppable-elements")))}function Be(e){const t=e.item,s=e.to.parentElement.closest(".element-editor"),i={element_id:t.dataset.elementId,position:e.newIndex+1};s&&(i.parent_element_id=s.dataset.elementId),e.target===e.to&&ee(Alchemy.routes.order_admin_elements_path,i).then((e=>{const s=e.data;F(s.message),s.pageHasUnpublishedChanges&&re(s),B(),t.updateTitle(s.preview_text)}))}function Me(){document.querySelectorAll("[data-droppable-elements]").forEach((e=>e.classList.remove("droppable-elements")))}class ze extends HTMLElement{connectedCallback(){const e={name:this.dataset.elementName,put:(e,t,s)=>e.el.dataset.droppableElements.split(" ").includes(s.dataset.elementName)};new i(this,{...qe,onStart:Ie,onSort:Be,onEnd:Me,group:e})}}customElements.define("alchemy-sortable-elements",ze);customElements.define("alchemy-spinner",class extends H{static properties={size:{default:"medium"},color:{default:"currentColor"}};render(){return this.className=`spinner spinner--${this.size}`,`\n \n \n \n \n \n `}});class He extends HTMLElement{async connectedCallback(){await h(),this.classList.add("autocomplete_tag_list"),$(this.input).select2(this.select2Config)}get input(){return this.getElementsByTagName("input")[0]}get select2Config(){return{tags:!0,tokenSeparators:[","],openOnEnter:!1,minimumInputLength:1,createSearchChoice:this.#F,ajax:{url:this.getAttribute("url"),dataType:"json",data:e=>({term:e}),results:e=>({results:e})},initSelection:this.#q}}#F(e,t){if(0===$(t).filter((function(){return 0===this.text.localeCompare(e)})).length)return{id:e,text:e}}#q(e,t){const s=[];$(e.val().split(",")).each((function(){s.push({id:this.trim(),text:this})})),t(s)}}customElements.define("alchemy-tags-autocomplete",He);const Pe="alchemy-dark",De="alchemy";customElements.define("alchemy-tinymce",class extends H{#I=null;connected(){this.className="tinymce_container";const e={root:document.getElementById("element_area"),rootMargin:"0px",threshold:[.05]};this.tinymceIntersectionObserver=new IntersectionObserver(((e,t)=>{e.forEach((e=>{e.intersectionRatio>0&&(this._initTinymceEditor(),t.unobserve(e.target))}))}),e),this.tinymceIntersectionObserver.observe(this),this._setupThemeChangeListener()}disconnected(){null!==this.tinymceIntersectionObserver&&this.tinymceIntersectionObserver.disconnect(),this._removeThemeChangeListener(),tinymce.get(this.editorId)?.remove(this.editorId)}render(){return`\n ${this.initialContent}\n \n `}afterRender(){this.style.minHeight=`${this.minHeight}px`,this.editor.style.display="none"}_initTinymceEditor(){tinymce.init(this.configuration).then((e=>{e.forEach((e=>this._setupEditor(e)))}))}_setupEditor(e){e.show();const t=this.getElementsByTagName("alchemy-spinner")[0];t&&t.remove(),this.elementEditor&&(e.on("dirty",(e=>{this.elementEditor.setDirty(e.target.editorContainer)})),e.on("click",(()=>this.elementEditor.onClickElement(!1))))}_setupThemeChangeListener(){this.darkModeMediaQuery=window.matchMedia("(prefers-color-scheme: dark)"),this.themeChangeHandler=e=>this._handleThemeChange(e),this.darkModeMediaQuery.addEventListener("change",this.themeChangeHandler)}_removeThemeChangeListener(){this.darkModeMediaQuery&&this.themeChangeHandler&&this.darkModeMediaQuery.removeEventListener("change",this.themeChangeHandler)}_handleThemeChange(e){const t=tinymce.get(this.editorId);if(t){const s=e.matches?Pe:De,i=e.matches?Pe:De;t.remove(),tinymce.init({content_css:i,...this.configuration,skin:s}).then((e=>{e.forEach((e=>this._setupEditor(e)))}))}}get configuration(){const e={};this.getAttributeNames().forEach((t=>{if(!["class","id","is","name","style"].includes(t)){const s=this.getAttribute(t),i=t.replaceAll("-","_");if(s===t||""===s)e[i]=!0;else try{e[i]=JSON.parse(s)}catch(t){e[i]=s}}}));const t={content_css:this.preferredTheme,...Alchemy.TinymceDefaults,...e,language:c(),selector:`#${this.editorId}`,skin:this.preferredTheme};return t.height=t.min_height,t}get preferredTheme(){return window.matchMedia("(prefers-color-scheme: dark)").matches?Pe:De}get editorId(){return this.editor.id}get editor(){return this.getElementsByTagName("textarea")[0]}get elementEditor(){return document.getElementById(this.editorId).closest("alchemy-element-editor")}get minHeight(){return this.#I||this.configuration.min_height}set minHeight(e){this.#I=e}});class Ne extends HTMLElement{async connectedCallback(){const e=new y("small");e.spin(this);try{const e=await fetch(this.url,{credentials:"include"}),t=await e.json();e.ok?this.showStatus(t):this.showError(e)}catch(e){this.showError(e)}finally{e.stop()}}get url(){return this.getAttribute("url")}showStatus(e){"true"==e.status?this.querySelector(".update_available").classList.remove("hidden"):this.querySelector(".up_to_date").classList.remove("hidden")}showError(e){this.querySelector(".error").classList.remove("hidden"),console.error("[alchemy] Error fetching update status",e)}}var Oe;customElements.define("alchemy-update-check",Ne),Oe=Handlebars.template,(Handlebars.templates=Handlebars.templates||{})["node_folder.hbs"]=Oe({0:function(e,t,s,i,n){return"right"},1:function(e,t,s,i,n){return"down"},compiler:[8,">= 4.3.0"],main:function(e,t,s,i,n){var r,l=e.lambda,o=e.escapeExpression,a=e.lookupProperty||function(e,t){if(Object.prototype.hasOwnProperty.call(e,t))return e[t]};return'\n \n\n'},useData:!0}),n("tooltip.show",{keyframes:[{transform:"translateY(10px)",opacity:"0"},{transform:"translateY(0)",opacity:"1"}],options:{duration:100}}),n("tooltip.hide",{keyframes:[{transform:"translateY(0)",opacity:"1"},{transform:"translateY(10px)",opacity:"0"}],options:{duration:100}}),n("dialog.show",{keyframes:[{transform:"scale(0.98)",opacity:"0"},{transform:"scale(1)",opacity:"1"}],options:{duration:150}}),n("dialog.hide",{keyframes:[{transform:"scale(1)",opacity:"1"},{transform:"scale(0.98)",opacity:"0"}],options:{duration:150}});const Re=document.querySelector('link[rel="preload"][as="image"]').getAttribute("href"),Ue={"x-lg":"close",caret:"arrow-down-s"},je={resolver:e=>`${Re}#ri-${Ue[e]||e}-line`,mutator:e=>{e.setAttribute("fill","currentColor"),e.setAttribute("viewBox","0 0 24 24")},spriteSheet:!0};function We(e,t,s,i){document.querySelectorAll(t).forEach((t=>{t.addEventListener(e,(e=>{const n=Array.from(t.querySelectorAll(s));let r=e.target;for(;r!==t;){if(n.includes(r))return void i.call(r,e);r=r.parentElement}}))}))}r("default",je),r("system",je);class Je{#B=!1;#M=null;#z=null;#H=null;constructor(e,t){this.image=e,this.defaultBox=t.default_box,this.aspectRatio=t.ratio,this.#z=document.getElementById(t.crop_from_form_field_id),this.#H=document.getElementById(t.crop_size_form_field_id),this.elementId=t.element_id,this.dialog=Alchemy.currentDialog(),this.dialog&&(this.dialog.options.closed=()=>this.destroy(),this.bind()),this.init()}get cropperOptions(){return{aspectRatio:this.aspectRatio,viewMode:1,zoomable:!1,checkCrossOrigin:!1,checkOrientation:!1,data:this.box,cropend:()=>{const e=this.#M.getData(!0);this.update(e)}}}get cropFrom(){if(this.#z?.value)return this.#z.value.split("x").map((e=>parseInt(e)))}get cropSize(){if(this.#H?.value)return this.#H.value.split("x").map((e=>parseInt(e)))}get box(){return this.cropFrom&&this.cropSize?{x:this.cropFrom[0],y:this.cropFrom[1],width:this.cropSize[0],height:this.cropSize[1]}:this.defaultBoxSize}get defaultBoxSize(){return{x:this.defaultBox[0],y:this.defaultBox[1],width:this.defaultBox[2],height:this.defaultBox[3]}}init(){this.#B||(this.#M=new l(this.image,this.cropperOptions),this.#B=!0)}update(e){this.#z.value=`${e.x}x${e.y}`,this.#z.dispatchEvent(new Event("change")),this.#H.value=`${e.width}x${e.height}`,this.#H.dispatchEvent(new Event("change"))}reset(){this.#M.setData(this.defaultBoxSize),this.update(this.defaultBoxSize)}destroy(){return this.#M&&this.#M.destroy(),this.#B=!1,!0}bind(){this.dialog.dialog_body.find('button[type="submit"]').on("click",(()=>(document.querySelector(`[data-element-id='${this.elementId}']`).setDirty(),this.dialog.close(),!1))),this.dialog.dialog_body.find('button[type="reset"]').on("click",(()=>(this.reset(),!1)))}}class Ve extends b{constructor(e,t={}){super(e,t)}init(){$(".zoomed-picture-background").on("click",(e=>{if(e.stopPropagation(),"IMG"!==e.target.nodeName)return this.close(),!1})),$(".picture-overlay-handle").on("click",(e=>(this.dialog.toggleClass("hide-form"),!1))),this.$previous=$(".previous-picture"),this.$next=$(".next-picture"),this.#P(),super.init()}previous(){null!=this.$previous[0]&&this.$previous[0].click()}next(){null!=this.$next[0]&&this.$next[0].click()}build(){this.dialog_container=$('
'),this.dialog=$('
'),this.dialog_body=$('
'),this.close_button=$('\n \n '),this.dialog.append(this.close_button),this.dialog.append(this.dialog_body),this.dialog_container.append(this.dialog),this.overlay=$('
'),this.$body.append(this.overlay),this.$body.append(this.dialog_container)}#P(){this.$document.keydown((e=>{if("INPUT"===e.target.nodeName||"TEXTAREA"===e.target.nodeName)return!0;switch(e.which){case 37:return this.previous(),!1;case 39:return this.next(),!1;default:return!0}}))}}function Xe(){return document.querySelectorAll("#picture_archive input:checked")}function Qe(){const e=document.querySelector("#select_all_pictures"),t=document.querySelector(".selected_item_tools");We("click",".toolbar_buttons","a#select_all_pictures",(s=>{s.preventDefault(),e.classList.toggle("active");const i=e.classList.contains("active");!function(e){document.querySelectorAll(".picture_tool.select input[type='checkbox']").forEach((t=>{t.checked=e,t.closest(".picture_thumbnail").classList.toggle("active",e)}))}(i),t.classList.toggle("hidden",!i)})),We("change",".picture_tool.select","input",(e=>{t.classList.toggle("hidden",0===Xe().length);const s=e.target.parentElement.classList,i=e.target.checked;s.toggle("visible",i)})),We("click",".selected_item_tools","a#edit_multiple_pictures",(e=>{e.preventDefault();_(function(e){const t=new URL(e);return Xe().forEach((e=>t.searchParams.append(e.name,e.value))),t.toString()}(e.target.href),{title:e.target.title,size:"400x295"})}))}function Ye(){document.querySelectorAll("li.menu-item").forEach((e=>{const t=e.querySelector(".nodes_tree-left_images"),s=e.querySelector(".children"),i={folded:"true"===e.dataset.folded,id:e.dataset.id,type:e.dataset.type};s.children.length>0||i.folded?t.innerHTML=Handlebars.templates["node_folder.hbs"]({node:i}):t.innerHTML=" "}))}function Ke(e){Z(Alchemy.routes[e.item.dataset.type].move_api_path(e.item.dataset.id),{target_parent_id:e.to.dataset.recordId,new_position:e.newIndex}).then((()=>{F(Alchemy.t("Successfully moved menu item")),Ye()})).catch((e=>{F(e.message||e,"error")}))}function Ge(){We("click",".nodes_tree",".node_folder",(function(){const e=this.dataset.recordId,t=this.closest("li.menu-item"),s=Alchemy.routes[this.dataset.recordType].toggle_folded_api_path(e),i=t.querySelector(".children");Z(s).then((()=>{i.classList.toggle("folded"),t.dataset.folded="true"==t.dataset.folded?"false":"true",Ye()})).catch((e=>{F(e.message||e)}))})),Ye(),document.querySelectorAll(".nodes_tree ul.children").forEach((e=>{new i(e,{group:"nodes",animation:150,fallbackOnBody:!0,swapThreshold:.65,handle:".node_name",invertSwap:!0,onEnd:Ke})}))}void 0===window.Alchemy&&(window.Alchemy={}),Object.assign(Alchemy,{closeCurrentDialog:w,currentDialog:E,...x,t:d,FixedElements:A,growl:F,LinkDialog:class extends b{#D;constructor(e){const t=new URL(Alchemy.routes.link_admin_pages_path,window.location),s={url:e.url,selected_tab:e.type,link_title:e.title,link_target:e.target};Object.keys(s).forEach((e=>{s[e]&&t.searchParams.set(e,s[e])})),super(t.href,{size:"600x320",title:d("Link")})}replace(e){super.replace(e),this.#c()}open(){return super.open(),new Promise((e=>this.#D=e))}#c(){const e=document.querySelector('[data-link-form-type="internal"]'),t=document.querySelector('[data-link-form-type="file"] alchemy-attachment-select');e.addEventListener("Alchemy.RemoteSelect.Change",(e=>{this.#N(e.detail.added)})),t.addEventListener("Alchemy.RemoteSelect.Change",(e=>{const t=e.detail.added;document.getElementById("file_link").value=t?t.url:""})),document.querySelectorAll("[data-link-form-type]").forEach((e=>{e.addEventListener("submit",(e=>{e.preventDefault(),this.#O(e.target.dataset.linkFormType)}))}))}#N(e=null){const t=document.getElementById("internal_link"),s=document.querySelector('[data-link-form-type="internal"] alchemy-dom-id-api-select');t.value=e?e.url_path:"",s.page=e?e.id:void 0}#O(e){const t=document.getElementById("element_anchor");let s=document.getElementById(`${e}_link`).value;if("internal"===e&&""!==t.value)s=s.replace(q,"")+t.value;else if("external"===e&&!s.match(Alchemy.link_url_regexp))return void this.#R();this.#D({url:s.trim(),title:document.getElementById(`${e}_link_title`).value,target:document.getElementById(`${e}_link_target`)?.value,type:e}),this.close()}#R(){const e=document.getElementById("errors");e.querySelector("ul").innerHTML=`
  • ${Alchemy.t("url_validation_failed")}
  • `,e.style.display="block"}},pleaseWaitOverlay:S,Spinner:y,reloadPreview:B}),t.start(),e.config.forms.confirm=L,document.addEventListener("turbo:load",(function(){document.documentElement.classList.remove("no-js"),p(),document.querySelectorAll(".please_wait").forEach((e=>{e.addEventListener("click",S)})),document.querySelectorAll("a.button").forEach((e=>{e.setAttribute("tabindex",0)})),key.filter=function(e){let t=(e.target||e.srcElement).tagName;return key.isPressed("esc")||!("INPUT"===t||"SELECT"===t||"TEXTAREA"===t)}}));export{Je as ImageCropper,Ve as ImageOverlay,Ge as NodeTree,D as RemoteSelect,We as on,Qe as pictureSelector}; +import"handlebars";import"jquery";import"@ungap/custom-elements";import{Turbo as e}from"@hotwired/turbo-rails";import"select2";import t from"@rails/ujs";import"keymaster";import"clipboard";import s from"flatpickr";import i from"sortablejs";import"tinymce";import{setDefaultAnimation as n,registerIconLibrary as r}from"shoelace";import l from"cropperjs";const o=/\./;function a(e){const t=c(),s=Alchemy.translations;return s?o.test(e)?function(e,t){const s=t.split(o),i=e[s[0]];return i&&i[s[1]]||t}(s,e):s[e]||e:(console.warn(`Translations for locale ${t} not found!`),e)}function c(){return document.documentElement.lang?document.documentElement.lang:"en"}function h(e,t=void 0){let s=a(e);return t?s.replace(/%\{.+\}/,t):s}async function d(){const e=c();"en"!==e&&(await import(`select2/${e}.js`),$.extend($.fn.select2.defaults,$.fn.select2.locales[e]))}const u=[];function m(e){return!(!$(e.target).is("input, textarea")&&"?"===String.fromCharCode(e.which))||(_("/admin/help",{title:Alchemy.t("help"),size:"400x492"}),!1)}function p(e=document){e instanceof jQuery&&(e=e[0]),e===document&&(document.removeEventListener("keypress",m),document.addEventListener("keypress",m),u.forEach((e=>key.unbind(e))));const t=e.querySelectorAll(".search_input_field"),s=e.querySelectorAll(".search_field_clear, .js_filter_field_clear");key("alt+f",(function(){return key.setScope("search"),t.forEach((e=>e.focus({focusVisible:!0}))),!1})),u.push("alt+f"),key("esc","search",(function(){s.forEach((e=>e.click())),t.forEach((e=>e.blur()))})),u.push("esc"),e.querySelectorAll("[data-alchemy-hotkey]").forEach((function(e){const t=e.dataset.alchemyHotkey;key(t,(()=>e.click())),u.push(t)}))}function g(e){const t=document.createElement("template");return t.innerHTML=e,t.content.children[0]}let y=class{constructor(e,t="currentColor"){this.size=e,this.color=t,this.spinner=void 0}get el(){return this.spinner}spin(e){return void 0===e&&(e=document.body),this.spinner=g(``),e.append(this.spinner),this}stop(){this.spinner&&(this.spinner.remove(),this.spinner=void 0)}};const v=[],f={header_height:36,size:"400x300",padding:!0,title:"",modal:!0,overflow:"visible",ready:()=>{},closed:()=>{}};class b{constructor(e,t={}){this.url=e,this.options={...f,...t},this.$document=$(document),this.$window=$(window),this.$body=$("body");const s=this.options.size.split("x");this.width=parseInt(s[0],10),this.height=parseInt(s[1],10),this.build(),this.resize()}open(){this.dialog.trigger("Alchemy.DialogOpen"),this.bind_close_events(),window.requestAnimationFrame((()=>{if(this.dialog_container.addClass("open"),null!=this.overlay)return this.overlay.addClass("open")})),this.$body.addClass("prevent-scrolling"),v.push(this),this.load()}close(){return this.dialog.trigger("DialogClose.Alchemy"),this.$document.off("keydown"),this.dialog_container.removeClass("open"),null!=this.overlay&&this.overlay.removeClass("open"),this.$document.on("webkitTransitionEnd transitionend oTransitionEnd",(()=>{if(this.$document.off("webkitTransitionEnd transitionend oTransitionEnd"),this.dialog_container.remove(),null!=this.overlay&&this.overlay.remove(),this.$body.removeClass("prevent-scrolling"),v.pop(this),null!=this.options.closed)return this.options.closed()})),!0}load(){this.show_spinner(),$.get(this.url,(e=>{this.replace(e)})).fail((e=>{this.show_error(e)}))}reload(){this.dialog_body.empty(),this.load()}replace(e){this.remove_spinner(),this.dialog_body.hide(),this.dialog_body.html(e),this.init(),this.dialog[0].dispatchEvent(new CustomEvent("DialogReady.Alchemy",{bubbles:!0,detail:{body:this.dialog_body[0]}})),null!=this.options.ready&&this.options.ready(this.dialog_body),this.dialog_body.show()}show_spinner(){this.spinner=new y("medium"),this.spinner.spin(this.dialog_body[0])}remove_spinner(){this.spinner.stop()}init(){p(this.dialog_body),this.watch_remote_forms()}watch_remote_forms(){const e=$('[data-remote="true"]',this.dialog_body);e.on("ajax:success",(e=>{const t=e.detail[2];t.getResponseHeader("Content-Type").match(/javascript/)||(this.dialog_body.html(t.responseText),this.init())})),e.on("ajax:error",(e=>{const t=e.detail[1],s=e.detail[2];this.show_error(s,t)}))}show_error(e,t){if(422===e.status)return this.dialog_body.html(e.responseText),void this.init();const{error_body:s,error_header:i,error_type:n}=this.error_messages(e,t),r=$(`\n

    ${i}

    \n

    ${s}

    \n
    `);this.dialog_body.html(r)}error_messages(e,t){let s,i,n="warning";switch(e.status){case 0:i="The server does not respond.",s="Please check server and try again.";break;case 403:i="You are not authorized!",s="Please close this window.";break;default:n="error",t?(i=t,console.error(e.responseText)):i=`${e.statusText} (${e.status})`,s="Please check log and try again."}return{error_header:i,error_body:s,error_type:n}}bind_close_events(){this.close_button.on("click",(()=>{this.close()})),this.dialog_container.addClass("closable").on("click",(e=>e.target!==this.dialog_container.get(0)||(this.close(),!1))),this.$document.keydown((e=>27!==e.which||(this.close(),!1)))}build(){this.dialog_container=$('
    '),this.dialog=$('
    '),this.dialog_body=$('
    '),this.dialog_header=$('
    '),this.dialog_title=$('
    '),this.close_button=$(''),this.dialog_title.text(this.options.title),this.dialog_header.append(this.dialog_title),this.dialog_header.append(this.close_button),this.dialog.append(this.dialog_header),this.dialog.append(this.dialog_body),this.dialog_container.append(this.dialog),this.options.modal&&this.dialog.addClass("modal"),this.options.padding&&this.dialog_body.addClass("padded"),this.options.modal&&(this.overlay=$('
    '),this.$body.append(this.overlay)),this.$body.append(this.dialog_container)}resize(){const{width:e,height:t}=this.getSize();this.dialog.css({width:e,"min-height":t,overflow:this.options.overflow}),"hidden"===this.options.overflow?this.dialog_body.css({height:t,overflow:"auto"}):this.dialog_body.css({"min-height":t,overflow:"visible"})}getSize(){const e=this.options.padding?16:0,t=this.$window.width(),s=this.$window.height();let i=this.width,n=this.height;return i>=t&&(i=t-e),n>=s&&(n=s-e-f.header_height),{width:i,height:n}}}function E(){const{length:e}=v;if(0!==e)return v[e-1]}function w(e){const t=E();if(null!=t)return t.options.closed=e,t.close()}function _(e,t){if(!e)throw"No url given! Please provide an url.";new b(e,t).open()}class k{constructor(e,t={}){this.message=e,this.options={...{size:"300x100",title:h("Please confirm"),ok_label:h("Yes"),cancel_label:h("No"),on_ok(){}},...t},this.#e(),this.#t()}open(){requestAnimationFrame((()=>{this.dialog.show()}))}#e(){const e=this.options.size.split("x")[0];this.dialog=g(`\n \n ${this.message}\n \n \n \n `),document.body.append(this.dialog)}#t(){this.cancelButton.addEventListener("click",(e=>{e.preventDefault(),this.options.on_cancel(),this.dialog.hide()})),this.okButton.addEventListener("click",(e=>{e.preventDefault(),this.options.on_ok(),this.dialog.hide()})),this.dialog.addEventListener("sl-request-close",(e=>{"overlay"===e.detail.source&&(this.options.on_cancel(),e.preventDefault())})),this.dialog.addEventListener("sl-after-hide",(()=>{this.dialog.remove()}))}get cancelButton(){return this.dialog.querySelector("button[type=reset]")}get okButton(){return this.dialog.querySelector("button[type=submit]")}}function L(e,t={}){return new Promise((s=>{new k(e,{...t,on_ok(){s(!0)},on_cancel(){s(!1)}}).open()}))}function S(e=!0){document.querySelector("alchemy-overlay").show=!!e}function C(e){let t=()=>{};$(e).is("form")?t=function(){const t=$(``);t.append($(e).find("input")),t.appendTo("body"),S(),t.trigger("submit")}:$(e).is("a")&&(t=()=>Turbo.visit(e.pathname));return!(document.querySelectorAll("alchemy-element-editor.dirty").length>0)||(L(h("page_dirty_notice"),{title:h("warning"),ok_label:h("ok"),cancel_label:h("cancel")}).then((e=>{e&&(window.onbeforeunload=void 0,t())})),!1)}var x={checkPageDirtyness:C,PageLeaveObserver:function(){document.querySelectorAll("#main_navi a").forEach((e=>{e.addEventListener("click",(e=>{C(e.currentTarget)||e.preventDefault()}))}))}};function T(e){const t=document.getElementById("fixed-elements"),s=`fixed-element-${e}`;t.querySelector(`sl-tab[panel="${s}"]`).remove(),t.querySelector(`sl-tab-panel[name="${s}"]`).remove(),t.show("main-content-elements")}var A=Object.freeze({__proto__:null,createTab:function(e,t){const s=document.getElementById("fixed-elements"),i=`fixed-element-${e}`,n=`${t}`,r=``;s.innerHTML+=n+r,window.requestAnimationFrame((function(){s.show(i)}))},removeTab:T});function q(e,t="notice"){!function(e,t){const s=document.getElementById("flash_notices"),i=g(`\n \n ${e}\n \n `);s.append(i)}(e,t)}const F=/#[\w.~-]+$/;class I extends HTMLIFrameElement{#s;#i;#n;#r;constructor(){super(),this.addEventListener("load",this),this.#r=this.#l.bind(this)}handleEvent(e){"load"===e.type&&(this.#o(),this.#a(),this.#s?.call(this,e))}#l(e){"Alchemy.previewReady"===e.data.message&&(this.#o(),this.#a(),this.#s?.call(this,e))}connectedCallback(){let e=this.url;this.#c(),window.addEventListener("message",this.#r),window.localStorage.getItem("alchemy-preview-url")&&(e=window.localStorage.getItem("alchemy-preview-url"),this.previewUrlSelect.value=e),this.refresh(e)}disconnectedCallback(){key.unbind("alt+r"),window.removeEventListener("message",this.#r)}postMessage(e){this.contentWindow.postMessage(e,"*")}resize(e){this.style.width=`${e}px`}refresh(e){return this.#h(),this.src=e||this.url,this.#o(),this.#n=setTimeout((()=>{this.#a(),q(h("Preview failed to load"),"warning")}),5e3),new Promise((e=>{this.#s=e}))}set isDragged(e){this.style.transitionProperty=e?"none":null,this.style.pointerEvents=e?"none":null}#c(){this.reloadButton?.addEventListener("click",(e=>{e.preventDefault(),this.refresh()})),key("alt+r",(()=>this.refresh())),this.sizeSelect.addEventListener("change",(e=>{const t=e.target.value;""===t?this.style.width=null:this.resize(t)})),this.previewUrlSelect?.addEventListener("change",(e=>{const t=e.target.value;window.localStorage.setItem("alchemy-preview-url",t),this.refresh(t)}))}#h(){this.reloadButton.innerHTML.includes("alchemy-spinner")||(this.#i=this.reloadButton.innerHTML),this.reloadButton.innerHTML=''}#a(){this.reloadButton.innerHTML=this.#i}#o(){this.#n&&(clearTimeout(this.#n),this.#n=null)}get url(){return this.getAttribute("url")}get sizeSelect(){return document.querySelector("select#preview_size")}get previewUrlSelect(){return document.querySelector("select#preview_url")}get reloadButton(){return document.querySelector("#reload_preview_button")}}function M(){document.getElementById("alchemy_preview_window").refresh()}customElements.define("alchemy-preview-window",I,{extends:"iframe"});class z{static updateIcon(e,t=!1){const s=document.querySelector(`[data-ingredient-id="${e}"]`);if(s){s.querySelector(".edit-ingredient-anchor-link alchemy-icon").setAttribute("icon-style",t?"fill":"line")}}}class B extends HTMLElement{constructor(){super(),this.actions={closeCurrentDialog:w,reloadPreview:M,removeFixedElement:T,updateAnchorIcon:z.updateIcon,hidePleaseWaitOverlay(){S(!1)}}}connectedCallback(){const e=this.actions[this.name];e?e(...this.params):console.error(`Unknown Alchemy action: ${this.name}`),this.remove()}get name(){return this.getAttribute("name")}get params(){return this.hasAttribute("params")?JSON.parse(this.getAttribute("params")):[]}}function H(e,t){return e.replace(new RegExp(t,"gi"),(e=>`${e}`))}customElements.define("alchemy-action",B);class D extends HTMLElement{#d=null;async connectedCallback(){await d(),this.isConnected&&(this.input.classList.add("alchemy_selectbox"),this.#d=$(this.input).select2(this.select2Config).on("select2-open",this.#u).on("change",this.#m))}disconnectedCallback(){this.#d&&(this.#d.off("select2-open",this.#u),this.#d.off("change",this.#m),this.#d.select2("destroy"),this.#d=null)}#u=e=>this.onOpen(e);#m=e=>this.onChange(e);onChange(e){this.dispatchCustomEvent("RemoteSelect.Change",{removed:e.removed,added:e.added})}onOpen(e){setTimeout((()=>{document.querySelector("#select2-drop .select2-input").focus()}),100)}dispatchCustomEvent(e,t={}){this.dispatchEvent(new CustomEvent(`Alchemy.${e}`,{bubbles:!0,detail:t}))}get allowClear(){return this.hasAttribute("allow-clear")}get selection(){return this.getAttribute("selection")}get placeholder(){return this.getAttribute("placeholder")??""}get queryParams(){return this.getAttribute("query-params")??"{}"}get url(){return this.getAttribute("url")??""}get input(){return this.getElementsByTagName("input")[0]}get select2Config(){return{placeholder:this.placeholder,allowClear:this.allowClear,initSelection:(e,t)=>{this.selection&&t(JSON.parse(this.selection))},ajax:this.ajaxConfig,formatSelection:e=>this._renderResult(e),formatResult:(e,t,s)=>this._renderListEntry(e,s.term)}}get ajaxConfig(){return{url:this.url,datatype:"json",quietMillis:300,data:(e,t)=>this._searchQuery(e,t),results:e=>this._parseResponse(e)}}_searchQuery(e,t){return{q:{name_cont:e,...JSON.parse(this.queryParams)},page:t}}_parseResponse(e){const t=e.meta;return{results:e.data,more:t.page*t.per_page\n \n ${this._hightlightTerm(e.name,t)}\n
    \n `}});class P extends HTMLElement{connectedCallback(){$(this).on("change",(function(e){const t=new Event("submit",{bubbles:!0,cancelable:!0});return e.target.form.dispatchEvent(t),!1}))}}customElements.define("alchemy-auto-submit",P);class O extends HTMLButtonElement{connectedCallback(){this.form?(this.form.addEventListener("submit",this),"true"==this.form.dataset.remote&&this.form.addEventListener("ajax:complete",this),this.form.addEventListener("turbo:submit-end",this)):console.warn("No form for button found!",this)}handleEvent(e){switch(e.type){case"submit":"disabled"===this.getAttribute("disabled")?(e.preventDefault(),e.stopPropagation()):this.disable();break;case"ajax:complete":case"turbo:submit-end":this.enable()}}disable(){const e=new y("small"),t=this.getBoundingClientRect();this.dataset.initialButtonText=this.innerHTML,this.setAttribute("disabled","disabled"),this.setAttribute("tabindex","-1"),this.classList.add("disabled"),this.style.width=`${t.width}px`,this.style.height=`${t.height}px`,this.innerHTML=" ",e.spin(this)}enable(){this.classList.remove("disabled"),this.removeAttribute("disabled"),this.removeAttribute("tabindex"),this.style.width=null,this.style.height=null,this.innerHTML=this.dataset.initialButtonText}}customElements.define("alchemy-button",O,{extends:"button"});class N extends HTMLElement{connectedCallback(){this.translation=h("allowed_chars",this.maxChars),this.formField=this.getFormField(),this.formField&&(this.createDisplayElement(),this.countCharacters(),this.formField.addEventListener("keyup",this))}disconnectedCallback(){this.formField?.removeEventListener("keyup",this)}handleEvent(e){"keyup"===e.type&&this.countCharacters()}getFormField(){const e=this.querySelectorAll("input, textarea");return e.length>0?e[0]:void 0}createDisplayElement(){this.display=this.querySelector(":scope > .alchemy-char-counter"),this.display||(this.display=document.createElement("small"),this.display.className="alchemy-char-counter",this.formField.after(this.display))}countCharacters(){const e=this.formField.value.length;this.display.textContent=`${e} ${this.translation}`,this.display.classList.toggle("too-long",e>this.maxChars)}get maxChars(){return this.getAttribute("max-chars")??60}}customElements.define("alchemy-char-counter",N);class U extends HTMLElement{constructor(){super(),this.innerHTML='\n \n ',this.clipboard=new ClipboardJS(this,{text:()=>this.getAttribute("content")}),this.clipboard.on("success",(()=>{q(this.getAttribute("success-text"))}))}disconnectedCallback(){this.clipboard.destroy()}}customElements.define("alchemy-clipboard-button",U);const R=e=>{const t=e.element[0],s=t.dataset.swatch||t.value;return`\n
    \n ${"custom_color"===t.value?'':``}\n ${e.text}\n
    `};class j extends HTMLElement{connectedCallback(){this.select?(this.#p(),$(this.select).on("change",(e=>this.#g("custom_color"===e.val)))):(this.colorInput?.addEventListener("input",this),this.textInput?.addEventListener("input",this),this.#g(!0))}handleEvent(e){switch(e.target){case this.colorInput:this.textInput.value=this.colorInput.value;break;case this.textInput:this.colorInput.value=this.textInput.value}}disconnectedCallback(){this.colorInput?.removeEventListener("input",this),this.textInput?.removeEventListener("input",this)}#p(){this.select.classList.add("alchemy_selectbox");const e={minimumResultsForSearch:10,formatResult:R,formatSelection:R};$(this.select).select2(e)}#g(e=!0){this.colorInput.disabled=!e}get colorInput(){return this.querySelector("input[type='color']")}get textInput(){return this.querySelector("input[type='text']")}get select(){return this.querySelector("select")}}customElements.define("alchemy-color-select",j);const W=c();class J extends HTMLElement{async connectedCallback(){"en"!==W&&await import(`flatpickr/${W}.js`),this.isConnected&&(this.flatpickr=s(this.inputField,this.flatpickrOptions))}disconnectedCallback(){this.flatpickr?.destroy()}get flatpickrOptions(){const e=/time/.test(this.inputType),t={locale:W.slice(0,2),altInput:!0,altFormat:h(`formats.${this.inputType}`),altInputClass:"flatpickr-input",enableTime:e,noCalendar:"time"===this.inputType,time_24hr:h("formats.time_24hr"),onValueUpdate(e,t,s){s.element.closest("alchemy-element-editor")?.setDirty(this.inputField)}};return e&&(t.dateFormat="Z"),t}get inputField(){return this.querySelector("input")}get inputType(){return this.getAttribute("input-type")||"date"}}customElements.define("alchemy-datepicker",J);class V extends HTMLAnchorElement{constructor(){super(),this.addEventListener("click",this)}handleEvent(e){this.disabled||this.openDialog(),e.preventDefault()}openDialog(){this.dialog=new b(this.getAttribute("href"),this.dialogOptions),this.dialog.open()}get dialogOptions(){return this.dataset.dialogOptions?JSON.parse(this.dataset.dialogOptions):{}}get disabled(){return this.classList.contains("disabled")}}customElements.define("alchemy-dialog-link",V,{extends:"a"});const X="application/json",Q="text/vnd.turbo-stream.html";function Y(e){return"get"===e.toLowerCase()}function K(e,t,s){const i=function(e){return{"Content-Type":"application/json; charset=utf-8",Accept:e,"X-Requested-With":"XMLHttpRequest","X-CSRF-Token":G()}}(s),n={method:e,headers:i};return t&&!Y(e)&&(n.body=JSON.stringify(t)),n}function G(){return document.querySelector('meta[name="csrf-token"]').attributes.content.textContent}function Z(e,t){return se("GET",e,t)}function ee(e,t,s){return se("PATCH",e,t,s)}function te(e,t,s=X){return se("POST",e,t,s)}async function se(e,t,s,i=X){const n=await fetch(function(e,t,s){const i=new URL(window.location.origin+e);return t&&Y(s)&&(i.search=new URLSearchParams(t).toString()),i.toString()}(t,s,e),K(e,s,i)),r=n.headers.get("content-type"),l=r?.includes(X),o=r?.includes(Q);let a=null;if(l?a=await n.json():o&&(a=await n.text(),"undefined"!=typeof Turbo&&Turbo.renderStreamMessage(a)),n.ok)return{data:a,status:n.status};throw a||new Error("An error occurred during the transaction")}class ie extends HTMLElement{dataItem(e){return{id:`#${e}`,text:`#${e}`}}get selectElement(){return this.querySelector('select[is="alchemy-select"]')}}customElements.define("alchemy-dom-id-api-select",class extends ie{#y=void 0;connectedCallback(){this.page=this.getAttribute("page")}async#v(){const e=(await Z(Alchemy.routes.api_ingredients_path,{page_id:this.#y})).data.ingredients.filter((e=>e.data?.dom_id)).map((e=>this.dataItem(e.data.dom_id))),t=e.length>0?h("None"):h("No anchors found");this.selectElement.setOptions(e,t),this.selectElement.enable()}#f(){requestAnimationFrame((()=>{this.selectElement.disable(),this.selectElement.setOptions([],h("Select a page first"))}))}set page(e){this.#y=e,e?this.#v():this.#f()}}),customElements.define("alchemy-dom-id-preview-select",class extends ie{connectedCallback(){requestAnimationFrame((()=>{const e=document.getElementById("alchemy_preview_window"),t=e.contentDocument?.querySelectorAll("[id]")||[];if(t.length>0){const e=Array.from(t).map((e=>this.dataItem(e.id)));this.selectElement.setOptions(e,h("None"))}}))}});class ne extends HTMLElement{#b;connectedCallback(){this.#b=this.scheduleButton.getAttribute("variant"),this.publishButton.addEventListener("click",this),this.dropdown.addEventListener("sl-show",this),this.dropdown.addEventListener("sl-hide",this)}disconnectedCallback(){this.publishButton.removeEventListener("click",this),this.dropdown.removeEventListener("sl-show",this),this.dropdown.removeEventListener("sl-hide",this)}handleEvent(e){switch(e.type){case"click":this.publishButton.loading=!0;break;case"sl-show":this.scheduleButton.setAttribute("variant","primary");break;case"sl-hide":this.scheduleButton.setAttribute("variant",this.#b)}}get publishButton(){return this.querySelector("sl-button[type='submit']")}get dropdown(){return this.querySelector("sl-dropdown")}get scheduleButton(){return this.querySelector("sl-button[slot='trigger']")}}customElements.define("alchemy-publish-element-button",ne);class re extends HTMLElement{constructor(){super(),this.button?.addEventListener("click",this)}async handleEvent(){if(await L(this.message)){const e=await se("DELETE",this.url);this.#E(e.data)}}#E(e){const t=this.closest("alchemy-element-editor");t.addEventListener("transitionend",(()=>{t.fixed&&T(t.elementId),t.remove()})),t.classList.add("dismiss"),q(e.message),e.pageHasUnpublishedChanges&&le(e),M()}get url(){return this.getAttribute("href")}get message(){return this.getAttribute("message")}get button(){return this.querySelector("button")}}function le(e){document.dispatchEvent(new CustomEvent("alchemy:page-dirty",{detail:{tooltip:e.publishButtonTooltip}}))}customElements.define("alchemy-delete-element-button",re);class oe extends HTMLElement{constructor(){super(),this.addEventListener("click",this),this.addEventListener("alchemy:element-update-title",this),this.addEventListener("ajax:complete",this),$(this.form).on("change",this.onChange),this.header?.addEventListener("dblclick",(()=>{this.toggle()})),this.toggleButton?.addEventListener("click",(e=>{e.target.closest("alchemy-element-editor")===this&&this.toggle()}))}connectedCallback(){this.classList.contains("ui-sortable-placeholder")||this.hasAttribute("created")&&(this.focusElement(),this.previewWindow?.refresh().then((()=>{this.focusElementPreview()})),this.removeAttribute("created"))}handleEvent(e){switch(e.type){case"click":e.target.closest("alchemy-element-editor")===this&&this.onClickElement();break;case"ajax:complete":if(e.target===this.body){const t=e.detail[0];e.stopPropagation(),this.onSaveElement(t)}break;case"alchemy:element-update-title":this.hasEditors||e.target!=this.firstChild||this.setTitle(e.detail.title)}}onChange(e){const t=e.target;if(!t.classList.contains("nested-elements"))return this.closest("alchemy-element-editor").setDirty(t),e.stopPropagation(),!1}async focusElement(){document.querySelector("#fixed-elements")&&await this.selectTabForElement(),await this.expand(),this.selectElement(!0)}focusElementPreview(){this.previewWindow?.postMessage({message:"Alchemy.focusElement",element_id:this.elementId})}onClickElement(){this.selectElement(),this.focusElementPreview()}onSaveElement(e){const t=JSON.parse(e.responseText);if(this.setClean(),422===e.status){const e=t.warning;t.ingredientsWithErrors.forEach((e=>{const t=this.querySelector(`[data-ingredient-id="${e.id}"]`),s=g(`${e.errorMessage}`);t?.appendChild(s),t?.classList.add("validation_failed")})),q(e,"warn"),this.elementErrors.classList.remove("hidden")}else q(t.notice),this.previewWindow?.refresh().then((()=>{this.focusElementPreview()})),this.updateTitle(t.previewText),t.ingredientAnchors.forEach((e=>{z.updateIcon(e.ingredientId,e.active)})),t.pageHasUnpublishedChanges&&le(t)}scrollToElement(){setTimeout((()=>{this.scrollIntoView({behavior:"smooth"})}),50)}selectElement(e=!1){document.querySelectorAll("alchemy-element-editor.selected").forEach((e=>{e.classList.remove("selected")})),window.requestAnimationFrame((()=>{this.classList.add("selected")})),e&&this.scrollToElement()}selectTabForElement(){return new Promise(((e,t)=>{const s=document.querySelector("#fixed-elements"),i=this.closest("sl-tab-panel");s&&i?(s.show(i.getAttribute("name")),e()):t(new Error("No tabs present"))}))}setClean(){this.dirty=!1,window.onbeforeunload=null,this.elementErrors.classList.add("hidden"),this.hasEditors&&this.body.querySelectorAll(".ingredient-editor").forEach((e=>{e.classList.remove("dirty","validation_failed"),e.querySelectorAll("small.error").forEach((e=>e.remove()))}))}setDirty(e){this.hasEditors&&(this.dirty=!0,window.onbeforeunload||(window.onbeforeunload=e=>e.preventDefault()),e?.closest(".ingredient-editor")?.classList.add("dirty"))}setTitle(e){this.querySelector(".element-header .preview_text_quote").textContent=e}async toggle(){this.collapsed?await this.expand():await this.collapse()}collapse(){if(this.collapsed||this.compact||this.fixed)return Promise.resolve("Element is already collapsed.");const e=new Alchemy.Spinner("small");return e.spin(this.toggleButton),this.toggleIcon?.classList?.add("hidden"),te(Alchemy.routes.collapse_admin_element_path(this.elementId)).then((e=>{const t=e.data;if(this.collapsed=!0,this.toggleButton?.setAttribute("title",t.title),t.nestedElementIds.length){const e=t.nestedElementIds.map((e=>`#element_${e}`)).join(", ");this.querySelectorAll(e).forEach((e=>{e.collapsed=!0,e.toggleButton?.setAttribute("title",t.title)}))}})).catch((e=>{q(e.message,"error"),console.error(e)})).finally((()=>{this.toggleIcon?.classList?.remove("hidden"),e.stop()}))}expand(){if(this.expanded&&!this.compact)return Promise.resolve("Element is already expanded.");if(this.compact&&this.parentElementEditor)return this.parentElementEditor.expand();{const e=new Alchemy.Spinner("small");return e.spin(this.toggleButton),this.toggleIcon?.classList.add("hidden"),new Promise(((t,s)=>{te(Alchemy.routes.expand_admin_element_path(this.elementId)).then((e=>{const s=e.data;if(s.parentElementIds.length){const e=s.parentElementIds.map((e=>`#element_${e}`)).join(", ");document.querySelectorAll(e).forEach((e=>{e.collapsed=!1,e.toggleButton?.setAttribute("title",s.title)}))}this.collapsed=!1,this.toggleButton?.setAttribute("title",s.title),t()})).catch((e=>{q(e.message,"error"),console.error(e),s(e)})).finally((()=>{this.toggleIcon?.classList?.remove("hidden"),e.stop()}))}))}}updateTitle(e){this.setTitle(e),this.dispatchEvent(new CustomEvent("alchemy:element-update-title",{bubbles:!0,detail:{title:e}}))}set published(e){e?this.classList.remove("element-hidden"):this.classList.add("element-hidden")}get published(){return!this.classList.contains("hidden")}get compact(){return null!==this.getAttribute("compact")}get fixed(){return null!==this.getAttribute("fixed")}set collapsed(e){this.classList.toggle("folded",e),this.classList.toggle("expanded",!e),this.toggleIcon&&(this.toggleIcon.name=e?"arrow-left-s":"arrow-down-s")}get collapsed(){return this.classList.contains("folded")}get expanded(){return!this.collapsed}set dirty(e){this.classList.toggle("dirty",e)}get dirty(){return this.classList.contains("dirty")}get header(){return this.querySelector(".element-header")}get body(){return this.querySelector(this.bodySelector)}get bodySelector(){return`#${this.id} > .element-body`}get footer(){return this.querySelector(`#${this.id} > .element-footer`)}get toggleButton(){return this.querySelector(".element-toggle")}get toggleIcon(){return this.toggleButton?.querySelector("alchemy-icon")}get elementErrors(){return this.body.querySelector(".element_errors")}get elementId(){return this.dataset.elementId}get elementName(){return this.dataset.elementName}get hasEditors(){return!!this.body?.querySelector(".element-ingredient-editors")}get hasChildren(){return!!this.querySelector(".nested-elements")}get firstChild(){return this.querySelector("alchemy-element-editor")}get form(){return this.querySelector("form.element-body")}get parentElementEditor(){return this.parentElement?.closest("alchemy-element-editor")}get previewWindow(){return document.getElementById("alchemy_preview_window")}}customElements.define("alchemy-element-editor",oe);const ae=e=>`\n
    \n ${e.icon}${e.text}\n
    \n `;class ce extends HTMLElement{constructor(){super()}connectedCallback(){const e=this.options,t={minimumResultsForSearch:3,dropdownAutoWidth:!0,data:()=>({results:e}),formatResult:(e,t,s)=>{let i;return""===e.id?e.text:(i=""!==s.term?H(e.text,s.term):e.text,((e,t,s)=>{const i=s?`
    ${s}
    `:"";return`\n
    \n ${ae({icon:e,text:t})}\n ${i}\n
    \n `})(e.icon,i,e.hint))},formatSelection:ae,placeholder:this.placeholder};$(this.inputField).select2(t)}get options(){return JSON.parse(this.getAttribute("options"))}get placeholder(){return this.getAttribute("placeholder")}get inputField(){return this.querySelector("input")}}customElements.define("alchemy-element-select",ce);class he extends HTMLElement{#w=!0;#_=null;constructor(){super(),this.#c()}connectedCallback(){this.toggleButton?.addEventListener("click",(e=>{e.preventDefault(),this.toggle()})),window.location.hash&&this.focusElementEditor(window.location.hash),this.resize()}collapseAllElements(){this.querySelectorAll("alchemy-element-editor:not([compact]):not([fixed])").forEach((e=>e.collapse()))}toggle(){this.#w?this.hide():this.show()}show(){document.body.classList.add("elements-window-visible"),this.#w=!0,this.toggleButton.closest("sl-tooltip").content=Alchemy.t("Hide elements"),this.toggleButton.querySelector("alchemy-icon").setAttribute("name","menu-unfold"),this.resize()}hide(){document.body.classList.remove("elements-window-visible"),document.body.style.removeProperty("--elements-window-width"),this.#w=!1,this.toggleButton.closest("sl-tooltip").content=Alchemy.t("Show elements"),this.toggleButton.querySelector("alchemy-icon").setAttribute("name","menu-fold")}resize(e){void 0===e&&(e=this.widthFromCookie),e&&(document.body.style.setProperty("--elements-window-width",`${e}px`),document.cookie=`alchemy-elements-window-width=${e}; SameSite=Lax; Path=/;`)}focusElementEditor(e){const t=document.querySelector(e);t instanceof oe&&t.focusElement()&&t.focusElementPreview()}get collapseButton(){return this.querySelector("#collapse-all-elements-button")}get toggleButton(){return document.querySelector("#element_window_button")}get previewWindow(){return document.getElementById("alchemy_preview_window")}get turboFrame(){return this.#_||(this.#_=this.closest("turbo-frame")),this.#_}get widthFromCookie(){return document.cookie.split("; ").find((e=>e.startsWith("alchemy-elements-window-width=")))?.split("=")[1]}set isDragged(e){this.turboFrame.style.transitionProperty=e?"none":null,this.turboFrame.style.pointerEvents=e?"none":null}#c(){this.collapseButton?.addEventListener("click",(()=>{this.collapseAllElements()})),window.addEventListener("message",(e=>{const t=e.data;if("Alchemy.focusElementEditor"==t?.message){const e=document.getElementById(`element_${t.element_id}`);this.show(),e?.focusElement()}})),document.body.addEventListener("click",(e=>{e.target.closest("alchemy-element-editor")||(this.querySelectorAll("alchemy-element-editor").forEach((e=>{e.classList.remove("selected")})),this.previewWindow?.postMessage({message:"Alchemy.blurElements"}))}))}}customElements.define("alchemy-elements-window",he);class de extends HTMLElement{#k=!1;#L=null;#S=null;constructor(){super(),this.addEventListener("mousedown",this),window.addEventListener("mousemove",this),window.addEventListener("mouseup",this)}handleEvent(e){switch(e.type){case"mousedown":e.stopPropagation(),this.onMouseDown();break;case"mouseup":this.onMouseUp();break;case"mousemove":this.#k&&this.onDrag(e.pageX)}}onMouseDown(){this.#k=!0,this.elementsWindow.isDragged=!0,this.previewWindow.isDragged=!0,this.classList.add("is-dragged")}onMouseUp(){this.#k=!1,this.elementsWindow.isDragged=!1,this.previewWindow.isDragged=!1,this.classList.remove("is-dragged")}onDrag(e){const t=window.innerWidth-e;this.elementsWindow.resize(t)}get elementsWindow(){return this.#L||(this.#L=document.querySelector("alchemy-elements-window")),this.#L}get previewWindow(){return this.#S||(this.#S=document.getElementById("alchemy_preview_window")),this.#S}}customElements.define("alchemy-elements-window-handle",de);class ue extends HTMLElement{constructor(){super(),this.deleteLink=this.querySelector(".remove_file_link"),this.fileIcon=this.querySelector(".file_icon"),this.fileName=this.querySelector(".file_name"),this.formFieldId=this.deleteLink?.dataset.formFieldId,this.formField=this.querySelector(`#${this.formFieldId}`),this.deleteLink?.addEventListener("click",this)}handleEvent(e){"click"===e.type&&this.removeFile(),e.stopPropagation()}removeFile(){this.formField.value="",this.fileIcon.innerHTML="",this.fileName.innerHTML="",this.deleteLink?.classList.add("hidden"),this.closest("alchemy-element-editor").setDirty(this.formField)}}customElements.define("alchemy-file-editor",ue);class me extends HTMLElement{#C;constructor(){super(),this.#c()}#c(){this.hotkey&&key(this.hotkey,(()=>(this.filterField.focus(),!1))),this.filterField.addEventListener("keyup",(()=>{clearTimeout(this.#C),this.#C=setTimeout((()=>{const e=this.filterField.value;this.clearButton.style.visibility=e?"visible":"hidden",this.filter(e)}),this.debounceTime)})),this.clearButton.addEventListener("click",(e=>{e.preventDefault(),this.clear()})),this.filterField.addEventListener("focus",(()=>key.setScope("list_filter"))),key("esc","list_filter",(()=>{this.clear(),this.filterField.blur()}))}disconnectedCallback(){this.hotkey&&key.unbind(this.hotkey),key.unbind("esc","list_filter")}filter(e){""===e&&(this.clearButton.style.visibility="hidden");const t=[],s=new Set,i=e.toLowerCase();this.items.forEach((e=>{const n=e.getAttribute(this.nameAttribute)?.toLowerCase();if(-1!==n.indexOf(i)){t.push(e),s.add(e);let i=e.parentElement?.closest(this.itemsSelector);for(;i;)s.add(i),i=i.parentElement?.closest(this.itemsSelector)}})),this.items.forEach((e=>{e.classList.toggle("hidden",!s.has(e))})),1===t.length&&t[0].scrollIntoView({behavior:"smooth",block:"nearest"})}clear(){this.filterField.value="",this.clearButton.style.visibility="hidden",this.items.forEach((e=>e.classList.remove("hidden")))}get nameAttribute(){return this.getAttribute("name-attribute")||"name"}get clearButton(){return this.querySelector('button[type="button"]')}get filterField(){return this.querySelector('input[type="text"]')}get items(){return document.querySelectorAll(this.itemsSelector)}get itemsSelector(){return this.getAttribute("items-selector")}get debounceTime(){return parseInt(this.getAttribute("debounce-time"))||150}get hotkey(){return this.getAttribute("hotkey")}}customElements.define("alchemy-list-filter",me);class pe extends HTMLElement{#x;constructor(){super(),this.#x=this.innerHTML,(this.dismissable||"error"===this.type)&&this.addEventListener("click",this)}handleEvent(e){"click"===e.type&&this.dismiss()}connectedCallback(){this.innerHTML=`\n \n ${this.dismissable&&"error"===this.type?'':""}\n ${this.#x}\n `,this.dismissable&&"error"!==this.type&&setTimeout((()=>{this.dismiss()}),this.dismissDelay)}dismiss(){this.addEventListener("transitionend",(()=>this.remove())),this.classList.add("dismissed")}get dismissable(){return this.hasAttribute("dismissable")}get icon(){return this.getAttribute("icon")}get type(){return this.getAttribute("type")||"notice"}get dismissDelay(){return parseInt(this.noticesWrapper?.dataset.autoDismissDelay||5e3)}get iconName(){switch(this.icon||this.type){case"warning":case"warn":case"alert":return"alert";case"notice":return"check";case"info":case"hint":return"information";case"error":return"bug";default:return this.type}}get noticesWrapper(){return this.closest("#flash_notices")}}customElements.define("alchemy-message",pe);class ge extends HTMLElement{connectedCallback(){q(this.message,this.getAttribute("type")||"notice"),this.remove()}get message(){return this.getAttribute("message")||this.innerHTML}}customElements.define("alchemy-growl",ge);class ye extends HTMLElement{static get observedAttributes(){return["name","size","icon-style"]}constructor(){super(),this.spriteUrl=document.querySelector('link[rel="preload"][as="image"]').getAttribute("href")}connectedCallback(){this.render()}attributeChangedCallback(){this.render()}render(){const e=this.size?` icon--${this.size}`:"";this.innerHTML=``}set name(e){this.setAttribute("name",e)}get iconName(){return this.getAttribute("name")}get size(){return this.getAttribute("size")}get style(){const e=this.getAttribute("icon-style");switch(e){case"none":return"";case null:return"-line";default:return`-${e}`}}}customElements.define("alchemy-icon",ye);class ve extends HTMLDetailsElement{#T="Alchemy.expanded_ingredient_groups";constructor(){super(),this.addEventListener("toggle",this),this.isInLocalStorage&&(this.open=!0)}handleEvent(){let e=this.localStorageItem;this.open?(this.toggleIcon.name="arrow-down-s",this.isInLocalStorage||e.push(this.id)):(this.toggleIcon.name="arrow-left-s",e=e.filter((e=>e!==this.id))),localStorage.setItem(this.#T,JSON.stringify(e))}get isInLocalStorage(){return this.localStorageItem.includes(this.id)}get localStorageItem(){const e=localStorage.getItem(this.#T);if(!e)return[];try{return JSON.parse(e)}catch(e){return console.error(e),[]}}get toggleIcon(){return this.querySelector("alchemy-icon")}}customElements.define("alchemy-ingredient-group",ve,{extends:"details"});class fe extends HTMLButtonElement{constructor(){super(),this.addEventListener("click",this),this.classList.add("icon_button"),this.setAttribute("type","button"),this.innerHTML=''}handleEvent(e){new Alchemy.LinkDialog({url:this.linkUrl,title:this.linkTitle,target:this.linkTarget,type:this.linkClass}).open().then((e=>this.setLink(e))),e.preventDefault()}setLink(e){""===e.url?(this.classList.remove("linked"),this.dispatchEvent(new CustomEvent("alchemy:unlink",{bubbles:!0}))):(this.classList.add("linked"),this.dispatchEvent(new CustomEvent("alchemy:link",{bubbles:!0,detail:e})))}get linkUrl(){return this.linkButtons.linkUrlField.value}get linkTitle(){return this.linkButtons.linkTitleField.value}get linkTarget(){return this.linkButtons.linkTargetField.value}get linkClass(){return this.linkButtons.linkClassField.value}get linkButtons(){return this.closest("alchemy-link-buttons")}}customElements.define("alchemy-link-button",fe,{extends:"button"});class be extends HTMLButtonElement{constructor(){super(),this.addEventListener("click",this),this.classList.add("icon_button"),this.setAttribute("type","button"),this.linked=this.linked,this.innerHTML=''}handleEvent(e){this.linked&&(this.linked=!1,this.blur(),this.dispatchEvent(new CustomEvent("alchemy:unlink",{bubbles:!0}))),e.preventDefault()}set linked(e){e?(this.classList.replace("disabled","linked"),this.removeAttribute("tabindex")):(this.classList.replace("linked","disabled"),this.setAttribute("tabindex","-1"))}get linked(){return this.classList.contains("linked")}}customElements.define("alchemy-unlink-button",be,{extends:"button"});class Ee extends HTMLElement{constructor(){super(),this.addEventListener("alchemy:link",this),this.addEventListener("alchemy:unlink",this)}handleEvent(e){switch(e.type){case"alchemy:link":this.setLink(e.detail);break;case"alchemy:unlink":this.removeLink()}e.stopPropagation()}setLink(e){this.linkUrlField.value=e.url,this.linkUrlField.dispatchEvent(new Event("change")),this.linkTitleField.value=e.title,this.linkClassField.value=e.type,this.linkTargetField.value=e.target,this.unlinkButton.linked=!0,this.setElementDirty()}removeLink(){this.linkUrlField.value="",this.linkUrlField.dispatchEvent(new Event("change")),this.linkTitleField.value="",this.linkClassField.value="",this.linkTargetField.value="",this.linkButton.classList.remove("linked"),this.unlinkButton.linked=!1,this.setElementDirty()}setElementDirty(){this.elementEditor.setDirty(this)}get linkButton(){return this.querySelector('[is="alchemy-link-button"]')}get unlinkButton(){return this.querySelector('[is="alchemy-unlink-button"]')}get ingredientEditor(){const e=this.dataset.ingredientId;return this.parentElement.closest(`[data-ingredient-id='${e}']`)}get elementEditor(){return this.closest("alchemy-element-editor")}get linkUrlField(){return this.ingredientEditor.querySelector("[data-link-value]")}get linkTitleField(){return this.ingredientEditor.querySelector("[data-link-title]")}get linkTargetField(){return this.ingredientEditor.querySelector("[data-link-target]")}get linkClassField(){return this.ingredientEditor.querySelector("[data-link-class]")}}customElements.define("alchemy-link-buttons",Ee);function we(e){let t=0===e?0:Math.floor(Math.log(e)/Math.log(1024));return t>3&&(t=3),(e/Math.pow(1024,t)).toFixed(2)+" "+["B","kB","MB","GB"][t]}customElements.define("alchemy-node-select",class extends D{_searchQuery(e,t){return{filter:{name_or_page_name_cont:e,...JSON.parse(this.queryParams)},page:t}}_renderResult(e){return this._renderListEntry(e)}_renderListEntry(e,t){const s=e.ancestors.map((e=>e.name)),i='';return`\n
    \n \n
    \n \n ${s.length>0?s.join(i)+i:""}\n \n \n ${this._hightlightTerm(e.name,t)}\n \n
    \n
    \n ${e.url||""}\n
    \n
    \n `}});class _e extends HTMLElement{file=null;request=null;progressEventLoaded=0;progressEventTotal=0;#A=!0;#$=0;#q=void 0;#F="";connectedCallback(){if(this.innerHTML=`\n \n
    \n ${this.file?.name}\n ${this.loadedSize}\n ${this.errorMessage}\n
    \n \n \n \n `,this.querySelector("button").addEventListener("click",(()=>this.cancel())),this.file?.type.includes("image")){const e=new FileReader;e.readAsDataURL(this.file),e.addEventListener("load",(()=>{const t=new Image;t.src=e.result,this.prepend(t)}))}}initialize(e,t){this.file=e,this.request=t,this.progressEventTotal=e?e.size:0,this.status="in-progress",this.#I(),this.#M()}cancel(){this.finished||(this.status="canceled",this.request?.abort(),this.dispatchCustomEvent("FileUpload.Change"))}dispatchCustomEvent(e){this.dispatchEvent(new CustomEvent(`Alchemy.${e}`,{bubbles:!0}))}#I(){const e=Alchemy.uploader_defaults,t=e.file_size_limit*Math.pow(1024,2);let s;this.file?.size>t&&(s=h("Uploaded bytes exceed file size"));const i=this.file?.type.includes("image")?e.allowed_filetypes.alchemy_pictures:e.allowed_filetypes.alchemy_attachments;i.includes("*")||i.includes(this.file?.type.replace(/^\w+\/(\w+)(\+\w+)?/i,"$1"))||(s=h("File type not allowed")),s&&(this.valid=!1,this.errorMessage=s)}#M(){this.request&&(this.request.upload.onprogress=e=>{this.progressEvent=e},this.request.onload=()=>{this.request.status<400?(this.status="successful",q(this.responseMessage)):(this.status="failed",this.errorMessage=this.responseMessage),this.dispatchCustomEvent("FileUpload.Change")},this.request.onerror=()=>{this.errorMessage=h("An error occurred during the transaction")})}get active(){return this.valid&&"canceled"!==this.status}get errorMessage(){return this.#F||""}set errorMessage(e){this.#F=e;const t=this.querySelector(".error-message");t&&(t.textContent=e),q(e,"error")}get finished(){return["canceled","successful","failed"].includes(this.status)}get loadedSize(){return`${we(this.progressEventLoaded)} / ${we(this.progressEventTotal)}`}get progressElement(){return this.querySelector("sl-progress-bar")}set progressEvent(e){this.progressEventLoaded=e.loaded,this.progressEventTotal=e.total,this.value=Math.round(e.loaded/e.total*100),this.querySelector(".loaded-size").textContent=this.loadedSize}get responseMessage(){try{return JSON.parse(this.request.responseText).message}catch(e){return`${this.request.status}: ${this.request.statusText}`}}get status(){return this.#q}set status(e){this.#q=e,this.className=e,this.progressElement?.toggleAttribute("indeterminate","upload-finished"===e)}get valid(){return this.#A}set valid(e){this.#A=e,this.classList.toggle("invalid",!e)}get value(){return this.#$}set value(e){this.#$=e,this.progressElement&&(this.progressElement.value=e),100===e&&(this.status="upload-finished"),this.dispatchCustomEvent("FileUpload.Change")}}customElements.define("alchemy-file-upload",_e);class ke extends HTMLElement{fileCount=0;#z=[];#B=h("Cancel all uploads");#H=null;#w=!1;#D=()=>this.#P();connectedCallback(){var e,t;this.innerHTML=(e=this.#B,t=this.fileCount,`\n \n
    \n \n\n \n \n \n
    \n
    \n
    \n`),this.visible=!0,this.#H=this.querySelector("button"),this.#H.addEventListener("click",(()=>{this.finished?this.onComplete(this.status):this.cancel()})),this.#z.forEach((e=>{this.querySelector(".single-uploads").append(e)})),this.#P(),this.addEventListener("Alchemy.FileUpload.Change",this.#D)}disconnectedCallback(){this.removeEventListener("Alchemy.FileUpload.Change",this.#D)}initialize(e=[]){this.#z=e,this.fileCount=e.length}cancel(){this.#O().forEach((e=>{e.cancel()})),this.#N()}onComplete(e){}#O(){return this.#z.filter((e=>e.active))}#N(){this.#B=h("Close"),this.#H.ariaLabel=this.#B,this.#H.parentElement.content=this.#B}#U(e){return this.#O().reduce(((t,s)=>s[e]+t),0)}#P(){const e=this.status;this.className=e,this.progressElement.value=this.totalProgress,this.progressElement.toggleAttribute("indeterminate","upload-finished"===e),this.querySelector(".overall-progress-value > span").textContent=this.overallProgressValue,this.querySelector(".overall-upload-value").textContent=this.overallUploadSize,this.finished?(this.#N(),this.onComplete(e)):this.visible=!0}get finished(){return this.#O().every((e=>e.finished))}get overallUploadSize(){this.#O().filter((e=>e.value>=100)).length;return this.totalProgress,this.#O().length,`${we(this.#U("progressEventLoaded"))} / ${we(this.#U("progressEventTotal"))}`}get overallProgressValue(){const e=this.#O().filter((e=>e.value>=100)).length;return`${this.totalProgress}% (${e} / ${this.#O().length})`}get progressElement(){return this.querySelector("sl-progress-bar")}get status(){const e=this.#O().map((e=>e.className));return e.includes("failed")?"failed":0===e.length?"canceled":e.every((t=>t===e[0]))?e[0]:"in-progress"}get totalProgress(){const e=this.#O().reduce(((e,t)=>e+t.file.size),0);let t=Math.ceil(this.#O().reduce(((t,s)=>{const i=s.file.size/e;return s.value*i+t}),0));return t>100&&(t=100),t}get visible(){return this.#w}set visible(e){this.classList.toggle("visible",e),this.#w=e}}customElements.define("alchemy-upload-progress",ke);class Le extends HTMLElement{#R=null;#j=!1;connectedCallback(){this.fileInput.addEventListener("change",this.#W),this.dropzone&&this.#J(),this.addEventListener("Alchemy.upload.successful",this)}disconnectedCallback(){this.fileInput?.removeEventListener("change",this.#W),this.#R&&(this.#R.removeEventListener("dragleave",this.#V),this.#R.removeEventListener("drop",this.#X),this.#R.removeEventListener("dragover",this.#Q),this.#R=null)}handleEvent(e){if("Alchemy.upload.successful"===e.type)this.#Y()}#W=e=>{this.uploadFiles(Array.from(e.target.files))};#K=e=>{this.#j!==e&&(this.#j=e,this.#R.classList.toggle("dragover"))};#V=()=>this.#K(!1);#X=async e=>{e.preventDefault(),this.#K(!1);const t=[...e.dataTransfer.items].map((e=>e.getAsFile()));this.uploadFiles(t)};#Q=e=>{e.preventDefault(),this.#K(!0)};#Y(){setTimeout((()=>{const e=this.redirectUrl,t=this.closest("turbo-frame");this.uploadProgress.visible=!1,e&&(t?(t.setAttribute("src",e),t.reload()):Turbo.visit(e))}),750)}#J(){this.#R=document.querySelector(this.dropzone),this.#R&&(this.#R.addEventListener("dragleave",this.#V),this.#R.addEventListener("drop",this.#X),this.#R.addEventListener("dragover",this.#Q))}uploadFiles(e){let t=0;const s=e.map((e=>{const s=new XMLHttpRequest,i=new _e;return i.initialize(e,s),Alchemy.uploader_defaults.upload_limit-1{this.dispatchEvent(new CustomEvent(`Alchemy.upload.${e}`,{bubbles:!0}))},document.body.append(this.uploadProgress)}get dropzone(){return this.getAttribute("dropzone")}get fileInput(){return this.querySelector("input[type='file']")}get redirectUrl(){return this.getAttribute("redirect-url")}}customElements.define("alchemy-uploader",Le);class Se extends HTMLElement{connectedCallback(){this.innerHTML=`\n \n
    \n ${this.getAttribute("text")??""}\n
    \n `}set show(e){this.classList.toggle("visible",e)}}customElements.define("alchemy-overlay",Se);const Ce="BUTTON";class xe extends HTMLElement{connectedCallback(){this.pageId=this.getAttribute("page-id"),this.folded=this.hasAttribute("folded"),this.folderButton?.addEventListener("click",this)}disconnectedCallback(){this.folderButton?.removeEventListener("click",this)}async handleEvent(e){"click"===e.type&&await this.handleFolderClick(e)}async handleFolderClick(e){e.preventDefault(),e.stopPropagation();const t=e.currentTarget;t.innerHTML="";const s=new y("small");s.spin(t);try{await ee(Alchemy.routes.fold_admin_page_path(this.pageId),null,"text/vnd.turbo-stream.html"),this.folded=!this.folded,this.toggleAttribute("folded",this.folded),this.toggleChildren(),this.updateFolderIcon()}catch(e){q(e.message||e,"error"),this.updateFolderIcon()}finally{s.stop()}}toggleChildren(){const e=this.querySelector(`#page_${this.pageId}_children`);e&&e.classList.toggle("hidden",this.folded)}updateFolderIcon(){if(this.folderButton){const e=this.folded?"arrow-right-s":"arrow-down-s";this.folderButton.innerHTML=``}}updateFolderButton(){const e=this.querySelector(".page_folder");if(!e)return;const t=this.hasChildren||this.folded;if(t&&"SPAN"===e.tagName){const t=this.folded?"arrow-right-s":"arrow-down-s";e.outerHTML=``,this.folderButton?.addEventListener("click",this)}else t||e.tagName!==Ce?t&&e.tagName===Ce&&this.updateFolderIcon():e.outerHTML=''}get hasChildren(){const e=this.querySelector(`#page_${this.pageId}_children`);return!!e&&e.querySelectorAll(":scope > alchemy-page-node").length>0}get folderButton(){return this.querySelector("button.page_folder")}}customElements.define("alchemy-page-node",xe);class Te extends HTMLElement{connectedCallback(){const e=this.querySelector("alchemy-datepicker:has(#page_public_on)"),t=this.querySelector("alchemy-datepicker:has(#page_public_until)"),s=this.querySelector(".page-publication-date-fields"),i=this.querySelector("#page_public");i&&i.addEventListener("click",(function(i){const n=i.target,r=new Date;n.checked?(s.classList.remove("hidden"),e.flatpickr.setDate(r)):(s.classList.add("hidden"),e.flatpickr.clear()),t.flatpickr?.clear()}))}}customElements.define("alchemy-page-publication-fields",Te);customElements.define("alchemy-page-select",class extends D{get pageId(){return this.selection?JSON.parse(this.selection).id:void 0}_searchQuery(e,t){return{q:{name_cont:e,...JSON.parse(this.queryParams)},page:t}}_parseResponse(e){const t=e.meta;return{results:e.pages,more:t.page*t.per_page\n
    \n \n ${this._hightlightTerm(e.name,t)}\n ${e.site.name}\n
    \n
    \n ${e.url_path}\n ${e.language_code}\n
    \n
    \n `}});class Ae extends HTMLElement{constructor(){super(),this.addEventListener("change",this)}handleEvent(e){if("change"===e.type)this.onChange()}onChange(){const e=new URL(this.getAttribute("url")),t=this.querySelector("select");e.searchParams.set("language_id",t.value),Turbo.visit(e,{frame:"picture_descriptions"})}}customElements.define("alchemy-picture-description-select",Ae);class $e extends HTMLElement{constructor(){super(),this.cropFromField=this.querySelector("[data-crop-from]"),this.cropSizeField=this.querySelector("[data-crop-size]"),this.pictureIdField=this.querySelector("[data-picture-id]"),this.targetSizeField=this.querySelector("[data-target-size]"),this.imageCropperField=this.querySelector("[data-image-cropper]"),this.image=this.querySelector("img"),this.pictureThumbnail=this.querySelector("alchemy-picture-thumbnail"),this.deleteButton=this.querySelector(".picture_tool.delete"),this.cropLink=this.querySelector(".crop_link"),this.targetSize=this.targetSizeField.dataset.targetSize,this.pictureId=this.pictureIdField.value,this.update=function(e,t){let s;return function(...i){const n=this;clearTimeout(s),s=setTimeout((()=>e.apply(n,i)),t)}}((()=>{this.updateImage(),this.updateCropLink()}),125),this.deleteButton?.addEventListener("click",this.removeImage.bind(this))}connectedCallback(){this.observer=new MutationObserver(this.mutationCallback.bind(this)),this.observer.observe(this.cropFromField,{attributes:!0}),this.observer.observe(this.cropSizeField,{attributes:!0}),this.observer.observe(this.pictureIdField,{attributes:!0})}disconnectedCallback(){this.observer.disconnect()}mutationCallback(e){for(const t of e)"pictureId"in t.target.dataset&&(this.cropFromField.value="",this.cropSizeField.value="",this.pictureId=t.target.value),this.update()}updateImage(){this.pictureId&&(this.pictureThumbnail.loading=!0,Z(Alchemy.routes.url_admin_picture_path(this.pictureId),{crop:this.imageCropperEnabled,crop_from:this.cropFrom,crop_size:this.cropSize,flatten:!0,size:"160x120"}).then((({data:e})=>{this.pictureThumbnail.src=e.url,this.pictureThumbnail.image.alt=e.alt,this.pictureThumbnail.image.title=e.title,this.setElementDirty()})).catch((e=>{console.error(e.message||e),q(e.message||e,"error")})))}removeImage(){this.pictureThumbnail.innerHTML='',this.pictureIdField.value="",this.image=null,this.cropLink.classList.add("disabled"),this.setElementDirty()}setElementDirty(){this.closest(".element-editor").setDirty(this)}updateCropLink(){this.pictureId&&this.imageCropperEnabled&&(this.cropLink.classList.remove("disabled"),this.cropLink.href.match(/(picture_id=)\d+/)?this.cropLink.href=this.cropLink.href.replace(/(picture_id=)\d+/,"$1"+this.pictureId):this.cropLink.href=this.cropLink.href+`&picture_id=${this.pictureId}`)}get cropFrom(){return""===this.cropFromField.value?this.defaultCropFrom.join("x"):this.cropFromField.value}get cropSize(){return""===this.cropSizeField.value?this.defaultCropSize.join("x"):this.cropSizeField.value}get defaultCropSize(){if(!this.imageCropperEnabled)return[];const e=this.targetSize.split("x").map((e=>parseInt(e))),t=(s=e[0]/this.imageFileWidth,i=e[1]/this.imageFileHeight,s>=i?s:i);var s,i;return[Math.round(e[0]/t),Math.round(e[1]/t)]}get defaultCropFrom(){if(!this.imageCropperEnabled)return[];const e=this.defaultCropSize;return[Math.round((this.imageFileWidth-e[0])/2),Math.round((this.imageFileHeight-e[1])/2)]}get imageFileWidth(){return parseInt(this.pictureIdField.dataset.imageFileWidth)}get imageFileHeight(){return parseInt(this.pictureIdField.dataset.imageFileHeight)}get imageCropperEnabled(){return"true"===this.targetSizeField.dataset.imageCropper}}customElements.define("alchemy-picture-editor",$e);class qe extends HTMLElement{constructor(){super(),this.classList.add("thumbnail_background"),this.spinner=new y("small"),this.src&&this.start()}handleEvent(e){switch(e.type){case"load":this.#ee();break;case"error":this.#te(e)}}connectedCallback(){this.#se()}disconnectedCallback(){this.image?.removeEventListener("load",this),this.image?.removeEventListener("error",this),this.stop()}createImage(e=this.src,t=this.name){this.image=new Image,this.image.src=e,t&&(this.image.alt=t)}start(e){this.createImage(e),this.image.addEventListener("load",this),this.image.addEventListener("error",this),this.load()}load(){this.image?.complete||(this.setAttribute("loading","loading"),this.innerHTML="",this.spinner.spin(this))}stop(){this.classList.remove("loading"),this.spinner.stop()}#ee(){this.spinner.stop(),this.removeAttribute("loading")}#te(e){const t=`Could not load ${this.image.src}`,s=this.closest(".ingredient-editor");this.spinner.stop(),this.innerHTML=`\n \n \n \n `,console.error(t,e)}#se(){this.image?.complete?this.replaceChildren(this.image):this.image&&this.append(this.image)}set loading(e){e?this.load():this.stop()}set src(e){this.start(e),this.#se()}get name(){return this.getAttribute("name")}get src(){return this.getAttribute("src")}}customElements.define("alchemy-picture-thumbnail",qe);class Fe extends HTMLElement{constructor(){super(),this.addEventListener("submit",this)}connectedCallback(){document.addEventListener("alchemy:page-dirty",this)}disconnectedCallback(){document.removeEventListener("alchemy:page-dirty",this)}handleEvent(e){switch(e.type){case"alchemy:page-dirty":this.markDirty(e.detail);break;case"submit":this.button.loading=!0}}markDirty(e){this.button.variant="primary",this.button.disabled=!1,this.tooltip.content=e.tooltip}get button(){return this.querySelector("sl-button")}get tooltip(){return this.querySelector("sl-tooltip")}}customElements.define("alchemy-publish-page-button",Fe);class Ie extends HTMLSelectElement{#ie;connectedCallback(){this.classList.add("alchemy_selectbox"),this.#ie=$(this).select2({minimumResultsForSearch:5,dropdownAutoWidth:!0,allowClear:!!this.allowClear}),this.allowClear||this.multiple||this.#ie.prev(".select2-container").find(".select2-search-choice-close").remove()}enable(){this.removeAttribute("disabled"),this.#ne()}disable(){this.setAttribute("disabled","disabled"),this.#ne()}setOptions(e,t=void 0){let s=this.value;this.innerHTML="",t&&this.add(new Option(t,"")),e.forEach((e=>{this.add(new Option(e.text,e.id,!1,e.id===s))})),this.#ne()}#ne(){this.#ie.trigger("change")}get allowClear(){return this.dataset.hasOwnProperty("allowClear")||this.multiple}}customElements.define("alchemy-select",Ie,{extends:"select"});class Me extends HTMLElement{connectedCallback(){this.searchInput=document.querySelector(".search_input_field"),this.clearButton=document.querySelector("#search_field_clear"),this.resultCounter=document.querySelector("#page_filter_result"),this.setupSearch(),requestAnimationFrame((()=>{this.setupSortables()})),this.observer=new MutationObserver((e=>{e.forEach((e=>{e.addedNodes.forEach((e=>{e.nodeType===Node.ELEMENT_NODE&&(e.classList?.contains("children")&&this.setupSortable(e),e.querySelectorAll(".children").forEach((e=>this.setupSortable(e))))}))}))})),this.observer.observe(this,{childList:!0,subtree:!0})}disconnectedCallback(){this.teardownSearch(),this.observer?.disconnect()}setupSearch(){this.searchInput?.addEventListener("input",this),this.clearButton?.addEventListener("click",this)}teardownSearch(){this.searchInput?.removeEventListener("input",this),this.clearButton?.removeEventListener("click",this)}handleEvent(e){"input"===e.type&&e.target===this.searchInput?this.handleSearch(e):"click"===e.type&&e.target===this.clearButton&&this.handleClearSearch(e)}handleSearch(e){const t=e.target.value.toLowerCase().trim();""!==t?this.filterPages(t):this.clearFilter()}filterPages(e){const t=this.querySelectorAll(".sitemap_page");let s=0,i=null;t.forEach((t=>{(t.getAttribute("name")||"").toLowerCase().includes(e)?(t.classList.add("highlight"),t.classList.remove("no-match"),s++,i||(i=t)):(t.classList.remove("highlight"),t.classList.add("no-match"))})),1===s?(this.resultCounter.textContent=`1 ${h("page_found")}`,this.resultCounter.style.display="block"):s>1?(this.resultCounter.textContent=`${s} ${h("pages_found")}`,this.resultCounter.style.display="block"):this.resultCounter.style.display="none",i&&i.scrollIntoView({behavior:"smooth",block:"center"})}clearFilter(){this.querySelectorAll(".sitemap_page").forEach((e=>{e.classList.remove("highlight","no-match")})),this.resultCounter.style.display="none"}handleClearSearch(e){e.preventDefault(),this.searchInput.value="",this.clearFilter()}setupSortable(e){new i(e,{group:"pages",animation:150,fallbackOnBody:!0,swapThreshold:.65,handle:".page-icon.handle",draggable:"alchemy-page-node",onEnd:e=>this.handleSort(e)})}setupSortables(){this.querySelectorAll(".children").forEach((e=>this.setupSortable(e)))}async handleSort(e){if(e.from===e.to&&e.oldIndex===e.newIndex)return;const t=e.item,s=t.pageId,i=Alchemy.routes.move_admin_page_path(s),n={target_parent_id:e.to.dataset.parentId,new_position:e.newIndex};S(!0);try{const r=await ee(i,n),l=await r.data,o=t.querySelector(`#page_${s}`);if(o){const e=o.querySelector(".sitemap_url");e&&l.url_path&&(e.textContent=l.url_path)}this.updateFolderIcons(e.from,e.to),q(h("Successfully moved page"))}catch(e){q(e.message||e,"error"),window.location.reload()}finally{S(!1)}}updateFolderIcons(e,t){const s=e.closest("alchemy-page-node");if(s?.updateFolderButton(),e!==t){const e=t.closest("alchemy-page-node");e?.updateFolderButton()}}}customElements.define("alchemy-sitemap",Me);const ze={draggable:".element-editor",handle:".element-handle.draggable",ghostClass:"dragged",animation:150,swapThreshold:.65,easing:"cubic-bezier(1, 0, 0, 1)"};function Be(e){const t=e.item.dataset.elementName;document.querySelectorAll(`[data-droppable-elements~="${t}"]`).forEach((e=>e.classList.add("droppable-elements")))}function He(e){const t=e.item,s=e.to.parentElement.closest(".element-editor"),i={element_id:t.dataset.elementId,position:e.newIndex+1};s&&(i.parent_element_id=s.dataset.elementId),e.target===e.to&&te(Alchemy.routes.order_admin_elements_path,i).then((e=>{const s=e.data;q(s.message),s.pageHasUnpublishedChanges&&le(s),M(),t.updateTitle(s.preview_text)}))}function De(){document.querySelectorAll("[data-droppable-elements]").forEach((e=>e.classList.remove("droppable-elements")))}class Pe extends HTMLElement{connectedCallback(){const e={name:this.dataset.elementName,put:(e,t,s)=>e.el.dataset.droppableElements.split(" ").includes(s.dataset.elementName)};new i(this,{...ze,onStart:Be,onSort:He,onEnd:De,group:e})}}customElements.define("alchemy-sortable-elements",Pe);class Oe extends HTMLElement{connectedCallback(){this.className=`spinner spinner--${this.size}`,this.innerHTML=`\n \n \n \n \n \n `}get size(){return this.getAttribute("size")||"medium"}get color(){return this.getAttribute("color")||"currentColor"}}customElements.define("alchemy-spinner",Oe);class Ne extends HTMLElement{async connectedCallback(){await d(),this.classList.add("autocomplete_tag_list"),$(this.input).select2(this.select2Config)}get input(){return this.getElementsByTagName("input")[0]}get select2Config(){return{tags:!0,tokenSeparators:[","],openOnEnter:!1,minimumInputLength:1,createSearchChoice:this.#re,ajax:{url:this.getAttribute("url"),dataType:"json",data:e=>({term:e}),results:e=>({results:e})},initSelection:this.#le}}#re(e,t){if(0===$(t).filter((function(){return 0===this.text.localeCompare(e)})).length)return{id:e,text:e}}#le(e,t){const s=[];$(e.val().split(",")).each((function(){s.push({id:this.trim(),text:this})})),t(s)}}customElements.define("alchemy-tags-autocomplete",Ne);const Ue="alchemy-dark",Re="alchemy";class je extends HTMLElement{#oe=null;connectedCallback(){this.className="tinymce_container",this.querySelector(":scope > alchemy-spinner")||this.insertAdjacentHTML("beforeend",''),this.style.minHeight=`${this.minHeight}px`,this.editor.style.display="none";const e={root:document.getElementById("element_area"),rootMargin:"0px",threshold:[.05]};this.tinymceIntersectionObserver=new IntersectionObserver(((e,t)=>{e.forEach((e=>{e.intersectionRatio>0&&(this._initTinymceEditor(),t.unobserve(e.target))}))}),e),this.tinymceIntersectionObserver.observe(this),this._setupThemeChangeListener()}disconnectedCallback(){this.tinymceIntersectionObserver?.disconnect(),this._removeThemeChangeListener(),tinymce.get(this.editorId)?.remove(this.editorId)}_initTinymceEditor(){tinymce.init(this.configuration).then((e=>{e.forEach((e=>this._setupEditor(e)))}))}_setupEditor(e){e.show();const t=this.getElementsByTagName("alchemy-spinner")[0];t&&t.remove(),this.elementEditor&&(e.on("dirty",(e=>{this.elementEditor.setDirty(e.target.editorContainer)})),e.on("click",(()=>this.elementEditor.onClickElement(!1))))}_setupThemeChangeListener(){this.darkModeMediaQuery=window.matchMedia("(prefers-color-scheme: dark)"),this.themeChangeHandler=e=>this._handleThemeChange(e),this.darkModeMediaQuery.addEventListener("change",this.themeChangeHandler)}_removeThemeChangeListener(){this.darkModeMediaQuery&&this.themeChangeHandler&&this.darkModeMediaQuery.removeEventListener("change",this.themeChangeHandler)}_handleThemeChange(e){const t=tinymce.get(this.editorId);if(t){const s=e.matches?Ue:Re,i=e.matches?Ue:Re;t.remove(),tinymce.init({content_css:i,...this.configuration,skin:s}).then((e=>{e.forEach((e=>this._setupEditor(e)))}))}}get configuration(){const e={};this.getAttributeNames().forEach((t=>{if(!["class","id","is","name","style"].includes(t)){const s=this.getAttribute(t),i=t.replaceAll("-","_");if(s===t||""===s)e[i]=!0;else try{e[i]=JSON.parse(s)}catch(t){e[i]=s}}}));const t={content_css:this.preferredTheme,...Alchemy.TinymceDefaults,...e,language:c(),selector:`#${this.editorId}`,skin:this.preferredTheme};return t.height=t.min_height,t}get preferredTheme(){return window.matchMedia("(prefers-color-scheme: dark)").matches?Ue:Re}get editorId(){return this.editor.id}get editor(){return this.getElementsByTagName("textarea")[0]}get elementEditor(){return document.getElementById(this.editorId).closest("alchemy-element-editor")}get minHeight(){return this.#oe||this.configuration.min_height}set minHeight(e){this.#oe=e}}customElements.define("alchemy-tinymce",je);class We extends HTMLElement{async connectedCallback(){const e=new y("small");e.spin(this);try{const e=await fetch(this.url,{credentials:"include"}),t=await e.json();e.ok?this.showStatus(t):this.showError(e)}catch(e){this.showError(e)}finally{e.stop()}}get url(){return this.getAttribute("url")}showStatus(e){"true"==e.status?this.querySelector(".update_available").classList.remove("hidden"):this.querySelector(".up_to_date").classList.remove("hidden")}showError(e){this.querySelector(".error").classList.remove("hidden"),console.error("[alchemy] Error fetching update status",e)}}var Je;customElements.define("alchemy-update-check",We),Je=Handlebars.template,(Handlebars.templates=Handlebars.templates||{})["node_folder.hbs"]=Je({0:function(e,t,s,i,n){return"right"},1:function(e,t,s,i,n){return"down"},compiler:[8,">= 4.3.0"],main:function(e,t,s,i,n){var r,l=e.lambda,o=e.escapeExpression,a=e.lookupProperty||function(e,t){if(Object.prototype.hasOwnProperty.call(e,t))return e[t]};return'\n \n\n'},useData:!0}),n("tooltip.show",{keyframes:[{transform:"translateY(10px)",opacity:"0"},{transform:"translateY(0)",opacity:"1"}],options:{duration:100}}),n("tooltip.hide",{keyframes:[{transform:"translateY(0)",opacity:"1"},{transform:"translateY(10px)",opacity:"0"}],options:{duration:100}}),n("dialog.show",{keyframes:[{transform:"scale(0.98)",opacity:"0"},{transform:"scale(1)",opacity:"1"}],options:{duration:150}}),n("dialog.hide",{keyframes:[{transform:"scale(1)",opacity:"1"},{transform:"scale(0.98)",opacity:"0"}],options:{duration:150}});const Ve=document.querySelector('link[rel="preload"][as="image"]').getAttribute("href"),Xe={"x-lg":"close",caret:"arrow-down-s"},Qe={resolver:e=>`${Ve}#ri-${Xe[e]||e}-line`,mutator:e=>{e.setAttribute("fill","currentColor"),e.setAttribute("viewBox","0 0 24 24")},spriteSheet:!0};function Ye(e,t,s,i){document.querySelectorAll(t).forEach((t=>{t.addEventListener(e,(e=>{const n=Array.from(t.querySelectorAll(s));let r=e.target;for(;r!==t;){if(n.includes(r))return void i.call(r,e);r=r.parentElement}}))}))}r("default",Qe),r("system",Qe);class Ke{#ae=!1;#ce=null;#he=null;#de=null;constructor(e,t){this.image=e,this.defaultBox=t.default_box,this.aspectRatio=t.ratio,this.#he=document.getElementById(t.crop_from_form_field_id),this.#de=document.getElementById(t.crop_size_form_field_id),this.elementId=t.element_id,this.dialog=Alchemy.currentDialog(),this.dialog&&(this.dialog.options.closed=()=>this.destroy(),this.bind()),this.init()}get cropperOptions(){return{aspectRatio:this.aspectRatio,viewMode:1,zoomable:!1,checkCrossOrigin:!1,checkOrientation:!1,data:this.box,cropend:()=>{const e=this.#ce.getData(!0);this.update(e)}}}get cropFrom(){if(this.#he?.value)return this.#he.value.split("x").map((e=>parseInt(e)))}get cropSize(){if(this.#de?.value)return this.#de.value.split("x").map((e=>parseInt(e)))}get box(){return this.cropFrom&&this.cropSize?{x:this.cropFrom[0],y:this.cropFrom[1],width:this.cropSize[0],height:this.cropSize[1]}:this.defaultBoxSize}get defaultBoxSize(){return{x:this.defaultBox[0],y:this.defaultBox[1],width:this.defaultBox[2],height:this.defaultBox[3]}}init(){this.#ae||(this.#ce=new l(this.image,this.cropperOptions),this.#ae=!0)}update(e){this.#he.value=`${e.x}x${e.y}`,this.#he.dispatchEvent(new Event("change")),this.#de.value=`${e.width}x${e.height}`,this.#de.dispatchEvent(new Event("change"))}reset(){this.#ce.setData(this.defaultBoxSize),this.update(this.defaultBoxSize)}destroy(){return this.#ce&&this.#ce.destroy(),this.#ae=!1,!0}bind(){this.dialog.dialog_body.find('button[type="submit"]').on("click",(()=>(document.querySelector(`[data-element-id='${this.elementId}']`).setDirty(),this.dialog.close(),!1))),this.dialog.dialog_body.find('button[type="reset"]').on("click",(()=>(this.reset(),!1)))}}class Ge extends b{constructor(e,t={}){super(e,t)}init(){$(".zoomed-picture-background").on("click",(e=>{if(e.stopPropagation(),"IMG"!==e.target.nodeName)return this.close(),!1})),$(".picture-overlay-handle").on("click",(e=>(this.dialog.toggleClass("hide-form"),!1))),this.$previous=$(".previous-picture"),this.$next=$(".next-picture"),this.#ue(),super.init()}previous(){null!=this.$previous[0]&&this.$previous[0].click()}next(){null!=this.$next[0]&&this.$next[0].click()}build(){this.dialog_container=$('
    '),this.dialog=$('
    '),this.dialog_body=$('
    '),this.close_button=$('\n \n '),this.dialog.append(this.close_button),this.dialog.append(this.dialog_body),this.dialog_container.append(this.dialog),this.overlay=$('
    '),this.$body.append(this.overlay),this.$body.append(this.dialog_container)}#ue(){this.$document.keydown((e=>{if("INPUT"===e.target.nodeName||"TEXTAREA"===e.target.nodeName)return!0;switch(e.which){case 37:return this.previous(),!1;case 39:return this.next(),!1;default:return!0}}))}}function Ze(){return document.querySelectorAll("#picture_archive input:checked")}function et(){const e=document.querySelector("#select_all_pictures"),t=document.querySelector(".selected_item_tools");Ye("click",".toolbar_buttons","a#select_all_pictures",(s=>{s.preventDefault(),e.classList.toggle("active");const i=e.classList.contains("active");!function(e){document.querySelectorAll(".picture_tool.select input[type='checkbox']").forEach((t=>{t.checked=e,t.closest(".picture_thumbnail").classList.toggle("active",e)}))}(i),t.classList.toggle("hidden",!i)})),Ye("change",".picture_tool.select","input",(e=>{t.classList.toggle("hidden",0===Ze().length);const s=e.target.parentElement.classList,i=e.target.checked;s.toggle("visible",i)})),Ye("click",".selected_item_tools","a#edit_multiple_pictures",(e=>{e.preventDefault();_(function(e){const t=new URL(e);return Ze().forEach((e=>t.searchParams.append(e.name,e.value))),t.toString()}(e.target.href),{title:e.target.title,size:"400x295"})}))}function tt(){document.querySelectorAll("li.menu-item").forEach((e=>{const t=e.querySelector(".nodes_tree-left_images"),s=e.querySelector(".children"),i={folded:"true"===e.dataset.folded,id:e.dataset.id,type:e.dataset.type};s.children.length>0||i.folded?t.innerHTML=Handlebars.templates["node_folder.hbs"]({node:i}):t.innerHTML=" "}))}function st(e){ee(Alchemy.routes[e.item.dataset.type].move_api_path(e.item.dataset.id),{target_parent_id:e.to.dataset.recordId,new_position:e.newIndex}).then((()=>{q(Alchemy.t("Successfully moved menu item")),tt()})).catch((e=>{q(e.message||e,"error")}))}function it(){Ye("click",".nodes_tree",".node_folder",(function(){const e=this.dataset.recordId,t=this.closest("li.menu-item"),s=Alchemy.routes[this.dataset.recordType].toggle_folded_api_path(e),i=t.querySelector(".children");ee(s).then((()=>{i.classList.toggle("folded"),t.dataset.folded="true"==t.dataset.folded?"false":"true",tt()})).catch((e=>{q(e.message||e)}))})),tt(),document.querySelectorAll(".nodes_tree ul.children").forEach((e=>{new i(e,{group:"nodes",animation:150,fallbackOnBody:!0,swapThreshold:.65,handle:".node_name",invertSwap:!0,onEnd:st})}))}void 0===window.Alchemy&&(window.Alchemy={}),Object.assign(Alchemy,{closeCurrentDialog:w,currentDialog:E,...x,t:h,FixedElements:A,growl:q,LinkDialog:class extends b{#me;constructor(e){const t=new URL(Alchemy.routes.link_admin_pages_path,window.location),s={url:e.url,selected_tab:e.type,link_title:e.title,link_target:e.target};Object.keys(s).forEach((e=>{s[e]&&t.searchParams.set(e,s[e])})),super(t.href,{size:"600x320",title:h("Link")})}replace(e){super.replace(e),this.#c()}open(){return super.open(),new Promise((e=>this.#me=e))}#c(){const e=document.querySelector('[data-link-form-type="internal"]'),t=document.querySelector('[data-link-form-type="file"] alchemy-attachment-select');e.addEventListener("Alchemy.RemoteSelect.Change",(e=>{this.#pe(e.detail.added)})),t.addEventListener("Alchemy.RemoteSelect.Change",(e=>{const t=e.detail.added;document.getElementById("file_link").value=t?t.url:""})),document.querySelectorAll("[data-link-form-type]").forEach((e=>{e.addEventListener("submit",(e=>{e.preventDefault(),this.#ge(e.target.dataset.linkFormType)}))}))}#pe(e=null){const t=document.getElementById("internal_link"),s=document.querySelector('[data-link-form-type="internal"] alchemy-dom-id-api-select');t.value=e?e.url_path:"",s.page=e?e.id:void 0}#ge(e){const t=document.getElementById("element_anchor");let s=document.getElementById(`${e}_link`).value;if("internal"===e&&""!==t.value)s=s.replace(F,"")+t.value;else if("external"===e&&!s.match(Alchemy.link_url_regexp))return void this.#ye();this.#me({url:s.trim(),title:document.getElementById(`${e}_link_title`).value,target:document.getElementById(`${e}_link_target`)?.value,type:e}),this.close()}#ye(){const e=document.getElementById("errors");e.querySelector("ul").innerHTML=`
  • ${Alchemy.t("url_validation_failed")}
  • `,e.style.display="block"}},pleaseWaitOverlay:S,Spinner:y,reloadPreview:M}),t.start(),e.config.forms.confirm=L,document.addEventListener("turbo:load",(function(){document.documentElement.classList.remove("no-js"),p(),document.querySelectorAll(".please_wait").forEach((e=>{e.addEventListener("click",S)})),document.querySelectorAll("a.button").forEach((e=>{e.setAttribute("tabindex",0)})),key.filter=function(e){let t=(e.target||e.srcElement).tagName;return key.isPressed("esc")||!("INPUT"===t||"SELECT"===t||"TEXTAREA"===t)}}));export{Ke as ImageCropper,Ge as ImageOverlay,it as NodeTree,D as RemoteSelect,Ye as on,et as pictureSelector}; //# sourceMappingURL=alchemy_admin.min.js.map diff --git a/app/assets/builds/alchemy/alchemy_admin.min.js.map b/app/assets/builds/alchemy/alchemy_admin.min.js.map index 75286476f7..972bea3398 100644 --- a/app/assets/builds/alchemy/alchemy_admin.min.js.map +++ b/app/assets/builds/alchemy/alchemy_admin.min.js.map @@ -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/dialog.js","../../../javascript/alchemy_admin/confirm_dialog.js","../../../javascript/alchemy_admin/please_wait_overlay.js","../../../javascript/alchemy_admin/dirty.js","../../../javascript/alchemy_admin/fixed_elements.js","../../../javascript/alchemy_admin/growler.js","../../../javascript/alchemy_admin/link_dialog.js","../../../javascript/alchemy_admin/components/preview_window.js","../../../javascript/alchemy_admin/ingredient_anchor_link.js","../../../javascript/alchemy_admin/components/action.js","../../../javascript/alchemy_admin/components/alchemy_html_element.js","../../../javascript/alchemy_admin/utils/string_conversions.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/clipboard_button.js","../../../javascript/alchemy_admin/components/color_select.js","../../../javascript/alchemy_admin/components/datepicker.js","../../../javascript/alchemy_admin/components/dialog_link.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/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/message.js","../../../javascript/alchemy_admin/components/growl.js","../../../javascript/alchemy_admin/components/icon.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/utils/format.js","../../../javascript/alchemy_admin/components/node_select.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_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/select.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/templates/compiled.js","../../../javascript/alchemy_admin/shoelace_theme.js","../../../javascript/alchemy_admin/utils/events.js","../../../javascript/alchemy_admin/image_cropper.js","../../../javascript/alchemy_admin/image_overlay.js","../../../javascript/alchemy_admin/picture_selector.js","../../../javascript/alchemy_admin/node_tree.js","../../../javascript/alchemy_admin.js","../../../javascript/alchemy_admin/initializer.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 const translations = 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\nexport async function setupSelectLocale() {\n const locale = currentLocale()\n if (locale === \"en\") return\n\n await import(`select2/${locale}.js`)\n $.extend($.fn.select2.defaults, $.fn.select2.locales[locale])\n}\n","import \"keymaster\"\nimport { openDialog } from \"alchemy_admin/dialog\"\n\nconst bindedHotkeys = []\n\nfunction showHelp(evt) {\n if (\n !$(evt.target).is(\"input, textarea\") &&\n String.fromCharCode(evt.which) === \"?\"\n ) {\n openDialog(\"/admin/help\", {\n title: Alchemy.t(\"help\"),\n size: \"400x492\"\n })\n return false\n } else {\n return true\n }\n}\n\nexport default function (scope = document) {\n // The scope can be a jQuery object because we still use jQuery in alchemy_admin/dialog.js.\n if (scope instanceof jQuery) {\n scope = scope[0]\n }\n\n // Unbind all previously registered hotkeys if we are not inside a dialog.\n if (scope === document) {\n document.removeEventListener(\"keypress\", showHelp)\n document.addEventListener(\"keypress\", showHelp)\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 ``\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","import Hotkeys from \"alchemy_admin/hotkeys\"\nimport Spinner from \"alchemy_admin/spinner\"\n\n// Collection of all current dialog instances\nconst currentDialogs = []\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 // 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 this.$document = $(document)\n this.$window = $(window)\n this.$body = $(\"body\")\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 this.dialog.trigger(\"Alchemy.DialogOpen\")\n this.bind_close_events()\n window.requestAnimationFrame(() => {\n this.dialog_container.addClass(\"open\")\n if (this.overlay != null) {\n return this.overlay.addClass(\"open\")\n }\n })\n this.$body.addClass(\"prevent-scrolling\")\n currentDialogs.push(this)\n this.load()\n }\n\n // Closes the Dialog and removes it from the DOM\n close() {\n this.dialog.trigger(\"DialogClose.Alchemy\")\n this.$document.off(\"keydown\")\n this.dialog_container.removeClass(\"open\")\n if (this.overlay != null) {\n this.overlay.removeClass(\"open\")\n }\n this.$document.on(\n \"webkitTransitionEnd transitionend oTransitionEnd\",\n () => {\n this.$document.off(\"webkitTransitionEnd transitionend oTransitionEnd\")\n this.dialog_container.remove()\n if (this.overlay != null) {\n this.overlay.remove()\n }\n this.$body.removeClass(\"prevent-scrolling\")\n currentDialogs.pop(this)\n if (this.options.closed != null) {\n return this.options.closed()\n }\n }\n )\n return true\n }\n\n // Loads the content via ajax and replaces the Dialog body with server response.\n load() {\n this.show_spinner()\n $.get(this.url, (data) => {\n this.replace(data)\n }).fail((xhr) => {\n this.show_error(xhr)\n })\n }\n\n // Reloads the Dialog content\n reload() {\n this.dialog_body.empty()\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.hide()\n this.dialog_body.html(data)\n this.init()\n this.dialog[0].dispatchEvent(\n new CustomEvent(\"DialogReady.Alchemy\", {\n bubbles: true,\n detail: {\n body: this.dialog_body[0]\n }\n })\n )\n if (this.options.ready != null) {\n this.options.ready(this.dialog_body)\n }\n this.dialog_body.show()\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[0])\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 this.watch_remote_forms()\n }\n\n // Watches ajax requests inside of dialog body and replaces the content accordingly\n watch_remote_forms() {\n const $form = $('[data-remote=\"true\"]', this.dialog_body)\n\n $form.on(\"ajax:success\", (event) => {\n const xhr = event.detail[2]\n const content_type = xhr.getResponseHeader(\"Content-Type\")\n if (content_type.match(/javascript/)) {\n return\n } else {\n this.dialog_body.html(xhr.responseText)\n this.init()\n }\n })\n\n $form.on(\"ajax:error\", (event) => {\n const statusText = event.detail[1]\n const xhr = event.detail[2]\n this.show_error(xhr, statusText)\n })\n }\n\n // Displays an error message\n show_error(xhr, statusText) {\n if (xhr.status === 422) {\n this.dialog_body.html(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 const $errorDiv = $(`\n

    ${error_header}

    \n

    ${error_body}

    \n
    `)\n\n this.dialog_body.html($errorDiv)\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\n bind_close_events() {\n this.close_button.on(\"click\", () => {\n this.close()\n })\n this.dialog_container.addClass(\"closable\").on(\"click\", (e) => {\n if (e.target !== this.dialog_container.get(0)) {\n return true\n }\n this.close()\n return false\n })\n this.$document.keydown((e) => {\n if (e.which === 27) {\n this.close()\n return false\n } else {\n return true\n }\n })\n }\n\n // Builds the html structure of the Dialog\n build() {\n this.dialog_container = $('
    ')\n this.dialog = $('
    ')\n this.dialog_body = $('
    ')\n this.dialog_header = $('
    ')\n this.dialog_title = $('
    ')\n this.close_button = $(\n ''\n )\n this.dialog_title.text(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.addClass(\"modal\")\n }\n if (this.options.padding) {\n this.dialog_body.addClass(\"padded\")\n }\n if (this.options.modal) {\n this.overlay = $('
    ')\n this.$body.append(this.overlay)\n }\n this.$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.css({\n width: width,\n \"min-height\": height,\n overflow: this.options.overflow\n })\n\n if (this.options.overflow === \"hidden\") {\n this.dialog_body.css({\n height: height,\n overflow: \"auto\"\n })\n } else {\n this.dialog_body.css({\n \"min-height\": height,\n overflow: \"visible\"\n })\n }\n }\n\n getSize() {\n const padding = this.options.padding ? 16 : 0\n const doc_width = this.$window.width()\n const doc_height = this.$window.height()\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","import { createHtmlElement } from \"alchemy_admin/utils/dom_helpers\"\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 \n ${this.message}\n \n \n \n `)\n document.body.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} 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","/**\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 { openConfirmDialog } from \"alchemy_admin/confirm_dialog\"\nimport { translate } from \"alchemy_admin/i18n\"\nimport pleaseWaitOverlay from \"alchemy_admin/please_wait_overlay\"\n\nfunction checkPageDirtyness(element) {\n let callback = () => {}\n\n if ($(element).is(\"form\")) {\n callback = function () {\n const $form = $(\n ``\n )\n $form.append($(element).find(\"input\"))\n $form.appendTo(\"body\")\n\n pleaseWaitOverlay()\n $form.trigger(\"submit\")\n }\n } else if ($(element).is(\"a\")) {\n callback = () => Turbo.visit(element.pathname)\n }\n\n const isPageDirty =\n document.querySelectorAll(\"alchemy-element-editor.dirty\").length > 0\n\n if (isPageDirty) {\n openConfirmDialog(translate(\"page_dirty_notice\"), {\n title: translate(\"warning\"),\n ok_label: translate(\"ok\"),\n cancel_label: translate(\"cancel\")\n }).then((proceed) => {\n if (proceed) {\n window.onbeforeunload = void 0\n callback()\n }\n })\n return false\n }\n return true\n}\n\nfunction PageLeaveObserver() {\n document.querySelectorAll(\"#main_navi a\").forEach((element) => {\n element.addEventListener(\"click\", (event) => {\n if (!checkPageDirtyness(event.currentTarget)) {\n event.preventDefault()\n }\n })\n })\n}\n\nexport default {\n checkPageDirtyness,\n PageLeaveObserver\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 = `${label}`\n const panel = ``\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 \n ${message}\n \n `)\n flashNotices.append(flashMessage)\n}\n\nexport function growl(message, style = \"notice\") {\n build(message, style)\n}\n","import { translate } from \"alchemy_admin/i18n\"\nimport { Dialog } from \"alchemy_admin/dialog\"\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// 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(Alchemy.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}\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\" && !url.match(Alchemy.link_url_regexp)) {\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 `
  • ${Alchemy.t(\"url_validation_failed\")}
  • `\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 #previewReadyHandler\n\n constructor() {\n super()\n this.addEventListener(\"load\", this)\n this.#previewReadyHandler = this.#handlePreviewReadyMessage.bind(this)\n }\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 #handlePreviewReadyMessage(event) {\n if (event.data.message === \"Alchemy.previewReady\") {\n this.#clearLoadTimeout()\n this.#stopSpinner()\n this.#afterLoad?.call(this, event)\n }\n }\n\n connectedCallback() {\n let url = this.url\n\n this.#attachEvents()\n window.addEventListener(\"message\", this.#previewReadyHandler)\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 key.unbind(\"alt+r\")\n window.removeEventListener(\"message\", this.#previewReadyHandler)\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 5s 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 }, 5000)\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 = ``\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 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","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 { reloadPreview } from \"alchemy_admin/components/preview_window\"\nimport { removeTab } from \"alchemy_admin/fixed_elements\"\nimport { closeCurrentDialog } from \"alchemy_admin/dialog\"\nimport IngredientAnchorLink from \"alchemy_admin/ingredient_anchor_link\"\nimport pleaseWaitOverlay from \"alchemy_admin/please_wait_overlay\"\n\nclass Action extends HTMLElement {\n constructor() {\n super()\n\n // map action names with Javascript functions\n this.actions = {\n // add a intermediate closeCurrentDialog - action\n // this will be gone, if all dialogs are working with a promise and\n // we don't have to implicitly close the dialog\n closeCurrentDialog,\n reloadPreview,\n removeFixedElement: removeTab,\n updateAnchorIcon: IngredientAnchorLink.updateIcon,\n hidePleaseWaitOverlay() {\n pleaseWaitOverlay(false)\n }\n }\n }\n\n connectedCallback() {\n const func = this.actions[this.name]\n\n if (func) {\n func(...this.params)\n } else {\n console.error(`Unknown Alchemy action: ${this.name}`)\n }\n\n this.remove()\n }\n\n get name() {\n return this.getAttribute(\"name\")\n }\n\n get params() {\n if (this.hasAttribute(\"params\")) {\n return JSON.parse(this.getAttribute(\"params\"))\n }\n return []\n }\n}\n\ncustomElements.define(\"alchemy-action\", Action)\n","import { toCamelCase } from \"alchemy_admin/utils/string_conversions\"\n\nexport class AlchemyHTMLElement extends HTMLElement {\n static properties = {}\n\n /**\n * create the list of observed attributes\n * this function is a requirement for the `attributeChangedCallback` - method\n * @returns {string[]}\n * @link https://developer.mozilla.org/en-US/docs/Web/API/Web_Components#reference\n */\n static get observedAttributes() {\n return Object.keys(this.properties)\n }\n\n constructor(options = {}) {\n super()\n\n this.options = options\n this.changeComponent = true\n this.initialContent = this.innerHTML // store the inner content of the component\n }\n\n /**\n * run when the component will be initialized by the Browser\n * this is a default function\n * @link https://developer.mozilla.org/en-US/docs/Web/API/Web_Components#reference\n */\n async connectedCallback() {\n // parse the properties object and register property with the default values\n Object.keys(this.constructor.properties).forEach((name) => {\n // if the options was given via the constructor, they should be prefer (e.g. new ({title: \"Foo\"}))\n this[name] =\n this.options[name] ?? this.constructor.properties[name].default\n })\n\n // then process the attributes\n this.getAttributeNames().forEach((name) => this._updateFromAttribute(name))\n\n // render the component\n this._updateComponent()\n await this.connected()\n }\n\n /**\n * disconnected callback if the component is removed from the DOM\n * this is currently only a Proxy to the disconnected - callback to use the same callback structure\n * as for the connected - callback\n * @link https://developer.mozilla.org/en-US/docs/Web/API/Web_Components#reference\n */\n disconnectedCallback() {\n this.disconnected()\n }\n\n /**\n * triggered by the browser, if one of the observed attributes is changing\n * @link https://developer.mozilla.org/en-US/docs/Web/API/Web_Components#reference\n */\n attributeChangedCallback(name) {\n this._updateFromAttribute(name)\n this._updateComponent()\n }\n\n /**\n * a connected method to make it easier to overwrite the connection callback\n */\n async connected() {}\n\n /**\n * a disconnected method to make it easier to overwrite the disconnection callback\n */\n disconnected() {}\n\n /**\n * empty method container to allow the child component to put the rendered string into this method\n * @returns {String}\n */\n render() {\n return this.initialContent\n }\n\n /**\n * after render callback\n * the function will be triggered after the DOM was updated\n */\n afterRender() {}\n\n /**\n * Dispatches a custom event with given name\n * @param {string} name The name of the custom event\n * @param {object} detail Optional event details\n */\n dispatchCustomEvent(name, detail = {}) {\n const event = new CustomEvent(`Alchemy.${name}`, { bubbles: true, detail })\n this.dispatchEvent(event)\n }\n\n /**\n * (re)render the component content inside the component container\n * @private\n */\n _updateComponent() {\n if (this.changeComponent) {\n this.innerHTML = this.render()\n this.changeComponent = false\n this.afterRender()\n }\n }\n\n /**\n * update the value from the given attribute\n *\n * @param {string} name\n * @private\n */\n _updateFromAttribute(name) {\n const attributeValue = this.getAttribute(name)\n const propertyName = toCamelCase(name)\n const isBooleanValue =\n attributeValue.length === 0 || attributeValue === \"true\"\n\n const value = isBooleanValue ? true : attributeValue\n\n if (this[propertyName] !== value) {\n this[propertyName] = value\n this.changeComponent = true\n }\n }\n}\n","/**\n * convert dashes and underscore strings into camelCase strings\n * @param {string} str\n * @returns {string}\n */\nexport function toCamelCase(str) {\n return str\n .split(/-|_/)\n .reduce((a, b) => a + b.charAt(0).toUpperCase() + b.slice(1))\n}\n","import { AlchemyHTMLElement } from \"alchemy_admin/components/alchemy_html_element\"\nimport { setupSelectLocale } from \"alchemy_admin/i18n\"\n\nexport function hightlightTerm(name, term) {\n return name.replace(new RegExp(term, \"gi\"), (match) => `${match}`)\n}\n\nexport class RemoteSelect extends AlchemyHTMLElement {\n static properties = {\n allowClear: { default: false },\n selection: { default: undefined },\n placeholder: { default: \"\" },\n queryParams: { default: \"{}\" },\n url: { default: \"\" }\n }\n\n async connected() {\n await setupSelectLocale()\n\n this.input.classList.add(\"alchemy_selectbox\")\n\n $(this.input)\n .select2(this.select2Config)\n .on(\"select2-open\", (evt) => {\n this.onOpen(evt)\n })\n .on(\"change\", (evt) => {\n this.onChange(evt)\n })\n }\n\n /**\n * Optional on change handler called by Select2.\n * @param {Event} event\n */\n onChange(event) {\n this.dispatchCustomEvent(\"RemoteSelect.Change\", {\n removed: event.removed,\n added: event.added\n })\n }\n\n /**\n * Optional on open handler called by Select2.\n * @param {Event} event\n */\n onOpen(event) {\n // add focus to the search input. Select2 is handling the focus on the first opening,\n // but it does not work the second time. One process in select2 is \"stealing\" the focus\n // if the command is not delayed. It is an intermediate solution until we are going to\n // move away from Select2\n setTimeout(() => {\n document.querySelector(\"#select2-drop .select2-input\").focus()\n }, 100)\n }\n\n get input() {\n return this.getElementsByTagName(\"input\")[0]\n }\n\n get select2Config() {\n return {\n placeholder: this.placeholder,\n allowClear: this.allowClear,\n initSelection: (_$el, callback) => {\n if (this.selection) {\n callback(JSON.parse(this.selection))\n }\n },\n ajax: this.ajaxConfig,\n formatSelection: (item) => this._renderResult(item),\n formatResult: (item, _el, query) =>\n this._renderListEntry(item, query.term)\n }\n }\n\n /**\n * Ajax configuration for Select2\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 * Search query send to server from select2\n * @param {string} term\n * @param {number} page\n * @returns {object}\n * @private\n */\n _searchQuery(term, page) {\n return {\n q: {\n name_cont: term,\n ...JSON.parse(this.queryParams)\n },\n page: page\n }\n }\n\n /**\n * Parses server response into select2 results object\n * @param {object} response\n * @returns {object}\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 * result which is visible if a page was selected\n * @param {object} item\n * @returns {string}\n * @private\n */\n _renderResult() {\n throw new Error(\n \"You need to define a _renderResult function on your sub class!\"\n )\n }\n\n /**\n * html template for each list entry\n * @param {object} item\n * @param {string} term\n * @returns {string}\n * @private\n */\n _renderListEntry() {\n throw new Error(\n \"You need to define a _renderListEntry function on your sub class!\"\n )\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\nclass AttachmentSelect extends RemoteSelect {\n _renderResult(item) {\n return this._renderListEntry(item)\n }\n\n /**\n * html template for each list entry\n * @param {object} page\n * @param {string} term\n * @returns {string}\n * @private\n */\n _renderListEntry(attachment, term) {\n return `\n
    \n \n ${this._hightlightTerm(attachment.name, term)}\n
    \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 // Still using jQuery here, because select2 does not emit\n // the event from the original select element.\n $(this).on(\"change\", function (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}\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\n if (this.form.dataset.remote == \"true\") {\n this.form.addEventListener(\"ajax:complete\", this)\n }\n\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 \"ajax:complete\":\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 { AlchemyHTMLElement } from \"alchemy_admin/components/alchemy_html_element\"\nimport { translate } from \"alchemy_admin/i18n\"\n\nclass CharCounter extends AlchemyHTMLElement {\n static properties = {\n maxChars: { default: 60 }\n }\n connected() {\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.countCharacters()) // add arrow function to get a implicit this - binding\n }\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 = 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\ncustomElements.define(\"alchemy-char-counter\", CharCounter)\n","import \"clipboard\"\nimport { growl } from \"alchemy_admin/growler\"\n\nclass ClipboardButton extends HTMLElement {\n constructor() {\n super()\n\n this.innerHTML = `\n \n `\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","const formatItem = (object) => {\n const optionEl = object.element[0]\n const swatch = optionEl.dataset.swatch || optionEl.value\n const customColor = optionEl.value === \"custom_color\"\n const colorIndicator = customColor\n ? ``\n : ``\n\n return `\n
    \n ${colorIndicator}\n ${object.text}\n
    `\n}\n\nclass ColorSelect extends HTMLElement {\n connectedCallback() {\n if (this.select) {\n this.#initializeSelect2()\n $(this.select).on(\"change\", (event) =>\n this.#toggleColorPicker(event.val === \"custom_color\")\n )\n } else {\n this.colorInput?.addEventListener(\"input\", this)\n this.textInput?.addEventListener(\"input\", this)\n this.#toggleColorPicker(true)\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 #initializeSelect2() {\n this.select.classList.add(\"alchemy_selectbox\")\n const options = {\n minimumResultsForSearch: 10,\n formatResult: formatItem,\n formatSelection: formatItem\n }\n $(this.select).select2(options)\n }\n\n #toggleColorPicker(enabled = true) {\n this.colorInput.disabled = !enabled\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 get select() {\n return this.querySelector(\"select\")\n }\n}\n\ncustomElements.define(\"alchemy-color-select\", ColorSelect)\n","import { AlchemyHTMLElement } from \"alchemy_admin/components/alchemy_html_element\"\nimport { translate, currentLocale } from \"alchemy_admin/i18n\"\nimport flatpickr from \"flatpickr\"\n\nconst locale = currentLocale()\n\nclass Datepicker extends AlchemyHTMLElement {\n static properties = {\n inputType: { default: \"date\" }\n }\n\n constructor() {\n super()\n this.flatpickr = undefined\n }\n\n // Load the locales for flatpickr before setting it up.\n async connected() {\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\n this.flatpickr = flatpickr(this.inputField, this.flatpickrOptions)\n }\n\n disconnected() {\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\ncustomElements.define(\"alchemy-datepicker\", Datepicker)\n","import { Dialog } from \"alchemy_admin/dialog\"\n\nexport class DialogLink extends HTMLAnchorElement {\n constructor() {\n super()\n this.addEventListener(\"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","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 url.search = new URLSearchParams(data).toString()\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 } else {\n throw responseData || new Error(\"An error occurred during the transaction\")\n }\n}\n","import { get } from \"alchemy_admin/utils/ajax\"\nimport { translate } from \"alchemy_admin/i18n\"\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 const result = await get(Alchemy.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 }\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","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/components/element_editor\"\n\nexport class DeleteElementButton extends HTMLElement {\n constructor() {\n super()\n this.button?.addEventListener(\"click\", this)\n }\n\n async handleEvent() {\n const confirmed = await openConfirmDialog(this.message)\n if (confirmed) {\n const response = await ajax(\"DELETE\", this.url)\n this.#removeElement(response.data)\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 IngredientAnchorLink from \"alchemy_admin/ingredient_anchor_link\"\nimport { post } from \"alchemy_admin/utils/ajax\"\nimport { createHtmlElement } from \"alchemy_admin/utils/dom_helpers\"\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\"\n\nexport function dispatchPageDirtyEvent(data) {\n document.dispatchEvent(\n new CustomEvent(\"alchemy:page-dirty\", {\n detail: { tooltip: data.publishButtonTooltip }\n })\n )\n}\n\nexport class ElementEditor extends HTMLElement {\n constructor() {\n super()\n\n // Add event listeners\n this.addEventListener(\"click\", this)\n // Triggered by child elements\n this.addEventListener(\"alchemy:element-update-title\", this)\n // We use of @rails/ujs for Rails remote forms\n this.addEventListener(\"ajax:complete\", this)\n\n // Dirty observer still needs to be jQuery\n // in order to support select2.\n $(this.form).on(\"change\", this.onChange)\n\n this.header?.addEventListener(\"dblclick\", () => {\n this.toggle()\n })\n this.toggleButton?.addEventListener(\"click\", (evt) => {\n const elementEditor = evt.target.closest(\"alchemy-element-editor\")\n if (elementEditor === this) {\n this.toggle()\n }\n })\n }\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 // 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 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 \"ajax:complete\":\n if (event.target === this.body) {\n const xhr = event.detail[0]\n event.stopPropagation()\n this.onSaveElement(xhr)\n }\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.closest(\"alchemy-element-editor\").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 * Sets the element to saved state\n * Updates title\n * JS event bubbling will also update the parents element quote.\n * Shows error messages if ingredient validations fail\n * @argument {XMLHttpRequest} xhr\n */\n onSaveElement(xhr) {\n const data = JSON.parse(xhr.responseText)\n // Reset errors that might be visible from last save attempt\n this.setClean()\n // If validation failed\n if (xhr.status === 422) {\n const warning = data.warning\n // Create error messages\n // Mark ingredients as failed\n data.ingredientsWithErrors.forEach((ingredient) => {\n const ingredientEditor = this.querySelector(\n `[data-ingredient-id=\"${ingredient.id}\"]`\n )\n const errorDisplay = createHtmlElement(\n `${ingredient.errorMessage}`\n )\n ingredientEditor?.appendChild(errorDisplay)\n ingredientEditor?.classList.add(\"validation_failed\")\n })\n // Show message\n growl(warning, \"warn\")\n this.elementErrors.classList.remove(\"hidden\")\n } else {\n growl(data.notice)\n this.previewWindow?.refresh().then(() => {\n this.focusElementPreview()\n })\n this.updateTitle(data.previewText)\n data.ingredientAnchors.forEach((anchor) => {\n IngredientAnchorLink.updateIcon(anchor.ingredientId, anchor.active)\n })\n if (data.pageHasUnpublishedChanges) {\n dispatchPageDirtyEvent(data)\n }\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 quote.textContent = title\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(Alchemy.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(Alchemy.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 * @returns {HTMLFormElement|undefined}\n */\n get form() {\n return this.querySelector(\"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\ncustomElements.define(\"alchemy-element-editor\", ElementEditor)\n","import { hightlightTerm } from \"alchemy_admin/components/remote_select\"\n\nconst formatSelection = (option) => {\n return `\n
    \n ${option.icon}${option.text}\n
    \n `\n}\n\nconst formatItem = (icon, text, hint) => {\n const description = hint\n ? `
    ${hint}
    `\n : \"\"\n return `\n
    \n ${formatSelection({ icon, text })}\n ${description}\n
    \n `\n}\n\nclass ElementSelect extends HTMLElement {\n constructor() {\n super()\n }\n\n connectedCallback() {\n const results = this.options\n const options = {\n minimumResultsForSearch: 3,\n dropdownAutoWidth: true,\n data() {\n return { results }\n },\n formatResult: (option, _el, search) => {\n let text\n\n if (option.id === \"\") return option.text\n if (search.term !== \"\") {\n text = hightlightTerm(option.text, search.term)\n } else {\n text = option.text\n }\n\n return formatItem(option.icon, text, option.hint)\n },\n formatSelection,\n placeholder: this.placeholder\n }\n $(this.inputField).select2(options)\n }\n\n get options() {\n return JSON.parse(this.getAttribute(\"options\"))\n }\n\n get placeholder() {\n return this.getAttribute(\"placeholder\")\n }\n\n get inputField() {\n return this.querySelector(\"input\")\n }\n}\n\ncustomElements.define(\"alchemy-element-select\", ElementSelect)\n","import { ElementEditor } from \"alchemy_admin/components/element_editor\"\n\nclass ElementsWindow extends HTMLElement {\n #visible = true\n #turboFrame = null\n\n constructor() {\n super()\n this.#attachEvents()\n }\n\n connectedCallback() {\n this.toggleButton?.addEventListener(\"click\", (evt) => {\n evt.preventDefault()\n this.toggle()\n })\n if (window.location.hash) {\n this.focusElementEditor(window.location.hash)\n }\n this.resize()\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 #attachEvents() {\n this.collapseButton?.addEventListener(\"click\", () => {\n this.collapseAllElements()\n })\n window.addEventListener(\"message\", (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 document.body.addEventListener(\"click\", (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}\n\ncustomElements.define(\"alchemy-elements-window\", ElementsWindow)\n","class ElementsWindowHandle extends HTMLElement {\n #dragging = false\n #elementsWindow = null\n #previewWindow = null\n\n constructor() {\n super()\n\n this.addEventListener(\"mousedown\", this)\n window.addEventListener(\"mousemove\", this)\n window.addEventListener(\"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 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 this.elementsWindow.resize(elementWindowWidth)\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 constructor() {\n super()\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 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\n constructor() {\n super()\n this.#attachEvents()\n }\n\n #attachEvents() {\n if (this.hotkey) {\n key(this.hotkey, () => {\n this.filterField.focus()\n return false\n })\n }\n this.filterField.addEventListener(\"keyup\", () => {\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 this.clearButton.addEventListener(\"click\", (e) => {\n e.preventDefault()\n this.clear()\n })\n this.filterField.addEventListener(\"focus\", () =>\n key.setScope(\"list_filter\")\n )\n key(\"esc\", \"list_filter\", () => {\n this.clear()\n this.filterField.blur()\n })\n }\n\n disconnectedCallback() {\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\ncustomElements.define(\"alchemy-list-filter\", ListFilter)\n","const DISMISS_DELAY = 5000\n\nclass Message extends HTMLElement {\n #message\n\n constructor() {\n super()\n this.#message = this.innerHTML\n if (this.dismissable || this.type === \"error\") {\n this.addEventListener(\"click\", this)\n }\n }\n\n handleEvent(event) {\n if (event.type === \"click\") {\n this.dismiss()\n }\n }\n\n connectedCallback() {\n this.innerHTML = `\n \n ${this.dismissable && this.type === \"error\" ? '' : \"\"}\n ${this.#message}\n `\n if (this.dismissable && this.type !== \"error\") {\n setTimeout(() => {\n this.dismiss()\n }, this.dismissDelay)\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","class Icon extends HTMLElement {\n static get observedAttributes() {\n return [\"name\", \"size\", \"icon-style\"]\n }\n\n constructor() {\n super()\n this.spriteUrl = 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 = ``\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\ncustomElements.define(\"alchemy-icon\", Icon)\n","export class IngredientGroup extends HTMLDetailsElement {\n #localStorageKey = \"Alchemy.expanded_ingredient_groups\"\n\n constructor() {\n super()\n\n this.addEventListener(\"toggle\", this)\n\n if (this.isInLocalStorage) {\n this.open = true\n }\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-left-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 constructor() {\n super()\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 = ''\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 constructor() {\n super()\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 ''\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 constructor() {\n super()\n this.addEventListener(\"alchemy:link\", this)\n this.addEventListener(\"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","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 { RemoteSelect } from \"alchemy_admin/components/remote_select\"\n\nclass NodeSelect extends RemoteSelect {\n _searchQuery(term, page) {\n return {\n filter: {\n name_or_page_name_cont: term,\n ...JSON.parse(this.queryParams)\n },\n page: page\n }\n }\n\n _renderResult(item) {\n return this._renderListEntry(item)\n }\n\n /**\n * html template for each list entry\n * @param {object} node\n * @param {string} term\n * @returns {string}\n * @private\n */\n _renderListEntry(node, term) {\n const ancestors = node.ancestors.map((a) => a.name)\n const seperator = ``\n\n return `\n
    \n \n
    \n \n ${ancestors.length > 0 ? ancestors.join(seperator) + seperator : \"\"}\n \n \n ${this._hightlightTerm(node.name, term)}\n \n
    \n
    \n ${node.url || \"\"}\n
    \n
    \n `\n }\n}\n\ncustomElements.define(\"alchemy-node-select\", NodeSelect)\n","import { AlchemyHTMLElement } from \"alchemy_admin/components/alchemy_html_element\"\nimport { formatFileSize } from \"alchemy_admin/utils/format\"\nimport { translate } from \"alchemy_admin/i18n\"\nimport { growl } from \"alchemy_admin/growler\"\n\nexport class FileUpload extends AlchemyHTMLElement {\n constructor() {\n super()\n\n this.file = null\n this.request = null\n\n this.progressEventLoaded = 0\n this.progressEventTotal = 0\n this.className = \"in-progress\"\n this.valid = true\n this.value = 0\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\n this._validateFile()\n this._addRequestEventListener()\n }\n\n render() {\n return `\n \n
    \n ${this.file?.name}\n ${this.loadedSize}\n ${this.errorMessage}\n
    \n \n \n \n `\n }\n\n afterRender() {\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 * 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 * validate given file with the `Alchemy.uploader_defaults` - configuration\n * @private\n */\n _validateFile() {\n const config = 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 const isFileFormatSupported =\n allowedFiletypes.includes(\"*\") ||\n allowedFiletypes.includes(\n this.file?.type.replace(/^\\w+\\/(\\w+)(\\+\\w+)?/i, \"$1\")\n )\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 * @private\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 { AlchemyHTMLElement } from \"alchemy_admin/components/alchemy_html_element\"\nimport { FileUpload } from \"alchemy_admin/components/uploader/file_upload\"\nimport { formatFileSize } from \"alchemy_admin/utils/format\"\nimport { translate } from \"alchemy_admin/i18n\"\n\nexport class Progress extends AlchemyHTMLElement {\n #visible = false\n\n constructor() {\n super()\n this.buttonLabel = translate(\"Cancel all uploads\")\n this.fileUploads = []\n this.fileCount = 0\n this.className = \"in-progress\"\n this.visible = true\n this.handleFileChange = () => this._updateView()\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 * append file progress - components for each file\n */\n afterRender() {\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\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 * update view and register change event\n */\n connected() {\n this._updateView()\n this.addEventListener(\"Alchemy.FileUpload.Change\", this.handleFileChange)\n }\n\n /**\n * deregister file upload change - event\n */\n disconnected() {\n this.removeEventListener(\"Alchemy.FileUpload.Change\", this.handleFileChange)\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 render() {\n return `\n \n
    \n \n\n \n \n \n
    \n
    3 ? 3 : this.fileCount\n }\">
    \n
    \n `\n }\n\n /**\n * get all active upload components\n * @returns {FileUpload[]}\n * @private\n */\n _activeUploads() {\n return this.fileUploads.filter((upload) => upload.active)\n }\n\n /**\n * replace cancel button to be the close button\n * @private\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 * @private\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 * @private\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 { AlchemyHTMLElement } from \"alchemy_admin/components/alchemy_html_element\"\nimport { Progress } from \"alchemy_admin/components/uploader/progress\"\nimport { FileUpload } from \"alchemy_admin/components/uploader/file_upload\"\nimport { translate } from \"alchemy_admin/i18n\"\nimport { getToken } from \"alchemy_admin/utils/ajax\"\n\nexport class Uploader extends AlchemyHTMLElement {\n static properties = {\n dropzone: { default: false }\n }\n\n connected() {\n this.fileInput.addEventListener(\"change\", (event) => {\n this._uploadFiles(Array.from(event.target.files))\n })\n if (this.dropzone) {\n this._dragAndDropBehavior()\n }\n this.addEventListener(\"Alchemy.upload.successful\", this)\n }\n\n handleEvent(evt) {\n switch (evt.type) {\n case \"Alchemy.upload.successful\":\n this._handleUploadComplete()\n break\n }\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 _dragAndDropBehavior() {\n const dropzoneElement = document.querySelector(this.dropzone)\n let isDraggedOver = false\n\n const toggleDropzoneClass = (enabled) => {\n if (isDraggedOver !== enabled) {\n isDraggedOver = enabled\n dropzoneElement.classList.toggle(\"dragover\")\n }\n }\n\n dropzoneElement.addEventListener(\"dragleave\", () =>\n toggleDropzoneClass(false)\n )\n dropzoneElement.addEventListener(\"drop\", async (event) => {\n event.preventDefault()\n toggleDropzoneClass(false)\n\n const files = [...event.dataTransfer.items].map((item) =>\n item.getAsFile()\n )\n\n this._uploadFiles(files)\n })\n\n dropzoneElement.addEventListener(\"dragover\", (event) => {\n event.preventDefault() // dragover has to be disabled to use the custom drop event\n toggleDropzoneClass(true)\n })\n }\n\n /**\n * @param {File[]} files\n * @private\n */\n _uploadFiles(files) {\n // prepare file progress bars and server request\n let fileUploadCount = 0\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 (Alchemy.uploader_defaults.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 document.body.removeChild(this.uploadProgress)\n }\n this.uploadProgress = new Progress()\n this.uploadProgress.initialize(fileUploads)\n this.uploadProgress.onComplete = (status) => {\n this.dispatchCustomEvent(`upload.${status}`)\n }\n\n document.body.append(this.uploadProgress)\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","import { AlchemyHTMLElement } from \"alchemy_admin/components/alchemy_html_element\"\n\nclass Overlay extends AlchemyHTMLElement {\n render() {\n return `\n \n
    \n ${this.getAttribute(\"text\")}\n
    \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\"\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 Alchemy.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 = ``\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 = ``\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 = ''\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 const public_on_picker = this.querySelector(\n \"alchemy-datepicker:has(#page_public_on)\"\n )\n const public_until_picker = this.querySelector(\n \"alchemy-datepicker:has(#page_public_until)\"\n )\n const publication_date_fields = this.querySelector(\n \".page-publication-date-fields\"\n )\n const public_field = this.querySelector(\"#page_public\")\n\n if (!public_field) return\n\n public_field.addEventListener(\"click\", function (evt) {\n const checkbox = evt.target\n const now = new Date()\n\n if (checkbox.checked) {\n publication_date_fields.classList.remove(\"hidden\")\n public_on_picker.flatpickr.setDate(now)\n } else {\n publication_date_fields.classList.add(\"hidden\")\n public_on_picker.flatpickr.clear()\n }\n public_until_picker.flatpickr?.clear()\n })\n }\n}\n\ncustomElements.define(\"alchemy-page-publication-fields\", PagePublicationFields)\n","import { RemoteSelect } from \"alchemy_admin/components/remote_select\"\n\nclass 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_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 /**\n * result which is visible if a page was selected\n * @param {object} page\n * @returns {string}\n * @private\n */\n _renderResult(page) {\n return page.text || page.name\n }\n\n /**\n * html template for each list entry\n * @param {object} page\n * @param {string} term\n * @returns {string}\n * @private\n */\n _renderListEntry(page, term) {\n return `\n
    \n
    \n \n ${this._hightlightTerm(page.name, term)}\n ${page.site.name}\n
    \n
    \n ${page.url_path}\n ${page.language_code}\n
    \n
    \n `\n }\n}\n\ncustomElements.define(\"alchemy-page-select\", PageSelect)\n","class PictureDescriptionSelect extends HTMLElement {\n constructor() {\n super()\n this.addEventListener(\"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\"\n\nconst UPDATE_DELAY = 125\nconst IMAGE_PLACEHOLDER = ''\nconst THUMBNAIL_SIZE = \"160x120\"\n\nexport class PictureEditor extends HTMLElement {\n constructor() {\n super()\n\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.bind(this))\n }\n\n connectedCallback() {\n this.observer = new MutationObserver(this.mutationCallback.bind(this))\n\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(Alchemy.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.classList.add(\"thumbnail_background\")\n this.spinner = new Spinner(\"small\")\n\n if (this.src) {\n this.start()\n }\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.#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 \n \n \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 constructor() {\n super()\n this.addEventListener(\"submit\", this)\n }\n\n connectedCallback() {\n document.addEventListener(\"alchemy:page-dirty\", this)\n }\n\n disconnectedCallback() {\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","class Select extends HTMLSelectElement {\n #select2Element\n\n connectedCallback() {\n this.classList.add(\"alchemy_selectbox\")\n\n this.#select2Element = $(this).select2({\n minimumResultsForSearch: 5,\n dropdownAutoWidth: true,\n allowClear: !!this.allowClear\n })\n\n // For single selects, remove the close button if allowClear is not set\n // For multiple selects, always keep the close buttons\n if (!this.allowClear && !this.multiple) {\n this.#select2Element\n .prev(\".select2-container\")\n .find(\".select2-search-choice-close\")\n .remove()\n }\n }\n\n enable() {\n this.removeAttribute(\"disabled\")\n this.#updateSelect2()\n }\n\n disable() {\n this.setAttribute(\"disabled\", \"disabled\")\n this.#updateSelect2()\n }\n\n setOptions(data, prompt = undefined) {\n let selectedValue = this.value\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.#updateSelect2()\n }\n\n /**\n * inform Select2 to update\n */\n #updateSelect2() {\n this.#select2Element.trigger(\"change\")\n }\n\n get allowClear() {\n return this.dataset.hasOwnProperty(\"allowClear\") || this.multiple\n }\n}\n\ncustomElements.define(\"alchemy-select\", Select, { extends: \"select\" })\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\"\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 element being dragged\n const pageNode = evt.item\n const pageId = pageNode.pageId\n const url = Alchemy.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/components/element_editor\"\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(Alchemy.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","import { AlchemyHTMLElement } from \"alchemy_admin/components/alchemy_html_element\"\n\nclass Spinner extends AlchemyHTMLElement {\n static properties = {\n size: { default: \"medium\" },\n color: { default: \"currentColor\" }\n }\n\n render() {\n this.className = `spinner spinner--${this.size}`\n\n return `\n \n \n \n \n \n `\n }\n}\n\ncustomElements.define(\"alchemy-spinner\", Spinner)\n","import { setupSelectLocale } from \"alchemy_admin/i18n\"\n\nclass TagsAutocomplete extends HTMLElement {\n async connectedCallback() {\n await setupSelectLocale()\n\n this.classList.add(\"autocomplete_tag_list\")\n $(this.input).select2(this.select2Config)\n }\n\n get input() {\n return this.getElementsByTagName(\"input\")[0]\n }\n\n get select2Config() {\n return {\n tags: true,\n tokenSeparators: [\",\"],\n openOnEnter: false,\n minimumInputLength: 1,\n createSearchChoice: this.#createSearchChoice,\n ajax: {\n url: this.getAttribute(\"url\"),\n dataType: \"json\",\n data: (term) => {\n return { term }\n },\n results: (data) => {\n return { results: data }\n }\n },\n initSelection: this.#initSelection\n }\n }\n\n #createSearchChoice(term, data) {\n if (\n $(data).filter(function () {\n return this.text.localeCompare(term) === 0\n }).length === 0\n ) {\n return {\n id: term,\n text: term\n }\n }\n }\n\n #initSelection(element, callback) {\n const data = []\n $(element.val().split(\",\")).each(function () {\n data.push({\n id: this.trim(),\n text: this\n })\n })\n callback(data)\n }\n}\n\ncustomElements.define(\"alchemy-tags-autocomplete\", TagsAutocomplete)\n","import \"tinymce\"\nimport { AlchemyHTMLElement } from \"alchemy_admin/components/alchemy_html_element\"\nimport { currentLocale } from \"alchemy_admin/i18n\"\n\nconst DARK_THEME = \"alchemy-dark\"\nconst LIGHT_THEME = \"alchemy\"\n\nclass Tinymce extends AlchemyHTMLElement {\n #min_height = null\n\n /**\n * the observer will initialize Tinymce if the textarea becomes visible\n */\n connected() {\n this.className = \"tinymce_container\"\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 disconnected() {\n if (this.tinymceIntersectionObserver !== null) {\n this.tinymceIntersectionObserver.disconnect()\n }\n\n // Remove theme change listener\n this._removeThemeChangeListener()\n\n tinymce.get(this.editorId)?.remove(this.editorId)\n }\n\n render() {\n return `\n ${this.initialContent}\n \n `\n }\n\n /**\n * hide the textarea until TinyMCE is ready to show the editor\n */\n afterRender() {\n this.style.minHeight = `${this.minHeight}px`\n this.editor.style.display = \"none\"\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 ...Alchemy.TinymceDefaults,\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","(()=>{var n=Handlebars.template;(Handlebars.templates=Handlebars.templates||{})[\"node_folder.hbs\"]=n({0:function(n,e,l,a,r){return\"right\"},1:function(n,e,l,a,r){return\"down\"},compiler:[8,\">= 4.3.0\"],main:function(n,e,l,a,r){var o,t=n.lambda,d=n.escapeExpression,u=n.lookupProperty||function(n,e){if(Object.prototype.hasOwnProperty.call(n,e))return n[e]};return'\\n \\n\\n'},useData:!0})})();","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","export 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 Cropper from \"cropperjs\"\n\nexport default class ImageCropper {\n #initialized = false\n #cropper = null\n #cropFromField = null\n #cropSizeField = null\n\n constructor(image, settings) {\n this.image = image\n this.defaultBox = settings.default_box\n this.aspectRatio = settings.ratio\n this.#cropFromField = document.getElementById(\n settings.crop_from_form_field_id\n )\n this.#cropSizeField = document.getElementById(\n settings.crop_size_form_field_id\n )\n this.elementId = settings.element_id\n this.dialog = Alchemy.currentDialog()\n if (this.dialog) {\n this.dialog.options.closed = () => this.destroy()\n this.bind()\n }\n this.init()\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 cropend: () => {\n const data = this.#cropper.getData(true)\n this.update(data)\n }\n }\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.defaultBoxSize\n }\n }\n\n get defaultBoxSize() {\n return {\n x: this.defaultBox[0],\n y: this.defaultBox[1],\n width: this.defaultBox[2],\n height: this.defaultBox[3]\n }\n }\n\n init() {\n if (!this.#initialized) {\n this.#cropper = new Cropper(this.image, this.cropperOptions)\n this.#initialized = true\n }\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 this.#cropper.setData(this.defaultBoxSize)\n this.update(this.defaultBoxSize)\n }\n\n destroy() {\n if (this.#cropper) {\n this.#cropper.destroy()\n }\n this.#initialized = false\n return true\n }\n\n bind() {\n this.dialog.dialog_body.find('button[type=\"submit\"]').on(\"click\", () => {\n const elementEditor = document.querySelector(\n `[data-element-id='${this.elementId}']`\n )\n elementEditor.setDirty()\n this.dialog.close()\n return false\n })\n this.dialog.dialog_body.find('button[type=\"reset\"]').on(\"click\", () => {\n this.reset()\n return false\n })\n }\n}\n","import { Dialog } from \"alchemy_admin/dialog\"\n\nexport default class ImageOverlay extends Dialog {\n constructor(url, options = {}) {\n super(url, options)\n }\n\n init() {\n $(\".zoomed-picture-background\").on(\"click\", (e) => {\n e.stopPropagation()\n if (e.target.nodeName === \"IMG\") {\n return\n }\n this.close()\n return false\n })\n $(\".picture-overlay-handle\").on(\"click\", (e) => {\n this.dialog.toggleClass(\"hide-form\")\n return false\n })\n this.$previous = $(\".previous-picture\")\n this.$next = $(\".next-picture\")\n this.#initKeyboardNavigation()\n super.init()\n }\n\n previous() {\n if (this.$previous[0] != null) {\n this.$previous[0].click()\n }\n }\n\n next() {\n if (this.$next[0] != null) {\n this.$next[0].click()\n }\n }\n\n build() {\n this.dialog_container = $('
    ')\n this.dialog = $('
    ')\n this.dialog_body = $('
    ')\n this.close_button = $(`\n \n `)\n this.dialog.append(this.close_button)\n this.dialog.append(this.dialog_body)\n this.dialog_container.append(this.dialog)\n this.overlay = $('
    ')\n this.$body.append(this.overlay)\n this.$body.append(this.dialog_container)\n }\n\n #initKeyboardNavigation() {\n this.$document.keydown((e) => {\n if (e.target.nodeName === \"INPUT\" || e.target.nodeName === \"TEXTAREA\") {\n return true\n }\n switch (e.which) {\n case 37:\n this.previous()\n return false\n case 39:\n this.next()\n return false\n default:\n return true\n }\n })\n }\n}\n","import { on } from \"alchemy_admin/utils/events\"\nimport { openDialog } from \"alchemy_admin/dialog\"\n\nfunction toggleCheckboxes(state) {\n document\n .querySelectorAll(\".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\nfunction checkedInputs() {\n return document.querySelectorAll(\"#picture_archive input:checked\")\n}\n\nfunction editMultiplePicturesUrl(href) {\n const url = new URL(href)\n\n checkedInputs().forEach((entry) =>\n url.searchParams.append(entry.name, entry.value)\n )\n\n return url.toString()\n}\n\n/**\n * Multiple picture select handler for the picture archive.\n */\nexport default function PictureSelector() {\n const selectAllButton = document.querySelector(\"#select_all_pictures\")\n const selectedItemTools = document.querySelector(\".selected_item_tools\")\n\n on(\"click\", \".toolbar_buttons\", \"a#select_all_pictures\", (event) => {\n event.preventDefault()\n\n selectAllButton.classList.toggle(\"active\")\n\n const state = selectAllButton.classList.contains(\"active\")\n\n toggleCheckboxes(state)\n\n 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 on(\"change\", \".picture_tool.select\", \"input\", (event) => {\n selectedItemTools.classList.toggle(\"hidden\", checkedInputs().length === 0)\n\n const parentElementClassList = event.target.parentElement.classList\n const checked = event.target.checked\n\n parentElementClassList.toggle(\"visible\", checked)\n })\n\n // open the edit view in a dialog modal\n on(\"click\", \".selected_item_tools\", \"a#edit_multiple_pictures\", (event) => {\n event.preventDefault()\n\n const url = editMultiplePicturesUrl(event.target.href)\n\n openDialog(url, {\n title: event.target.title,\n size: \"400x295\"\n })\n })\n}\n","import Sortable from \"sortablejs\"\nimport { patch } from \"alchemy_admin/utils/ajax\"\nimport { on } from \"alchemy_admin/utils/events\"\nimport { growl } from \"alchemy_admin/growler\"\n\nfunction displayNodeFolders() {\n document.querySelectorAll(\"li.menu-item\").forEach((el) => {\n const leftIconArea = el.querySelector(\".nodes_tree-left_images\")\n const list = el.querySelector(\".children\")\n const node = {\n folded: el.dataset.folded === \"true\",\n id: el.dataset.id,\n type: el.dataset.type\n }\n\n if (list.children.length > 0 || node.folded) {\n leftIconArea.innerHTML = Handlebars.templates[\"node_folder.hbs\"]({\n node: node\n })\n } else {\n leftIconArea.innerHTML = \" \"\n }\n })\n}\n\nfunction onFinishDragging(evt) {\n const url = Alchemy.routes[evt.item.dataset.type].move_api_path(\n evt.item.dataset.id\n )\n const data = {\n target_parent_id: evt.to.dataset.recordId,\n new_position: evt.newIndex\n }\n\n patch(url, data)\n .then(() => {\n const message = Alchemy.t(\"Successfully moved menu item\")\n growl(message)\n displayNodeFolders()\n })\n .catch((error) => {\n growl(error.message || error, \"error\")\n })\n}\n\nfunction handleNodeFolders() {\n on(\"click\", \".nodes_tree\", \".node_folder\", function () {\n const nodeId = this.dataset.recordId\n const menu_item = this.closest(\"li.menu-item\")\n const url =\n Alchemy.routes[this.dataset.recordType].toggle_folded_api_path(nodeId)\n const list = menu_item.querySelector(\".children\")\n\n patch(url)\n .then(() => {\n list.classList.toggle(\"folded\")\n menu_item.dataset.folded =\n menu_item.dataset.folded == \"true\" ? \"false\" : \"true\"\n displayNodeFolders()\n })\n .catch((error) => {\n growl(error.message || error)\n })\n })\n}\n\nexport default function NodeTree() {\n handleNodeFolders()\n displayNodeFolders()\n\n document.querySelectorAll(\".nodes_tree ul.children\").forEach((el) => {\n new Sortable(el, {\n group: \"nodes\",\n animation: 150,\n fallbackOnBody: true,\n swapThreshold: 0.65,\n handle: \".node_name\",\n invertSwap: true,\n onEnd: onFinishDragging\n })\n })\n}\n","// We still use jQuery in some places (ie. select2)\nimport \"handlebars\"\nimport \"jquery\"\nimport \"@ungap/custom-elements\"\nimport { Turbo } from \"@hotwired/turbo-rails\"\nimport \"select2\"\n\nimport Rails from \"@rails/ujs\"\n\nimport { translate } from \"alchemy_admin/i18n\"\nimport { currentDialog, closeCurrentDialog } from \"alchemy_admin/dialog\"\nimport Dirty from \"alchemy_admin/dirty\"\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\n// Web Components\nimport \"alchemy_admin/components\"\n\n// Handlebars Templates\nimport \"alchemy_admin/templates/compiled\"\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 ...Dirty,\n t: translate, // Global utility method for translating a given string\n FixedElements,\n growl,\n LinkDialog,\n pleaseWaitOverlay,\n Spinner,\n reloadPreview\n})\n\nRails.start()\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 { on } from \"alchemy_admin/utils/events\"\n\n// Page-specific modules - bundled to avoid dual-loading\nexport { default as ImageCropper } from \"alchemy_admin/image_cropper\"\nexport { default as ImageOverlay } from \"alchemy_admin/image_overlay\"\nexport { default as pictureSelector } from \"alchemy_admin/picture_selector\"\nexport { default as NodeTree } from \"alchemy_admin/node_tree\"\n","import Hotkeys from \"alchemy_admin/hotkeys\"\nimport pleaseWaitOverlay from \"alchemy_admin/please_wait_overlay\"\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 // 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 '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"],"names":["KEY_SEPARATOR","getTranslation","key","locale","currentLocale","translations","Alchemy","test","keys","split","group","nestedTranslation","console","warn","document","documentElement","lang","translate","replacement","undefined","translation","replace","async","setupSelectLocale","import","$","extend","fn","select2","defaults","locales","bindedHotkeys","showHelp","evt","target","is","String","fromCharCode","which","openDialog","title","t","size","Hotkeys","scope","jQuery","removeEventListener","addEventListener","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","color","this","spinner","spin","parent","body","append","stop","remove","currentDialogs","DEFAULTS","header_height","padding","modal","overflow","ready","closed","Dialog","url","options","$document","$window","window","$body","width","parseInt","height","build","resize","open","dialog","trigger","bind_close_events","requestAnimationFrame","dialog_container","addClass","overlay","load","close","off","removeClass","on","pop","show_spinner","get","data","fail","xhr","show_error","reload","dialog_body","empty","remove_spinner","hide","html","init","dispatchEvent","CustomEvent","bubbles","detail","show","Spinner","watch_remote_forms","$form","event","getResponseHeader","match","responseText","statusText","status","error_body","error_header","error_type","error_messages","$errorDiv","error","close_button","e","keydown","dialog_header","dialog_title","getSize","css","doc_width","doc_height","currentDialog","length","closeCurrentDialog","callback","ConfirmDialog","message","ok_label","cancel_label","on_ok","bindEvents","cancelButton","preventDefault","on_cancel","okButton","source","querySelector","openConfirmDialog","Promise","resolve","pleaseWaitOverlay","checkPageDirtyness","action","find","appendTo","Turbo","visit","pathname","then","proceed","onbeforeunload","Dirty","PageLeaveObserver","currentTarget","removeTab","element_id","fixed_elements","getElementById","panel_name","label","tab","panel","growl","style","flashType","flashNotices","flashMessage","ANCHOR_REGEX","PreviewWindow","HTMLIFrameElement","afterLoad","reloadIcon","loadTimeout","previewReadyHandler","super","handlePreviewReadyMessage","bind","handleEvent","type","clearLoadTimeout","stopSpinner","call","connectedCallback","attachEvents","localStorage","getItem","previewUrlSelect","value","refresh","disconnectedCallback","postMessage","contentWindow","startSpinner","src","setTimeout","isDragged","dragged","transitionProperty","pointerEvents","reloadButton","sizeSelect","setItem","includes","clearTimeout","getAttribute","reloadPreview","customElements","define","extends","IngredientAnchorLink","updateIcon","ingredientId","active","ingredientEditor","setAttribute","Action","HTMLElement","actions","removeFixedElement","updateAnchorIcon","hidePleaseWaitOverlay","func","name","params","hasAttribute","JSON","parse","AlchemyHTMLElement","static","observedAttributes","Object","properties","changeComponent","initialContent","default","getAttributeNames","_updateFromAttribute","_updateComponent","connected","disconnected","attributeChangedCallback","render","afterRender","dispatchCustomEvent","attributeValue","propertyName","reduce","a","b","charAt","toUpperCase","slice","hightlightTerm","term","RegExp","RemoteSelect","allowClear","selection","placeholder","queryParams","input","classList","add","select2Config","onOpen","onChange","removed","added","getElementsByTagName","initSelection","_$el","ajax","ajaxConfig","formatSelection","item","_renderResult","formatResult","_el","query","_renderListEntry","datatype","quietMillis","page","_searchQuery","results","response","_parseResponse","q","name_cont","meta","more","per_page","total_count","Error","_hightlightTerm","attachment","icon_css_class","AutoSubmit","submitEvent","Event","cancelable","form","Button","HTMLButtonElement","remote","stopPropagation","disable","enable","rect","getBoundingClientRect","initialButtonText","removeAttribute","maxChars","formField","getFormField","createDisplayElement","countCharacters","formFields","display","className","after","charLength","textContent","toggle","ClipboardButton","clipboard","ClipboardJS","destroy","formatItem","object","optionEl","swatch","ColorSelect","select","initializeSelect2","toggleColorPicker","val","colorInput","textInput","minimumResultsForSearch","enabled","disabled","inputType","flatpickr","inputField","flatpickrOptions","enableTime","altInput","altFormat","altInputClass","noCalendar","time_24hr","onValueUpdate","_selectedDates","_dateStr","instance","closest","setDirty","dateFormat","DialogLink","HTMLAnchorElement","dialogOptions","contains","JSON_CONTENT_TYPE","TURBO_STREAM_CONTENT_TYPE","isGetRequest","method","toLowerCase","prepareOptions","accept","headers","Accept","getToken","prepareHeaders","stringify","attributes","patch","post","path","fetch","URL","location","origin","search","URLSearchParams","toString","prepareURL","contentType","isJson","isTurboStream","responseData","json","renderStreamMessage","ok","DomIdSelect","dataItem","hash","id","selectElement","pageId","fetchDomIds","routes","api_ingredients_path","page_id","ingredients","filter","ingredient","dom_id","map","prompt","setOptions","reset","frame","elements","contentDocument","Array","from","PublishElementButton","scheduleButtonVariant","scheduleButton","publishButton","dropdown","loading","DeleteElementButton","button","removeElement","elementEditor","fixed","elementId","pageHasUnpublishedChanges","dispatchPageDirtyEvent","tooltip","publishButtonTooltip","ElementEditor","header","toggleButton","focusElement","previewWindow","focusElementPreview","onClickElement","onSaveElement","hasEditors","firstChild","setTitle","selectTabForElement","expand","setClean","warning","ingredientsWithErrors","errorDisplay","errorMessage","appendChild","elementErrors","notice","updateTitle","previewText","ingredientAnchors","anchor","scrollToElement","scrollIntoView","behavior","scroll","reject","tabs","dirty","editor","collapsed","collapse","compact","toggleIcon","collapse_admin_element_path","nestedElementIds","selector","join","nestedElement","catch","finally","expanded","parentElementEditor","expand_admin_element_path","parentElementIds","parentElement","published","isPublished","bodySelector","footer","elementName","hasChildren","option","icon","ElementSelect","dropdownAutoWidth","hint","description","ElementsWindow","visible","turboFrame","focusElementEditor","collapseAllElements","removeProperty","widthFromCookie","setProperty","cookie","collapseButton","row","startsWith","ElementsWindowHandle","dragging","elementsWindow","onMouseDown","onMouseUp","onDrag","pageX","elementWindowWidth","innerWidth","FileEditor","deleteLink","fileIcon","fileName","formFieldId","removeFile","ListFilter","debounceTimer","filterField","clearButton","visibility","debounceTime","clear","matchedItems","itemsToShow","Set","lowerTerm","items","nameAttribute","indexOf","ancestor","itemsSelector","has","block","Message","dismissable","dismiss","iconName","dismissDelay","noticesWrapper","autoDismissDelay","Growl","Icon","spriteUrl","sizeClass","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","formatFileSize","bytes","exponent","Math","floor","log","pow","toFixed","name_or_page_name_cont","node","ancestors","seperator","FileUpload","file","request","progressEventLoaded","progressEventTotal","valid","initialize","_validateFile","_addRequestEventListener","loadedSize","cancel","reader","FileReader","readAsDataURL","image","Image","result","prepend","finished","abort","config","uploader_defaults","maxFileSize","file_size_limit","allowedFiletypes","allowed_filetypes","alchemy_pictures","alchemy_attachments","upload","onprogress","progressEvent","onload","responseMessage","onerror","_errorMessage","errorMessageContainer","progressElement","loaded","total","round","_status","toggleAttribute","_valid","isValid","_value","Progress","buttonLabel","fileUploads","fileCount","handleFileChange","_updateView","actionButton","onComplete","fileUpload","_activeUploads","_setupCloseButton","ariaLabel","_sumFileProgresses","field","accumulator","totalProgress","overallProgressValue","overallUploadSize","every","entry","fileProgress","uploadedFileCount","uploadsStatuses","totalSize","ceil","weight","dropzone","fileInput","_uploadFiles","files","_dragAndDropBehavior","_handleUploadComplete","redirectUrl","uploadProgress","dropzoneElement","isDraggedOver","toggleDropzoneClass","dataTransfer","getAsFile","fileUploadCount","XMLHttpRequest","upload_limit","_submitFile","_createProgress","formData","FormData","set","setRequestHeader","send","removeChild","BUTTON","AlchemyPageNode","folded","folderButton","handleFolderClick","fold_admin_page_path","toggleChildren","updateFolderIcon","childrenContainer","updateFolderButton","folderElement","shouldShowButton","tagName","outerHTML","PagePublicationFields","public_on_picker","public_until_picker","publication_date_fields","public_field","checkbox","now","Date","checked","setDate","pages","site","url_path","language_code","PictureDescriptionSelect","searchParams","PictureEditor","cropFromField","cropSizeField","pictureIdField","targetSizeField","imageCropperField","pictureThumbnail","deleteButton","cropLink","targetSize","pictureId","update","delay","timeout","args","that","apply","debounce","updateImage","updateCropLink","removeImage","observer","MutationObserver","mutationCallback","observe","disconnect","mutationsList","mutation","url_admin_picture_path","crop","imageCropperEnabled","crop_from","cropFrom","crop_size","cropSize","flatten","alt","href","defaultCropFrom","defaultCropSize","mask","n","zoom","imageFileWidth","imageFileHeight","dimensions","imageCropper","PictureThumbnail","start","onLoaded","onError","setImage","createImage","complete","hoist","replaceChildren","PublishPageButton","markDirty","variant","Select","HTMLSelectElement","select2Element","multiple","prev","updateSelect2","selectedValue","Option","hasOwnProperty","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","Sortable","animation","fallbackOnBody","swapThreshold","handle","draggable","onEnd","handleSort","to","oldIndex","newIndex","pageNode","move_admin_page_path","target_parent_id","parentId","new_position","pageData","pageEl","urlPathEl","updateFolderIcons","fromContainer","toContainer","fromParent","toParent","SORTABLE_OPTIONS","ghostClass","easing","onStart","onSort","position","parent_element_id","order_admin_elements_path","preview_text","SortableElements","put","_from","droppableElements","TagsAutocomplete","tags","tokenSeparators","openOnEnter","minimumInputLength","createSearchChoice","dataType","localeCompare","each","DARK_THEME","LIGHT_THEME","min_height","root","rootMargin","threshold","tinymceIntersectionObserver","IntersectionObserver","entries","intersectionRatio","_initTinymceEditor","unobserve","_setupThemeChangeListener","_removeThemeChangeListener","tinymce","editorId","minHeight","configuration","editors","_setupEditor","editorContainer","darkModeMediaQuery","matchMedia","themeChangeHandler","_handleThemeChange","skin","matches","content_css","customConfig","attributeName","replaceAll","preferredTheme","TinymceDefaults","language","UpdateCheck","credentials","responseJSON","showStatus","showError","Handlebars","template","templates","l","r","compiler","main","o","lambda","d","escapeExpression","u","lookupProperty","prototype","nullContext","program","inverse","loc","line","column","end","useData","setDefaultAnimation","keyframes","transform","opacity","duration","iconMap","caret","resolver","mutator","svg","spriteSheet","eventName","baseSelector","targetSelector","baseNode","targets","currentNode","registerIconLibrary","ImageCropper","initialized","cropper","settings","defaultBox","default_box","aspectRatio","ratio","crop_from_form_field_id","crop_size_form_field_id","cropperOptions","viewMode","zoomable","checkCrossOrigin","checkOrientation","box","cropend","getData","v","x","y","defaultBoxSize","Cropper","coords","setData","ImageOverlay","nodeName","toggleClass","$previous","$next","initKeyboardNavigation","previous","next","checkedInputs","PictureSelector","selectAllButton","selectedItemTools","state","toggleCheckboxes","parentElementClassList","editMultiplePicturesUrl","displayNodeFolders","leftIconArea","list","onFinishDragging","move_api_path","recordId","NodeTree","nodeId","menu_item","recordType","toggle_folded_api_path","invertSwap","assign","FixedElements","onCreateLink","link_admin_pages_path","parameterMapping","selected_tab","link_title","link_target","internalForm","attachmentSelect","updatePage","submitForm","linkFormType","internalLink","domIdSelect","linkType","elementAnchor","link_url_regexp","showValidationError","errors","Rails","forms","confirm","srcElement","isPressed"],"mappings":"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"} \ No newline at end of file +{"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/dialog.js","../../../javascript/alchemy_admin/confirm_dialog.js","../../../javascript/alchemy_admin/please_wait_overlay.js","../../../javascript/alchemy_admin/dirty.js","../../../javascript/alchemy_admin/fixed_elements.js","../../../javascript/alchemy_admin/growler.js","../../../javascript/alchemy_admin/link_dialog.js","../../../javascript/alchemy_admin/components/preview_window.js","../../../javascript/alchemy_admin/ingredient_anchor_link.js","../../../javascript/alchemy_admin/components/action.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/clipboard_button.js","../../../javascript/alchemy_admin/components/color_select.js","../../../javascript/alchemy_admin/components/datepicker.js","../../../javascript/alchemy_admin/components/dialog_link.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/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/message.js","../../../javascript/alchemy_admin/components/growl.js","../../../javascript/alchemy_admin/components/icon.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/utils/format.js","../../../javascript/alchemy_admin/components/node_select.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_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/select.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/templates/compiled.js","../../../javascript/alchemy_admin/shoelace_theme.js","../../../javascript/alchemy_admin/utils/events.js","../../../javascript/alchemy_admin/image_cropper.js","../../../javascript/alchemy_admin/image_overlay.js","../../../javascript/alchemy_admin/picture_selector.js","../../../javascript/alchemy_admin/node_tree.js","../../../javascript/alchemy_admin.js","../../../javascript/alchemy_admin/initializer.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 const translations = 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\nexport async function setupSelectLocale() {\n const locale = currentLocale()\n if (locale === \"en\") return\n\n await import(`select2/${locale}.js`)\n $.extend($.fn.select2.defaults, $.fn.select2.locales[locale])\n}\n","import \"keymaster\"\nimport { openDialog } from \"alchemy_admin/dialog\"\n\nconst bindedHotkeys = []\n\nfunction showHelp(evt) {\n if (\n !$(evt.target).is(\"input, textarea\") &&\n String.fromCharCode(evt.which) === \"?\"\n ) {\n openDialog(\"/admin/help\", {\n title: Alchemy.t(\"help\"),\n size: \"400x492\"\n })\n return false\n } else {\n return true\n }\n}\n\nexport default function (scope = document) {\n // The scope can be a jQuery object because we still use jQuery in alchemy_admin/dialog.js.\n if (scope instanceof jQuery) {\n scope = scope[0]\n }\n\n // Unbind all previously registered hotkeys if we are not inside a dialog.\n if (scope === document) {\n document.removeEventListener(\"keypress\", showHelp)\n document.addEventListener(\"keypress\", showHelp)\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 ``\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","import Hotkeys from \"alchemy_admin/hotkeys\"\nimport Spinner from \"alchemy_admin/spinner\"\n\n// Collection of all current dialog instances\nconst currentDialogs = []\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 // 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 this.$document = $(document)\n this.$window = $(window)\n this.$body = $(\"body\")\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 this.dialog.trigger(\"Alchemy.DialogOpen\")\n this.bind_close_events()\n window.requestAnimationFrame(() => {\n this.dialog_container.addClass(\"open\")\n if (this.overlay != null) {\n return this.overlay.addClass(\"open\")\n }\n })\n this.$body.addClass(\"prevent-scrolling\")\n currentDialogs.push(this)\n this.load()\n }\n\n // Closes the Dialog and removes it from the DOM\n close() {\n this.dialog.trigger(\"DialogClose.Alchemy\")\n this.$document.off(\"keydown\")\n this.dialog_container.removeClass(\"open\")\n if (this.overlay != null) {\n this.overlay.removeClass(\"open\")\n }\n this.$document.on(\n \"webkitTransitionEnd transitionend oTransitionEnd\",\n () => {\n this.$document.off(\"webkitTransitionEnd transitionend oTransitionEnd\")\n this.dialog_container.remove()\n if (this.overlay != null) {\n this.overlay.remove()\n }\n this.$body.removeClass(\"prevent-scrolling\")\n currentDialogs.pop(this)\n if (this.options.closed != null) {\n return this.options.closed()\n }\n }\n )\n return true\n }\n\n // Loads the content via ajax and replaces the Dialog body with server response.\n load() {\n this.show_spinner()\n $.get(this.url, (data) => {\n this.replace(data)\n }).fail((xhr) => {\n this.show_error(xhr)\n })\n }\n\n // Reloads the Dialog content\n reload() {\n this.dialog_body.empty()\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.hide()\n this.dialog_body.html(data)\n this.init()\n this.dialog[0].dispatchEvent(\n new CustomEvent(\"DialogReady.Alchemy\", {\n bubbles: true,\n detail: {\n body: this.dialog_body[0]\n }\n })\n )\n if (this.options.ready != null) {\n this.options.ready(this.dialog_body)\n }\n this.dialog_body.show()\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[0])\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 this.watch_remote_forms()\n }\n\n // Watches ajax requests inside of dialog body and replaces the content accordingly\n watch_remote_forms() {\n const $form = $('[data-remote=\"true\"]', this.dialog_body)\n\n $form.on(\"ajax:success\", (event) => {\n const xhr = event.detail[2]\n const content_type = xhr.getResponseHeader(\"Content-Type\")\n if (content_type.match(/javascript/)) {\n return\n } else {\n this.dialog_body.html(xhr.responseText)\n this.init()\n }\n })\n\n $form.on(\"ajax:error\", (event) => {\n const statusText = event.detail[1]\n const xhr = event.detail[2]\n this.show_error(xhr, statusText)\n })\n }\n\n // Displays an error message\n show_error(xhr, statusText) {\n if (xhr.status === 422) {\n this.dialog_body.html(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 const $errorDiv = $(`\n

    ${error_header}

    \n

    ${error_body}

    \n
    `)\n\n this.dialog_body.html($errorDiv)\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\n bind_close_events() {\n this.close_button.on(\"click\", () => {\n this.close()\n })\n this.dialog_container.addClass(\"closable\").on(\"click\", (e) => {\n if (e.target !== this.dialog_container.get(0)) {\n return true\n }\n this.close()\n return false\n })\n this.$document.keydown((e) => {\n if (e.which === 27) {\n this.close()\n return false\n } else {\n return true\n }\n })\n }\n\n // Builds the html structure of the Dialog\n build() {\n this.dialog_container = $('
    ')\n this.dialog = $('
    ')\n this.dialog_body = $('
    ')\n this.dialog_header = $('
    ')\n this.dialog_title = $('
    ')\n this.close_button = $(\n ''\n )\n this.dialog_title.text(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.addClass(\"modal\")\n }\n if (this.options.padding) {\n this.dialog_body.addClass(\"padded\")\n }\n if (this.options.modal) {\n this.overlay = $('
    ')\n this.$body.append(this.overlay)\n }\n this.$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.css({\n width: width,\n \"min-height\": height,\n overflow: this.options.overflow\n })\n\n if (this.options.overflow === \"hidden\") {\n this.dialog_body.css({\n height: height,\n overflow: \"auto\"\n })\n } else {\n this.dialog_body.css({\n \"min-height\": height,\n overflow: \"visible\"\n })\n }\n }\n\n getSize() {\n const padding = this.options.padding ? 16 : 0\n const doc_width = this.$window.width()\n const doc_height = this.$window.height()\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","import { createHtmlElement } from \"alchemy_admin/utils/dom_helpers\"\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 \n ${this.message}\n \n \n \n `)\n document.body.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} 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","/**\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 { openConfirmDialog } from \"alchemy_admin/confirm_dialog\"\nimport { translate } from \"alchemy_admin/i18n\"\nimport pleaseWaitOverlay from \"alchemy_admin/please_wait_overlay\"\n\nfunction checkPageDirtyness(element) {\n let callback = () => {}\n\n if ($(element).is(\"form\")) {\n callback = function () {\n const $form = $(\n ``\n )\n $form.append($(element).find(\"input\"))\n $form.appendTo(\"body\")\n\n pleaseWaitOverlay()\n $form.trigger(\"submit\")\n }\n } else if ($(element).is(\"a\")) {\n callback = () => Turbo.visit(element.pathname)\n }\n\n const isPageDirty =\n document.querySelectorAll(\"alchemy-element-editor.dirty\").length > 0\n\n if (isPageDirty) {\n openConfirmDialog(translate(\"page_dirty_notice\"), {\n title: translate(\"warning\"),\n ok_label: translate(\"ok\"),\n cancel_label: translate(\"cancel\")\n }).then((proceed) => {\n if (proceed) {\n window.onbeforeunload = void 0\n callback()\n }\n })\n return false\n }\n return true\n}\n\nfunction PageLeaveObserver() {\n document.querySelectorAll(\"#main_navi a\").forEach((element) => {\n element.addEventListener(\"click\", (event) => {\n if (!checkPageDirtyness(event.currentTarget)) {\n event.preventDefault()\n }\n })\n })\n}\n\nexport default {\n checkPageDirtyness,\n PageLeaveObserver\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 = `${label}`\n const panel = ``\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 \n ${message}\n \n `)\n flashNotices.append(flashMessage)\n}\n\nexport function growl(message, style = \"notice\") {\n build(message, style)\n}\n","import { translate } from \"alchemy_admin/i18n\"\nimport { Dialog } from \"alchemy_admin/dialog\"\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// 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(Alchemy.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}\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\" && !url.match(Alchemy.link_url_regexp)) {\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 `
  • ${Alchemy.t(\"url_validation_failed\")}
  • `\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 #previewReadyHandler\n\n constructor() {\n super()\n this.addEventListener(\"load\", this)\n this.#previewReadyHandler = this.#handlePreviewReadyMessage.bind(this)\n }\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 #handlePreviewReadyMessage(event) {\n if (event.data.message === \"Alchemy.previewReady\") {\n this.#clearLoadTimeout()\n this.#stopSpinner()\n this.#afterLoad?.call(this, event)\n }\n }\n\n connectedCallback() {\n let url = this.url\n\n this.#attachEvents()\n window.addEventListener(\"message\", this.#previewReadyHandler)\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 key.unbind(\"alt+r\")\n window.removeEventListener(\"message\", this.#previewReadyHandler)\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 5s 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 }, 5000)\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 = ``\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 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","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 { reloadPreview } from \"alchemy_admin/components/preview_window\"\nimport { removeTab } from \"alchemy_admin/fixed_elements\"\nimport { closeCurrentDialog } from \"alchemy_admin/dialog\"\nimport IngredientAnchorLink from \"alchemy_admin/ingredient_anchor_link\"\nimport pleaseWaitOverlay from \"alchemy_admin/please_wait_overlay\"\n\nclass Action extends HTMLElement {\n constructor() {\n super()\n\n // map action names with Javascript functions\n this.actions = {\n // add a intermediate closeCurrentDialog - action\n // this will be gone, if all dialogs are working with a promise and\n // we don't have to implicitly close the dialog\n closeCurrentDialog,\n reloadPreview,\n removeFixedElement: removeTab,\n updateAnchorIcon: IngredientAnchorLink.updateIcon,\n hidePleaseWaitOverlay() {\n pleaseWaitOverlay(false)\n }\n }\n }\n\n connectedCallback() {\n const func = this.actions[this.name]\n\n if (func) {\n func(...this.params)\n } else {\n console.error(`Unknown Alchemy action: ${this.name}`)\n }\n\n this.remove()\n }\n\n get name() {\n return this.getAttribute(\"name\")\n }\n\n get params() {\n if (this.hasAttribute(\"params\")) {\n return JSON.parse(this.getAttribute(\"params\"))\n }\n return []\n }\n}\n\ncustomElements.define(\"alchemy-action\", Action)\n","import { setupSelectLocale } from \"alchemy_admin/i18n\"\n\nexport function hightlightTerm(name, term) {\n return name.replace(new RegExp(term, \"gi\"), (match) => `${match}`)\n}\n\nexport class RemoteSelect extends HTMLElement {\n #select2 = null\n\n async connectedCallback() {\n await setupSelectLocale()\n // Bail out if the element was disconnected while the locale was loading.\n // Otherwise Select2 would leak onto a detached input.\n if (!this.isConnected) return\n\n this.input.classList.add(\"alchemy_selectbox\")\n\n this.#select2 = $(this.input)\n .select2(this.select2Config)\n .on(\"select2-open\", this.#onOpen)\n .on(\"change\", this.#onChange)\n }\n\n disconnectedCallback() {\n if (this.#select2) {\n this.#select2.off(\"select2-open\", this.#onOpen)\n this.#select2.off(\"change\", this.#onChange)\n this.#select2.select2(\"destroy\")\n this.#select2 = null\n }\n }\n\n #onOpen = (evt) => this.onOpen(evt)\n #onChange = (evt) => this.onChange(evt)\n\n /**\n * Optional on change handler called by Select2.\n * @param {Event} event\n */\n onChange(event) {\n this.dispatchCustomEvent(\"RemoteSelect.Change\", {\n removed: event.removed,\n added: event.added\n })\n }\n\n /**\n * Optional on open handler called by Select2.\n * @param {Event} event\n */\n onOpen(event) {\n // add focus to the search input. Select2 is handling the focus on the first opening,\n // but it does not work the second time. One process in select2 is \"stealing\" the focus\n // if the command is not delayed. It is an intermediate solution until we are going to\n // move away from Select2\n setTimeout(() => {\n document.querySelector(\"#select2-drop .select2-input\").focus()\n }, 100)\n }\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 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 return this.getElementsByTagName(\"input\")[0]\n }\n\n get select2Config() {\n return {\n placeholder: this.placeholder,\n allowClear: this.allowClear,\n initSelection: (_$el, callback) => {\n if (this.selection) {\n callback(JSON.parse(this.selection))\n }\n },\n ajax: this.ajaxConfig,\n formatSelection: (item) => this._renderResult(item),\n formatResult: (item, _el, query) =>\n this._renderListEntry(item, query.term)\n }\n }\n\n /**\n * Ajax configuration for Select2\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 * Search query send to server from select2\n * @param {string} term\n * @param {number} page\n * @returns {object}\n * @private\n */\n _searchQuery(term, page) {\n return {\n q: {\n name_cont: term,\n ...JSON.parse(this.queryParams)\n },\n page: page\n }\n }\n\n /**\n * Parses server response into select2 results object\n * @param {object} response\n * @returns {object}\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 * result which is visible if a page was selected\n * @param {object} item\n * @returns {string}\n * @private\n */\n _renderResult() {\n throw new Error(\n \"You need to define a _renderResult function on your sub class!\"\n )\n }\n\n /**\n * html template for each list entry\n * @param {object} item\n * @param {string} term\n * @returns {string}\n * @private\n */\n _renderListEntry() {\n throw new Error(\n \"You need to define a _renderListEntry function on your sub class!\"\n )\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\nclass AttachmentSelect extends RemoteSelect {\n _renderResult(item) {\n return this._renderListEntry(item)\n }\n\n /**\n * html template for each list entry\n * @param {object} page\n * @param {string} term\n * @returns {string}\n * @private\n */\n _renderListEntry(attachment, term) {\n return `\n
    \n \n ${this._hightlightTerm(attachment.name, term)}\n
    \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 // Still using jQuery here, because select2 does not emit\n // the event from the original select element.\n $(this).on(\"change\", function (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}\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\n if (this.form.dataset.remote == \"true\") {\n this.form.addEventListener(\"ajax:complete\", this)\n }\n\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 \"ajax:complete\":\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","import \"clipboard\"\nimport { growl } from \"alchemy_admin/growler\"\n\nclass ClipboardButton extends HTMLElement {\n constructor() {\n super()\n\n this.innerHTML = `\n \n `\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","const formatItem = (object) => {\n const optionEl = object.element[0]\n const swatch = optionEl.dataset.swatch || optionEl.value\n const customColor = optionEl.value === \"custom_color\"\n const colorIndicator = customColor\n ? ``\n : ``\n\n return `\n
    \n ${colorIndicator}\n ${object.text}\n
    `\n}\n\nclass ColorSelect extends HTMLElement {\n connectedCallback() {\n if (this.select) {\n this.#initializeSelect2()\n $(this.select).on(\"change\", (event) =>\n this.#toggleColorPicker(event.val === \"custom_color\")\n )\n } else {\n this.colorInput?.addEventListener(\"input\", this)\n this.textInput?.addEventListener(\"input\", this)\n this.#toggleColorPicker(true)\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 #initializeSelect2() {\n this.select.classList.add(\"alchemy_selectbox\")\n const options = {\n minimumResultsForSearch: 10,\n formatResult: formatItem,\n formatSelection: formatItem\n }\n $(this.select).select2(options)\n }\n\n #toggleColorPicker(enabled = true) {\n this.colorInput.disabled = !enabled\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 get select() {\n return this.querySelector(\"select\")\n }\n}\n\ncustomElements.define(\"alchemy-color-select\", ColorSelect)\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 constructor() {\n super()\n this.addEventListener(\"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","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 url.search = new URLSearchParams(data).toString()\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 } else {\n throw responseData || new Error(\"An error occurred during the transaction\")\n }\n}\n","import { get } from \"alchemy_admin/utils/ajax\"\nimport { translate } from \"alchemy_admin/i18n\"\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 const result = await get(Alchemy.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 }\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","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/components/element_editor\"\n\nexport class DeleteElementButton extends HTMLElement {\n constructor() {\n super()\n this.button?.addEventListener(\"click\", this)\n }\n\n async handleEvent() {\n const confirmed = await openConfirmDialog(this.message)\n if (confirmed) {\n const response = await ajax(\"DELETE\", this.url)\n this.#removeElement(response.data)\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 IngredientAnchorLink from \"alchemy_admin/ingredient_anchor_link\"\nimport { post } from \"alchemy_admin/utils/ajax\"\nimport { createHtmlElement } from \"alchemy_admin/utils/dom_helpers\"\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\"\n\nexport function dispatchPageDirtyEvent(data) {\n document.dispatchEvent(\n new CustomEvent(\"alchemy:page-dirty\", {\n detail: { tooltip: data.publishButtonTooltip }\n })\n )\n}\n\nexport class ElementEditor extends HTMLElement {\n constructor() {\n super()\n\n // Add event listeners\n this.addEventListener(\"click\", this)\n // Triggered by child elements\n this.addEventListener(\"alchemy:element-update-title\", this)\n // We use of @rails/ujs for Rails remote forms\n this.addEventListener(\"ajax:complete\", this)\n\n // Dirty observer still needs to be jQuery\n // in order to support select2.\n $(this.form).on(\"change\", this.onChange)\n\n this.header?.addEventListener(\"dblclick\", () => {\n this.toggle()\n })\n this.toggleButton?.addEventListener(\"click\", (evt) => {\n const elementEditor = evt.target.closest(\"alchemy-element-editor\")\n if (elementEditor === this) {\n this.toggle()\n }\n })\n }\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 // 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 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 \"ajax:complete\":\n if (event.target === this.body) {\n const xhr = event.detail[0]\n event.stopPropagation()\n this.onSaveElement(xhr)\n }\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.closest(\"alchemy-element-editor\").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 * Sets the element to saved state\n * Updates title\n * JS event bubbling will also update the parents element quote.\n * Shows error messages if ingredient validations fail\n * @argument {XMLHttpRequest} xhr\n */\n onSaveElement(xhr) {\n const data = JSON.parse(xhr.responseText)\n // Reset errors that might be visible from last save attempt\n this.setClean()\n // If validation failed\n if (xhr.status === 422) {\n const warning = data.warning\n // Create error messages\n // Mark ingredients as failed\n data.ingredientsWithErrors.forEach((ingredient) => {\n const ingredientEditor = this.querySelector(\n `[data-ingredient-id=\"${ingredient.id}\"]`\n )\n const errorDisplay = createHtmlElement(\n `${ingredient.errorMessage}`\n )\n ingredientEditor?.appendChild(errorDisplay)\n ingredientEditor?.classList.add(\"validation_failed\")\n })\n // Show message\n growl(warning, \"warn\")\n this.elementErrors.classList.remove(\"hidden\")\n } else {\n growl(data.notice)\n this.previewWindow?.refresh().then(() => {\n this.focusElementPreview()\n })\n this.updateTitle(data.previewText)\n data.ingredientAnchors.forEach((anchor) => {\n IngredientAnchorLink.updateIcon(anchor.ingredientId, anchor.active)\n })\n if (data.pageHasUnpublishedChanges) {\n dispatchPageDirtyEvent(data)\n }\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 quote.textContent = title\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(Alchemy.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(Alchemy.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 * @returns {HTMLFormElement|undefined}\n */\n get form() {\n return this.querySelector(\"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\ncustomElements.define(\"alchemy-element-editor\", ElementEditor)\n","import { hightlightTerm } from \"alchemy_admin/components/remote_select\"\n\nconst formatSelection = (option) => {\n return `\n
    \n ${option.icon}${option.text}\n
    \n `\n}\n\nconst formatItem = (icon, text, hint) => {\n const description = hint\n ? `
    ${hint}
    `\n : \"\"\n return `\n
    \n ${formatSelection({ icon, text })}\n ${description}\n
    \n `\n}\n\nclass ElementSelect extends HTMLElement {\n constructor() {\n super()\n }\n\n connectedCallback() {\n const results = this.options\n const options = {\n minimumResultsForSearch: 3,\n dropdownAutoWidth: true,\n data() {\n return { results }\n },\n formatResult: (option, _el, search) => {\n let text\n\n if (option.id === \"\") return option.text\n if (search.term !== \"\") {\n text = hightlightTerm(option.text, search.term)\n } else {\n text = option.text\n }\n\n return formatItem(option.icon, text, option.hint)\n },\n formatSelection,\n placeholder: this.placeholder\n }\n $(this.inputField).select2(options)\n }\n\n get options() {\n return JSON.parse(this.getAttribute(\"options\"))\n }\n\n get placeholder() {\n return this.getAttribute(\"placeholder\")\n }\n\n get inputField() {\n return this.querySelector(\"input\")\n }\n}\n\ncustomElements.define(\"alchemy-element-select\", ElementSelect)\n","import { ElementEditor } from \"alchemy_admin/components/element_editor\"\n\nclass ElementsWindow extends HTMLElement {\n #visible = true\n #turboFrame = null\n\n constructor() {\n super()\n this.#attachEvents()\n }\n\n connectedCallback() {\n this.toggleButton?.addEventListener(\"click\", (evt) => {\n evt.preventDefault()\n this.toggle()\n })\n if (window.location.hash) {\n this.focusElementEditor(window.location.hash)\n }\n this.resize()\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 #attachEvents() {\n this.collapseButton?.addEventListener(\"click\", () => {\n this.collapseAllElements()\n })\n window.addEventListener(\"message\", (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 document.body.addEventListener(\"click\", (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}\n\ncustomElements.define(\"alchemy-elements-window\", ElementsWindow)\n","class ElementsWindowHandle extends HTMLElement {\n #dragging = false\n #elementsWindow = null\n #previewWindow = null\n\n constructor() {\n super()\n\n this.addEventListener(\"mousedown\", this)\n window.addEventListener(\"mousemove\", this)\n window.addEventListener(\"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 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 this.elementsWindow.resize(elementWindowWidth)\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 constructor() {\n super()\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 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\n constructor() {\n super()\n this.#attachEvents()\n }\n\n #attachEvents() {\n if (this.hotkey) {\n key(this.hotkey, () => {\n this.filterField.focus()\n return false\n })\n }\n this.filterField.addEventListener(\"keyup\", () => {\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 this.clearButton.addEventListener(\"click\", (e) => {\n e.preventDefault()\n this.clear()\n })\n this.filterField.addEventListener(\"focus\", () =>\n key.setScope(\"list_filter\")\n )\n key(\"esc\", \"list_filter\", () => {\n this.clear()\n this.filterField.blur()\n })\n }\n\n disconnectedCallback() {\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\ncustomElements.define(\"alchemy-list-filter\", ListFilter)\n","const DISMISS_DELAY = 5000\n\nclass Message extends HTMLElement {\n #message\n\n constructor() {\n super()\n this.#message = this.innerHTML\n if (this.dismissable || this.type === \"error\") {\n this.addEventListener(\"click\", this)\n }\n }\n\n handleEvent(event) {\n if (event.type === \"click\") {\n this.dismiss()\n }\n }\n\n connectedCallback() {\n this.innerHTML = `\n \n ${this.dismissable && this.type === \"error\" ? '' : \"\"}\n ${this.#message}\n `\n if (this.dismissable && this.type !== \"error\") {\n setTimeout(() => {\n this.dismiss()\n }, this.dismissDelay)\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","class Icon extends HTMLElement {\n static get observedAttributes() {\n return [\"name\", \"size\", \"icon-style\"]\n }\n\n constructor() {\n super()\n this.spriteUrl = 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 = ``\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\ncustomElements.define(\"alchemy-icon\", Icon)\n","export class IngredientGroup extends HTMLDetailsElement {\n #localStorageKey = \"Alchemy.expanded_ingredient_groups\"\n\n constructor() {\n super()\n\n this.addEventListener(\"toggle\", this)\n\n if (this.isInLocalStorage) {\n this.open = true\n }\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-left-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 constructor() {\n super()\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 = ''\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 constructor() {\n super()\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 ''\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 constructor() {\n super()\n this.addEventListener(\"alchemy:link\", this)\n this.addEventListener(\"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","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 { RemoteSelect } from \"alchemy_admin/components/remote_select\"\n\nclass NodeSelect extends RemoteSelect {\n _searchQuery(term, page) {\n return {\n filter: {\n name_or_page_name_cont: term,\n ...JSON.parse(this.queryParams)\n },\n page: page\n }\n }\n\n _renderResult(item) {\n return this._renderListEntry(item)\n }\n\n /**\n * html template for each list entry\n * @param {object} node\n * @param {string} term\n * @returns {string}\n * @private\n */\n _renderListEntry(node, term) {\n const ancestors = node.ancestors.map((a) => a.name)\n const seperator = ``\n\n return `\n
    \n \n
    \n \n ${ancestors.length > 0 ? ancestors.join(seperator) + seperator : \"\"}\n \n \n ${this._hightlightTerm(node.name, term)}\n \n
    \n
    \n ${node.url || \"\"}\n
    \n
    \n `\n }\n}\n\ncustomElements.define(\"alchemy-node-select\", NodeSelect)\n","import { formatFileSize } from \"alchemy_admin/utils/format\"\nimport { translate } from \"alchemy_admin/i18n\"\nimport { growl } from \"alchemy_admin/growler\"\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 \n
    \n ${this.file?.name}\n ${this.loadedSize}\n ${this.errorMessage}\n
    \n \n \n \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 `Alchemy.uploader_defaults` - configuration\n */\n #validateFile() {\n const config = 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 const isFileFormatSupported =\n allowedFiletypes.includes(\"*\") ||\n allowedFiletypes.includes(\n this.file?.type.replace(/^\\w+\\/(\\w+)(\\+\\w+)?/i, \"$1\")\n )\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 \n
    \n \n\n \n \n \n
    \n
    3 ? 3 : fileCount\n }\">
    \n
    \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 { 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\n const fileUploads = files.map((file) => {\n const request = new XMLHttpRequest()\n const fileUpload = new FileUpload()\n fileUpload.initialize(file, request)\n\n if (Alchemy.uploader_defaults.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 document.body.removeChild(this.uploadProgress)\n }\n this.uploadProgress = new Progress()\n this.uploadProgress.initialize(fileUploads)\n this.uploadProgress.onComplete = (status) => {\n this.dispatchEvent(\n new CustomEvent(`Alchemy.upload.${status}`, { bubbles: true })\n )\n }\n\n document.body.append(this.uploadProgress)\n }\n\n get dropzone() {\n return this.getAttribute(\"dropzone\")\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 \n
    \n ${this.getAttribute(\"text\") ?? \"\"}\n
    \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\"\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 Alchemy.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 = ``\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 = ``\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 = ''\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 const public_on_picker = this.querySelector(\n \"alchemy-datepicker:has(#page_public_on)\"\n )\n const public_until_picker = this.querySelector(\n \"alchemy-datepicker:has(#page_public_until)\"\n )\n const publication_date_fields = this.querySelector(\n \".page-publication-date-fields\"\n )\n const public_field = this.querySelector(\"#page_public\")\n\n if (!public_field) return\n\n public_field.addEventListener(\"click\", function (evt) {\n const checkbox = evt.target\n const now = new Date()\n\n if (checkbox.checked) {\n publication_date_fields.classList.remove(\"hidden\")\n public_on_picker.flatpickr.setDate(now)\n } else {\n publication_date_fields.classList.add(\"hidden\")\n public_on_picker.flatpickr.clear()\n }\n public_until_picker.flatpickr?.clear()\n })\n }\n}\n\ncustomElements.define(\"alchemy-page-publication-fields\", PagePublicationFields)\n","import { RemoteSelect } from \"alchemy_admin/components/remote_select\"\n\nclass 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_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 /**\n * result which is visible if a page was selected\n * @param {object} page\n * @returns {string}\n * @private\n */\n _renderResult(page) {\n return page.text || page.name\n }\n\n /**\n * html template for each list entry\n * @param {object} page\n * @param {string} term\n * @returns {string}\n * @private\n */\n _renderListEntry(page, term) {\n return `\n
    \n
    \n \n ${this._hightlightTerm(page.name, term)}\n ${page.site.name}\n
    \n
    \n ${page.url_path}\n ${page.language_code}\n
    \n
    \n `\n }\n}\n\ncustomElements.define(\"alchemy-page-select\", PageSelect)\n","class PictureDescriptionSelect extends HTMLElement {\n constructor() {\n super()\n this.addEventListener(\"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\"\n\nconst UPDATE_DELAY = 125\nconst IMAGE_PLACEHOLDER = ''\nconst THUMBNAIL_SIZE = \"160x120\"\n\nexport class PictureEditor extends HTMLElement {\n constructor() {\n super()\n\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.bind(this))\n }\n\n connectedCallback() {\n this.observer = new MutationObserver(this.mutationCallback.bind(this))\n\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(Alchemy.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.classList.add(\"thumbnail_background\")\n this.spinner = new Spinner(\"small\")\n\n if (this.src) {\n this.start()\n }\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.#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 \n \n \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 constructor() {\n super()\n this.addEventListener(\"submit\", this)\n }\n\n connectedCallback() {\n document.addEventListener(\"alchemy:page-dirty\", this)\n }\n\n disconnectedCallback() {\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","class Select extends HTMLSelectElement {\n #select2Element\n\n connectedCallback() {\n this.classList.add(\"alchemy_selectbox\")\n\n this.#select2Element = $(this).select2({\n minimumResultsForSearch: 5,\n dropdownAutoWidth: true,\n allowClear: !!this.allowClear\n })\n\n // For single selects, remove the close button if allowClear is not set\n // For multiple selects, always keep the close buttons\n if (!this.allowClear && !this.multiple) {\n this.#select2Element\n .prev(\".select2-container\")\n .find(\".select2-search-choice-close\")\n .remove()\n }\n }\n\n enable() {\n this.removeAttribute(\"disabled\")\n this.#updateSelect2()\n }\n\n disable() {\n this.setAttribute(\"disabled\", \"disabled\")\n this.#updateSelect2()\n }\n\n setOptions(data, prompt = undefined) {\n let selectedValue = this.value\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.#updateSelect2()\n }\n\n /**\n * inform Select2 to update\n */\n #updateSelect2() {\n this.#select2Element.trigger(\"change\")\n }\n\n get allowClear() {\n return this.dataset.hasOwnProperty(\"allowClear\") || this.multiple\n }\n}\n\ncustomElements.define(\"alchemy-select\", Select, { extends: \"select\" })\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\"\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 element being dragged\n const pageNode = evt.item\n const pageId = pageNode.pageId\n const url = Alchemy.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/components/element_editor\"\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(Alchemy.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 \n \n \n \n \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 { setupSelectLocale } from \"alchemy_admin/i18n\"\n\nclass TagsAutocomplete extends HTMLElement {\n async connectedCallback() {\n await setupSelectLocale()\n\n this.classList.add(\"autocomplete_tag_list\")\n $(this.input).select2(this.select2Config)\n }\n\n get input() {\n return this.getElementsByTagName(\"input\")[0]\n }\n\n get select2Config() {\n return {\n tags: true,\n tokenSeparators: [\",\"],\n openOnEnter: false,\n minimumInputLength: 1,\n createSearchChoice: this.#createSearchChoice,\n ajax: {\n url: this.getAttribute(\"url\"),\n dataType: \"json\",\n data: (term) => {\n return { term }\n },\n results: (data) => {\n return { results: data }\n }\n },\n initSelection: this.#initSelection\n }\n }\n\n #createSearchChoice(term, data) {\n if (\n $(data).filter(function () {\n return this.text.localeCompare(term) === 0\n }).length === 0\n ) {\n return {\n id: term,\n text: term\n }\n }\n }\n\n #initSelection(element, callback) {\n const data = []\n $(element.val().split(\",\")).each(function () {\n data.push({\n id: this.trim(),\n text: this\n })\n })\n callback(data)\n }\n}\n\ncustomElements.define(\"alchemy-tags-autocomplete\", TagsAutocomplete)\n","import \"tinymce\"\nimport { currentLocale } from \"alchemy_admin/i18n\"\n\nconst DARK_THEME = \"alchemy-dark\"\nconst LIGHT_THEME = \"alchemy\"\n\nclass 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 ``\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 ...Alchemy.TinymceDefaults,\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","(()=>{var n=Handlebars.template;(Handlebars.templates=Handlebars.templates||{})[\"node_folder.hbs\"]=n({0:function(n,e,l,a,r){return\"right\"},1:function(n,e,l,a,r){return\"down\"},compiler:[8,\">= 4.3.0\"],main:function(n,e,l,a,r){var o,t=n.lambda,d=n.escapeExpression,u=n.lookupProperty||function(n,e){if(Object.prototype.hasOwnProperty.call(n,e))return n[e]};return'
    \\n \\n\\n'},useData:!0})})();","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","export 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 Cropper from \"cropperjs\"\n\nexport default class ImageCropper {\n #initialized = false\n #cropper = null\n #cropFromField = null\n #cropSizeField = null\n\n constructor(image, settings) {\n this.image = image\n this.defaultBox = settings.default_box\n this.aspectRatio = settings.ratio\n this.#cropFromField = document.getElementById(\n settings.crop_from_form_field_id\n )\n this.#cropSizeField = document.getElementById(\n settings.crop_size_form_field_id\n )\n this.elementId = settings.element_id\n this.dialog = Alchemy.currentDialog()\n if (this.dialog) {\n this.dialog.options.closed = () => this.destroy()\n this.bind()\n }\n this.init()\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 cropend: () => {\n const data = this.#cropper.getData(true)\n this.update(data)\n }\n }\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.defaultBoxSize\n }\n }\n\n get defaultBoxSize() {\n return {\n x: this.defaultBox[0],\n y: this.defaultBox[1],\n width: this.defaultBox[2],\n height: this.defaultBox[3]\n }\n }\n\n init() {\n if (!this.#initialized) {\n this.#cropper = new Cropper(this.image, this.cropperOptions)\n this.#initialized = true\n }\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 this.#cropper.setData(this.defaultBoxSize)\n this.update(this.defaultBoxSize)\n }\n\n destroy() {\n if (this.#cropper) {\n this.#cropper.destroy()\n }\n this.#initialized = false\n return true\n }\n\n bind() {\n this.dialog.dialog_body.find('button[type=\"submit\"]').on(\"click\", () => {\n const elementEditor = document.querySelector(\n `[data-element-id='${this.elementId}']`\n )\n elementEditor.setDirty()\n this.dialog.close()\n return false\n })\n this.dialog.dialog_body.find('button[type=\"reset\"]').on(\"click\", () => {\n this.reset()\n return false\n })\n }\n}\n","import { Dialog } from \"alchemy_admin/dialog\"\n\nexport default class ImageOverlay extends Dialog {\n constructor(url, options = {}) {\n super(url, options)\n }\n\n init() {\n $(\".zoomed-picture-background\").on(\"click\", (e) => {\n e.stopPropagation()\n if (e.target.nodeName === \"IMG\") {\n return\n }\n this.close()\n return false\n })\n $(\".picture-overlay-handle\").on(\"click\", (e) => {\n this.dialog.toggleClass(\"hide-form\")\n return false\n })\n this.$previous = $(\".previous-picture\")\n this.$next = $(\".next-picture\")\n this.#initKeyboardNavigation()\n super.init()\n }\n\n previous() {\n if (this.$previous[0] != null) {\n this.$previous[0].click()\n }\n }\n\n next() {\n if (this.$next[0] != null) {\n this.$next[0].click()\n }\n }\n\n build() {\n this.dialog_container = $('
    ')\n this.dialog = $('
    ')\n this.dialog_body = $('
    ')\n this.close_button = $(`\n \n `)\n this.dialog.append(this.close_button)\n this.dialog.append(this.dialog_body)\n this.dialog_container.append(this.dialog)\n this.overlay = $('
    ')\n this.$body.append(this.overlay)\n this.$body.append(this.dialog_container)\n }\n\n #initKeyboardNavigation() {\n this.$document.keydown((e) => {\n if (e.target.nodeName === \"INPUT\" || e.target.nodeName === \"TEXTAREA\") {\n return true\n }\n switch (e.which) {\n case 37:\n this.previous()\n return false\n case 39:\n this.next()\n return false\n default:\n return true\n }\n })\n }\n}\n","import { on } from \"alchemy_admin/utils/events\"\nimport { openDialog } from \"alchemy_admin/dialog\"\n\nfunction toggleCheckboxes(state) {\n document\n .querySelectorAll(\".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\nfunction checkedInputs() {\n return document.querySelectorAll(\"#picture_archive input:checked\")\n}\n\nfunction editMultiplePicturesUrl(href) {\n const url = new URL(href)\n\n checkedInputs().forEach((entry) =>\n url.searchParams.append(entry.name, entry.value)\n )\n\n return url.toString()\n}\n\n/**\n * Multiple picture select handler for the picture archive.\n */\nexport default function PictureSelector() {\n const selectAllButton = document.querySelector(\"#select_all_pictures\")\n const selectedItemTools = document.querySelector(\".selected_item_tools\")\n\n on(\"click\", \".toolbar_buttons\", \"a#select_all_pictures\", (event) => {\n event.preventDefault()\n\n selectAllButton.classList.toggle(\"active\")\n\n const state = selectAllButton.classList.contains(\"active\")\n\n toggleCheckboxes(state)\n\n 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 on(\"change\", \".picture_tool.select\", \"input\", (event) => {\n selectedItemTools.classList.toggle(\"hidden\", checkedInputs().length === 0)\n\n const parentElementClassList = event.target.parentElement.classList\n const checked = event.target.checked\n\n parentElementClassList.toggle(\"visible\", checked)\n })\n\n // open the edit view in a dialog modal\n on(\"click\", \".selected_item_tools\", \"a#edit_multiple_pictures\", (event) => {\n event.preventDefault()\n\n const url = editMultiplePicturesUrl(event.target.href)\n\n openDialog(url, {\n title: event.target.title,\n size: \"400x295\"\n })\n })\n}\n","import Sortable from \"sortablejs\"\nimport { patch } from \"alchemy_admin/utils/ajax\"\nimport { on } from \"alchemy_admin/utils/events\"\nimport { growl } from \"alchemy_admin/growler\"\n\nfunction displayNodeFolders() {\n document.querySelectorAll(\"li.menu-item\").forEach((el) => {\n const leftIconArea = el.querySelector(\".nodes_tree-left_images\")\n const list = el.querySelector(\".children\")\n const node = {\n folded: el.dataset.folded === \"true\",\n id: el.dataset.id,\n type: el.dataset.type\n }\n\n if (list.children.length > 0 || node.folded) {\n leftIconArea.innerHTML = Handlebars.templates[\"node_folder.hbs\"]({\n node: node\n })\n } else {\n leftIconArea.innerHTML = \" \"\n }\n })\n}\n\nfunction onFinishDragging(evt) {\n const url = Alchemy.routes[evt.item.dataset.type].move_api_path(\n evt.item.dataset.id\n )\n const data = {\n target_parent_id: evt.to.dataset.recordId,\n new_position: evt.newIndex\n }\n\n patch(url, data)\n .then(() => {\n const message = Alchemy.t(\"Successfully moved menu item\")\n growl(message)\n displayNodeFolders()\n })\n .catch((error) => {\n growl(error.message || error, \"error\")\n })\n}\n\nfunction handleNodeFolders() {\n on(\"click\", \".nodes_tree\", \".node_folder\", function () {\n const nodeId = this.dataset.recordId\n const menu_item = this.closest(\"li.menu-item\")\n const url =\n Alchemy.routes[this.dataset.recordType].toggle_folded_api_path(nodeId)\n const list = menu_item.querySelector(\".children\")\n\n patch(url)\n .then(() => {\n list.classList.toggle(\"folded\")\n menu_item.dataset.folded =\n menu_item.dataset.folded == \"true\" ? \"false\" : \"true\"\n displayNodeFolders()\n })\n .catch((error) => {\n growl(error.message || error)\n })\n })\n}\n\nexport default function NodeTree() {\n handleNodeFolders()\n displayNodeFolders()\n\n document.querySelectorAll(\".nodes_tree ul.children\").forEach((el) => {\n new Sortable(el, {\n group: \"nodes\",\n animation: 150,\n fallbackOnBody: true,\n swapThreshold: 0.65,\n handle: \".node_name\",\n invertSwap: true,\n onEnd: onFinishDragging\n })\n })\n}\n","// We still use jQuery in some places (ie. select2)\nimport \"handlebars\"\nimport \"jquery\"\nimport \"@ungap/custom-elements\"\nimport { Turbo } from \"@hotwired/turbo-rails\"\nimport \"select2\"\n\nimport Rails from \"@rails/ujs\"\n\nimport { translate } from \"alchemy_admin/i18n\"\nimport { currentDialog, closeCurrentDialog } from \"alchemy_admin/dialog\"\nimport Dirty from \"alchemy_admin/dirty\"\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\n// Web Components\nimport \"alchemy_admin/components\"\n\n// Handlebars Templates\nimport \"alchemy_admin/templates/compiled\"\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 ...Dirty,\n t: translate, // Global utility method for translating a given string\n FixedElements,\n growl,\n LinkDialog,\n pleaseWaitOverlay,\n Spinner,\n reloadPreview\n})\n\nRails.start()\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 { on } from \"alchemy_admin/utils/events\"\n\n// Page-specific modules - bundled to avoid dual-loading\nexport { default as ImageCropper } from \"alchemy_admin/image_cropper\"\nexport { default as ImageOverlay } from \"alchemy_admin/image_overlay\"\nexport { default as pictureSelector } from \"alchemy_admin/picture_selector\"\nexport { default as NodeTree } from \"alchemy_admin/node_tree\"\n","import Hotkeys from \"alchemy_admin/hotkeys\"\nimport pleaseWaitOverlay from \"alchemy_admin/please_wait_overlay\"\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 // 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 '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"],"names":["KEY_SEPARATOR","getTranslation","key","locale","currentLocale","translations","Alchemy","test","keys","split","group","nestedTranslation","console","warn","document","documentElement","lang","translate","replacement","undefined","translation","replace","async","setupSelectLocale","import","$","extend","fn","select2","defaults","locales","bindedHotkeys","showHelp","evt","target","is","String","fromCharCode","which","openDialog","title","t","size","Hotkeys","scope","jQuery","removeEventListener","addEventListener","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","color","this","spinner","spin","parent","body","append","stop","remove","currentDialogs","DEFAULTS","header_height","padding","modal","overflow","ready","closed","Dialog","url","options","$document","$window","window","$body","width","parseInt","height","build","resize","open","dialog","trigger","bind_close_events","requestAnimationFrame","dialog_container","addClass","overlay","load","close","off","removeClass","on","pop","show_spinner","get","data","fail","xhr","show_error","reload","dialog_body","empty","remove_spinner","hide","html","init","dispatchEvent","CustomEvent","bubbles","detail","show","Spinner","watch_remote_forms","$form","event","getResponseHeader","match","responseText","statusText","status","error_body","error_header","error_type","error_messages","$errorDiv","error","close_button","e","keydown","dialog_header","dialog_title","getSize","css","doc_width","doc_height","currentDialog","length","closeCurrentDialog","callback","ConfirmDialog","message","ok_label","cancel_label","on_ok","bindEvents","cancelButton","preventDefault","on_cancel","okButton","source","querySelector","openConfirmDialog","Promise","resolve","pleaseWaitOverlay","checkPageDirtyness","action","find","appendTo","Turbo","visit","pathname","then","proceed","onbeforeunload","Dirty","PageLeaveObserver","currentTarget","removeTab","element_id","fixed_elements","getElementById","panel_name","label","tab","panel","growl","style","flashType","flashNotices","flashMessage","ANCHOR_REGEX","PreviewWindow","HTMLIFrameElement","afterLoad","reloadIcon","loadTimeout","previewReadyHandler","super","handlePreviewReadyMessage","bind","handleEvent","type","clearLoadTimeout","stopSpinner","call","connectedCallback","attachEvents","localStorage","getItem","previewUrlSelect","value","refresh","disconnectedCallback","postMessage","contentWindow","startSpinner","src","setTimeout","isDragged","dragged","transitionProperty","pointerEvents","reloadButton","sizeSelect","setItem","includes","clearTimeout","getAttribute","reloadPreview","customElements","define","extends","IngredientAnchorLink","updateIcon","ingredientId","active","ingredientEditor","setAttribute","Action","HTMLElement","actions","removeFixedElement","updateAnchorIcon","hidePleaseWaitOverlay","func","name","params","hasAttribute","JSON","parse","hightlightTerm","term","RegExp","RemoteSelect","isConnected","input","classList","add","select2Config","onOpen","onChange","dispatchCustomEvent","removed","added","allowClear","selection","placeholder","queryParams","getElementsByTagName","initSelection","_$el","ajax","ajaxConfig","formatSelection","item","_renderResult","formatResult","_el","query","_renderListEntry","datatype","quietMillis","page","_searchQuery","results","response","_parseResponse","q","name_cont","meta","more","per_page","total_count","Error","_hightlightTerm","attachment","icon_css_class","AutoSubmit","submitEvent","Event","cancelable","form","Button","HTMLButtonElement","remote","stopPropagation","disable","enable","rect","getBoundingClientRect","initialButtonText","removeAttribute","CharCounter","maxChars","formField","getFormField","createDisplayElement","countCharacters","formFields","display","className","after","charLength","textContent","toggle","ClipboardButton","clipboard","ClipboardJS","destroy","formatItem","object","optionEl","swatch","ColorSelect","select","initializeSelect2","toggleColorPicker","val","colorInput","textInput","minimumResultsForSearch","enabled","disabled","Datepicker","flatpickr","inputField","flatpickrOptions","enableTime","inputType","slice","altInput","altFormat","altInputClass","noCalendar","time_24hr","onValueUpdate","_selectedDates","_dateStr","instance","closest","setDirty","dateFormat","DialogLink","HTMLAnchorElement","dialogOptions","contains","JSON_CONTENT_TYPE","TURBO_STREAM_CONTENT_TYPE","isGetRequest","method","toLowerCase","prepareOptions","accept","headers","Accept","getToken","prepareHeaders","stringify","attributes","patch","post","path","fetch","URL","location","origin","search","URLSearchParams","toString","prepareURL","contentType","isJson","isTurboStream","responseData","json","renderStreamMessage","ok","DomIdSelect","dataItem","hash","id","selectElement","pageId","fetchDomIds","routes","api_ingredients_path","page_id","ingredients","filter","ingredient","dom_id","map","prompt","setOptions","reset","frame","elements","contentDocument","Array","from","PublishElementButton","scheduleButtonVariant","scheduleButton","publishButton","dropdown","loading","DeleteElementButton","button","removeElement","elementEditor","fixed","elementId","pageHasUnpublishedChanges","dispatchPageDirtyEvent","tooltip","publishButtonTooltip","ElementEditor","header","toggleButton","focusElement","previewWindow","focusElementPreview","onClickElement","onSaveElement","hasEditors","firstChild","setTitle","selectTabForElement","expand","setClean","warning","ingredientsWithErrors","errorDisplay","errorMessage","appendChild","elementErrors","notice","updateTitle","previewText","ingredientAnchors","anchor","scrollToElement","scrollIntoView","behavior","scroll","reject","tabs","dirty","editor","collapsed","collapse","compact","toggleIcon","collapse_admin_element_path","nestedElementIds","selector","join","nestedElement","catch","finally","expanded","parentElementEditor","expand_admin_element_path","parentElementIds","parentElement","published","isPublished","bodySelector","footer","elementName","hasChildren","option","icon","ElementSelect","dropdownAutoWidth","hint","description","ElementsWindow","visible","turboFrame","focusElementEditor","collapseAllElements","removeProperty","widthFromCookie","setProperty","cookie","collapseButton","row","startsWith","ElementsWindowHandle","dragging","elementsWindow","onMouseDown","onMouseUp","onDrag","pageX","elementWindowWidth","innerWidth","FileEditor","deleteLink","fileIcon","fileName","formFieldId","removeFile","ListFilter","debounceTimer","filterField","clearButton","visibility","debounceTime","clear","matchedItems","itemsToShow","Set","lowerTerm","items","nameAttribute","indexOf","ancestor","itemsSelector","has","block","Message","dismissable","dismiss","iconName","dismissDelay","noticesWrapper","autoDismissDelay","Growl","Icon","observedAttributes","spriteUrl","render","attributeChangedCallback","sizeClass","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","formatFileSize","bytes","exponent","Math","floor","log","pow","toFixed","name_or_page_name_cont","node","ancestors","a","seperator","FileUpload","file","request","progressEventLoaded","progressEventTotal","valid","loadedSize","cancel","reader","FileReader","readAsDataURL","image","Image","result","prepend","initialize","validateFile","addRequestEventListener","finished","abort","config","uploader_defaults","maxFileSize","file_size_limit","allowedFiletypes","allowed_filetypes","alchemy_pictures","alchemy_attachments","upload","onprogress","progressEvent","onload","responseMessage","onerror","errorMessageContainer","progressElement","loaded","total","round","toggleAttribute","isValid","Progress","fileCount","fileUploads","buttonLabel","actionButton","handleFileChange","updateView","onComplete","fileUpload","activeUploads","setupCloseButton","_status","ariaLabel","sumFileProgresses","field","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","dataTransfer","getAsFile","redirectUrl","uploadProgress","fileUploadCount","XMLHttpRequest","upload_limit","submitFile","createProgress","formData","FormData","set","setRequestHeader","send","removeChild","Overlay","BUTTON","AlchemyPageNode","folded","folderButton","handleFolderClick","fold_admin_page_path","toggleChildren","updateFolderIcon","childrenContainer","updateFolderButton","folderElement","shouldShowButton","tagName","outerHTML","PagePublicationFields","public_on_picker","public_until_picker","publication_date_fields","public_field","checkbox","now","Date","checked","setDate","pages","site","url_path","language_code","PictureDescriptionSelect","searchParams","PictureEditor","cropFromField","cropSizeField","pictureIdField","targetSizeField","imageCropperField","pictureThumbnail","deleteButton","cropLink","targetSize","pictureId","update","delay","timeout","args","that","apply","debounce","updateImage","updateCropLink","removeImage","observer","MutationObserver","mutationCallback","observe","disconnect","mutationsList","mutation","url_admin_picture_path","crop","imageCropperEnabled","crop_from","cropFrom","crop_size","cropSize","flatten","alt","href","defaultCropFrom","defaultCropSize","mask","n","zoom","imageFileWidth","b","imageFileHeight","dimensions","imageCropper","PictureThumbnail","start","onLoaded","onError","setImage","createImage","complete","hoist","replaceChildren","PublishPageButton","markDirty","variant","Select","HTMLSelectElement","select2Element","multiple","prev","updateSelect2","selectedValue","Option","hasOwnProperty","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","Sortable","animation","fallbackOnBody","swapThreshold","handle","draggable","onEnd","handleSort","to","oldIndex","newIndex","pageNode","move_admin_page_path","target_parent_id","parentId","new_position","pageData","pageEl","urlPathEl","updateFolderIcons","fromContainer","toContainer","fromParent","toParent","SORTABLE_OPTIONS","ghostClass","easing","onStart","onSort","position","parent_element_id","order_admin_elements_path","preview_text","SortableElements","put","_from","droppableElements","TagsAutocomplete","tags","tokenSeparators","openOnEnter","minimumInputLength","createSearchChoice","dataType","localeCompare","each","DARK_THEME","LIGHT_THEME","Tinymce","min_height","insertAdjacentHTML","minHeight","root","rootMargin","threshold","tinymceIntersectionObserver","IntersectionObserver","entries","intersectionRatio","_initTinymceEditor","unobserve","_setupThemeChangeListener","_removeThemeChangeListener","tinymce","editorId","configuration","editors","_setupEditor","editorContainer","darkModeMediaQuery","matchMedia","themeChangeHandler","_handleThemeChange","skin","matches","content_css","customConfig","getAttributeNames","attributeName","replaceAll","preferredTheme","TinymceDefaults","language","UpdateCheck","credentials","responseJSON","showStatus","showError","Handlebars","template","templates","l","r","compiler","main","o","lambda","d","escapeExpression","u","lookupProperty","Object","prototype","nullContext","program","inverse","loc","line","column","end","useData","setDefaultAnimation","keyframes","transform","opacity","duration","iconMap","caret","resolver","mutator","svg","spriteSheet","eventName","baseSelector","targetSelector","baseNode","targets","currentNode","registerIconLibrary","ImageCropper","initialized","cropper","settings","defaultBox","default_box","aspectRatio","ratio","crop_from_form_field_id","crop_size_form_field_id","cropperOptions","viewMode","zoomable","checkCrossOrigin","checkOrientation","box","cropend","getData","v","x","y","defaultBoxSize","Cropper","coords","setData","ImageOverlay","nodeName","toggleClass","$previous","$next","initKeyboardNavigation","previous","next","checkedInputs","PictureSelector","selectAllButton","selectedItemTools","state","toggleCheckboxes","parentElementClassList","editMultiplePicturesUrl","displayNodeFolders","leftIconArea","list","onFinishDragging","move_api_path","recordId","NodeTree","nodeId","menu_item","recordType","toggle_folded_api_path","invertSwap","assign","FixedElements","onCreateLink","link_admin_pages_path","parameterMapping","selected_tab","link_title","link_target","internalForm","attachmentSelect","updatePage","submitForm","linkFormType","internalLink","domIdSelect","linkType","elementAnchor","link_url_regexp","showValidationError","errors","Rails","forms","confirm","srcElement","isPressed"],"mappings":"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"} \ No newline at end of file diff --git a/app/javascript/alchemy_admin/components/alchemy_html_element.js b/app/javascript/alchemy_admin/components/alchemy_html_element.js deleted file mode 100644 index 1de23f85e1..0000000000 --- a/app/javascript/alchemy_admin/components/alchemy_html_element.js +++ /dev/null @@ -1,129 +0,0 @@ -import { toCamelCase } from "alchemy_admin/utils/string_conversions" - -export class AlchemyHTMLElement extends HTMLElement { - static properties = {} - - /** - * create the list of observed attributes - * this function is a requirement for the `attributeChangedCallback` - method - * @returns {string[]} - * @link https://developer.mozilla.org/en-US/docs/Web/API/Web_Components#reference - */ - static get observedAttributes() { - return Object.keys(this.properties) - } - - constructor(options = {}) { - super() - - this.options = options - this.changeComponent = true - this.initialContent = this.innerHTML // store the inner content of the component - } - - /** - * run when the component will be initialized by the Browser - * this is a default function - * @link https://developer.mozilla.org/en-US/docs/Web/API/Web_Components#reference - */ - async connectedCallback() { - // parse the properties object and register property with the default values - Object.keys(this.constructor.properties).forEach((name) => { - // if the options was given via the constructor, they should be prefer (e.g. new ({title: "Foo"})) - this[name] = - this.options[name] ?? this.constructor.properties[name].default - }) - - // then process the attributes - this.getAttributeNames().forEach((name) => this._updateFromAttribute(name)) - - // render the component - this._updateComponent() - await this.connected() - } - - /** - * disconnected callback if the component is removed from the DOM - * this is currently only a Proxy to the disconnected - callback to use the same callback structure - * as for the connected - callback - * @link https://developer.mozilla.org/en-US/docs/Web/API/Web_Components#reference - */ - disconnectedCallback() { - this.disconnected() - } - - /** - * triggered by the browser, if one of the observed attributes is changing - * @link https://developer.mozilla.org/en-US/docs/Web/API/Web_Components#reference - */ - attributeChangedCallback(name) { - this._updateFromAttribute(name) - this._updateComponent() - } - - /** - * a connected method to make it easier to overwrite the connection callback - */ - async connected() {} - - /** - * a disconnected method to make it easier to overwrite the disconnection callback - */ - disconnected() {} - - /** - * empty method container to allow the child component to put the rendered string into this method - * @returns {String} - */ - render() { - return this.initialContent - } - - /** - * after render callback - * the function will be triggered after the DOM was updated - */ - afterRender() {} - - /** - * Dispatches a custom event with given name - * @param {string} name The name of the custom event - * @param {object} detail Optional event details - */ - dispatchCustomEvent(name, detail = {}) { - const event = new CustomEvent(`Alchemy.${name}`, { bubbles: true, detail }) - this.dispatchEvent(event) - } - - /** - * (re)render the component content inside the component container - * @private - */ - _updateComponent() { - if (this.changeComponent) { - this.innerHTML = this.render() - this.changeComponent = false - this.afterRender() - } - } - - /** - * update the value from the given attribute - * - * @param {string} name - * @private - */ - _updateFromAttribute(name) { - const attributeValue = this.getAttribute(name) - const propertyName = toCamelCase(name) - const isBooleanValue = - attributeValue.length === 0 || attributeValue === "true" - - const value = isBooleanValue ? true : attributeValue - - if (this[propertyName] !== value) { - this[propertyName] = value - this.changeComponent = true - } - } -} diff --git a/app/javascript/alchemy_admin/components/char_counter.js b/app/javascript/alchemy_admin/components/char_counter.js index 2a8752b43d..53d9f97a21 100644 --- a/app/javascript/alchemy_admin/components/char_counter.js +++ b/app/javascript/alchemy_admin/components/char_counter.js @@ -1,30 +1,36 @@ /** * Show the character counter below input fields and textareas */ -import { AlchemyHTMLElement } from "alchemy_admin/components/alchemy_html_element" import { translate } from "alchemy_admin/i18n" -class CharCounter extends AlchemyHTMLElement { - static properties = { - maxChars: { default: 60 } - } - connected() { +class CharCounter extends HTMLElement { + connectedCallback() { this.translation = translate("allowed_chars", this.maxChars) this.formField = this.getFormField() if (this.formField) { this.createDisplayElement() this.countCharacters() - this.formField.addEventListener("keyup", () => this.countCharacters()) // add arrow function to get a implicit this - binding + this.formField.addEventListener("keyup", this) } } + disconnectedCallback() { + this.formField?.removeEventListener("keyup", this) + } + + handleEvent(event) { + if (event.type === "keyup") this.countCharacters() + } + getFormField() { const formFields = this.querySelectorAll("input, textarea") return formFields.length > 0 ? formFields[0] : undefined } createDisplayElement() { + this.display = this.querySelector(":scope > .alchemy-char-counter") + if (this.display) return this.display = document.createElement("small") this.display.className = "alchemy-char-counter" this.formField.after(this.display) @@ -35,6 +41,10 @@ class CharCounter extends AlchemyHTMLElement { this.display.textContent = `${charLength} ${this.translation}` this.display.classList.toggle("too-long", charLength > this.maxChars) } + + get maxChars() { + return this.getAttribute("max-chars") ?? 60 + } } customElements.define("alchemy-char-counter", CharCounter) diff --git a/app/javascript/alchemy_admin/components/datepicker.js b/app/javascript/alchemy_admin/components/datepicker.js index b36f9bb1ab..3e27c7e706 100644 --- a/app/javascript/alchemy_admin/components/datepicker.js +++ b/app/javascript/alchemy_admin/components/datepicker.js @@ -1,31 +1,24 @@ -import { AlchemyHTMLElement } from "alchemy_admin/components/alchemy_html_element" import { translate, currentLocale } from "alchemy_admin/i18n" import flatpickr from "flatpickr" const locale = currentLocale() -class Datepicker extends AlchemyHTMLElement { - static properties = { - inputType: { default: "date" } - } - - constructor() { - super() - this.flatpickr = undefined - } - +class Datepicker extends HTMLElement { // Load the locales for flatpickr before setting it up. - async connected() { + async connectedCallback() { // English is the default locale for flatpickr, so we don't need to load it if (locale !== "en") { await import(`flatpickr/${locale}.js`) } + // Bail out if the element was disconnected while the locale was loading. + // Otherwise flatpickr would leak a calendar onto a detached input. + if (!this.isConnected) return this.flatpickr = flatpickr(this.inputField, this.flatpickrOptions) } - disconnected() { - this.flatpickr.destroy() + disconnectedCallback() { + this.flatpickr?.destroy() } get flatpickrOptions() { @@ -56,6 +49,10 @@ class Datepicker extends AlchemyHTMLElement { get inputField() { return this.querySelector("input") } + + get inputType() { + return this.getAttribute("input-type") || "date" + } } customElements.define("alchemy-datepicker", Datepicker) diff --git a/app/javascript/alchemy_admin/components/overlay.js b/app/javascript/alchemy_admin/components/overlay.js index 1deee2ec4f..f76e31fbf3 100644 --- a/app/javascript/alchemy_admin/components/overlay.js +++ b/app/javascript/alchemy_admin/components/overlay.js @@ -1,13 +1,11 @@ -import { AlchemyHTMLElement } from "alchemy_admin/components/alchemy_html_element" - -class Overlay extends AlchemyHTMLElement { - render() { - return ` +class Overlay extends HTMLElement { + connectedCallback() { + this.innerHTML = `
    - ${this.getAttribute("text")} + ${this.getAttribute("text") ?? ""}
    - ` + ` } set show(value) { diff --git a/app/javascript/alchemy_admin/components/remote_select.js b/app/javascript/alchemy_admin/components/remote_select.js index 428c57d540..70199eeb7a 100644 --- a/app/javascript/alchemy_admin/components/remote_select.js +++ b/app/javascript/alchemy_admin/components/remote_select.js @@ -1,34 +1,38 @@ -import { AlchemyHTMLElement } from "alchemy_admin/components/alchemy_html_element" import { setupSelectLocale } from "alchemy_admin/i18n" export function hightlightTerm(name, term) { return name.replace(new RegExp(term, "gi"), (match) => `${match}`) } -export class RemoteSelect extends AlchemyHTMLElement { - static properties = { - allowClear: { default: false }, - selection: { default: undefined }, - placeholder: { default: "" }, - queryParams: { default: "{}" }, - url: { default: "" } - } +export class RemoteSelect extends HTMLElement { + #select2 = null - async connected() { + async connectedCallback() { await setupSelectLocale() + // Bail out if the element was disconnected while the locale was loading. + // Otherwise Select2 would leak onto a detached input. + if (!this.isConnected) return this.input.classList.add("alchemy_selectbox") - $(this.input) + this.#select2 = $(this.input) .select2(this.select2Config) - .on("select2-open", (evt) => { - this.onOpen(evt) - }) - .on("change", (evt) => { - this.onChange(evt) - }) + .on("select2-open", this.#onOpen) + .on("change", this.#onChange) } + disconnectedCallback() { + if (this.#select2) { + this.#select2.off("select2-open", this.#onOpen) + this.#select2.off("change", this.#onChange) + this.#select2.select2("destroy") + this.#select2 = null + } + } + + #onOpen = (evt) => this.onOpen(evt) + #onChange = (evt) => this.onChange(evt) + /** * Optional on change handler called by Select2. * @param {Event} event @@ -54,6 +58,38 @@ export class RemoteSelect extends AlchemyHTMLElement { }, 100) } + /** + * Dispatches a custom event with given name, namespaced under `Alchemy.`. + * Subclasses may call this to emit their own events. + * @param {string} name The name of the custom event + * @param {object} detail Optional event details + */ + dispatchCustomEvent(name, detail = {}) { + this.dispatchEvent( + new CustomEvent(`Alchemy.${name}`, { bubbles: true, detail }) + ) + } + + get allowClear() { + return this.hasAttribute("allow-clear") + } + + get selection() { + return this.getAttribute("selection") + } + + get placeholder() { + return this.getAttribute("placeholder") ?? "" + } + + get queryParams() { + return this.getAttribute("query-params") ?? "{}" + } + + get url() { + return this.getAttribute("url") ?? "" + } + get input() { return this.getElementsByTagName("input")[0] } diff --git a/app/javascript/alchemy_admin/components/spinner.js b/app/javascript/alchemy_admin/components/spinner.js index 8b0cdc88e7..1a7818d602 100644 --- a/app/javascript/alchemy_admin/components/spinner.js +++ b/app/javascript/alchemy_admin/components/spinner.js @@ -1,15 +1,7 @@ -import { AlchemyHTMLElement } from "alchemy_admin/components/alchemy_html_element" - -class Spinner extends AlchemyHTMLElement { - static properties = { - size: { default: "medium" }, - color: { default: "currentColor" } - } - - render() { +class Spinner extends HTMLElement { + connectedCallback() { this.className = `spinner spinner--${this.size}` - - return ` + this.innerHTML = ` ` } + + get size() { + return this.getAttribute("size") || "medium" + } + + get color() { + return this.getAttribute("color") || "currentColor" + } } customElements.define("alchemy-spinner", Spinner) diff --git a/app/javascript/alchemy_admin/components/tinymce.js b/app/javascript/alchemy_admin/components/tinymce.js index 892cd9d2c5..8877410fc6 100644 --- a/app/javascript/alchemy_admin/components/tinymce.js +++ b/app/javascript/alchemy_admin/components/tinymce.js @@ -1,19 +1,30 @@ import "tinymce" -import { AlchemyHTMLElement } from "alchemy_admin/components/alchemy_html_element" import { currentLocale } from "alchemy_admin/i18n" const DARK_THEME = "alchemy-dark" const LIGHT_THEME = "alchemy" -class Tinymce extends AlchemyHTMLElement { +class Tinymce extends HTMLElement { #min_height = null /** * the observer will initialize Tinymce if the textarea becomes visible */ - connected() { + connectedCallback() { this.className = "tinymce_container" + // Append the spinner if not already present (idempotent on reconnect/clone) + if (!this.querySelector(":scope > alchemy-spinner")) { + this.insertAdjacentHTML( + "beforeend", + `` + ) + } + + // hide the textarea until TinyMCE is ready to show the editor + this.style.minHeight = `${this.minHeight}px` + this.editor.style.display = "none" + const observerCallback = (entries, observer) => { entries.forEach((entry) => { if (entry.intersectionRatio > 0) { @@ -43,10 +54,8 @@ class Tinymce extends AlchemyHTMLElement { /** * disconnect intersection observer and remove Tinymce editor if the web components get destroyed */ - disconnected() { - if (this.tinymceIntersectionObserver !== null) { - this.tinymceIntersectionObserver.disconnect() - } + disconnectedCallback() { + this.tinymceIntersectionObserver?.disconnect() // Remove theme change listener this._removeThemeChangeListener() @@ -54,21 +63,6 @@ class Tinymce extends AlchemyHTMLElement { tinymce.get(this.editorId)?.remove(this.editorId) } - render() { - return ` - ${this.initialContent} - - ` - } - - /** - * hide the textarea until TinyMCE is ready to show the editor - */ - afterRender() { - this.style.minHeight = `${this.minHeight}px` - this.editor.style.display = "none" - } - /** * initialize Richtext area after the Intersection observer triggered * @private diff --git a/app/javascript/alchemy_admin/components/uploader.js b/app/javascript/alchemy_admin/components/uploader.js index 470303dcb5..3e069285c8 100644 --- a/app/javascript/alchemy_admin/components/uploader.js +++ b/app/javascript/alchemy_admin/components/uploader.js @@ -3,36 +3,75 @@ * @property {string} name * @property {number} size */ -import { AlchemyHTMLElement } from "alchemy_admin/components/alchemy_html_element" import { Progress } from "alchemy_admin/components/uploader/progress" import { FileUpload } from "alchemy_admin/components/uploader/file_upload" import { translate } from "alchemy_admin/i18n" import { getToken } from "alchemy_admin/utils/ajax" -export class Uploader extends AlchemyHTMLElement { - static properties = { - dropzone: { default: false } - } +export class Uploader extends HTMLElement { + #dropzoneElement = null + #isDraggedOver = false - connected() { - this.fileInput.addEventListener("change", (event) => { - this._uploadFiles(Array.from(event.target.files)) - }) + connectedCallback() { + this.fileInput.addEventListener("change", this.#onFileInputChange) if (this.dropzone) { - this._dragAndDropBehavior() + this.#setupDropZone() } this.addEventListener("Alchemy.upload.successful", this) } + disconnectedCallback() { + this.fileInput?.removeEventListener("change", this.#onFileInputChange) + if (this.#dropzoneElement) { + this.#dropzoneElement.removeEventListener( + "dragleave", + this.#onDropzoneDragleave + ) + this.#dropzoneElement.removeEventListener("drop", this.#onDropzoneDrop) + this.#dropzoneElement.removeEventListener( + "dragover", + this.#onDropzoneDragover + ) + this.#dropzoneElement = null + } + } + handleEvent(evt) { switch (evt.type) { case "Alchemy.upload.successful": - this._handleUploadComplete() + this.#handleUploadComplete() break } } - _handleUploadComplete() { + #onFileInputChange = (event) => { + this.uploadFiles(Array.from(event.target.files)) + } + + #toggleDropzoneClass = (enabled) => { + if (this.#isDraggedOver !== enabled) { + this.#isDraggedOver = enabled + this.#dropzoneElement.classList.toggle("dragover") + } + } + + #onDropzoneDragleave = () => this.#toggleDropzoneClass(false) + + #onDropzoneDrop = async (event) => { + event.preventDefault() + this.#toggleDropzoneClass(false) + + const files = [...event.dataTransfer.items].map((item) => item.getAsFile()) + + this.uploadFiles(files) + } + + #onDropzoneDragover = (event) => { + event.preventDefault() // dragover has to be disabled to use the custom drop event + this.#toggleDropzoneClass(true) + } + + #handleUploadComplete() { setTimeout(() => { const url = this.redirectUrl const turboFrame = this.closest("turbo-frame") @@ -53,42 +92,22 @@ export class Uploader extends AlchemyHTMLElement { * add dragover class to indicate, if the file is draggable * @private */ - _dragAndDropBehavior() { - const dropzoneElement = document.querySelector(this.dropzone) - let isDraggedOver = false - - const toggleDropzoneClass = (enabled) => { - if (isDraggedOver !== enabled) { - isDraggedOver = enabled - dropzoneElement.classList.toggle("dragover") - } - } + #setupDropZone() { + this.#dropzoneElement = document.querySelector(this.dropzone) + if (!this.#dropzoneElement) return - dropzoneElement.addEventListener("dragleave", () => - toggleDropzoneClass(false) + this.#dropzoneElement.addEventListener( + "dragleave", + this.#onDropzoneDragleave ) - dropzoneElement.addEventListener("drop", async (event) => { - event.preventDefault() - toggleDropzoneClass(false) - - const files = [...event.dataTransfer.items].map((item) => - item.getAsFile() - ) - - this._uploadFiles(files) - }) - - dropzoneElement.addEventListener("dragover", (event) => { - event.preventDefault() // dragover has to be disabled to use the custom drop event - toggleDropzoneClass(true) - }) + this.#dropzoneElement.addEventListener("drop", this.#onDropzoneDrop) + this.#dropzoneElement.addEventListener("dragover", this.#onDropzoneDragover) } /** * @param {File[]} files - * @private */ - _uploadFiles(files) { + uploadFiles(files) { // prepare file progress bars and server request let fileUploadCount = 0 @@ -102,13 +121,13 @@ export class Uploader extends AlchemyHTMLElement { fileUpload.errorMessage = translate("Maximum number of files exceeded") } else if (fileUpload.valid) { fileUploadCount++ - this._submitFile(request, file) + this.#submitFile(request, file) } return fileUpload }) - this._createProgress(fileUploads) + this.#createProgress(fileUploads) } /** @@ -116,7 +135,7 @@ export class Uploader extends AlchemyHTMLElement { * @param {File} file * @private */ - _submitFile(request, file) { + #submitFile(request, file) { const form = this.querySelector("form") const formData = new FormData(form) formData.set(this.fileInput.name, file) @@ -132,7 +151,7 @@ export class Uploader extends AlchemyHTMLElement { * @param {FileUpload[]} fileUploads * @private */ - _createProgress(fileUploads) { + #createProgress(fileUploads) { if (this.uploadProgress) { this.uploadProgress.cancel() document.body.removeChild(this.uploadProgress) @@ -140,12 +159,18 @@ export class Uploader extends AlchemyHTMLElement { this.uploadProgress = new Progress() this.uploadProgress.initialize(fileUploads) this.uploadProgress.onComplete = (status) => { - this.dispatchCustomEvent(`upload.${status}`) + this.dispatchEvent( + new CustomEvent(`Alchemy.upload.${status}`, { bubbles: true }) + ) } document.body.append(this.uploadProgress) } + get dropzone() { + return this.getAttribute("dropzone") + } + /** * @returns {HTMLInputElement} */ diff --git a/app/javascript/alchemy_admin/components/uploader/file_upload.js b/app/javascript/alchemy_admin/components/uploader/file_upload.js index b45436f91a..bf3084e8e2 100644 --- a/app/javascript/alchemy_admin/components/uploader/file_upload.js +++ b/app/javascript/alchemy_admin/components/uploader/file_upload.js @@ -1,38 +1,22 @@ -import { AlchemyHTMLElement } from "alchemy_admin/components/alchemy_html_element" import { formatFileSize } from "alchemy_admin/utils/format" import { translate } from "alchemy_admin/i18n" import { growl } from "alchemy_admin/growler" -export class FileUpload extends AlchemyHTMLElement { - constructor() { - super() - - this.file = null - this.request = null - - this.progressEventLoaded = 0 - this.progressEventTotal = 0 - this.className = "in-progress" - this.valid = true - this.value = 0 - } - - /** - * Initialize the component with file and request - * @param {File} file - * @param {XMLHttpRequest} request - */ - initialize(file, request) { - this.file = file - this.request = request - this.progressEventTotal = file ? file.size : 0 - - this._validateFile() - this._addRequestEventListener() - } - - render() { - return ` +export class FileUpload extends HTMLElement { + // public — used by callers (Uploader, Progress, tests) + file = null + request = null + progressEventLoaded = 0 + progressEventTotal = 0 + + // private — backing state for getters/setters + #valid = true + #value = 0 + #status = undefined + #errorMessage = "" + + connectedCallback() { + this.innerHTML = `
    ${this.file?.name} @@ -45,9 +29,7 @@ export class FileUpload extends AlchemyHTMLElement { ` - } - afterRender() { this.querySelector("button").addEventListener("click", () => this.cancel()) if (this.file?.type.includes("image")) { @@ -61,6 +43,21 @@ export class FileUpload extends AlchemyHTMLElement { } } + /** + * Initialize the component with file and request + * @param {File} file + * @param {XMLHttpRequest} request + */ + initialize(file, request) { + this.file = file + this.request = request + this.progressEventTotal = file ? file.size : 0 + this.status = "in-progress" + + this.#validateFile() + this.#addRequestEventListener() + } + /** * cancel the upload */ @@ -72,11 +69,18 @@ export class FileUpload extends AlchemyHTMLElement { } } + /** + * Dispatches a custom event with given name, namespaced under `Alchemy.`. + * @param {string} name The name of the custom event + */ + dispatchCustomEvent(name) { + this.dispatchEvent(new CustomEvent(`Alchemy.${name}`, { bubbles: true })) + } + /** * validate given file with the `Alchemy.uploader_defaults` - configuration - * @private */ - _validateFile() { + #validateFile() { const config = Alchemy.uploader_defaults const maxFileSize = config.file_size_limit * Math.pow(1024, 2) // in Byte let errorMessage = undefined @@ -107,9 +111,8 @@ export class FileUpload extends AlchemyHTMLElement { /** * register event listeners to react on request changes - * @private */ - _addRequestEventListener() { + #addRequestEventListener() { // prevent errors if the component will be called without a request - object if (!this.request) { return @@ -149,14 +152,14 @@ export class FileUpload extends AlchemyHTMLElement { * @returns {string} */ get errorMessage() { - return this._errorMessage || "" + return this.#errorMessage || "" } /** * @param {string} message */ set errorMessage(message) { - this._errorMessage = message + this.#errorMessage = message const errorMessageContainer = this.querySelector(".error-message") if (errorMessageContainer) { errorMessageContainer.textContent = message @@ -215,14 +218,14 @@ export class FileUpload extends AlchemyHTMLElement { * @returns {string} */ get status() { - return this._status + return this.#status } /** * @param {string} status */ set status(status) { - this._status = status + this.#status = status this.className = status this.progressElement?.toggleAttribute( @@ -235,14 +238,14 @@ export class FileUpload extends AlchemyHTMLElement { * @returns {boolean} */ get valid() { - return this._valid + return this.#valid } /** * @param {boolean} isValid */ set valid(isValid) { - this._valid = isValid + this.#valid = isValid this.classList.toggle("invalid", !isValid) } @@ -251,14 +254,14 @@ export class FileUpload extends AlchemyHTMLElement { * @returns {number} */ get value() { - return this._value + return this.#value } /** * @param {number} value */ set value(value) { - this._value = value + this.#value = value if (this.progressElement) { this.progressElement.value = value } diff --git a/app/javascript/alchemy_admin/components/uploader/progress.js b/app/javascript/alchemy_admin/components/uploader/progress.js index ef547d6cc3..5a1dcdca25 100644 --- a/app/javascript/alchemy_admin/components/uploader/progress.js +++ b/app/javascript/alchemy_admin/components/uploader/progress.js @@ -1,36 +1,41 @@ -import { AlchemyHTMLElement } from "alchemy_admin/components/alchemy_html_element" import { FileUpload } from "alchemy_admin/components/uploader/file_upload" import { formatFileSize } from "alchemy_admin/utils/format" import { translate } from "alchemy_admin/i18n" -export class Progress extends AlchemyHTMLElement { +const template = (buttonLabel, fileCount) => ` + +
    + + + + + +
    +
    +
    +` + +export class Progress extends HTMLElement { + // public — accessed by Uploader and tests + fileCount = 0 + + // private — backing state and internals + #fileUploads = [] + #buttonLabel = translate("Cancel all uploads") + #actionButton = null #visible = false + #handleFileChange = () => this.#updateView() - constructor() { - super() - this.buttonLabel = translate("Cancel all uploads") - this.fileUploads = [] - this.fileCount = 0 - this.className = "in-progress" + connectedCallback() { + this.innerHTML = template(this.#buttonLabel, this.fileCount) this.visible = true - this.handleFileChange = () => this._updateView() - } - /** - * Initialize the component with file uploads - * @param {FileUpload[]} fileUploads - */ - initialize(fileUploads = []) { - this.fileUploads = fileUploads - this.fileCount = fileUploads.length - } - - /** - * append file progress - components for each file - */ - afterRender() { - this.actionButton = this.querySelector("button") - this.actionButton.addEventListener("click", () => { + this.#actionButton = this.querySelector("button") + this.#actionButton.addEventListener("click", () => { if (this.finished) { this.onComplete(this.status) } else { @@ -38,34 +43,38 @@ export class Progress extends AlchemyHTMLElement { } }) - this.fileUploads.forEach((fileUpload) => { + this.#fileUploads.forEach((fileUpload) => { this.querySelector(".single-uploads").append(fileUpload) }) + + this.#updateView() + this.addEventListener("Alchemy.FileUpload.Change", this.#handleFileChange) } - /** - * cancel requests in all remaining uploads - */ - cancel() { - this._activeUploads().forEach((upload) => { - upload.cancel() - }) - this._setupCloseButton() + disconnectedCallback() { + this.removeEventListener( + "Alchemy.FileUpload.Change", + this.#handleFileChange + ) } /** - * update view and register change event + * Initialize the component with file uploads + * @param {FileUpload[]} fileUploads */ - connected() { - this._updateView() - this.addEventListener("Alchemy.FileUpload.Change", this.handleFileChange) + initialize(fileUploads = []) { + this.#fileUploads = fileUploads + this.fileCount = fileUploads.length } /** - * deregister file upload change - event + * cancel requests in all remaining uploads */ - disconnected() { - this.removeEventListener("Alchemy.FileUpload.Change", this.handleFileChange) + cancel() { + this.#activeUploads().forEach((upload) => { + upload.cancel() + }) + this.#setupCloseButton() } /** @@ -75,51 +84,29 @@ export class Progress extends AlchemyHTMLElement { */ onComplete(_status) {} - render() { - return ` - -
    - - - - - -
    -
    -
    - ` - } - /** * get all active upload components * @returns {FileUpload[]} - * @private */ - _activeUploads() { - return this.fileUploads.filter((upload) => upload.active) + #activeUploads() { + return this.#fileUploads.filter((upload) => upload.active) } /** * replace cancel button to be the close button - * @private */ - _setupCloseButton() { - this.buttonLabel = translate("Close") - this.actionButton.ariaLabel = this.buttonLabel - this.actionButton.parentElement.content = this.buttonLabel // update tooltip content + #setupCloseButton() { + this.#buttonLabel = translate("Close") + this.#actionButton.ariaLabel = this.#buttonLabel + this.#actionButton.parentElement.content = this.#buttonLabel // update tooltip content } /** * @param {string} field * @returns {number} - * @private */ - _sumFileProgresses(field) { - return this._activeUploads().reduce( + #sumFileProgresses(field) { + return this.#activeUploads().reduce( (accumulator, upload) => upload[field] + accumulator, 0 ) @@ -127,9 +114,8 @@ export class Progress extends AlchemyHTMLElement { /** * don't render the whole element new, because it would prevent selecting buttons - * @private */ - _updateView() { + #updateView() { const status = this.status this.className = status @@ -147,7 +133,7 @@ export class Progress extends AlchemyHTMLElement { this.overallUploadSize if (this.finished) { - this._setupCloseButton() + this.#setupCloseButton() this.onComplete(status) } else { this.visible = true @@ -158,34 +144,34 @@ export class Progress extends AlchemyHTMLElement { * @returns {boolean} */ get finished() { - return this._activeUploads().every((entry) => entry.finished) + return this.#activeUploads().every((entry) => entry.finished) } /** * @returns {string} */ get overallUploadSize() { - const uploadedFileCount = this._activeUploads().filter( + const uploadedFileCount = this.#activeUploads().filter( (fileProgress) => fileProgress.value >= 100 ).length const overallProgressValue = `${ this.totalProgress - }% (${uploadedFileCount} / ${this._activeUploads().length})` + }% (${uploadedFileCount} / ${this.#activeUploads().length})` return `${formatFileSize( - this._sumFileProgresses("progressEventLoaded") - )} / ${formatFileSize(this._sumFileProgresses("progressEventTotal"))}` + this.#sumFileProgresses("progressEventLoaded") + )} / ${formatFileSize(this.#sumFileProgresses("progressEventTotal"))}` } /** * @returns {string} */ get overallProgressValue() { - const uploadedFileCount = this._activeUploads().filter( + const uploadedFileCount = this.#activeUploads().filter( (fileProgress) => fileProgress.value >= 100 ).length return `${this.totalProgress}% (${uploadedFileCount} / ${ - this._activeUploads().length + this.#activeUploads().length })` } @@ -201,7 +187,7 @@ export class Progress extends AlchemyHTMLElement { * @returns {string} */ get status() { - const uploadsStatuses = this._activeUploads().map( + const uploadsStatuses = this.#activeUploads().map( (upload) => upload.className ) @@ -227,12 +213,12 @@ export class Progress extends AlchemyHTMLElement { * @returns {number} */ get totalProgress() { - const totalSize = this._activeUploads().reduce( + const totalSize = this.#activeUploads().reduce( (accumulator, upload) => accumulator + upload.file.size, 0 ) let totalProgress = Math.ceil( - this._activeUploads().reduce((accumulator, upload) => { + this.#activeUploads().reduce((accumulator, upload) => { const weight = upload.file.size / totalSize return upload.value * weight + accumulator }, 0) diff --git a/spec/javascript/alchemy_admin/components/alchemy_html_element.spec.js b/spec/javascript/alchemy_admin/components/alchemy_html_element.spec.js deleted file mode 100644 index 21c197c016..0000000000 --- a/spec/javascript/alchemy_admin/components/alchemy_html_element.spec.js +++ /dev/null @@ -1,175 +0,0 @@ -import { AlchemyHTMLElement } from "alchemy_admin/components/alchemy_html_element" -import { renderComponent } from "./component.helper" - -describe("AlchemyHTMLElement", () => { - let component = undefined - - const componentName = () => - `test-element-${Math.ceil(Math.random() * 10000000000000)}` - - describe("Render", () => { - /** - * create a new web component - * you can't recreate (or remove) a web component. So it has to be a new one with another name - * @param {string} content - * @param {string} initialContent - */ - const createComponent = (content = "", initialContent = "") => { - const name = componentName() - - customElements.define( - name, - class Test extends AlchemyHTMLElement { - render() { - return content - } - } - ) - component = renderComponent(name, `<${name}>${initialContent}`) - } - - it("should render only the component", () => { - createComponent() - expect(component).toBeInstanceOf(HTMLElement) - expect(component.innerHTML).toEqual("") - }) - - it("should render the content of the given render function", () => { - createComponent("Foo") - expect(component.innerHTML).toEqual("Foo") - }) - - it("should store the initial content", () => { - createComponent("", "Bar") - expect(component.initialContent).toEqual("Bar") - }) - - it("should render the initial content if no render function is given", () => { - customElements.define( - "test-initial-content", - class Test extends AlchemyHTMLElement {} - ) - component = renderComponent( - "test-initial-content", - `FooBar` - ) - expect(component.innerHTML).toEqual("FooBar") - }) - }) - - describe("Attributes", () => { - /** - * @param {string} name - */ - const createComponent = (name = componentName()) => { - customElements.define( - name, - class Test extends AlchemyHTMLElement { - static properties = { - size: { default: "medium" }, - color: { default: "currentColor" }, - longLongAttribute: { default: "foo" }, - booleanType: { default: false } - } - } - ) - component = renderComponent(name, `<${name}>`) - } - - it("should configure attributes and set default values", () => { - createComponent() - expect(component.size).toEqual("medium") - expect(component.color).toEqual("currentColor") - }) - - it("should be able to set attributes", () => { - createComponent("test-size") - component = renderComponent( - "test-size", - `` - ) - expect(component.size).toEqual("large") - }) - - it("should cast dashes to camelcase", () => { - createComponent("test-camelcase") - component = renderComponent( - "test-camelcase", - `` - ) - expect(component.longLongAttribute).toEqual("bar") - }) - - it("should support boolean types", () => { - createComponent("test-boolean") - component = renderComponent( - "test-boolean", - `` - ) - expect(component.booleanType).toBeTruthy() - - const second_component = renderComponent( - "test-boolean", - `` - ) - expect(second_component.booleanType).toBeFalsy() - }) - - it("should observe an attribute change", () => { - createComponent("test-color") - expect(component.color).toEqual("currentColor") - component.setAttribute("color", "pink") - expect(component.color).toEqual("pink") - }) - - it("should rerender after a attribute change", () => { - customElements.define( - "test-attribute-change", - class Test extends AlchemyHTMLElement { - static properties = { - foo: { default: "bar" } - } - - render() { - return `Test: ${this.foo}` - } - } - ) - component = renderComponent( - "test-attribute-change", - "" - ) - expect(component.innerHTML).toEqual("Test: foo") - component.setAttribute("foo", "fooBar") - expect(component.innerHTML).toEqual("Test: fooBar") - }) - }) - - describe("Options", () => { - class Test extends AlchemyHTMLElement { - static properties = { - test: { default: "foo" } - } - } - - beforeAll(() => { - customElements.define("test-options", Test) - component = renderComponent( - "test-options", - "" - ) - }) - - it("should have options", () => { - const newComponent = new Test({ test: "bar" }) - expect(newComponent.options).toEqual({ test: "bar" }) - }) - - it("should use the given option as default property", () => { - const newComponent = new Test({ test: "bar" }) - document.body.append(newComponent) // the new component should be append to a DOM node to run as a Web Component - expect(component.test).toEqual("foo") - expect(newComponent.test).toEqual("bar") - }) - }) -}) diff --git a/spec/javascript/alchemy_admin/components/uploader.spec.js b/spec/javascript/alchemy_admin/components/uploader.spec.js index 63ada6a123..739a1cc7f0 100644 --- a/spec/javascript/alchemy_admin/components/uploader.spec.js +++ b/spec/javascript/alchemy_admin/components/uploader.spec.js @@ -79,20 +79,20 @@ describe("alchemy-uploader", () => { describe("input field", () => { it("should call the upload function if the file input changes", () => { - component._uploadFiles = vi.fn() + component.uploadFiles = vi.fn() input.dispatchEvent(new CustomEvent("change")) - expect(component._uploadFiles).toHaveBeenCalledTimes(1) + expect(component.uploadFiles).toHaveBeenCalledTimes(1) }) }) - describe("_uploadFiles", () => { + describe("uploadFiles", () => { it("should upload files", () => { - component._uploadFiles([firstFile, secondFile]) + component.uploadFiles([firstFile, secondFile]) expect(XMLHttpRequest).toHaveBeenCalledTimes(2) }) it("should open the correct url", () => { - component._uploadFiles([firstFile]) + component.uploadFiles([firstFile]) const mockInstance = XMLHttpRequest.mock.results[0].value expect(mockInstance.open).toHaveBeenCalledWith( "POST", @@ -101,7 +101,7 @@ describe("alchemy-uploader", () => { }) it("should send the file form", () => { - component._uploadFiles([firstFile]) + component.uploadFiles([firstFile]) const mockInstance = XMLHttpRequest.mock.results[0].value expect(mockInstance.send).toHaveBeenCalledWith(new FormData(form)) }) @@ -111,7 +111,7 @@ describe("alchemy-uploader", () => { let progressBar = undefined beforeEach(() => { - component._uploadFiles([firstFile]) + component.uploadFiles([firstFile]) progressBar = document.querySelector( "alchemy-upload-progress sl-progress-bar" ) @@ -160,7 +160,7 @@ describe("alchemy-uploader", () => { describe("another upload", () => { it("should have only one progress - component", () => { - component._uploadFiles([firstFile]) + component.uploadFiles([firstFile]) expect( document.querySelectorAll("alchemy-upload-progress").length ).toEqual(1) @@ -169,7 +169,7 @@ describe("alchemy-uploader", () => { it("should cancel the previous process", () => { const uploadProgress = document.querySelector("alchemy-upload-progress") uploadProgress.cancel = vi.fn() - component._uploadFiles([firstFile]) + component.uploadFiles([firstFile]) expect(uploadProgress.cancel).toBeCalled() }) }) @@ -180,7 +180,7 @@ describe("alchemy-uploader", () => { beforeEach(() => { vi.clearAllMocks() // Clear mocks before this specific test Alchemy.uploader_defaults.upload_limit = 2 - component._uploadFiles([firstFile, secondFile, new File([], "foo")]) + component.uploadFiles([firstFile, secondFile, new File([], "foo")]) }) it("should upload only two files", () => { @@ -217,7 +217,7 @@ describe("alchemy-uploader", () => { beforeEach(() => { vi.clearAllMocks() // Clear mocks before this specific test Alchemy.uploader_defaults.allowed_filetypes.alchemy_attachments = ["txt"] - component._uploadFiles([ + component.uploadFiles([ new File([], "foo.pdf", { type: "application/pdf" }), firstFile, secondFile @@ -244,8 +244,8 @@ describe("alchemy-uploader", () => { describe("on complete", () => { beforeEach(() => { - component.dispatchCustomEvent = vi.fn() - component._uploadFiles([firstFile, secondFile]) + component.dispatchEvent = vi.fn() + component.uploadFiles([firstFile, secondFile]) }) describe("successful", () => { @@ -254,8 +254,8 @@ describe("alchemy-uploader", () => { }) it("should fire upload - event", () => { - expect(component.dispatchCustomEvent).toBeCalledWith( - "upload.successful" + expect(component.dispatchEvent).toBeCalledWith( + expect.objectContaining({ type: "Alchemy.upload.successful" }) ) }) }) @@ -266,7 +266,9 @@ describe("alchemy-uploader", () => { }) it("should fire upload - event", () => { - expect(component.dispatchCustomEvent).toBeCalledWith("upload.canceled") + expect(component.dispatchEvent).toBeCalledWith( + expect.objectContaining({ type: "Alchemy.upload.canceled" }) + ) }) }) @@ -276,7 +278,9 @@ describe("alchemy-uploader", () => { }) it("should fire upload - event", () => { - expect(component.dispatchCustomEvent).toBeCalledWith("upload.failed") + expect(component.dispatchEvent).toBeCalledWith( + expect.objectContaining({ type: "Alchemy.upload.failed" }) + ) }) it("should not hide the progress component", () => { @@ -285,10 +289,10 @@ describe("alchemy-uploader", () => { }) }) - describe("_handleUploadComplete", () => { + describe("uploadFiles", () => { beforeEach(() => { vi.useFakeTimers() - component._uploadFiles([firstFile]) + component.uploadFiles([firstFile]) }) afterEach(() => {