mirror of
https://github.com/BookStackApp/BookStack.git
synced 2024-10-01 01:36:00 -04:00
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.
This commit is contained in:
parent
b1c489090e
commit
c2ecbf071f
1
resources/icons/editor/fullscreen.svg
Normal file
1
resources/icons/editor/fullscreen.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path d="M120-120v-200h80v120h120v80H120Zm520 0v-80h120v-120h80v200H640ZM120-640v-200h200v80H200v120h-80Zm640 0v-120H640v-80h200v200h-80Z"/></svg>
|
After Width: | Height: | Size: 211 B |
@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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');
|
||||
}
|
||||
};
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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({
|
||||
|
@ -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;
|
||||
|
@ -6,48 +6,49 @@
|
||||
option:wysiwyg-editor:server-upload-limit-text="{{ trans('errors.server_upload_limit') }}"
|
||||
class="">
|
||||
|
||||
<div class="editor-container">
|
||||
<div refs="wysiwyg-editor@edit-area" contenteditable="true">
|
||||
<p id="Content!">Some <strong>content</strong> here</p>
|
||||
<p>Content with image in, before text. <img src="https://bookstack.local/bookstack/uploads/images/gallery/2022-03/scaled-1680-/cats-image-2400x1000-2.jpg" width="200" alt="Sleepy meow"> After text.</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>
|
||||
|
||||
<details>
|
||||
<summary>Collapsible details/summary block</summary>
|
||||
<p>Inner text here</p>
|
||||
<h4>Inner Header</h4>
|
||||
<p>More text <strong>with bold in</strong> it</p>
|
||||
</details>
|
||||
|
||||
<p class="callout info">
|
||||
Hello there, this is an info callout
|
||||
</p>
|
||||
|
||||
<h3>Table</h3>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Cell A</th>
|
||||
<th>Cell B</th>
|
||||
<th>Cell C</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Cell D</td>
|
||||
<td>Cell E</td>
|
||||
<td>Cell F</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="editor-container" refs="wysiwyg-editor@edit-container">
|
||||
</div>
|
||||
|
||||
<script type="text/html" refs="wysiwyg-editor@edit-content">
|
||||
<p id="Content!">Some <strong>content</strong> here</p>
|
||||
<p>Content with image in, before text. <img src="https://bookstack.local/bookstack/uploads/images/gallery/2022-03/scaled-1680-/cats-image-2400x1000-2.jpg" width="200" alt="Sleepy meow"> After text.</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>
|
||||
|
||||
<details>
|
||||
<summary>Collapsible details/summary block</summary>
|
||||
<p>Inner text here</p>
|
||||
<h4>Inner Header</h4>
|
||||
<p>More text <strong>with bold in</strong> it</p>
|
||||
</details>
|
||||
|
||||
<p class="callout info">
|
||||
Hello there, this is an info callout
|
||||
</p>
|
||||
|
||||
<h3>Table</h3>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Cell A</th>
|
||||
<th>Cell B</th>
|
||||
<th>Cell C</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Cell D</td>
|
||||
<td>Cell E</td>
|
||||
<td>Cell F</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</script>
|
||||
|
||||
<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>
|
||||
|
||||
{{-- <textarea id="html-editor" name="html" rows="5"--}}
|
||||
|
Loading…
Reference in New Issue
Block a user