mirror of
https://github.com/BookStackApp/BookStack.git
synced 2024-10-01 01:36:00 -04: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 {
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
@ -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),
|
||||||
|
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 "forms";
|
||||||
@import "animations";
|
@import "animations";
|
||||||
@import "tinymce";
|
@import "tinymce";
|
||||||
|
@import "editor";
|
||||||
@import "codemirror";
|
@import "codemirror";
|
||||||
@import "components";
|
@import "components";
|
||||||
@import "header";
|
@import "header";
|
||||||
|
@ -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>
|
||||||
|
Loading…
Reference in New Issue
Block a user