Input WYSIWYG: Added dynamic options for entity selector popups

So that multiple elements on the page can share the same popup, with
different search options.
This commit is contained in:
Dan Brown 2023-12-19 12:09:57 +00:00
parent c07aa056c2
commit 2fbed3919b
No known key found for this signature in database
GPG Key ID: 46D9F943C24A2EF9
12 changed files with 79 additions and 17 deletions

View File

@ -15,8 +15,15 @@ export class EntitySelectorPopup extends Component {
window.$events.listen('entity-select-confirm', this.handleConfirmedSelection.bind(this)); 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.callback = callback;
this.getSelector().configureSearchOptions(searchOptions);
this.getPopup().show(); this.getPopup().show();
if (searchText) { if (searchText) {

View File

@ -1,6 +1,13 @@
import {onChildEvent} from '../services/dom'; import {onChildEvent} from '../services/dom';
import {Component} from './component'; import {Component} from './component';
/**
* @typedef EntitySelectorSearchOptions
* @property entityTypes string
* @property entityPermission string
* @property searchEndpoint string
*/
/** /**
* Entity Selector * Entity Selector
*/ */
@ -8,22 +15,36 @@ export class EntitySelector extends Component {
setup() { setup() {
this.elem = this.$el; 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.input = this.$refs.input;
this.searchInput = this.$refs.search; this.searchInput = this.$refs.search;
this.loading = this.$refs.loading; this.loading = this.$refs.loading;
this.resultsContainer = this.$refs.results; 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.search = '';
this.lastClick = 0; this.lastClick = 0;
this.setupListeners(); this.setupListeners();
this.showLoading(); this.showLoading();
if (this.searchOptions.searchEndpoint) {
this.initialLoad(); this.initialLoad();
} }
}
/**
* @param {EntitySelectorSearchOptions} options
*/
configureSearchOptions(options) {
Object.assign(this.searchOptions, options);
this.reset();
}
setupListeners() { setupListeners() {
this.elem.addEventListener('click', this.onClick.bind(this)); this.elem.addEventListener('click', this.onClick.bind(this));
@ -103,6 +124,10 @@ export class EntitySelector extends Component {
} }
initialLoad() { initialLoad() {
if (!this.searchOptions.searchEndpoint) {
throw new Error('Search endpoint not set for entity-selector load');
}
window.$http.get(this.searchUrl()).then(resp => { window.$http.get(this.searchUrl()).then(resp => {
this.resultsContainer.innerHTML = resp.data; this.resultsContainer.innerHTML = resp.data;
this.hideLoading(); this.hideLoading();
@ -110,10 +135,15 @@ export class EntitySelector extends Component {
} }
searchUrl() { 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) { searchEntities(searchTerm) {
if (!this.searchOptions.searchEndpoint) {
throw new Error('Search endpoint not set for entity-selector load');
}
this.input.value = ''; this.input.value = '';
const url = `${this.searchUrl()}&term=${encodeURIComponent(searchTerm)}`; const url = `${this.searchUrl()}&term=${encodeURIComponent(searchTerm)}`;
window.$http.get(url).then(resp => { window.$http.get(url).then(resp => {

View File

@ -14,6 +14,8 @@ export class PagePicker extends Component {
this.defaultDisplay = this.$refs.defaultDisplay; this.defaultDisplay = this.$refs.defaultDisplay;
this.buttonSep = this.$refs.buttonSeperator; this.buttonSep = this.$refs.buttonSeperator;
this.selectorEndpoint = this.$opts.selectorEndpoint;
this.value = this.input.value; this.value = this.input.value;
this.setupListeners(); this.setupListeners();
} }
@ -33,6 +35,10 @@ export class PagePicker extends Component {
const selectorPopup = window.$components.first('entity-selector-popup'); const selectorPopup = window.$components.first('entity-selector-popup');
selectorPopup.show(entity => { selectorPopup.show(entity => {
this.setValue(entity.id, entity.name); this.setValue(entity.id, entity.name);
}, '', {
searchEndpoint: this.selectorEndpoint,
entityTypes: 'page',
entityPermission: 'view',
}); });
} }

View File

@ -73,7 +73,11 @@ export class Actions {
const selectedText = selectionText || entity.name; const selectedText = selectionText || entity.name;
const newText = `[${selectedText}](${entity.link})`; const newText = `[${selectedText}](${entity.link})`;
this.#replaceSelection(newText, newText.length, selectionRange); 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. // Show draw.io if enabled and handle save.

View File

@ -85,7 +85,11 @@ function filePickerCallback(callback, value, meta) {
text: entity.name, text: entity.name,
title: entity.name, title: entity.name,
}); });
}, selectionText); }, selectionText, {
searchEndpoint: '/search/entity-selector',
entityTypes: 'page,book,chapter,bookshelf',
entityPermission: 'view',
});
} }
if (meta.filetype === 'image') { if (meta.filetype === 'image') {

View File

@ -58,6 +58,10 @@ export function register(editor) {
editor.selection.collapse(false); editor.selection.collapse(false);
editor.focus(); editor.focus();
}, selectionText); }, selectionText, {
searchEndpoint: '/search/entity-selector',
entityTypes: 'page,book,chapter,bookshelf',
entityPermission: 'view',
});
}); });
} }

