]> BookStack Code Mirror - bookstack/commitdiff
Lexical: Added new WYSIWYG to chapter/book/shelf descriptions
authorDan Brown <redacted>
Thu, 26 Jun 2025 10:00:17 +0000 (11:00 +0100)
committerDan Brown <redacted>
Thu, 26 Jun 2025 10:00:17 +0000 (11:00 +0100)
resources/js/components/wysiwyg-input.js [deleted file]
resources/js/components/wysiwyg-input.ts [new file with mode: 0644]
resources/js/wysiwyg-tinymce/config.js
resources/js/wysiwyg/index.ts
resources/views/books/parts/form.blade.php
resources/views/chapters/parts/form.blade.php
resources/views/form/description-html-input.blade.php
resources/views/shelves/parts/form.blade.php

diff --git a/resources/js/components/wysiwyg-input.js b/resources/js/components/wysiwyg-input.js
deleted file mode 100644 (file)
index aa21a63..0000000
+++ /dev/null
@@ -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];
-        });
-    }
-
-}
diff --git a/resources/js/components/wysiwyg-input.ts b/resources/js/components/wysiwyg-input.ts
new file mode 100644 (file)
index 0000000..85ebcea
--- /dev/null
@@ -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;
+            });
+        });
+    }
+}
index 1666aa50066af25f3ab38b12a996cce2148672bb..c0cfd37d97b21eb8186415e9da9d00b50cdc9329 100644 (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
  * @property {Element} containerElement
index 8f6c41c1ac4f118b9103cc32c03130e9e06177f0..b9770219de9b103ee53a3a12b676ef03797926a2 100644 (file)
@@ -123,6 +123,8 @@ export function createBasicEditorInstance(container: HTMLElement, htmlContent: s
 
 export class SimpleWysiwygEditorInterface {
     protected context: EditorUiContext;
+    protected onChangeListeners: (() => void)[] = [];
+    protected editorListenerTeardown: (() => void)|null = null;
 
     constructor(context: EditorUiContext) {
         this.context = context;
@@ -132,6 +134,11 @@ export class SimpleWysiwygEditorInterface {
         return await getEditorContentAsHtml(this.context.editor);
     }
 
+    onChange(listener: () => void) {
+        this.onChangeListeners.push(listener);
+        this.startListeningToChanges();
+    }
+
     focus(): void {
         focusEditor(this.context.editor);
     }
@@ -139,5 +146,20 @@ export class SimpleWysiwygEditorInterface {
     remove() {
         this.context.editorDOM.remove();
         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();
+             }
+        });
     }
 }
\ No newline at end of file
index ee261e72d4a55b5449f84891cafcec85502711c2..44d495c27770afe3b512222706f02c02ae5cf8fc 100644 (file)
@@ -1,7 +1,3 @@
-@push('head')
-    <script src="{{ versioned_asset('libs/tinymce/tinymce.min.js') }}" nonce="{{ $cspNonce }}"></script>
-@endpush
-
 {{ csrf_field() }}
 <div class="form-group title-input">
     <label for="name">{{ trans('common.name') }}</label>
index 602693916ea04ef2b00c9779b7a93ea0c0a28e61..70721631d43dbeefe5fbd613086f0bad6d650f6d 100644 (file)
@@ -1,7 +1,3 @@
-@push('head')
-    <script src="{{ versioned_asset('libs/tinymce/tinymce.min.js') }}" nonce="{{ $cspNonce }}"></script>
-@endpush
-
 {{ csrf_field() }}
 <div class="form-group title-input">
     <label for="name">{{ trans('common.name') }}</label>
index 3cf726ba48932a2ff88ed144418c4b2742bbf474..52244eda6f9f4cd2369cdab3ce295108532066e7 100644 (file)
@@ -1,5 +1,4 @@
 <textarea component="wysiwyg-input"
-          option:wysiwyg-input:language="{{ $locale->htmlLang() }}"
           option:wysiwyg-input:text-direction="{{ $locale->htmlDirection() }}"
           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>
index 7790ba5a4e7fab2e994999a8e17fbfaaf3c8e8f5..0207d72780be1aeb32e39d899bdb4244477fcb88 100644 (file)
@@ -1,7 +1,3 @@
-@push('head')
-    <script src="{{ versioned_asset('libs/tinymce/tinymce.min.js') }}" nonce="{{ $cspNonce }}"></script>
-@endpush
-
 {{ csrf_field() }}
 <div class="form-group title-input">
     <label for="name">{{ trans('common.name') }}</label>