]> BookStack Code Mirror - bookstack/commitdiff
Lexical: Added format previews to format buttons
authorDan Brown <redacted>
Thu, 30 May 2024 11:25:25 +0000 (12:25 +0100)
committerDan Brown <redacted>
Thu, 30 May 2024 11:25:25 +0000 (12:25 +0100)
resources/js/wysiwyg/ui/framework/buttons.ts
resources/js/wysiwyg/ui/framework/containers.ts
resources/js/wysiwyg/ui/toolbars.ts
resources/sass/_editor.scss [new file with mode: 0644]
resources/sass/styles.scss
resources/views/pages/parts/wysiwyg-editor.blade.php

index 51c7d294d90947963b50bcf2e4a3436a86b86625..2a6f5a976fd21532bab01b5341fecd9f8615ea78 100644 (file)
@@ -19,22 +19,70 @@ export class EditorButton extends EditorUiElement {
     protected buildDOM(): HTMLButtonElement {
         const button = el('button', {
             type: 'button',
-            class: 'editor-toolbar-button',
+            class: 'editor-button',
         }, [this.definition.label]) as HTMLButtonElement;
 
-        button.addEventListener('click', event => {
-            this.definition.action(this.getContext().editor);
-        });
+        button.addEventListener('click', this.onClick.bind(this));
 
         return button;
     }
 
+    protected onClick() {
+        this.definition.action(this.getContext().editor);
+    }
+
     updateActiveState(selection: BaseSelection|null) {
         const isActive = this.definition.isActive(selection);
-        this.dom?.classList.toggle('editor-toolbar-button-active', isActive);
+        this.dom?.classList.toggle('editor-button-active', isActive);
     }
 
     updateState(state: EditorUiStateUpdate): void {
         this.updateActiveState(state.selection);
     }
+}
+
+export class FormatPreviewButton extends EditorButton {
+    protected previewSampleElement: HTMLElement;
+
+    constructor(previewSampleElement: HTMLElement,definition: EditorButtonDefinition) {
+        super(definition);
+        this.previewSampleElement = previewSampleElement;
+    }
+
+    protected buildDOM(): HTMLButtonElement {
+        const button = super.buildDOM();
+        button.innerHTML = '';
+
+        const preview = el('span', {
+            class: 'editor-button-format-preview'
+        }, [this.definition.label]);
+
+        const stylesToApply = this.getStylesFromPreview();
+        console.log(stylesToApply);
+        for (const style of Object.keys(stylesToApply)) {
+            preview.style.setProperty(style, stylesToApply[style]);
+        }
+
+        button.append(preview);
+        return button;
+    }
+
+    protected getStylesFromPreview(): Record<string, string> {
+        const wrap = el('div', {style: 'display: none', hidden: 'true', class: 'page-content'});
+        const sampleClone = this.previewSampleElement.cloneNode() as HTMLElement;
+        sampleClone.textContent = this.definition.label;
+        wrap.append(sampleClone);
+        document.body.append(wrap);
+
+        const propertiesToFetch = ['color', 'font-size', 'background-color', 'border-inline-start'];
+        const propertiesToReturn: Record<string, string> = {};
+
+        const computed = window.getComputedStyle(sampleClone);
+        for (const property of propertiesToFetch) {
+            propertiesToReturn[property] = computed.getPropertyValue(property);
+        }
+        wrap.remove();
+
+        return propertiesToReturn;
+    }
 }
\ No newline at end of file
index 9ef59c72f1938501fa6f14315d4a2fbf9f808112..e58988e7b843dc11b6708a55ab1904a343002df4 100644 (file)
@@ -30,11 +30,62 @@ export class EditorContainerUiElement extends EditorUiElement {
     }
 }
 