View File

@ -53,6 +53,7 @@
'name' => 'default_template_id', 'name' => 'default_template_id',
'placeholder' => trans('entities.books_default_template_select'), 'placeholder' => trans('entities.books_default_template_select'),
'value' => $book->default_template_id ?? null, 'value' => $book->default_template_id ?? null,
'selectorEndpoint' => '/search/entity-selector-templates',
]) ])
</div> </div>
</div> </div>
@ -65,5 +66,5 @@
<button type="submit" class="button">{{ trans('entities.books_save') }}</button> <button type="submit" class="button">{{ trans('entities.books_save') }}</button>
</div> </div>
@include('entities.selector-popup', ['entityTypes' => 'page', 'selectorEndpoint' => '/search/entity-selector-templates']) @include('entities.selector-popup')
@include('form.editor-translations') @include('form.editor-translations')

View File

@ -27,5 +27,5 @@
<button type="submit" class="button">{{ trans('entities.chapters_save') }}</button> <button type="submit" class="button">{{ trans('entities.chapters_save') }}</button>
</div> </div>
@include('entities.selector-popup', ['entityTypes' => 'page', 'selectorEndpoint' => '/search/entity-selector-templates']) @include('entities.selector-popup')
@include('form.editor-translations') @include('form.editor-translations')

View File

@ -5,7 +5,7 @@
<div class="popup-title">{{ trans('entities.entity_select') }}</div> <div class="popup-title">{{ trans('entities.entity_select') }}</div>
<button refs="popup@hide" type="button" class="popup-header-close">@icon('close')</button> <button refs="popup@hide" type="button" class="popup-header-close">@icon('close')</button>
</div> </div>
@include('entities.selector', ['name' => 'entity-selector']) @include('entities.selector', ['name' => 'entity-selector', 'selectorEndpoint' => ''])
<div class="popup-footer"> <div class="popup-footer">
<button refs="entity-selector-popup@select" type="button" disabled class="button">{{ trans('common.select') }}</button> <button refs="entity-selector-popup@select" type="button" disabled class="button">{{ trans('common.select') }}</button>
</div> </div>

View File

@ -1,6 +1,7 @@
{{--Depends on entity selector popup--}} {{--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"> <div class="input-base overflow-hidden height-auto">
<span @if($value) hidden @endif refs="page-picker@default-display" class="text-muted italic">{{ $placeholder }}</span> <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> <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>

View File

@ -3,7 +3,7 @@
@section('card') @section('card')
<h1 id="customization" class="list-heading">{{ trans('settings.app_customization') }}</h1> <h1 id="customization" class="list-heading">{{ trans('settings.app_customization') }}</h1>
<form action="{{ url("/settings/customization") }}" method="POST" enctype="multipart/form-data"> <form action="{{ url("/settings/customization") }}" method="POST" enctype="multipart/form-data">
{!! csrf_field() !!} {{ csrf_field() }}
<input type="hidden" name="section" value="customization"> <input type="hidden" name="section" value="customization">
<div class="setting-list"> <div class="setting-list">
@ -133,7 +133,12 @@
</select> </select>
<div refs="setting-homepage-control@page-picker-container" style="display: none;" class="mt-m"> <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> </div>
</div> </div>
@ -168,5 +173,5 @@
@endsection @endsection
@section('after-content') @section('after-content')
@include('entities.selector-popup', ['entityTypes' => 'page']) @include('entities.selector-popup')
@endsection @endsection

View File

@ -89,5 +89,5 @@
<button type="submit" class="button">{{ trans('entities.shelves_save') }}</button> <button type="submit" class="button">{{ trans('entities.shelves_save') }}</button>
</div> </div>
@include('entities.selector-popup', ['entityTypes' => 'page', 'selectorEndpoint' => '/search/entity-selector-templates']) @include('entities.selector-popup')
@include('form.editor-translations') @include('form.editor-translations')