]> BookStack Code Mirror - bookstack/commitdiff
Updated a batch of JS components
authorDan Brown <redacted>
Tue, 15 Nov 2022 11:24:31 +0000 (11:24 +0000)
committerDan Brown <redacted>
Tue, 15 Nov 2022 11:24:31 +0000 (11:24 +0000)
19 files changed:
resources/js/components/book-sort.js
resources/js/components/chapter-contents.js
resources/js/components/code-highlighter.js
resources/js/components/details-highlighter.js
resources/js/components/dropdown.js
resources/js/components/entity-selector.js
resources/js/components/index.js
resources/js/components/notification.js
resources/js/components/page-comments.js
resources/js/components/pointer.js
resources/js/components/shortcut-input.js
resources/js/components/shortcuts.js
resources/js/services/components.js
resources/sass/_components.scss
resources/views/api-docs/index.blade.php
resources/views/api-docs/parts/endpoint.blade.php
resources/views/books/parts/sort-box.blade.php
resources/views/books/sort.blade.php
resources/views/common/notifications.blade.php

index 2b94ca4a7a19a68ff82b31345efbc52fc28dc56e..3ffadf99150d3813b06faea5d706e789257a3e8d 100644 (file)
@@ -1,4 +1,6 @@
 import Sortable from "sortablejs";
+import {Component} from "./component";
+import {htmlToDom} from "../services/dom";
 
 // Auto sort control
 const sortOperations = {
@@ -35,14 +37,14 @@ const sortOperations = {
     },
 };
 
