From 13d970c7ce0bf9b88c3553b561cef11cbba0e71a Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Wed, 19 Jun 2024 20:00:29 +0100 Subject: [PATCH] Lexical: Added button icon system With a bunch of default icons --- dev/build/esbuild.js | 7 +++ resources/icons/editor/align-center.svg | 1 + resources/icons/editor/align-justify.svg | 1 + resources/icons/editor/align-left.svg | 1 + resources/icons/editor/align-right.svg | 1 + resources/icons/editor/bold.svg | 1 + resources/icons/editor/code-block.svg | 1 + resources/icons/editor/code.svg | 1 + resources/icons/editor/details.svg | 1 + resources/icons/editor/format-clear.svg | 1 + resources/icons/editor/help.svg | 1 + resources/icons/editor/horizontal-rule.svg | 1 + resources/icons/editor/image.svg | 1 + resources/icons/editor/indent-decrease.svg | 1 + resources/icons/editor/indent-increase.svg | 1 + resources/icons/editor/italic.svg | 1 + resources/icons/editor/link.svg | 1 + resources/icons/editor/list-bullet.svg | 1 + resources/icons/editor/list-check.svg | 1 + resources/icons/editor/list-numbered.svg | 1 + resources/icons/editor/redo.svg | 1 + resources/icons/editor/source-view.svg | 1 + resources/icons/editor/strikethrough.svg | 1 + resources/icons/editor/subscript.svg | 1 + resources/icons/editor/superscript.svg | 1 + resources/icons/editor/underlined.svg | 1 + resources/icons/editor/undo.svg | 1 + resources/js/global.d.ts | 4 ++ resources/js/wysiwyg/helpers.ts | 6 ++- .../wysiwyg/ui/defaults/button-definitions.ts | 50 ++++++++++++++----- resources/js/wysiwyg/ui/framework/buttons.ts | 12 ++++- resources/js/wysiwyg/ui/toolbars.ts | 1 + resources/sass/_editor.scss | 5 ++ tsconfig.json | 4 +- 34 files changed, 99 insertions(+), 16 deletions(-) create mode 100644 resources/icons/editor/align-center.svg create mode 100644 resources/icons/editor/align-justify.svg create mode 100644 resources/icons/editor/align-left.svg create mode 100644 resources/icons/editor/align-right.svg create mode 100644 resources/icons/editor/bold.svg create mode 100644 resources/icons/editor/code-block.svg create mode 100644 resources/icons/editor/code.svg create mode 100644 resources/icons/editor/details.svg create mode 100644 resources/icons/editor/format-clear.svg create mode 100644 resources/icons/editor/help.svg create mode 100644 resources/icons/editor/horizontal-rule.svg create mode 100644 resources/icons/editor/image.svg create mode 100644 resources/icons/editor/indent-decrease.svg create mode 100644 resources/icons/editor/indent-increase.svg create mode 100644 resources/icons/editor/italic.svg create mode 100644 resources/icons/editor/link.svg create mode 100644 resources/icons/editor/list-bullet.svg create mode 100644 resources/icons/editor/list-check.svg create mode 100644 resources/icons/editor/list-numbered.svg create mode 100644 resources/icons/editor/redo.svg create mode 100644 resources/icons/editor/source-view.svg create mode 100644 resources/icons/editor/strikethrough.svg create mode 100644 resources/icons/editor/subscript.svg create mode 100644 resources/icons/editor/superscript.svg create mode 100644 resources/icons/editor/underlined.svg create mode 100644 resources/icons/editor/undo.svg create mode 100644 resources/js/global.d.ts diff --git a/dev/build/esbuild.js b/dev/build/esbuild.js index 7f180fc07..0680f4ac3 100644 --- a/dev/build/esbuild.js +++ b/dev/build/esbuild.js @@ -32,6 +32,13 @@ esbuild.build({ format: 'esm', minify: isProd, logLevel: 'info', + loader: { + '.svg': 'text', + }, + absWorkingDir: path.join(__dirname, '../..'), + alias: { + '@icons': './resources/icons', + }, banner: { js: '// See the "/licenses" URI for full package license details', css: '/* See the "/licenses" URI for full package license details */', diff --git a/resources/icons/editor/align-center.svg b/resources/icons/editor/align-center.svg new file mode 100644 index 000000000..495ae000c --- /dev/null +++ b/resources/icons/editor/align-center.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/editor/align-justify.svg b/resources/icons/editor/align-justify.svg new file mode 100644 index 000000000..bf8f61abb --- /dev/null +++ b/resources/icons/editor/align-justify.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/editor/align-left.svg b/resources/icons/editor/align-left.svg new file mode 100644 index 000000000..811212755 --- /dev/null +++ b/resources/icons/editor/align-left.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/editor/align-right.svg b/resources/icons/editor/align-right.svg new file mode 100644 index 000000000..839110c42 --- /dev/null +++ b/resources/icons/editor/align-right.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/editor/bold.svg b/resources/icons/editor/bold.svg new file mode 100644 index 000000000..93cc44a3f --- /dev/null +++ b/resources/icons/editor/bold.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/editor/code-block.svg b/resources/icons/editor/code-block.svg new file mode 100644 index 000000000..308db53b4 --- /dev/null +++ b/resources/icons/editor/code-block.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/editor/code.svg b/resources/icons/editor/code.svg new file mode 100644 index 000000000..d8434b761 --- /dev/null +++ b/resources/icons/editor/code.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/editor/details.svg b/resources/icons/editor/details.svg new file mode 100644 index 000000000..d86e8c423 --- /dev/null +++ b/resources/icons/editor/details.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/editor/format-clear.svg b/resources/icons/editor/format-clear.svg new file mode 100644 index 000000000..b6483fb56 --- /dev/null +++ b/resources/icons/editor/format-clear.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/editor/help.svg b/resources/icons/editor/help.svg new file mode 100644 index 000000000..8c3410b84 --- /dev/null +++ b/resources/icons/editor/help.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/editor/horizontal-rule.svg b/resources/icons/editor/horizontal-rule.svg new file mode 100644 index 000000000..c70df0d6e --- /dev/null +++ b/resources/icons/editor/horizontal-rule.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/editor/image.svg b/resources/icons/editor/image.svg new file mode 100644 index 000000000..81d04cea7 --- /dev/null +++ b/resources/icons/editor/image.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/editor/indent-decrease.svg b/resources/icons/editor/indent-decrease.svg new file mode 100644 index 000000000..af0caa862 --- /dev/null +++ b/resources/icons/editor/indent-decrease.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/editor/indent-increase.svg b/resources/icons/editor/indent-increase.svg new file mode 100644 index 000000000..aa6b4cb36 --- /dev/null +++ b/resources/icons/editor/indent-increase.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/editor/italic.svg b/resources/icons/editor/italic.svg new file mode 100644 index 000000000..a98819427 --- /dev/null +++ b/resources/icons/editor/italic.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/editor/link.svg b/resources/icons/editor/link.svg new file mode 100644 index 000000000..b29800dc3 --- /dev/null +++ b/resources/icons/editor/link.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/editor/list-bullet.svg b/resources/icons/editor/list-bullet.svg new file mode 100644 index 000000000..c073c6ff0 --- /dev/null +++ b/resources/icons/editor/list-bullet.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/editor/list-check.svg b/resources/icons/editor/list-check.svg new file mode 100644 index 000000000..f30266b27 --- /dev/null +++ b/resources/icons/editor/list-check.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/editor/list-numbered.svg b/resources/icons/editor/list-numbered.svg new file mode 100644 index 000000000..92cdbf0ae --- /dev/null +++ b/resources/icons/editor/list-numbered.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/editor/redo.svg b/resources/icons/editor/redo.svg new file mode 100644 index 000000000..d542296c5 --- /dev/null +++ b/resources/icons/editor/redo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/editor/source-view.svg b/resources/icons/editor/source-view.svg new file mode 100644 index 000000000..5314c39da --- /dev/null +++ b/resources/icons/editor/source-view.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/editor/strikethrough.svg b/resources/icons/editor/strikethrough.svg new file mode 100644 index 000000000..92d14aa76 --- /dev/null +++ b/resources/icons/editor/strikethrough.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/editor/subscript.svg b/resources/icons/editor/subscript.svg new file mode 100644 index 000000000..e877b3359 --- /dev/null +++ b/resources/icons/editor/subscript.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/editor/superscript.svg b/resources/icons/editor/superscript.svg new file mode 100644 index 000000000..897ceddc2 --- /dev/null +++ b/resources/icons/editor/superscript.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/editor/underlined.svg b/resources/icons/editor/underlined.svg new file mode 100644 index 000000000..5d17ef6ef --- /dev/null +++ b/resources/icons/editor/underlined.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/editor/undo.svg b/resources/icons/editor/undo.svg new file mode 100644 index 000000000..4b9f22675 --- /dev/null +++ b/resources/icons/editor/undo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/js/global.d.ts b/resources/js/global.d.ts new file mode 100644 index 000000000..c5aba8ee2 --- /dev/null +++ b/resources/js/global.d.ts @@ -0,0 +1,4 @@ +declare module '*.svg' { + const content: string; + export default content; +} \ No newline at end of file diff --git a/resources/js/wysiwyg/helpers.ts b/resources/js/wysiwyg/helpers.ts index 40379cc27..d7cd23a35 100644 --- a/resources/js/wysiwyg/helpers.ts +++ b/resources/js/wysiwyg/helpers.ts @@ -9,11 +9,13 @@ import {LexicalElementNodeCreator, LexicalNodeMatcher} from "./nodes"; import {$getNearestBlockElementAncestorOrThrow} from "@lexical/utils"; import {$setBlocksType} from "@lexical/selection"; -export function el(tag: string, attrs: Record = {}, children: (string|HTMLElement)[] = []): HTMLElement { +export function el(tag: string, attrs: Record = {}, children: (string|HTMLElement)[] = []): HTMLElement { const el = document.createElement(tag); const attrKeys = Object.keys(attrs); for (const attr of attrKeys) { - el.setAttribute(attr, attrs[attr]); + if (attrs[attr] !== null) { + el.setAttribute(attr, attrs[attr] as string); + } } for (const child of children) { diff --git a/resources/js/wysiwyg/ui/defaults/button-definitions.ts b/resources/js/wysiwyg/ui/defaults/button-definitions.ts index 57460ef60..7fa1fb5f8 100644 --- a/resources/js/wysiwyg/ui/defaults/button-definitions.ts +++ b/resources/js/wysiwyg/ui/defaults/button-definitions.ts @@ -29,9 +29,27 @@ import {$isImageNode, ImageNode} from "../../nodes/image"; import {$createDetailsNode, $isDetailsNode} from "../../nodes/details"; import {getEditorContentAsHtml} from "../../actions"; import {$isListNode, insertList, ListNode, ListType, removeList} from "@lexical/list"; +import undoIcon from "@icons/editor/undo.svg" +import redoIcon from "@icons/editor/redo.svg" +import boldIcon from "@icons/editor/bold.svg" +import italicIcon from "@icons/editor/italic.svg" +import underlinedIcon from "@icons/editor/underlined.svg" +import strikethroughIcon from "@icons/editor/strikethrough.svg" +import superscriptIcon from "@icons/editor/superscript.svg" +import subscriptIcon from "@icons/editor/subscript.svg" +import codeIcon from "@icons/editor/code.svg" +import formatClearIcon from "@icons/editor/format-clear.svg" +import listBulletIcon from "@icons/editor/list-bullet.svg" +import listNumberedIcon from "@icons/editor/list-numbered.svg" +import listCheckIcon from "@icons/editor/list-check.svg" +import linkIcon from "@icons/editor/link.svg" +import imageIcon from "@icons/editor/image.svg" +import detailsIcon from "@icons/editor/details.svg" +import sourceIcon from "@icons/editor/source-view.svg" export const undo: EditorButtonDefinition = { label: 'Undo', + icon: undoIcon, action(context: EditorUiContext) { context.editor.dispatchCommand(UNDO_COMMAND, undefined); }, @@ -42,6 +60,7 @@ export const undo: EditorButtonDefinition = { export const redo: EditorButtonDefinition = { label: 'Redo', + icon: redoIcon, action(context: EditorUiContext) { context.editor.dispatchCommand(REDO_COMMAND, undefined); }, @@ -116,9 +135,10 @@ export const paragraph: EditorButtonDefinition = { } } -function buildFormatButton(label: string, format: TextFormatType): EditorButtonDefinition { +function buildFormatButton(label: string, format: TextFormatType, icon: string): EditorButtonDefinition { return { label: label, + icon, action(context: EditorUiContext) { context.editor.dispatchCommand(FORMAT_TEXT_COMMAND, format); }, @@ -128,18 +148,19 @@ function buildFormatButton(label: string, format: TextFormatType): EditorButtonD }; } -export const bold: EditorButtonDefinition = buildFormatButton('Bold', 'bold'); -export const italic: EditorButtonDefinition = buildFormatButton('Italic', 'italic'); -export const underline: EditorButtonDefinition = buildFormatButton('Underline', 'underline'); +export const bold: EditorButtonDefinition = buildFormatButton('Bold', 'bold', boldIcon); +export const italic: EditorButtonDefinition = buildFormatButton('Italic', 'italic', italicIcon); +export const underline: EditorButtonDefinition = buildFormatButton('Underline', 'underline', underlinedIcon); export const textColor: EditorBasicButtonDefinition = {label: 'Text color'}; export const highlightColor: EditorBasicButtonDefinition = {label: 'Highlight color'}; -export const strikethrough: EditorButtonDefinition = buildFormatButton('Strikethrough', 'strikethrough'); -export const superscript: EditorButtonDefinition = buildFormatButton('Superscript', 'superscript'); -export const subscript: EditorButtonDefinition = buildFormatButton('Subscript', 'subscript'); -export const code: EditorButtonDefinition = buildFormatButton('Inline Code', 'code'); +export const strikethrough: EditorButtonDefinition = buildFormatButton('Strikethrough', 'strikethrough', strikethroughIcon); +export const superscript: EditorButtonDefinition = buildFormatButton('Superscript', 'superscript', superscriptIcon); +export const subscript: EditorButtonDefinition = buildFormatButton('Subscript', 'subscript', subscriptIcon); +export const code: EditorButtonDefinition = buildFormatButton('Inline Code', 'code', codeIcon); export const clearFormating: EditorButtonDefinition = { label: 'Clear formatting', + icon: formatClearIcon, action(context: EditorUiContext) { context.editor.update(() => { const selection = $getSelection(); @@ -155,9 +176,10 @@ export const clearFormating: EditorButtonDefinition = { } }; -function buildListButton(label: string, type: ListType): EditorButtonDefinition { +function buildListButton(label: string, type: ListType, icon: string): EditorButtonDefinition { return { label, + icon, action(context: EditorUiContext) { context.editor.getEditorState().read(() => { const selection = $getSelection(); @@ -176,13 +198,14 @@ function buildListButton(label: string, type: ListType): EditorButtonDefinition }; } -export const bulletList: EditorButtonDefinition = buildListButton('Bullet list', 'bullet'); -export const numberList: EditorButtonDefinition = buildListButton('Numbered list', 'number'); -export const taskList: EditorButtonDefinition = buildListButton('Task list', 'check'); +export const bulletList: EditorButtonDefinition = buildListButton('Bullet list', 'bullet', listBulletIcon); +export const numberList: EditorButtonDefinition = buildListButton('Numbered list', 'number', listNumberedIcon); +export const taskList: EditorButtonDefinition = buildListButton('Task list', 'check', listCheckIcon); export const link: EditorButtonDefinition = { label: 'Insert/edit link', + icon: linkIcon, action(context: EditorUiContext) { const linkModal = context.manager.createModal('link'); context.editor.getEditorState().read(() => { @@ -215,6 +238,7 @@ export const link: EditorButtonDefinition = { export const image: EditorButtonDefinition = { label: 'Insert/Edit Image', + icon: imageIcon, action(context: EditorUiContext) { const imageModal = context.manager.createModal('image'); const selection = context.lastSelection; @@ -247,6 +271,7 @@ export const image: EditorButtonDefinition = { export const details: EditorButtonDefinition = { label: 'Insert collapsible block', + icon: detailsIcon, action(context: EditorUiContext) { context.editor.update(() => { const selection = $getSelection(); @@ -274,6 +299,7 @@ export const details: EditorButtonDefinition = { export const source: EditorButtonDefinition = { label: 'Source code', + icon: sourceIcon, async action(context: EditorUiContext) { const modal = context.manager.createModal('source'); const source = await getEditorContentAsHtml(context.editor); diff --git a/resources/js/wysiwyg/ui/framework/buttons.ts b/resources/js/wysiwyg/ui/framework/buttons.ts index c3ba533b3..332b35099 100644 --- a/resources/js/wysiwyg/ui/framework/buttons.ts +++ b/resources/js/wysiwyg/ui/framework/buttons.ts @@ -4,6 +4,7 @@ import {el} from "../../helpers"; export interface EditorBasicButtonDefinition { label: string; + icon?: string|undefined; } export interface EditorButtonDefinition extends EditorBasicButtonDefinition { @@ -21,10 +22,19 @@ export class EditorButton extends EditorUiElement { } protected buildDOM(): HTMLButtonElement { + + const label = this.getLabel(); + let child: string|HTMLElement = label; + if (this.definition.icon) { + child = el('span', {class: 'editor-button-icon'}); + child.innerHTML = this.definition.icon; + } + const button = el('button', { type: 'button', class: 'editor-button', - }, [this.getLabel()]) as HTMLButtonElement; + title: this.definition.icon ? label : null, + }, [child]) as HTMLButtonElement; button.addEventListener('click', this.onClick.bind(this)); diff --git a/resources/js/wysiwyg/ui/toolbars.ts b/resources/js/wysiwyg/ui/toolbars.ts index fe19b94ed..559e9a87c 100644 --- a/resources/js/wysiwyg/ui/toolbars.ts +++ b/resources/js/wysiwyg/ui/toolbars.ts @@ -16,6 +16,7 @@ import {FormatPreviewButton} from "./framework/blocks/format-preview-button"; import {EditorDropdownButton} from "./framework/blocks/dropdown-button"; import {EditorColorPicker} from "./framework/blocks/color-picker"; +console.log(undo); export function getMainEditorFullToolbar(): EditorContainerUiElement { return new EditorSimpleClassContainer('editor-toolbar-main', [ diff --git a/resources/sass/_editor.scss b/resources/sass/_editor.scss index 13d8e96f9..f8c895afd 100644 --- a/resources/sass/_editor.scss +++ b/resources/sass/_editor.scss @@ -29,6 +29,11 @@ padding: 4px 6px; display: block; } +.editor-button-icon svg { + width: 24px; + height: 24px; + fill: #000; +} // Containers .editor-dropdown-menu-container { diff --git a/tsconfig.json b/tsconfig.json index e075f973c..40d930149 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -29,7 +29,9 @@ // "rootDir": "./", /* Specify the root folder within your source files. */ // "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */ // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ - // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ + "paths": { /* Specify a set of entries that re-map imports to additional lookup locations. */ + "@icons/*": ["./resources/icons/*"] + }, // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ // "types": [], /* Specify type package names to be included without being referenced in a source file. */