katalyst-content 3.0.0.alpha.6 → 3.0.0.beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/builds/katalyst/content.esm.js +3 -3
  3. data/app/assets/builds/katalyst/content.js +3 -3
  4. data/app/assets/builds/katalyst/content.min.js +1 -1
  5. data/app/assets/builds/katalyst/content.min.js.map +1 -1
  6. data/app/assets/stylesheets/katalyst/content/_editor.scss +1 -0
  7. data/app/assets/stylesheets/katalyst/content/editor/statuses.css +14 -0
  8. data/app/assets/stylesheets/katalyst/content/editor/table.css +66 -32
  9. data/app/assets/stylesheets/katalyst/content/editor.css +1 -0
  10. data/app/assets/stylesheets/katalyst/content/frontend/frontend.css +2 -8
  11. data/app/components/katalyst/content/editor/new_items_component.html.erb +4 -1
  12. data/app/controllers/katalyst/content/items_controller.rb +4 -19
  13. data/app/helpers/katalyst/content/editor_helper.rb +44 -0
  14. data/app/javascript/content/editor/item_editor_controller.js +1 -1
  15. data/app/javascript/content/editor/new_items_controller.js +1 -1
  16. data/app/javascript/content/editor/table_controller.js +1 -1
  17. data/app/models/concerns/katalyst/content/has_tree.rb +6 -0
  18. data/app/models/katalyst/content/item.rb +1 -4
  19. data/app/views/katalyst/content/asides/_aside.html+form.erb +4 -26
  20. data/app/views/katalyst/content/columns/_column.html+form.erb +3 -21
  21. data/app/views/katalyst/content/contents/_content.html+form.erb +4 -26
  22. data/app/views/katalyst/content/figures/_figure.html+form.erb +6 -30
  23. data/app/views/katalyst/content/groups/_group.html+form.erb +3 -21
  24. data/app/views/katalyst/content/items/_form.html.erb +1 -1
  25. data/app/views/katalyst/content/items/_form_errors.html.erb +3 -5
  26. data/app/views/katalyst/content/items/_hidden_fields.html.erb +2 -0
  27. data/app/views/katalyst/content/items/_item.html+form.erb +3 -21
  28. data/app/views/katalyst/content/items/edit.html.erb +2 -1
  29. data/app/views/katalyst/content/sections/_section.html+form.erb +3 -21
  30. data/app/views/katalyst/content/tables/_table.html+form.erb +32 -39
  31. data/db/migrate/20230515151440_change_katalyst_content_items_show_heading_column.rb +3 -1
  32. data/db/migrate/20250619122652_remove_not_null_on_katalyst_content_items_theme.rb +7 -0
  33. data/lib/katalyst/content/engine.rb +1 -0
  34. metadata +17 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4860ae9358b580469a78695e2aecae224bd99511a92f086bf320bc2ca36fd341
4
- data.tar.gz: 493dbcc76a0c79fc70ac124c4a2035d34279cda4b743f21e43cb15db93d3b38a
3
+ metadata.gz: 0a6a593e5e883d07a6b3753649fe21e6e7378d74e7eabaf5691fb6ffe7500cca
4
+ data.tar.gz: bbe4a5aeee835210c7887a148a6272b4bdc2a887fb7cbdb7af9e8d2a4111757f
5
5
  SHA512:
6
- metadata.gz: 9bd9b4d42b594c88f3b458dff4f917b14ebf76a17c99132ccdd10ed49ded5cb4becf716ff2d16602bcb27ea31b4efc3f2de3b2549761d36fafe53645d255db79
7
- data.tar.gz: 4a9557b7aaa00ddb16557ba9f32936ffbb94250957d1d284819050db6839d0cfe5f29963841f192178c7b4f1e170485b7257f21d393785adcc4cf1a044796fc2
6
+ metadata.gz: 7e855e1637f62e50aa4610d0f40ed647d80194a7829b9ee01fb026f79d278ae7981db0089578388bd08ee2dc2336e5ee03330c44dc4aecae987425c9caec63d0
7
+ data.tar.gz: aa17b123070e33cd11cc5364b353f8a307d0394685c72aa861f0e221d3a5ca5dc017b5dc991de29a161bcf0824880fff98d2ed8a47b9ebfaacc0c3180bf046df
@@ -747,7 +747,7 @@ class ItemEditorController extends Controller {
747
747
  this.element.removeEventListener("turbo:submit-end", this.onSubmit);
748
748
  }
749
749
 
