1
0
mirror of https://github.com/BookStackApp/BookStack.git synced 2025-07-25 18:42:01 +03:00

Lexical: Added new WYSIWYG to chapter/book/shelf descriptions

This commit is contained in:
Dan Brown
2025-06-26 11:00:17 +01:00
parent b80992ca59
commit 02a35b6db4
8 changed files with 54 additions and 84 deletions

View File

@ -1,23 +0,0 @@
import {Component} from './component';
import {buildForInput} from '../wysiwyg-tinymce/config';
export class WysiwygInput extends Component {
setup() {
this.elem = this.$el;
const config = buildForInput({
language: this.$opts.language,
containerElement: this.elem,
darkMode: document.documentElement.classList.contains('dark-mode'),
textDirection: this.$opts.textDirection,
translations: {},
translationMap: window.editor_translations,
});
window.tinymce.init(config).then(editors => {
this.editor = editors[0];
});
}
}

View File

@ -0,0 +1,32 @@
import {Component} from './component';
import {el} from "../wysiwyg/utils/dom";
import {SimpleWysiwygEditorInterface} from "../wysiwyg";
export class WysiwygInput extends Component {
private elem!: HTMLTextAreaElement;
private wysiwygEditor!: SimpleWysiwygEditorInterface;
private textDirection!: string;
async setup() {
this.elem = this.$el as HTMLTextAreaElement;
this.textDirection = this.$opts.textDirection;
type WysiwygModule = typeof import('../wysiwyg');
const wysiwygModule = (await window.importVersioned('wysiwyg')) as WysiwygModule;
const container = el('div', {class: 'comment-editor-container'});
this.elem.parentElement?.appendChild(container);
this.elem.hidden = true;
this.wysiwygEditor = wysiwygModule.createBasicEditorInstance(container as HTMLElement, this.elem.value, {
darkMode: document.documentElement.classList.contains('dark-mode'),
textDirection: this.textDirection,
translations: (window as unknown as Record<string, Object>).editor_translations,
});
this.wysiwygEditor.onChange(() => {
this.wysiwygEditor.getContentAsHtml().then(html => {
this.elem.value = html;
});
});
}
}

View File

@ -310,54 +310,6 @@ export function buildForEditor(options) {
}; };
} }
/**
* @param {WysiwygConfigOptions} options
* @return {RawEditorOptions}
*/
export function buildForInput(options) {
// Set language
window.tinymce.addI18n(options.language, options.translationMap);
// BookStack Version
const version = document.querySelector('script[src*="/dist/app.js"]').getAttribute('src').split('?version=')[1];
// Return config object
return {
width: '100%',
height: '185px',
target: options.containerElement,
cache_suffix: `?version=${version}`,
content_css: [
window.baseUrl('/dist/styles.css'),
],
branding: false,
skin: options.darkMode ? 'tinymce-5-dark' : 'tinymce-5',
body_class: 'wysiwyg-input',
browser_spellcheck: true,
relative_urls: false,
language: options.language,
directionality: options.textDirection,
remove_script_host: false,
document_base_url: window.baseUrl('/'),
end_container_on_empty_block: true,
remove_trailing_brs: false,
statusbar: false,
menubar: false,
plugins: 'link autolink lists',
contextmenu: false,
toolbar: 'bold italic link bullist numlist',
content_style: getContentStyle(options),
file_picker_types: 'file',
valid_elements: 'p,a[href|title|target],ol,ul,li,strong,em,br',
file_picker_callback: filePickerCallback,
init_instance_callback(editor) {
addCustomHeadContent(editor.getDoc());
editor.contentDocument.documentElement.classList.toggle('dark-mode', options.darkMode);
},
};
}
/** /**
* @typedef {Object} WysiwygConfigOptions * @typedef {Object} WysiwygConfigOptions
* @property {Element} containerElement * @property {Element} containerElement

View File

@ -123,6 +123,8 @@ export function createBasicEditorInstance(container: HTMLElement, htmlContent: s
export class SimpleWysiwygEditorInterface { export class SimpleWysiwygEditorInterface {
protected context: EditorUiContext; protected context: EditorUiContext;
protected onChangeListeners: (() => void)[] = [];
protected editorListenerTeardown: (() => void)|null = null;
constructor(context: EditorUiContext) { constructor(context: EditorUiContext) {
this.context = context; this.context = context;
@ -132,6 +134,11 @@ export class SimpleWysiwygEditorInterface {
return await getEditorContentAsHtml(this.context.editor); return await getEditorContentAsHtml(this.context.editor);
} }
onChange(listener: () => void) {
this.onChangeListeners.push(listener);
this.startListeningToChanges();
}
focus(): void { focus(): void {
focusEditor(this.context.editor); focusEditor(this.context.editor);
} }
@ -139,5 +146,20 @@ export class SimpleWysiwygEditorInterface {
remove() { remove() {
this.context.editorDOM.remove(); this.context.editorDOM.remove();
this.context.manager.teardown(); this.context.manager.teardown();
if (this.editorListenerTeardown) {
this.editorListenerTeardown();
}
}
protected startListeningToChanges(): void {
if (this.editorListenerTeardown) {
return;
}
this.editorListenerTeardown = this.context.editor.registerUpdateListener(() => {
for (const listener of this.onChangeListeners) {
listener();
}
});
} }
} }

View File

@ -1,7 +1,3 @@
@push('head')
<script src="{{ versioned_asset('libs/tinymce/tinymce.min.js') }}" nonce="{{ $cspNonce }}"></script>
@endpush
{{ csrf_field() }} {{ csrf_field() }}
<div class="form-group title-input"> <div class="form-group title-input">
<label for="name">{{ trans('common.name') }}</label> <label for="name">{{ trans('common.name') }}</label>

View File

@ -1,7 +1,3 @@
@push('head')
<script src="{{ versioned_asset('libs/tinymce/tinymce.min.js') }}" nonce="{{ $cspNonce }}"></script>
@endpush
{{ csrf_field() }} {{ csrf_field() }}
<div class="form-group title-input"> <div class="form-group title-input">
<label for="name">{{ trans('common.name') }}</label> <label for="name">{{ trans('common.name') }}</label>

View File

@ -1,5 +1,4 @@
<textarea component="wysiwyg-input" <textarea component="wysiwyg-input"
option:wysiwyg-input:language="{{ $locale->htmlLang() }}"
option:wysiwyg-input:text-direction="{{ $locale->htmlDirection() }}" option:wysiwyg-input:text-direction="{{ $locale->htmlDirection() }}"
id="description_html" name="description_html" rows="5" id="description_html" name="description_html" rows="5"
@if($errors->has('description_html')) class="text-neg" @endif>@if(isset($model) || old('description_html')){{ old('description_html') ?? $model->descriptionHtml()}}@endif</textarea> @if($errors->has('description_html')) class="text-neg" @endif>@if(isset($model) || old('description_html')){{ old('description_html') ?? $model->descriptionHtml()}}@endif</textarea>

View File

@ -1,7 +1,3 @@
@push('head')
<script src="{{ versioned_asset('libs/tinymce/tinymce.min.js') }}" nonce="{{ $cspNonce }}"></script>
@endpush
{{ csrf_field() }} {{ csrf_field() }}
<div class="form-group title-input"> <div class="form-group title-input">
<label for="name">{{ trans('common.name') }}</label> <label for="name">{{ trans('common.name') }}</label>