]> BookStack Code Mirror - bookstack/commitdiff
Input WYSIWYG: Added dynamic options for entity selector popups
authorDan Brown <redacted>
Tue, 19 Dec 2023 12:09:57 +0000 (12:09 +0000)
committerDan Brown <redacted>
Tue, 19 Dec 2023 12:09:57 +0000 (12:09 +0000)
So that multiple elements on the page can share the same popup, with
different search options.

12 files changed:
resources/js/components/entity-selector-popup.js
resources/js/components/entity-selector.js
resources/js/components/page-picker.js
resources/js/markdown/actions.js
resources/js/wysiwyg/config.js
resources/js/wysiwyg/shortcuts.js
resources/views/books/parts/form.blade.php
resources/views/chapters/parts/form.blade.php
resources/views/entities/selector-popup.blade.php
resources/views/form/page-picker.blade.php
resources/views/settings/customization.blade.php
resources/views/shelves/parts/form.blade.php

index 9ff67d53efbe49637ddabd87f9fc50f333e9f2ac..6fb461968859ece0ad0f8934743c42e4f97c13d0 100644 (file)
@@ -15,8 +15,15 @@ export class EntitySelectorPopup extends Component {
         window.$events.listen('entity-select-confirm', this.handleConfirmedSelection.bind(this));
     }
 
