link.removeAttribute('target');
}
}
-}
-
-export default AttachmentsList;
\ No newline at end of file
+}
\ No newline at end of file
this.listContainer.classList.remove('hidden');
}
-}
-
-export default Attachments;
\ No newline at end of file
+}
\ No newline at end of file
requestAnimationFrame(setPos.bind(this));
}
-}
-
-export default BackToTop;
\ No newline at end of file
+}
\ No newline at end of file
import {onChildEvent, onEnterPress, onSelect} from "../services/dom";
+import {Component} from "./component";
-/**
- * Code Editor
- * @extends {Component}
- */
-class CodeEditor {
+
+export class CodeEditor extends Component {
setup() {
this.container = this.$refs.container;
window.sessionStorage.setItem(this.historyKey, historyString);
}
-}
-
-export default CodeEditor;
\ No newline at end of file
+}
\ No newline at end of file
Code.inlineEditor(this.$el, mode);
}
-}
-
-export default CodeTextarea;
\ No newline at end of file
+}
\ No newline at end of file
import {onSelect} from "../services/dom";
+import {Component} from "./component";
/**
* Custom equivalent of window.confirm() using our popup component.
* Is promise based so can be used like so:
* `const result = await dialog.show()`
- * @extends {Component}
*/
-class ConfirmDialog {
+export class ConfirmDialog extends Component {
setup() {
this.container = this.$el;
}
}
-}
-
-export default ConfirmDialog;
\ No newline at end of file
+}
\ No newline at end of file
import DropZoneLib from "dropzone";
import {fadeOut} from "../services/animations";
+import {Component} from "./component";
-/**
- * Dropzone
- * @extends {Component}
- */
-class Dropzone {
+export class Dropzone extends Component {
setup() {
this.container = this.$el;
this.url = this.$opts.url;
removeAll() {
this.dz.removeAllFiles(true);
}
-}
-
-export default Dropzone;
\ No newline at end of file
+}
\ No newline at end of file
-class EditorToolbox {
+import {Component} from "./component";
- constructor(elem) {
+export class EditorToolbox extends Component {
+
+ setup() {
// Elements
- this.elem = elem;
- this.buttons = elem.querySelectorAll('[toolbox-tab-button]');
- this.contentElements = elem.querySelectorAll('[toolbox-tab-content]');
- this.toggleButton = elem.querySelector('[toolbox-toggle]');
+ this.container = this.$el;
+ this.buttons = this.$manyRefs.tabButton;
+ this.contentElements = this.$manyRefs.tabContent;
+ this.toggleButton = this.$refs.toggle;
+
+ this.setupListeners();
+
+ // Set the first tab as active on load
+ this.setActiveTab(this.contentElements[0].dataset.tabContent);
+ }
+ setupListeners() {
// Toolbox toggle button click
- this.toggleButton.addEventListener('click', this.toggle.bind(this));
+ this.toggleButton.addEventListener('click', () => this.toggle());
// Tab button click
- this.elem.addEventListener('click', event => {
- let button = event.target.closest('[toolbox-tab-button]');
- if (button === null) return;
- let name = button.getAttribute('toolbox-tab-button');
- this.setActiveTab(name, true);
+ this.container.addEventListener('click', event => {
+ const button = event.target.closest('button');
+ if (this.buttons.includes(button)) {
+ const name = button.dataset.tab;
+ this.setActiveTab(name, true);
+ }
});
-
- // Set the first tab as active on load
- this.setActiveTab(this.contentElements[0].getAttribute('toolbox-tab-content'));
}
toggle() {
- this.elem.classList.toggle('open');
- const expanded = this.elem.classList.contains('open') ? 'true' : 'false';
+ this.container.classList.toggle('open');
+ const expanded = this.container.classList.contains('open') ? 'true' : 'false';
this.toggleButton.setAttribute('aria-expanded', expanded);
}
setActiveTab(tabName, openToolbox = false) {
+
// Set button visibility
- for (let i = 0, len = this.buttons.length; i < len; i++) {
- this.buttons[i].classList.remove('active');
- let bName = this.buttons[i].getAttribute('toolbox-tab-button');
- if (bName === tabName) this.buttons[i].classList.add('active');
+ for (const button of this.buttons) {
+ button.classList.remove('active');
+ const bName = button.dataset.tab;
+ if (bName === tabName) button.classList.add('active');
}
+
// Set content visibility
- for (let i = 0, len = this.contentElements.length; i < len; i++) {
- this.contentElements[i].style.display = 'none';
- let cName = this.contentElements[i].getAttribute('toolbox-tab-content');
- if (cName === tabName) this.contentElements[i].style.display = 'block';
+ for (const contentEl of this.contentElements) {
+ contentEl.style.display = 'none';
+ const cName = contentEl.dataset.tabContent;
+ if (cName === tabName) contentEl.style.display = 'block';
}
- if (openToolbox && !this.elem.classList.contains('open')) {
+ if (openToolbox && !this.container.classList.contains('open')) {
this.toggle();
}
}
-}
-
-export default EditorToolbox;
\ No newline at end of file
+}
\ No newline at end of file
import {onSelect} from "../services/dom";
+import {Component} from "./component";
-/**
- * Class EntitySearch
- * @extends {Component}
- */
-class EntitySearch {
+export class EntitySearch extends Component {
setup() {
this.entityId = this.$opts.entityId;
this.entityType = this.$opts.entityType;
this.loadingBlock.classList.add('hidden');
this.searchInput.value = '';
}
-}
-
-export default EntitySearch;
\ No newline at end of file
+}
\ No newline at end of file
import {onSelect} from "../services/dom";
+import {Component} from "./component";
/**
* EventEmitSelect
*
* All options will be set as the "detail" of the event with
* their values included.
- *
- * @extends {Component}
*/
-class EventEmitSelect {
+export class EventEmitSelect extends Component{
setup() {
this.container = this.$el;
this.name = this.$opts.name;
});
}
-}
-
-export default EventEmitSelect;
\ No newline at end of file
+}
\ No newline at end of file
import {onChildEvent, onSelect, removeLoading, showLoading} from "../services/dom";
+import {Component} from "./component";
-/**
- * ImageManager
- * @extends {Component}
- */
-class ImageManager {
+export class ImageManager extends Component {
setup() {
window.$components.init(this.formContainer);
}
-}
-
-export default ImageManager;
\ No newline at end of file
+}
\ No newline at end of file
export {BackToTop} from "./back-to-top.js"
export {BookSort} from "./book-sort.js"
export {ChapterContents} from "./chapter-contents.js"
-// export {CodeEditor} from "./code-editor.js"
+export {CodeEditor} from "./code-editor.js"
export {CodeHighlighter} from "./code-highlighter.js"
export {CodeTextarea} from "./code-textarea.js"
export {Collapsible} from "./collapsible.js"
-// export {ConfirmDialog} from "./confirm-dialog"
+export {ConfirmDialog} from "./confirm-dialog"
export {CustomCheckbox} from "./custom-checkbox.js"
export {DetailsHighlighter} from "./details-highlighter.js"
export {Dropdown} from "./dropdown.js"
export {DropdownSearch} from "./dropdown-search.js"
-// export {Dropzone} from "./dropzone.js"
-// export {EditorToolbox} from "./editor-toolbox.js"
+export {Dropzone} from "./dropzone.js"
+export {EditorToolbox} from "./editor-toolbox.js"
export {EntityPermissions} from "./entity-permissions"
-// export {EntitySearch} from "./entity-search.js"
+export {EntitySearch} from "./entity-search.js"
export {EntitySelector} from "./entity-selector.js"
export {EntitySelectorPopup} from "./entity-selector-popup.js"
-// export {EventEmitSelect} from "./event-emit-select.js"
+export {EventEmitSelect} from "./event-emit-select.js"
export {ExpandToggle} from "./expand-toggle.js"
export {HeaderMobileToggle} from "./header-mobile-toggle.js"
-// export {ImageManager} from "./image-manager.js"
+export {ImageManager} from "./image-manager.js"
export {ImagePicker} from "./image-picker.js"
export {ListSortControl} from "./list-sort-control.js"
-// export {MarkdownEditor} from "./markdown-editor.js"
+export {MarkdownEditor} from "./markdown-editor.js"
export {NewUserPassword} from "./new-user-password.js"
export {Notification} from "./notification.js"
export {OptionalInput} from "./optional-input.js"
export {PageComments} from "./page-comments.js"
export {PageDisplay} from "./page-display.js"
-// export {PageEditor} from "./page-editor.js"
+export {PageEditor} from "./page-editor.js"
export {PagePicker} from "./page-picker.js"
export {PermissionsTable} from "./permissions-table.js"
export {Pointer} from "./pointer.js";
export {ShelfSort} from "./shelf-sort.js"
export {Shortcuts} from "./shortcuts"
export {ShortcutInput} from "./shortcut-input"
-// export {SortableList} from "./sortable-list.js"
+export {SortableList} from "./sortable-list.js"
export {SubmitOnChange} from "./submit-on-change.js"
-// export {Tabs} from "./tabs.js"
-// export {TagManager} from "./tag-manager.js"
-// export {TemplateManager} from "./template-manager.js"
+export {Tabs} from "./tabs.js"
+export {TagManager} from "./tag-manager.js"
+export {TemplateManager} from "./template-manager.js"
export {ToggleSwitch} from "./toggle-switch.js"
export {TriLayout} from "./tri-layout.js"
export {UserSelect} from "./user-select.js"
export {WebhookEvents} from "./webhook-events";
-// export {WysiwygEditor} from "./wysiwyg-editor.js"
\ No newline at end of file
+export {WysiwygEditor} from "./wysiwyg-editor.js"
\ No newline at end of file
import {debounce} from "../services/util";
import {patchDomFromHtmlString} from "../services/vdom";
import DrawIO from "../services/drawio";
+import {Component} from "./component";
-class MarkdownEditor {
+export class MarkdownEditor extends Component {
setup() {
this.elem = this.$el;
});
}
}
-
-export default MarkdownEditor ;
import * as Dates from "../services/dates";
import {onSelect} from "../services/dom";
+import {Component} from "./component";
-/**
- * Page Editor
- * @extends {Component}
- */
-class PageEditor {
+export class PageEditor extends Component {
setup() {
// Options
this.draftsEnabled = this.$opts.draftsEnabled === 'true';
}
}
-}
-
-export default PageEditor;
\ No newline at end of file
+}
\ No newline at end of file
this.input.value = shelfBookElems.map(elem => elem.getAttribute('data-id')).join(',');
}
-}
-
-export default ShelfSort;
\ No newline at end of file
+}
\ No newline at end of file
import Sortable from "sortablejs";
+import {Component} from "./component";
/**
* SortableList
* Can have data set on the dragged items by setting a 'data-drag-content' attribute.
* This attribute must contain JSON where the keys are content types and the values are
* the data to set on the data-transfer.
- *
- * @extends {Component}
*/
-class SortableList {
+export class SortableList extends Component {
setup() {
this.container = this.$el;
this.handleSelector = this.$opts.handleSelector;
dragoverBubble: false,
});
}
-}
-
-export default SortableList;
\ No newline at end of file
+}
\ No newline at end of file
+import {onSelect} from "../services/dom";
+import {Component} from "./component";
+
/**
* Tabs
* Works by matching 'tabToggle<Key>' with 'tabContent<Key>' sections.
- * @extends {Component}
*/
-import {onSelect} from "../services/dom";
-
-class Tabs {
+export class Tabs extends Component {
setup() {
this.tabContentsByName = {};
}
}
-}
-
-export default Tabs;
\ No newline at end of file
+}
\ No newline at end of file
-/**
- * TagManager
- * @extends {Component}
- */
-class TagManager {
+import {Component} from "./component";
+
+export class TagManager extends Component {
setup() {
this.addRemoveComponentEl = this.$refs.addRemove;
this.container = this.$el;
});
return firstEmpty !== undefined;
}
-}
-
-export default TagManager;
\ No newline at end of file
+}
\ No newline at end of file
import * as DOM from "../services/dom";
+import {Component} from "./component";
-class TemplateManager {
+export class TemplateManager extends Component {
- constructor(elem) {
- this.elem = elem;
- this.list = elem.querySelector('[template-manager-list]');
- this.searching = false;
+ setup() {
+ this.container = this.$el;
+ this.list = this.$refs.list;
+ this.searchInput = this.$refs.searchInput;
+ this.searchButton = this.$refs.searchButton;
+ this.searchCancel = this.$refs.searchCancel;
+
+ this.setupListeners();
+ }
+
+ setupListeners() {
// Template insert action buttons
- DOM.onChildEvent(this.elem, '[template-action]', 'click', this.handleTemplateActionClick.bind(this));
+ DOM.onChildEvent(this.container, '[template-action]', 'click', this.handleTemplateActionClick.bind(this));
// Template list pagination click
- DOM.onChildEvent(this.elem, '.pagination a', 'click', this.handlePaginationClick.bind(this));
+ DOM.onChildEvent(this.container, '.pagination a', 'click', this.handlePaginationClick.bind(this));
// Template list item content click
- DOM.onChildEvent(this.elem, '.template-item-content', 'click', this.handleTemplateItemClick.bind(this));
+ DOM.onChildEvent(this.container, '.template-item-content', 'click', this.handleTemplateItemClick.bind(this));
// Template list item drag start
- DOM.onChildEvent(this.elem, '.template-item', 'dragstart', this.handleTemplateItemDragStart.bind(this));
+ DOM.onChildEvent(this.container, '.template-item', 'dragstart', this.handleTemplateItemDragStart.bind(this));
- this.setupSearchBox();
+ // Search box enter press
+ this.searchInput.addEventListener('keypress', event => {
+ if (event.key === 'Enter') {
+ event.preventDefault();
+ this.performSearch();
+ }
+ });
+
+ // Search submit button press
+ this.searchButton.addEventListener('click', event => this.performSearch());
+
+ // Search cancel button press
+ this.searchCancel.addEventListener('click', event => {
+ this.searchInput.value = '';
+ this.performSearch();
+ });
}
handleTemplateItemClick(event, templateItem) {
this.list.innerHTML = resp.data;
}
- setupSearchBox() {
- const searchBox = this.elem.querySelector('.search-box');
-
- // Search box may not exist if there are no existing templates in the system.
- if (!searchBox) return;
-
- const input = searchBox.querySelector('input');
- const submitButton = searchBox.querySelector('button');
- const cancelButton = searchBox.querySelector('button.search-box-cancel');
-
- async function performSearch() {
- const searchTerm = input.value;
- const resp = await window.$http.get(`/templates`, {
- search: searchTerm
- });
- cancelButton.style.display = searchTerm ? 'block' : 'none';
- this.list.innerHTML = resp.data;
- }
- performSearch = performSearch.bind(this);
-
- // Search box enter press
- searchBox.addEventListener('keypress', event => {
- if (event.key === 'Enter') {
- event.preventDefault();
- performSearch();
- }
- });
-
- // Submit button press
- submitButton.addEventListener('click', event => {
- performSearch();
- });
-
- // Cancel button press
- cancelButton.addEventListener('click', event => {
- input.value = '';
- performSearch();
+ async performSearch() {
+ const searchTerm = this.searchInput.value;
+ const resp = await window.$http.get(`/templates`, {
+ search: searchTerm
});
+ this.searchCancel.style.display = searchTerm ? 'block' : 'none';
+ this.list.innerHTML = resp.data;
}
-}
-
-export default TemplateManager;
\ No newline at end of file
+}
\ No newline at end of file
import {build as buildEditorConfig} from "../wysiwyg/config";
+import {Component} from "./component";
-class WysiwygEditor {
+export class WysiwygEditor extends Component {
setup() {
this.elem = this.$el;
return '';
}
-}
-
-export default WysiwygEditor;
+}
\ No newline at end of file
// Link selector shortcut
editor.shortcuts.add('meta+shift+K', '', function() {
- window.EntitySelectorPopup.show(function(entity) {
+ /** @var {EntitySelectorPopup} **/
+ const selectorPopup = window.$components.first('entity-selector-popup');
+ selectorPopup.show(function(entity) {
if (editor.selection.isCollapsed()) {
editor.insertContent(editor.dom.createHTML('a', {href: entity.link}, editor.dom.encode(entity.name)));
&.open {
width: 480px;
}
- [toolbox-toggle] svg {
+ .toolbox-toggle svg {
transition: transform ease-in-out 180ms;
}
- [toolbox-toggle] {
+ .toolbox-toggle {
transition: background-color ease-in-out 180ms;
}
- &.open [toolbox-toggle] {
+ &.open .toolbox-toggle {
background-color: rgba(255, 0, 0, 0.29);
}
- &.open [toolbox-toggle] svg {
+ &.open .toolbox-toggle svg {
transform: rotate(180deg);
}
> div {
}
}
-[toolbox-tab-content] {
+.toolbox-tab-content {
display: none;
}
-<div style="display: block;" toolbox-tab-content="files"
+<div style="display: block;"
+ refs="editor-toolbox@tab-content"
+ data-tab-content="files"
component="attachments"
- option:attachments:page-id="{{ $page->id ?? 0 }}">
+ option:attachments:page-id="{{ $page->id ?? 0 }}"
+ class="toolbox-tab-content">
<h4>{{ trans('entities.attachments') }}</h4>
<div class="px-l files">
-<div editor-toolbox class="floating-toolbox">
+<div component="editor-toolbox" class="floating-toolbox">
<div class="tabs primary-background-light">
- <button type="button" toolbox-toggle aria-expanded="false">@icon('caret-left-circle')</button>
- <button type="button" toolbox-tab-button="tags" title="{{ trans('entities.page_tags') }}" class="active">@icon('tag')</button>
+ <button type="button" refs="editor-toolbox@toggle" aria-expanded="false" class="toolbox-toggle">@icon('caret-left-circle')</button>
+ <button type="button" refs="editor-toolbox@tab-button" data-tab="tags" title="{{ trans('entities.page_tags') }}" class="active">@icon('tag')</button>
@if(userCan('attachment-create-all'))
- <button type="button" toolbox-tab-button="files" title="{{ trans('entities.attachments') }}">@icon('attach')</button>
+ <button type="button" refs="editor-toolbox@tab-button" data-tab="files" title="{{ trans('entities.attachments') }}">@icon('attach')</button>
@endif
- <button type="button" toolbox-tab-button="templates" title="{{ trans('entities.templates') }}">@icon('template')</button>
+ <button type="button" refs="editor-toolbox@tab-button" data-tab="templates" title="{{ trans('entities.templates') }}">@icon('template')</button>
</div>
- <div toolbox-tab-content="tags">
+ <div refs="editor-toolbox@tab-content" data-tab-content="tags" class="toolbox-tab-content">
<h4>{{ trans('entities.page_tags') }}</h4>
<div class="px-l">
@include('entities.tag-manager', ['entity' => $page])
@include('attachments.manager', ['page' => $page])
@endif
- <div toolbox-tab-content="templates">
+ <div refs="editor-toolbox@tab-content" data-tab-content="templates" class="toolbox-tab-content">
<h4>{{ trans('entities.templates') }}</h4>
<div class="px-l">
-<div template-manager>
+<div component="template-manager">
@if(userCan('templates-manage'))
<p class="text-muted small mb-none">
{{ trans('entities.templates_explain_set_as_template') }}
<hr>
@endif
- @if(count($templates) > 0)
- <div class="search-box flexible mb-m">
- <input type="text" name="template-search" placeholder="{{ trans('common.search') }}">
- <button type="button">@icon('search')</button>
- <button class="search-box-cancel text-neg hidden" type="button">@icon('close')</button>
- </div>
- @endif
+ <div class="search-box flexible mb-m" style="display: {{ count($templates) > 0 ? 'block' : 'none' }}">
+ <input refs="template-manager@searchInput" type="text" name="template-search" placeholder="{{ trans('common.search') }}">
+ <button refs="template-manager@searchButton" type="button">@icon('search')</button>
+ <button refs="template-manager@searchCancel" class="search-box-cancel text-neg" type="button" style="display: none">@icon('close')</button>
+ </div>
- <div template-manager-list>
+ <div refs="template-manager@list">
@include('pages.parts.template-manager-list', ['templates' => $templates])
</div>
</div>
\ No newline at end of file