]> BookStack Code Mirror - bookstack/commitdiff
Lexical: Refined editor UI
authorDan Brown <redacted>
Mon, 9 Sep 2024 13:06:41 +0000 (14:06 +0100)
committerDan Brown <redacted>
Mon, 9 Sep 2024 13:06:41 +0000 (14:06 +0100)
- Cleaned up dropdown lists to look integrated
- Added icons for color picker clear and menu list items

resources/icons/editor/color-clear.svg [new file with mode: 0644]
resources/js/wysiwyg/todo.md
resources/js/wysiwyg/ui/defaults/buttons/tables.ts
resources/js/wysiwyg/ui/framework/blocks/color-picker.ts
resources/js/wysiwyg/ui/framework/blocks/dropdown-button.ts
resources/js/wysiwyg/ui/framework/blocks/menu-button.ts [new file with mode: 0644]
resources/js/wysiwyg/ui/framework/blocks/separator.ts [new file with mode: 0644]
resources/js/wysiwyg/ui/toolbars.ts
resources/sass/_editor.scss

diff --git a/resources/icons/editor/color-clear.svg b/resources/icons/editor/color-clear.svg
new file mode 100644 (file)
index 0000000..5d08502
--- /dev/null
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path d="M800-436q0 36-8 69t-22 63l-62-60q6-17 9-34.5t3-37.5q0-47-17.5-89T650-600L480-768l-88 86-56-56 144-142 226 222q44 42 69 99.5T800-436Zm-8 380L668-180q-41 29-88 44.5T480-120q-133 0-226.5-92.5T160-436q0-51 16-98t48-90L56-792l56-56 736 736-56 56ZM480-200q36 0 68.5-10t61.5-28L280-566q-21 32-30.5 64t-9.5 66q0 98 70 167t170 69Zm-37-204Zm110-116Z"/></svg>
\ No newline at end of file
index 9c196d6d306975a90a6e5e32ec8a1bfd3452fd24..498d286fd45b75040c08083753ad9b27dc7e27a1 100644 (file)
@@ -11,6 +11,7 @@
 ## Secondary Todo
 
 - Color picker support in table form color fields
