From c2ecbf071ffddb3f23dd535495ea361954a5ee57 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Mon, 1 Jul 2024 10:44:23 +0100 Subject: [PATCH] Lexical: Added tracked container, added fullscreen action Changed how the editor is loaded in, so it now creates its own DOM, and content is passed via creation function, to be better self-contained. --- resources/icons/editor/fullscreen.svg | 1 + resources/js/components/wysiwyg-editor.js | 6 +- resources/js/wysiwyg/index.ts | 32 +++----- .../wysiwyg/ui/defaults/button-definitions.ts | 17 +++- resources/js/wysiwyg/ui/framework/buttons.ts | 13 ++- resources/js/wysiwyg/ui/framework/core.ts | 1 + resources/js/wysiwyg/ui/framework/manager.ts | 11 ++- resources/js/wysiwyg/ui/index.ts | 3 +- resources/js/wysiwyg/ui/toolbars.ts | 3 +- resources/sass/_editor.scss | 14 ++++ .../pages/parts/wysiwyg-editor.blade.php | 81 ++++++++++--------- 11 files changed, 108 insertions(+), 74 deletions(-) create mode 100644 resources/icons/editor/fullscreen.svg diff --git a/resources/icons/editor/fullscreen.svg b/resources/icons/editor/fullscreen.svg new file mode 100644 index 000000000..3cca3097a --- /dev/null +++ b/resources/icons/editor/fullscreen.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/js/components/wysiwyg-editor.js b/resources/js/components/wysiwyg-editor.js index 98732dab7..bdcdd5c51 100644 --- a/resources/js/components/wysiwyg-editor.js +++ b/resources/js/components/wysiwyg-editor.js @@ -4,10 +4,12 @@ export class WysiwygEditor extends Component { setup() { this.elem = this.$el; - this.editArea = this.$refs.editArea; + this.editContainer = this.$refs.editContainer; + this.editContent = this.$refs.editContent; window.importVersioned('wysiwyg').then(wysiwyg => { - wysiwyg.createPageEditorInstance(this.editArea); + const editorContent = this.editContent.textContent; + wysiwyg.createPageEditorInstance(this.editContainer, editorContent); }); } diff --git a/resources/js/wysiwyg/index.ts b/resources/js/wysiwyg/index.ts index 9f2f1645a..3ca01835f 100644 --- a/resources/js/wysiwyg/index.ts +++ b/resources/js/wysiwyg/index.ts @@ -6,8 +6,9 @@ import {getNodesForPageEditor} from './nodes'; import {buildEditorUI} from "./ui"; import {setEditorContentFromHtml} from "./actions"; import {registerTableResizer} from "./ui/framework/helpers/table-resizer"; +import {el} from "./helpers"; -export function createPageEditorInstance(editArea: HTMLElement) { +export function createPageEditorInstance(container: HTMLElement, htmlContent: string) { const config: CreateEditorArgs = { namespace: 'BookStackPageEditor', nodes: getNodesForPageEditor(), @@ -26,7 +27,11 @@ export function createPageEditorInstance(editArea: HTMLElement) { } }; - const startingHtml = editArea.innerHTML; + const editArea = el('div', { + contenteditable: 'true', + }); + container.append(editArea); + container.classList.add('editor-container'); const editor = createEditor(config); editor.setRootElement(editArea); @@ -37,7 +42,7 @@ export function createPageEditorInstance(editArea: HTMLElement) { registerTableResizer(editor, editArea), ); - setEditorContentFromHtml(editor, startingHtml); + setEditorContentFromHtml(editor, htmlContent); const debugView = document.getElementById('lexical-debug'); editor.registerUpdateListener(({editorState}) => { @@ -47,24 +52,5 @@ export function createPageEditorInstance(editArea: HTMLElement) { } }); - buildEditorUI(editArea, editor); - - // Example of creating, registering and using a custom command - - // const SET_BLOCK_CALLOUT_COMMAND = createCommand(); - // editor.registerCommand(SET_BLOCK_CALLOUT_COMMAND, (category: CalloutCategory = 'info') => { - // const selection = $getSelection(); - // const blockElement = $getNearestBlockElementAncestorOrThrow(selection.getNodes()[0]); - // if ($isCalloutNode(blockElement)) { - // $setBlocksType(selection, $createParagraphNode); - // } else { - // $setBlocksType(selection, () => $createCalloutNode(category)); - // } - // return true; - // }, COMMAND_PRIORITY_LOW); - // - // const button = document.getElementById('lexical-button'); - // button.addEventListener('click', event => { - // editor.dispatchCommand(SET_BLOCK_CALLOUT_COMMAND, 'info'); - // }); + buildEditorUI(container, editArea, editor); } diff --git a/resources/js/wysiwyg/ui/defaults/button-definitions.ts b/resources/js/wysiwyg/ui/defaults/button-definitions.ts index cebf25807..4a45ef75d 100644 --- a/resources/js/wysiwyg/ui/defaults/button-definitions.ts +++ b/resources/js/wysiwyg/ui/defaults/button-definitions.ts @@ -51,6 +51,7 @@ import imageIcon from "@icons/editor/image.svg" import horizontalRuleIcon from "@icons/editor/horizontal-rule.svg" import detailsIcon from "@icons/editor/details.svg" import sourceIcon from "@icons/editor/source-view.svg" +import fullscreenIcon from "@icons/editor/fullscreen.svg" import {$createHorizontalRuleNode, $isHorizontalRuleNode} from "../../nodes/horizontal-rule"; export const undo: EditorButtonDefinition = { @@ -206,7 +207,7 @@ function buildListButton(label: string, type: ListType, icon: string): EditorBut action(context: EditorUiContext) { context.editor.getEditorState().read(() => { const selection = $getSelection(); - if (this.isActive(selection)) { + if (this.isActive(selection, context)) { removeList(context.editor); } else { insertList(context.editor, type); @@ -374,4 +375,18 @@ export const source: EditorButtonDefinition = { isActive() { return false; } +}; + +export const fullscreen: EditorButtonDefinition = { + label: 'Fullscreen', + icon: fullscreenIcon, + async action(context: EditorUiContext, button: EditorButton) { + const isFullScreen = context.containerDOM.classList.contains('fullscreen'); + context.containerDOM.classList.toggle('fullscreen', !isFullScreen); + (context.containerDOM.closest('body') as HTMLElement).classList.toggle('editor-is-fullscreen', !isFullScreen); + button.setActiveState(!isFullScreen); + }, + isActive(selection, context: EditorUiContext) { + return context.containerDOM.classList.contains('fullscreen'); + } }; \ No newline at end of file diff --git a/resources/js/wysiwyg/ui/framework/buttons.ts b/resources/js/wysiwyg/ui/framework/buttons.ts index 7e8df076a..f74201ff7 100644 --- a/resources/js/wysiwyg/ui/framework/buttons.ts +++ b/resources/js/wysiwyg/ui/framework/buttons.ts @@ -8,8 +8,8 @@ export interface EditorBasicButtonDefinition { } export interface EditorButtonDefinition extends EditorBasicButtonDefinition { - action: (context: EditorUiContext) => void; - isActive: (selection: BaseSelection|null) => boolean; + action: (context: EditorUiContext, button: EditorButton) => void; + isActive: (selection: BaseSelection|null, context: EditorUiContext) => boolean; setup?: (context: EditorUiContext, button: EditorButton) => void; } @@ -68,11 +68,16 @@ export class EditorButton extends EditorUiElement { } protected onClick() { - this.definition.action(this.getContext()); + this.definition.action(this.getContext(), this); } updateActiveState(selection: BaseSelection|null) { - this.active = this.definition.isActive(selection); + const isActive = this.definition.isActive(selection, this.getContext()); + this.setActiveState(isActive); + } + + setActiveState(active: boolean) { + this.active = active; this.dom?.classList.toggle('editor-button-active', this.active); } diff --git a/resources/js/wysiwyg/ui/framework/core.ts b/resources/js/wysiwyg/ui/framework/core.ts index 2972c9821..465765caa 100644 --- a/resources/js/wysiwyg/ui/framework/core.ts +++ b/resources/js/wysiwyg/ui/framework/core.ts @@ -10,6 +10,7 @@ export type EditorUiStateUpdate = { export type EditorUiContext = { editor: LexicalEditor, editorDOM: HTMLElement, + containerDOM: HTMLElement, translate: (text: string) => string, manager: EditorUIManager, lastSelection: BaseSelection|null, diff --git a/resources/js/wysiwyg/ui/framework/manager.ts b/resources/js/wysiwyg/ui/framework/manager.ts index 4ca90a12a..3c2ad8926 100644 --- a/resources/js/wysiwyg/ui/framework/manager.ts +++ b/resources/js/wysiwyg/ui/framework/manager.ts @@ -79,7 +79,7 @@ export class EditorUIManager { this.toolbar = toolbar; toolbar.setContext(this.getContext()); - this.getContext().editorDOM.before(toolbar.getDOMElement()); + this.getContext().containerDOM.prepend(toolbar.getDOMElement()); } registerContextToolbar(key: string, definition: EditorContextToolbarDefinition) { @@ -97,6 +97,13 @@ export class EditorUIManager { // console.log('selection update', update.selection); } + triggerStateRefresh(): void { + this.triggerStateUpdate({ + editor: this.getContext().editor, + selection: this.getContext().lastSelection, + }); + } + protected updateContextToolbars(update: EditorUiStateUpdate): void { for (const toolbar of this.activeContextToolbars) { toolbar.empty(); @@ -133,7 +140,7 @@ export class EditorUIManager { toolbar.setContext(this.getContext()); this.activeContextToolbars.push(toolbar); - this.getContext().editorDOM.after(toolbar.getDOMElement()); + this.getContext().containerDOM.append(toolbar.getDOMElement()); toolbar.attachTo(target); } } diff --git a/resources/js/wysiwyg/ui/index.ts b/resources/js/wysiwyg/ui/index.ts index 1f07fe710..3501ed557 100644 --- a/resources/js/wysiwyg/ui/index.ts +++ b/resources/js/wysiwyg/ui/index.ts @@ -5,10 +5,11 @@ import {image as imageFormDefinition, link as linkFormDefinition, source as sour import {ImageDecorator} from "./decorators/image"; import {EditorUiContext} from "./framework/core"; -export function buildEditorUI(element: HTMLElement, editor: LexicalEditor) { +export function buildEditorUI(container: HTMLElement, element: HTMLElement, editor: LexicalEditor) { const manager = new EditorUIManager(); const context: EditorUiContext = { editor, + containerDOM: container, editorDOM: element, manager, translate: (text: string): string => text, diff --git a/resources/js/wysiwyg/ui/toolbars.ts b/resources/js/wysiwyg/ui/toolbars.ts index bb3b436b9..550c798c2 100644 --- a/resources/js/wysiwyg/ui/toolbars.ts +++ b/resources/js/wysiwyg/ui/toolbars.ts @@ -1,7 +1,7 @@ import {EditorButton} from "./framework/buttons"; import { blockquote, bold, bulletList, clearFormating, code, - dangerCallout, details, + dangerCallout, details, fullscreen, h2, h3, h4, h5, highlightColor, horizontalRule, image, infoCallout, italic, link, numberList, paragraph, redo, source, strikethrough, subscript, @@ -73,6 +73,7 @@ export function getMainEditorFullToolbar(): EditorContainerUiElement { // Meta elements new EditorButton(source), + new EditorButton(fullscreen), // Test new EditorButton({ diff --git a/resources/sass/_editor.scss b/resources/sass/_editor.scss index 2fcf4edb3..79cbc73e8 100644 --- a/resources/sass/_editor.scss +++ b/resources/sass/_editor.scss @@ -4,11 +4,25 @@ } // Main UI elements +.editor-container { + background-color: #FFF; + position: relative; + &.fullscreen { + z-index: 500; + } +} .editor-toolbar-main { display: flex; flex-wrap: wrap; } +body.editor-is-fullscreen { + overflow: hidden; + .edit-area { + z-index: 20; + } +} + // Buttons .editor-button { border: 1px solid #DDD; diff --git a/resources/views/pages/parts/wysiwyg-editor.blade.php b/resources/views/pages/parts/wysiwyg-editor.blade.php index 5cd60bbc6..8fc0dc55a 100644 --- a/resources/views/pages/parts/wysiwyg-editor.blade.php +++ b/resources/views/pages/parts/wysiwyg-editor.blade.php @@ -6,48 +6,49 @@ option:wysiwyg-editor:server-upload-limit-text="{{ trans('errors.server_upload_limit') }}" class=""> -
-
-

Some content here

-

Content with image in, before text. Sleepy meow After text.

-

This has a link in it

-

List below this h2 header

-
    -
  • Hello
  • -
- -
- Collapsible details/summary block -

Inner text here

-

Inner Header

-

More text with bold in it

-
- -

- Hello there, this is an info callout -

- -

Table

- - - - - - - - - - - - - - - - -
Cell ACell BCell C
Cell DCell ECell F
-
+
+ +
{{--