-export class EditorFormatMenu extends EditorContainerUiElement {
-    buildDOM(): HTMLElement {
+export class EditorSimpleClassContainer extends EditorContainerUiElement {
+    protected className;
+
+    constructor(className: string, children: EditorUiElement[]) {
+        super(children);
+        this.className = className;
+    }
+
+    protected buildDOM(): HTMLElement {
         return el('div', {
-            class: 'editor-format-menu'
+            class: this.className,
         }, this.getChildren().map(child => child.getDOMElement()));
     }
+}
 
+export class EditorFormatMenu extends EditorContainerUiElement {
+    buildDOM(): HTMLElement {
+        const childElements: HTMLElement[] = this.getChildren().map(child => child.getDOMElement());
+        const menu = el('div', {
+            class: 'editor-format-menu-dropdown editor-dropdown-menu editor-menu-list',
+            hidden: 'true',
+        }, childElements);
+
+        const toggle = el('button', {
+            class: 'editor-format-menu-toggle',
+            type: 'button',
+        }, ['Formats']);
+
+        const wrapper = el('div', {
+            class: 'editor-format-menu editor-dropdown-menu-container',
+        }, [toggle, menu]);
+
+        let clickListener: Function|null = null;
+
+        const hide = () => {
+            menu.hidden = true;
+            if (clickListener) {
+                window.removeEventListener('click', clickListener as EventListener);
+            }
+        };
+
+        const show = () => {
+            menu.hidden = false
+            clickListener = (event: MouseEvent) => {
+                if (!wrapper.contains(event.target as HTMLElement)) {
+                    hide();
+                }
+            }
+            window.addEventListener('click', clickListener as EventListener);
+        };
+
+        toggle.addEventListener('click', event => {
+            menu.hasAttribute('hidden') ? show() : hide();
+        });
+        menu.addEventListener('mouseleave', hide);
+
+        return wrapper;
+    }
 }
\ No newline at end of file
index 0f46f5b2a55032e1a3ae6cc0436b8e3e9bbef049..2d5063cf4447f48a97dc2bd8e9ef35afb3a8f07a 100644 (file)
@@ -1,4 +1,4 @@
-import {EditorButton} from "./framework/buttons";
+import {EditorButton, FormatPreviewButton} from "./framework/buttons";
 import {
     blockquote, bold, code,
     dangerCallout,
@@ -9,25 +9,26 @@ import {
     undo,
     warningCallout
 } from "./defaults/button-definitions";
-import {EditorContainerUiElement, EditorFormatMenu} from "./framework/containers";
+import {EditorContainerUiElement, EditorFormatMenu, EditorSimpleClassContainer} from "./framework/containers";
+import {el} from "../helpers";
 
 
 export function getMainEditorFullToolbar(): EditorContainerUiElement {
-    return new EditorContainerUiElement([
+    return new EditorSimpleClassContainer('editor-toolbar-main', [
         new EditorButton(undo),
         new EditorButton(redo),
 
         new EditorFormatMenu([
-            new EditorButton(h2),
-            new EditorButton(h3),
-            new EditorButton(h4),
-            new EditorButton(h5),
-            new EditorButton(blockquote),
-            new EditorButton(paragraph),
-            new EditorButton(infoCallout),
-            new EditorButton(successCallout),
-            new EditorButton(warningCallout),
-            new EditorButton(dangerCallout),
+            new FormatPreviewButton(el('h2'), h2),
+            new FormatPreviewButton(el('h3'), h3),
+            new FormatPreviewButton(el('h4'), h4),
+            new FormatPreviewButton(el('h5'), h5),
+            new FormatPreviewButton(el('blockquote'), blockquote),
+            new FormatPreviewButton(el('p'), paragraph),
+            new FormatPreviewButton(el('p', {class: 'callout info'}), infoCallout),
+            new FormatPreviewButton(el('p', {class: 'callout success'}), successCallout),
+            new FormatPreviewButton(el('p', {class: 'callout warning'}), warningCallout),
+            new FormatPreviewButton(el('p', {class: 'callout danger'}), dangerCallout),
         ]),
 
         new EditorButton(bold),
diff --git a/resources/sass/_editor.scss b/resources/sass/_editor.scss
new file mode 100644 (file)
index 0000000..48912be
--- /dev/null
@@ -0,0 +1,49 @@
+// Main UI elements
+.editor-toolbar-main {
+  display: flex;
+}
+
+// Buttons
+.editor-button {
+  border: 1px solid #DDD;
+  font-size: 12px;
+  padding: 4px 6px;
+  color: #444;
+}
+.editor-button:hover {
+  background-color: #EEE;
+  cursor: pointer;
+  color: #000;
+}
+.editor-button-active, .editor-button-active:hover {
+  background-color: #ceebff;
+  color: #000;
+}
+.editor-button-format-preview {
+  padding: 4px 6px;
+  display: block;
+}
+
+// Containers
+.editor-dropdown-menu-container {
+    position: relative;
+}
+.editor-dropdown-menu {
+  position: absolute;
+  background-color: #FFF;
+  box-shadow: 0 0 6px 0 rgba(0, 0, 0, 0.15);
+  z-index: 99;
+  min-width: 120px;
+}
+.editor-menu-list {
+  display: flex;
+  flex-direction: column;
+}
+.editor-menu-list > .editor-button {
+  border-bottom: 0;
+  text-align: start;
+}
+
+.editor-format-menu .editor-dropdown-menu {
+  min-width: 320px;
+}
\ No newline at end of file
index f52b61992622badae7a85b2a2a42c35fc6fadd76..636367e3aeb0f14cce35fa77e4f903455914335d 100644 (file)
@@ -15,6 +15,7 @@
 @import "forms";
 @import "animations";
 @import "tinymce";
+@import "editor";
 @import "codemirror";
 @import "components";
 @import "header";
index b48e10570b2dc6d2e1dca5807940d017dc42a756..940c005b5b737535353230e31f28cfa230221713 100644 (file)
@@ -6,23 +6,19 @@
      option:wysiwyg-editor:server-upload-limit-text="{{ trans('errors.server_upload_limit') }}"
      class="">
 
-    <style>
-        .editor-toolbar-button-active {
-            background-color: tomato;
-        }
-    </style>
+    <div class="editor-container">
+        <div refs="wysiwyg-editor@edit-area" contenteditable="true">
+            <p id="Content!">Some <strong>content</strong> here</p>
+            <p>This has a <a href="https://example.com" target="_blank" title="Link to example">link</a> in it</p>
+            <h2>List below this h2 header</h2>
+            <ul>
+                <li>Hello</li>
+            </ul>
 
-    <div refs="wysiwyg-editor@edit-area" contenteditable="true">
-        <p id="Content!">Some <strong>content</strong> here</p>
-        <p>This has a <a href="https://example.com" target="_blank" title="Link to example">link</a> in it</p>
-        <h2>List below this h2 header</h2>
-        <ul>
-            <li>Hello</li>
-        </ul>
-
-        <p class="callout danger">
-            Hello there, this is an info callout
-        </p>
+            <p class="callout danger">
+                Hello there, this is an info callout
+            </p>
+        </div>
     </div>
 
     <div id="lexical-debug" style="white-space: pre-wrap; font-size: 12px; height: 200px; overflow-y: scroll; background-color: #000; padding: 1rem; border-radius: 4px; color: #FFF;"></div>