-class BookSort {
+export class BookSort extends Component {
 
-    constructor(elem) {
-        this.elem = elem;
-        this.sortContainer = elem.querySelector('[book-sort-boxes]');
-        this.input = elem.querySelector('[book-sort-input]');
+    setup() {
+        this.container = this.$el;
+        this.sortContainer = this.$refs.sortContainer;
+        this.input = this.$refs.input;
 
-        const initialSortBox = elem.querySelector('.sort-box');
+        const initialSortBox = this.container.querySelector('.sort-box');
         this.setupBookSortable(initialSortBox);
         this.setupSortPresets();
 
@@ -90,14 +92,12 @@ class BookSort {
      * @param {Object} entityInfo
      */
     bookSelect(entityInfo) {
-        const alreadyAdded = this.elem.querySelector(`[data-type="book"][data-id="${entityInfo.id}"]`) !== null;
+        const alreadyAdded = this.container.querySelector(`[data-type="book"][data-id="${entityInfo.id}"]`) !== null;
         if (alreadyAdded) return;
 
         const entitySortItemUrl = entityInfo.link + '/sort-item';
         window.$http.get(entitySortItemUrl).then(resp => {
-            const wrap = document.createElement('div');
-            wrap.innerHTML = resp.data;
-            const newBookContainer = wrap.children[0];
+            const newBookContainer = htmlToDom(resp.data);
             this.sortContainer.append(newBookContainer);
             this.setupBookSortable(newBookContainer);
         });
@@ -155,7 +155,7 @@ class BookSort {
      */
     buildEntityMap() {
         const entityMap = [];
-        const lists = this.elem.querySelectorAll('.sort-list');
+        const lists = this.container.querySelectorAll('.sort-list');
 
         for (let list of lists) {
             const bookId = list.closest('[data-type="book"]').getAttribute('data-id');
@@ -202,6 +202,4 @@ class BookSort {
         }
     }
 
-}
-
-export default BookSort;
\ No newline at end of file
+}
\ No newline at end of file
index c824d0f78b0b7fad13fcb16f84453746be6f1c7c..37df213e3c98e1ce12fa2de347e9397c3213238f 100644 (file)
@@ -1,9 +1,7 @@
 import {slideUp, slideDown} from "../services/animations";
+import {Component} from "./component";
 
-/**
- * @extends {Component}
- */
-class ChapterContents {
+export class ChapterContents extends Component {
 
     setup() {
         this.list = this.$refs.list;
@@ -31,7 +29,4 @@ class ChapterContents {
         event.preventDefault();
         this.isOpen ?  this.close() : this.open();
     }
-
 }
-
-export default ChapterContents;
index 5ffab377525d875f34e02f7be6d65e68dee1c4c3..14bfc97f04ed5358b2758112e9cf3f06d47ae423 100644 (file)
@@ -1,14 +1,16 @@
-class CodeHighlighter {
+import {Component} from "./component";
 
-    constructor(elem) {
-        const codeBlocks = elem.querySelectorAll('pre');
+export class CodeHighlighter extends Component{
+
+    setup() {
+        const container = this.$el;
+
+        const codeBlocks = container.querySelectorAll('pre');
         if (codeBlocks.length > 0) {
             window.importVersioned('code').then(Code => {
-               Code.highlightWithin(elem);
+               Code.highlightWithin(container);
             });
         }
     }
 
-}
-
-export default CodeHighlighter;
\ No newline at end of file
+}
\ No newline at end of file
index 1f3b66c674c2c882b7f1d71b4fcb79867c06dcfe..6466fb584882b0af36280f06dc2420f7599305e8 100644 (file)
@@ -1,21 +1,22 @@
-class DetailsHighlighter {
+import {Component} from "./component";
 
-    constructor(elem) {
-        this.elem = elem;
+export class DetailsHighlighter extends Component {
+
+    setup() {
+        this.container = this.$el;
         this.dealtWith = false;
-        elem.addEventListener('toggle', this.onToggle.bind(this));
+
+        this.container.addEventListener('toggle', this.onToggle.bind(this));
     }
 
     onToggle() {
         if (this.dealtWith) return;
 
-        if (this.elem.querySelector('pre')) {
+        if (this.container.querySelector('pre')) {
             window.importVersioned('code').then(Code => {
-                Code.highlightWithin(this.elem);
+                Code.highlightWithin(this.container);
             });
         }
         this.dealtWith = true;
     }
-}
-
-export default DetailsHighlighter;
\ No newline at end of file
+}
\ No newline at end of file
index 06a89d08c471d5fa351ebcd51c5ff0f461d1e8b9..2625ff4de2f38c27d983d14d431e07e3c31fd44b 100644 (file)
@@ -1,11 +1,11 @@
 import {onSelect} from "../services/dom";
+import {Component} from "./component";
 
 /**
  * Dropdown
  * Provides some simple logic to create simple dropdown menus.
- * @extends {Component}
  */
-class DropDown {
+export class Dropdown extends Component {
 
     setup() {
         this.container = this.$el;
@@ -171,6 +171,4 @@ class DropDown {
         });
     }
 
-}
-
-export default DropDown;
\ No newline at end of file
+}
\ No newline at end of file
index e2596998aedea01a105b40496f1909c0d6b60f20..1496ea89e6da2e118b90ce0ade757df43778be12 100644 (file)
@@ -1,10 +1,10 @@
 import {onChildEvent} from "../services/dom";
+import {Component} from "./component";
 
 /**
  * Entity Selector
- * @extends {Component}
  */
-class EntitySelector {
+export class EntitySelector extends Component {
 
     setup() {
         this.elem = this.$el;
@@ -185,6 +185,4 @@ class EntitySelector {
         this.selectedItemData = null;
     }
 
-}
-
-export default EntitySelector;
\ No newline at end of file
+}
\ No newline at end of file
index e3fa2cf78001535a01bbb981e9ea83eed29b424a..1f8a051736f48acdebeaa9a1e7009ed9bb83ab3a 100644 (file)
@@ -4,24 +4,24 @@ export {AjaxForm} from "./ajax-form.js"
 export {Attachments} from "./attachments.js"
 export {AttachmentsList} from "./attachments-list.js"
 export {AutoSuggest} from "./auto-suggest.js"
-export {AutoSubmit} from "./auto-submit.js";
+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 {BookSort} from "./book-sort.js"
+export {ChapterContents} from "./chapter-contents.js"
 // export {CodeEditor} from "./code-editor.js"
-// export {CodeHighlighter} from "./code-highlighter.js"
+export {CodeHighlighter} from "./code-highlighter.js"
 // export {CodeTextarea} from "./code-textarea.js"
 // export {Collapsible} from "./collapsible.js"
 // export {ConfirmDialog} from "./confirm-dialog"
 // export {CustomCheckbox} from "./custom-checkbox.js"
-// export {DetailsHighlighter} from "./details-highlighter.js"
-// export {Dropdown} from "./dropdown.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 {EntityPermissions} from "./entity-permissions";
+// export {EntityPermissions} from "./entity-permissions"
 // export {EntitySearch} from "./entity-search.js"
-// export {EntitySelector} from "./entity-selector.js"
+export {EntitySelector} from "./entity-selector.js"
 // export {EntitySelectorPopup} from "./entity-selector-popup.js"
 // export {EventEmitSelect} from "./event-emit-select.js"
 // export {ExpandToggle} from "./expand-toggle.js"
@@ -32,20 +32,20 @@ export {BackToTop} from "./back-to-top.js"
 // export {ListSortControl} from "./list-sort-control.js"
 // export {MarkdownEditor} from "./markdown-editor.js"
 // export {NewUserPassword} from "./new-user-password.js"
-// export {Notification} from "./notification.js"
+export {Notification} from "./notification.js"
 // export {OptionalInput} from "./optional-input.js"
-// export {PageComments} from "./page-comments.js"
+export {PageComments} from "./page-comments.js"
 // export {PageDisplay} from "./page-display.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 {Pointer} from "./pointer.js";
 // export {Popup} from "./popup.js"
 // export {SettingAppColorPicker} from "./setting-app-color-picker.js"
 // export {SettingColorPicker} from "./setting-color-picker.js"
 // export {ShelfSort} from "./shelf-sort.js"
-// export {Shortcuts} from "./shortcuts";
-// export {ShortcutInput} from "./shortcut-input";
+export {Shortcuts} from "./shortcuts"
+export {ShortcutInput} from "./shortcut-input"
 // export {Sidebar} from "./sidebar.js"
 // export {SortableList} from "./sortable-list.js"
 // export {SubmitOnChange} from "./submit-on-change.js"
index 35bab4ea656b1c052e875fac2bcdf0e4ef27d268..8a0876241fe15232b82264e93556357691816614 100644 (file)
@@ -1,19 +1,21 @@
+import {Component} from "./component";
 
-class Notification {
+export class Notification  extends Component {
 
-    constructor(elem) {
-        this.elem = elem;
-        this.type = elem.getAttribute('notification');
-        this.textElem = elem.querySelector('span');
-        this.autohide = this.elem.hasAttribute('data-autohide');
-        this.elem.style.display = 'grid';
+    setup() {
+        this.container = this.$el;
+        this.type = this.$opts.type;
+        this.textElem = this.container.querySelector('span');
+        this.autoHide = this.$opts.autoHide === 'true';
+        this.initialShow = this.$opts.show === 'true'
+        this.container.style.display = 'grid';
 
         window.$events.listen(this.type, text => {
             this.show(text);
         });
-        elem.addEventListener('click', this.hide.bind(this));
+        this.container.addEventListener('click', this.hide.bind(this));
 
-        if (elem.hasAttribute('data-show')) {
+        if (this.initialShow) {
             setTimeout(() => this.show(this.textElem.textContent), 100);
         }
 
@@ -21,14 +23,14 @@ class Notification {
     }
 
     show(textToShow = '') {
-        this.elem.removeEventListener('transitionend', this.hideCleanup);
+        this.container.removeEventListener('transitionend', this.hideCleanup);
         this.textElem.textContent = textToShow;
-        this.elem.style.display = 'grid';
+        this.container.style.display = 'grid';
         setTimeout(() => {
-            this.elem.classList.add('showing');
+            this.container.classList.add('showing');
         }, 1);
 
-        if (this.autohide) {
+        if (this.autoHide) {
             const words = textToShow.split(' ').length;
             const timeToShow = Math.max(2000, 1000 + (250 * words));
             setTimeout(this.hide.bind(this), timeToShow);
@@ -36,15 +38,13 @@ class Notification {
     }
 
     hide() {
-        this.elem.classList.remove('showing');
-        this.elem.addEventListener('transitionend', this.hideCleanup);
+        this.container.classList.remove('showing');
+        this.container.addEventListener('transitionend', this.hideCleanup);
     }
 
     hideCleanup() {
-        this.elem.style.display = 'none';
-        this.elem.removeEventListener('transitionend', this.hideCleanup);
+        this.container.style.display = 'none';
+        this.container.removeEventListener('transitionend', this.hideCleanup);
     }
 
-}
-
-export default Notification;
\ No newline at end of file
+}
\ No newline at end of file
index 0264e24c6b3c13c6b8bf376c1bfadb69f1b73000..726531e951f86f4a8447b41844803eb1d01dd083 100644 (file)
@@ -1,9 +1,8 @@
 import {scrollAndHighlightElement} from "../services/util";
+import {Component} from "./component";
+import {htmlToDom} from "../services/dom";
 
-/**
- * @extends {Component}
- */
-class PageComments {
+export class PageComments extends Component {
 
     setup() {
         this.elem = this.$el;
@@ -119,9 +118,7 @@ class PageComments {
         };
         this.showLoading(this.form);
         window.$http.post(`/comment/${this.pageId}`, reqData).then(resp => {
-            let newComment = document.createElement('div');
-            newComment.innerHTML = resp.data;
-            let newElem = newComment.children[0];
+            const newElem = htmlToDom(resp.data);
             this.container.appendChild(newElem);
             window.$components.init(newElem);
             window.$events.success(this.createdText);
@@ -199,6 +196,4 @@ class PageComments {
         formElem.querySelector('.form-group.loading').style.display = 'none';
     }
 
-}
-
-export default PageComments;
\ No newline at end of file
+}
\ No newline at end of file
index d1967acd01c7821e81291ac22e4b1474568ac529..d884dc7214ce3ac71673ac0bddf71f39c2755fee 100644 (file)
@@ -1,10 +1,9 @@
 import * as DOM from "../services/dom";
 import Clipboard from "clipboard/dist/clipboard.min";
+import {Component} from "./component";
 
-/**
- * @extends Component
- */
-class Pointer {
+
+export class Pointer extends Component {
 
     setup() {
         this.container = this.$el;
@@ -126,6 +125,4 @@ class Pointer {
             editAnchor.href = `${editHref}?content-id=${elementId}&content-text=${encodeURIComponent(queryContent)}`;
         }
     }
-}
-
-export default Pointer;
\ No newline at end of file
+}
\ No newline at end of file
index fa137898856da8e12dabfb96d998bb8e959e32e6..2a2aaa225a518c34413df94de2851745129dfa9e 100644 (file)
@@ -1,13 +1,12 @@
+import {Component} from "./component";
+
 /**
  * Keys to ignore when recording shortcuts.
  * @type {string[]}
  */
 const ignoreKeys = ['Control', 'Alt', 'Shift', 'Meta', 'Super', ' ', '+', 'Tab', 'Escape'];
 
-/**
- * @extends {Component}
- */
-class ShortcutInput {
+export class ShortcutInput extends Component {
 
     setup() {
         this.input = this.$el;
@@ -52,6 +51,4 @@ class ShortcutInput {
         this.input.removeEventListener('keydown', this.listenerRecordKey);
     }
 
-}
-
-export default ShortcutInput;
\ No newline at end of file
+}
\ No newline at end of file
index 4efa3d42b8734458d9f6bc5fa2a782dcaa58702d..a87213b2e8968070b5a0934cc3db0e6a1ef75fa0 100644 (file)
@@ -1,3 +1,5 @@
+import {Component} from "./component";
+
 function reverseMap(map) {
     const reversed = {};
     for (const [key, value] of Object.entries(map)) {
@@ -6,10 +8,8 @@ function reverseMap(map) {
     return reversed;
 }
 
-/**
- * @extends {Component}
- */
-class Shortcuts {
+
+export class Shortcuts extends Component {
 
     setup() {
         this.container = this.$el;
@@ -159,6 +159,4 @@ class Shortcuts {
 
         this.hintsShowing = false;
     }
-}
-
-export default Shortcuts;
\ No newline at end of file
+}
\ No newline at end of file
index 006f16e86f4b20711dc8f5aac8e71386f840c234..04fd0dcf4bb7f5c459027ebda3883214e0c5415e 100644 (file)
@@ -127,7 +127,6 @@ export function register(mapping) {
     for (const key of keys) {
         componentMap[camelToKebab(key)] = mapping[key];
     }
-    console.log(componentMap);
 }
 
 /**
index db9248719c4f5afb9119c223b215a260cb7b02f5..ff60cd50ad613cd2dd112bacce191dae6a22b14b 100644 (file)
@@ -1,6 +1,6 @@
 
 // System wide notifications
-[notification] {
+.notification {
   position: fixed;
   top: 0;
   right: 0;
index 8ce24baaec5e8e447d541f7010ffcc2420c5fe5b..9345a7bcead45420fdb3825b1cf51227edb21861 100644 (file)
@@ -38,7 +38,7 @@
 
             <div style="overflow: auto;">
 
-                <section code-highlighter class="card content-wrap auto-height">
+                <section component="code-highlighter" class="card content-wrap auto-height">
                     @include('api-docs.parts.getting-started')
                 </section>
 
index 6e3d93659d2e76af07a2a2cc868e8663f4958b74..60c478fe563deaacd6c60c368c8f8cf16fafee6d 100644 (file)
 @endif
 
 @if($endpoint['example_request'] ?? false)
-    <details details-highlighter class="mb-m">
+    <details component="details-highlighter" class="mb-m">
         <summary class="text-muted">Example Request</summary>
         <pre><code class="language-json">{{ $endpoint['example_request'] }}</code></pre>
     </details>
 @endif
 
 @if($endpoint['example_response'] ?? false)
-    <details details-highlighter class="mb-m">
+    <details component="details-highlighter" class="mb-m">
         <summary class="text-muted">Example Response</summary>
         <pre><code class="language-json">{{ $endpoint['example_response'] }}</code></pre>
     </details>
index f043735bbf4c9c0df001d5f40fc565756b467550..ef9929e46486b9e9fd944ca8cc1652a5551f366d 100644 (file)
@@ -4,11 +4,11 @@
         <span>{{ $book->name }}</span>
     </h5>
     <div class="sort-box-options pb-sm">
-        <a href="#" data-sort="name" class="button outline small">{{ trans('entities.books_sort_name') }}</a>
-        <a href="#" data-sort="created" class="button outline small">{{ trans('entities.books_sort_created') }}</a>
-        <a href="#" data-sort="updated" class="button outline small">{{ trans('entities.books_sort_updated') }}</a>
-        <a href="#" data-sort="chaptersFirst" class="button outline small">{{ trans('entities.books_sort_chapters_first') }}</a>
-        <a href="#" data-sort="chaptersLast" class="button outline small">{{ trans('entities.books_sort_chapters_last') }}</a>
+        <button type="button" data-sort="name" class="button outline small">{{ trans('entities.books_sort_name') }}</button>
+        <button type="button" data-sort="created" class="button outline small">{{ trans('entities.books_sort_created') }}</button>
+        <button type="button" data-sort="updated" class="button outline small">{{ trans('entities.books_sort_updated') }}</button>
+        <button type="button" data-sort="chaptersFirst" class="button outline small">{{ trans('entities.books_sort_chapters_first') }}</button>
+        <button type="button" data-sort="chaptersLast" class="button outline small">{{ trans('entities.books_sort_chapters_last') }}</button>
     </div>
     <ul class="sortable-page-list sort-list">
 
index a24bd8959203c9b5f24b263aec0374d8fced88c2..077da101d13bddb518f141ad2cde2fe60e2173e1 100644 (file)
 
         <div class="grid left-focus gap-xl">
             <div>
-                <div book-sort class="card content-wrap">
+                <div component="book-sort" class="card content-wrap">
                     <h1 class="list-heading mb-l">{{ trans('entities.books_sort') }}</h1>
-                    <div book-sort-boxes>
+                    <div refs="book-sort@sortContainer">
                         @include('books.parts.sort-box', ['book' => $book, 'bookChildren' => $bookChildren])
                     </div>
 
                     <form action="{{ $book->getUrl('/sort') }}" method="POST">
                         {!! csrf_field() !!}
                         <input type="hidden" name="_method" value="PUT">
-                        <input book-sort-input type="hidden" name="sort-tree">
+                        <input refs="book-sort@input" type="hidden" name="sort-tree">
                         <div class="list text-right">
                             <a href="{{ $book->getUrl() }}" class="button outline">{{ trans('common.cancel') }}</a>
                             <button class="button" type="submit">{{ trans('entities.books_sort_save') }}</button>
index 752920917570a9a11d974f7088bf7e572a5e6a0f..e06bd5fd13861b0e85a03ebdbea69817fc8644f6 100644 (file)
@@ -1,11 +1,29 @@
-<div notification="success" style="display: none;" data-autohide class="pos" role="alert" @if(session()->has('success')) data-show @endif>
+<div component="notification"
+     option:notification:type="success"
+     option:notification:auto-hide="true"
+     option:notification:show="{{ session()->has('success') ? 'true' : 'false' }}"
+     style="display: none;"
+     class="notification pos"
+     role="alert">
     @icon('check-circle') <span>{!! nl2br(htmlentities(session()->get('success'))) !!}</span><div class="dismiss">@icon('close')</div>
 </div>
 
-<div notification="warning" style="display: none;" class="warning" role="alert" @if(session()->has('warning')) data-show @endif>
+<div component="notification"
+     option:notification:type="warning"
+     option:notification:auto-hide="false"
+     option:notification:show="{{ session()->has('warning') ? 'true' : 'false' }}"
+     style="display: none;"
+     class="notification warning"
+     role="alert">
     @icon('info') <span>{!! nl2br(htmlentities(session()->get('warning'))) !!}</span><div class="dismiss">@icon('close')</div>
 </div>
 
-<div notification="error" style="display: none;" class="neg" role="alert" @if(session()->has('error')) data-show @endif>
+<div component="notification"
+     option:notification:type="error"
+     option:notification:auto-hide="false"
+     option:notification:show="{{ session()->has('error') ? 'true' : 'false' }}"
+     style="display: none;"
+     class="notification neg"
+     role="alert">
     @icon('danger') <span>{!! nl2br(htmlentities(session()->get('error'))) !!}</span><div class="dismiss">@icon('close')</div>
-</div>
+</div>
\ No newline at end of file