+- Color picker for color controls
 - Table caption text support
 - Support media src conversions (https://github.com/tinymce/tinymce/blob/release/6.6/modules/tinymce/src/plugins/media/main/ts/core/UrlPatterns.ts)
 
index 49e36bdacbeb5003d058536a9df454babfd70387..fc4196f0a0888e9ab8ac4d5e41491049567c0a7c 100644 (file)
@@ -347,6 +347,7 @@ export const deleteColumn: EditorButtonDefinition = {
 
 export const cellProperties: EditorButtonDefinition = {
     label: 'Cell properties',
+    format: 'long',
     action(context: EditorUiContext) {
         context.editor.getEditorState().read(() => {
             const cell = $getNodeFromSelection($getSelection(), $isCustomTableCellNode);
@@ -361,6 +362,7 @@ export const cellProperties: EditorButtonDefinition = {
 
 export const mergeCells: EditorButtonDefinition = {
     label: 'Merge cells',
+    format: 'long',
     action(context: EditorUiContext) {
         context.editor.update(() => {
             const selection = $getSelection();
@@ -377,6 +379,7 @@ export const mergeCells: EditorButtonDefinition = {
 
 export const splitCell: EditorButtonDefinition = {
     label: 'Split cell',
+    format: 'long',
     action(context: EditorUiContext) {
         context.editor.update(() => {
             $unmergeCell();
index 48e313f5cca213e49309f3c0d9bace0771a2941f..b068fb4f0bf323e7f5f91890bd95f61b07f1fb7d 100644 (file)
@@ -3,6 +3,8 @@ import {$getSelection} from "lexical";
 import {$patchStyleText} from "@lexical/selection";
 import {el} from "../../../utils/dom";
 
+import removeIcon from "@icons/editor/color-clear.svg";
+
 const colorChoices = [
     '#000000',
     '#ffffff',
@@ -52,11 +54,13 @@ export class EditorColorPicker extends EditorUiElement {
             });
         });
 
-        colorOptions.push(el('div', {
+        const removeButton = el('div', {
             class: 'editor-color-select-option',
             'data-color': '',
             title: 'Clear color',
-        }, ['x']));
+        }, []);
+        removeButton.innerHTML = removeIcon;
+        colorOptions.push(removeButton);
 
         const colorRows = [];
         for (let i = 0; i < colorOptions.length; i+=5) {
index a7905a6dde2ab0899d54c30110c4cae88ca40542..cba141f6c77a0777eb2dc10b1517b63206930a49 100644 (file)
@@ -2,6 +2,7 @@ import {handleDropdown} from "../helpers/dropdowns";
 import {EditorContainerUiElement, EditorUiElement} from "../core";
 import {EditorBasicButtonDefinition, EditorButton} from "../buttons";
 import {el} from "../../../utils/dom";
+import {EditorMenuButton} from "./menu-button";
 
 export type EditorDropdownButtonOptions = {
     showOnHover?: boolean;
@@ -29,7 +30,8 @@ export class EditorDropdownButton extends EditorContainerUiElement {
         if (options.button instanceof EditorButton) {
             this.button = options.button;
         } else {
-            this.button = new EditorButton({
+            const type = options.button.format === 'long' ? EditorMenuButton : EditorButton;
+            this.button = new type({
                 ...options.button,
                 action() {
                     return false;
diff --git a/resources/js/wysiwyg/ui/framework/blocks/menu-button.ts b/resources/js/wysiwyg/ui/framework/blocks/menu-button.ts
new file mode 100644 (file)
index 0000000..6f6c8cf
--- /dev/null
@@ -0,0 +1,15 @@
+import {EditorButton} from "../buttons";
+import {el} from "../../../utils/dom";
+import arrowIcon from "@icons/chevron-right.svg"
+
+export class EditorMenuButton extends EditorButton {
+    protected buildDOM(): HTMLButtonElement {
+        const dom = super.buildDOM();
+
+        const icon = el('div', {class: 'editor-menu-button-icon'});
+        icon.innerHTML = arrowIcon;
+        dom.append(icon);
+
+        return dom;
+    }
+}
\ No newline at end of file
diff --git a/resources/js/wysiwyg/ui/framework/blocks/separator.ts b/resources/js/wysiwyg/ui/framework/blocks/separator.ts
new file mode 100644 (file)
index 0000000..c0ef353
--- /dev/null
@@ -0,0 +1,10 @@
+import {EditorUiElement} from "../core";
+import {el} from "../../../utils/dom";
+
+export class EditorSeparator extends EditorUiElement {
+    buildDOM(): HTMLElement {
+        return el('div', {
+            class: 'editor-separator',
+        });
+    }
+}
index 87ecae03e2be775eccbed96bf4b86e8bb3900ede..e7d486cd5303ef2ce8a233ae5f50e35206bf377b 100644 (file)
@@ -65,6 +65,7 @@ import {
 } from "./defaults/buttons/objects";
 import {el} from "../utils/dom";
 import {EditorButtonWithMenu} from "./framework/blocks/button-with-menu";
+import {EditorSeparator} from "./framework/blocks/separator";
 
 export function getMainEditorFullToolbar(): EditorContainerUiElement {
     return new EditorSimpleClassContainer('editor-toolbar-main', [
@@ -83,7 +84,7 @@ export function getMainEditorFullToolbar(): EditorContainerUiElement {
             new FormatPreviewButton(el('h5'), h5),
             new FormatPreviewButton(el('blockquote'), blockquote),
             new FormatPreviewButton(el('p'), paragraph),
-            new EditorDropdownButton({button: {label: 'Callouts'}, showOnHover: true, direction: 'vertical'}, [
+            new EditorDropdownButton({button: {label: 'Callouts', format: 'long'}, showOnHover: true, direction: 'vertical'}, [
                 new FormatPreviewButton(el('p', {class: 'callout info'}), infoCallout),
                 new FormatPreviewButton(el('p', {class: 'callout success'}), successCallout),
                 new FormatPreviewButton(el('p', {class: 'callout warning'}), warningCallout),
@@ -125,37 +126,41 @@ export function getMainEditorFullToolbar(): EditorContainerUiElement {
         ]),
 
         // Insert types
-        new EditorOverflowContainer(8, [
+        new EditorOverflowContainer(4, [
             new EditorButton(link),
 
             new EditorDropdownButton({button: table, direction: 'vertical'}, [
-                new EditorDropdownButton({button: {...table, format: 'long'}, showOnHover: true}, [
+                new EditorDropdownButton({button: {label: 'Insert', format: 'long'}, showOnHover: true}, [
                     new EditorTableCreator(),
                 ]),
-                new EditorDropdownButton({button: {label: 'Cell'}, direction: 'vertical', showOnHover: true}, [
+                new EditorSeparator(),
+                new EditorDropdownButton({button: {label: 'Cell', format: 'long'}, direction: 'vertical', showOnHover: true}, [
                     new EditorButton(cellProperties),
                     new EditorButton(mergeCells),
                     new EditorButton(splitCell),
                 ]),
-                new EditorDropdownButton({button: {label: 'Row'}, direction: 'vertical', showOnHover: true}, [
+                new EditorDropdownButton({button: {label: 'Row', format: 'long'}, direction: 'vertical', showOnHover: true}, [
                     new EditorButton({...insertRowAbove, format: 'long'}),
                     new EditorButton({...insertRowBelow, format: 'long'}),
                     new EditorButton({...deleteRow, format: 'long'}),
                     new EditorButton(rowProperties),
+                    new EditorSeparator(),
                     new EditorButton(cutRow),
                     new EditorButton(copyRow),
                     new EditorButton(pasteRowBefore),
                     new EditorButton(pasteRowAfter),
                 ]),
-                new EditorDropdownButton({button: {label: 'Column'}, direction: 'vertical', showOnHover: true}, [
+                new EditorDropdownButton({button: {label: 'Column', format: 'long'}, direction: 'vertical', showOnHover: true}, [
                     new EditorButton({...insertColumnBefore, format: 'long'}),
                     new EditorButton({...insertColumnAfter, format: 'long'}),
                     new EditorButton({...deleteColumn, format: 'long'}),
+                    new EditorSeparator(),
                     new EditorButton(cutColumn),
                     new EditorButton(copyColumn),
                     new EditorButton(pasteColumnBefore),
                     new EditorButton(pasteColumnAfter),
                 ]),
+                new EditorSeparator(),
                 new EditorButton({...tableProperties, format: 'long'}),
                 new EditorButton(clearTableFormatting),
                 new EditorButton(resizeTableToContents),
index 04f18702e4e80cb2c595618d69fe3ecbb4287596..61a9f2de01fa579b6975d67d272dd369bf9159a0 100644 (file)
@@ -70,12 +70,18 @@ body.editor-is-fullscreen {
 .editor-button-text {
   font-weight: 400;
   color: #000;
-  font-size: 12.2px;
+  font-size: 14px;
+  flex: 1;
+  padding-inline-end: 4px;
 }
 .editor-button-format-preview {
   padding: 4px 6px;
   display: block;
 }
+.editor-button-long .editor-button-icon {
+  width: 24px;
+  height: 24px;
+}
 .editor-button-icon svg {
   width: 24px;
   height: 24px;
@@ -83,6 +89,13 @@ body.editor-is-fullscreen {
   fill: currentColor;
   display: block;
 }
+.editor-menu-button-icon {
+  width: 24px;
+  height: 24px;
+  svg {
+    fill: #888;
+  }
+}
 .editor-button-with-menu-container {
   display: flex;
   flex-direction: row;
@@ -126,6 +139,7 @@ body.editor-is-fullscreen {
   display: flex;
   flex-direction: column;
   align-items: stretch;
+  min-width: 160px;
 }
 .editor-dropdown-menu-vertical .editor-button {
   border-bottom: 0;
@@ -138,9 +152,17 @@ body.editor-is-fullscreen {
   top: 0;
 }
 
+.editor-separator {
+  display: block;
+  height: 1px;
+  background-color: #DDD;
+  opacity: .8;
+}
+
 .editor-format-menu-toggle {
   width: 130px;
   height: 32px;
+  font-size: 13px;
   overflow: hidden;
   padding-inline: 12px;
   justify-content: start;
@@ -154,6 +176,9 @@ body.editor-is-fullscreen {
   .editor-dropdown-menu {
     min-width: 220px;
   }
+  .editor-button-icon {
+    display: none;
+  }
 }
 .editor-format-menu .editor-dropdown-menu .editor-dropdown-menu-container > .editor-button {
   padding: 8px 10px;
@@ -259,6 +284,9 @@ body.editor-is-fullscreen {
   width: 28px;
   height: 28px;
   cursor: pointer;
+  display: flex;
+  align-items: center;
+  justify-content: center;
 }
 .editor-color-select-option:hover {
   border-radius: 3px;
@@ -266,6 +294,11 @@ body.editor-is-fullscreen {
   z-index: 3;
   box-shadow: 0 0 4px 1px rgba(0, 0, 0, 0.25);
 }
+.editor-color-select-option[data-color=""] svg {
+  width: 20px;
+  height: 20px;
+  fill: #888;
+}
 .editor-table-creator-row {
   display: flex;
 }
@@ -422,7 +455,9 @@ body.editor-is-fullscreen {
   background-size: 100% 100%;
 }
 
-// Editor form elements
+/**
+ * Form elements
+ */
 .editor-form-field-wrapper {
   margin-bottom: .5rem;
 }