Started refactor and alignment of component system
- Updates old components to newer format, removes legacy component support. - Makes component registration easier and less duplicated. - Adds base component class to extend for better editor support. - Aligns global window exposure usage and aligns with other service names.
This commit is contained in:
		
							parent
							
								
									a1b1f8138a
								
							
						
					
					
						commit
						09c6a3c240
					
				| 
						 | 
					@ -27,5 +27,8 @@ window.trans_choice = translator.getPlural.bind(translator);
 | 
				
			||||||
window.trans_plural = translator.parsePlural.bind(translator);
 | 
					window.trans_plural = translator.parsePlural.bind(translator);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Load Components
 | 
					// Load Components
 | 
				
			||||||
import components from "./components"
 | 
					import * as components from "./services/components"
 | 
				
			||||||
components();
 | 
					import * as componentMap from "./components";
 | 
				
			||||||
 | 
					components.register(componentMap);
 | 
				
			||||||
 | 
					window.$components = components;
 | 
				
			||||||
 | 
					components.init();
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,13 +1,13 @@
 | 
				
			||||||
import {onChildEvent} from "../services/dom";
 | 
					import {onChildEvent} from "../services/dom";
 | 
				
			||||||
import {uniqueId} from "../services/util";
 | 
					import {uniqueId} from "../services/util";
 | 
				
			||||||
 | 
					import {Component} from "./component";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * AddRemoveRows
 | 
					 * AddRemoveRows
 | 
				
			||||||
 * Allows easy row add/remove controls onto a table.
 | 
					 * Allows easy row add/remove controls onto a table.
 | 
				
			||||||
 * Needs a model row to use when adding a new row.
 | 
					 * Needs a model row to use when adding a new row.
 | 
				
			||||||
 * @extends {Component}
 | 
					 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
class AddRemoveRows {
 | 
					export class AddRemoveRows extends Component {
 | 
				
