--- /dev/null
+<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>
\ No newline at end of file
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);
});
}
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(),
}
};
- 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);
registerTableResizer(editor, editArea),
);
- setEditorContentFromHtml(editor, startingHtml);
+ setEditorContentFromHtml(editor, htmlContent);
const debugView = document.getElementById('lexical-debug');
editor.registerUpdateListener(({editorState}) => {
}
});
- 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);
}
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 = {
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);
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
}
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;
}
}
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);
}
export type EditorUiContext = {
editor: LexicalEditor,
editorDOM: HTMLElement,
+ containerDOM: HTMLElement,
translate: (text: string) => string,
manager: EditorUIManager,
lastSelection: BaseSelection|null,
this.toolbar = toolbar;
toolbar.setContext(this.getContext());
- this.getContext().editorDOM.before(toolbar.getDOMElement());
+ this.getContext().containerDOM.prepend(toolbar.getDOMElement());
}
registerContextToolbar(key: string, definition: EditorContextToolbarDefinition) {
// 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();
toolbar.setContext(this.getContext());
this.activeContextToolbars.push(toolbar);
- this.getContext().editorDOM.after(toolbar.getDOMElement());
+ this.getContext().containerDOM.append(toolbar.getDOMElement());
toolbar.attachTo(target);
}
}
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,
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,
// Meta elements
new EditorButton(source),
+ new EditorButton(fullscreen),
// Test
new EditorButton({
}
// 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;
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"--}}