-    show(callback, searchText = '') {
+    /**
+     * Show the selector popup.
+     * @param {Function} callback
+     * @param {String} searchText
+     * @param {EntitySelectorSearchOptions} searchOptions
+     */
+    show(callback, searchText = '', searchOptions = {}) {
         this.callback = callback;
+        this.getSelector().configureSearchOptions(searchOptions);
         this.getPopup().show();
 
         if (searchText) {
index b12eeb402ba14768d8b4ae573b9864ad4b15c844..5ad9914378e2632e446ffbcaff7728a5bc42fdc1 100644 (file)
@@ -1,6 +1,13 @@
 import {onChildEvent} from '../services/dom';
 import {Component} from './component';
 
+/**
+ * @typedef EntitySelectorSearchOptions
+ * @property entityTypes string
+ * @property entityPermission string
+ * @property searchEndpoint string
+ */
+
 /**
  * Entity Selector
  */
@@ -8,21 +15,35 @@ export class EntitySelector extends Component {
 
     setup() {
         this.elem = this.$el;
-        this.entityTypes = this.$opts.entityTypes || 'page,book,chapter';
-        this.entityPermission = this.$opts.entityPermission || 'view';
-        this.searchEndpoint = this.$opts.searchEndpoint || '/search/entity-selector';
 
         this.input = this.$refs.input;
         this.searchInput = this.$refs.search;
         this.loading = this.$refs.loading;
         this.resultsContainer = this.$refs.results;
 
+        this.searchOptions = {
+            entityTypes: this.$opts.entityTypes || 'page,book,chapter',
+            entityPermission: this.$opts.entityPermission || 'view',
+            searchEndpoint: this.$opts.searchEndpoint || '',
+        };
+
         this.search = '';
         this.lastClick = 0;
 
         this.setupListeners();
         this.showLoading();
-        this.initialLoad();
+
+        if (this.searchOptions.searchEndpoint) {
+            this.initialLoad();
+        }
+    }
+
+    /**
+     * @param {EntitySelectorSearchOptions} options
+     */
+    configureSearchOptions(options) {
+        Object.assign(this.searchOptions, options);
+        this.reset();
     }
 
     setupListeners() {
@@ -103,6 +124,10 @@ export class EntitySelector extends Component {
     }
 
     initialLoad() {
+        if (!this.searchOptions.searchEndpoint) {
+            throw new Error('Search endpoint not set for entity-selector load');
+        }
+
         window.$http.get(this.searchUrl()).then(resp => {
             this.resultsContainer.innerHTML = resp.data;
             this.hideLoading();
@@ -110,10 +135,15 @@ export class EntitySelector extends Component {
     }
 
     searchUrl() {
-        return `${this.searchEndpoint}?types=${encodeURIComponent(this.entityTypes)}&permission=${encodeURIComponent(this.entityPermission)}`;
+        const query = `types=${encodeURIComponent(this.searchOptions.entityTypes)}&permission=${encodeURIComponent(this.searchOptions.entityPermission)}`;
+        return `${this.searchOptions.searchEndpoint}?${query}`;
     }
 
     searchEntities(searchTerm) {
+        if (!this.searchOptions.searchEndpoint) {
+            throw new Error('Search endpoint not set for entity-selector load');
+        }
+
         this.input.value = '';
         const url = `${this.searchUrl()}&term=${encodeURIComponent(searchTerm)}`;
         window.$http.get(url).then(resp => {
index 9bb0bee04a3fb6be13e798d01352e0b59b6fa2bf..39af67229932910b3e283ed6859a53ffc67ef937 100644 (file)
@@ -14,6 +14,8 @@ export class PagePicker extends Component {
         this.defaultDisplay = this.$refs.defaultDisplay;
         this.buttonSep = this.$refs.buttonSeperator;
 
+        this.selectorEndpoint = this.$opts.selectorEndpoint;
+
         this.value = this.input.value;
         this.setupListeners();
     }
@@ -33,6 +35,10 @@ export class PagePicker extends Component {
         const selectorPopup = window.$components.first('entity-selector-popup');
         selectorPopup.show(entity => {
             this.setValue(entity.id, entity.name);
+        }, '', {
+            searchEndpoint: this.selectorEndpoint,
+            entityTypes: 'page',
+            entityPermission: 'view',
         });
     }
 
index 4909a59d066f9627b3756e951be2079940369083..511f1ebdae6a38caa7fa31b3d965cc9890e4694e 100644 (file)
@@ -73,7 +73,11 @@ export class Actions {
             const selectedText = selectionText || entity.name;
             const newText = `[${selectedText}](${entity.link})`;
             this.#replaceSelection(newText, newText.length, selectionRange);
-        }, selectionText);
+        }, selectionText, {
+            searchEndpoint: '/search/entity-selector',
+            entityTypes: 'page,book,chapter,bookshelf',
+            entityPermission: 'view',
+        });
     }
 
     // Show draw.io if enabled and handle save.
index f0a2dbe1ceb3e66231306ce71d1c43d1816d43bd..2bd4b630b038a35eecf9de0e301079ebdd85b4fa 100644 (file)
@@ -85,7 +85,11 @@ function filePickerCallback(callback, value, meta) {
                 text: entity.name,
                 title: entity.name,
             });
-        }, selectionText);
+        }, selectionText, {
+            searchEndpoint: '/search/entity-selector',
+            entityTypes: 'page,book,chapter,bookshelf',
+            entityPermission: 'view',
+        });
     }
 
     if (meta.filetype === 'image') {
index 147e3c2d5c9aceb2d4c573a6aef5d8e4f74335c7..da9e0227099aaa9e6adad45398884987c1c9435a 100644 (file)
@@ -58,6 +58,10 @@ export function register(editor) {
 
             editor.selection.collapse(false);
             editor.focus();
-        }, selectionText);
+        }, selectionText, {
+            searchEndpoint: '/search/entity-selector',
+            entityTypes: 'page,book,chapter,bookshelf',
+            entityPermission: 'view',
+        });
     });
 }
index d380c5871c8b38c630b9b4e575ba32876f971617..a3d4be5e981c3c7a480ccd3fc2e96deddd1d8fbc 100644 (file)
@@ -53,6 +53,7 @@
                     'name' => 'default_template_id',
                     'placeholder' => trans('entities.books_default_template_select'),
                     'value' => $book->default_template_id ?? null,
+                    'selectorEndpoint' => '/search/entity-selector-templates',
                 ])
             </div>
         </div>
@@ -65,5 +66,5 @@
     <button type="submit" class="button">{{ trans('entities.books_save') }}</button>
 </div>
 
-@include('entities.selector-popup', ['entityTypes' => 'page', 'selectorEndpoint' => '/search/entity-selector-templates'])
+@include('entities.selector-popup')
 @include('form.editor-translations')