750
- click(e) {
750
+ outside(e) {
751
751
  if (e.target.tagName === "DIALOG") this.dismiss();
752
752
  }
753
753
 
@@ -915,7 +915,7 @@ class NewItemsController extends Controller {
915
915
  delete this.currentItem;
916
916
  }
917
917
 
918
- click(e) {
918
+ outside(e) {
919
919
  if (e.target.tagName === "DIALOG") this.close(e);
920
920
  }
921
921
 
@@ -1075,7 +1075,7 @@ class StatusBarController extends Controller {
1075
1075
  }
1076
1076
 
1077
1077
  const EDITOR = `
1078
- <div class="content--editor--table-editor"
1078
+ <div class="content--editor--table-content"
1079
1079
  contenteditable="true"
1080
1080
  data-content--editor--table-target="content"
1081
1081
  data-action="paste->content--editor--table#paste"
@@ -747,7 +747,7 @@ class ItemEditorController extends Controller {
747
747
  this.element.removeEventListener("turbo:submit-end", this.onSubmit);
748
748
  }
749
749
 
750
- click(e) {
750
+ outside(e) {
751
751
  if (e.target.tagName === "DIALOG") this.dismiss();
752
752
  }
753
753
 
@@ -915,7 +915,7 @@ class NewItemsController extends Controller {
915
915
  delete this.currentItem;
916
916
  }
917
917
 
918
- click(e) {
918
+ outside(e) {
919
919
  if (e.target.tagName === "DIALOG") this.close(e);
920
920
  }
921
921
 
@@ -1075,7 +1075,7 @@ class StatusBarController extends Controller {
1075
1075
  }
1076
1076
 
1077
1077
  const EDITOR = `
1078
- <div class="content--editor--table-editor"
1078
+ <div class="content--editor--table-content"
1079
1079
  contenteditable="true"
1080
1080
  data-content--editor--table-target="content"
1081
1081
  data-action="paste->content--editor--table#paste"
@@ -1,2 +1,2 @@
1
- import{Controller as t}from"@hotwired/stimulus";import"trix";class e{static comparator(t,e){return t.index-e.index}constructor(t){this.node=t}get itemId(){return this.node.dataset.contentItemId}get#t(){return this.node.querySelector('input[name$="[id]"]')}set itemId(t){this.itemId!==t&&(this.node.dataset.contentItemId=`${t}`,this.#t.value=`${t}`)}get depth(){return parseInt(this.node.dataset.contentDepth)||0}get#e(){return this.node.querySelector('input[name$="[depth]"]')}set depth(t){this.depth!==t&&(this.node.dataset.contentDepth=`${t}`,this.#e.value=`${t}`)}get index(){return parseInt(this.node.dataset.contentIndex)}get#n(){return this.node.querySelector('input[name$="[index]"]')}set index(t){this.index!==t&&(this.node.dataset.contentIndex=`${t}`,this.#n.value=`${t}`)}get isLayout(){return this.node.hasAttribute("data-content-layout")}get previousItem(){let t=this.node.previousElementSibling;if(t)return new e(t)}get nextItem(){let t=this.node.nextElementSibling;if(t)return new e(t)}hasCollapsedDescendants(){let t=this.#i;return!!t&&t.children.length>0}hasExpandedDescendants(){let t=this.nextItem;return!!t&&t.depth>this.depth}traverse(t){const e=this.#s;t(this),this.#r(t),e.forEach((e=>e.#r(t)))}#r(t){this.hasCollapsedDescendants()&&this.#o.forEach((e=>{t(e),e.#r(t)}))}collapseChild(t){this.#i.appendChild(t.node)}collapse(){let t=this.#i;t||(t=function(t){const e=document.createElement("ol");return e.toggleAttribute("hidden",!0),e.dataset.contentChildren="",t.appendChild(e),e}(this.node)),this.#s.forEach((e=>t.appendChild(e.node)))}expand(){this.hasCollapsedDescendants()&&Array.from(this.#i.children).reverse().forEach((t=>{this.node.insertAdjacentElement("afterend",t)}))}toggleRule(t,e=!1){this.node.dataset.hasOwnProperty(t)&&!e&&delete this.node.dataset[t],!this.node.dataset.hasOwnProperty(t)&&e&&(this.node.dataset[t]=""),"denyDrag"===t&&(this.node.hasAttribute("draggable")||e||this.node.setAttribute("draggable","true"),this.node.hasAttribute("draggable")&&e&&this.node.removeAttribute("draggable"))}hasItemIdChanged(){return!(this.#t.value===this.itemId)}updateAfterChange(){this.itemId=this.#t.value,this.#n.value=this.index,this.#e.value=this.depth}get#i(){return this.node.querySelector(":scope > [data-content-children]")}get#s(){const t=[];let e=this.nextItem;for(;e&&e.depth>this.depth;)t.push(e),e=e.nextItem;return t}get#o(){return this.hasCollapsedDescendants()?Array.from(this.#i.children).map((t=>new e(t))):[]}}class n{constructor(t){this.node=t}get items(){return t=this.node.querySelectorAll("[data-content-index]"),Array.from(t).map((t=>new e(t)));var t}get state(){const t=this.node.querySelectorAll("li input[type=hidden]");return Array.from(t).map((t=>t.value)).join("/")}reindex(){this.items.map(((t,e)=>t.index=e))}reset(){this.items.sort(e.comparator).forEach((t=>{this.node.appendChild(t.node)}))}}class i{static rules=["denyDeNest","denyNest","denyCollapse","denyExpand","denyRemove","denyDrag","denyEdit"];constructor(t=!1){this.debug=t?(...t)=>console.log(...t):()=>{}}normalize(t){this.firstItemDepthZero(t),this.depthMustBeSet(t),this.itemCannotHaveInvalidDepth(t),this.parentMustBeLayout(t),this.parentCannotHaveExpandedAndCollapsedChildren(t)}update(t){this.rules={},this.parentsCannotDeNest(t),this.rootsCannotDeNest(t),this.onlyLastItemCanDeNest(t),this.nestingNeedsParent(t),this.leavesCannotCollapse(t),this.needHiddenItemsToExpand(t),this.parentsCannotBeDeleted(t),this.parentsCannotBeDragged(t),i.rules.forEach((e=>{t.toggleRule(e,!!this.rules[e])}))}firstItemDepthZero(t){0===t.index&&0!==t.depth&&(this.debug(`enforce depth on item ${t.index}: ${t.depth} => 0`),t.depth=0)}depthMustBeSet(t){(isNaN(t.depth)||t.depth<0)&&(this.debug(`unset depth on item ${t.index}: => 0`),t.depth=0)}itemCannotHaveInvalidDepth(t){const e=t.previousItem;e&&e.depth<t.depth-1&&(this.debug(`invalid depth on item ${t.index}: ${t.depth} => ${e.depth+1}`),t.depth=e.depth+1)}parentMustBeLayout(t){const e=t.previousItem;e&&e.depth<t.depth&&!e.isLayout&&(this.debug(`invalid parent for item ${t.index}: ${t.depth} => ${e.depth}`),t.depth=e.depth)}parentCannotHaveExpandedAndCollapsedChildren(t){t.hasCollapsedDescendants()&&t.hasExpandedDescendants()&&(this.debug(`expanding collapsed children of item ${t.index}`),t.expand())}parentsCannotDeNest(t){t.hasExpandedDescendants()&&this.#a("denyDeNest")}rootsCannotDeNest(t){0===t.depth&&this.#a("denyDeNest")}onlyLastItemCanDeNest(t){const e=t.nextItem;e&&e.depth===t.depth&&!t.isLayout&&this.#a("denyDeNest")}leavesCannotCollapse(t){t.hasExpandedDescendants()||this.#a("denyCollapse")}needHiddenItemsToExpand(t){t.hasCollapsedDescendants()||this.#a("denyExpand")}nestingNeedsParent(t){const e=t.previousItem;e?e.depth<t.depth?this.#a("denyNest"):e.depth!==t.depth||e.isLayout||this.#a("denyNest"):this.#a("denyNest")}parentsCannotBeDeleted(t){t.itemId&&!t.hasExpandedDescendants()||this.#a("denyRemove")}parentsCannotBeDragged(t){t.hasExpandedDescendants()&&this.#a("denyDrag")}#a(t){this.rules[t]=!0}}function s(t){return new e(t.target.closest("[data-content-item]"))}function r(t,e){if(!t)return;if(t===e)return;const n=t.compareDocumentPosition(e);n&Node.DOCUMENT_POSITION_FOLLOWING?t.insertAdjacentElement("beforebegin",e):n&Node.DOCUMENT_POSITION_PRECEDING&&t.insertAdjacentElement("afterend",e)}function o(t){return t&&t.closest("[data-controller='content--editor--list'] > *")}const a=window.Trix;a.config.blockAttributes.heading4={tagName:"h4",terminal:!0,breakOnReturn:!0,group:!1},delete a.config.blockAttributes.heading1;a.config.toolbar.getDefaultHTML=()=>{const{lang:t}=a.config;return`\n<div class="trix-button-row">\n <span class="trix-button-group trix-button-group--text-tools" data-trix-button-group="text-tools">\n <button type="button" class="trix-button trix-button--icon trix-button--icon-bold" data-trix-attribute="bold" data-trix-key="b" title="${t.bold}" tabindex="-1">${t.bold}</button>\n <button type="button" class="trix-button trix-button--icon trix-button--icon-italic" data-trix-attribute="italic" data-trix-key="i" title="${t.italic}" tabindex="-1">${t.italic}</button>\n <button type="button" class="trix-button trix-button--icon trix-button--icon-strike" data-trix-attribute="strike" title="${t.strike}" tabindex="-1">${t.strike}</button>\n <button type="button" class="trix-button trix-button--icon trix-button--icon-link" data-trix-attribute="href" data-trix-action="link" data-trix-key="k" title="${t.link}" tabindex="-1">${t.link}</button>\n </span>\n <span class="trix-button-group trix-button-group--block-tools" data-trix-button-group="block-tools">\n <button type="button" class="trix-button trix-button--icon trix-button--icon-heading-1" data-trix-attribute="heading4" title="${t.heading1}" tabindex="-1">${t.heading1}</button>\n <button type="button" class="trix-button trix-button--icon trix-button--icon-quote" data-trix-attribute="quote" title="${t.quote}" tabindex="-1">${t.quote}</button>\n <button type="button" class="trix-button trix-button--icon trix-button--icon-code" data-trix-attribute="code" title="${t.code}" tabindex="-1">${t.code}</button>\n <button type="button" class="trix-button trix-button--icon trix-button--icon-bullet-list" data-trix-attribute="bullet" title="${t.bullets}" tabindex="-1">${t.bullets}</button>\n <button type="button" class="trix-button trix-button--icon trix-button--icon-number-list" data-trix-attribute="number" title="${t.numbers}" tabindex="-1">${t.numbers}</button>\n <button type="button" class="trix-button trix-button--icon trix-button--icon-decrease-nesting-level" data-trix-action="decreaseNestingLevel" title="${t.outdent}" tabindex="-1">${t.outdent}</button>\n <button type="button" class="trix-button trix-button--icon trix-button--icon-increase-nesting-level" data-trix-action="increaseNestingLevel" title="${t.indent}" tabindex="-1">${t.indent}</button>\n </span>\n <span class="trix-button-group trix-button-group--file-tools" data-trix-button-group="file-tools">\n <button type="button" class="trix-button trix-button--icon trix-button--icon-attach" data-trix-action="attachFiles" title="${t.attachFiles}" tabindex="-1">${t.attachFiles}</button>\n </span>\n <span class="trix-button-group-spacer"></span>\n <span class="trix-button-group trix-button-group--history-tools" data-trix-button-group="history-tools">\n <button type="button" class="trix-button trix-button--icon trix-button--icon-undo" data-trix-action="undo" data-trix-key="z" title="${t.undo}" tabindex="-1">${t.undo}</button>\n <button type="button" class="trix-button trix-button--icon trix-button--icon-redo" data-trix-action="redo" data-trix-key="shift+z" title="${t.redo}" tabindex="-1">${t.redo}</button>\n </span>\n</div>\n<div class="trix-dialogs" data-trix-dialogs>\n <div class="trix-dialog trix-dialog--link" data-trix-dialog="href" data-trix-dialog-attribute="href">\n <div class="trix-dialog__link-fields">\n <input type="text" name="href" pattern="(https?|mailto:|tel:|/|#).*?" class="trix-input trix-input--dialog" placeholder="${t.urlPlaceholder}" aria-label="${t.url}" required data-trix-input>\n <div class="trix-button-group">\n <input type="button" class="trix-button trix-button--dialog" value="${t.link}" data-trix-method="setAttribute">\n <input type="button" class="trix-button trix-button--dialog" value="${t.unlink}" data-trix-method="removeAttribute">\n </div>\n </div>\n </div>\n</div>\n`},document.querySelectorAll("trix-toolbar").forEach((t=>{t.innerHTML=a.config.toolbar.getDefaultHTML()}));const d=[{identifier:"content--editor--container",controllerConstructor:class extends t{static targets=["container"];connect(){this.state=this.container.state,this.reindex()}get container(){return new n(this.containerTarget)}reindex(){this.container.reindex(),this.#d()}reset(){this.container.reset()}drop(t){this.container.reindex();const e=s(t),n=e.previousItem;let i=0;i=void 0===n?-e.depth:n.isLayout&&e.nextItem&&e.nextItem.depth>n.depth?n.depth-e.depth+1:n.depth-e.depth,e.traverse((t=>{t.depth+=i})),this.#d(),t.preventDefault()}remove(t){s(t).node.remove(),this.#d(),t.preventDefault()}nest(t){s(t).traverse((t=>{t.depth+=1})),this.#d(),t.preventDefault()}deNest(t){s(t).traverse((t=>{t.depth-=1})),this.#d(),t.preventDefault()}collapse(t){s(t).collapse(),this.#d(),t.preventDefault()}expand(t){s(t).expand(),this.#d(),t.preventDefault()}#d(){this.updateRequested=!0,setTimeout((()=>{if(!this.updateRequested)return;this.updateRequested=!1;const t=new i;this.container.items.forEach((e=>t.normalize(e))),this.container.items.forEach((e=>t.update(e))),this.#l()}),0)}#l(){this.dispatch("change",{bubbles:!0,prefix:"content",detail:{dirty:this.#u()}})}#u(){return this.container.state!==this.state}}},{identifier:"content--editor--item",controllerConstructor:class extends t{get item(){return new e(this.li)}get ol(){return this.element.closest("ol")}get li(){return this.element.closest("li")}connect(){this.element.dataset.hasOwnProperty("delete")?this.remove():this.item.index>=0?this.item.hasItemIdChanged()&&(this.item.updateAfterChange(),this.reindex()):this.reindex()}remove(){this.ol,this.li.remove(),this.reindex()}reindex(){this.dispatch("reindex",{bubbles:!0,prefix:"content"})}}},{identifier:"content--editor--item-editor",controllerConstructor:class extends t{static targets=["dialog"];connect(){this.element.addEventListener("turbo:submit-end",this.onSubmit)}disconnect(){this.element.removeEventListener("turbo:submit-end",this.onSubmit)}click(t){"DIALOG"===t.target.tagName&&this.dismiss()}dismiss(){this.dialogTarget&&(this.dialogTarget.open||this.dialogTarget.close(),"itemPersisted"in this.dialogTarget.dataset||this.#h(),this.element.removeAttribute("src"),this.dialogTarget.remove())}dialogTargetConnected(t){t.showModal()}onSubmit=t=>{t.detail.success&&"closeDialog"in t.detail.formSubmission?.submitter?.dataset&&(this.dialogTarget.close(),this.element.removeAttribute("src"),this.dialogTarget.remove())};#h(){const t=document.getElementById(this.dialogTarget.dataset.itemId),n=new e(t.closest("[data-content-item]")),i=n.node.parentElement;n.node.remove(),this.dispatch("reindex",{target:i,bubbles:!0,prefix:"content"})}}},{identifier:"content--editor--list",controllerConstructor:class extends t{dragstart(t){if(this.element!==t.target.parentElement)return;const e=t.target;t.dataTransfer.effectAllowed="move",requestAnimationFrame((()=>e.dataset.dragging=""))}dragover(t){const e=this.dragItem;if(e)return r(o(t.target),e),t.preventDefault(),!0}drop(t){let e=this.dragItem;e&&(t.preventDefault(),delete e.dataset.dragging,r(o(t.target),e),this.dispatch("drop",{target:e,bubbles:!0,prefix:"content"}))}dragend(){const t=this.dragItem;t&&(delete t.dataset.dragging,this.reset())}get isDragging(){return!!this.dragItem}get dragItem(){return this.element.querySelector("[data-dragging]")}reindex(){this.dispatch("reindex",{bubbles:!0,prefix:"content"})}reset(){this.dispatch("reset",{bubbles:!0,prefix:"content"})}}},{identifier:"content--editor--new-items",controllerConstructor:class extends t{static targets=["inline"];connect(){this.form.addEventListener("mousemove",this.move)}disconnect(){this.form?.removeEventListener("mousemove",this.move),delete this.currentItem}click(t){"DIALOG"===t.target.tagName&&this.close(t)}open(t){t.preventDefault(),this.dialog.showModal()}close(t){t.preventDefault(),this.dialog.close()}add(t){t.preventDefault();const n=t.target.closest("li").querySelector("template").content.querySelector("li").cloneNode(!0),i=this.currentItem;i?(i.insertAdjacentElement("beforebegin",n),new e(n).depth=new e(i).depth):this.list.insertAdjacentElement("beforeend",n),this.toggleInline(!1),this.dialog.close(),requestAnimationFrame((()=>{n.querySelector('[value="edit"]').click()}))}morph(t){t.preventDefault(),this.dialog.close()}move=t=>{if(this.isOverInlineTarget(t))return;if(this.dialog.open)return;const e=this.getCurrentItem(t);this.currentItem!==e&&(this.currentItem&&this.toggleInline(!1),this.currentItem=e,this.timer&&clearTimeout(this.timer),this.timer=setTimeout((()=>{delete this.timer,this.toggleInline()}),100))};toggleInline(t=!!this.currentItem){t?(this.inlineTarget.style.top=`${this.currentItem.offsetTop}px`,this.inlineTarget.toggleAttribute("hidden",!1)):this.inlineTarget.toggleAttribute("hidden",!0)}get dialog(){return this.element.querySelector("dialog")}get form(){return this.element.closest("form")}get list(){return this.form.querySelector('[data-controller="content--editor--list"]')}getCurrentItem(t){const e=document.elementFromPoint(t.clientX,t.clientY).closest("li");if(!e)return null;const n=e.getBoundingClientRect();return t.clientX<n.left+n.width/2-48||t.clientX>n.left+n.width/2+48?null:t.clientY-n.y<=24?e:n.y+n.height-t.clientY<=24?e.nextElementSibling:null}isOverInlineTarget(t){return this.inlineTarget===document.elementFromPoint(t.clientX,t.clientY).closest("div")}}},{identifier:"content--editor--status-bar",controllerConstructor:class extends t{connect(){this.versionState=this.element.dataset.state}morph(t){t.target===this.element&&(this.versionState=this.element.dataset.state,this.update({dirty:!1}))}change(t){t.detail&&t.detail.hasOwnProperty("dirty")&&this.update(t.detail)}update({dirty:t}){this.element.dataset.state=t?"dirty":this.versionState}}},{identifier:"content--editor--table",controllerConstructor:class extends t{static targets=["input","update"];constructor(t){super(t),this.observer=new MutationObserver(this.change)}connect(){const t=document.createElement("TEMPLATE");t.innerHTML='\n<div class="content--editor--table-editor"\n contenteditable="true"\n data-content--editor--table-target="content"\n data-action="paste->content--editor--table#paste"\n id="item-content-field">\n</div>',this.content=t.content.firstElementChild,this.content.innerHTML=this.inputTarget.value,this.content.className+=` ${this.inputTarget.className}`,this.inputTarget.insertAdjacentElement("beforebegin",this.content),this.inputTarget.hidden=!0,this.observer.observe(this.content,{attributes:!0,childList:!0,characterData:!0,subtree:!0})}disconnect(){this.observer.disconnect(),this.content.remove(),delete this.content}change=t=>{this.inputTarget.value=this.table?.outerHTML};update=()=>{this.updateTarget.click()};paste=t=>{-1!==t.clipboardData.getData("text/html").indexOf("<table")&&(t.preventDefault(),this.inputTarget.value=t.clipboardData.getData("text/html"),this.update())};get table(){return this.content.querySelector("table")}}},{identifier:"content--editor--trix",controllerConstructor:class extends t{trixInitialize(t){this.element.addEventListener("turbo:before-morph-attribute",this.suppressMorph),this.element.addEventListener("turbo:before-morph-element",this.suppressMorph),this.element.toolbarElement&&(this.element.toolbarElement.addEventListener("turbo:before-morph-attribute",this.suppressMorph),this.element.toolbarElement.addEventListener("turbo:before-morph-element",this.suppressMorph))}disconnect(){this.element.removeEventListener("turbo:before-morph-attribute",this.suppressMorph),this.element.removeEventListener("turbo:before-morph-element",this.suppressMorph),this.element.toolbarElement&&(this.element.toolbarElement.removeEventListener("turbo:before-morph-attribute",this.suppressMorph),this.element.toolbarElement.removeEventListener("turbo:before-morph-element",this.suppressMorph))}suppressMorph=t=>{"TRIX-EDITOR"!==t.target.tagName&&"TRIX-TOOLBAR"!==t.target.tagName||t.preventDefault()}}}];export{d as default};
1
+ import{Controller as t}from"@hotwired/stimulus";import"trix";class e{static comparator(t,e){return t.index-e.index}constructor(t){this.node=t}get itemId(){return this.node.dataset.contentItemId}get#t(){return this.node.querySelector('input[name$="[id]"]')}set itemId(t){this.itemId!==t&&(this.node.dataset.contentItemId=`${t}`,this.#t.value=`${t}`)}get depth(){return parseInt(this.node.dataset.contentDepth)||0}get#e(){return this.node.querySelector('input[name$="[depth]"]')}set depth(t){this.depth!==t&&(this.node.dataset.contentDepth=`${t}`,this.#e.value=`${t}`)}get index(){return parseInt(this.node.dataset.contentIndex)}get#n(){return this.node.querySelector('input[name$="[index]"]')}set index(t){this.index!==t&&(this.node.dataset.contentIndex=`${t}`,this.#n.value=`${t}`)}get isLayout(){return this.node.hasAttribute("data-content-layout")}get previousItem(){let t=this.node.previousElementSibling;if(t)return new e(t)}get nextItem(){let t=this.node.nextElementSibling;if(t)return new e(t)}hasCollapsedDescendants(){let t=this.#i;return!!t&&t.children.length>0}hasExpandedDescendants(){let t=this.nextItem;return!!t&&t.depth>this.depth}traverse(t){const e=this.#s;t(this),this.#r(t),e.forEach(e=>e.#r(t))}#r(t){this.hasCollapsedDescendants()&&this.#o.forEach(e=>{t(e),e.#r(t)})}collapseChild(t){this.#i.appendChild(t.node)}collapse(){let t=this.#i;t||(t=function(t){const e=document.createElement("ol");return e.toggleAttribute("hidden",!0),e.dataset.contentChildren="",t.appendChild(e),e}(this.node)),this.#s.forEach(e=>t.appendChild(e.node))}expand(){this.hasCollapsedDescendants()&&Array.from(this.#i.children).reverse().forEach(t=>{this.node.insertAdjacentElement("afterend",t)})}toggleRule(t,e=!1){this.node.dataset.hasOwnProperty(t)&&!e&&delete this.node.dataset[t],!this.node.dataset.hasOwnProperty(t)&&e&&(this.node.dataset[t]=""),"denyDrag"===t&&(this.node.hasAttribute("draggable")||e||this.node.setAttribute("draggable","true"),this.node.hasAttribute("draggable")&&e&&this.node.removeAttribute("draggable"))}hasItemIdChanged(){return!(this.#t.value===this.itemId)}updateAfterChange(){this.itemId=this.#t.value,this.#n.value=this.index,this.#e.value=this.depth}get#i(){return this.node.querySelector(":scope > [data-content-children]")}get#s(){const t=[];let e=this.nextItem;for(;e&&e.depth>this.depth;)t.push(e),e=e.nextItem;return t}get#o(){return this.hasCollapsedDescendants()?Array.from(this.#i.children).map(t=>new e(t)):[]}}class n{constructor(t){this.node=t}get items(){return t=this.node.querySelectorAll("[data-content-index]"),Array.from(t).map(t=>new e(t));var t}get state(){const t=this.node.querySelectorAll("li input[type=hidden]");return Array.from(t).map(t=>t.value).join("/")}reindex(){this.items.map((t,e)=>t.index=e)}reset(){this.items.sort(e.comparator).forEach(t=>{this.node.appendChild(t.node)})}}class i{static rules=["denyDeNest","denyNest","denyCollapse","denyExpand","denyRemove","denyDrag","denyEdit"];constructor(t=!1){this.debug=t?(...t)=>console.log(...t):()=>{}}normalize(t){this.firstItemDepthZero(t),this.depthMustBeSet(t),this.itemCannotHaveInvalidDepth(t),this.parentMustBeLayout(t),this.parentCannotHaveExpandedAndCollapsedChildren(t)}update(t){this.rules={},this.parentsCannotDeNest(t),this.rootsCannotDeNest(t),this.onlyLastItemCanDeNest(t),this.nestingNeedsParent(t),this.leavesCannotCollapse(t),this.needHiddenItemsToExpand(t),this.parentsCannotBeDeleted(t),this.parentsCannotBeDragged(t),i.rules.forEach(e=>{t.toggleRule(e,!!this.rules[e])})}firstItemDepthZero(t){0===t.index&&0!==t.depth&&(this.debug(`enforce depth on item ${t.index}: ${t.depth} => 0`),t.depth=0)}depthMustBeSet(t){(isNaN(t.depth)||t.depth<0)&&(this.debug(`unset depth on item ${t.index}: => 0`),t.depth=0)}itemCannotHaveInvalidDepth(t){const e=t.previousItem;e&&e.depth<t.depth-1&&(this.debug(`invalid depth on item ${t.index}: ${t.depth} => ${e.depth+1}`),t.depth=e.depth+1)}parentMustBeLayout(t){const e=t.previousItem;e&&e.depth<t.depth&&!e.isLayout&&(this.debug(`invalid parent for item ${t.index}: ${t.depth} => ${e.depth}`),t.depth=e.depth)}parentCannotHaveExpandedAndCollapsedChildren(t){t.hasCollapsedDescendants()&&t.hasExpandedDescendants()&&(this.debug(`expanding collapsed children of item ${t.index}`),t.expand())}parentsCannotDeNest(t){t.hasExpandedDescendants()&&this.#a("denyDeNest")}rootsCannotDeNest(t){0===t.depth&&this.#a("denyDeNest")}onlyLastItemCanDeNest(t){const e=t.nextItem;e&&e.depth===t.depth&&!t.isLayout&&this.#a("denyDeNest")}leavesCannotCollapse(t){t.hasExpandedDescendants()||this.#a("denyCollapse")}needHiddenItemsToExpand(t){t.hasCollapsedDescendants()||this.#a("denyExpand")}nestingNeedsParent(t){const e=t.previousItem;e?e.depth<t.depth?this.#a("denyNest"):e.depth!==t.depth||e.isLayout||this.#a("denyNest"):this.#a("denyNest")}parentsCannotBeDeleted(t){t.itemId&&!t.hasExpandedDescendants()||this.#a("denyRemove")}parentsCannotBeDragged(t){t.hasExpandedDescendants()&&this.#a("denyDrag")}#a(t){this.rules[t]=!0}}function s(t){return new e(t.target.closest("[data-content-item]"))}function r(t,e){if(!t)return;if(t===e)return;const n=t.compareDocumentPosition(e);n&Node.DOCUMENT_POSITION_FOLLOWING?t.insertAdjacentElement("beforebegin",e):n&Node.DOCUMENT_POSITION_PRECEDING&&t.insertAdjacentElement("afterend",e)}function o(t){return t&&t.closest("[data-controller='content--editor--list'] > *")}const a=window.Trix;a.config.blockAttributes.heading4={tagName:"h4",terminal:!0,breakOnReturn:!0,group:!1},delete a.config.blockAttributes.heading1;a.config.toolbar.getDefaultHTML=()=>{const{lang:t}=a.config;return`\n<div class="trix-button-row">\n <span class="trix-button-group trix-button-group--text-tools" data-trix-button-group="text-tools">\n <button type="button" class="trix-button trix-button--icon trix-button--icon-bold" data-trix-attribute="bold" data-trix-key="b" title="${t.bold}" tabindex="-1">${t.bold}</button>\n <button type="button" class="trix-button trix-button--icon trix-button--icon-italic" data-trix-attribute="italic" data-trix-key="i" title="${t.italic}" tabindex="-1">${t.italic}</button>\n <button type="button" class="trix-button trix-button--icon trix-button--icon-strike" data-trix-attribute="strike" title="${t.strike}" tabindex="-1">${t.strike}</button>\n <button type="button" class="trix-button trix-button--icon trix-button--icon-link" data-trix-attribute="href" data-trix-action="link" data-trix-key="k" title="${t.link}" tabindex="-1">${t.link}</button>\n </span>\n <span class="trix-button-group trix-button-group--block-tools" data-trix-button-group="block-tools">\n <button type="button" class="trix-button trix-button--icon trix-button--icon-heading-1" data-trix-attribute="heading4" title="${t.heading1}" tabindex="-1">${t.heading1}</button>\n <button type="button" class="trix-button trix-button--icon trix-button--icon-quote" data-trix-attribute="quote" title="${t.quote}" tabindex="-1">${t.quote}</button>\n <button type="button" class="trix-button trix-button--icon trix-button--icon-code" data-trix-attribute="code" title="${t.code}" tabindex="-1">${t.code}</button>\n <button type="button" class="trix-button trix-button--icon trix-button--icon-bullet-list" data-trix-attribute="bullet" title="${t.bullets}" tabindex="-1">${t.bullets}</button>\n <button type="button" class="trix-button trix-button--icon trix-button--icon-number-list" data-trix-attribute="number" title="${t.numbers}" tabindex="-1">${t.numbers}</button>\n <button type="button" class="trix-button trix-button--icon trix-button--icon-decrease-nesting-level" data-trix-action="decreaseNestingLevel" title="${t.outdent}" tabindex="-1">${t.outdent}</button>\n <button type="button" class="trix-button trix-button--icon trix-button--icon-increase-nesting-level" data-trix-action="increaseNestingLevel" title="${t.indent}" tabindex="-1">${t.indent}</button>\n </span>\n <span class="trix-button-group trix-button-group--file-tools" data-trix-button-group="file-tools">\n <button type="button" class="trix-button trix-button--icon trix-button--icon-attach" data-trix-action="attachFiles" title="${t.attachFiles}" tabindex="-1">${t.attachFiles}</button>\n </span>\n <span class="trix-button-group-spacer"></span>\n <span class="trix-button-group trix-button-group--history-tools" data-trix-button-group="history-tools">\n <button type="button" class="trix-button trix-button--icon trix-button--icon-undo" data-trix-action="undo" data-trix-key="z" title="${t.undo}" tabindex="-1">${t.undo}</button>\n <button type="button" class="trix-button trix-button--icon trix-button--icon-redo" data-trix-action="redo" data-trix-key="shift+z" title="${t.redo}" tabindex="-1">${t.redo}</button>\n </span>\n</div>\n<div class="trix-dialogs" data-trix-dialogs>\n <div class="trix-dialog trix-dialog--link" data-trix-dialog="href" data-trix-dialog-attribute="href">\n <div class="trix-dialog__link-fields">\n <input type="text" name="href" pattern="(https?|mailto:|tel:|/|#).*?" class="trix-input trix-input--dialog" placeholder="${t.urlPlaceholder}" aria-label="${t.url}" required data-trix-input>\n <div class="trix-button-group">\n <input type="button" class="trix-button trix-button--dialog" value="${t.link}" data-trix-method="setAttribute">\n <input type="button" class="trix-button trix-button--dialog" value="${t.unlink}" data-trix-method="removeAttribute">\n </div>\n </div>\n </div>\n</div>\n`},document.querySelectorAll("trix-toolbar").forEach(t=>{t.innerHTML=a.config.toolbar.getDefaultHTML()});const d=[{identifier:"content--editor--container",controllerConstructor:class extends t{static targets=["container"];connect(){this.state=this.container.state,this.reindex()}get container(){return new n(this.containerTarget)}reindex(){this.container.reindex(),this.#d()}reset(){this.container.reset()}drop(t){this.container.reindex();const e=s(t),n=e.previousItem;let i=0;i=void 0===n?-e.depth:n.isLayout&&e.nextItem&&e.nextItem.depth>n.depth?n.depth-e.depth+1:n.depth-e.depth,e.traverse(t=>{t.depth+=i}),this.#d(),t.preventDefault()}remove(t){s(t).node.remove(),this.#d(),t.preventDefault()}nest(t){s(t).traverse(t=>{t.depth+=1}),this.#d(),t.preventDefault()}deNest(t){s(t).traverse(t=>{t.depth-=1}),this.#d(),t.preventDefault()}collapse(t){s(t).collapse(),this.#d(),t.preventDefault()}expand(t){s(t).expand(),this.#d(),t.preventDefault()}#d(){this.updateRequested=!0,setTimeout(()=>{if(!this.updateRequested)return;this.updateRequested=!1;const t=new i;this.container.items.forEach(e=>t.normalize(e)),this.container.items.forEach(e=>t.update(e)),this.#l()},0)}#l(){this.dispatch("change",{bubbles:!0,prefix:"content",detail:{dirty:this.#u()}})}#u(){return this.container.state!==this.state}}},{identifier:"content--editor--item",controllerConstructor:class extends t{get item(){return new e(this.li)}get ol(){return this.element.closest("ol")}get li(){return this.element.closest("li")}connect(){this.element.dataset.hasOwnProperty("delete")?this.remove():this.item.index>=0?this.item.hasItemIdChanged()&&(this.item.updateAfterChange(),this.reindex()):this.reindex()}remove(){this.ol,this.li.remove(),this.reindex()}reindex(){this.dispatch("reindex",{bubbles:!0,prefix:"content"})}}},{identifier:"content--editor--item-editor",controllerConstructor:class extends t{static targets=["dialog"];connect(){this.element.addEventListener("turbo:submit-end",this.onSubmit)}disconnect(){this.element.removeEventListener("turbo:submit-end",this.onSubmit)}outside(t){"DIALOG"===t.target.tagName&&this.dismiss()}dismiss(){this.dialogTarget&&(this.dialogTarget.open||this.dialogTarget.close(),"itemPersisted"in this.dialogTarget.dataset||this.#h(),this.element.removeAttribute("src"),this.dialogTarget.remove())}dialogTargetConnected(t){t.showModal()}onSubmit=t=>{t.detail.success&&"closeDialog"in t.detail.formSubmission?.submitter?.dataset&&(this.dialogTarget.close(),this.element.removeAttribute("src"),this.dialogTarget.remove())};#h(){const t=document.getElementById(this.dialogTarget.dataset.itemId),n=new e(t.closest("[data-content-item]")),i=n.node.parentElement;n.node.remove(),this.dispatch("reindex",{target:i,bubbles:!0,prefix:"content"})}}},{identifier:"content--editor--list",controllerConstructor:class extends t{dragstart(t){if(this.element!==t.target.parentElement)return;const e=t.target;t.dataTransfer.effectAllowed="move",requestAnimationFrame(()=>e.dataset.dragging="")}dragover(t){const e=this.dragItem;if(e)return r(o(t.target),e),t.preventDefault(),!0}drop(t){let e=this.dragItem;e&&(t.preventDefault(),delete e.dataset.dragging,r(o(t.target),e),this.dispatch("drop",{target:e,bubbles:!0,prefix:"content"}))}dragend(){const t=this.dragItem;t&&(delete t.dataset.dragging,this.reset())}get isDragging(){return!!this.dragItem}get dragItem(){return this.element.querySelector("[data-dragging]")}reindex(){this.dispatch("reindex",{bubbles:!0,prefix:"content"})}reset(){this.dispatch("reset",{bubbles:!0,prefix:"content"})}}},{identifier:"content--editor--new-items",controllerConstructor:class extends t{static targets=["inline"];connect(){this.form.addEventListener("mousemove",this.move)}disconnect(){this.form?.removeEventListener("mousemove",this.move),delete this.currentItem}outside(t){"DIALOG"===t.target.tagName&&this.close(t)}open(t){t.preventDefault(),this.dialog.showModal()}close(t){t.preventDefault(),this.dialog.close()}add(t){t.preventDefault();const n=t.target.closest("li").querySelector("template").content.querySelector("li").cloneNode(!0),i=this.currentItem;i?(i.insertAdjacentElement("beforebegin",n),new e(n).depth=new e(i).depth):this.list.insertAdjacentElement("beforeend",n),this.toggleInline(!1),this.dialog.close(),requestAnimationFrame(()=>{n.querySelector('[value="edit"]').click()})}morph(t){t.preventDefault(),this.dialog.close()}move=t=>{if(this.isOverInlineTarget(t))return;if(this.dialog.open)return;const e=this.getCurrentItem(t);this.currentItem!==e&&(this.currentItem&&this.toggleInline(!1),this.currentItem=e,this.timer&&clearTimeout(this.timer),this.timer=setTimeout(()=>{delete this.timer,this.toggleInline()},100))};toggleInline(t=!!this.currentItem){t?(this.inlineTarget.style.top=`${this.currentItem.offsetTop}px`,this.inlineTarget.toggleAttribute("hidden",!1)):this.inlineTarget.toggleAttribute("hidden",!0)}get dialog(){return this.element.querySelector("dialog")}get form(){return this.element.closest("form")}get list(){return this.form.querySelector('[data-controller="content--editor--list"]')}getCurrentItem(t){const e=document.elementFromPoint(t.clientX,t.clientY).closest("li");if(!e)return null;const n=e.getBoundingClientRect();return t.clientX<n.left+n.width/2-48||t.clientX>n.left+n.width/2+48?null:t.clientY-n.y<=24?e:n.y+n.height-t.clientY<=24?e.nextElementSibling:null}isOverInlineTarget(t){return this.inlineTarget===document.elementFromPoint(t.clientX,t.clientY).closest("div")}}},{identifier:"content--editor--status-bar",controllerConstructor:class extends t{connect(){this.versionState=this.element.dataset.state}morph(t){t.target===this.element&&(this.versionState=this.element.dataset.state,this.update({dirty:!1}))}change(t){t.detail&&t.detail.hasOwnProperty("dirty")&&this.update(t.detail)}update({dirty:t}){this.element.dataset.state=t?"dirty":this.versionState}}},{identifier:"content--editor--table",controllerConstructor:class extends t{static targets=["input","update"];constructor(t){super(t),this.observer=new MutationObserver(this.change)}connect(){const t=document.createElement("TEMPLATE");t.innerHTML='\n<div class="content--editor--table-content"\n contenteditable="true"\n data-content--editor--table-target="content"\n data-action="paste->content--editor--table#paste"\n id="item-content-field">\n</div>',this.content=t.content.firstElementChild,this.content.innerHTML=this.inputTarget.value,this.content.className+=` ${this.inputTarget.className}`,this.inputTarget.insertAdjacentElement("beforebegin",this.content),this.inputTarget.hidden=!0,this.observer.observe(this.content,{attributes:!0,childList:!0,characterData:!0,subtree:!0})}disconnect(){this.observer.disconnect(),this.content.remove(),delete this.content}change=t=>{this.inputTarget.value=this.table?.outerHTML};update=()=>{this.updateTarget.click()};paste=t=>{-1!==t.clipboardData.getData("text/html").indexOf("<table")&&(t.preventDefault(),this.inputTarget.value=t.clipboardData.getData("text/html"),this.update())};get table(){return this.content.querySelector("table")}}},{identifier:"content--editor--trix",controllerConstructor:class extends t{trixInitialize(t){this.element.addEventListener("turbo:before-morph-attribute",this.suppressMorph),this.element.addEventListener("turbo:before-morph-element",this.suppressMorph),this.element.toolbarElement&&(this.element.toolbarElement.addEventListener("turbo:before-morph-attribute",this.suppressMorph),this.element.toolbarElement.addEventListener("turbo:before-morph-element",this.suppressMorph))}disconnect(){this.element.removeEventListener("turbo:before-morph-attribute",this.suppressMorph),this.element.removeEventListener("turbo:before-morph-element",this.suppressMorph),this.element.toolbarElement&&(this.element.toolbarElement.removeEventListener("turbo:before-morph-attribute",this.suppressMorph),this.element.toolbarElement.removeEventListener("turbo:before-morph-element",this.suppressMorph))}suppressMorph=t=>{"TRIX-EDITOR"!==t.target.tagName&&"TRIX-TOOLBAR"!==t.target.tagName||t.preventDefault()}}}];export{d as default};
2
2
  //# sourceMappingURL=content.min.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"content.min.js","sources":["../../../javascript/content/editor/item.js","../../../javascript/content/editor/container.js","../../../javascript/content/editor/rules_engine.js","../../../javascript/content/editor/container_controller.js","../../../javascript/content/editor/list_controller.js","../../../javascript/content/editor/trix_controller.js","../../../javascript/content/application.js","../../../javascript/content/editor/item_controller.js","../../../javascript/content/editor/item_editor_controller.js","../../../javascript/content/editor/new_items_controller.js","../../../javascript/content/editor/status_bar_controller.js","../../../javascript/content/editor/table_controller.js"],"sourcesContent":["export default class Item {\n /**\n * Sort items by their index.\n *\n * @param a {Item}\n * @param b {Item}\n * @returns {number}\n */\n static comparator(a, b) {\n return a.index - b.index;\n }\n\n /**\n * @param node {Element} li[data-content-index]\n */\n constructor(node) {\n this.node = node;\n }\n\n /**\n * @returns {String} id of the node's item (from data attributes)\n */\n get itemId() {\n return this.node.dataset[`contentItemId`];\n }\n\n get #itemIdInput() {\n return this.node.querySelector(`input[name$=\"[id]\"]`);\n }\n\n /**\n * @param itemId {String} id\n */\n set itemId(id) {\n if (this.itemId === id) return;\n\n this.node.dataset[`contentItemId`] = `${id}`;\n this.#itemIdInput.value = `${id}`;\n }\n\n /**\n * @returns {number} logical nesting depth of node in container\n */\n get depth() {\n return parseInt(this.node.dataset[`contentDepth`]) || 0;\n }\n\n get #depthInput() {\n return this.node.querySelector(`input[name$=\"[depth]\"]`);\n }\n\n /**\n * @param depth {number} depth >= 0\n */\n set depth(depth) {\n if (this.depth === depth) return;\n\n this.node.dataset[`contentDepth`] = `${depth}`;\n this.#depthInput.value = `${depth}`;\n }\n\n /**\n * @returns {number} logical index of node in container (pre-order traversal)\n */\n get index() {\n return parseInt(this.node.dataset[`contentIndex`]);\n }\n\n get #indexInput() {\n return this.node.querySelector(`input[name$=\"[index]\"]`);\n }\n\n /**\n * @param index {number} index >= 0\n */\n set index(index) {\n if (this.index === index) return;\n\n this.node.dataset[`contentIndex`] = `${index}`;\n this.#indexInput.value = `${index}`;\n }\n\n /**\n * @returns {boolean} true if this item can have children\n */\n get isLayout() {\n return this.node.hasAttribute(\"data-content-layout\");\n }\n\n /**\n * @returns {Item} nearest neighbour (index - 1)\n */\n get previousItem() {\n let sibling = this.node.previousElementSibling;\n if (sibling) return new Item(sibling);\n }\n\n /**\n * @returns {Item} nearest neighbour (index + 1)\n */\n get nextItem() {\n let sibling = this.node.nextElementSibling;\n if (sibling) return new Item(sibling);\n }\n\n /**\n * @returns {boolean} true if this item has any collapsed children\n */\n hasCollapsedDescendants() {\n let childrenList = this.#childrenListElement;\n return !!childrenList && childrenList.children.length > 0;\n }\n\n /**\n * @returns {boolean} true if this item has any expanded children\n */\n hasExpandedDescendants() {\n let sibling = this.nextItem;\n return !!sibling && sibling.depth > this.depth;\n }\n\n /**\n * Recursively traverse the node and its descendants.\n *\n * @callback {Item}\n */\n traverse(callback) {\n // capture descendants before traversal in case of side-effects\n // specifically, setting depth affects calculation\n const expanded = this.#expandedDescendants;\n\n callback(this);\n this.#traverseCollapsed(callback);\n expanded.forEach((item) => item.#traverseCollapsed(callback));\n }\n\n /**\n * Recursively traverse the node's collapsed descendants, if any.\n *\n * @callback {Item}\n */\n #traverseCollapsed(callback) {\n if (!this.hasCollapsedDescendants()) return;\n\n this.#collapsedDescendants.forEach((item) => {\n callback(item);\n item.#traverseCollapsed(callback);\n });\n }\n\n /**\n * Move the given item into this element's hidden children list.\n * Assumes the list already exists.\n *\n * @param item {Item}\n */\n collapseChild(item) {\n this.#childrenListElement.appendChild(item.node);\n }\n\n /**\n * Collapses visible (logical) children into this element's hidden children\n * list, creating it if it doesn't already exist.\n */\n collapse() {\n let listElement = this.#childrenListElement;\n\n if (!listElement) listElement = createChildrenList(this.node);\n\n this.#expandedDescendants.forEach((child) =>\n listElement.appendChild(child.node),\n );\n }\n\n /**\n * Moves any collapsed children back into the parent container.\n */\n expand() {\n if (!this.hasCollapsedDescendants()) return;\n\n Array.from(this.#childrenListElement.children)\n .reverse()\n .forEach((node) => {\n this.node.insertAdjacentElement(\"afterend\", node);\n });\n }\n\n /**\n * Sets the state of a given rule on the target node.\n *\n * @param rule {String}\n * @param deny {boolean}\n */\n toggleRule(rule, deny = false) {\n if (this.node.dataset.hasOwnProperty(rule) && !deny) {\n delete this.node.dataset[rule];\n }\n if (!this.node.dataset.hasOwnProperty(rule) && deny) {\n this.node.dataset[rule] = \"\";\n }\n\n if (rule === \"denyDrag\") {\n if (!this.node.hasAttribute(\"draggable\") && !deny) {\n this.node.setAttribute(\"draggable\", \"true\");\n }\n if (this.node.hasAttribute(\"draggable\") && deny) {\n this.node.removeAttribute(\"draggable\");\n }\n }\n }\n\n /**\n * Detects turbo item changes by comparing the dataset id with the input\n */\n hasItemIdChanged() {\n return !(this.#itemIdInput.value === this.itemId);\n }\n\n /**\n * Updates inputs, in case they don't match the data values, e.g., when the\n * nested inputs have been hot-swapped by turbo with data from the server.\n *\n * Updates itemId from input as that is the canonical source.\n */\n updateAfterChange() {\n this.itemId = this.#itemIdInput.value;\n this.#indexInput.value = this.index;\n this.#depthInput.value = this.depth;\n }\n\n /**\n * Finds the dom container for storing collapsed (hidden) children, if present.\n *\n * @returns {Element} ol[data-content-children]\n */\n get #childrenListElement() {\n return this.node.querySelector(`:scope > [data-content-children]`);\n }\n\n /**\n * @returns {Item[]} all items that follow this element that have a greater depth.\n */\n get #expandedDescendants() {\n const descendants = [];\n\n let sibling = this.nextItem;\n while (sibling && sibling.depth > this.depth) {\n descendants.push(sibling);\n sibling = sibling.nextItem;\n }\n\n return descendants;\n }\n\n /**\n * @returns {Item[]} all items directly contained inside this element's hidden children element.\n */\n get #collapsedDescendants() {\n if (!this.hasCollapsedDescendants()) return [];\n\n return Array.from(this.#childrenListElement.children).map(\n (node) => new Item(node),\n );\n }\n}\n\n/**\n * Finds or creates a dom container for storing collapsed (hidden) children.\n *\n * @param node {Element} li[data-content-index]\n * @returns {Element} ol[data-content-children]\n */\nfunction createChildrenList(node) {\n const childrenList = document.createElement(\"ol\");\n childrenList.toggleAttribute(\"hidden\", true);\n\n // if objectType is \"rich-content\" set richContentChildren as a data attribute\n childrenList.dataset[`contentChildren`] = \"\";\n\n node.appendChild(childrenList);\n\n return childrenList;\n}\n","import Item from \"./item\";\n\n/**\n * @param nodes {NodeList}\n * @returns {Item[]}\n */\nfunction createItemList(nodes) {\n return Array.from(nodes).map((node) => new Item(node));\n}\n\nexport default class Container {\n /**\n * @param node {Element} content editor list\n */\n constructor(node) {\n this.node = node;\n }\n\n /**\n * @return {Item[]} an ordered list of all items in the container\n */\n get items() {\n return createItemList(this.node.querySelectorAll(\"[data-content-index]\"));\n }\n\n /**\n * @return {String} a serialized description of the structure of the container\n */\n get state() {\n const inputs = this.node.querySelectorAll(\"li input[type=hidden]\");\n return Array.from(inputs)\n .map((e) => e.value)\n .join(\"/\");\n }\n\n /**\n * Set the index of items based on their current position.\n */\n reindex() {\n this.items.map((item, index) => (item.index = index));\n }\n\n /**\n * Resets the order of items to their defined index.\n * Useful after an aborted drag.\n */\n reset() {\n this.items.sort(Item.comparator).forEach((item) => {\n this.node.appendChild(item.node);\n });\n }\n}\n","export default class RulesEngine {\n static rules = [\n \"denyDeNest\",\n \"denyNest\",\n \"denyCollapse\",\n \"denyExpand\",\n \"denyRemove\",\n \"denyDrag\",\n \"denyEdit\",\n ];\n\n constructor(debug = false) {\n if (debug) {\n this.debug = (...args) => console.log(...args);\n } else {\n this.debug = () => {};\n }\n }\n\n /**\n * Enforce structural rules to ensure that the given item is currently in a\n * valid state.\n *\n * @param {Item} item\n */\n normalize(item) {\n // structural rules enforce a valid tree structure\n this.firstItemDepthZero(item);\n this.depthMustBeSet(item);\n this.itemCannotHaveInvalidDepth(item);\n this.parentMustBeLayout(item);\n this.parentCannotHaveExpandedAndCollapsedChildren(item);\n }\n\n /**\n * Apply rules to the given item to determine what operations are permitted.\n *\n * @param {Item} item\n */\n update(item) {\n this.rules = {};\n\n // behavioural rules define what the user is allowed to do\n this.parentsCannotDeNest(item);\n this.rootsCannotDeNest(item);\n this.onlyLastItemCanDeNest(item);\n this.nestingNeedsParent(item);\n this.leavesCannotCollapse(item);\n this.needHiddenItemsToExpand(item);\n this.parentsCannotBeDeleted(item);\n this.parentsCannotBeDragged(item);\n\n RulesEngine.rules.forEach((rule) => {\n item.toggleRule(rule, !!this.rules[rule]);\n });\n }\n\n /**\n * First item can't have a parent, so its depth should always be 0\n */\n firstItemDepthZero(item) {\n if (item.index === 0 && item.depth !== 0) {\n this.debug(`enforce depth on item ${item.index}: ${item.depth} => 0`);\n\n item.depth = 0;\n }\n }\n\n /**\n * Every item should have a non-negative depth set.\n *\n * @param {Item} item\n */\n depthMustBeSet(item) {\n if (isNaN(item.depth) || item.depth < 0) {\n this.debug(`unset depth on item ${item.index}: => 0`);\n\n item.depth = 0;\n }\n }\n\n /**\n * Depth must increase stepwise.\n *\n * @param {Item} item\n */\n itemCannotHaveInvalidDepth(item) {\n const previous = item.previousItem;\n if (previous && previous.depth < item.depth - 1) {\n this.debug(\n `invalid depth on item ${item.index}: ${item.depth} => ${\n previous.depth + 1\n }`,\n );\n\n item.depth = previous.depth + 1;\n }\n }\n\n /**\n * Parent item, if any, must be a layout.\n *\n * @param {Item} item\n */\n parentMustBeLayout(item) {\n // if we're the first child, make sure our parent is a layout\n // if we're a sibling, we know the previous item is valid so we must be too\n const previous = item.previousItem;\n if (previous && previous.depth < item.depth && !previous.isLayout) {\n this.debug(\n `invalid parent for item ${item.index}: ${item.depth} => ${previous.depth}`,\n );\n\n item.depth = previous.depth;\n }\n }\n\n /**\n * If a parent has expanded and collapsed children, expand.\n *\n * @param {Item} item\n */\n parentCannotHaveExpandedAndCollapsedChildren(item) {\n if (item.hasCollapsedDescendants() && item.hasExpandedDescendants()) {\n this.debug(`expanding collapsed children of item ${item.index}`);\n\n item.expand();\n }\n }\n\n /**\n * De-nesting an item would create a gap of 2 between itself and its children\n *\n * @param {Item} item\n */\n parentsCannotDeNest(item) {\n if (item.hasExpandedDescendants()) this.#deny(\"denyDeNest\");\n }\n\n /**\n * Item depth can't go below 0.\n *\n * @param {Item} item\n */\n rootsCannotDeNest(item) {\n if (item.depth === 0) this.#deny(\"denyDeNest\");\n }\n\n /**\n * De-nesting an item that has siblings would make it a container.\n *\n * @param {Item} item\n */\n onlyLastItemCanDeNest(item) {\n const next = item.nextItem;\n if (next && next.depth === item.depth && !item.isLayout)\n this.#deny(\"denyDeNest\");\n }\n\n /**\n * If an item doesn't have children it can't be collapsed.\n *\n * @param {Item} item\n */\n leavesCannotCollapse(item) {\n if (!item.hasExpandedDescendants()) this.#deny(\"denyCollapse\");\n }\n\n /**\n * If an item doesn't have any hidden descendants then it can't be expanded.\n *\n * @param {Item} item\n */\n needHiddenItemsToExpand(item) {\n if (!item.hasCollapsedDescendants()) this.#deny(\"denyExpand\");\n }\n\n /**\n * An item can't be nested (indented) if it doesn't have a valid parent.\n *\n * @param {Item} item\n */\n nestingNeedsParent(item) {\n const previous = item.previousItem;\n // no previous, so cannot nest\n if (!previous) this.#deny(\"denyNest\");\n // previous is too shallow, nesting would increase depth too much\n else if (previous.depth < item.depth) this.#deny(\"denyNest\");\n // new parent is not a layout\n else if (previous.depth === item.depth && !previous.isLayout)\n this.#deny(\"denyNest\");\n }\n\n /**\n * An item can't be deleted if it has visible children.\n *\n * @param {Item} item\n */\n parentsCannotBeDeleted(item) {\n if (!item.itemId || item.hasExpandedDescendants()) this.#deny(\"denyRemove\");\n }\n\n /**\n * Items cannot be dragged if they have visible children.\n *\n * @param {Item} item\n */\n parentsCannotBeDragged(item) {\n if (item.hasExpandedDescendants()) this.#deny(\"denyDrag\");\n }\n\n /**\n * Record a deny.\n *\n * @param rule {String}\n */\n #deny(rule) {\n this.rules[rule] = true;\n }\n}\n","import { Controller } from \"@hotwired/stimulus\";\n\nimport Item from \"./item\";\nimport Container from \"./container\";\nimport RulesEngine from \"./rules_engine\";\n\nexport default class ContainerController extends Controller {\n static targets = [\"container\"];\n\n // Caution: connect is called on attachment, but also on morph/render\n connect() {\n this.state = this.container.state;\n this.reindex();\n }\n\n get container() {\n return new Container(this.containerTarget);\n }\n\n reindex() {\n this.container.reindex();\n this.#update();\n }\n\n reset() {\n this.container.reset();\n }\n\n drop(event) {\n this.container.reindex(); // set indexes before calculating previous\n\n const item = getEventItem(event);\n const previous = item.previousItem;\n\n let delta = 0;\n if (previous === undefined) {\n // if previous does not exist, set depth to 0\n delta = -item.depth;\n } else if (\n previous.isLayout &&\n item.nextItem &&\n item.nextItem.depth > previous.depth\n ) {\n // if previous is a layout and next is a child of previous, make item a child of previous\n delta = previous.depth - item.depth + 1;\n } else {\n // otherwise, make item a sibling of previous\n delta = previous.depth - item.depth;\n }\n\n item.traverse((child) => {\n child.depth += delta;\n });\n\n this.#update();\n event.preventDefault();\n }\n\n remove(event) {\n const item = getEventItem(event);\n\n item.node.remove();\n\n this.#update();\n event.preventDefault();\n }\n\n nest(event) {\n const item = getEventItem(event);\n\n item.traverse((child) => {\n child.depth += 1;\n });\n\n this.#update();\n event.preventDefault();\n }\n\n deNest(event) {\n const item = getEventItem(event);\n\n item.traverse((child) => {\n child.depth -= 1;\n });\n\n this.#update();\n event.preventDefault();\n }\n\n collapse(event) {\n const item = getEventItem(event);\n\n item.collapse();\n\n this.#update();\n event.preventDefault();\n }\n\n expand(event) {\n const item = getEventItem(event);\n\n item.expand();\n\n this.#update();\n event.preventDefault();\n }\n\n /**\n * Re-apply rules to items to enable/disable appropriate actions.\n */\n #update() {\n // debounce requests to ensure that we only update once per tick\n this.updateRequested = true;\n setTimeout(() => {\n if (!this.updateRequested) return;\n\n this.updateRequested = false;\n const engine = new RulesEngine();\n this.container.items.forEach((item) => engine.normalize(item));\n this.container.items.forEach((item) => engine.update(item));\n\n this.#notifyChange();\n }, 0);\n }\n\n #notifyChange() {\n this.dispatch(\"change\", {\n bubbles: true,\n prefix: \"content\",\n detail: { dirty: this.#isDirty() },\n });\n }\n\n #isDirty() {\n return this.container.state !== this.state;\n }\n}\n\nfunction getEventItem(event) {\n return new Item(event.target.closest(\"[data-content-item]\"));\n}\n","import { Controller } from \"@hotwired/stimulus\";\n\nexport default class ListController extends Controller {\n /**\n * When the user starts a drag within the list, set the item's dataTransfer\n * properties to indicate that it's being dragged and update its style.\n *\n * We delay setting the dataset property until the next animation frame\n * so that the style updates can be computed before the drag begins.\n *\n * @param event {DragEvent}\n */\n dragstart(event) {\n if (this.element !== event.target.parentElement) return;\n\n const target = event.target;\n event.dataTransfer.effectAllowed = \"move\";\n\n // update element style after drag has begun\n requestAnimationFrame(() => (target.dataset.dragging = \"\"));\n }\n\n /**\n * When the user drags an item over another item in the last, swap the\n * dragging item with the item under the cursor.\n *\n * @param event {DragEvent}\n */\n dragover(event) {\n const item = this.dragItem;\n if (!item) return;\n\n swap(dropTarget(event.target), item);\n\n event.preventDefault();\n return true;\n }\n\n /**\n * When the user drops an item, end the drag and reindex the list.\n *\n * @param event {DragEvent}\n */\n drop(event) {\n let item = this.dragItem;\n\n if (!item) return;\n\n event.preventDefault();\n delete item.dataset.dragging;\n swap(dropTarget(event.target), item);\n\n this.dispatch(\"drop\", { target: item, bubbles: true, prefix: \"content\" });\n }\n\n /**\n * End an in-progress drag by resetting the item's style and restoring its\n * original position in the list.\n */\n dragend() {\n const item = this.dragItem;\n\n if (item) {\n delete item.dataset.dragging;\n this.reset();\n }\n }\n\n get isDragging() {\n return !!this.dragItem;\n }\n\n get dragItem() {\n return this.element.querySelector(\"[data-dragging]\");\n }\n\n reindex() {\n this.dispatch(\"reindex\", { bubbles: true, prefix: \"content\" });\n }\n\n reset() {\n this.dispatch(\"reset\", { bubbles: true, prefix: \"content\" });\n }\n}\n\n/**\n * Swaps two list items.\n *\n * @param target the target element to swap with\n * @param item the item the user is dragging\n */\nfunction swap(target, item) {\n if (!target) return;\n if (target === item) return;\n\n const positionComparison = target.compareDocumentPosition(item);\n if (positionComparison & Node.DOCUMENT_POSITION_FOLLOWING) {\n target.insertAdjacentElement(\"beforebegin\", item);\n } else if (positionComparison & Node.DOCUMENT_POSITION_PRECEDING) {\n target.insertAdjacentElement(\"afterend\", item);\n }\n}\n\n/**\n * Given an event target, return the closest drop target, if any.\n */\nfunction dropTarget(e) {\n return e && e.closest(\"[data-controller='content--editor--list'] > *\");\n}\n","import { Controller } from \"@hotwired/stimulus\";\nimport \"trix\";\n\n// Note, action_text 7.1.2 changes how Trix is bundled and loaded. This\n// seems to have broken the default export from trix. This is a workaround\n// that relies on the backwards compatibility of the old export to window.Trix.\nconst Trix = window.Trix;\n\n// Stimulus controller doesn't do anything, but having one ensures that trix\n// will be lazy loaded when a trix-editor is added to the dom.\nexport default class TrixController extends Controller {\n trixInitialize(e) {\n this.element.addEventListener(\n \"turbo:before-morph-attribute\",\n this.suppressMorph,\n );\n this.element.addEventListener(\n \"turbo:before-morph-element\",\n this.suppressMorph,\n );\n\n if (this.element.toolbarElement) {\n this.element.toolbarElement.addEventListener(\n \"turbo:before-morph-attribute\",\n this.suppressMorph,\n );\n this.element.toolbarElement.addEventListener(\n \"turbo:before-morph-element\",\n this.suppressMorph,\n );\n }\n }\n\n disconnect() {\n this.element.removeEventListener(\n \"turbo:before-morph-attribute\",\n this.suppressMorph,\n );\n this.element.removeEventListener(\n \"turbo:before-morph-element\",\n this.suppressMorph,\n );\n\n if (this.element.toolbarElement) {\n this.element.toolbarElement.removeEventListener(\n \"turbo:before-morph-attribute\",\n this.suppressMorph,\n );\n this.element.toolbarElement.removeEventListener(\n \"turbo:before-morph-element\",\n this.suppressMorph,\n );\n }\n }\n\n suppressMorph = (e) => {\n // https://github.com/hotwired/turbo-rails/issues/533\n // Note that this will prevent updates from the server from making their way\n // to the trix element. Once the upstream issue is resolved we should remove\n // this compatibility patch.\n if (\n e.target.tagName === \"TRIX-EDITOR\" ||\n e.target.tagName === \"TRIX-TOOLBAR\"\n ) {\n e.preventDefault();\n }\n };\n}\n\n// Add H4 as an acceptable tag\nTrix.config.blockAttributes[\"heading4\"] = {\n tagName: \"h4\",\n terminal: true,\n breakOnReturn: true,\n group: false,\n};\n\n// Remove H1 from trix list of acceptable tags\ndelete Trix.config.blockAttributes.heading1;\n\n/**\n * Allow users to enter path and fragment URIs which the input[type=url] browser\n * input does not permit. Uses a permissive regex pattern which is not suitable\n * for untrusted use cases.\n */\nconst LINK_PATTERN = \"(https?|mailto:|tel:|/|#).*?\";\n\n/**\n * Customize default toolbar:\n *\n * * headings: h4 instead of h1\n * * links: use type=text instead of type=url\n *\n * @returns {String} toolbar html fragment\n */\nTrix.config.toolbar.getDefaultHTML = () => {\n const { lang } = Trix.config;\n return `\n<div class=\"trix-button-row\">\n <span class=\"trix-button-group trix-button-group--text-tools\" data-trix-button-group=\"text-tools\">\n <button type=\"button\" class=\"trix-button trix-button--icon trix-button--icon-bold\" data-trix-attribute=\"bold\" data-trix-key=\"b\" title=\"${lang.bold}\" tabindex=\"-1\">${lang.bold}</button>\n <button type=\"button\" class=\"trix-button trix-button--icon trix-button--icon-italic\" data-trix-attribute=\"italic\" data-trix-key=\"i\" title=\"${lang.italic}\" tabindex=\"-1\">${lang.italic}</button>\n <button type=\"button\" class=\"trix-button trix-button--icon trix-button--icon-strike\" data-trix-attribute=\"strike\" title=\"${lang.strike}\" tabindex=\"-1\">${lang.strike}</button>\n <button type=\"button\" class=\"trix-button trix-button--icon trix-button--icon-link\" data-trix-attribute=\"href\" data-trix-action=\"link\" data-trix-key=\"k\" title=\"${lang.link}\" tabindex=\"-1\">${lang.link}</button>\n </span>\n <span class=\"trix-button-group trix-button-group--block-tools\" data-trix-button-group=\"block-tools\">\n <button type=\"button\" class=\"trix-button trix-button--icon trix-button--icon-heading-1\" data-trix-attribute=\"heading4\" title=\"${lang.heading1}\" tabindex=\"-1\">${lang.heading1}</button>\n <button type=\"button\" class=\"trix-button trix-button--icon trix-button--icon-quote\" data-trix-attribute=\"quote\" title=\"${lang.quote}\" tabindex=\"-1\">${lang.quote}</button>\n <button type=\"button\" class=\"trix-button trix-button--icon trix-button--icon-code\" data-trix-attribute=\"code\" title=\"${lang.code}\" tabindex=\"-1\">${lang.code}</button>\n <button type=\"button\" class=\"trix-button trix-button--icon trix-button--icon-bullet-list\" data-trix-attribute=\"bullet\" title=\"${lang.bullets}\" tabindex=\"-1\">${lang.bullets}</button>\n <button type=\"button\" class=\"trix-button trix-button--icon trix-button--icon-number-list\" data-trix-attribute=\"number\" title=\"${lang.numbers}\" tabindex=\"-1\">${lang.numbers}</button>\n <button type=\"button\" class=\"trix-button trix-button--icon trix-button--icon-decrease-nesting-level\" data-trix-action=\"decreaseNestingLevel\" title=\"${lang.outdent}\" tabindex=\"-1\">${lang.outdent}</button>\n <button type=\"button\" class=\"trix-button trix-button--icon trix-button--icon-increase-nesting-level\" data-trix-action=\"increaseNestingLevel\" title=\"${lang.indent}\" tabindex=\"-1\">${lang.indent}</button>\n </span>\n <span class=\"trix-button-group trix-button-group--file-tools\" data-trix-button-group=\"file-tools\">\n <button type=\"button\" class=\"trix-button trix-button--icon trix-button--icon-attach\" data-trix-action=\"attachFiles\" title=\"${lang.attachFiles}\" tabindex=\"-1\">${lang.attachFiles}</button>\n </span>\n <span class=\"trix-button-group-spacer\"></span>\n <span class=\"trix-button-group trix-button-group--history-tools\" data-trix-button-group=\"history-tools\">\n <button type=\"button\" class=\"trix-button trix-button--icon trix-button--icon-undo\" data-trix-action=\"undo\" data-trix-key=\"z\" title=\"${lang.undo}\" tabindex=\"-1\">${lang.undo}</button>\n <button type=\"button\" class=\"trix-button trix-button--icon trix-button--icon-redo\" data-trix-action=\"redo\" data-trix-key=\"shift+z\" title=\"${lang.redo}\" tabindex=\"-1\">${lang.redo}</button>\n </span>\n</div>\n<div class=\"trix-dialogs\" data-trix-dialogs>\n <div class=\"trix-dialog trix-dialog--link\" data-trix-dialog=\"href\" data-trix-dialog-attribute=\"href\">\n <div class=\"trix-dialog__link-fields\">\n <input type=\"text\" name=\"href\" pattern=\"${LINK_PATTERN}\" class=\"trix-input trix-input--dialog\" placeholder=\"${lang.urlPlaceholder}\" aria-label=\"${lang.url}\" required data-trix-input>\n <div class=\"trix-button-group\">\n <input type=\"button\" class=\"trix-button trix-button--dialog\" value=\"${lang.link}\" data-trix-method=\"setAttribute\">\n <input type=\"button\" class=\"trix-button trix-button--dialog\" value=\"${lang.unlink}\" data-trix-method=\"removeAttribute\">\n </div>\n </div>\n </div>\n</div>\n`;\n};\n\n/**\n * If the <trix-editor> element is in the HTML when Trix loads, then Trix will have already injected the toolbar content\n * before our code gets a chance to run. Fix that now.\n *\n * Note: in Trix 2 this is likely to no longer be necessary.\n */\ndocument.querySelectorAll(\"trix-toolbar\").forEach((e) => {\n e.innerHTML = Trix.config.toolbar.getDefaultHTML();\n});\n","import ContainerController from \"./editor/container_controller\";\nimport ItemController from \"./editor/item_controller\";\nimport ItemEditorController from \"./editor/item_editor_controller\";\nimport ListController from \"./editor/list_controller\";\nimport NewItemsController from \"./editor/new_items_controller\";\nimport StatusBarController from \"./editor/status_bar_controller\";\nimport TableController from \"./editor/table_controller\";\nimport TrixController from \"./editor/trix_controller\";\n\nconst Definitions = [\n {\n identifier: \"content--editor--container\",\n controllerConstructor: ContainerController,\n },\n {\n identifier: \"content--editor--item\",\n controllerConstructor: ItemController,\n },\n {\n identifier: \"content--editor--item-editor\",\n controllerConstructor: ItemEditorController,\n },\n {\n identifier: \"content--editor--list\",\n controllerConstructor: ListController,\n },\n {\n identifier: \"content--editor--new-items\",\n controllerConstructor: NewItemsController,\n },\n {\n identifier: \"content--editor--status-bar\",\n controllerConstructor: StatusBarController,\n },\n {\n identifier: \"content--editor--table\",\n controllerConstructor: TableController,\n },\n {\n identifier: \"content--editor--trix\",\n controllerConstructor: TrixController,\n },\n];\n\nexport { Definitions as default };\n","import { Controller } from \"@hotwired/stimulus\";\nimport Item from \"./item\";\n\nexport default class ItemController extends Controller {\n get item() {\n return new Item(this.li);\n }\n\n get ol() {\n return this.element.closest(\"ol\");\n }\n\n get li() {\n return this.element.closest(\"li\");\n }\n\n connect() {\n if (this.element.dataset.hasOwnProperty(\"delete\")) {\n this.remove();\n }\n // if index is not already set, re-index will set it\n else if (!(this.item.index >= 0)) {\n this.reindex();\n }\n // if item has been replaced via turbo, re-index will run the rules engine\n // update our depth and index with values from the li's data attributes\n else if (this.item.hasItemIdChanged()) {\n this.item.updateAfterChange();\n this.reindex();\n }\n }\n\n remove() {\n // capture ol\n const ol = this.ol;\n // remove self from dom\n this.li.remove();\n // reindex ol\n this.reindex();\n }\n\n reindex() {\n this.dispatch(\"reindex\", { bubbles: true, prefix: \"content\" });\n }\n}\n","import { Controller } from \"@hotwired/stimulus\";\nimport Item from \"./item\";\n\nexport default class ItemEditorController extends Controller {\n static targets = [\"dialog\"];\n\n connect() {\n this.element.addEventListener(\"turbo:submit-end\", this.onSubmit);\n }\n\n disconnect() {\n this.element.removeEventListener(\"turbo:submit-end\", this.onSubmit);\n }\n\n click(e) {\n if (e.target.tagName === \"DIALOG\") this.dismiss();\n }\n\n dismiss() {\n if (!this.dialogTarget) return;\n if (!this.dialogTarget.open) this.dialogTarget.close();\n\n if (!(\"itemPersisted\" in this.dialogTarget.dataset)) {\n this.#removeTargetItem();\n }\n\n this.element.removeAttribute(\"src\");\n this.dialogTarget.remove();\n }\n\n dialogTargetConnected(dialog) {\n dialog.showModal();\n }\n\n onSubmit = (event) => {\n if (\n event.detail.success &&\n \"closeDialog\" in event.detail.formSubmission?.submitter?.dataset\n ) {\n this.dialogTarget.close();\n this.element.removeAttribute(\"src\");\n this.dialogTarget.remove();\n }\n };\n\n #removeTargetItem() {\n const el = document.getElementById(this.dialogTarget.dataset.itemId);\n const item = new Item(el.closest(\"[data-content-item]\"));\n const list = item.node.parentElement;\n\n item.node.remove();\n\n this.dispatch(\"reindex\", {\n target: list,\n bubbles: true,\n prefix: \"content\",\n });\n }\n}\n","import { Controller } from \"@hotwired/stimulus\";\n\nimport Item from \"./item\";\n\nconst EDGE_AREA = 24;\n\nexport default class NewItemsController extends Controller {\n static targets = [\"inline\"];\n\n connect() {\n this.form.addEventListener(\"mousemove\", this.move);\n }\n\n disconnect() {\n this.form?.removeEventListener(\"mousemove\", this.move);\n delete this.currentItem;\n }\n\n click(e) {\n if (e.target.tagName === \"DIALOG\") this.close(e);\n }\n\n open(e) {\n e.preventDefault();\n this.dialog.showModal();\n }\n\n close(e) {\n e.preventDefault();\n this.dialog.close();\n }\n\n /**\n * Add the selected item to the DOM at the current position or the end of the list.\n */\n add(e) {\n e.preventDefault();\n\n const template = e.target.closest(\"li\").querySelector(\"template\");\n const item = template.content.querySelector(\"li\").cloneNode(true);\n const target = this.currentItem;\n\n if (target) {\n target.insertAdjacentElement(\"beforebegin\", item);\n new Item(item).depth = new Item(target).depth;\n } else {\n this.list.insertAdjacentElement(\"beforeend\", item);\n }\n\n this.toggleInline(false);\n this.dialog.close();\n\n requestAnimationFrame(() => {\n item.querySelector(`[value=\"edit\"]`).click();\n });\n }\n\n morph(e) {\n e.preventDefault();\n this.dialog.close();\n }\n\n move = (e) => {\n if (this.isOverInlineTarget(e)) return;\n if (this.dialog.open) return;\n\n const target = this.getCurrentItem(e);\n\n // return if we're already showing this item\n if (this.currentItem === target) return;\n\n // hide the button if it's already visible\n if (this.currentItem) this.toggleInline(false);\n\n this.currentItem = target;\n\n // clear any previously set timer\n if (this.timer) clearTimeout(this.timer);\n\n // show the button after a debounce pause\n this.timer = setTimeout(() => {\n delete this.timer;\n this.toggleInline();\n }, 100);\n };\n\n toggleInline(show = !!this.currentItem) {\n if (show) {\n this.inlineTarget.style.top = `${this.currentItem.offsetTop}px`;\n this.inlineTarget.toggleAttribute(\"hidden\", false);\n } else {\n this.inlineTarget.toggleAttribute(\"hidden\", true);\n }\n }\n\n get dialog() {\n return this.element.querySelector(\"dialog\");\n }\n\n /**\n * @returns {HTMLFormElement}\n */\n get form() {\n return this.element.closest(\"form\");\n }\n\n /**\n * @returns {HTMLUListElement,null}\n */\n get list() {\n return this.form.querySelector(`[data-controller=\"content--editor--list\"]`);\n }\n\n /**\n * @param {MouseEvent} e\n * @returns {HTMLLIElement,null}\n */\n getCurrentItem(e) {\n const item = document.elementFromPoint(e.clientX, e.clientY).closest(\"li\");\n if (!item) return null;\n\n const bounds = item.getBoundingClientRect();\n\n // check X for center(ish) mouse position\n if (e.clientX < bounds.left + bounds.width / 2 - 2 * EDGE_AREA) return null;\n if (e.clientX > bounds.left + bounds.width / 2 + 2 * EDGE_AREA) return null;\n\n // check Y for hits on this item or it's next sibling\n if (e.clientY - bounds.y <= EDGE_AREA) {\n return item;\n } else if (bounds.y + bounds.height - e.clientY <= EDGE_AREA) {\n return item.nextElementSibling;\n } else {\n return null;\n }\n }\n\n /**\n * @param {MouseEvent} e\n * @returns {Boolean} true when the target of the event is the floating button\n */\n isOverInlineTarget(e) {\n return (\n this.inlineTarget ===\n document.elementFromPoint(e.clientX, e.clientY).closest(\"div\")\n );\n }\n}\n","import { Controller } from \"@hotwired/stimulus\";\n\nexport default class StatusBarController extends Controller {\n connect() {\n // cache the version's state in the controller on connect\n this.versionState = this.element.dataset.state;\n }\n\n morph(e) {\n if (e.target !== this.element) return;\n\n this.versionState = this.element.dataset.state;\n this.update({ dirty: false });\n }\n\n change(e) {\n if (e.detail && e.detail.hasOwnProperty(\"dirty\")) {\n this.update(e.detail);\n }\n }\n\n update({ dirty }) {\n if (dirty) {\n this.element.dataset.state = \"dirty\";\n } else {\n this.element.dataset.state = this.versionState;\n }\n }\n}\n","import { Controller } from \"@hotwired/stimulus\";\n\nconst EDITOR = `\n<div class=\"content--editor--table-editor\"\n contenteditable=\"true\"\n data-content--editor--table-target=\"content\"\n data-action=\"paste->content--editor--table#paste\"\n id=\"item-content-field\">\n</div>`;\n\nexport default class TableController extends Controller {\n static targets = [\"input\", \"update\"];\n\n constructor(config) {\n super(config);\n\n this.observer = new MutationObserver(this.change);\n }\n\n connect() {\n const template = document.createElement(\"TEMPLATE\");\n template.innerHTML = EDITOR;\n this.content = template.content.firstElementChild;\n this.content.innerHTML = this.inputTarget.value;\n this.content.className += ` ${this.inputTarget.className}`;\n this.inputTarget.insertAdjacentElement(\"beforebegin\", this.content);\n this.inputTarget.hidden = true;\n\n this.observer.observe(this.content, {\n attributes: true,\n childList: true,\n characterData: true,\n subtree: true,\n });\n }\n\n disconnect() {\n this.observer.disconnect();\n this.content.remove();\n delete this.content;\n }\n\n change = (mutations) => {\n this.inputTarget.value = this.table?.outerHTML;\n };\n\n update = () => {\n this.updateTarget.click();\n };\n\n paste = (e) => {\n if (e.clipboardData.getData(\"text/html\").indexOf(\"<table\") === -1) return;\n\n e.preventDefault();\n\n this.inputTarget.value = e.clipboardData.getData(\"text/html\");\n\n this.update();\n };\n\n /**\n * @returns {HTMLTableElement} The table element from the content target\n */\n get table() {\n return this.content.querySelector(\"table\");\n }\n}\n"],"names":["Item","comparator","a","b","index","constructor","node","this","itemId","dataset","itemIdInput","querySelector","id","value","depth","parseInt","depthInput","indexInput","isLayout","hasAttribute","previousItem","sibling","previousElementSibling","nextItem","nextElementSibling","hasCollapsedDescendants","childrenList","childrenListElement","children","length","hasExpandedDescendants","traverse","callback","expanded","expandedDescendants","traverseCollapsed","forEach","item","collapsedDescendants","collapseChild","appendChild","collapse","listElement","document","createElement","toggleAttribute","createChildrenList","child","expand","Array","from","reverse","insertAdjacentElement","toggleRule","rule","deny","hasOwnProperty","setAttribute","removeAttribute","hasItemIdChanged","updateAfterChange","descendants","push","map","Container","items","nodes","querySelectorAll","state","inputs","e","join","reindex","reset","sort","RulesEngine","static","debug","args","console","log","normalize","firstItemDepthZero","depthMustBeSet","itemCannotHaveInvalidDepth","parentMustBeLayout","parentCannotHaveExpandedAndCollapsedChildren","update","rules","parentsCannotDeNest","rootsCannotDeNest","onlyLastItemCanDeNest","nestingNeedsParent","leavesCannotCollapse","needHiddenItemsToExpand","parentsCannotBeDeleted","parentsCannotBeDragged","isNaN","previous","next","getEventItem","event","target","closest","swap","positionComparison","compareDocumentPosition","Node","DOCUMENT_POSITION_FOLLOWING","DOCUMENT_POSITION_PRECEDING","dropTarget","Trix","window","config","blockAttributes","tagName","terminal","breakOnReturn","group","heading1","toolbar","getDefaultHTML","lang","bold","italic","strike","link","quote","code","bullets","numbers","outdent","indent","attachFiles","undo","redo","urlPlaceholder","url","unlink","innerHTML","Definitions","identifier","controllerConstructor","Controller","connect","container","containerTarget","drop","delta","undefined","preventDefault","remove","nest","deNest","updateRequested","setTimeout","engine","notifyChange","dispatch","bubbles","prefix","detail","dirty","isDirty","li","ol","element","addEventListener","onSubmit","disconnect","removeEventListener","click","dismiss","dialogTarget","open","close","removeTargetItem","dialogTargetConnected","dialog","showModal","success","formSubmission","submitter","el","getElementById","list","parentElement","dragstart","dataTransfer","effectAllowed","requestAnimationFrame","dragging","dragover","dragItem","dragend","isDragging","form","move","currentItem","add","content","cloneNode","toggleInline","morph","isOverInlineTarget","getCurrentItem","timer","clearTimeout","show","inlineTarget","style","top","offsetTop","elementFromPoint","clientX","clientY","bounds","getBoundingClientRect","left","width","y","height","versionState","change","super","observer","MutationObserver","template","firstElementChild","inputTarget","className","hidden","observe","attributes","childList","characterData","subtree","mutations","table","outerHTML","updateTarget","paste","clipboardData","getData","indexOf","trixInitialize","suppressMorph","toolbarElement"],"mappings":"6DAAe,MAAMA,EAQnB,iBAAOC,CAAWC,EAAGC,GACnB,OAAOD,EAAEE,MAAQD,EAAEC,KACvB,CAKE,WAAAC,CAAYC,GACVC,KAAKD,KAAOA,CAChB,CAKE,UAAIE,GACF,OAAOD,KAAKD,KAAKG,QAAuB,aAC5C,CAEE,KAAIC,GACF,OAAOH,KAAKD,KAAKK,cAAc,sBACnC,CAKE,UAAIH,CAAOI,GACLL,KAAKC,SAAWI,IAEpBL,KAAKD,KAAKG,QAAuB,cAAI,GAAGG,IACxCL,MAAKG,EAAaG,MAAQ,GAAGD,IACjC,CAKE,SAAIE,GACF,OAAOC,SAASR,KAAKD,KAAKG,QAAsB,eAAM,CAC1D,CAEE,KAAIO,GACF,OAAOT,KAAKD,KAAKK,cAAc,yBACnC,CAKE,SAAIG,CAAMA,GACJP,KAAKO,QAAUA,IAEnBP,KAAKD,KAAKG,QAAsB,aAAI,GAAGK,IACvCP,MAAKS,EAAYH,MAAQ,GAAGC,IAChC,CAKE,SAAIV,GACF,OAAOW,SAASR,KAAKD,KAAKG,QAAsB,aACpD,CAEE,KAAIQ,GACF,OAAOV,KAAKD,KAAKK,cAAc,yBACnC,CAKE,SAAIP,CAAMA,GACJG,KAAKH,QAAUA,IAEnBG,KAAKD,KAAKG,QAAsB,aAAI,GAAGL,IACvCG,MAAKU,EAAYJ,MAAQ,GAAGT,IAChC,CAKE,YAAIc,GACF,OAAOX,KAAKD,KAAKa,aAAa,sBAClC,CAKE,gBAAIC,GACF,IAAIC,EAAUd,KAAKD,KAAKgB,uBACxB,GAAID,EAAS,OAAO,IAAIrB,EAAKqB,EACjC,CAKE,YAAIE,GACF,IAAIF,EAAUd,KAAKD,KAAKkB,mBACxB,GAAIH,EAAS,OAAO,IAAIrB,EAAKqB,EACjC,CAKE,uBAAAI,GACE,IAAIC,EAAenB,MAAKoB,EACxB,QAASD,GAAgBA,EAAaE,SAASC,OAAS,CAC5D,CAKE,sBAAAC,GACE,IAAIT,EAAUd,KAAKgB,SACnB,QAASF,GAAWA,EAAQP,MAAQP,KAAKO,KAC7C,CAOE,QAAAiB,CAASC,GAGP,MAAMC,EAAW1B,MAAK2B,EAEtBF,EAASzB,MACTA,MAAK4B,EAAmBH,GACxBC,EAASG,SAASC,GAASA,GAAKF,EAAmBH,IACvD,CAOE,EAAAG,CAAmBH,GACZzB,KAAKkB,2BAEVlB,MAAK+B,EAAsBF,SAASC,IAClCL,EAASK,GACTA,GAAKF,EAAmBH,KAE9B,CAQE,aAAAO,CAAcF,GACZ9B,MAAKoB,EAAqBa,YAAYH,EAAK/B,KAC/C,CAME,QAAAmC,GACE,IAAIC,EAAcnC,MAAKoB,EAElBe,IAAaA,EAyGtB,SAA4BpC,GAC1B,MAAMoB,EAAeiB,SAASC,cAAc,MAQ5C,OAPAlB,EAAamB,gBAAgB,UAAU,GAGvCnB,EAAajB,QAAyB,gBAAI,GAE1CH,EAAKkC,YAAYd,GAEVA,CACT,CAnHoCoB,CAAmBvC,KAAKD,OAExDC,MAAK2B,EAAqBE,SAASW,GACjCL,EAAYF,YAAYO,EAAMzC,OAEpC,CAKE,MAAA0C,GACOzC,KAAKkB,2BAEVwB,MAAMC,KAAK3C,MAAKoB,EAAqBC,UAClCuB,UACAf,SAAS9B,IACRC,KAAKD,KAAK8C,sBAAsB,WAAY9C,KAEpD,CAQE,UAAA+C,CAAWC,EAAMC,GAAO,GAClBhD,KAAKD,KAAKG,QAAQ+C,eAAeF,KAAUC,UACtChD,KAAKD,KAAKG,QAAQ6C,IAEtB/C,KAAKD,KAAKG,QAAQ+C,eAAeF,IAASC,IAC7ChD,KAAKD,KAAKG,QAAQ6C,GAAQ,IAGf,aAATA,IACG/C,KAAKD,KAAKa,aAAa,cAAiBoC,GAC3ChD,KAAKD,KAAKmD,aAAa,YAAa,QAElClD,KAAKD,KAAKa,aAAa,cAAgBoC,GACzChD,KAAKD,KAAKoD,gBAAgB,aAGlC,CAKE,gBAAAC,GACE,QAASpD,MAAKG,EAAaG,QAAUN,KAAKC,OAC9C,CAQE,iBAAAoD,GACErD,KAAKC,OAASD,MAAKG,EAAaG,MAChCN,MAAKU,EAAYJ,MAAQN,KAAKH,MAC9BG,MAAKS,EAAYH,MAAQN,KAAKO,KAClC,CAOE,KAAIa,GACF,OAAOpB,KAAKD,KAAKK,cAAc,mCACnC,CAKE,KAAIuB,GACF,MAAM2B,EAAc,GAEpB,IAAIxC,EAAUd,KAAKgB,SACnB,KAAOF,GAAWA,EAAQP,MAAQP,KAAKO,OACrC+C,EAAYC,KAAKzC,GACjBA,EAAUA,EAAQE,SAGpB,OAAOsC,CACX,CAKE,KAAIvB,GACF,OAAK/B,KAAKkB,0BAEHwB,MAAMC,KAAK3C,MAAKoB,EAAqBC,UAAUmC,KACnDzD,GAAS,IAAIN,EAAKM,KAHuB,EAKhD,EC7Pe,MAAM0D,EAInB,WAAA3D,CAAYC,GACVC,KAAKD,KAAOA,CAChB,CAKE,SAAI2D,GACF,OAhBoBC,EAgBE3D,KAAKD,KAAK6D,iBAAiB,wBAf5ClB,MAAMC,KAAKgB,GAAOH,KAAKzD,GAAS,IAAIN,EAAKM,KADlD,IAAwB4D,CAiBxB,CAKE,SAAIE,GACF,MAAMC,EAAS9D,KAAKD,KAAK6D,iBAAiB,yBAC1C,OAAOlB,MAAMC,KAAKmB,GACfN,KAAKO,GAAMA,EAAEzD,QACb0D,KAAK,IACZ,CAKE,OAAAC,GACEjE,KAAK0D,MAAMF,KAAI,CAAC1B,EAAMjC,IAAWiC,EAAKjC,MAAQA,GAClD,CAME,KAAAqE,GACElE,KAAK0D,MAAMS,KAAK1E,EAAKC,YAAYmC,SAASC,IACxC9B,KAAKD,KAAKkC,YAAYH,EAAK/B,QAEjC,EClDe,MAAMqE,EACnBC,aAAe,CACb,aACA,WACA,eACA,aACA,aACA,WACA,YAGF,WAAAvE,CAAYwE,GAAQ,GAEhBtE,KAAKsE,MADHA,EACW,IAAIC,IAASC,QAAQC,OAAOF,GAE5B,MAEnB,CAQE,SAAAG,CAAU5C,GAER9B,KAAK2E,mBAAmB7C,GACxB9B,KAAK4E,eAAe9C,GACpB9B,KAAK6E,2BAA2B/C,GAChC9B,KAAK8E,mBAAmBhD,GACxB9B,KAAK+E,6CAA6CjD,EACtD,CAOE,MAAAkD,CAAOlD,GACL9B,KAAKiF,MAAQ,CAAE,EAGfjF,KAAKkF,oBAAoBpD,GACzB9B,KAAKmF,kBAAkBrD,GACvB9B,KAAKoF,sBAAsBtD,GAC3B9B,KAAKqF,mBAAmBvD,GACxB9B,KAAKsF,qBAAqBxD,GAC1B9B,KAAKuF,wBAAwBzD,GAC7B9B,KAAKwF,uBAAuB1D,GAC5B9B,KAAKyF,uBAAuB3D,GAE5BsC,EAAYa,MAAMpD,SAASkB,IACzBjB,EAAKgB,WAAWC,IAAQ/C,KAAKiF,MAAMlC,MAEzC,CAKE,kBAAA4B,CAAmB7C,GACE,IAAfA,EAAKjC,OAA8B,IAAfiC,EAAKvB,QAC3BP,KAAKsE,MAAM,yBAAyBxC,EAAKjC,UAAUiC,EAAKvB,cAExDuB,EAAKvB,MAAQ,EAEnB,CAOE,cAAAqE,CAAe9C,IACT4D,MAAM5D,EAAKvB,QAAUuB,EAAKvB,MAAQ,KACpCP,KAAKsE,MAAM,uBAAuBxC,EAAKjC,eAEvCiC,EAAKvB,MAAQ,EAEnB,CAOE,0BAAAsE,CAA2B/C,GACzB,MAAM6D,EAAW7D,EAAKjB,aAClB8E,GAAYA,EAASpF,MAAQuB,EAAKvB,MAAQ,IAC5CP,KAAKsE,MACH,yBAAyBxC,EAAKjC,UAAUiC,EAAKvB,YAC3CoF,EAASpF,MAAQ,KAIrBuB,EAAKvB,MAAQoF,EAASpF,MAAQ,EAEpC,CAOE,kBAAAuE,CAAmBhD,GAGjB,MAAM6D,EAAW7D,EAAKjB,aAClB8E,GAAYA,EAASpF,MAAQuB,EAAKvB,QAAUoF,EAAShF,WACvDX,KAAKsE,MACH,2BAA2BxC,EAAKjC,UAAUiC,EAAKvB,YAAYoF,EAASpF,SAGtEuB,EAAKvB,MAAQoF,EAASpF,MAE5B,CAOE,4CAAAwE,CAA6CjD,GACvCA,EAAKZ,2BAA6BY,EAAKP,2BACzCvB,KAAKsE,MAAM,wCAAwCxC,EAAKjC,SAExDiC,EAAKW,SAEX,CAOE,mBAAAyC,CAAoBpD,GACdA,EAAKP,0BAA0BvB,MAAKgD,EAAM,aAClD,CAOE,iBAAAmC,CAAkBrD,GACG,IAAfA,EAAKvB,OAAaP,MAAKgD,EAAM,aACrC,CAOE,qBAAAoC,CAAsBtD,GACpB,MAAM8D,EAAO9D,EAAKd,SACd4E,GAAQA,EAAKrF,QAAUuB,EAAKvB,QAAUuB,EAAKnB,UAC7CX,MAAKgD,EAAM,aACjB,CAOE,oBAAAsC,CAAqBxD,GACdA,EAAKP,0BAA0BvB,MAAKgD,EAAM,eACnD,CAOE,uBAAAuC,CAAwBzD,GACjBA,EAAKZ,2BAA2BlB,MAAKgD,EAAM,aACpD,CAOE,kBAAAqC,CAAmBvD,GACjB,MAAM6D,EAAW7D,EAAKjB,aAEjB8E,EAEIA,EAASpF,MAAQuB,EAAKvB,MAAOP,MAAKgD,EAAM,YAExC2C,EAASpF,QAAUuB,EAAKvB,OAAUoF,EAAShF,UAClDX,MAAKgD,EAAM,YALEhD,MAAKgD,EAAM,WAM9B,CAOE,sBAAAwC,CAAuB1D,GAChBA,EAAK7B,SAAU6B,EAAKP,0BAA0BvB,MAAKgD,EAAM,aAClE,CAOE,sBAAAyC,CAAuB3D,GACjBA,EAAKP,0BAA0BvB,MAAKgD,EAAM,WAClD,CAOE,EAAAA,CAAMD,GACJ/C,KAAKiF,MAAMlC,IAAQ,CACvB,EChFA,SAAS8C,EAAaC,GACpB,OAAO,IAAIrG,EAAKqG,EAAMC,OAAOC,QAAQ,uBACvC,CCjDA,SAASC,EAAKF,EAAQjE,GACpB,IAAKiE,EAAQ,OACb,GAAIA,IAAWjE,EAAM,OAErB,MAAMoE,EAAqBH,EAAOI,wBAAwBrE,GACtDoE,EAAqBE,KAAKC,4BAC5BN,EAAOlD,sBAAsB,cAAef,GACnCoE,EAAqBE,KAAKE,6BACnCP,EAAOlD,sBAAsB,WAAYf,EAE7C,CAKA,SAASyE,EAAWxC,GAClB,OAAOA,GAAKA,EAAEiC,QAAQ,gDACxB,CCtGA,MAAMQ,EAAOC,OAAOD,KAgEpBA,EAAKE,OAAOC,gBAA0B,SAAI,CACxCC,QAAS,KACTC,UAAU,EACVC,eAAe,EACfC,OAAO,UAIFP,EAAKE,OAAOC,gBAAgBK,SAiBnCR,EAAKE,OAAOO,QAAQC,eAAiB,KACnC,MAAMC,KAAEA,GAASX,EAAKE,OACtB,MAAO,qRAGoIS,EAAKC,uBAAuBD,EAAKC,iKAC7BD,EAAKE,yBAAyBF,EAAKE,iJACrDF,EAAKG,yBAAyBH,EAAKG,uLACGH,EAAKI,uBAAuBJ,EAAKI,uQAGlEJ,EAAKH,2BAA2BG,EAAKH,iJAC5CG,EAAKK,wBAAwBL,EAAKK,4IACpCL,EAAKM,uBAAuBN,EAAKM,oJACxBN,EAAKO,0BAA0BP,EAAKO,uJACpCP,EAAKQ,0BAA0BR,EAAKQ,6KACdR,EAAKS,0BAA0BT,EAAKS,6KACpCT,EAAKU,yBAAyBV,EAAKU,oQAG5DV,EAAKW,8BAA8BX,EAAKW,0UAI/BX,EAAKY,uBAAuBZ,EAAKY,gKAC3BZ,EAAKa,uBAAuBb,EAAKa,uWAM7Db,EAAKc,+BAA+Bd,EAAKe,sJAE/Ef,EAAKI,uHACLJ,EAAKgB,6FAcnF/F,SAASwB,iBAAiB,gBAAgB/B,SAASkC,IACjDA,EAAEqE,UAAY5B,EAAKE,OAAOO,QAAQC,oBCvI/B,MAACmB,EAAc,CAClB,CACEC,WAAY,6BACZC,sBHNW,cAAkCC,EAC/CnE,eAAiB,CAAC,aAGlB,OAAAoE,GACEzI,KAAK6D,MAAQ7D,KAAK0I,UAAU7E,MAC5B7D,KAAKiE,SACT,CAEE,aAAIyE,GACF,OAAO,IAAIjF,EAAUzD,KAAK2I,gBAC9B,CAEE,OAAA1E,GACEjE,KAAK0I,UAAUzE,UACfjE,MAAKgF,GACT,CAEE,KAAAd,GACElE,KAAK0I,UAAUxE,OACnB,CAEE,IAAA0E,CAAK9C,GACH9F,KAAK0I,UAAUzE,UAEf,MAAMnC,EAAO+D,EAAaC,GACpBH,EAAW7D,EAAKjB,aAEtB,IAAIgI,EAAQ,EAGVA,OAFeC,IAAbnD,GAEO7D,EAAKvB,MAEdoF,EAAShF,UACTmB,EAAKd,UACLc,EAAKd,SAAST,MAAQoF,EAASpF,MAGvBoF,EAASpF,MAAQuB,EAAKvB,MAAQ,EAG9BoF,EAASpF,MAAQuB,EAAKvB,MAGhCuB,EAAKN,UAAUgB,IACbA,EAAMjC,OAASsI,KAGjB7I,MAAKgF,IACLc,EAAMiD,gBACV,CAEE,MAAAC,CAAOlD,GACQD,EAAaC,GAErB/F,KAAKiJ,SAEVhJ,MAAKgF,IACLc,EAAMiD,gBACV,CAEE,IAAAE,CAAKnD,GACUD,EAAaC,GAErBtE,UAAUgB,IACbA,EAAMjC,OAAS,KAGjBP,MAAKgF,IACLc,EAAMiD,gBACV,CAEE,MAAAG,CAAOpD,GACQD,EAAaC,GAErBtE,UAAUgB,IACbA,EAAMjC,OAAS,KAGjBP,MAAKgF,IACLc,EAAMiD,gBACV,CAEE,QAAA7G,CAAS4D,GACMD,EAAaC,GAErB5D,WAELlC,MAAKgF,IACLc,EAAMiD,gBACV,CAEE,MAAAtG,CAAOqD,GACQD,EAAaC,GAErBrD,SAELzC,MAAKgF,IACLc,EAAMiD,gBACV,CAKE,EAAA/D,GAEEhF,KAAKmJ,iBAAkB,EACvBC,YAAW,KACT,IAAKpJ,KAAKmJ,gBAAiB,OAE3BnJ,KAAKmJ,iBAAkB,EACvB,MAAME,EAAS,IAAIjF,EACnBpE,KAAK0I,UAAUhF,MAAM7B,SAASC,GAASuH,EAAO3E,UAAU5C,KACxD9B,KAAK0I,UAAUhF,MAAM7B,SAASC,GAASuH,EAAOrE,OAAOlD,KAErD9B,MAAKsJ,MACJ,EACP,CAEE,EAAAA,GACEtJ,KAAKuJ,SAAS,SAAU,CACtBC,SAAS,EACTC,OAAQ,UACRC,OAAQ,CAAEC,MAAO3J,MAAK4J,MAE5B,CAEE,EAAAA,GACE,OAAO5J,KAAK0I,UAAU7E,QAAU7D,KAAK6D,KACzC,IGzHE,CACEyE,WAAY,wBACZC,sBCbW,cAA6BC,EAC1C,QAAI1G,GACF,OAAO,IAAIrC,EAAKO,KAAK6J,GACzB,CAEE,MAAIC,GACF,OAAO9J,KAAK+J,QAAQ/D,QAAQ,KAChC,CAEE,MAAI6D,GACF,OAAO7J,KAAK+J,QAAQ/D,QAAQ,KAChC,CAEE,OAAAyC,GACMzI,KAAK+J,QAAQ7J,QAAQ+C,eAAe,UACtCjD,KAAKgJ,SAGIhJ,KAAK8B,KAAKjC,OAAS,EAKrBG,KAAK8B,KAAKsB,qBACjBpD,KAAK8B,KAAKuB,oBACVrD,KAAKiE,WANLjE,KAAKiE,SAQX,CAEE,MAAA+E,GAEahJ,KAAK8J,GAEhB9J,KAAK6J,GAAGb,SAERhJ,KAAKiE,SACT,CAEE,OAAAA,GACEjE,KAAKuJ,SAAS,UAAW,CAAEC,SAAS,EAAMC,OAAQ,WACtD,IDzBE,CACEnB,WAAY,+BACZC,sBEjBW,cAAmCC,EAChDnE,eAAiB,CAAC,UAElB,OAAAoE,GACEzI,KAAK+J,QAAQC,iBAAiB,mBAAoBhK,KAAKiK,SAC3D,CAEE,UAAAC,GACElK,KAAK+J,QAAQI,oBAAoB,mBAAoBnK,KAAKiK,SAC9D,CAEE,KAAAG,CAAMrG,GACqB,WAArBA,EAAEgC,OAAOa,SAAsB5G,KAAKqK,SAC5C,CAEE,OAAAA,GACOrK,KAAKsK,eACLtK,KAAKsK,aAAaC,MAAMvK,KAAKsK,aAAaE,QAEzC,kBAAmBxK,KAAKsK,aAAapK,SACzCF,MAAKyK,IAGPzK,KAAK+J,QAAQ5G,gBAAgB,OAC7BnD,KAAKsK,aAAatB,SACtB,CAEE,qBAAA0B,CAAsBC,GACpBA,EAAOC,WACX,CAEEX,SAAYnE,IAERA,EAAM4D,OAAOmB,SACb,gBAAiB/E,EAAM4D,OAAOoB,gBAAgBC,WAAW7K,UAEzDF,KAAKsK,aAAaE,QAClBxK,KAAK+J,QAAQ5G,gBAAgB,OAC7BnD,KAAKsK,aAAatB,WAItB,EAAAyB,GACE,MAAMO,EAAK5I,SAAS6I,eAAejL,KAAKsK,aAAapK,QAAQD,QACvD6B,EAAO,IAAIrC,EAAKuL,EAAGhF,QAAQ,wBAC3BkF,EAAOpJ,EAAK/B,KAAKoL,cAEvBrJ,EAAK/B,KAAKiJ,SAEVhJ,KAAKuJ,SAAS,UAAW,CACvBxD,OAAQmF,EACR1B,SAAS,EACTC,OAAQ,WAEd,IFnCE,CACEnB,WAAY,wBACZC,sBFtBW,cAA6BC,EAU1C,SAAA4C,CAAUtF,GACR,GAAI9F,KAAK+J,UAAYjE,EAAMC,OAAOoF,cAAe,OAEjD,MAAMpF,EAASD,EAAMC,OACrBD,EAAMuF,aAAaC,cAAgB,OAGnCC,uBAAsB,IAAOxF,EAAO7F,QAAQsL,SAAW,IAC3D,CAQE,QAAAC,CAAS3F,GACP,MAAMhE,EAAO9B,KAAK0L,SAClB,GAAK5J,EAKL,OAHAmE,EAAKM,EAAWT,EAAMC,QAASjE,GAE/BgE,EAAMiD,kBACC,CACX,CAOE,IAAAH,CAAK9C,GACH,IAAIhE,EAAO9B,KAAK0L,SAEX5J,IAELgE,EAAMiD,wBACCjH,EAAK5B,QAAQsL,SACpBvF,EAAKM,EAAWT,EAAMC,QAASjE,GAE/B9B,KAAKuJ,SAAS,OAAQ,CAAExD,OAAQjE,EAAM0H,SAAS,EAAMC,OAAQ,YACjE,CAME,OAAAkC,GACE,MAAM7J,EAAO9B,KAAK0L,SAEd5J,WACKA,EAAK5B,QAAQsL,SACpBxL,KAAKkE,QAEX,CAEE,cAAI0H,GACF,QAAS5L,KAAK0L,QAClB,CAEE,YAAIA,GACF,OAAO1L,KAAK+J,QAAQ3J,cAAc,kBACtC,CAEE,OAAA6D,GACEjE,KAAKuJ,SAAS,UAAW,CAAEC,SAAS,EAAMC,OAAQ,WACtD,CAEE,KAAAvF,GACElE,KAAKuJ,SAAS,QAAS,CAAEC,SAAS,EAAMC,OAAQ,WACpD,IExDE,CACEnB,WAAY,6BACZC,sBGtBW,cAAiCC,EAC9CnE,eAAiB,CAAC,UAElB,OAAAoE,GACEzI,KAAK6L,KAAK7B,iBAAiB,YAAahK,KAAK8L,KACjD,CAEE,UAAA5B,GACElK,KAAK6L,MAAM1B,oBAAoB,YAAanK,KAAK8L,aAC1C9L,KAAK+L,WAChB,CAEE,KAAA3B,CAAMrG,GACqB,WAArBA,EAAEgC,OAAOa,SAAsB5G,KAAKwK,MAAMzG,EAClD,CAEE,IAAAwG,CAAKxG,GACHA,EAAEgF,iBACF/I,KAAK2K,OAAOC,WAChB,CAEE,KAAAJ,CAAMzG,GACJA,EAAEgF,iBACF/I,KAAK2K,OAAOH,OAChB,CAKE,GAAAwB,CAAIjI,GACFA,EAAEgF,iBAEF,MACMjH,EADWiC,EAAEgC,OAAOC,QAAQ,MAAM5F,cAAc,YAChC6L,QAAQ7L,cAAc,MAAM8L,WAAU,GACtDnG,EAAS/F,KAAK+L,YAEhBhG,GACFA,EAAOlD,sBAAsB,cAAef,GAC5C,IAAIrC,EAAKqC,GAAMvB,MAAQ,IAAId,EAAKsG,GAAQxF,OAExCP,KAAKkL,KAAKrI,sBAAsB,YAAaf,GAG/C9B,KAAKmM,cAAa,GAClBnM,KAAK2K,OAAOH,QAEZe,uBAAsB,KACpBzJ,EAAK1B,cAAc,kBAAkBgK,UAE3C,CAEE,KAAAgC,CAAMrI,GACJA,EAAEgF,iBACF/I,KAAK2K,OAAOH,OAChB,CAEEsB,KAAQ/H,IACN,GAAI/D,KAAKqM,mBAAmBtI,GAAI,OAChC,GAAI/D,KAAK2K,OAAOJ,KAAM,OAEtB,MAAMxE,EAAS/F,KAAKsM,eAAevI,GAG/B/D,KAAK+L,cAAgBhG,IAGrB/F,KAAK+L,aAAa/L,KAAKmM,cAAa,GAExCnM,KAAK+L,YAAchG,EAGf/F,KAAKuM,OAAOC,aAAaxM,KAAKuM,OAGlCvM,KAAKuM,MAAQnD,YAAW,YACfpJ,KAAKuM,MACZvM,KAAKmM,iBACJ,OAGL,YAAAA,CAAaM,IAASzM,KAAK+L,aACrBU,GACFzM,KAAK0M,aAAaC,MAAMC,IAAM,GAAG5M,KAAK+L,YAAYc,cAClD7M,KAAK0M,aAAapK,gBAAgB,UAAU,IAE5CtC,KAAK0M,aAAapK,gBAAgB,UAAU,EAElD,CAEE,UAAIqI,GACF,OAAO3K,KAAK+J,QAAQ3J,cAAc,SACtC,CAKE,QAAIyL,GACF,OAAO7L,KAAK+J,QAAQ/D,QAAQ,OAChC,CAKE,QAAIkF,GACF,OAAOlL,KAAK6L,KAAKzL,cAAc,4CACnC,CAME,cAAAkM,CAAevI,GACb,MAAMjC,EAAOM,SAAS0K,iBAAiB/I,EAAEgJ,QAAShJ,EAAEiJ,SAAShH,QAAQ,MACrE,IAAKlE,EAAM,OAAO,KAElB,MAAMmL,EAASnL,EAAKoL,wBAGpB,OAAInJ,EAAEgJ,QAAUE,EAAOE,KAAOF,EAAOG,MAAQ,EAAI,IAC7CrJ,EAAEgJ,QAAUE,EAAOE,KAAOF,EAAOG,MAAQ,EAAI,GADsB,KAInErJ,EAAEiJ,QAAUC,EAAOI,GA5HT,GA6HLvL,EACEmL,EAAOI,EAAIJ,EAAOK,OAASvJ,EAAEiJ,SA9H1B,GA+HLlL,EAAKb,mBAEL,IAEb,CAME,kBAAAoL,CAAmBtI,GACjB,OACE/D,KAAK0M,eACLtK,SAAS0K,iBAAiB/I,EAAEgJ,QAAShJ,EAAEiJ,SAAShH,QAAQ,MAE9D,IHpHE,CACEsC,WAAY,8BACZC,sBI9BW,cAAkCC,EAC/C,OAAAC,GAEEzI,KAAKuN,aAAevN,KAAK+J,QAAQ7J,QAAQ2D,KAC7C,CAEE,KAAAuI,CAAMrI,GACAA,EAAEgC,SAAW/F,KAAK+J,UAEtB/J,KAAKuN,aAAevN,KAAK+J,QAAQ7J,QAAQ2D,MACzC7D,KAAKgF,OAAO,CAAE2E,OAAO,IACzB,CAEE,MAAA6D,CAAOzJ,GACDA,EAAE2F,QAAU3F,EAAE2F,OAAOzG,eAAe,UACtCjD,KAAKgF,OAAOjB,EAAE2F,OAEpB,CAEE,MAAA1E,EAAO2E,MAAEA,IAEL3J,KAAK+J,QAAQ7J,QAAQ2D,MADnB8F,EAC2B,QAEA3J,KAAKuN,YAExC,IJOE,CACEjF,WAAY,yBACZC,sBK1BW,cAA8BC,EAC3CnE,eAAiB,CAAC,QAAS,UAE3B,WAAAvE,CAAY4G,GACV+G,MAAM/G,GAEN1G,KAAK0N,SAAW,IAAIC,iBAAiB3N,KAAKwN,OAC9C,CAEE,OAAA/E,GACE,MAAMmF,EAAWxL,SAASC,cAAc,YACxCuL,EAASxF,UAnBE,sNAoBXpI,KAAKiM,QAAU2B,EAAS3B,QAAQ4B,kBAChC7N,KAAKiM,QAAQ7D,UAAYpI,KAAK8N,YAAYxN,MAC1CN,KAAKiM,QAAQ8B,WAAa,IAAI/N,KAAK8N,YAAYC,YAC/C/N,KAAK8N,YAAYjL,sBAAsB,cAAe7C,KAAKiM,SAC3DjM,KAAK8N,YAAYE,QAAS,EAE1BhO,KAAK0N,SAASO,QAAQjO,KAAKiM,QAAS,CAClCiC,YAAY,EACZC,WAAW,EACXC,eAAe,EACfC,SAAS,GAEf,CAEE,UAAAnE,GACElK,KAAK0N,SAASxD,aACdlK,KAAKiM,QAAQjD,gBACNhJ,KAAKiM,OAChB,CAEEuB,OAAUc,IACRtO,KAAK8N,YAAYxN,MAAQN,KAAKuO,OAAOC,WAGvCxJ,OAAS,KACPhF,KAAKyO,aAAarE,SAGpBsE,MAAS3K,SACHA,EAAE4K,cAAcC,QAAQ,aAAaC,QAAQ,YAEjD9K,EAAEgF,iBAEF/I,KAAK8N,YAAYxN,MAAQyD,EAAE4K,cAAcC,QAAQ,aAEjD5O,KAAKgF,WAMP,SAAIuJ,GACF,OAAOvO,KAAKiM,QAAQ7L,cAAc,QACtC,IL3BE,CACEkI,WAAY,wBACZC,sBD9BW,cAA6BC,EAC1C,cAAAsG,CAAe/K,GACb/D,KAAK+J,QAAQC,iBACX,+BACAhK,KAAK+O,eAEP/O,KAAK+J,QAAQC,iBACX,6BACAhK,KAAK+O,eAGH/O,KAAK+J,QAAQiF,iBACfhP,KAAK+J,QAAQiF,eAAehF,iBAC1B,+BACAhK,KAAK+O,eAEP/O,KAAK+J,QAAQiF,eAAehF,iBAC1B,6BACAhK,KAAK+O,eAGb,CAEE,UAAA7E,GACElK,KAAK+J,QAAQI,oBACX,+BACAnK,KAAK+O,eAEP/O,KAAK+J,QAAQI,oBACX,6BACAnK,KAAK+O,eAGH/O,KAAK+J,QAAQiF,iBACfhP,KAAK+J,QAAQiF,eAAe7E,oBAC1B,+BACAnK,KAAK+O,eAEP/O,KAAK+J,QAAQiF,eAAe7E,oBAC1B,6BACAnK,KAAK+O,eAGb,CAEEA,cAAiBhL,IAMQ,gBAArBA,EAAEgC,OAAOa,SACY,iBAArB7C,EAAEgC,OAAOa,SAET7C,EAAEgF"}
1
+ {"version":3,"file":"content.min.js","sources":["../../../javascript/content/editor/item.js","../../../javascript/content/editor/container.js","../../../javascript/content/editor/rules_engine.js","../../../javascript/content/editor/container_controller.js","../../../javascript/content/editor/list_controller.js","../../../javascript/content/editor/trix_controller.js","../../../javascript/content/application.js","../../../javascript/content/editor/item_controller.js","../../../javascript/content/editor/item_editor_controller.js","../../../javascript/content/editor/new_items_controller.js","../../../javascript/content/editor/status_bar_controller.js","../../../javascript/content/editor/table_controller.js"],"sourcesContent":["export default class Item {\n /**\n * Sort items by their index.\n *\n * @param a {Item}\n * @param b {Item}\n * @returns {number}\n */\n static comparator(a, b) {\n return a.index - b.index;\n }\n\n /**\n * @param node {Element} li[data-content-index]\n */\n constructor(node) {\n this.node = node;\n }\n\n /**\n * @returns {String} id of the node's item (from data attributes)\n */\n get itemId() {\n return this.node.dataset[`contentItemId`];\n }\n\n get #itemIdInput() {\n return this.node.querySelector(`input[name$=\"[id]\"]`);\n }\n\n /**\n * @param itemId {String} id\n */\n set itemId(id) {\n if (this.itemId === id) return;\n\n this.node.dataset[`contentItemId`] = `${id}`;\n this.#itemIdInput.value = `${id}`;\n }\n\n /**\n * @returns {number} logical nesting depth of node in container\n */\n get depth() {\n return parseInt(this.node.dataset[`contentDepth`]) || 0;\n }\n\n get #depthInput() {\n return this.node.querySelector(`input[name$=\"[depth]\"]`);\n }\n\n /**\n * @param depth {number} depth >= 0\n */\n set depth(depth) {\n if (this.depth === depth) return;\n\n this.node.dataset[`contentDepth`] = `${depth}`;\n this.#depthInput.value = `${depth}`;\n }\n\n /**\n * @returns {number} logical index of node in container (pre-order traversal)\n */\n get index() {\n return parseInt(this.node.dataset[`contentIndex`]);\n }\n\n get #indexInput() {\n return this.node.querySelector(`input[name$=\"[index]\"]`);\n }\n\n /**\n * @param index {number} index >= 0\n */\n set index(index) {\n if (this.index === index) return;\n\n this.node.dataset[`contentIndex`] = `${index}`;\n this.#indexInput.value = `${index}`;\n }\n\n /**\n * @returns {boolean} true if this item can have children\n */\n get isLayout() {\n return this.node.hasAttribute(\"data-content-layout\");\n }\n\n /**\n * @returns {Item} nearest neighbour (index - 1)\n */\n get previousItem() {\n let sibling = this.node.previousElementSibling;\n if (sibling) return new Item(sibling);\n }\n\n /**\n * @returns {Item} nearest neighbour (index + 1)\n */\n get nextItem() {\n let sibling = this.node.nextElementSibling;\n if (sibling) return new Item(sibling);\n }\n\n /**\n * @returns {boolean} true if this item has any collapsed children\n */\n hasCollapsedDescendants() {\n let childrenList = this.#childrenListElement;\n return !!childrenList && childrenList.children.length > 0;\n }\n\n /**\n * @returns {boolean} true if this item has any expanded children\n */\n hasExpandedDescendants() {\n let sibling = this.nextItem;\n return !!sibling && sibling.depth > this.depth;\n }\n\n /**\n * Recursively traverse the node and its descendants.\n *\n * @callback {Item}\n */\n traverse(callback) {\n // capture descendants before traversal in case of side-effects\n // specifically, setting depth affects calculation\n const expanded = this.#expandedDescendants;\n\n callback(this);\n this.#traverseCollapsed(callback);\n expanded.forEach((item) => item.#traverseCollapsed(callback));\n }\n\n /**\n * Recursively traverse the node's collapsed descendants, if any.\n *\n * @callback {Item}\n */\n #traverseCollapsed(callback) {\n if (!this.hasCollapsedDescendants()) return;\n\n this.#collapsedDescendants.forEach((item) => {\n callback(item);\n item.#traverseCollapsed(callback);\n });\n }\n\n /**\n * Move the given item into this element's hidden children list.\n * Assumes the list already exists.\n *\n * @param item {Item}\n */\n collapseChild(item) {\n this.#childrenListElement.appendChild(item.node);\n }\n\n /**\n * Collapses visible (logical) children into this element's hidden children\n * list, creating it if it doesn't already exist.\n */\n collapse() {\n let listElement = this.#childrenListElement;\n\n if (!listElement) listElement = createChildrenList(this.node);\n\n this.#expandedDescendants.forEach((child) =>\n listElement.appendChild(child.node),\n );\n }\n\n /**\n * Moves any collapsed children back into the parent container.\n */\n expand() {\n if (!this.hasCollapsedDescendants()) return;\n\n Array.from(this.#childrenListElement.children)\n .reverse()\n .forEach((node) => {\n this.node.insertAdjacentElement(\"afterend\", node);\n });\n }\n\n /**\n * Sets the state of a given rule on the target node.\n *\n * @param rule {String}\n * @param deny {boolean}\n */\n toggleRule(rule, deny = false) {\n if (this.node.dataset.hasOwnProperty(rule) && !deny) {\n delete this.node.dataset[rule];\n }\n if (!this.node.dataset.hasOwnProperty(rule) && deny) {\n this.node.dataset[rule] = \"\";\n }\n\n if (rule === \"denyDrag\") {\n if (!this.node.hasAttribute(\"draggable\") && !deny) {\n this.node.setAttribute(\"draggable\", \"true\");\n }\n if (this.node.hasAttribute(\"draggable\") && deny) {\n this.node.removeAttribute(\"draggable\");\n }\n }\n }\n\n /**\n * Detects turbo item changes by comparing the dataset id with the input\n */\n hasItemIdChanged() {\n return !(this.#itemIdInput.value === this.itemId);\n }\n\n /**\n * Updates inputs, in case they don't match the data values, e.g., when the\n * nested inputs have been hot-swapped by turbo with data from the server.\n *\n * Updates itemId from input as that is the canonical source.\n */\n updateAfterChange() {\n this.itemId = this.#itemIdInput.value;\n this.#indexInput.value = this.index;\n this.#depthInput.value = this.depth;\n }\n\n /**\n * Finds the dom container for storing collapsed (hidden) children, if present.\n *\n * @returns {Element} ol[data-content-children]\n */\n get #childrenListElement() {\n return this.node.querySelector(`:scope > [data-content-children]`);\n }\n\n /**\n * @returns {Item[]} all items that follow this element that have a greater depth.\n */\n get #expandedDescendants() {\n const descendants = [];\n\n let sibling = this.nextItem;\n while (sibling && sibling.depth > this.depth) {\n descendants.push(sibling);\n sibling = sibling.nextItem;\n }\n\n return descendants;\n }\n\n /**\n * @returns {Item[]} all items directly contained inside this element's hidden children element.\n */\n get #collapsedDescendants() {\n if (!this.hasCollapsedDescendants()) return [];\n\n return Array.from(this.#childrenListElement.children).map(\n (node) => new Item(node),\n );\n }\n}\n\n/**\n * Finds or creates a dom container for storing collapsed (hidden) children.\n *\n * @param node {Element} li[data-content-index]\n * @returns {Element} ol[data-content-children]\n */\nfunction createChildrenList(node) {\n const childrenList = document.createElement(\"ol\");\n childrenList.toggleAttribute(\"hidden\", true);\n\n // if objectType is \"rich-content\" set richContentChildren as a data attribute\n childrenList.dataset[`contentChildren`] = \"\";\n\n node.appendChild(childrenList);\n\n return childrenList;\n}\n","import Item from \"./item\";\n\n/**\n * @param nodes {NodeList}\n * @returns {Item[]}\n */\nfunction createItemList(nodes) {\n return Array.from(nodes).map((node) => new Item(node));\n}\n\nexport default class Container {\n /**\n * @param node {Element} content editor list\n */\n constructor(node) {\n this.node = node;\n }\n\n /**\n * @return {Item[]} an ordered list of all items in the container\n */\n get items() {\n return createItemList(this.node.querySelectorAll(\"[data-content-index]\"));\n }\n\n /**\n * @return {String} a serialized description of the structure of the container\n */\n get state() {\n const inputs = this.node.querySelectorAll(\"li input[type=hidden]\");\n return Array.from(inputs)\n .map((e) => e.value)\n .join(\"/\");\n }\n\n /**\n * Set the index of items based on their current position.\n */\n reindex() {\n this.items.map((item, index) => (item.index = index));\n }\n\n /**\n * Resets the order of items to their defined index.\n * Useful after an aborted drag.\n */\n reset() {\n this.items.sort(Item.comparator).forEach((item) => {\n this.node.appendChild(item.node);\n });\n }\n}\n","export default class RulesEngine {\n static rules = [\n \"denyDeNest\",\n \"denyNest\",\n \"denyCollapse\",\n \"denyExpand\",\n \"denyRemove\",\n \"denyDrag\",\n \"denyEdit\",\n ];\n\n constructor(debug = false) {\n if (debug) {\n this.debug = (...args) => console.log(...args);\n } else {\n this.debug = () => {};\n }\n }\n\n /**\n * Enforce structural rules to ensure that the given item is currently in a\n * valid state.\n *\n * @param {Item} item\n */\n normalize(item) {\n // structural rules enforce a valid tree structure\n this.firstItemDepthZero(item);\n this.depthMustBeSet(item);\n this.itemCannotHaveInvalidDepth(item);\n this.parentMustBeLayout(item);\n this.parentCannotHaveExpandedAndCollapsedChildren(item);\n }\n\n /**\n * Apply rules to the given item to determine what operations are permitted.\n *\n * @param {Item} item\n */\n update(item) {\n this.rules = {};\n\n // behavioural rules define what the user is allowed to do\n this.parentsCannotDeNest(item);\n this.rootsCannotDeNest(item);\n this.onlyLastItemCanDeNest(item);\n this.nestingNeedsParent(item);\n this.leavesCannotCollapse(item);\n this.needHiddenItemsToExpand(item);\n this.parentsCannotBeDeleted(item);\n this.parentsCannotBeDragged(item);\n\n RulesEngine.rules.forEach((rule) => {\n item.toggleRule(rule, !!this.rules[rule]);\n });\n }\n\n /**\n * First item can't have a parent, so its depth should always be 0\n */\n firstItemDepthZero(item) {\n if (item.index === 0 && item.depth !== 0) {\n this.debug(`enforce depth on item ${item.index}: ${item.depth} => 0`);\n\n item.depth = 0;\n }\n }\n\n /**\n * Every item should have a non-negative depth set.\n *\n * @param {Item} item\n */\n depthMustBeSet(item) {\n if (isNaN(item.depth) || item.depth < 0) {\n this.debug(`unset depth on item ${item.index}: => 0`);\n\n item.depth = 0;\n }\n }\n\n /**\n * Depth must increase stepwise.\n *\n * @param {Item} item\n */\n itemCannotHaveInvalidDepth(item) {\n const previous = item.previousItem;\n if (previous && previous.depth < item.depth - 1) {\n this.debug(\n `invalid depth on item ${item.index}: ${item.depth} => ${\n previous.depth + 1\n }`,\n );\n\n item.depth = previous.depth + 1;\n }\n }\n\n /**\n * Parent item, if any, must be a layout.\n *\n * @param {Item} item\n */\n parentMustBeLayout(item) {\n // if we're the first child, make sure our parent is a layout\n // if we're a sibling, we know the previous item is valid so we must be too\n const previous = item.previousItem;\n if (previous && previous.depth < item.depth && !previous.isLayout) {\n this.debug(\n `invalid parent for item ${item.index}: ${item.depth} => ${previous.depth}`,\n );\n\n item.depth = previous.depth;\n }\n }\n\n /**\n * If a parent has expanded and collapsed children, expand.\n *\n * @param {Item} item\n */\n parentCannotHaveExpandedAndCollapsedChildren(item) {\n if (item.hasCollapsedDescendants() && item.hasExpandedDescendants()) {\n this.debug(`expanding collapsed children of item ${item.index}`);\n\n item.expand();\n }\n }\n\n /**\n * De-nesting an item would create a gap of 2 between itself and its children\n *\n * @param {Item} item\n */\n parentsCannotDeNest(item) {\n if (item.hasExpandedDescendants()) this.#deny(\"denyDeNest\");\n }\n\n /**\n * Item depth can't go below 0.\n *\n * @param {Item} item\n */\n rootsCannotDeNest(item) {\n if (item.depth === 0) this.#deny(\"denyDeNest\");\n }\n\n /**\n * De-nesting an item that has siblings would make it a container.\n *\n * @param {Item} item\n */\n onlyLastItemCanDeNest(item) {\n const next = item.nextItem;\n if (next && next.depth === item.depth && !item.isLayout)\n this.#deny(\"denyDeNest\");\n }\n\n /**\n * If an item doesn't have children it can't be collapsed.\n *\n * @param {Item} item\n */\n leavesCannotCollapse(item) {\n if (!item.hasExpandedDescendants()) this.#deny(\"denyCollapse\");\n }\n\n /**\n * If an item doesn't have any hidden descendants then it can't be expanded.\n *\n * @param {Item} item\n */\n needHiddenItemsToExpand(item) {\n if (!item.hasCollapsedDescendants()) this.#deny(\"denyExpand\");\n }\n\n /**\n * An item can't be nested (indented) if it doesn't have a valid parent.\n *\n * @param {Item} item\n */\n nestingNeedsParent(item) {\n const previous = item.previousItem;\n // no previous, so cannot nest\n if (!previous) this.#deny(\"denyNest\");\n // previous is too shallow, nesting would increase depth too much\n else if (previous.depth < item.depth) this.#deny(\"denyNest\");\n // new parent is not a layout\n else if (previous.depth === item.depth && !previous.isLayout)\n this.#deny(\"denyNest\");\n }\n\n /**\n * An item can't be deleted if it has visible children.\n *\n * @param {Item} item\n */\n parentsCannotBeDeleted(item) {\n if (!item.itemId || item.hasExpandedDescendants()) this.#deny(\"denyRemove\");\n }\n\n /**\n * Items cannot be dragged if they have visible children.\n *\n * @param {Item} item\n */\n parentsCannotBeDragged(item) {\n if (item.hasExpandedDescendants()) this.#deny(\"denyDrag\");\n }\n\n /**\n * Record a deny.\n *\n * @param rule {String}\n */\n #deny(rule) {\n this.rules[rule] = true;\n }\n}\n","import { Controller } from \"@hotwired/stimulus\";\n\nimport Item from \"./item\";\nimport Container from \"./container\";\nimport RulesEngine from \"./rules_engine\";\n\nexport default class ContainerController extends Controller {\n static targets = [\"container\"];\n\n // Caution: connect is called on attachment, but also on morph/render\n connect() {\n this.state = this.container.state;\n this.reindex();\n }\n\n get container() {\n return new Container(this.containerTarget);\n }\n\n reindex() {\n this.container.reindex();\n this.#update();\n }\n\n reset() {\n this.container.reset();\n }\n\n drop(event) {\n this.container.reindex(); // set indexes before calculating previous\n\n const item = getEventItem(event);\n const previous = item.previousItem;\n\n let delta = 0;\n if (previous === undefined) {\n // if previous does not exist, set depth to 0\n delta = -item.depth;\n } else if (\n previous.isLayout &&\n item.nextItem &&\n item.nextItem.depth > previous.depth\n ) {\n // if previous is a layout and next is a child of previous, make item a child of previous\n delta = previous.depth - item.depth + 1;\n } else {\n // otherwise, make item a sibling of previous\n delta = previous.depth - item.depth;\n }\n\n item.traverse((child) => {\n child.depth += delta;\n });\n\n this.#update();\n event.preventDefault();\n }\n\n remove(event) {\n const item = getEventItem(event);\n\n item.node.remove();\n\n this.#update();\n event.preventDefault();\n }\n\n nest(event) {\n const item = getEventItem(event);\n\n item.traverse((child) => {\n child.depth += 1;\n });\n\n this.#update();\n event.preventDefault();\n }\n\n deNest(event) {\n const item = getEventItem(event);\n\n item.traverse((child) => {\n child.depth -= 1;\n });\n\n this.#update();\n event.preventDefault();\n }\n\n collapse(event) {\n const item = getEventItem(event);\n\n item.collapse();\n\n this.#update();\n event.preventDefault();\n }\n\n expand(event) {\n const item = getEventItem(event);\n\n item.expand();\n\n this.#update();\n event.preventDefault();\n }\n\n /**\n * Re-apply rules to items to enable/disable appropriate actions.\n */\n #update() {\n // debounce requests to ensure that we only update once per tick\n this.updateRequested = true;\n setTimeout(() => {\n if (!this.updateRequested) return;\n\n this.updateRequested = false;\n const engine = new RulesEngine();\n this.container.items.forEach((item) => engine.normalize(item));\n this.container.items.forEach((item) => engine.update(item));\n\n this.#notifyChange();\n }, 0);\n }\n\n #notifyChange() {\n this.dispatch(\"change\", {\n bubbles: true,\n prefix: \"content\",\n detail: { dirty: this.#isDirty() },\n });\n }\n\n #isDirty() {\n return this.container.state !== this.state;\n }\n}\n\nfunction getEventItem(event) {\n return new Item(event.target.closest(\"[data-content-item]\"));\n}\n","import { Controller } from \"@hotwired/stimulus\";\n\nexport default class ListController extends Controller {\n /**\n * When the user starts a drag within the list, set the item's dataTransfer\n * properties to indicate that it's being dragged and update its style.\n *\n * We delay setting the dataset property until the next animation frame\n * so that the style updates can be computed before the drag begins.\n *\n * @param event {DragEvent}\n */\n dragstart(event) {\n if (this.element !== event.target.parentElement) return;\n\n const target = event.target;\n event.dataTransfer.effectAllowed = \"move\";\n\n // update element style after drag has begun\n requestAnimationFrame(() => (target.dataset.dragging = \"\"));\n }\n\n /**\n * When the user drags an item over another item in the last, swap the\n * dragging item with the item under the cursor.\n *\n * @param event {DragEvent}\n */\n dragover(event) {\n const item = this.dragItem;\n if (!item) return;\n\n swap(dropTarget(event.target), item);\n\n event.preventDefault();\n return true;\n }\n\n /**\n * When the user drops an item, end the drag and reindex the list.\n *\n * @param event {DragEvent}\n */\n drop(event) {\n let item = this.dragItem;\n\n if (!item) return;\n\n event.preventDefault();\n delete item.dataset.dragging;\n swap(dropTarget(event.target), item);\n\n this.dispatch(\"drop\", { target: item, bubbles: true, prefix: \"content\" });\n }\n\n /**\n * End an in-progress drag by resetting the item's style and restoring its\n * original position in the list.\n */\n dragend() {\n const item = this.dragItem;\n\n if (item) {\n delete item.dataset.dragging;\n this.reset();\n }\n }\n\n get isDragging() {\n return !!this.dragItem;\n }\n\n get dragItem() {\n return this.element.querySelector(\"[data-dragging]\");\n }\n\n reindex() {\n this.dispatch(\"reindex\", { bubbles: true, prefix: \"content\" });\n }\n\n reset() {\n this.dispatch(\"reset\", { bubbles: true, prefix: \"content\" });\n }\n}\n\n/**\n * Swaps two list items.\n *\n * @param target the target element to swap with\n * @param item the item the user is dragging\n */\nfunction swap(target, item) {\n if (!target) return;\n if (target === item) return;\n\n const positionComparison = target.compareDocumentPosition(item);\n if (positionComparison & Node.DOCUMENT_POSITION_FOLLOWING) {\n target.insertAdjacentElement(\"beforebegin\", item);\n } else if (positionComparison & Node.DOCUMENT_POSITION_PRECEDING) {\n target.insertAdjacentElement(\"afterend\", item);\n }\n}\n\n/**\n * Given an event target, return the closest drop target, if any.\n */\nfunction dropTarget(e) {\n return e && e.closest(\"[data-controller='content--editor--list'] > *\");\n}\n","import { Controller } from \"@hotwired/stimulus\";\nimport \"trix\";\n\n// Note, action_text 7.1.2 changes how Trix is bundled and loaded. This\n// seems to have broken the default export from trix. This is a workaround\n// that relies on the backwards compatibility of the old export to window.Trix.\nconst Trix = window.Trix;\n\n// Stimulus controller doesn't do anything, but having one ensures that trix\n// will be lazy loaded when a trix-editor is added to the dom.\nexport default class TrixController extends Controller {\n trixInitialize(e) {\n this.element.addEventListener(\n \"turbo:before-morph-attribute\",\n this.suppressMorph,\n );\n this.element.addEventListener(\n \"turbo:before-morph-element\",\n this.suppressMorph,\n );\n\n if (this.element.toolbarElement) {\n this.element.toolbarElement.addEventListener(\n \"turbo:before-morph-attribute\",\n this.suppressMorph,\n );\n this.element.toolbarElement.addEventListener(\n \"turbo:before-morph-element\",\n this.suppressMorph,\n );\n }\n }\n\n disconnect() {\n this.element.removeEventListener(\n \"turbo:before-morph-attribute\",\n this.suppressMorph,\n );\n this.element.removeEventListener(\n \"turbo:before-morph-element\",\n this.suppressMorph,\n );\n\n if (this.element.toolbarElement) {\n this.element.toolbarElement.removeEventListener(\n \"turbo:before-morph-attribute\",\n this.suppressMorph,\n );\n this.element.toolbarElement.removeEventListener(\n \"turbo:before-morph-element\",\n this.suppressMorph,\n );\n }\n }\n\n suppressMorph = (e) => {\n // https://github.com/hotwired/turbo-rails/issues/533\n // Note that this will prevent updates from the server from making their way\n // to the trix element. Once the upstream issue is resolved we should remove\n // this compatibility patch.\n if (\n e.target.tagName === \"TRIX-EDITOR\" ||\n e.target.tagName === \"TRIX-TOOLBAR\"\n ) {\n e.preventDefault();\n }\n };\n}\n\n// Add H4 as an acceptable tag\nTrix.config.blockAttributes[\"heading4\"] = {\n tagName: \"h4\",\n terminal: true,\n breakOnReturn: true,\n group: false,\n};\n\n// Remove H1 from trix list of acceptable tags\ndelete Trix.config.blockAttributes.heading1;\n\n/**\n * Allow users to enter path and fragment URIs which the input[type=url] browser\n * input does not permit. Uses a permissive regex pattern which is not suitable\n * for untrusted use cases.\n */\nconst LINK_PATTERN = \"(https?|mailto:|tel:|/|#).*?\";\n\n/**\n * Customize default toolbar:\n *\n * * headings: h4 instead of h1\n * * links: use type=text instead of type=url\n *\n * @returns {String} toolbar html fragment\n */\nTrix.config.toolbar.getDefaultHTML = () => {\n const { lang } = Trix.config;\n return `\n<div class=\"trix-button-row\">\n <span class=\"trix-button-group trix-button-group--text-tools\" data-trix-button-group=\"text-tools\">\n <button type=\"button\" class=\"trix-button trix-button--icon trix-button--icon-bold\" data-trix-attribute=\"bold\" data-trix-key=\"b\" title=\"${lang.bold}\" tabindex=\"-1\">${lang.bold}</button>\n <button type=\"button\" class=\"trix-button trix-button--icon trix-button--icon-italic\" data-trix-attribute=\"italic\" data-trix-key=\"i\" title=\"${lang.italic}\" tabindex=\"-1\">${lang.italic}</button>\n <button type=\"button\" class=\"trix-button trix-button--icon trix-button--icon-strike\" data-trix-attribute=\"strike\" title=\"${lang.strike}\" tabindex=\"-1\">${lang.strike}</button>\n <button type=\"button\" class=\"trix-button trix-button--icon trix-button--icon-link\" data-trix-attribute=\"href\" data-trix-action=\"link\" data-trix-key=\"k\" title=\"${lang.link}\" tabindex=\"-1\">${lang.link}</button>\n </span>\n <span class=\"trix-button-group trix-button-group--block-tools\" data-trix-button-group=\"block-tools\">\n <button type=\"button\" class=\"trix-button trix-button--icon trix-button--icon-heading-1\" data-trix-attribute=\"heading4\" title=\"${lang.heading1}\" tabindex=\"-1\">${lang.heading1}</button>\n <button type=\"button\" class=\"trix-button trix-button--icon trix-button--icon-quote\" data-trix-attribute=\"quote\" title=\"${lang.quote}\" tabindex=\"-1\">${lang.quote}</button>\n <button type=\"button\" class=\"trix-button trix-button--icon trix-button--icon-code\" data-trix-attribute=\"code\" title=\"${lang.code}\" tabindex=\"-1\">${lang.code}</button>\n <button type=\"button\" class=\"trix-button trix-button--icon trix-button--icon-bullet-list\" data-trix-attribute=\"bullet\" title=\"${lang.bullets}\" tabindex=\"-1\">${lang.bullets}</button>\n <button type=\"button\" class=\"trix-button trix-button--icon trix-button--icon-number-list\" data-trix-attribute=\"number\" title=\"${lang.numbers}\" tabindex=\"-1\">${lang.numbers}</button>\n <button type=\"button\" class=\"trix-button trix-button--icon trix-button--icon-decrease-nesting-level\" data-trix-action=\"decreaseNestingLevel\" title=\"${lang.outdent}\" tabindex=\"-1\">${lang.outdent}</button>\n <button type=\"button\" class=\"trix-button trix-button--icon trix-button--icon-increase-nesting-level\" data-trix-action=\"increaseNestingLevel\" title=\"${lang.indent}\" tabindex=\"-1\">${lang.indent}</button>\n </span>\n <span class=\"trix-button-group trix-button-group--file-tools\" data-trix-button-group=\"file-tools\">\n <button type=\"button\" class=\"trix-button trix-button--icon trix-button--icon-attach\" data-trix-action=\"attachFiles\" title=\"${lang.attachFiles}\" tabindex=\"-1\">${lang.attachFiles}</button>\n </span>\n <span class=\"trix-button-group-spacer\"></span>\n <span class=\"trix-button-group trix-button-group--history-tools\" data-trix-button-group=\"history-tools\">\n <button type=\"button\" class=\"trix-button trix-button--icon trix-button--icon-undo\" data-trix-action=\"undo\" data-trix-key=\"z\" title=\"${lang.undo}\" tabindex=\"-1\">${lang.undo}</button>\n <button type=\"button\" class=\"trix-button trix-button--icon trix-button--icon-redo\" data-trix-action=\"redo\" data-trix-key=\"shift+z\" title=\"${lang.redo}\" tabindex=\"-1\">${lang.redo}</button>\n </span>\n</div>\n<div class=\"trix-dialogs\" data-trix-dialogs>\n <div class=\"trix-dialog trix-dialog--link\" data-trix-dialog=\"href\" data-trix-dialog-attribute=\"href\">\n <div class=\"trix-dialog__link-fields\">\n <input type=\"text\" name=\"href\" pattern=\"${LINK_PATTERN}\" class=\"trix-input trix-input--dialog\" placeholder=\"${lang.urlPlaceholder}\" aria-label=\"${lang.url}\" required data-trix-input>\n <div class=\"trix-button-group\">\n <input type=\"button\" class=\"trix-button trix-button--dialog\" value=\"${lang.link}\" data-trix-method=\"setAttribute\">\n <input type=\"button\" class=\"trix-button trix-button--dialog\" value=\"${lang.unlink}\" data-trix-method=\"removeAttribute\">\n </div>\n </div>\n </div>\n</div>\n`;\n};\n\n/**\n * If the <trix-editor> element is in the HTML when Trix loads, then Trix will have already injected the toolbar content\n * before our code gets a chance to run. Fix that now.\n *\n * Note: in Trix 2 this is likely to no longer be necessary.\n */\ndocument.querySelectorAll(\"trix-toolbar\").forEach((e) => {\n e.innerHTML = Trix.config.toolbar.getDefaultHTML();\n});\n","import ContainerController from \"./editor/container_controller\";\nimport ItemController from \"./editor/item_controller\";\nimport ItemEditorController from \"./editor/item_editor_controller\";\nimport ListController from \"./editor/list_controller\";\nimport NewItemsController from \"./editor/new_items_controller\";\nimport StatusBarController from \"./editor/status_bar_controller\";\nimport TableController from \"./editor/table_controller\";\nimport TrixController from \"./editor/trix_controller\";\n\nconst Definitions = [\n {\n identifier: \"content--editor--container\",\n controllerConstructor: ContainerController,\n },\n {\n identifier: \"content--editor--item\",\n controllerConstructor: ItemController,\n },\n {\n identifier: \"content--editor--item-editor\",\n controllerConstructor: ItemEditorController,\n },\n {\n identifier: \"content--editor--list\",\n controllerConstructor: ListController,\n },\n {\n identifier: \"content--editor--new-items\",\n controllerConstructor: NewItemsController,\n },\n {\n identifier: \"content--editor--status-bar\",\n controllerConstructor: StatusBarController,\n },\n {\n identifier: \"content--editor--table\",\n controllerConstructor: TableController,\n },\n {\n identifier: \"content--editor--trix\",\n controllerConstructor: TrixController,\n },\n];\n\nexport { Definitions as default };\n","import { Controller } from \"@hotwired/stimulus\";\nimport Item from \"./item\";\n\nexport default class ItemController extends Controller {\n get item() {\n return new Item(this.li);\n }\n\n get ol() {\n return this.element.closest(\"ol\");\n }\n\n get li() {\n return this.element.closest(\"li\");\n }\n\n connect() {\n if (this.element.dataset.hasOwnProperty(\"delete\")) {\n this.remove();\n }\n // if index is not already set, re-index will set it\n else if (!(this.item.index >= 0)) {\n this.reindex();\n }\n // if item has been replaced via turbo, re-index will run the rules engine\n // update our depth and index with values from the li's data attributes\n else if (this.item.hasItemIdChanged()) {\n this.item.updateAfterChange();\n this.reindex();\n }\n }\n\n remove() {\n // capture ol\n const ol = this.ol;\n // remove self from dom\n this.li.remove();\n // reindex ol\n this.reindex();\n }\n\n reindex() {\n this.dispatch(\"reindex\", { bubbles: true, prefix: \"content\" });\n }\n}\n","import { Controller } from \"@hotwired/stimulus\";\nimport Item from \"./item\";\n\nexport default class ItemEditorController extends Controller {\n static targets = [\"dialog\"];\n\n connect() {\n this.element.addEventListener(\"turbo:submit-end\", this.onSubmit);\n }\n\n disconnect() {\n this.element.removeEventListener(\"turbo:submit-end\", this.onSubmit);\n }\n\n outside(e) {\n if (e.target.tagName === \"DIALOG\") this.dismiss();\n }\n\n dismiss() {\n if (!this.dialogTarget) return;\n if (!this.dialogTarget.open) this.dialogTarget.close();\n\n if (!(\"itemPersisted\" in this.dialogTarget.dataset)) {\n this.#removeTargetItem();\n }\n\n this.element.removeAttribute(\"src\");\n this.dialogTarget.remove();\n }\n\n dialogTargetConnected(dialog) {\n dialog.showModal();\n }\n\n onSubmit = (event) => {\n if (\n event.detail.success &&\n \"closeDialog\" in event.detail.formSubmission?.submitter?.dataset\n ) {\n this.dialogTarget.close();\n this.element.removeAttribute(\"src\");\n this.dialogTarget.remove();\n }\n };\n\n #removeTargetItem() {\n const el = document.getElementById(this.dialogTarget.dataset.itemId);\n const item = new Item(el.closest(\"[data-content-item]\"));\n const list = item.node.parentElement;\n\n item.node.remove();\n\n this.dispatch(\"reindex\", {\n target: list,\n bubbles: true,\n prefix: \"content\",\n });\n }\n}\n","import { Controller } from \"@hotwired/stimulus\";\n\nimport Item from \"./item\";\n\nconst EDGE_AREA = 24;\n\nexport default class NewItemsController extends Controller {\n static targets = [\"inline\"];\n\n connect() {\n this.form.addEventListener(\"mousemove\", this.move);\n }\n\n disconnect() {\n this.form?.removeEventListener(\"mousemove\", this.move);\n delete this.currentItem;\n }\n\n outside(e) {\n if (e.target.tagName === \"DIALOG\") this.close(e);\n }\n\n open(e) {\n e.preventDefault();\n this.dialog.showModal();\n }\n\n close(e) {\n e.preventDefault();\n this.dialog.close();\n }\n\n /**\n * Add the selected item to the DOM at the current position or the end of the list.\n */\n add(e) {\n e.preventDefault();\n\n const template = e.target.closest(\"li\").querySelector(\"template\");\n const item = template.content.querySelector(\"li\").cloneNode(true);\n const target = this.currentItem;\n\n if (target) {\n target.insertAdjacentElement(\"beforebegin\", item);\n new Item(item).depth = new Item(target).depth;\n } else {\n this.list.insertAdjacentElement(\"beforeend\", item);\n }\n\n this.toggleInline(false);\n this.dialog.close();\n\n requestAnimationFrame(() => {\n item.querySelector(`[value=\"edit\"]`).click();\n });\n }\n\n morph(e) {\n e.preventDefault();\n this.dialog.close();\n }\n\n move = (e) => {\n if (this.isOverInlineTarget(e)) return;\n if (this.dialog.open) return;\n\n const target = this.getCurrentItem(e);\n\n // return if we're already showing this item\n if (this.currentItem === target) return;\n\n // hide the button if it's already visible\n if (this.currentItem) this.toggleInline(false);\n\n this.currentItem = target;\n\n // clear any previously set timer\n if (this.timer) clearTimeout(this.timer);\n\n // show the button after a debounce pause\n this.timer = setTimeout(() => {\n delete this.timer;\n this.toggleInline();\n }, 100);\n };\n\n toggleInline(show = !!this.currentItem) {\n if (show) {\n this.inlineTarget.style.top = `${this.currentItem.offsetTop}px`;\n this.inlineTarget.toggleAttribute(\"hidden\", false);\n } else {\n this.inlineTarget.toggleAttribute(\"hidden\", true);\n }\n }\n\n get dialog() {\n return this.element.querySelector(\"dialog\");\n }\n\n /**\n * @returns {HTMLFormElement}\n */\n get form() {\n return this.element.closest(\"form\");\n }\n\n /**\n * @returns {HTMLUListElement,null}\n */\n get list() {\n return this.form.querySelector(`[data-controller=\"content--editor--list\"]`);\n }\n\n /**\n * @param {MouseEvent} e\n * @returns {HTMLLIElement,null}\n */\n getCurrentItem(e) {\n const item = document.elementFromPoint(e.clientX, e.clientY).closest(\"li\");\n if (!item) return null;\n\n const bounds = item.getBoundingClientRect();\n\n // check X for center(ish) mouse position\n if (e.clientX < bounds.left + bounds.width / 2 - 2 * EDGE_AREA) return null;\n if (e.clientX > bounds.left + bounds.width / 2 + 2 * EDGE_AREA) return null;\n\n // check Y for hits on this item or it's next sibling\n if (e.clientY - bounds.y <= EDGE_AREA) {\n return item;\n } else if (bounds.y + bounds.height - e.clientY <= EDGE_AREA) {\n return item.nextElementSibling;\n } else {\n return null;\n }\n }\n\n /**\n * @param {MouseEvent} e\n * @returns {Boolean} true when the target of the event is the floating button\n */\n isOverInlineTarget(e) {\n return (\n this.inlineTarget ===\n document.elementFromPoint(e.clientX, e.clientY).closest(\"div\")\n );\n }\n}\n","import { Controller } from \"@hotwired/stimulus\";\n\nexport default class StatusBarController extends Controller {\n connect() {\n // cache the version's state in the controller on connect\n this.versionState = this.element.dataset.state;\n }\n\n morph(e) {\n if (e.target !== this.element) return;\n\n this.versionState = this.element.dataset.state;\n this.update({ dirty: false });\n }\n\n change(e) {\n if (e.detail && e.detail.hasOwnProperty(\"dirty\")) {\n this.update(e.detail);\n }\n }\n\n update({ dirty }) {\n if (dirty) {\n this.element.dataset.state = \"dirty\";\n } else {\n this.element.dataset.state = this.versionState;\n }\n }\n}\n","import { Controller } from \"@hotwired/stimulus\";\n\nconst EDITOR = `\n<div class=\"content--editor--table-content\"\n contenteditable=\"true\"\n data-content--editor--table-target=\"content\"\n data-action=\"paste->content--editor--table#paste\"\n id=\"item-content-field\">\n</div>`;\n\nexport default class TableController extends Controller {\n static targets = [\"input\", \"update\"];\n\n constructor(config) {\n super(config);\n\n this.observer = new MutationObserver(this.change);\n }\n\n connect() {\n const template = document.createElement(\"TEMPLATE\");\n template.innerHTML = EDITOR;\n this.content = template.content.firstElementChild;\n this.content.innerHTML = this.inputTarget.value;\n this.content.className += ` ${this.inputTarget.className}`;\n this.inputTarget.insertAdjacentElement(\"beforebegin\", this.content);\n this.inputTarget.hidden = true;\n\n this.observer.observe(this.content, {\n attributes: true,\n childList: true,\n characterData: true,\n subtree: true,\n });\n }\n\n disconnect() {\n this.observer.disconnect();\n this.content.remove();\n delete this.content;\n }\n\n change = (mutations) => {\n this.inputTarget.value = this.table?.outerHTML;\n };\n\n update = () => {\n this.updateTarget.click();\n };\n\n paste = (e) => {\n if (e.clipboardData.getData(\"text/html\").indexOf(\"<table\") === -1) return;\n\n e.preventDefault();\n\n this.inputTarget.value = e.clipboardData.getData(\"text/html\");\n\n this.update();\n };\n\n /**\n * @returns {HTMLTableElement} The table element from the content target\n */\n get table() {\n return this.content.querySelector(\"table\");\n }\n}\n"],"names":["Item","comparator","a","b","index","constructor","node","this","itemId","dataset","itemIdInput","querySelector","id","value","depth","parseInt","depthInput","indexInput","isLayout","hasAttribute","previousItem","sibling","previousElementSibling","nextItem","nextElementSibling","hasCollapsedDescendants","childrenList","childrenListElement","children","length","hasExpandedDescendants","traverse","callback","expanded","expandedDescendants","traverseCollapsed","forEach","item","collapsedDescendants","collapseChild","appendChild","collapse","listElement","document","createElement","toggleAttribute","createChildrenList","child","expand","Array","from","reverse","insertAdjacentElement","toggleRule","rule","deny","hasOwnProperty","setAttribute","removeAttribute","hasItemIdChanged","updateAfterChange","descendants","push","map","Container","items","nodes","querySelectorAll","state","inputs","e","join","reindex","reset","sort","RulesEngine","static","debug","args","console","log","normalize","firstItemDepthZero","depthMustBeSet","itemCannotHaveInvalidDepth","parentMustBeLayout","parentCannotHaveExpandedAndCollapsedChildren","update","rules","parentsCannotDeNest","rootsCannotDeNest","onlyLastItemCanDeNest","nestingNeedsParent","leavesCannotCollapse","needHiddenItemsToExpand","parentsCannotBeDeleted","parentsCannotBeDragged","isNaN","previous","next","getEventItem","event","target","closest","swap","positionComparison","compareDocumentPosition","Node","DOCUMENT_POSITION_FOLLOWING","DOCUMENT_POSITION_PRECEDING","dropTarget","Trix","window","config","blockAttributes","tagName","terminal","breakOnReturn","group","heading1","toolbar","getDefaultHTML","lang","bold","italic","strike","link","quote","code","bullets","numbers","outdent","indent","attachFiles","undo","redo","urlPlaceholder","url","unlink","innerHTML","Definitions","identifier","controllerConstructor","Controller","connect","container","containerTarget","drop","delta","undefined","preventDefault","remove","nest","deNest","updateRequested","setTimeout","engine","notifyChange","dispatch","bubbles","prefix","detail","dirty","isDirty","li","ol","element","addEventListener","onSubmit","disconnect","removeEventListener","outside","dismiss","dialogTarget","open","close","removeTargetItem","dialogTargetConnected","dialog","showModal","success","formSubmission","submitter","el","getElementById","list","parentElement","dragstart","dataTransfer","effectAllowed","requestAnimationFrame","dragging","dragover","dragItem","dragend","isDragging","form","move","currentItem","add","content","cloneNode","toggleInline","click","morph","isOverInlineTarget","getCurrentItem","timer","clearTimeout","show","inlineTarget","style","top","offsetTop","elementFromPoint","clientX","clientY","bounds","getBoundingClientRect","left","width","y","height","versionState","change","super","observer","MutationObserver","template","firstElementChild","inputTarget","className","hidden","observe","attributes","childList","characterData","subtree","mutations","table","outerHTML","updateTarget","paste","clipboardData","getData","indexOf","trixInitialize","suppressMorph","toolbarElement"],"mappings":"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"}
@@ -1,4 +1,5 @@
1
1
  @use "editor/editor";
2
2
  @use "editor/icons";
3
3
  @use "editor/status-bar";
4
+ @use "editor/statuses";
4
5
  @use "editor/table";
@@ -0,0 +1,14 @@
1
+ [data-enum="state"][data-value="published"] {
2
+ --tag-background-color: #ebf9eb;
3
+ --tag-color: #4dd45c;
4
+ }
5
+
6
+ [data-enum="state"][data-value="draft"] {
7
+ --tag-background-color: #fefaf3;
8
+ --tag-color: #ffa800;
9
+ }
10
+
11
+ [data-enum="state"][data-value="unpublished"] {
12
+ --tag-background-color: #eee;
13
+ --tag-color: #888;
14
+ }
@@ -1,40 +1,74 @@
1
- [data-content--editor--table-target="content"] {
2
- position: relative;
3
- min-height: 8rem;
4
-
5
- &::after {
6
- content: "Paste table here";
7
- display: block;
8
- position: absolute;
9
- top: 50%;
10
- left: 50%;
11
- transform: translate(-50%, -50%);
12
- opacity: 0.5;
13
- }
1
+ .content--editor--table-editor {
2
+ .content--editor--table-content {
3
+ position: relative;
4
+ min-height: 8rem;
14
5
 
15
- &:has(table)::after {
16
- content: unset;
17
- }
6
+ &::after {
7
+ content: "Paste table here";
8
+ display: block;
9
+ position: absolute;
10
+ top: 50%;
11
+ left: 50%;
12
+ transform: translate(-50%, -50%);
13
+ opacity: 0.5;
14
+ }
18
15
 
19
- table {
20
- border-collapse: collapse;
21
- max-width: 100%;
22
- overflow: hidden;
23
- }
16
+ /* tight wrap the table editor around the table and use table borders */
24
17
 
25
- th,
26
- td {
27
- border: 1px solid var(--color-mid, #9ca3af);
28
- padding: 0.25rem 0.5rem;
29
- text-align: left;
30
- vertical-align: top;
31
- }
18
+ &:has(table) {
19
+ border: none !important;
20
+ padding: 0 !important;
21
+ }
22
+
23
+ &:has(table)::after {
24
+ content: unset;
25
+ }
26
+
27
+ /* ensure the table editor is easy to target when empty */
28
+
29
+ &:not(:has(table)) {
30
+ min-height: 4rem !important;
31
+ }
32
+
33
+ table {
34
+ border: var(--stroke-input);
35
+ border-collapse: collapse;
36
+ max-width: 100%;
37
+ width: 100%;
38
+ overflow: hidden;
39
+ }
40
+
41
+ th,
42
+ td {
43
+ font-size: var(--size-step--1);
44
+ line-height: var(--leading-fine);
45
+ border: var(--stroke-input);
46
+ padding: var(--space-2xs) var(--space-xs);
47
+ text-align: left;
48
+ vertical-align: top;
49
+ }
50
+
51
+ thead {
52
+ background-color: var(--color-mid, #9ca3af);
53
+ }
32
54
 
33
- thead {
34
- background-color: var(--color-mid, #9ca3af);
55
+ tbody th {
56
+ background-color: var(--color-tint, #f0ecf3);
57
+ }
35
58
  }
36
59
 
37
- tbody th {
38
- background-color: var(--color-tint, #f0ecf3);
60
+ /*
61
+ restore webkit spinners, hidden by govuk
62
+ these are not ideal, but spinners are much easier to work with for this
63
+ use case than the default number input, because we submit the form on
64
+ change to implement the live preview
65
+ */
66
+ .govuk-input[type="number"]::-webkit-outer-spin-button,
67
+ .govuk-input[type="number"]::-webkit-inner-spin-button {
68
+ position: relative;
69
+ margin: -3px -3px -3px 0;
70
+ -webkit-appearance: inner-spin-button;
71
+ appearance: auto;
72
+ opacity: 1;
39
73
  }
40
74
  }
@@ -1,4 +1,5 @@
1
1
  @import url("editor/editor.css");
2
2
  @import url("editor/icons.css");
3
3
  @import url("editor/status-bar.css");
4
+ @import url("editor/statuses.css");
4
5
  @import url("editor/table.css");
@@ -33,13 +33,7 @@
33
33
  }
34
34
  }
35
35
 
36
- /*
37
- If a content item has children with a theme applied, we want to steal some space
38
- from the item to give to the children for a gutter. This makes content line up
39
- between items with and without a theme.
40
- */
41
36
  .content-item [data-content-theme] {
42
- margin-inline: calc(-1 * var(--content-inline-gutter));
43
37
  padding-inline: var(--content-inline-gutter);
44
38
  }
45
39
 
@@ -89,7 +83,7 @@
89
83
  .content-columns {
90
84
  display: flex;
91
85
  flex-wrap: wrap;
92
- gap: var(--content-inline-gap) calc(2 * var(--content-inline-gap));
86
+ gap: var(--content-inline-gap);
93
87
 
94
88
  > * {
95
89
  flex-grow: 1;
@@ -104,7 +98,7 @@
104
98
  .content-aside {
105
99
  display: flex;
106
100
  flex-wrap: wrap;
107
- gap: var(--content-inline-gap) calc(2 * var(--content-inline-gap));
101
+ gap: var(--content-inline-gap);
108
102
  align-items: start;
109
103
 
110
104
  > :last-child {
@@ -18,7 +18,10 @@
18
18
  <span class="visually-hidden"><%= t(".add_item_inline") %></span>
19
19
  </button>
20
20
  </div>
21
- <dialog id="content--editor--new-items-dialog" class="modal" data-action="click->content--editor--new-items#click">
21
+ <dialog id="content--editor--new-items-dialog"
22
+ class="modal"
23
+ closedby="any"
24
+ data-action="mousedown->content--editor--new-items#outside">
22
25
  <article class="flow">
23
26
  <header class="repel" data-nowrap>
24
27
  <h2><%= t(".add_item") %></h2>
@@ -9,6 +9,8 @@ module Katalyst
9
9
 
10
10
  attr_reader :container, :item, :editor
11
11
 
12
+ default_form_builder "Katalyst::Content::EditorHelper::FormBuilder"
13
+
12
14
  helper EditorHelper
13
15
 
14
16
  def new
@@ -87,17 +89,6 @@ module Katalyst
87
89
  # This mimics the behaviour of direct uploads without requiring the JS
88
90
  # integration.
89
91
  #
90
- # Note: this idea comes from various blogs who have documented this approach, such as
91
- # https://medium.com/@TETRA2000/active-storage-how-to-retain-uploaded-files-on-form-resubmission-91b57be78d53
92
- #
93
- # In Rails 7.2 the simple version of this approach was broken to work around a bug that Rails plans to address
94
- # in a future release.
95
- # https://github.com/rails/rails/commit/82d4ad5da336a18a55a05a50b851e220032369a0
96
- #
97
- # The work around is to decouple the blobs from their attachments before saving by duping
98
- # them. This approach feels a bit hairy and might need to be replaced by a 'standard' direct upload approach
99
- # in the future.
100
- #
101
92
  # The reason we use this approach currently is to avoid needing a separate direct upload controller for
102
93
  # content which would potentially introduce a back door where un-trusted users are allowed to upload
103
94
  # attachments.
@@ -106,12 +97,10 @@ module Katalyst
106
97
  case change
107
98
  when ActiveStorage::Attached::Changes::CreateOne
108
99
  change.upload
109
- change.attachment.blob = change.blob.dup.tap(&:save!)
100
+ change.blob.save!
110
101
  when ActiveStorage::Attached::Changes::CreateMany
111
102
  change.upload
112
- change.attachments.zip(change.blobs).each do |attachment, blob|
113
- attachment.blob = blob.dup.tap(&:save!)
114
- end
103
+ change.blobs.each(&:save!)
115
104
  end
116
105
  end
117
106
  end
@@ -120,10 +109,6 @@ module Katalyst
120
109
  def prefix_partial_path_with_controller_namespace
121
110
  false
122
111
  end
123
-
124
- def kpop_fallback_location
125
- main_app.root_path
126
- end
127
112
  end
128
113
  end
129
114
  end
@@ -16,6 +16,50 @@ module Katalyst
16
16
  },
17
17
  }.merge_html(attributes)
18
18
  end
19
+
20
+ module Builder
21
+ def content_heading_fieldset(legend: { text: "Heading" })
22
+ govuk_fieldset(legend:) do
23
+ concat(content_heading_field(label: { text: "Heading", class: "govuk-visually-hidden" }))
24
+ concat(content_heading_style_field)
25
+ end
26
+ end
27
+
28
+ def content_heading_field(label: { text: "Heading" }, **)
29
+ govuk_text_field(:heading, label:, **)
30
+ end
31
+
32
+ def content_heading_style_field(legend: { text: "Style" }, **)
33
+ govuk_enum_radio_buttons(:heading_style, legend:, **)
34
+ end
35
+
36
+ def content_url_field(label: { text: "URL" }, **)
37
+ govuk_text_field(:url, label:, **)
38
+ end
39
+
40
+ def content_http_method_field(label: { text: "HTTP method" }, **)
41
+ govuk_enum_select(:http_method, label:, **)
42
+ end
43
+
44
+ def content_target_field(label: { text: "HTTP target" }, **)
45
+ govuk_enum_select(:target, label:, **)
46
+ end
47
+
48
+ def content_theme_field(options: { include_blank: true }, **)
49
+ govuk_enum_select(:theme, options:, **)
50
+ end
51
+
52
+ def content_visible_field(label: { text: "Visible?" }, **)
53
+ govuk_check_box_field(:visible, label:, **)
54
+ end
55
+ end
56
+
57
+ class FormBuilder < ActionView::Helpers::FormBuilder
58
+ include GOVUKDesignSystemFormBuilder::Builder
59
+ include Builder
60
+
61
+ delegate_missing_to :@template
62
+ end
19
63
  end
20
64
  end
21
65
  end
@@ -12,7 +12,7 @@ export default class ItemEditorController extends Controller {
12
12
  this.element.removeEventListener("turbo:submit-end", this.onSubmit);
13
13
  }
14
14
 
15
- click(e) {
15
+ outside(e) {
16
16
  if (e.target.tagName === "DIALOG") this.dismiss();
17
17
  }
18
18
 
@@ -16,7 +16,7 @@ export default class NewItemsController extends Controller {
16
16
  delete this.currentItem;
17
17
  }
18
18
 
19
- click(e) {
19
+ outside(e) {
20
20
  if (e.target.tagName === "DIALOG") this.close(e);
21
21
  }
22
22
 
@@ -1,7 +1,7 @@
1
1
  import { Controller } from "@hotwired/stimulus";
2
2
 
3
3
  const EDITOR = `
4
- <div class="content--editor--table-editor"
4
+ <div class="content--editor--table-content"
5
5
  contenteditable="true"
6
6
  data-content--editor--table-target="content"
7
7
  data-action="paste->content--editor--table#paste"
@@ -42,6 +42,12 @@ module Katalyst
42
42
  node.previous_sibling = previous
43
43
  end
44
44
 
45
+ # If the node does not have an explicit theme set already then set a
46
+ # rendering theme from context.
47
+ if node.theme.blank?
48
+ node.theme = current ? current.theme : Content.config.default_theme
49
+ end
50
+
45
51
  self
46
52
  end
47
53
 
@@ -11,6 +11,7 @@ module Katalyst
11
11
  end
12
12
 
13
13
  enum :heading_style, config.heading_styles, prefix: :heading
14
+ enum :theme, config.themes.index_with(&:itself), prefix: :theme
14
15
 
15
16
  belongs_to :container, polymorphic: true
16
17
 
@@ -59,10 +60,6 @@ module Katalyst
59
60
  model_name.param_key
60
61
  end
61
62
 
62
- def theme
63
- super.presence || parent&.theme || Config.default_theme
64
- end
65
-
66
63
  private
67
64
 
68
65
  def initialize_tree
@@ -1,28 +1,6 @@
1
1
  <%# locals: (form:, aside:) %>
2
2
 
3
- <%= render "form_errors", form: %>
4
-
5
- <div class="field">
6
- <%= form.label :heading %>
7
- <%= form.text_field :heading %>
8
- </div>
9
-
10
- <div class="field">
11
- <%= form.label :heading_style %>
12
- <%= form.collection_radio_buttons :heading_style, Katalyst::Content.config.heading_styles, :itself, :itself %>
13
- </div>
14
-
15
- <div class="field">
16
- <%= form.label :theme %>
17
- <%= form.select :theme, Katalyst::Content.config.themes, include_blank: true %>
18
- </div>
19
-
20
- <div class="field">
21
- <%= form.label :visible %>
22
- <%= form.check_box :visible %>
23
- </div>
24
-
25
- <div class="field">
26
- <%= form.label :reverse %>
27
- <%= form.check_box :reverse %>
28
- </div>
3
+ <%= form.content_heading_fieldset %>
4
+ <%= form.govuk_check_box_field(:reverse, label: { text: "Show aside before content on mobile" }) %>
5
+ <%= form.content_theme_field %>
6
+ <%= form.content_visible_field %>
@@ -1,23 +1,5 @@
1
1
  <%# locals: (form:, column:) %>
2
2
 
3
- <%= render "form_errors", form: %>
4
-
5
- <div class="field">
6
- <%= form.label :heading %>
7
- <%= form.text_field :heading %>
8
- </div>
9
-
10
- <div class="field">
11
- <%= form.label :heading_style %>
12
- <%= form.collection_radio_buttons :heading_style, Katalyst::Content.config.heading_styles, :itself, :itself %>
13
- </div>
14
-
15
- <div class="field">
16
- <%= form.label :theme %>
17
- <%= form.select :theme, Katalyst::Content.config.themes, include_blank: true %>
18
- </div>
19
-
20
- <div class="field">
21
- <%= form.label :visible %>
22
- <%= form.check_box :visible %>
23
- </div>
3
+ <%= form.content_heading_fieldset %>
4
+ <%= form.content_theme_field %>
5
+ <%= form.content_visible_field %>
@@ -1,28 +1,6 @@
1
1
  <%# locals: (form:, content:) %>
2
2
 
3
- <%= render "form_errors", form: %>
4
-
5
- <div class="field">
6
- <%= form.label :heading %>
7
- <%= form.text_field :heading %>
8
- </div>
9
-
10
- <div class="field">
11
- <%= form.label :heading_style %>
12
- <%= form.collection_radio_buttons :heading_style, Katalyst::Content.config.heading_styles, :itself, :itself %>
13
- </div>
14
-
15
- <div class="field">
16
- <%= form.label :theme %>
17
- <%= form.select :theme, Katalyst::Content.config.themes, include_blank: true %>
18
- </div>
19
-
20
- <div class="field">
21
- <%= form.label :visible %>
22
- <%= form.check_box :visible %>
23
- </div>
24
-
25
- <div class="field">
26
- <%= form.label :content %>
27
- <%= form.rich_text_area :content, content_editor_rich_text_attributes %>
28
- </div>
3
+ <%= form.content_heading_fieldset %>
4
+ <%= form.govuk_rich_text_area(:content) %>
5
+ <%= form.content_theme_field %>
6
+ <%= form.content_visible_field %>
@@ -1,33 +1,9 @@
1
1
  <%# locals: (form:, figure:) %>
2
2
 
3
- <%= render "form_errors", form: %>
3
+ <%= form.govuk_image_field(:image) do %>
4
+ <%= form.govuk_text_field(:heading, label: { text: "Alternate text" }) %>
5
+ <%= form.govuk_text_field(:caption, label: { text: "Caption (optional)" }, optional: true) %>
6
+ <% end %>
4
7
 
5
- <div class="field">
6
- <%= form.label :image %>
7
- <% if (image = form.object.image).attached? %>
8
- <%= image.filename %>
9
- <br>
10
- <%= form.hidden_field :image, value: form.object.image.signed_id %>
11
- <% end %>
12
- <%= form.file_field :image %>
13
- </div>
14
-
15
- <div class="field">
16
- <%= form.label :heading %>
17
- <%= form.text_field :heading %>
18
- </div>
19
-
20
- <div class="field">
21
- <%= form.label :theme %>
22
- <%= form.select :theme, Katalyst::Content.config.themes, include_blank: true %>
23
- </div>
24
-
25
- <div class="field">
26
- <%= form.label :visible %>
27
- <%= form.check_box :visible %>
28
- </div>
29
-
30
- <div class="field">
31
- <%= form.label :caption %>
32
- <%= form.text_field :caption %>
33
- </div>
8
+ <%= form.content_theme_field %>
9
+ <%= form.content_visible_field %>
@@ -1,23 +1,5 @@
1
1
  <%# locals: (form:, group:) %>
2
2
 
3
- <%= render "form_errors", form: %>
4
-
5
- <div class="field">
6
- <%= form.label :heading %>
7
- <%= form.text_field :heading %>
8
- </div>
9
-
10
- <div class="field">
11
- <%= form.label :heading_style %>
12
- <%= form.collection_radio_buttons :heading_style, Katalyst::Content.config.heading_styles, :itself, :itself %>
13
- </div>
14
-
15
- <div class="field">
16
- <%= form.label :theme %>
17
- <%= form.select :theme, Katalyst::Content.config.themes, include_blank: true %>
18
- </div>
19
-
20
- <div class="field">
21
- <%= form.label :visible %>
22
- <%= form.check_box :visible %>
23
- </div>
3
+ <%= form.content_heading_fieldset %>
4
+ <%= form.content_theme_field %>
5
+ <%= form.content_visible_field %>
@@ -1,6 +1,6 @@
1
1
  <%# locals: (model:, scope:, url:, id:) %>
2
2
 
3
3
  <%= form_with(model:, scope:, url:, id:) do |form| %>
4
- <%= render "hidden_fields", form: %>
4
+ <%= render("hidden_fields", form:) %>
5
5
  <%= render(model, form:) %>
6
6
  <% end %>
@@ -1,5 +1,3 @@
1
- <%= tag.ul class: "errors" do %>
2
- <% form.object.errors.full_messages.each do |error| %>
3
- <li class="error"><%= error %></li>
4
- <% end %>
5
- <% end if form.object.errors.any? %>
1
+ <%# locals: (form:) %>
2
+
3
+ <%= form.govuk_error_summary %>
@@ -1,3 +1,5 @@
1
+ <%# locals: (form:) %>
2
+
1
3
  <%= form.hidden_field :container_type %>
2
4
  <%= form.hidden_field :container_id %>
3
5
  <%= form.hidden_field :type %>
@@ -1,23 +1,5 @@
1
1
  <%# locals: (form:, item:) %>
2
2
 
3
- <%= render "form_errors", form: %>
4
-
5
- <div class="field">
6
- <%= form.label :heading %>
7
- <%= form.text_field :heading %>
8
- </div>
9
-
10
- <div class="field">
11
- <%= form.label :heading_style %>
12
- <%= form.collection_radio_buttons :heading_style, Katalyst::Content.config.heading_styles, :itself, :itself %>
13
- </div>
14
-
15
- <div class="field">
16
- <%= form.label :theme %>
17
- <%= form.select :theme, Katalyst::Content.config.themes, include_blank: true %>
18
- </div>
19
-
20
- <div class="field">
21
- <%= form.label :visible %>
22
- <%= form.check_box :visible %>
23
- </div>
3
+ <%= form.content_heading_fieldset %>
4
+ <%= form.content_theme_field %>
5
+ <%= form.content_visible_field %>
@@ -8,9 +8,10 @@
8
8
  data: { controller: "content--editor--item-editor" },
9
9
  ) do %>
10
10
  <dialog class="modal"
11
+ closedby="any"
11
12
  data-content--editor--item-editor-target="dialog"
12
13
  data-action="
13
- click->content--editor--item-editor#click
14
+ mousedown->content--editor--item-editor#outside
14
15
  close->content--editor--item-editor#dismiss
15
16
  "
16
17
  data-item-id="<%= dom_id(item_editor.item) %>"
@@ -1,23 +1,5 @@
1
1
  <%# locals: (form:, section:) %>
2
2
 
3
- <%= render "form_errors", form: %>
4
-
5
- <div class="field">
6
- <%= form.label :heading %>
7
- <%= form.text_field :heading %>
8
- </div>
9
-
10
- <div class="field">
11
- <%= form.label :heading_style %>
12
- <%= form.collection_radio_buttons :heading_style, Katalyst::Content.config.heading_styles, :itself, :itself %>
13
- </div>
14
-
15
- <div class="field">
16
- <%= form.label :theme %>
17
- <%= form.select :theme, Katalyst::Content.config.themes, include_blank: true %>
18
- </div>
19
-
20
- <div class="field">
21
- <%= form.label :visible %>
22
- <%= form.check_box :visible %>
23
- </div>
3
+ <%= form.content_heading_fieldset %>
4
+ <%= form.content_theme_field %>
5
+ <%= form.content_visible_field %>
@@ -1,46 +1,39 @@
1
1
  <%# locals: (form:, table:) %>
2
2
 
3
- <div class="flow" data-controller="content--editor--table">
4
- <%= render "form_errors", form: %>
3
+ <div class="content--editor--table-editor | flow" data-controller="content--editor--table">
4
+ <%= form.content_heading_fieldset %>
5
5
 
6
- <div class="field">
7
- <%= form.label :heading %>
8
- <%= form.text_field :heading %>
9
- </div>
6
+ <% content = sanitize_content_table(normalize_content_table(form.object, heading: false)) %>
7
+ <%= form.govuk_text_area(:content,
8
+ value: content,
9
+ class: "table-input",
10
+ data: { content__editor__table_target: "input" }) %>
10
11
 
11
- <div class="field">
12
- <%= form.label :heading_style %>
13
- <%= form.collection_radio_buttons :heading_style, Katalyst::Content.config.heading_styles, :itself, :itself %>
14
- </div>
12
+ <%# hidden button to receive <Enter> events (HTML-default is to click first button in form) %>
13
+ <%= form.button("Save", hidden: "") %>
15
14
 
16
- <div class="field">
17
- <%= form.label :theme %>
18
- <%= form.select :theme, Katalyst::Content.config.themes, include_blank: true %>
19
- </div>
20
-
21
- <div class="field">
22
- <%= form.label :visible %>
23
- <%= form.check_box :visible %>
24
- </div>
25
-
26
- <div class="field">
27
- <%= form.label :content %>
28
- <% content = sanitize_content_table(normalize_content_table(form.object, heading: false)) %>
29
- <%= form.hidden_field :content, value: content, data: { content__editor__table_target: "input" } %>
30
- </div>
31
-
32
- <div class="field">
33
- <%= form.label :heading_rows %>
34
- <%= form.text_field :heading_rows, type: :number, data: { action: "input->content--editor--table#update" } %>
35
- </div>
36
-
37
- <div class="field">
38
- <%= form.label :heading_columns %>
39
- <%= form.text_field :heading_columns, type: :number, data: { action: "input->content--editor--table#update" } %>
40
- </div>
41
-
42
- <%= form.button "Update",
43
- class: "button",
15
+ <%# hidden button to submit the table for re-rendering %>
16
+ <%= form.button("Update",
17
+ hidden: "",
44
18
  formaction: table.persisted? ? katalyst_content.table_path : katalyst_content.tables_path,
45
- data: { ghost_button: "", content__editor__table_target: "update" } %>
19
+ data: { content__editor__table_target: "update" }) %>
20
+
21
+ <%= form.govuk_number_field(:heading_rows,
22
+ label: { text: "Heading rows" },
23
+ width: 2,
24
+ placeholder: 0,
25
+ min: 0,
26
+ data: { content__editor__table_target: "headerRows",
27
+ action: "input->content--editor--table#update" }) %>
28
+
29
+ <%= form.govuk_number_field(:heading_columns,
30
+ label: { text: "Heading columns" },
31
+ width: 2,
32
+ placeholder: 0,
33
+ min: 0,
34
+ data: { content__editor__table_target: "headerColumns",
35
+ action: "input->content--editor--table#update" }) %>
46
36
  </div>
37
+
38
+ <%= form.content_theme_field %>
39
+ <%= form.content_visible_field %>
@@ -4,7 +4,9 @@ class ChangeKatalystContentItemsShowHeadingColumn < ActiveRecord::Migration[7.0]
4
4
  # rubocop:disable Rails/SkipsModelValidations
5
5
  def up
6
6
  add_column :katalyst_content_items, :heading_style, :integer, null: false, default: 0
7
- Katalyst::Content::Item.where(show_heading: true).update_all(heading_style: 1)
7
+ ActiveRecord::Base.connection.execute(<<~SQL)
8
+ UPDATE katalyst_content_items SET heading_style = 1 WHERE show_heading = true;
9
+ SQL
8
10
  remove_column :katalyst_content_items, :show_heading, :boolean
9
11
  end
10
12
  # rubocop:enable Rails/SkipsModelValidations
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ class RemoveNotNullOnKatalystContentItemsTheme < ActiveRecord::Migration[8.0]
4
+ def change
5
+ change_column_null :katalyst_content_items, :theme, true
6
+ end
7
+ end
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "katalyst-govuk-formbuilder"
3
4
  require "katalyst/html_attributes"
4
5
  require "rails/engine"
5
6
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: katalyst-content
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.0.0.alpha.6
4
+ version: 3.0.0.beta.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Katalyst Interactive
@@ -37,6 +37,20 @@ dependencies:
37
37
  - - ">="
38
38
  - !ruby/object:Gem::Version
39
39
  version: '0'
40
+ - !ruby/object:Gem::Dependency
41
+ name: katalyst-govuk-formbuilder
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ type: :runtime
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
40
54
  - !ruby/object:Gem::Dependency
41
55
  name: katalyst-html-attributes
42
56
  requirement: !ruby/object:Gem::Requirement
@@ -99,6 +113,7 @@ files:
99
113
  - app/assets/stylesheets/katalyst/content/editor/editor.css
100
114
  - app/assets/stylesheets/katalyst/content/editor/icons.css
101
115
  - app/assets/stylesheets/katalyst/content/editor/status-bar.css
116
+ - app/assets/stylesheets/katalyst/content/editor/statuses.css
102
117
  - app/assets/stylesheets/katalyst/content/editor/table.css
103
118
  - app/assets/stylesheets/katalyst/content/frontend.css
104
119
  - app/assets/stylesheets/katalyst/content/frontend/frontend.css
@@ -189,6 +204,7 @@ files:
189
204
  - db/migrate/20230515151440_change_katalyst_content_items_show_heading_column.rb
190
205
  - db/migrate/20240826042004_add_style_to_content_items.rb
191
206
  - db/migrate/20250321045027_rename_background_to_theme.rb
207
+ - db/migrate/20250619122652_remove_not_null_on_katalyst_content_items_theme.rb
192
208
  - lib/katalyst/content.rb
193
209
  - lib/katalyst/content/config.rb
194
210
  - lib/katalyst/content/engine.rb