katalyst-content 1.1.1 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +81 -7
- data/app/assets/builds/katalyst/content.esm.js +1004 -0
- data/app/assets/builds/katalyst/content.js +1004 -0
- data/app/assets/builds/katalyst/content.min.js +2 -0
- data/app/assets/builds/katalyst/content.min.js.map +1 -0
- data/app/assets/config/katalyst-content.js +1 -1
- data/app/assets/stylesheets/katalyst/content/editor/_index.scss +1 -1
- data/app/assets/stylesheets/katalyst/content/editor/_new-items.scss +36 -10
- data/app/assets/stylesheets/katalyst/content/editor/_status-bar.scss +3 -1
- data/app/{helpers/katalyst/content/editor/base.rb → components/katalyst/content/editor/base_component.rb} +15 -12
- data/app/components/katalyst/content/editor/errors_component.html.erb +12 -0
- data/app/components/katalyst/content/editor/errors_component.rb +15 -0
- data/app/components/katalyst/content/editor/item_component.html.erb +25 -0
- data/app/components/katalyst/content/editor/item_component.rb +28 -0
- data/app/components/katalyst/content/editor/new_item_component.html.erb +22 -0
- data/app/components/katalyst/content/editor/new_item_component.rb +52 -0
- data/app/{views/katalyst/content/editor/_new_items.html.erb → components/katalyst/content/editor/new_items_component.html.erb} +1 -1
- data/app/components/katalyst/content/editor/new_items_component.rb +20 -0
- data/app/{views/katalyst/content/editor/_list_item.html.erb → components/katalyst/content/editor/row_component.html.erb} +1 -1
- data/app/components/katalyst/content/editor/row_component.rb +13 -0
- data/app/{helpers/katalyst/content/editor/status_bar.rb → components/katalyst/content/editor/status_bar_component.rb} +17 -13
- data/app/components/katalyst/content/editor/table_component.html.erb +11 -0
- data/app/components/katalyst/content/editor/table_component.rb +36 -0
- data/app/components/katalyst/content/editor_component.html.erb +9 -0
- data/app/components/katalyst/content/editor_component.rb +49 -0
- data/app/controllers/katalyst/content/items_controller.rb +6 -3
- data/app/helpers/katalyst/content/editor_helper.rb +4 -48
- data/app/helpers/katalyst/content/frontend_helper.rb +3 -3
- data/app/javascript/content/application.js +35 -0
- data/app/{assets/javascripts/utils → javascript}/content/editor/container.js +1 -1
- data/app/{assets/javascripts/controllers → javascript}/content/editor/container_controller.js +3 -3
- data/app/{assets/javascripts/controllers → javascript}/content/editor/item_controller.js +1 -1
- data/app/{assets/javascripts/controllers → javascript}/content/editor/list_controller.js +12 -2
- data/app/models/katalyst/content/types/nodes_type.rb +2 -2
- data/app/views/active_storage/blobs/_blob.html.erb +1 -1
- data/app/views/katalyst/content/asides/_aside.html+form.erb +2 -2
- data/app/views/katalyst/content/columns/_column.html+form.erb +2 -2
- data/app/views/katalyst/content/contents/_content.html+form.erb +3 -3
- data/app/views/katalyst/content/figures/_figure.html+form.erb +2 -2
- data/app/views/katalyst/content/groups/_group.html+form.erb +2 -2
- data/app/views/katalyst/content/items/_item.html+form.erb +2 -2
- data/app/views/katalyst/content/items/update.turbo_stream.erb +2 -2
- data/app/views/katalyst/content/sections/_section.html+form.erb +2 -2
- data/config/importmap.rb +1 -6
- data/lib/katalyst/content.rb +0 -1
- metadata +61 -24
- data/app/helpers/katalyst/content/editor/container.rb +0 -43
- data/app/helpers/katalyst/content/editor/errors.rb +0 -24
- data/app/helpers/katalyst/content/editor/item.rb +0 -67
- data/app/helpers/katalyst/content/editor/list.rb +0 -41
- data/app/helpers/katalyst/content/editor/new_item.rb +0 -53
- data/app/views/katalyst/content/editor/_item.html.erb +0 -14
- data/app/views/katalyst/content/editor/_new_item.html.erb +0 -3
- data/lib/katalyst/content/version.rb +0 -7
- /data/app/{assets/javascripts/utils → javascript}/content/editor/item.js +0 -0
- /data/app/{assets/javascripts/controllers → javascript}/content/editor/new_item_controller.js +0 -0
- /data/app/{assets/javascripts/utils/content/editor/rules-engine.js → javascript/content/editor/rules_engine.js} +0 -0
- /data/app/{assets/javascripts/controllers → javascript}/content/editor/status_bar_controller.js +0 -0
- /data/app/{assets/javascripts/controllers → javascript}/content/editor/trix_controller.js +0 -0
@@ -0,0 +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.#a;t(this),this.#s(t),e.forEach((e=>e.#s(t)))}#s(t){this.hasCollapsedDescendants()&&this.#r.forEach((e=>{t(e),e.#s(t)}))}collapseChild(t){this.#i.appendChild(t.node)}collapse(){let t=this.#i;t||(t=function(t){const e=document.createElement("ol");return e.setAttribute("class","hidden"),e.dataset.contentChildren="",t.appendChild(e),e}(this.node)),this.#a.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#a(){const t=[];let e=this.nextItem;for(;e&&e.depth>this.depth;)t.push(e),e=e.nextItem;return t}get#r(){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.#o("denyDeNest")}rootsCannotDeNest(t){0===t.depth&&this.#o("denyDeNest")}onlyLastItemCanDeNest(t){const e=t.nextItem;e&&e.depth===t.depth&&!t.isLayout&&this.#o("denyDeNest")}leavesCannotCollapse(t){t.hasExpandedDescendants()||this.#o("denyCollapse")}needHiddenItemsToExpand(t){t.hasCollapsedDescendants()||this.#o("denyExpand")}nestingNeedsParent(t){const e=t.previousItem;e?e.depth<t.depth?this.#o("denyNest"):e.depth!==t.depth||e.isLayout||this.#o("denyNest"):this.#o("denyNest")}parentsCannotBeDeleted(t){t.itemId&&!t.hasExpandedDescendants()||this.#o("denyRemove")}parentsCannotBeDragged(t){t.hasExpandedDescendants()&&this.#o("denyDrag")}#o(t){this.rules[t]=!0}}function a(t){return new e(t.target.closest("[data-content-item]"))}function s(t,e){if(t&&t!==e){if("LI"===t.nodeName){const n=t.compareDocumentPosition(e);n&Node.DOCUMENT_POSITION_FOLLOWING?t.insertAdjacentElement("beforebegin",e):n&Node.DOCUMENT_POSITION_PRECEDING&&t.insertAdjacentElement("afterend",e)}"OL"===t.nodeName&&t.appendChild(e)}}const r=window.Trix;r.config.blockAttributes.heading4={tagName:"h4",terminal:!0,breakOnReturn:!0,group:!1},delete r.config.blockAttributes.heading1;r.config.toolbar.getDefaultHTML=()=>{const{lang:t}=r.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=r.config.toolbar.getDefaultHTML()}));const o=[{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=a(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){a(t).node.remove(),this.#d(),t.preventDefault()}nest(t){a(t).traverse((t=>{t.depth+=1})),this.#d(),t.preventDefault()}deNest(t){a(t).traverse((t=>{t.depth-=1})),this.#d(),t.preventDefault()}collapse(t){a(t).collapse(),this.#d(),t.preventDefault()}expand(t){a(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--list",controllerConstructor:class extends t{dragstart(t){if(this.element!==t.target.parentElement)return;const e=t.target;t.dataTransfer.effectAllowed="move",setTimeout((()=>e.dataset.dragging=""))}dragover(t){const e=this.dragItem();if(e)return s(this.dropTarget(t.target),e),t.preventDefault(),!0}dragenter(t){if(t.preventDefault(),"copy"===t.dataTransfer.effectAllowed&&!this.dragItem()){const t=document.createElement("li");t.dataset.dragging="",t.dataset.newItem="",this.element.prepend(t)}}dragleave(t){const e=this.dragItem(),n=this.dropTarget(t.relatedTarget);e&&!n&&e.dataset.hasOwnProperty("newItem")&&e.remove()}drop(t){let e=this.dragItem();if(e){if(t.preventDefault(),delete e.dataset.dragging,s(this.dropTarget(t.target),e),e.dataset.hasOwnProperty("newItem")){const n=e,i=document.createElement("template");i.innerHTML=t.dataTransfer.getData("text/html"),e=i.content.querySelector("li"),this.element.replaceChild(e,n),setTimeout((()=>e.querySelector("[role='button'][value='edit']").click()))}this.dispatch("drop",{target:e,bubbles:!0,prefix:"content"})}}dragend(){const t=this.dragItem();t&&(delete t.dataset.dragging,this.reset())}dragItem(){return this.element.querySelector("[data-dragging]")}dropTarget(t){return t.closest("[data-controller='content--editor--list'] > *")||t.closest("[data-controller='content--editor--list']")}reindex(){this.dispatch("reindex",{bubbles:!0,prefix:"content"})}reset(){this.dispatch("reset",{bubbles:!0,prefix:"content"})}}},{identifier:"content--editor--new-item",controllerConstructor:class extends t{static targets=["template"];dragstart(t){this.element===t.target&&(t.dataTransfer.setData("text/html",this.templateTarget.innerHTML),t.dataTransfer.effectAllowed="copy")}}},{identifier:"content--editor--status-bar",controllerConstructor:class extends t{connect(){this.versionState=this.element.dataset.state}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--trix",controllerConstructor:class extends t{trixInitialize(t){}}}];export{o as default};
|
2
|
+
//# sourceMappingURL=content.min.js.map
|
@@ -0,0 +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/new_item_controller.js","../../../javascript/content/editor/status_bar_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.setAttribute(\"class\", \"hidden\");\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 connect() {\n this.state = this.container.state;\n\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 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 setTimeout(() => (target.dataset.dragging = \"\"));\n }\n\n dragover(event) {\n const item = this.dragItem();\n if (!item) return;\n\n swap(this.dropTarget(event.target), item);\n\n event.preventDefault();\n return true;\n }\n\n dragenter(event) {\n event.preventDefault();\n\n if (event.dataTransfer.effectAllowed === \"copy\" && !this.dragItem()) {\n const item = document.createElement(\"li\");\n item.dataset.dragging = \"\";\n item.dataset.newItem = \"\";\n this.element.prepend(item);\n }\n }\n\n dragleave(event) {\n const item = this.dragItem();\n const related = this.dropTarget(event.relatedTarget);\n\n // ignore if item is not set or we're moving into a valid drop target\n if (!item || related) return;\n\n // remove item if it's a new item\n if (item.dataset.hasOwnProperty(\"newItem\")) {\n item.remove();\n }\n }\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(this.dropTarget(event.target), item);\n\n if (item.dataset.hasOwnProperty(\"newItem\")) {\n const placeholder = item;\n const template = document.createElement(\"template\");\n template.innerHTML = event.dataTransfer.getData(\"text/html\");\n item = template.content.querySelector(\"li\");\n\n this.element.replaceChild(item, placeholder);\n setTimeout(() =>\n item.querySelector(\"[role='button'][value='edit']\").click()\n );\n }\n\n this.dispatch(\"drop\", { target: item, bubbles: true, prefix: \"content\" });\n }\n\n dragend() {\n const item = this.dragItem();\n if (!item) return;\n\n delete item.dataset.dragging;\n this.reset();\n }\n\n dragItem() {\n return this.element.querySelector(\"[data-dragging]\");\n }\n\n dropTarget(e) {\n return (\n e.closest(\"[data-controller='content--editor--list'] > *\") ||\n e.closest(\"[data-controller='content--editor--list']\")\n );\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\nfunction swap(target, item) {\n if (!target) return;\n if (target === item) return;\n\n if (target.nodeName === \"LI\") {\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 if (target.nodeName === \"OL\") {\n target.appendChild(item);\n }\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 // noop, useful as an extension point for registering behaviour on load\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 ListController from \"./editor/list_controller\";\nimport NewItemController from \"./editor/new_item_controller\";\nimport StatusBarController from \"./editor/status_bar_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--list\",\n controllerConstructor: ListController,\n },\n {\n identifier: \"content--editor--new-item\",\n controllerConstructor: NewItemController,\n },\n {\n identifier: \"content--editor--status-bar\",\n controllerConstructor: StatusBarController,\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\";\n\nexport default class NewItemController extends Controller {\n static targets = [\"template\"];\n\n dragstart(event) {\n if (this.element !== event.target) return;\n\n event.dataTransfer.setData(\"text/html\", this.templateTarget.innerHTML);\n event.dataTransfer.effectAllowed = \"copy\";\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 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"],"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","setAttribute","createChildrenList","child","expand","Array","from","reverse","insertAdjacentElement","toggleRule","rule","deny","hasOwnProperty","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","nodeName","positionComparison","compareDocumentPosition","Node","DOCUMENT_POSITION_FOLLOWING","DOCUMENT_POSITION_PRECEDING","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","dragstart","parentElement","dataTransfer","effectAllowed","dragging","dragover","dragItem","dropTarget","dragenter","newItem","prepend","dragleave","related","relatedTarget","placeholder","template","getData","content","replaceChild","click","dragend","setData","templateTarget","versionState","change","trixInitialize"],"mappings":"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"}
|
@@ -1 +1 @@
|
|
1
|
-
//= link_tree ../
|
1
|
+
//= link_tree ../builds
|
@@ -1,32 +1,46 @@
|
|
1
1
|
@use "icon" as *;
|
2
2
|
|
3
|
+
// provide size for container queries
|
4
|
+
#content--editor--item-frame {
|
5
|
+
container-type: inline-size;
|
6
|
+
}
|
7
|
+
|
3
8
|
.content--editor--new-items {
|
4
9
|
display: grid;
|
5
|
-
grid-template-columns: repeat(3,
|
10
|
+
grid-template-columns: repeat(3, calc((100% - 1rem) / 3));
|
11
|
+
grid-auto-rows: minmax(5rem, auto);
|
6
12
|
gap: 0.5rem;
|
7
13
|
|
8
14
|
[role="listitem"] {
|
9
|
-
display:
|
10
|
-
|
11
|
-
justify-content: center;
|
12
|
-
align-items: center;
|
15
|
+
display: grid;
|
16
|
+
grid-template-rows: 1fr auto;
|
13
17
|
text-align: center;
|
14
18
|
transform: translate3d(0, 0, 0);
|
15
19
|
cursor: grab;
|
16
20
|
background: white;
|
17
|
-
|
18
|
-
|
19
|
-
|
21
|
+
padding: 0.5rem;
|
22
|
+
white-space: nowrap;
|
23
|
+
border-radius: 0.5rem;
|
24
|
+
|
25
|
+
label {
|
26
|
+
font-weight: 400;
|
27
|
+
font-size: 0.8rem;
|
28
|
+
overflow: hidden;
|
29
|
+
text-overflow: ellipsis;
|
30
|
+
margin-bottom: 0;
|
31
|
+
}
|
20
32
|
|
21
33
|
&:hover {
|
22
34
|
box-shadow: rgb(0 0 0 / 25%) 0 1px 2px, rgb(0 0 0 / 31%) 0 0 5px;
|
23
35
|
}
|
24
36
|
|
25
37
|
&::before {
|
38
|
+
display: block;
|
26
39
|
@extend %icon;
|
27
|
-
width:
|
28
|
-
height:
|
40
|
+
width: 2rem;
|
41
|
+
height: 2rem;
|
29
42
|
position: unset;
|
43
|
+
margin: 0 auto;
|
30
44
|
}
|
31
45
|
|
32
46
|
&[data-item-type="content"]:before {
|
@@ -54,3 +68,15 @@
|
|
54
68
|
}
|
55
69
|
}
|
56
70
|
}
|
71
|
+
|
72
|
+
@media (max-width: 75rem) {
|
73
|
+
.content--editor--new-items {
|
74
|
+
grid-template-columns: repeat(2, calc((100% - 0.5rem) / 2));
|
75
|
+
}
|
76
|
+
}
|
77
|
+
|
78
|
+
@media (max-width: 60rem) {
|
79
|
+
.content--editor--new-items {
|
80
|
+
grid-template-columns: 1fr;
|
81
|
+
}
|
82
|
+
}
|
@@ -6,18 +6,20 @@
|
|
6
6
|
color: var(--color);
|
7
7
|
border: 1px solid var(--border);
|
8
8
|
margin-bottom: 1rem;
|
9
|
+
white-space: nowrap;
|
9
10
|
|
10
11
|
display: grid;
|
11
12
|
grid-template-columns: 1fr auto;
|
12
13
|
grid-template-areas: "status actions";
|
13
14
|
align-items: baseline;
|
14
|
-
grid-column-gap: 2rem;
|
15
15
|
|
16
16
|
.status-text {
|
17
17
|
display: none;
|
18
18
|
grid-area: status;
|
19
19
|
font-weight: bold;
|
20
20
|
color: var(--color);
|
21
|
+
overflow: hidden;
|
22
|
+
text-overflow: ellipsis;
|
21
23
|
}
|
22
24
|
|
23
25
|
&[data-state="published"] .status-text[data-published],
|
@@ -3,7 +3,9 @@
|
|
3
3
|
module Katalyst
|
4
4
|
module Content
|
5
5
|
module Editor
|
6
|
-
class Base
|
6
|
+
class BaseComponent < ViewComponent::Base
|
7
|
+
include Katalyst::HtmlAttributes
|
8
|
+
|
7
9
|
CONTAINER_CONTROLLER = "content--editor--container"
|
8
10
|
LIST_CONTROLLER = "content--editor--list"
|
9
11
|
ITEM_CONTROLLER = "content--editor--item"
|
@@ -12,16 +14,19 @@ module Katalyst
|
|
12
14
|
|
13
15
|
TURBO_FRAME = "content--editor--item-frame"
|
14
16
|
|
15
|
-
attr_accessor :
|
17
|
+
attr_accessor :container, :item
|
16
18
|
|
17
19
|
delegate :config, to: ::Katalyst::Content
|
18
|
-
delegate_missing_to :template
|
19
20
|
|
20
|
-
def initialize(
|
21
|
-
|
22
|
-
|
21
|
+
def initialize(container:, item: nil, **)
|
22
|
+
super(**)
|
23
|
+
|
24
|
+
@container = container
|
25
|
+
@item = item
|
23
26
|
end
|
24
27
|
|
28
|
+
def call; end
|
29
|
+
|
25
30
|
def container_form_id
|
26
31
|
dom_id(container, :items)
|
27
32
|
end
|
@@ -30,13 +35,11 @@ module Katalyst
|
|
30
35
|
"#{container.model_name.param_key}[items_attributes][]"
|
31
36
|
end
|
32
37
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
if path.length > 1
|
37
|
-
add_option(options[key] ||= {}, *path)
|
38
|
+
def inspect
|
39
|
+
if item.present?
|
40
|
+
"<#{self.class.name} container: #{container.inspect}, item: #{item.inspect}>"
|
38
41
|
else
|
39
|
-
|
42
|
+
"<#{self.class.name} container: #{container.inspect}>"
|
40
43
|
end
|
41
44
|
end
|
42
45
|
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
<turbo-frame id="<%= dom_id(container, :errors) %>">
|
2
|
+
<% if container.errors.any? %>
|
3
|
+
<%= tag.div(class: "content-errors", **html_attributes) do %>
|
4
|
+
<h2>Errors in content</h2>
|
5
|
+
<ul class="errors">
|
6
|
+
<% container.errors.each do |error| %>
|
7
|
+
<li class="error"><%= error.message %></li>
|
8
|
+
<% end %>
|
9
|
+
</ul>
|
10
|
+
<% end %>
|
11
|
+
<% end %>
|
12
|
+
</turbo-frame>
|
@@ -0,0 +1,25 @@
|
|
1
|
+
<%= tag.div(**html_attributes) do %>
|
2
|
+
<div class="tree" data-invisible="<%= !item.visible? %>">
|
3
|
+
<div role="toolbar" data-tree-accordion-controls>
|
4
|
+
<span role="button" value="collapse" data-action="click-><%= CONTAINER_CONTROLLER %>#collapse" title="Collapse tree"></span>
|
5
|
+
<span role="button" value="expand" data-action="click-><%= CONTAINER_CONTROLLER %>#expand" title="Expand tree"></span>
|
6
|
+
</div>
|
7
|
+
|
8
|
+
<span role="img" value="<%= item.model_name.param_key %>" title="Type"></span>
|
9
|
+
<h4 class="heading" title="<%= item.heading %>"><%= item.heading %></h4>
|
10
|
+
<span role="img" value="invisible" title="Hidden"></span>
|
11
|
+
</div>
|
12
|
+
|
13
|
+
<div class="item-background <%= item.background %>"></div>
|
14
|
+
|
15
|
+
<div role="toolbar" data-tree-controls>
|
16
|
+
<span role="button" value="de-nest" data-action="click-><%= CONTAINER_CONTROLLER %>#deNest" title="Outdent"></span>
|
17
|
+
<span role="button" value="nest" data-action="click-><%= CONTAINER_CONTROLLER %>#nest" title="Indent"></span>
|
18
|
+
<%= link_to("", edit_item_link, role: "button", title: "Edit", value: "edit", data: { turbo_frame: TURBO_FRAME }) %>
|
19
|
+
<span role="button" value="remove" data-action="click-><%= CONTAINER_CONTROLLER %>#remove" title="Remove"></span>
|
20
|
+
</div>
|
21
|
+
|
22
|
+
<input autocomplete="off" type="hidden" name="<%= attributes_scope %>[id]" value="<%= item.id %>">
|
23
|
+
<input autocomplete="off" type="hidden" name="<%= attributes_scope %>[depth]" value="<%= item.depth %>">
|
24
|
+
<input autocomplete="off" type="hidden" name="<%= attributes_scope %>[index]" value="<%= item.index %>">
|
25
|
+
<% end %>
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Katalyst
|
4
|
+
module Content
|
5
|
+
module Editor
|
6
|
+
class ItemComponent < BaseComponent
|
7
|
+
def edit_item_link
|
8
|
+
if item.persisted?
|
9
|
+
helpers.katalyst_content.edit_item_path(item)
|
10
|
+
else
|
11
|
+
helpers.katalyst_content.new_item_path(
|
12
|
+
item: item.attributes.slice("type", "container_id", "container_type").compact,
|
13
|
+
)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def default_html_attributes
|
18
|
+
{
|
19
|
+
id: dom_id(item),
|
20
|
+
data: {
|
21
|
+
controller: ITEM_CONTROLLER,
|
22
|
+
},
|
23
|
+
}
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
<%= tag.div(**html_attributes) do %>
|
2
|
+
<label><%= label %></label>
|
3
|
+
<%#
|
4
|
+
# Template is stored inside the new item dom, and copied into drag
|
5
|
+
# events when the user initiates drag so that it can be copied into the
|
6
|
+
# editor list on drop.
|
7
|
+
#
|
8
|
+
%>
|
9
|
+
<template data-<%= NEW_ITEM_CONTROLLER %>-target="template">
|
10
|
+
<%= render row_component do %>
|
11
|
+
<%= render item_component %>
|
12
|
+
<% end %>
|
13
|
+
</template>
|
14
|
+
<% end %>
|
15
|
+
<%#
|
16
|
+
# Remove items that are incomplete when rendering new items, this
|
17
|
+
# causes incomplete items to be removed from the list when the user
|
18
|
+
# cancels adding a new item by pressing 'discard' in the new item form.
|
19
|
+
%>
|
20
|
+
<%= turbo_stream.replace dom_id(item) do %>
|
21
|
+
<%= render item_component(data: { delete: "" }) %>
|
22
|
+
<% end %>
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Katalyst
|
4
|
+
module Content
|
5
|
+
module Editor
|
6
|
+
class NewItemComponent < BaseComponent
|
7
|
+
include ::Turbo::StreamsHelper
|
8
|
+
|
9
|
+
with_collection_parameter :item
|
10
|
+
|
11
|
+
ACTIONS = <<~ACTIONS.gsub(/\s+/, " ").freeze
|
12
|
+
dragstart->#{NEW_ITEM_CONTROLLER}#dragstart
|
13
|
+
ACTIONS
|
14
|
+
|
15
|
+
def initialize(item:, container: item.container)
|
16
|
+
super(item:, container:)
|
17
|
+
end
|
18
|
+
|
19
|
+
def item_component(**)
|
20
|
+
ItemComponent.new(item:, container:, **)
|
21
|
+
end
|
22
|
+
|
23
|
+
def row_component(**)
|
24
|
+
RowComponent.new(item:, container:, **)
|
25
|
+
end
|
26
|
+
|
27
|
+
def label
|
28
|
+
t("katalyst.content.editor.new_item.#{item_type}", default: item.model_name.human)
|
29
|
+
end
|
30
|
+
|
31
|
+
def item_type
|
32
|
+
item.model_name.param_key
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def default_html_attributes
|
38
|
+
{
|
39
|
+
draggable: "true",
|
40
|
+
role: "listitem",
|
41
|
+
data: {
|
42
|
+
item_type:,
|
43
|
+
turbo_frame: TURBO_FRAME,
|
44
|
+
controller: NEW_ITEM_CONTROLLER,
|
45
|
+
action: ACTIONS,
|
46
|
+
},
|
47
|
+
}
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -1,5 +1,5 @@
|
|
1
1
|
<%= turbo_frame_tag "content--editor--item-frame" do %>
|
2
2
|
<div class="content--editor--new-items" role="listbox">
|
3
|
-
<%= render
|
3
|
+
<%= render Katalyst::Content::Editor::NewItemComponent.with_collection(items) %>
|
4
4
|
</div>
|
5
5
|
<% end %>
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Katalyst
|
4
|
+
module Content
|
5
|
+
module Editor
|
6
|
+
class NewItemsComponent < BaseComponent
|
7
|
+
include ::Turbo::FramesHelper
|
8
|
+
|
9
|
+
renders_many :items, Editor::NewItemComponent
|
10
|
+
|
11
|
+
def items
|
12
|
+
Katalyst::Content.config.items.map do |item_class|
|
13
|
+
item_class = item_class.safe_constantize if item_class.is_a?(String)
|
14
|
+
item_class.new(container:)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -3,13 +3,15 @@
|
|
3
3
|
module Katalyst
|
4
4
|
module Content
|
5
5
|
module Editor
|
6
|
-
class
|
6
|
+
class StatusBarComponent < BaseComponent
|
7
7
|
ACTIONS = <<~ACTIONS.gsub(/\s+/, " ").freeze
|
8
8
|
content:change@document->#{STATUS_BAR_CONTROLLER}#change
|
9
9
|
ACTIONS
|
10
10
|
|
11
|
-
|
12
|
-
|
11
|
+
attr_reader :container
|
12
|
+
|
13
|
+
def call
|
14
|
+
tag.div(**html_attributes) do
|
13
15
|
concat status(:published, last_update: l(container.updated_at, format: :short))
|
14
16
|
concat status(:unpublished)
|
15
17
|
concat status(:draft)
|
@@ -18,8 +20,8 @@ module Katalyst
|
|
18
20
|
end
|
19
21
|
end
|
20
22
|
|
21
|
-
def status(state, **
|
22
|
-
status_text = t("views.katalyst.content.editor.#{state}_html", **
|
23
|
+
def status(state, **)
|
24
|
+
status_text = t("views.katalyst.content.editor.#{state}_html", **)
|
23
25
|
html_options = { class: "status-text", data: { state => "", turbo: false } }
|
24
26
|
|
25
27
|
case state
|
@@ -41,24 +43,26 @@ module Katalyst
|
|
41
43
|
end
|
42
44
|
end
|
43
45
|
|
44
|
-
def action(action, **
|
46
|
+
def action(action, **)
|
45
47
|
tag.li do
|
46
48
|
button_tag(t("views.katalyst.content.editor.#{action}"),
|
47
49
|
name: "commit",
|
48
50
|
value: action,
|
49
51
|
form: container_form_id,
|
50
|
-
**
|
52
|
+
**)
|
51
53
|
end
|
52
54
|
end
|
53
55
|
|
54
56
|
private
|
55
57
|
|
56
|
-
def
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
58
|
+
def default_html_attributes
|
59
|
+
{
|
60
|
+
data: {
|
61
|
+
controller: STATUS_BAR_CONTROLLER,
|
62
|
+
action: ACTIONS,
|
63
|
+
state: container.state,
|
64
|
+
},
|
65
|
+
}
|
62
66
|
end
|
63
67
|
end
|
64
68
|
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Katalyst
|
4
|
+
module Content
|
5
|
+
module Editor
|
6
|
+
class TableComponent < BaseComponent
|
7
|
+
ACTIONS = <<~ACTIONS.gsub(/\s+/, " ").freeze
|
8
|
+
dragstart->#{LIST_CONTROLLER}#dragstart
|
9
|
+
dragover->#{LIST_CONTROLLER}#dragover
|
10
|
+
dragenter->#{LIST_CONTROLLER}#dragenter
|
11
|
+
dragleave->#{LIST_CONTROLLER}#dragleave
|
12
|
+
drop->#{LIST_CONTROLLER}#drop
|
13
|
+
dragend->#{LIST_CONTROLLER}#dragend
|
14
|
+
ACTIONS
|
15
|
+
|
16
|
+
renders_many :items, ->(item) do
|
17
|
+
row = RowComponent.new(item:, container:)
|
18
|
+
row.with_content(render(ItemComponent.new(item:, container:)))
|
19
|
+
row
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def default_html_attributes
|
25
|
+
{
|
26
|
+
data: {
|
27
|
+
controller: LIST_CONTROLLER,
|
28
|
+
action: ACTIONS,
|
29
|
+
"#{CONTAINER_CONTROLLER}_target": "container",
|
30
|
+
},
|
31
|
+
}
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|