Lexical: Added format previews to format buttons

This commit is contained in:
Dan Brown 2024-05-30 12:25:25 +01:00
parent dc1a40ea74
commit 57259aee00
No known key found for this signature in database
GPG Key ID: 46D9F943C24A2EF9
6 changed files with 185 additions and 39 deletions

View File

@ -19,22 +19,70 @@ export class EditorButton extends EditorUiElement {
protected buildDOM(): HTMLButtonElement { protected buildDOM(): HTMLButtonElement {
const button = el('button', { const button = el('button', {
type: 'button', type: 'button',
class: 'editor-toolbar-button', class: 'editor-button',
}, [this.definition.label]) as HTMLButtonElement; }, [this.definition.label]) as HTMLButtonElement;
button.addEventListener('click', event => { button.addEventListener('click', this.onClick.bind(this));
this.definition.action(this.getContext().editor);
});
return button; return button;
} }
protected onClick() {
this.definition.action(this.getContext().editor);
}
updateActiveState(selection: BaseSelection|null) { updateActiveState(selection: BaseSelection|null) {
const isActive = this.definition.isActive(selection); 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 { updateState(state: EditorUiStateUpdate): void {
this.updateActiveState(state.selection); 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;
}
} }

View File

@ -30,11 +30,62 @@ export class EditorContainerUiElement extends EditorUiElement {
} }
} }
export class EditorFormatMenu extends EditorContainerUiElement { export class EditorSimpleClassContainer extends EditorContainerUiElement {
buildDOM(): HTMLElement { protected className;
return el('div', {
class: 'editor-format-menu' constructor(className: string, children: EditorUiElement[]) {
}, this.getChildren().map(child => child.getDOMElement())); super(children);
this.className = className;
} }
protected buildDOM(): HTMLElement {
return el('div', {
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;
}
} }

View File

@ -1,4 +1,4 @@
import {EditorButton} from "./framework/buttons"; import {EditorButton, FormatPreviewButton} from "./framework/buttons";
import { import {
blockquote, bold, code, blockquote, bold, code,
dangerCallout, dangerCallout,
@ -9,25 +9,26 @@ import {
undo, undo,
warningCallout warningCallout
} from "./defaults/button-definitions"; } 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 { export function getMainEditorFullToolbar(): EditorContainerUiElement {
return new EditorContainerUiElement([ return new EditorSimpleClassContainer('editor-toolbar-main', [
new EditorButton(undo), new EditorButton(undo),
new EditorButton(redo), new EditorButton(redo),
new EditorFormatMenu([ new EditorFormatMenu([
new EditorButton(h2), new FormatPreviewButton(el('h2'), h2),
new EditorButton(h3), new FormatPreviewButton(el('h3'), h3),
new EditorButton(h4), new FormatPreviewButton(el('h4'), h4),
new EditorButton(h5), new FormatPreviewButton(el('h5'), h5),
new EditorButton(blockquote), new FormatPreviewButton(el('blockquote'), blockquote),
new EditorButton(paragraph), new FormatPreviewButton(el('p'), paragraph),
new EditorButton(infoCallout), new FormatPreviewButton(el('p', {class: 'callout info'}), infoCallout),
new EditorButton(successCallout), new FormatPreviewButton(el('p', {class: 'callout success'}), successCallout),
new EditorButton(warningCallout), new FormatPreviewButton(el('p', {class: 'callout warning'}), warningCallout),
new EditorButton(dangerCallout), new FormatPreviewButton(el('p', {class: 'callout danger'}), dangerCallout),
]), ]),
new EditorButton(bold), new EditorButton(bold),

View File

@ -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;
}

View File

@ -15,6 +15,7 @@
@import "forms"; @import "forms";
@import "animations"; @import "animations";
@import "tinymce"; @import "tinymce";
@import "editor";
@import "codemirror"; @import "codemirror";
@import "components"; @import "components";
@import "header"; @import "header";

View File

@ -6,23 +6,19 @@
option:wysiwyg-editor:server-upload-limit-text="{{ trans('errors.server_upload_limit') }}" option:wysiwyg-editor:server-upload-limit-text="{{ trans('errors.server_upload_limit') }}"
class=""> class="">
<style> <div class="editor-container">
.editor-toolbar-button-active { <div refs="wysiwyg-editor@edit-area" contenteditable="true">
background-color: tomato; <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>
</style> <h2>List below this h2 header</h2>
<ul>
<li>Hello</li>
</ul>
<div refs="wysiwyg-editor@edit-area" contenteditable="true"> <p class="callout danger">
<p id="Content!">Some <strong>content</strong> here</p> Hello there, this is an info callout
<p>This has a <a href="https://example.com" target="_blank" title="Link to example">link</a> in it</p> </p>
<h2>List below this h2 header</h2> </div>
<ul>
<li>Hello</li>
</ul>
<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> <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>