mirror of
https://github.com/BookStackApp/BookStack.git
synced 2024-10-29 23:22:34 +01:00
Lexical: Added format previews to format buttons
This commit is contained in:
parent
dc1a40ea74
commit
57259aee00
@ -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;
|
||||
}
|
||||
}
|
@ -30,11 +30,62 @@ export class EditorContainerUiElement extends EditorUiElement {
|
||||
}
|
||||
}
|
||||
|
||||
export class EditorFormatMenu extends EditorContainerUiElement {
|
||||
buildDOM(): HTMLElement {
|
||||
return el('div', {
|
||||
class: 'editor-format-menu'
|
||||
}, this.getChildren().map(child => child.getDOMElement()));
|
||||
export class EditorSimpleClassContainer extends EditorContainerUiElement {
|
||||
protected className;
|
||||
|
||||
constructor(className: string, children: EditorUiElement[]) {
|
||||
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;
|
||||
}
|
||||
}
|
@ -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),
|
||||
|
49
resources/sass/_editor.scss
Normal file
49
resources/sass/_editor.scss
Normal 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;
|
||||
}
|
@ -15,6 +15,7 @@
|
||||
@import "forms";
|
||||
@import "animations";
|
||||
@import "tinymce";
|
||||
@import "editor";
|
||||
@import "codemirror";
|
||||
@import "components";
|
||||
@import "header";
|
||||
|
@ -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>
|
||||
|
Loading…
Reference in New Issue
Block a user