\ No newline at end of file
index 7c565f43cf59bb850aa4d89dccadbe962d50853a..c6052c93af415b82d523f3163d3ceeaf0a45e108 100644 (file)
@@ -27,5 +27,5 @@
     <button type="submit" class="button">{{ trans('entities.chapters_save') }}</button>
 </div>
 
-@include('entities.selector-popup', ['entityTypes' => 'page', 'selectorEndpoint' => '/search/entity-selector-templates'])
+@include('entities.selector-popup')
 @include('form.editor-translations')
\ No newline at end of file
index d4c941e9a3358fa82e392d81639148c5a3019244..ac91725d63dc53d5f9f089c37bad8c1095e0c664 100644 (file)
@@ -5,7 +5,7 @@
                 <div class="popup-title">{{ trans('entities.entity_select') }}</div>
                 <button refs="popup@hide" type="button" class="popup-header-close">@icon('close')</button>
             </div>
-            @include('entities.selector', ['name' => 'entity-selector'])
+            @include('entities.selector', ['name' => 'entity-selector', 'selectorEndpoint' => ''])
             <div class="popup-footer">
                 <button refs="entity-selector-popup@select" type="button" disabled class="button">{{ trans('common.select') }}</button>
             </div>
index d9810d575df311b0d0b5955c0afa231c570f74ca..ad0a9d516710b6def674dff1a417bfda2a06e877 100644 (file)
@@ -1,6 +1,7 @@
 
 {{--Depends on entity selector popup--}}
-<div component="page-picker">
+<div component="page-picker"
+     option:page-picker:selector-endpoint="{{ $selectorEndpoint }}">
     <div class="input-base overflow-hidden height-auto">
         <span @if($value) hidden @endif refs="page-picker@default-display" class="text-muted italic">{{ $placeholder }}</span>
         <a @if(!$value) hidden @endif href="{{ url('/link/' . $value) }}" target="_blank" rel="noopener" class="text-page" refs="page-picker@display">#{{$value}}, {{$value ? \BookStack\Entities\Models\Page::query()->visible()->find($value)->name ?? '' : '' }}</a>
index 7112ebcff64d44fb9f5daee60da23a5f9a8cd4c2..4845e2055fc38f3d75248910666cf55978a89962 100644 (file)
@@ -3,7 +3,7 @@
 @section('card')
     <h1 id="customization" class="list-heading">{{ trans('settings.app_customization') }}</h1>
     <form action="{{ url("/settings/customization") }}" method="POST" enctype="multipart/form-data">
-        {!! csrf_field() !!}
+        {{ csrf_field() }}
         <input type="hidden" name="section" value="customization">
 
         <div class="setting-list">
                     </select>
 
                     <div refs="setting-homepage-control@page-picker-container" style="display: none;" class="mt-m">
-                        @include('form.page-picker', ['name' => 'setting-app-homepage', 'placeholder' => trans('settings.app_homepage_select'), 'value' => setting('app-homepage')])
+                        @include('form.page-picker', [
+                            'name' => 'setting-app-homepage',
+                            'placeholder' => trans('settings.app_homepage_select'),
+                            'value' => setting('app-homepage'),
+                            'selectorEndpoint' => '/search/entity-selector',
+                        ])
                     </div>
                 </div>
             </div>
 @endsection
 
 @section('after-content')
-    @include('entities.selector-popup', ['entityTypes' => 'page'])
+    @include('entities.selector-popup')
 @endsection
index a724c99ce751dea451b2750377fc51b2cf32be2d..a75dd6ac1b53b62b3929d33d7d324a827810e966 100644 (file)
@@ -89,5 +89,5 @@
     <button type="submit" class="button">{{ trans('entities.shelves_save') }}</button>
 </div>
 
-@include('entities.selector-popup', ['entityTypes' => 'page', 'selectorEndpoint' => '/search/entity-selector-templates'])
+@include('entities.selector-popup')
 @include('form.editor-translations')
\ No newline at end of file