			||||||
    setup() {
 | 
					    setup() {
 | 
				
			||||||
        this.modelRow = this.$refs.model;
 | 
					        this.modelRow = this.$refs.model;
 | 
				
			||||||
        this.addButton = this.$refs.add;
 | 
					        this.addButton = this.$refs.add;
 | 
				
			||||||
| 
						 | 
					@ -31,7 +31,7 @@ class AddRemoveRows {
 | 
				
			||||||
        clone.classList.remove('hidden');
 | 
					        clone.classList.remove('hidden');
 | 
				
			||||||
        this.setClonedInputNames(clone);
 | 
					        this.setClonedInputNames(clone);
 | 
				
			||||||
        this.modelRow.parentNode.insertBefore(clone, this.modelRow);
 | 
					        this.modelRow.parentNode.insertBefore(clone, this.modelRow);
 | 
				
			||||||
        window.components.init(clone);
 | 
					        window.$components.init(clone);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
| 
						 | 
					@ -50,5 +50,3 @@ class AddRemoveRows {
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					 | 
				
			||||||
export default AddRemoveRows;
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,10 +1,7 @@
 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * AjaxDelete
 | 
					 | 
				
			||||||
 * @extends {Component}
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
import {onSelect} from "../services/dom";
 | 
					import {onSelect} from "../services/dom";
 | 
				
			||||||
 | 
					import {Component} from "./component";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class AjaxDeleteRow {
 | 
					export class AjaxDeleteRow extends Component {
 | 
				
			||||||
    setup() {
 | 
					    setup() {
 | 
				
			||||||
        this.row = this.$el;
 | 
					        this.row = this.$el;
 | 
				
			||||||
        this.url = this.$opts.url;
 | 
					        this.url = this.$opts.url;
 | 
				
			||||||
| 
						 | 
					@ -28,5 +25,3 @@ class AjaxDeleteRow {
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					 | 
				
			||||||
export default AjaxDeleteRow;
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,4 +1,5 @@
 | 
				
			||||||
import {onEnterPress, onSelect} from "../services/dom";
 | 
					import {onEnterPress, onSelect} from "../services/dom";
 | 
				
			||||||
 | 
					import {Component} from "./component";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Ajax Form
 | 
					 * Ajax Form
 | 
				
			||||||
| 
						 | 
					@ -8,10 +9,8 @@ import {onEnterPress, onSelect} from "../services/dom";
 | 
				
			||||||
 *
 | 
					 *
 | 
				
			||||||
 * Will handle a real form if that's what the component is added to
 | 
					 * Will handle a real form if that's what the component is added to
 | 
				
			||||||
 * otherwise will act as a fake form element.
 | 
					 * otherwise will act as a fake form element.
 | 
				
			||||||
 *
 | 
					 | 
				
			||||||
 * @extends {Component}
 | 
					 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
class AjaxForm {
 | 
					export class AjaxForm extends Component {
 | 
				
			||||||
    setup() {
 | 
					    setup() {
 | 
				
			||||||
        this.container = this.$el;
 | 
					        this.container = this.$el;
 | 
				
			||||||
        this.responseContainer = this.container;
 | 
					        this.responseContainer = this.container;
 | 
				
			||||||
| 
						 | 
					@ -72,11 +71,9 @@ class AjaxForm {
 | 
				
			||||||
            this.responseContainer.innerHTML = err.data;
 | 
					            this.responseContainer.innerHTML = err.data;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        window.components.init(this.responseContainer);
 | 
					        window.$components.init(this.responseContainer);
 | 
				
			||||||
        this.responseContainer.style.opacity = null;
 | 
					        this.responseContainer.style.opacity = null;
 | 
				
			||||||
        this.responseContainer.style.pointerEvents = null;
 | 
					        this.responseContainer.style.pointerEvents = null;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					 | 
				
			||||||
export default AjaxForm;
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,10 +1,11 @@
 | 
				
			||||||
 | 
					import {Component} from "./component";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Attachments List
 | 
					 * Attachments List
 | 
				
			||||||
 * Adds '?open=true' query to file attachment links
 | 
					 * Adds '?open=true' query to file attachment links
 | 
				
			||||||
 * when ctrl/cmd is pressed down.
 | 
					 * when ctrl/cmd is pressed down.
 | 
				
			||||||
 * @extends {Component}
 | 
					 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
class AttachmentsList {
 | 
					export class AttachmentsList extends Component {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    setup() {
 | 
					    setup() {
 | 
				
			||||||
        this.container = this.$el;
 | 
					        this.container = this.$el;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,10 +1,7 @@
 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Attachments
 | 
					 | 
				
			||||||
 * @extends {Component}
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
import {showLoading} from "../services/dom";
 | 
					import {showLoading} from "../services/dom";
 | 
				
			||||||
 | 
					import {Component} from "./component";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Attachments {
 | 
					export class Attachments extends Component {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    setup() {
 | 
					    setup() {
 | 
				
			||||||
        this.container = this.$el;
 | 
					        this.container = this.$el;
 | 
				
			||||||
| 
						 | 
					@ -49,7 +46,7 @@ class Attachments {
 | 
				
			||||||
        this.mainTabs.components.tabs.show('items');
 | 
					        this.mainTabs.components.tabs.show('items');
 | 
				
			||||||
        window.$http.get(`/attachments/get/page/${this.pageId}`).then(resp => {
 | 
					        window.$http.get(`/attachments/get/page/${this.pageId}`).then(resp => {
 | 
				
			||||||
            this.list.innerHTML = resp.data;
 | 
					            this.list.innerHTML = resp.data;
 | 
				
			||||||
            window.components.init(this.list);
 | 
					            window.$components.init(this.list);
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -66,7 +63,7 @@ class Attachments {
 | 
				
			||||||
        showLoading(this.editContainer);
 | 
					        showLoading(this.editContainer);
 | 
				
			||||||
        const resp = await window.$http.get(`/attachments/edit/${id}`);
 | 
					        const resp = await window.$http.get(`/attachments/edit/${id}`);
 | 
				
			||||||
        this.editContainer.innerHTML = resp.data;
 | 
					        this.editContainer.innerHTML = resp.data;
 | 
				
			||||||
        window.components.init(this.editContainer);
 | 
					        window.$components.init(this.editContainer);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    stopEdit() {
 | 
					    stopEdit() {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,5 +1,6 @@
 | 
				
			||||||
 | 
					import {Component} from "./component";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class AutoSubmit {
 | 
					export class AutoSubmit extends Component {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    setup() {
 | 
					    setup() {
 | 
				
			||||||
        this.form = this.$el;
 | 
					        this.form = this.$el;
 | 
				
			||||||
| 
						 | 
					@ -8,5 +9,3 @@ class AutoSubmit {
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					 | 
				
			||||||
export default AutoSubmit;
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,13 +1,13 @@
 | 
				
			||||||
import {escapeHtml} from "../services/util";
 | 
					import {escapeHtml} from "../services/util";
 | 
				
			||||||
import {onChildEvent} from "../services/dom";
 | 
					import {onChildEvent} from "../services/dom";
 | 
				
			||||||
 | 
					import {Component} from "./component";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const ajaxCache = {};
 | 
					const ajaxCache = {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * AutoSuggest
 | 
					 * AutoSuggest
 | 
				
			||||||
 * @extends {Component}
 | 
					 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
class AutoSuggest {
 | 
					export class AutoSuggest extends Component {
 | 
				
			||||||
    setup() {
 | 
					    setup() {
 | 
				
			||||||
        this.parent = this.$el.parentElement;
 | 
					        this.parent = this.$el.parentElement;
 | 
				
			||||||
        this.container = this.$el;
 | 
					        this.container = this.$el;
 | 
				
			||||||
| 
						 | 
					@ -149,5 +149,3 @@ class AutoSuggest {
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					 | 
				
			||||||
export default AutoSuggest;
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,34 +1,35 @@
 | 
				
			||||||
 | 
					import {Component} from "./component";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class BackToTop {
 | 
					export class BackToTop extends Component {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    constructor(elem) {
 | 
					    setup() {
 | 
				
			||||||
        this.elem = elem;
 | 
					        this.button = this.$el;
 | 
				
			||||||
        this.targetElem = document.getElementById('header');
 | 
					        this.targetElem = document.getElementById('header');
 | 
				
			||||||
        this.showing = false;
 | 
					        this.showing = false;
 | 
				
			||||||
        this.breakPoint = 1200;
 | 
					        this.breakPoint = 1200;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (document.body.classList.contains('flexbox')) {
 | 
					        if (document.body.classList.contains('flexbox')) {
 | 
				
			||||||
            this.elem.style.display = 'none';
 | 
					            this.button.style.display = 'none';
 | 
				
			||||||
            return;
 | 
					            return;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        this.elem.addEventListener('click', this.scrollToTop.bind(this));
 | 
					        this.button.addEventListener('click', this.scrollToTop.bind(this));
 | 
				
			||||||
        window.addEventListener('scroll', this.onPageScroll.bind(this));
 | 
					        window.addEventListener('scroll', this.onPageScroll.bind(this));
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    onPageScroll() {
 | 
					    onPageScroll() {
 | 
				
			||||||
        let scrollTopPos = document.documentElement.scrollTop || document.body.scrollTop || 0;
 | 
					        let scrollTopPos = document.documentElement.scrollTop || document.body.scrollTop || 0;
 | 
				
			||||||
        if (!this.showing && scrollTopPos > this.breakPoint) {
 | 
					        if (!this.showing && scrollTopPos > this.breakPoint) {
 | 
				
			||||||
            this.elem.style.display = 'block';
 | 
					            this.button.style.display = 'block';
 | 
				
			||||||
            this.showing = true;
 | 
					            this.showing = true;
 | 
				
			||||||
            setTimeout(() => {
 | 
					            setTimeout(() => {
 | 
				
			||||||
                this.elem.style.opacity = 0.4;
 | 
					                this.button.style.opacity = 0.4;
 | 
				
			||||||
            }, 1);
 | 
					            }, 1);
 | 
				
			||||||
        } else if (this.showing && scrollTopPos < this.breakPoint) {
 | 
					        } else if (this.showing && scrollTopPos < this.breakPoint) {
 | 
				
			||||||
            this.elem.style.opacity = 0;
 | 
					            this.button.style.opacity = 0;
 | 
				
			||||||
            this.showing = false;
 | 
					            this.showing = false;
 | 
				
			||||||
            setTimeout(() => {
 | 
					            setTimeout(() => {
 | 
				
			||||||
                this.elem.style.display = 'none';
 | 
					                this.button.style.display = 'none';
 | 
				
			||||||
            }, 500);
 | 
					            }, 500);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,58 @@
 | 
				
			||||||
 | 
					export class Component {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * The registered name of the component.
 | 
				
			||||||
 | 
					     * @type {string}
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    $name = '';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * The element that the component is registered upon.
 | 
				
			||||||
 | 
					     * @type {Element}
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    $el = null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Mapping of referenced elements within the component.
 | 
				
			||||||
 | 
					     * @type {Object<string, Element>}
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    $refs = {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Mapping of arrays of referenced elements within the component so multiple
 | 
				
			||||||
 | 
					     * references, sharing the same name, can be fetched.
 | 
				
			||||||
 | 
					     * @type {Object<string, Element[]>}
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    $manyRefs = {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Options passed into this component.
 | 
				
			||||||
 | 
					     * @type {Object<String, String>}
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    $opts = {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Component-specific setup methods.
 | 
				
			||||||
 | 
					     * Use this to assign local variables and run any initial setup or actions.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    setup() {
 | 
				
			||||||
 | 
					        //
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Emit an event from this component.
 | 
				
			||||||
 | 
					     * Will be bubbled up from the dom element this is registered on, as a custom event
 | 
				
			||||||
 | 
					     * with the name `<elementName>-<eventName>`, with the provided data in the event detail.
 | 
				
			||||||
 | 
					     * @param {String} eventName
 | 
				
			||||||
 | 
					     * @param {Object} data
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    $emit(eventName, data = {}) {
 | 
				
			||||||
 | 
					        data.from = this;
 | 
				
			||||||
 | 
					        const componentName = this.$name;
 | 
				
			||||||
 | 
					        const event = new CustomEvent(`${componentName}-${eventName}`, {
 | 
				
			||||||
 | 
					            bubbles: true,
 | 
				
			||||||
 | 
					            detail: data
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					        this.$el.dispatchEvent(event);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -74,7 +74,7 @@ class DropDown {
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    hideAll() {
 | 
					    hideAll() {
 | 
				
			||||||
        for (let dropdown of window.components.dropdown) {
 | 
					        for (let dropdown of window.$components.get('dropdown')) {
 | 
				
			||||||
            dropdown.hide();
 | 
					            dropdown.hide();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -132,7 +132,7 @@ class ImageManager {
 | 
				
			||||||
    addReturnedHtmlElementsToList(html) {
 | 
					    addReturnedHtmlElementsToList(html) {
 | 
				
			||||||
        const el = document.createElement('div');
 | 
					        const el = document.createElement('div');
 | 
				
			||||||
        el.innerHTML = html;
 | 
					        el.innerHTML = html;
 | 
				
			||||||
        window.components.init(el);
 | 
					        window.$components.init(el);
 | 
				
			||||||
        for (const child of [...el.children]) {
 | 
					        for (const child of [...el.children]) {
 | 
				
			||||||
            this.listContainer.appendChild(child);
 | 
					            this.listContainer.appendChild(child);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
| 
						 | 
					@ -207,7 +207,7 @@ class ImageManager {
 | 
				
			||||||
        const params = requestDelete ? {delete: true} : {};
 | 
					        const params = requestDelete ? {delete: true} : {};
 | 
				
			||||||
        const {data: formHtml} = await window.$http.get(`/images/edit/${imageId}`, params);
 | 
					        const {data: formHtml} = await window.$http.get(`/images/edit/${imageId}`, params);
 | 
				
			||||||
        this.formContainer.innerHTML = formHtml;
 | 
					        this.formContainer.innerHTML = formHtml;
 | 
				
			||||||
        window.components.init(this.formContainer);
 | 
					        window.$components.init(this.formContainer);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,282 +1,59 @@
 | 
				
			||||||
import addRemoveRows from "./add-remove-rows.js"
 | 
					export {AddRemoveRows} from "./add-remove-rows.js"
 | 
				
			||||||
import ajaxDeleteRow from "./ajax-delete-row.js"
 | 
					export {AjaxDeleteRow} from "./ajax-delete-row.js"
 | 
				
			||||||
import ajaxForm from "./ajax-form.js"
 | 
					export {AjaxForm} from "./ajax-form.js"
 | 
				
			||||||
import attachments from "./attachments.js"
 | 
					export {Attachments} from "./attachments.js"
 | 
				
			||||||
import attachmentsList from "./attachments-list.js"
 | 
					export {AttachmentsList} from "./attachments-list.js"
 | 
				
			||||||
import autoSuggest from "./auto-suggest.js"
 | 
					export {AutoSuggest} from "./auto-suggest.js"
 | 
				
			||||||
import autoSubmit from "./auto-submit.js";
 | 
					export {AutoSubmit} from "./auto-submit.js";
 | 
				
			||||||
import backToTop from "./back-to-top.js"
 | 
					export {BackToTop} from "./back-to-top.js"
 | 
				
			||||||
import bookSort from "./book-sort.js"
 | 
					// export {BookSort} from "./book-sort.js"
 | 
				
			||||||
import chapterContents from "./chapter-contents.js"
 | 
					// export {ChapterContents} from "./chapter-contents.js"
 | 
				
			||||||
import codeEditor from "./code-editor.js"
 | 
					// export {CodeEditor} from "./code-editor.js"
 | 
				
			||||||
import codeHighlighter from "./code-highlighter.js"
 | 
					// export {CodeHighlighter} from "./code-highlighter.js"
 | 
				
			||||||
import codeTextarea from "./code-textarea.js"
 | 
					// export {CodeTextarea} from "./code-textarea.js"
 | 
				
			||||||
import collapsible from "./collapsible.js"
 | 
					// export {Collapsible} from "./collapsible.js"
 | 
				
			||||||
import confirmDialog from "./confirm-dialog"
 | 
					// export {ConfirmDialog} from "./confirm-dialog"
 | 
				
			||||||
import customCheckbox from "./custom-checkbox.js"
 | 
					// export {CustomCheckbox} from "./custom-checkbox.js"
 | 
				
			||||||
import detailsHighlighter from "./details-highlighter.js"
 | 
					// export {DetailsHighlighter} from "./details-highlighter.js"
 | 
				
			||||||
import dropdown from "./dropdown.js"
 | 
					// export {Dropdown} from "./dropdown.js"
 | 
				
			||||||
import dropdownSearch from "./dropdown-search.js"
 | 
					// export {DropdownSearch} from "./dropdown-search.js"
 | 
				
			||||||
import dropzone from "./dropzone.js"
 | 
					// export {Dropzone} from "./dropzone.js"
 | 
				
			||||||
import editorToolbox from "./editor-toolbox.js"
 | 
					// export {EditorToolbox} from "./editor-toolbox.js"
 | 
				
			||||||
import entityPermissions from "./entity-permissions";
 | 
					// export {EntityPermissions} from "./entity-permissions";
 | 
				
			||||||
import entitySearch from "./entity-search.js"
 | 
					// export {EntitySearch} from "./entity-search.js"
 | 
				
			||||||
import entitySelector from "./entity-selector.js"
 | 
					// export {EntitySelector} from "./entity-selector.js"
 | 
				
			||||||
import entitySelectorPopup from "./entity-selector-popup.js"
 | 
					// export {EntitySelectorPopup} from "./entity-selector-popup.js"
 | 
				
			||||||
import eventEmitSelect from "./event-emit-select.js"
 | 
					// export {EventEmitSelect} from "./event-emit-select.js"
 | 
				
			||||||
import expandToggle from "./expand-toggle.js"
 | 
					// export {ExpandToggle} from "./expand-toggle.js"
 | 
				
			||||||
import headerMobileToggle from "./header-mobile-toggle.js"
 | 
					// export {HeaderMobileToggle} from "./header-mobile-toggle.js"
 | 
				
			||||||
import homepageControl from "./homepage-control.js"
 | 
					// export {HomepageControl} from "./homepage-control.js"
 | 
				
			||||||
import imageManager from "./image-manager.js"
 | 
					// export {ImageManager} from "./image-manager.js"
 | 
				
			||||||
import imagePicker from "./image-picker.js"
 | 
					// export {ImagePicker} from "./image-picker.js"
 | 
				
			||||||
import listSortControl from "./list-sort-control.js"
 | 
					// export {ListSortControl} from "./list-sort-control.js"
 | 
				
			||||||
import markdownEditor from "./markdown-editor.js"
 | 
					// export {MarkdownEditor} from "./markdown-editor.js"
 | 
				
			||||||
import newUserPassword from "./new-user-password.js"
 | 
					// export {NewUserPassword} from "./new-user-password.js"
 | 
				
			||||||
import notification from "./notification.js"
 | 
					// export {Notification} from "./notification.js"
 | 
				
			||||||
import optionalInput from "./optional-input.js"
 | 
					// export {OptionalInput} from "./optional-input.js"
 | 
				
			||||||
import pageComments from "./page-comments.js"
 | 
					// export {PageComments} from "./page-comments.js"
 | 
				
			||||||
import pageDisplay from "./page-display.js"
 | 
					// export {PageDisplay} from "./page-display.js"
 | 
				
			||||||
import pageEditor from "./page-editor.js"
 | 
					// export {PageEditor} from "./page-editor.js"
 | 
				
			||||||
import pagePicker from "./page-picker.js"
 | 
					// export {PagePicker} from "./page-picker.js"
 | 
				
			||||||
import permissionsTable from "./permissions-table.js"
 | 
					// export {PermissionsTable} from "./permissions-table.js"
 | 
				
			||||||
import pointer from "./pointer.js";
 | 
					// export {Pointer} from "./pointer.js";
 | 
				
			||||||
import popup from "./popup.js"
 | 
					// export {Popup} from "./popup.js"
 | 
				
			||||||
import settingAppColorPicker from "./setting-app-color-picker.js"
 | 
					// export {SettingAppColorPicker} from "./setting-app-color-picker.js"
 | 
				
			||||||
import settingColorPicker from "./setting-color-picker.js"
 | 
					// export {SettingColorPicker} from "./setting-color-picker.js"
 | 
				
			||||||
import shelfSort from "./shelf-sort.js"
 | 
					// export {ShelfSort} from "./shelf-sort.js"
 | 
				
			||||||
import shortcuts from "./shortcuts";
 | 
					// export {Shortcuts} from "./shortcuts";
 | 
				
			||||||
import shortcutInput from "./shortcut-input";
 | 
					// export {ShortcutInput} from "./shortcut-input";
 | 
				
			||||||
import sidebar from "./sidebar.js"
 | 
					// export {Sidebar} from "./sidebar.js"
 | 
				
			||||||
import sortableList from "./sortable-list.js"
 | 
					// export {SortableList} from "./sortable-list.js"
 | 
				
			||||||
import submitOnChange from "./submit-on-change.js"
 | 
					// export {SubmitOnChange} from "./submit-on-change.js"
 | 
				
			||||||
import tabs from "./tabs.js"
 | 
					// export {Tabs} from "./tabs.js"
 | 
				
			||||||
import tagManager from "./tag-manager.js"
 | 
					// export {TagManager} from "./tag-manager.js"
 | 
				
			||||||
import templateManager from "./template-manager.js"
 | 
					// export {TemplateManager} from "./template-manager.js"
 | 
				
			||||||
import toggleSwitch from "./toggle-switch.js"
 | 
					// export {ToggleSwitch} from "./toggle-switch.js"
 | 
				
			||||||
import triLayout from "./tri-layout.js"
 | 
					// export {TriLayout} from "./tri-layout.js"
 | 
				
			||||||
import userSelect from "./user-select.js"
 | 
					// export {UserSelect} from "./user-select.js"
 | 
				
			||||||
import webhookEvents from "./webhook-events";
 | 
					// export {WebhookEvents} from "./webhook-events";
 | 
				
			||||||
import wysiwygEditor from "./wysiwyg-editor.js"
 | 
					// export {WysiwygEditor} from "./wysiwyg-editor.js"
 | 
				
			||||||
 | 
					 | 
				
			||||||
const componentMapping = {
 | 
					 | 
				
			||||||
    "add-remove-rows": addRemoveRows,
 | 
					 | 
				
			||||||
    "ajax-delete-row": ajaxDeleteRow,
 | 
					 | 
				
			||||||
    "ajax-form": ajaxForm,
 | 
					 | 
				
			||||||
    "attachments": attachments,
 | 
					 | 
				
			||||||
    "attachments-list": attachmentsList,
 | 
					 | 
				
			||||||
    "auto-suggest": autoSuggest,
 | 
					 | 
				
			||||||
    "auto-submit": autoSubmit,
 | 
					 | 
				
			||||||
    "back-to-top": backToTop,
 | 
					 | 
				
			||||||
    "book-sort": bookSort,
 | 
					 | 
				
			||||||
    "chapter-contents": chapterContents,
 | 
					 | 
				
			||||||
    "code-editor": codeEditor,
 | 
					 | 
				
			||||||
    "code-highlighter": codeHighlighter,
 | 
					 | 
				
			||||||
    "code-textarea": codeTextarea,
 | 
					 | 
				
			||||||
    "collapsible": collapsible,
 | 
					 | 
				
			||||||
    "confirm-dialog": confirmDialog,
 | 
					 | 
				
			||||||
    "custom-checkbox": customCheckbox,
 | 
					 | 
				
			||||||
    "details-highlighter": detailsHighlighter,
 | 
					 | 
				
			||||||
    "dropdown": dropdown,
 | 
					 | 
				
			||||||
    "dropdown-search": dropdownSearch,
 | 
					 | 
				
			||||||
    "dropzone": dropzone,
 | 
					 | 
				
			||||||
    "editor-toolbox": editorToolbox,
 | 
					 | 
				
			||||||
    "entity-permissions": entityPermissions,
 | 
					 | 
				
			||||||
    "entity-search": entitySearch,
 | 
					 | 
				
			||||||
    "entity-selector": entitySelector,
 | 
					 | 
				
			||||||
    "entity-selector-popup": entitySelectorPopup,
 | 
					 | 
				
			||||||
    "event-emit-select": eventEmitSelect,
 | 
					 | 
				
			||||||
    "expand-toggle": expandToggle,
 | 
					 | 
				
			||||||
    "header-mobile-toggle": headerMobileToggle,
 | 
					 | 
				
			||||||
    "homepage-control": homepageControl,
 | 
					 | 
				
			||||||
    "image-manager": imageManager,
 | 
					 | 
				
			||||||
    "image-picker": imagePicker,
 | 
					 | 
				
			||||||
    "list-sort-control": listSortControl,
 | 
					 | 
				
			||||||
    "markdown-editor": markdownEditor,
 | 
					 | 
				
			||||||
    "new-user-password": newUserPassword,
 | 
					 | 
				
			||||||
    "notification": notification,
 | 
					 | 
				
			||||||
    "optional-input": optionalInput,
 | 
					 | 
				
			||||||
    "page-comments": pageComments,
 | 
					 | 
				
			||||||
    "page-display": pageDisplay,
 | 
					 | 
				
			||||||
    "page-editor": pageEditor,
 | 
					 | 
				
			||||||
    "page-picker": pagePicker,
 | 
					 | 
				
			||||||
    "permissions-table": permissionsTable,
 | 
					 | 
				
			||||||
    "pointer": pointer,
 | 
					 | 
				
			||||||
    "popup": popup,
 | 
					 | 
				
			||||||
    "setting-app-color-picker": settingAppColorPicker,
 | 
					 | 
				
			||||||
    "setting-color-picker": settingColorPicker,
 | 
					 | 
				
			||||||
    "shelf-sort": shelfSort,
 | 
					 | 
				
			||||||
    "shortcuts": shortcuts,
 | 
					 | 
				
			||||||
    "shortcut-input": shortcutInput,
 | 
					 | 
				
			||||||
    "sidebar": sidebar,
 | 
					 | 
				
			||||||
    "sortable-list": sortableList,
 | 
					 | 
				
			||||||
    "submit-on-change": submitOnChange,
 | 
					 | 
				
			||||||
    "tabs": tabs,
 | 
					 | 
				
			||||||
    "tag-manager": tagManager,
 | 
					 | 
				
			||||||
    "template-manager": templateManager,
 | 
					 | 
				
			||||||
    "toggle-switch": toggleSwitch,
 | 
					 | 
				
			||||||
    "tri-layout": triLayout,
 | 
					 | 
				
			||||||
    "user-select": userSelect,
 | 
					 | 
				
			||||||
    "webhook-events": webhookEvents,
 | 
					 | 
				
			||||||
    "wysiwyg-editor": wysiwygEditor,
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
window.components = {};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Initialize components of the given name within the given element.
 | 
					 | 
				
			||||||
 * @param {String} componentName
 | 
					 | 
				
			||||||
 * @param {HTMLElement|Document} parentElement
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
function searchForComponentInParent(componentName, parentElement) {
 | 
					 | 
				
			||||||
    const elems = parentElement.querySelectorAll(`[${componentName}]`);
 | 
					 | 
				
			||||||
    for (let j = 0, jLen = elems.length; j < jLen; j++) {
 | 
					 | 
				
			||||||
        initComponent(componentName, elems[j]);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Initialize a component instance on the given dom element.
 | 
					 | 
				
			||||||
 * @param {String} name
 | 
					 | 
				
			||||||
 * @param {Element} element
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
function initComponent(name, element) {
 | 
					 | 
				
			||||||
    const componentModel = componentMapping[name];
 | 
					 | 
				
			||||||
    if (componentModel === undefined) return;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Create our component instance
 | 
					 | 
				
			||||||
    let instance;
 | 
					 | 
				
			||||||
    try {
 | 
					 | 
				
			||||||
        instance = new componentModel(element);
 | 
					 | 
				
			||||||
        instance.$el = element;
 | 
					 | 
				
			||||||
        const allRefs = parseRefs(name, element);
 | 
					 | 
				
			||||||
        instance.$refs = allRefs.refs;
 | 
					 | 
				
			||||||
        instance.$manyRefs = allRefs.manyRefs;
 | 
					 | 
				
			||||||
        instance.$opts = parseOpts(name, element);
 | 
					 | 
				
			||||||
        instance.$emit = (eventName, data = {}) => {
 | 
					 | 
				
			||||||
            data.from = instance;
 | 
					 | 
				
			||||||
            const event = new CustomEvent(`${name}-${eventName}`, {
 | 
					 | 
				
			||||||
                bubbles: true,
 | 
					 | 
				
			||||||
                detail: data
 | 
					 | 
				
			||||||
            });
 | 
					 | 
				
			||||||
            instance.$el.dispatchEvent(event);
 | 
					 | 
				
			||||||
        };
 | 
					 | 
				
			||||||
        if (typeof instance.setup === 'function') {
 | 
					 | 
				
			||||||
            instance.setup();
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    } catch (e) {
 | 
					 | 
				
			||||||
        console.error('Failed to create component', e, name, element);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Add to global listing
 | 
					 | 
				
			||||||
    if (typeof window.components[name] === "undefined") {
 | 
					 | 
				
			||||||
        window.components[name] = [];
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    window.components[name].push(instance);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Add to element listing
 | 
					 | 
				
			||||||
    if (typeof element.components === 'undefined') {
 | 
					 | 
				
			||||||
        element.components = {};
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    element.components[name] = instance;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Parse out the element references within the given element
 | 
					 | 
				
			||||||
 * for the given component name.
 | 
					 | 
				
			||||||
 * @param {String} name
 | 
					 | 
				
			||||||
 * @param {Element} element
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
function parseRefs(name, element) {
 | 
					 | 
				
			||||||
    const refs = {};
 | 
					 | 
				
			||||||
    const manyRefs = {};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const prefix = `${name}@`
 | 
					 | 
				
			||||||
    const selector = `[refs*="${prefix}"]`;
 | 
					 | 
				
			||||||
    const refElems = [...element.querySelectorAll(selector)];
 | 
					 | 
				
			||||||
    if (element.matches(selector)) {
 | 
					 | 
				
			||||||
        refElems.push(element);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    for (const el of refElems) {
 | 
					 | 
				
			||||||
        const refNames = el.getAttribute('refs')
 | 
					 | 
				
			||||||
            .split(' ')
 | 
					 | 
				
			||||||
            .filter(str => str.startsWith(prefix))
 | 
					 | 
				
			||||||
            .map(str => str.replace(prefix, ''))
 | 
					 | 
				
			||||||
            .map(kebabToCamel);
 | 
					 | 
				
			||||||
        for (const ref of refNames) {
 | 
					 | 
				
			||||||
            refs[ref] = el;
 | 
					 | 
				
			||||||
            if (typeof manyRefs[ref] === 'undefined') {
 | 
					 | 
				
			||||||
                manyRefs[ref] = [];
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            manyRefs[ref].push(el);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    return {refs, manyRefs};
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Parse out the element component options.
 | 
					 | 
				
			||||||
 * @param {String} name
 | 
					 | 
				
			||||||
 * @param {Element} element
 | 
					 | 
				
			||||||
 * @return {Object<String, String>}
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
function parseOpts(name, element) {
 | 
					 | 
				
			||||||
    const opts = {};
 | 
					 | 
				
			||||||
    const prefix = `option:${name}:`;
 | 
					 | 
				
			||||||
    for (const {name, value} of element.attributes) {
 | 
					 | 
				
			||||||
        if (name.startsWith(prefix)) {
 | 
					 | 
				
			||||||
            const optName = name.replace(prefix, '');
 | 
					 | 
				
			||||||
            opts[kebabToCamel(optName)] = value || '';
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    return opts;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Convert a kebab-case string to camelCase
 | 
					 | 
				
			||||||
 * @param {String} kebab
 | 
					 | 
				
			||||||
 * @returns {string}
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
function kebabToCamel(kebab) {
 | 
					 | 
				
			||||||
    const ucFirst = (word) => word.slice(0,1).toUpperCase() + word.slice(1);
 | 
					 | 
				
			||||||
    const words = kebab.split('-');
 | 
					 | 
				
			||||||
    return words[0] + words.slice(1).map(ucFirst).join('');
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Initialize all components found within the given element.
 | 
					 | 
				
			||||||
 * @param parentElement
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
function initAll(parentElement) {
 | 
					 | 
				
			||||||
    if (typeof parentElement === 'undefined') parentElement = document;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Old attribute system
 | 
					 | 
				
			||||||
    for (const componentName of Object.keys(componentMapping)) {
 | 
					 | 
				
			||||||
        searchForComponentInParent(componentName, parentElement);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // New component system
 | 
					 | 
				
			||||||
    const componentElems = parentElement.querySelectorAll(`[component],[components]`);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    for (const el of componentElems) {
 | 
					 | 
				
			||||||
        const componentNames = `${el.getAttribute('component') || ''} ${(el.getAttribute('components'))}`.toLowerCase().split(' ').filter(Boolean);
 | 
					 | 
				
			||||||
        for (const name of componentNames) {
 | 
					 | 
				
			||||||
            initComponent(name, el);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
window.components.init = initAll;
 | 
					 | 
				
			||||||
window.components.first = (name) => (window.components[name] || [null])[0];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default initAll;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * @typedef Component
 | 
					 | 
				
			||||||
 * @property {HTMLElement} $el
 | 
					 | 
				
			||||||
 * @property {Object<String, HTMLElement>} $refs
 | 
					 | 
				
			||||||
 * @property {Object<String, HTMLElement[]>} $manyRefs
 | 
					 | 
				
			||||||
 * @property {Object<String, String>} $opts
 | 
					 | 
				
			||||||
 * @property {function(string, Object)} $emit
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
| 
						 | 
					@ -90,7 +90,7 @@ class PageComments {
 | 
				
			||||||
            newComment.innerHTML = resp.data;
 | 
					            newComment.innerHTML = resp.data;
 | 
				
			||||||
            this.editingComment.innerHTML = newComment.children[0].innerHTML;
 | 
					            this.editingComment.innerHTML = newComment.children[0].innerHTML;
 | 
				
			||||||
            window.$events.success(this.updatedText);
 | 
					            window.$events.success(this.updatedText);
 | 
				
			||||||
            window.components.init(this.editingComment);
 | 
					            window.$components.init(this.editingComment);
 | 
				
			||||||
            this.closeUpdateForm();
 | 
					            this.closeUpdateForm();
 | 
				
			||||||
            this.editingComment = null;
 | 
					            this.editingComment = null;
 | 
				
			||||||
        }).catch(window.$events.showValidationErrors).then(() => {
 | 
					        }).catch(window.$events.showValidationErrors).then(() => {
 | 
				
			||||||
| 
						 | 
					@ -123,7 +123,7 @@ class PageComments {
 | 
				
			||||||
            newComment.innerHTML = resp.data;
 | 
					            newComment.innerHTML = resp.data;
 | 
				
			||||||
            let newElem = newComment.children[0];
 | 
					            let newElem = newComment.children[0];
 | 
				
			||||||
            this.container.appendChild(newElem);
 | 
					            this.container.appendChild(newElem);
 | 
				
			||||||
            window.components.init(newElem);
 | 
					            window.$components.init(newElem);
 | 
				
			||||||
            window.$events.success(this.createdText);
 | 
					            window.$events.success(this.createdText);
 | 
				
			||||||
            this.resetForm();
 | 
					            this.resetForm();
 | 
				
			||||||
            this.updateCount();
 | 
					            this.updateCount();
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -22,7 +22,7 @@ class PageDisplay {
 | 
				
			||||||
        if (sidebarPageNav) {
 | 
					        if (sidebarPageNav) {
 | 
				
			||||||
            DOM.onChildEvent(sidebarPageNav, 'a', 'click', (event, child) => {
 | 
					            DOM.onChildEvent(sidebarPageNav, 'a', 'click', (event, child) => {
 | 
				
			||||||
                event.preventDefault();
 | 
					                event.preventDefault();
 | 
				
			||||||
                window.components['tri-layout'][0].showContent();
 | 
					                window.$components.first('tri-layout').showContent();
 | 
				
			||||||
                const contentId = child.getAttribute('href').substr(1);
 | 
					                const contentId = child.getAttribute('href').substr(1);
 | 
				
			||||||
                this.goToText(contentId);
 | 
					                this.goToText(contentId);
 | 
				
			||||||
                window.history.pushState(null, null, '#' + contentId);
 | 
					                window.history.pushState(null, null, '#' + contentId);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,153 @@
 | 
				
			||||||
 | 
					const components = {};
 | 
				
			||||||
 | 
					const componentMap = {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Initialize a component instance on the given dom element.
 | 
				
			||||||
 | 
					 * @param {String} name
 | 
				
			||||||
 | 
					 * @param {Element} element
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					function initComponent(name, element) {
 | 
				
			||||||
 | 
					    /** @type {Function<Component>|undefined} **/
 | 
				
			||||||
 | 
					    const componentModel = componentMap[name];
 | 
				
			||||||
 | 
					    if (componentModel === undefined) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Create our component instance
 | 
				
			||||||
 | 
					    /** @type {Component} **/
 | 
				
			||||||
 | 
					    let instance;
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					        instance = new componentModel();
 | 
				
			||||||
 | 
					        instance.$name = name;
 | 
				
			||||||
 | 
					        instance.$el = element;
 | 
				
			||||||
 | 
					        const allRefs = parseRefs(name, element);
 | 
				
			||||||
 | 
					        instance.$refs = allRefs.refs;
 | 
				
			||||||
 | 
					        instance.$manyRefs = allRefs.manyRefs;
 | 
				
			||||||
 | 
					        instance.$opts = parseOpts(name, element);
 | 
				
			||||||
 | 
					        instance.setup();
 | 
				
			||||||
 | 
					    } catch (e) {
 | 
				
			||||||
 | 
					        console.error('Failed to create component', e, name, element);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Add to global listing
 | 
				
			||||||
 | 
					    if (typeof components[name] === "undefined") {
 | 
				
			||||||
 | 
					        components[name] = [];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    components[name].push(instance);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Add to element listing
 | 
				
			||||||
 | 
					    if (typeof element.components === 'undefined') {
 | 
				
			||||||
 | 
					        element.components = {};
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    element.components[name] = instance;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Parse out the element references within the given element
 | 
				
			||||||
 | 
					 * for the given component name.
 | 
				
			||||||
 | 
					 * @param {String} name
 | 
				
			||||||
 | 
					 * @param {Element} element
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					function parseRefs(name, element) {
 | 
				
			||||||
 | 
					    const refs = {};
 | 
				
			||||||
 | 
					    const manyRefs = {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const prefix = `${name}@`
 | 
				
			||||||
 | 
					    const selector = `[refs*="${prefix}"]`;
 | 
				
			||||||
 | 
					    const refElems = [...element.querySelectorAll(selector)];
 | 
				
			||||||
 | 
					    if (element.matches(selector)) {
 | 
				
			||||||
 | 
					        refElems.push(element);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for (const el of refElems) {
 | 
				
			||||||
 | 
					        const refNames = el.getAttribute('refs')
 | 
				
			||||||
 | 
					            .split(' ')
 | 
				
			||||||
 | 
					            .filter(str => str.startsWith(prefix))
 | 
				
			||||||
 | 
					            .map(str => str.replace(prefix, ''))
 | 
				
			||||||
 | 
					            .map(kebabToCamel);
 | 
				
			||||||
 | 
					        for (const ref of refNames) {
 | 
				
			||||||
 | 
					            refs[ref] = el;
 | 
				
			||||||
 | 
					            if (typeof manyRefs[ref] === 'undefined') {
 | 
				
			||||||
 | 
					                manyRefs[ref] = [];
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            manyRefs[ref].push(el);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return {refs, manyRefs};
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Parse out the element component options.
 | 
				
			||||||
 | 
					 * @param {String} name
 | 
				
			||||||
 | 
					 * @param {Element} element
 | 
				
			||||||
 | 
					 * @return {Object<String, String>}
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					function parseOpts(name, element) {
 | 
				
			||||||
 | 
					    const opts = {};
 | 
				
			||||||
 | 
					    const prefix = `option:${name}:`;
 | 
				
			||||||
 | 
					    for (const {name, value} of element.attributes) {
 | 
				
			||||||
 | 
					        if (name.startsWith(prefix)) {
 | 
				
			||||||
 | 
					            const optName = name.replace(prefix, '');
 | 
				
			||||||
 | 
					            opts[kebabToCamel(optName)] = value || '';
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return opts;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Convert a kebab-case string to camelCase
 | 
				
			||||||
 | 
					 * @param {String} kebab
 | 
				
			||||||
 | 
					 * @returns {string}
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					function kebabToCamel(kebab) {
 | 
				
			||||||
 | 
					    const ucFirst = (word) => word.slice(0,1).toUpperCase() + word.slice(1);
 | 
				
			||||||
 | 
					    const words = kebab.split('-');
 | 
				
			||||||
 | 
					    return words[0] + words.slice(1).map(ucFirst).join('');
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Initialize all components found within the given element.
 | 
				
			||||||
 | 
					 * @param {Element|Document} parentElement
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export function init(parentElement = document) {
 | 
				
			||||||
 | 
					    const componentElems = parentElement.querySelectorAll(`[component],[components]`);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for (const el of componentElems) {
 | 
				
			||||||
 | 
					        const componentNames = `${el.getAttribute('component') || ''} ${(el.getAttribute('components'))}`.toLowerCase().split(' ').filter(Boolean);
 | 
				
			||||||
 | 
					        for (const name of componentNames) {
 | 
				
			||||||
 | 
					            initComponent(name, el);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Register the given component mapping into the component system.
 | 
				
			||||||
 | 
					 * @param {Object<String, ObjectConstructor<Component>>} mapping
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export function register(mapping) {
 | 
				
			||||||
 | 
					    const keys = Object.keys(mapping);
 | 
				
			||||||
 | 
					    for (const key of keys) {
 | 
				
			||||||
 | 
					        componentMap[camelToKebab(key)] = mapping[key];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    console.log(componentMap);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Get the first component of the given name.
 | 
				
			||||||
 | 
					 * @param {String} name
 | 
				
			||||||
 | 
					 * @returns {Component|null}
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export function first(name) {
 | 
				
			||||||
 | 
					    return (components[name] || [null])[0];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Get all the components of the given name.
 | 
				
			||||||
 | 
					 * @param {String} name
 | 
				
			||||||
 | 
					 * @returns {Component[]}
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export function get(name = '') {
 | 
				
			||||||
 | 
					    return components[name] || [];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function camelToKebab(camelStr) {
 | 
				
			||||||
 | 
					    return camelStr.replace(/[A-Z]/g, (str, offset) =>  (offset > 0 ? '-' : '') + str.toLowerCase());
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -128,6 +128,6 @@ export function removeLoading(element) {
 | 
				
			||||||
export function htmlToDom(html) {
 | 
					export function htmlToDom(html) {
 | 
				
			||||||
    const wrap = document.createElement('div');
 | 
					    const wrap = document.createElement('div');
 | 
				
			||||||
    wrap.innerHTML = html;
 | 
					    wrap.innerHTML = html;
 | 
				
			||||||
    window.components.init(wrap);
 | 
					    window.$components.init(wrap);
 | 
				
			||||||
    return wrap.children[0];
 | 
					    return wrap.children[0];
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -9,7 +9,7 @@ function elemIsCodeBlock(elem) {
 | 
				
			||||||
 * @param {function(string, string)} callback (Receives (code: string,language: string)
 | 
					 * @param {function(string, string)} callback (Receives (code: string,language: string)
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
function showPopup(editor, code, language, callback) {
 | 
					function showPopup(editor, code, language, callback) {
 | 
				
			||||||
    window.components.first('code-editor').open(code, language, (newCode, newLang) => {
 | 
					    window.$components.first('code-editor').open(code, language, (newCode, newLang) => {
 | 
				
			||||||
        callback(newCode, newLang)
 | 
					        callback(newCode, newLang)
 | 
				
			||||||
        editor.focus()
 | 
					        editor.focus()
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1011,3 +1011,40 @@ body.flexbox-support #entity-selector-wrap .popup-body .form-group {
 | 
				
			||||||
  box-shadow: 0 1px 1px rgba(0, 0, 0, .2), 0 2px 0 0 rgba(255, 255, 255, .7) inset;
 | 
					  box-shadow: 0 1px 1px rgba(0, 0, 0, .2), 0 2px 0 0 rgba(255, 255, 255, .7) inset;
 | 
				
			||||||
  color: #333;
 | 
					  color: #333;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Back to top link
 | 
				
			||||||
 | 
					$btt-size: 40px;
 | 
				
			||||||
 | 
					.back-to-top {
 | 
				
			||||||
 | 
					  background-color: var(--color-primary);
 | 
				
			||||||
 | 
					  position: fixed;
 | 
				
			||||||
 | 
					  bottom: $-m;
 | 
				
			||||||
 | 
					  right: $-l;
 | 
				
			||||||
 | 
					  padding: 5px 7px;
 | 
				
			||||||
 | 
					  cursor: pointer;
 | 
				
			||||||
 | 
					  color: #FFF;
 | 
				
			||||||
 | 
					  fill: #FFF;
 | 
				
			||||||
 | 
					  svg {
 | 
				
			||||||
 | 
					    width: math.div($btt-size, 1.5);
 | 
				
			||||||
 | 
					    height: math.div($btt-size, 1.5);
 | 
				
			||||||
 | 
					    margin-inline-end: 4px;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  width: $btt-size;
 | 
				
			||||||
 | 
					  height: $btt-size;
 | 
				
			||||||
 | 
					  border-radius: $btt-size;
 | 
				
			||||||
 | 
					  transition: all ease-in-out 180ms;
 | 
				
			||||||
 | 
					  opacity: 0;
 | 
				
			||||||
 | 
					  z-index: 999;
 | 
				
			||||||
 | 
					  overflow: hidden;
 | 
				
			||||||
 | 
					  &:hover {
 | 
				
			||||||
 | 
					    width: $btt-size*3.4;
 | 
				
			||||||
 | 
					    opacity: 1 !important;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  .inner {
 | 
				
			||||||
 | 
					    width: $btt-size*3.4;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  span {
 | 
				
			||||||
 | 
					    position: relative;
 | 
				
			||||||
 | 
					    vertical-align: top;
 | 
				
			||||||
 | 
					    line-height: 2;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -100,43 +100,6 @@ $loadingSize: 10px;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Back to top link
 | 
					 | 
				
			||||||
$btt-size: 40px;
 | 
					 | 
				
			||||||
[back-to-top] {
 | 
					 | 
				
			||||||
  background-color: var(--color-primary);
 | 
					 | 
				
			||||||
  position: fixed;
 | 
					 | 
				
			||||||
  bottom: $-m;
 | 
					 | 
				
			||||||
  right: $-l;
 | 
					 | 
				
			||||||
  padding: 5px 7px;
 | 
					 | 
				
			||||||
  cursor: pointer;
 | 
					 | 
				
			||||||
  color: #FFF;
 | 
					 | 
				
			||||||
  fill: #FFF;
 | 
					 | 
				
			||||||
  svg {
 | 
					 | 
				
			||||||
    width: math.div($btt-size, 1.5);
 | 
					 | 
				
			||||||
    height: math.div($btt-size, 1.5);
 | 
					 | 
				
			||||||
    margin-inline-end: 4px;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  width: $btt-size;
 | 
					 | 
				
			||||||
  height: $btt-size;
 | 
					 | 
				
			||||||
  border-radius: $btt-size;
 | 
					 | 
				
			||||||
  transition: all ease-in-out 180ms;
 | 
					 | 
				
			||||||
  opacity: 0;
 | 
					 | 
				
			||||||
  z-index: 999;
 | 
					 | 
				
			||||||
  overflow: hidden;
 | 
					 | 
				
			||||||
  &:hover {
 | 
					 | 
				
			||||||
    width: $btt-size*3.4;
 | 
					 | 
				
			||||||
    opacity: 1 !important;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  .inner {
 | 
					 | 
				
			||||||
    width: $btt-size*3.4;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  span {
 | 
					 | 
				
			||||||
    position: relative;
 | 
					 | 
				
			||||||
    vertical-align: top;
 | 
					 | 
				
			||||||
    line-height: 2;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.skip-to-content-link {
 | 
					.skip-to-content-link {
 | 
				
			||||||
  position: fixed;
 | 
					  position: fixed;
 | 
				
			||||||
  top: -52px;
 | 
					  top: -52px;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -49,7 +49,7 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @include('common.footer')
 | 
					    @include('common.footer')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <div back-to-top class="primary-background print-hidden">
 | 
					    <div component="back-to-top" class="back-to-top print-hidden">
 | 
				
			||||||
        <div class="inner">
 | 
					        <div class="inner">
 | 
				
			||||||
            @icon('chevron-up') <span>{{ trans('common.back_to_top') }}</span>
 | 
					            @icon('chevron-up') <span>{{ trans('common.back_to_top') }}</span>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue