Finished updating remainder of JS components to new system

This commit is contained in:
Dan Brown 2022-11-16 13:04:22 +00:00
parent db79167469
commit 3b8ee3954e
No known key found for this signature in database
GPG Key ID: 46D9F943C24A2EF9
25 changed files with 164 additions and 214 deletions

View File

@ -43,6 +43,4 @@ export class AttachmentsList extends Component {
link.removeAttribute('target');
}
}
}
export default AttachmentsList;
}

View File

@ -71,6 +71,4 @@ export class Attachments extends Component {
this.listContainer.classList.remove('hidden');
}
}
export default Attachments;
}

View File

@ -55,6 +55,4 @@ export class BackToTop extends Component {
requestAnimationFrame(setPos.bind(this));
}
}
export default BackToTop;
}

View File

@ -1,10 +1,8 @@
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;
@ -184,6 +182,4 @@ class CodeEditor {
window.sessionStorage.setItem(this.historyKey, historyString);
}
}
export default CodeEditor;
}

View File

@ -12,6 +12,4 @@ export class CodeTextarea extends Component {
Code.inlineEditor(this.$el, mode);
}
}
export default CodeTextarea;
}

View File

@ -1,12 +1,12 @@
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;
@ -47,6 +47,4 @@ class ConfirmDialog {
}
}
}
export default ConfirmDialog;
}

View File

@ -1,11 +1,8 @@
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;
@ -73,6 +70,4 @@ class Dropzone {
removeAll() {
this.dz.removeAllFiles(true);
}
}
export default Dropzone;
}

View File

@ -1,51 +1,58 @@
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;
// Toolbox toggle button click
this.toggleButton.addEventListener('click', this.toggle.bind(this));
// 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.setupListeners();
// Set the first tab as active on load
this.setActiveTab(this.contentElements[0].getAttribute('toolbox-tab-content'));
this.setActiveTab(this.contentElements[0].dataset.tabContent);
}
setupListeners() {
// Toolbox toggle button click
this.toggleButton.addEventListener('click', () => this.toggle());
// Tab button click
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);
}
});
}
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');
}
// 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 button of this.buttons) {
button.classList.remove('active');
const bName = button.dataset.tab;
if (bName === tabName) button.classList.add('active');
}
if (openToolbox && !this.elem.classList.contains('open')) {
// Set content visibility
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.container.classList.contains('open')) {
this.toggle();
}
}
}
export default EditorToolbox;
}

View File

@ -1,10 +1,7 @@
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;
@ -54,6 +51,4 @@ class EntitySearch {
this.loadingBlock.classList.add('hidden');
this.searchInput.value = '';
}
}
export default EntitySearch;
}

View File

@ -1,4 +1,5 @@
import {onSelect} from "../services/dom";
import {Component} from "./component";
/**
* EventEmitSelect
@ -10,10 +11,8 @@ import {onSelect} from "../services/dom";
*
* 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;
@ -24,6 +23,4 @@ class EventEmitSelect {
});
}
}
export default EventEmitSelect;
}

View File

@ -1,10 +1,7 @@
import {onChildEvent, onSelect, removeLoading, showLoading} from "../services/dom";
import {Component} from "./component";
/**
* ImageManager
* @extends {Component}
*/
class ImageManager {
export class ImageManager extends Component {
setup() {
@ -210,6 +207,4 @@ class ImageManager {
window.$components.init(this.formContainer);
}
}
export default ImageManager;
}

View File

@ -8,34 +8,34 @@ export {AutoSubmit} from "./auto-submit.js"
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";
@ -46,13 +46,13 @@ export {SettingHomepageControl} from "./setting-homepage-control.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"
export {WysiwygEditor} from "./wysiwyg-editor.js"

View File

@ -4,8 +4,9 @@ import Clipboard from "../services/clipboard";
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;
@ -619,5 +620,3 @@ class MarkdownEditor {
});
}
}
export default MarkdownEditor ;

View File

@ -1,11 +1,8 @@
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';
@ -207,6 +204,4 @@ class PageEditor {
}
}
}
export default PageEditor;
}

View File

@ -79,6 +79,4 @@ export class ShelfSort extends Component {
this.input.value = shelfBookElems.map(elem => elem.getAttribute('data-id')).join(',');
}
}
export default ShelfSort;
}

View File

@ -1,4 +1,5 @@
import Sortable from "sortablejs";
import {Component} from "./component";
/**
* SortableList
@ -6,10 +7,8 @@ import Sortable from "sortablejs";
* 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;
@ -34,6 +33,4 @@ class SortableList {
dragoverBubble: false,
});
}
}
export default SortableList;
}

View File

@ -1,11 +1,11 @@
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 = {};
@ -46,6 +46,4 @@ class Tabs {
}
}
}
export default Tabs;
}

View File

@ -1,8 +1,6 @@
/**
* TagManager
* @extends {Component}
*/
class TagManager {
import {Component} from "./component";
export class TagManager extends Component {
setup() {
this.addRemoveComponentEl = this.$refs.addRemove;
this.container = this.$el;
@ -27,6 +25,4 @@ class TagManager {
});
return firstEmpty !== undefined;
}
}
export default TagManager;
}

View File

@ -1,25 +1,48 @@
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) {
@ -54,45 +77,12 @@ class TemplateManager {
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;
}

View File

@ -1,6 +1,7 @@
import {build as buildEditorConfig} from "../wysiwyg/config";
import {Component} from "./component";
class WysiwygEditor {
export class WysiwygEditor extends Component {
setup() {
this.elem = this.$el;
@ -35,6 +36,4 @@ class WysiwygEditor {
return '';
}
}
export default WysiwygEditor;
}

View File

@ -44,7 +44,9 @@ export function register(editor) {
// 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)));

View File

@ -278,16 +278,16 @@ body.tox-fullscreen, body.markdown-fullscreen {
&.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 {
@ -357,7 +357,7 @@ body.tox-fullscreen, body.markdown-fullscreen {
}
}
[toolbox-tab-content] {
.toolbox-tab-content {
display: none;
}

View File

@ -1,6 +1,9 @@
<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">

View File

@ -1,15 +1,15 @@
<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])
@ -20,7 +20,7 @@
@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">

View File

@ -1,4 +1,4 @@
<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') }}
@ -11,15 +11,13 @@
<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>