1 import addRemoveRows from "./add-remove-rows.js"
2 import ajaxDeleteRow from "./ajax-delete-row.js"
3 import ajaxForm from "./ajax-form.js"
4 import attachments from "./attachments.js"
5 import attachmentsList from "./attachments-list.js"
6 import autoSuggest from "./auto-suggest.js"
7 import backToTop from "./back-to-top.js"
8 import bookSort from "./book-sort.js"
9 import chapterContents from "./chapter-contents.js"
10 import codeEditor from "./code-editor.js"
11 import codeHighlighter from "./code-highlighter.js"
12 import codeTextarea from "./code-textarea.js"
13 import collapsible from "./collapsible.js"
14 import confirmDialog from "./confirm-dialog"
15 import customCheckbox from "./custom-checkbox.js"
16 import detailsHighlighter from "./details-highlighter.js"
17 import dropdown from "./dropdown.js"
18 import dropdownSearch from "./dropdown-search.js"
19 import dropzone from "./dropzone.js"
20 import editorToolbox from "./editor-toolbox.js"
21 import entityPermissions from "./entity-permissions";
22 import entitySearch from "./entity-search.js"
23 import entitySelector from "./entity-selector.js"
24 import entitySelectorPopup from "./entity-selector-popup.js"
25 import eventEmitSelect from "./event-emit-select.js"
26 import expandToggle from "./expand-toggle.js"
27 import headerMobileToggle from "./header-mobile-toggle.js"
28 import homepageControl from "./homepage-control.js"
29 import imageManager from "./image-manager.js"
30 import imagePicker from "./image-picker.js"
31 import listSortControl from "./list-sort-control.js"
32 import markdownEditor from "./markdown-editor.js"
33 import newUserPassword from "./new-user-password.js"
34 import notification from "./notification.js"
35 import optionalInput from "./optional-input.js"
36 import pageComments from "./page-comments.js"
37 import pageDisplay from "./page-display.js"
38 import pageEditor from "./page-editor.js"
39 import pagePicker from "./page-picker.js"
40 import permissionsTable from "./permissions-table.js"
41 import pointer from "./pointer.js";
42 import popup from "./popup.js"
43 import settingAppColorPicker from "./setting-app-color-picker.js"
44 import settingColorPicker from "./setting-color-picker.js"
45 import shelfSort from "./shelf-sort.js"
46 import shortcuts from "./shortcuts";
47 import shortcutInput from "./shortcut-input";
48 import sidebar from "./sidebar.js"
49 import sortableList from "./sortable-list.js"
50 import submitOnChange from "./submit-on-change.js"
51 import tabs from "./tabs.js"
52 import tagManager from "./tag-manager.js"
53 import templateManager from "./template-manager.js"
54 import toggleSwitch from "./toggle-switch.js"
55 import triLayout from "./tri-layout.js"
56 import userSelect from "./user-select.js"
57 import webhookEvents from "./webhook-events";
58 import wysiwygEditor from "./wysiwyg-editor.js"
60 const componentMapping = {
61 "add-remove-rows": addRemoveRows,
62 "ajax-delete-row": ajaxDeleteRow,
63 "ajax-form": ajaxForm,
64 "attachments": attachments,
65 "attachments-list": attachmentsList,
66 "auto-suggest": autoSuggest,
67 "back-to-top": backToTop,
68 "book-sort": bookSort,
69 "chapter-contents": chapterContents,
70 "code-editor": codeEditor,
71 "code-highlighter": codeHighlighter,
72 "code-textarea": codeTextarea,
73 "collapsible": collapsible,
74 "confirm-dialog": confirmDialog,
75 "custom-checkbox": customCheckbox,
76 "details-highlighter": detailsHighlighter,
78 "dropdown-search": dropdownSearch,
80 "editor-toolbox": editorToolbox,
81 "entity-permissions": entityPermissions,
82 "entity-search": entitySearch,
83 "entity-selector": entitySelector,
84 "entity-selector-popup": entitySelectorPopup,
85 "event-emit-select": eventEmitSelect,
86 "expand-toggle": expandToggle,
87 "header-mobile-toggle": headerMobileToggle,
88 "homepage-control": homepageControl,
89 "image-manager": imageManager,
90 "image-picker": imagePicker,
91 "list-sort-control": listSortControl,
92 "markdown-editor": markdownEditor,
93 "new-user-password": newUserPassword,
94 "notification": notification,
95 "optional-input": optionalInput,
96 "page-comments": pageComments,
97 "page-display": pageDisplay,
98 "page-editor": pageEditor,
99 "page-picker": pagePicker,
100 "permissions-table": permissionsTable,
103 "setting-app-color-picker": settingAppColorPicker,
104 "setting-color-picker": settingColorPicker,
105 "shelf-sort": shelfSort,
106 "shortcuts": shortcuts,
107 "shortcut-input": shortcutInput,
109 "sortable-list": sortableList,
110 "submit-on-change": submitOnChange,
112 "tag-manager": tagManager,
113 "template-manager": templateManager,
114 "toggle-switch": toggleSwitch,
115 "tri-layout": triLayout,
116 "user-select": userSelect,
117 "webhook-events": webhookEvents,
118 "wysiwyg-editor": wysiwygEditor,
121 window.components = {};
124 * Initialize components of the given name within the given element.
125 * @param {String} componentName
126 * @param {HTMLElement|Document} parentElement
128 function searchForComponentInParent(componentName, parentElement) {
129 const elems = parentElement.querySelectorAll(`[${componentName}]`);
130 for (let j = 0, jLen = elems.length; j < jLen; j++) {
131 initComponent(componentName, elems[j]);
136 * Initialize a component instance on the given dom element.
137 * @param {String} name
138 * @param {Element} element
140 function initComponent(name, element) {
141 const componentModel = componentMapping[name];
142 if (componentModel === undefined) return;
144 // Create our component instance
147 instance = new componentModel(element);
148 instance.$el = element;
149 const allRefs = parseRefs(name, element);
150 instance.$refs = allRefs.refs;
151 instance.$manyRefs = allRefs.manyRefs;
152 instance.$opts = parseOpts(name, element);
153 instance.$emit = (eventName, data = {}) => {
154 data.from = instance;
155 const event = new CustomEvent(`${name}-${eventName}`, {
159 instance.$el.dispatchEvent(event);
161 if (typeof instance.setup === 'function') {
165 console.error('Failed to create component', e, name, element);
169 // Add to global listing
170 if (typeof window.components[name] === "undefined") {
171 window.components[name] = [];
173 window.components[name].push(instance);
175 // Add to element listing
176 if (typeof element.components === 'undefined') {
177 element.components = {};
179 element.components[name] = instance;
183 * Parse out the element references within the given element
184 * for the given component name.
185 * @param {String} name
186 * @param {Element} element
188 function parseRefs(name, element) {
192 const prefix = `${name}@`
193 const selector = `[refs*="${prefix}"]`;
194 const refElems = [...element.querySelectorAll(selector)];
195 if (element.matches(selector)) {
196 refElems.push(element);
199 for (const el of refElems) {
200 const refNames = el.getAttribute('refs')
202 .filter(str => str.startsWith(prefix))
203 .map(str => str.replace(prefix, ''))
205 for (const ref of refNames) {
207 if (typeof manyRefs[ref] === 'undefined') {
210 manyRefs[ref].push(el);
213 return {refs, manyRefs};
217 * Parse out the element component options.
218 * @param {String} name
219 * @param {Element} element
220 * @return {Object<String, String>}
222 function parseOpts(name, element) {
224 const prefix = `option:${name}:`;
225 for (const {name, value} of element.attributes) {
226 if (name.startsWith(prefix)) {
227 const optName = name.replace(prefix, '');
228 opts[kebabToCamel(optName)] = value || '';
235 * Convert a kebab-case string to camelCase
236 * @param {String} kebab
239 function kebabToCamel(kebab) {
240 const ucFirst = (word) => word.slice(0,1).toUpperCase() + word.slice(1);
241 const words = kebab.split('-');
242 return words[0] + words.slice(1).map(ucFirst).join('');
246 * Initialize all components found within the given element.
247 * @param parentElement
249 function initAll(parentElement) {
250 if (typeof parentElement === 'undefined') parentElement = document;
252 // Old attribute system
253 for (const componentName of Object.keys(componentMapping)) {
254 searchForComponentInParent(componentName, parentElement);
257 // New component system
258 const componentElems = parentElement.querySelectorAll(`[component],[components]`);
260 for (const el of componentElems) {
261 const componentNames = `${el.getAttribute('component') || ''} ${(el.getAttribute('components'))}`.toLowerCase().split(' ').filter(Boolean);
262 for (const name of componentNames) {
263 initComponent(name, el);
268 window.components.init = initAll;
269 window.components.first = (name) => (window.components[name] || [null])[0];
271 export default initAll;
275 * @property {HTMLElement} $el
276 * @property {Object<String, HTMLElement>} $refs
277 * @property {Object<String, HTMLElement[]>} $manyRefs
278 * @property {Object<String, String>} $opts
279 * @property {function(string, Object)} $emit