1
0
mirror of https://github.com/BookStackApp/BookStack.git synced 2024-10-30 07:32:39 +01:00

Updated a whole load more js components

This commit is contained in:
Dan Brown 2022-11-15 16:04:46 +00:00
parent b37e84dc10
commit db79167469
No known key found for this signature in database
GPG Key ID: 46D9F943C24A2EF9
25 changed files with 152 additions and 184 deletions

View File

@ -1,35 +1,37 @@
import {slideDown, slideUp} from "../services/animations";
import {Component} from "./component";
/**
* Collapsible
* Provides some simple logic to allow collapsible sections.
*/
class Collapsible {
export class Collapsible extends Component {
constructor(elem) {
this.elem = elem;
this.trigger = elem.querySelector('[collapsible-trigger]');
this.content = elem.querySelector('[collapsible-content]');
setup() {
this.container = this.$el;
this.trigger = this.$refs.trigger;
this.content = this.$refs.content;
if (!this.trigger) return;
this.trigger.addEventListener('click', this.toggle.bind(this));
this.openIfContainsError();
if (this.trigger) {
this.trigger.addEventListener('click', this.toggle.bind(this));
this.openIfContainsError();
}
}
open() {
this.elem.classList.add('open');
this.container.classList.add('open');
this.trigger.setAttribute('aria-expanded', 'true');
slideDown(this.content, 300);
}
close() {
this.elem.classList.remove('open');
this.container.classList.remove('open');
this.trigger.setAttribute('aria-expanded', 'false');
slideUp(this.content, 300);
}
toggle() {
if (this.elem.classList.contains('open')) {
if (this.container.classList.contains('open')) {
this.close();
} else {
this.open();
@ -44,5 +46,3 @@ class Collapsible {
}
}
export default Collapsible;

View File

@ -1,7 +1,8 @@
import {debounce} from "../services/util";
import {transitionHeight} from "../services/animations";
import {Component} from "./component";
class DropdownSearch {
export class DropdownSearch extends Component {
setup() {
this.elem = this.$el;
@ -79,5 +80,3 @@ class DropdownSearch {
}
}
export default DropdownSearch;

View File

@ -1,17 +1,15 @@
import {slideUp, slideDown} from "../services/animations";
import {Component} from "./component";
class ExpandToggle {
export class ExpandToggle extends Component {
constructor(elem) {
this.elem = elem;
// Component state
this.isOpen = elem.getAttribute('expand-toggle-is-open') === 'yes';
this.updateEndpoint = elem.getAttribute('expand-toggle-update-endpoint');
this.selector = elem.getAttribute('expand-toggle');
setup(elem) {
this.targetSelector = this.$opts.targetSelector;
this.isOpen = this.$opts.isOpen === 'true';
this.updateEndpoint = this.$opts.updateEndpoint;
// Listener setup
elem.addEventListener('click', this.click.bind(this));
this.$el.addEventListener('click', this.click.bind(this));
}
open(elemToToggle) {
@ -25,7 +23,7 @@ class ExpandToggle {
click(event) {
event.preventDefault();
const matchingElems = document.querySelectorAll(this.selector);
const matchingElems = document.querySelectorAll(this.targetSelector);
for (let match of matchingElems) {
this.isOpen ? this.close(match) : this.open(match);
}
@ -41,5 +39,3 @@ class ExpandToggle {
}
}
export default ExpandToggle;

View File

@ -1,5 +1,6 @@
import {Component} from "./component";
class HeaderMobileToggle {
export class HeaderMobileToggle extends Component {
setup() {
this.elem = this.$el;
@ -37,5 +38,3 @@ class HeaderMobileToggle {
}
}
export default HeaderMobileToggle;

View File

@ -1,21 +1,25 @@
import {Component} from "./component";
class ImagePicker {
export class ImagePicker extends Component {
constructor(elem) {
this.elem = elem;
this.imageElem = elem.querySelector('img');
this.imageInput = elem.querySelector('input[type=file]');
this.resetInput = elem.querySelector('input[data-reset-input]');
this.removeInput = elem.querySelector('input[data-remove-input]');
setup() {
this.imageElem = this.$refs.image;
this.imageInput = this.$refs.imageInput;
this.resetInput = this.$refs.resetInput;
this.removeInput = this.$refs.removeInput;
this.resetButton = this.$refs.resetButton;
this.removeButton = this.$refs.removeButton || null;
this.defaultImage = elem.getAttribute('data-default-image');
this.defaultImage = this.$opts.defaultImage;
const resetButton = elem.querySelector('button[data-action="reset-image"]');
resetButton.addEventListener('click', this.reset.bind(this));
this.setupListeners();
}
const removeButton = elem.querySelector('button[data-action="remove-image"]');
if (removeButton) {
removeButton.addEventListener('click', this.removeImage.bind(this));
setupListeners() {
this.resetButton.addEventListener('click', this.reset.bind(this));
if (this.removeButton) {
this.removeButton.addEventListener('click', this.removeImage.bind(this));
}
this.imageInput.addEventListener('change', this.fileInputChange.bind(this));
@ -51,5 +55,3 @@ class ImagePicker {
}
}
export default ImagePicker;

View File

@ -11,12 +11,12 @@ export {ChapterContents} from "./chapter-contents.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 {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 {DropdownSearch} from "./dropdown-search.js"
export {DropdownSearch} from "./dropdown-search.js"
// export {Dropzone} from "./dropzone.js"
// export {EditorToolbox} from "./editor-toolbox.js"
export {EntityPermissions} from "./entity-permissions"
@ -24,17 +24,17 @@ export {EntityPermissions} from "./entity-permissions"
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"
// export {HeaderMobileToggle} from "./header-mobile-toggle.js"
export {ExpandToggle} from "./expand-toggle.js"
export {HeaderMobileToggle} from "./header-mobile-toggle.js"
// export {ImageManager} from "./image-manager.js"
// export {ImagePicker} from "./image-picker.js"
// export {ListSortControl} from "./list-sort-control.js"
export {ImagePicker} from "./image-picker.js"
export {ListSortControl} from "./list-sort-control.js"
// export {MarkdownEditor} from "./markdown-editor.js"
// export {NewUserPassword} from "./new-user-password.js"
export {NewUserPassword} from "./new-user-password.js"
export {Notification} from "./notification.js"
// export {OptionalInput} from "./optional-input.js"
export {OptionalInput} from "./optional-input.js"
export {PageComments} from "./page-comments.js"
// export {PageDisplay} from "./page-display.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"
@ -43,17 +43,16 @@ export {Popup} from "./popup.js"
export {SettingAppColorPicker} from "./setting-app-color-picker.js"
export {SettingColorPicker} from "./setting-color-picker.js"
export {SettingHomepageControl} from "./setting-homepage-control.js"
// export {ShelfSort} from "./shelf-sort.js"
export {ShelfSort} from "./shelf-sort.js"
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"
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 {ToggleSwitch} from "./toggle-switch.js"
// export {TriLayout} from "./tri-layout.js"
// export {UserSelect} from "./user-select.js"
// export {WebhookEvents} from "./webhook-events";
export {TriLayout} from "./tri-layout.js"
export {UserSelect} from "./user-select.js"
export {WebhookEvents} from "./webhook-events";
// export {WysiwygEditor} from "./wysiwyg-editor.js"

View File

@ -1,9 +1,10 @@
/**
* ListSortControl
* Manages the logic for the control which provides list sorting options.
* @extends {Component}
*/
class ListSortControl {
import {Component} from "./component";
export class ListSortControl extends Component {
setup() {
this.elem = this.$el;
@ -45,5 +46,3 @@ class ListSortControl {
}
}
export default ListSortControl;

View File

@ -1,9 +1,11 @@
import {Component} from "./component";
class NewUserPassword {
export class NewUserPassword extends Component {
constructor(elem) {
this.elem = elem;
this.inviteOption = elem.querySelector('input[name=send_invite]');
setup() {
this.container = this.$el;
this.inputContainer = this.$refs.inputContainer;
this.inviteOption = this.container.querySelector('input[name=send_invite]');
if (this.inviteOption) {
this.inviteOption.addEventListener('change', this.inviteOptionChange.bind(this));
@ -13,16 +15,12 @@ class NewUserPassword {
inviteOptionChange() {
const inviting = (this.inviteOption.value === 'true');
const passwordBoxes = this.elem.querySelectorAll('input[type=password]');
const passwordBoxes = this.container.querySelectorAll('input[type=password]');
for (const input of passwordBoxes) {
input.disabled = inviting;
}
const container = this.elem.querySelector('#password-input-container');
if (container) {
container.style.display = inviting ? 'none' : 'block';
}
this.inputContainer.style.display = inviting ? 'none' : 'block';
}
}
export default NewUserPassword;

View File

@ -1,6 +1,7 @@
import {onSelect} from "../services/dom";
import {Component} from "./component";
class OptionalInput {
export class OptionalInput extends Component {
setup() {
this.removeButton = this.$refs.remove;
this.showButton = this.$refs.show;
@ -24,5 +25,3 @@ class OptionalInput {
}
}
export default OptionalInput;

View File

@ -1,11 +1,12 @@
import * as DOM from "../services/dom";
import {scrollAndHighlightElement} from "../services/util";
import {Component} from "./component";
class PageDisplay {
export class PageDisplay extends Component {
constructor(elem) {
this.elem = elem;
this.pageId = elem.getAttribute('page-display');
setup() {
this.container = this.$el;
this.pageId = this.$opts.pageId;
window.importVersioned('code').then(Code => Code.highlight());
this.setupNavHighlighting();
@ -13,7 +14,7 @@ class PageDisplay {
// Check the hash on load
if (window.location.hash) {
let text = window.location.hash.replace(/\%20/g, ' ').substr(1);
const text = window.location.hash.replace(/%20/g, ' ').substring(1);
this.goToText(text);
}
@ -49,17 +50,10 @@ class PageDisplay {
}
setupNavHighlighting() {
// Check if support is present for IntersectionObserver
if (!('IntersectionObserver' in window) ||
!('IntersectionObserverEntry' in window) ||
!('intersectionRatio' in window.IntersectionObserverEntry.prototype)) {
return;
}
let pageNav = document.querySelector('.sidebar-page-nav');
const pageNav = document.querySelector('.sidebar-page-nav');
// fetch all the headings.
let headings = document.querySelector('.page-content').querySelectorAll('h1, h2, h3, h4, h5, h6');
const headings = document.querySelector('.page-content').querySelectorAll('h1, h2, h3, h4, h5, h6');
// if headings are present, add observers.
if (headings.length > 0 && pageNav !== null) {
addNavObserver(headings);
@ -67,21 +61,21 @@ class PageDisplay {
function addNavObserver(headings) {
// Setup the intersection observer.
let intersectOpts = {
const intersectOpts = {
rootMargin: '0px 0px 0px 0px',
threshold: 1.0
};
let pageNavObserver = new IntersectionObserver(headingVisibilityChange, intersectOpts);
const pageNavObserver = new IntersectionObserver(headingVisibilityChange, intersectOpts);
// observe each heading
for (let heading of headings) {
for (const heading of headings) {
pageNavObserver.observe(heading);
}
}
function headingVisibilityChange(entries, observer) {
for (let entry of entries) {
let isVisible = (entry.intersectionRatio === 1);
for (const entry of entries) {
const isVisible = (entry.intersectionRatio === 1);
toggleAnchorHighlighting(entry.target.id, isVisible);
}
}
@ -99,9 +93,7 @@ class PageDisplay {
codeMirrors.forEach(cm => cm.CodeMirror && cm.CodeMirror.refresh());
};
const details = [...this.elem.querySelectorAll('details')];
const details = [...this.container.querySelectorAll('details')];
details.forEach(detail => detail.addEventListener('toggle', onToggle));
}
}
export default PageDisplay;

View File

@ -1,6 +1,7 @@
import Sortable from "sortablejs";
import {Component} from "./component";
class ShelfSort {
export class ShelfSort extends Component {
setup() {
this.elem = this.$el;
@ -15,7 +16,7 @@ class ShelfSort {
initSortable() {
const scrollBoxes = this.elem.querySelectorAll('.scroll-box');
for (let scrollBox of scrollBoxes) {
for (const scrollBox of scrollBoxes) {
new Sortable(scrollBox, {
group: 'shelf-books',
ghostClass: 'primary-background-light',

View File

@ -1,16 +0,0 @@
class Sidebar {
constructor(elem) {
this.elem = elem;
this.toggleElem = elem.querySelector('.sidebar-toggle');
this.toggleElem.addEventListener('click', this.toggle.bind(this));
}
toggle(show = true) {
this.elem.classList.toggle('open');
}
}
export default Sidebar;

View File

@ -1,9 +1,10 @@
import {Component} from "./component";
/**
* Submit on change
* Simply submits a parent form when this input is changed.
* @extends {Component}
*/
class SubmitOnChange {
export class SubmitOnChange extends Component {
setup() {
this.filter = this.$opts.filter;
@ -22,5 +23,3 @@ class SubmitOnChange {
}
}
export default SubmitOnChange;

View File

@ -1,5 +1,6 @@
import {Component} from "./component";
class TriLayout {
export class TriLayout extends Component {
setup() {
this.container = this.$refs.container;
@ -109,5 +110,3 @@ class TriLayout {
}
}
export default TriLayout;

View File

@ -1,6 +1,7 @@
import {onChildEvent} from "../services/dom";
import {Component} from "./component";
class UserSelect {
export class UserSelect extends Component {
setup() {
this.input = this.$refs.input;
@ -13,13 +14,10 @@ class UserSelect {
selectUser(event, userEl) {
event.preventDefault();
const id = userEl.getAttribute('data-id');
this.input.value = id;
this.input.value = userEl.getAttribute('data-id');
this.userInfoContainer.innerHTML = userEl.innerHTML;
this.input.dispatchEvent(new Event('change', {bubbles: true}));
this.hide();
}
}
export default UserSelect;

View File

@ -1,10 +1,10 @@
/**
* Webhook Events
* Manages dynamic selection control in the webhook form interface.
* @extends {Component}
*/
class WebhookEvents {
import {Component} from "./component";
export class WebhookEvents extends Component {
setup() {
this.checkboxes = this.$el.querySelectorAll('input[type="checkbox"]');
@ -28,5 +28,3 @@ class WebhookEvents {
}
}
export default WebhookEvents;

View File

@ -328,7 +328,7 @@ input[type=color] {
}
}
.form-group[collapsible] {
.form-group.collapsible {
padding: 0 $-m;
border: 1px solid;
@include lightDark(border-color, #DDD, #000);

View File

@ -10,11 +10,11 @@
@include('form.textarea', ['name' => 'description'])
</div>
<div class="form-group" collapsible id="logo-control">
<button type="button" class="collapse-title text-primary" collapsible-trigger aria-expanded="false">
<div class="form-group collapsible" component="collapsible" id="logo-control">
<button refs="collapsible@trigger" type="button" class="collapse-title text-primary" aria-expanded="false">
<label>{{ trans('common.cover_image') }}</label>
</button>
<div class="collapse-content" collapsible-content>
<div refs="collapsible@content" class="collapse-content">
<p class="small">{{ trans('common.cover_image_description') }}</p>
@include('form.image-picker', [
@ -26,11 +26,11 @@
</div>
</div>
<div class="form-group" collapsible id="tags-control">
<button type="button" class="collapse-title text-primary" collapsible-trigger aria-expanded="false">
<div class="form-group collapsible" component="collapsible" id="tags-control">
<button refs="collapsible@trigger" type="button" class="collapse-title text-primary" aria-expanded="false">
<label for="tag-manager">{{ trans('entities.book_tags') }}</label>
</button>
<div class="collapse-content" collapsible-content>
<div refs="collapsible@content" class="collapse-content">
@include('entities.tag-manager', ['entity' => $book ?? null])
</div>
</div>

View File

@ -11,11 +11,11 @@
@include('form.textarea', ['name' => 'description'])
</div>
<div class="form-group" collapsible id="logo-control">
<button type="button" class="collapse-title text-primary" collapsible-trigger aria-expanded="false">
<div class="form-group collapsible" component="collapsible" id="logo-control">
<button refs="collapsible@trigger" type="button" class="collapse-title text-primary" aria-expanded="false">
<label for="tags">{{ trans('entities.chapter_tags') }}</label>
</button>
<div class="collapse-content" collapsible-content>
<div refs="collapsible@content" class="collapse-content">
@include('entities.tag-manager', ['entity' => $chapter ?? null])
</div>
</div>

View File

@ -1,26 +1,27 @@
<div class="image-picker @if($errors->has($name)) has-error @endif"
image-picker="{{$name}}"
data-default-image="{{ $defaultImage }}">
<div component="image-picker"
option:image-picker:default-image="{{ $defaultImage }}"
class="image-picker @if($errors->has($name)) has-error @endif">
<div class="grid half">
<div class="text-center">
<img @if($currentImage && $currentImage !== 'none') src="{{$currentImage}}" @else src="{{$defaultImage}}" @endif class="{{$imageClass}} @if($currentImage=== 'none') none @endif" alt="{{ trans('components.image_preview') }}">
<img refs="image-picker@image"
@if($currentImage && $currentImage !== 'none') src="{{$currentImage}}" @else src="{{$defaultImage}}" @endif
class="{{$imageClass}} @if($currentImage=== 'none') none @endif" alt="{{ trans('components.image_preview') }}">
</div>
<div class="text-center">
<input type="file" class="custom-file-input" accept="image/*" name="{{ $name }}" id="{{ $name }}">
<input refs="image-picker@image-input" type="file" class="custom-file-input" accept="image/*" name="{{ $name }}" id="{{ $name }}">
<label for="{{ $name }}" class="button outline">{{ trans('components.image_select_image') }}</label>
<input type="hidden" data-reset-input name="{{ $name }}_reset" value="true" disabled="disabled">
<input refs="image-picker@reset-input" type="hidden" name="{{ $name }}_reset" value="true" disabled="disabled">
@if(isset($removeName))
<input type="hidden" data-remove-input name="{{ $removeName }}" value="{{ $removeValue }}" disabled="disabled">
<input refs="image-picker@remove-input" type="hidden" name="{{ $removeName }}" value="{{ $removeValue }}" disabled="disabled">
@endif
<br>
<button class="text-button text-muted" data-action="reset-image" type="button">{{ trans('common.reset') }}</button>
<button refs="image-picker@reset-button" class="text-button text-muted" type="button">{{ trans('common.reset') }}</button>
@if(isset($removeName))
<span class="sep">|</span>
<button class="text-button text-muted" data-action="remove-image" type="button">{{ trans('common.remove') }}</button>
<button refs="image-picker@remove-button" class="text-button text-muted" type="button">{{ trans('common.remove') }}</button>
@endif
</div>
</div>

View File

@ -3,10 +3,12 @@ $target - CSS selector of items to expand
$key - Unique key for checking existing stored state.
--}}
<?php $isOpen = setting()->getForCurrentUser('section_expansion#'. $key); ?>
<button type="button" expand-toggle="{{ $target }}"
expand-toggle-update-endpoint="{{ url('/preferences/change-expansion/' . $key) }}"
expand-toggle-is-open="{{ $isOpen ? 'yes' : 'no' }}"
class="icon-list-item {{ $classes ?? '' }}">
<button component="expand-toggle"
option:expand-toggle:target-selector="{{ $target }}"
option:expand-toggle:update-endpoint="{{ url('/preferences/change-expansion/' . $key) }}"
option:expand-toggle:is-open="{{ $isOpen ? 'true' : 'false' }}"
type="button"
class="icon-list-item {{ $classes ?? '' }}">
<span>@icon('expand-text')</span>
<span>{{ trans('common.toggle_details') }}</span>
</button>

View File

@ -3,7 +3,9 @@
@section('body')
<div class="mt-m">
<main class="content-wrap card">
<div class="page-content" page-display="{{ $customHomepage->id }}">
<div component="page-display"
option:page-display:page-id="{{ $page->id }}"
class="page-content">
@include('pages.parts.page-display', ['page' => $customHomepage])
</div>
</main>

View File

@ -17,7 +17,9 @@
</div>
<main class="content-wrap card">
<div class="page-content clearfix" page-display="{{ $page->id }}">
<div component="page-display"
option:page-display:page-id="{{ $page->id }}"
class="page-content clearfix">
@include('pages.parts.page-display')
</div>
@include('pages.parts.pointer', ['page' => $page])

View File

@ -43,11 +43,11 @@
<div class="form-group" collapsible id="logo-control">
<button type="button" class="collapse-title text-primary" collapsible-trigger aria-expanded="false">
<div class="form-group collapsible" component="collapsible" id="logo-control">
<button refs="collapsible@trigger" type="button" class="collapse-title text-primary" aria-expanded="false">
<label>{{ trans('common.cover_image') }}</label>
</button>
<div class="collapse-content" collapsible-content>
<div refs="collapsible@content" class="collapse-content">
<p class="small">{{ trans('common.cover_image_description') }}</p>
@include('form.image-picker', [
@ -59,11 +59,11 @@
</div>
</div>
<div class="form-group" collapsible id="tags-control">
<button type="button" class="collapse-title text-primary" collapsible-trigger aria-expanded="false">
<div class="form-group collapsible" component="collapsible" id="tags-control">
<button refs="collapsible@trigger" type="button" class="collapse-title text-primary" aria-expanded="false">
<label for="tag-manager">{{ trans('entities.shelf_tags') }}</label>
</button>
<div class="collapse-content" collapsible-content>
<div refs="collapsible@content" class="collapse-content">
@include('entities.tag-manager', ['entity' => $shelf ?? null])
</div>
</div>

View File

@ -48,7 +48,7 @@
@endif
@if($authMethod === 'standard')
<div new-user-password>
<div component="new-user-password">
<label class="setting-list-label">{{ trans('settings.users_password') }}</label>
@if(!isset($model))
@ -61,10 +61,9 @@
'value' => old('send_invite', 'true') === 'true',
'label' => trans('settings.users_send_invite_option')
])
@endif
<div id="password-input-container" @if(!isset($model)) style="display: none;" @endif>
<div refs="new-user-password@input-container" @if(!isset($model)) style="display: none;" @endif>
<p class="small">{{ trans('settings.users_password_desc') }}</p>
@if(isset($model))
<p class="small">