mirror of
https://github.com/BookStackApp/BookStack.git
synced 2024-10-01 01:36:00 -04:00
Lexical: Linked code block to editor, added button
This commit is contained in:
parent
97f570a4ee
commit
d0a5a5ef37
8
resources/js/global.d.ts
vendored
8
resources/js/global.d.ts
vendored
@ -1,4 +1,12 @@
|
||||
declare module '*.svg' {
|
||||
const content: string;
|
||||
export default content;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
$components: {
|
||||
first: (string) => Object,
|
||||
}
|
||||
}
|
||||
}
|
@ -73,7 +73,6 @@ export class CodeBlockNode extends DecoratorNode<EditorDecoratorAdapter> {
|
||||
}
|
||||
|
||||
decorate(editor: LexicalEditor, config: EditorConfig): EditorDecoratorAdapter {
|
||||
// TODO
|
||||
return {
|
||||
type: 'code',
|
||||
getNode: () => this,
|
||||
@ -165,4 +164,22 @@ export function $createCodeBlockNode(language: string = '', code: string = ''):
|
||||
|
||||
export function $isCodeBlockNode(node: LexicalNode | null | undefined) {
|
||||
return node instanceof CodeBlockNode;
|
||||
}
|
||||
|
||||
export function $openCodeEditorForNode(editor: LexicalEditor, node: CodeBlockNode): void {
|
||||
const code = node.getCode();
|
||||
const language = node.getLanguage();
|
||||
|
||||
// @ts-ignore
|
||||
const codeEditor = window.$components.first('code-editor');
|
||||
// TODO - Handle direction
|
||||
codeEditor.open(code, language, 'ltr', (newCode: string, newLang: string) => {
|
||||
editor.update(() => {
|
||||
node.setCode(newCode);
|
||||
node.setLanguage(newLang);
|
||||
});
|
||||
// TODO - Re-focus
|
||||
}, () => {
|
||||
// TODO - Re-focus
|
||||
});
|
||||
}
|
@ -1,33 +1,46 @@
|
||||
import {EditorDecorator} from "../framework/decorator";
|
||||
import {el} from "../../helpers";
|
||||
import {EditorUiContext} from "../framework/core";
|
||||
import {CodeBlockNode} from "../../nodes/code-block";
|
||||
import {$openCodeEditorForNode, CodeBlockNode} from "../../nodes/code-block";
|
||||
import {ImageNode} from "../../nodes/image";
|
||||
|
||||
|
||||
export class CodeBlockDecorator extends EditorDecorator {
|
||||
|
||||
render(context: EditorUiContext, element: HTMLElement): void {
|
||||
protected completedSetup: boolean = false;
|
||||
protected latestCode: string = '';
|
||||
protected latestLanguage: string = '';
|
||||
|
||||
// @ts-ignore
|
||||
protected editor: any = null;
|
||||
|
||||
setup(context: EditorUiContext, element: HTMLElement) {
|
||||
const codeNode = this.getNode() as CodeBlockNode;
|
||||
const preEl = element.querySelector('pre');
|
||||
if (!preEl) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (preEl) {
|
||||
preEl.hidden = true;
|
||||
}
|
||||
|
||||
const code = codeNode.__code;
|
||||
const language = codeNode.__language;
|
||||
const lines = code.split('\n').length;
|
||||
this.latestCode = codeNode.__code;
|
||||
this.latestLanguage = codeNode.__language;
|
||||
const lines = this.latestCode.split('\n').length;
|
||||
const height = (lines * 19.2) + 18 + 24;
|
||||
element.style.height = `${height}px`;
|
||||
|
||||
let editor = null;
|
||||
const startTime = Date.now();
|
||||
|
||||
// Todo - Handling click/edit control
|
||||
// Todo - Add toolbar button for code
|
||||
element.addEventListener('dblclick', event => {
|
||||
context.editor.getEditorState().read(() => {
|
||||
$openCodeEditorForNode(context.editor, (this.getNode() as CodeBlockNode));
|
||||
});
|
||||
});
|
||||
|
||||
// @ts-ignore
|
||||
const renderEditor = (Code) => {
|
||||
editor = Code.wysiwygView(element, document, code, language);
|
||||
this.editor = Code.wysiwygView(element, document, this.latestCode, this.latestLanguage);
|
||||
setTimeout(() => {
|
||||
element.style.height = '';
|
||||
}, 12);
|
||||
@ -38,5 +51,32 @@ export class CodeBlockDecorator extends EditorDecorator {
|
||||
const timeout = (Date.now() - startTime < 20) ? 20 : 0;
|
||||
setTimeout(() => renderEditor(Code), timeout);
|
||||
});
|
||||
|
||||
this.completedSetup = true;
|
||||
}
|
||||
|
||||
update() {
|
||||
const codeNode = this.getNode() as CodeBlockNode;
|
||||
const code = codeNode.getCode();
|
||||
const language = codeNode.getLanguage();
|
||||
|
||||
if (this.latestCode === code && this.latestLanguage === language) {
|
||||
return;
|
||||
}
|
||||
this.latestLanguage = language;
|
||||
this.latestCode = code;
|
||||
|
||||
if (this.editor) {
|
||||
this.editor.setContent(code);
|
||||
this.editor.setMode(language, code);
|
||||
}
|
||||
}
|
||||
|
||||
render(context: EditorUiContext, element: HTMLElement): void {
|
||||
if (this.completedSetup) {
|
||||
this.update();
|
||||
} else {
|
||||
this.setup(context, element);
|
||||
}
|
||||
}
|
||||
}
|
@ -49,10 +49,12 @@ import unlinkIcon from "@icons/editor/unlink.svg"
|
||||
import tableIcon from "@icons/editor/table.svg"
|
||||
import imageIcon from "@icons/editor/image.svg"
|
||||
import horizontalRuleIcon from "@icons/editor/horizontal-rule.svg"
|
||||
import codeBlockIcon from "@icons/editor/code-block.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";
|
||||
import {$createCodeBlockNode, $isCodeBlockNode, $openCodeEditorForNode, CodeBlockNode} from "../../nodes/code-block";
|
||||
|
||||
export const undo: EditorButtonDefinition = {
|
||||
label: 'Undo',
|
||||
@ -336,6 +338,31 @@ export const horizontalRule: EditorButtonDefinition = {
|
||||
}
|
||||
};
|
||||
|
||||
export const codeBlock: EditorButtonDefinition = {
|
||||
label: 'Insert code block',
|
||||
icon: codeBlockIcon,
|
||||
action(context: EditorUiContext) {
|
||||
context.editor.getEditorState().read(() => {
|
||||
const selection = $getSelection();
|
||||
const codeBlock = getNodeFromSelection(selection, $isCodeBlockNode) as (CodeBlockNode|null);
|
||||
if (codeBlock === null) {
|
||||
context.editor.update(() => {
|
||||
const codeBlock = $createCodeBlockNode();
|
||||
codeBlock.setCode(selection?.getTextContent() || '');
|
||||
insertNewBlockNodeAtSelection(codeBlock, true);
|
||||
$openCodeEditorForNode(context.editor, codeBlock);
|
||||
codeBlock.selectStart();
|
||||
});
|
||||
} else {
|
||||
$openCodeEditorForNode(context.editor, codeBlock);
|
||||
}
|
||||
});
|
||||
},
|
||||
isActive(selection: BaseSelection|null): boolean {
|
||||
return selectionContainsNodeType(selection, $isCodeBlockNode);
|
||||
}
|
||||
};
|
||||
|
||||
export const details: EditorButtonDefinition = {
|
||||
label: 'Insert collapsible block',
|
||||
icon: detailsIcon,
|
||||
|
@ -29,6 +29,7 @@ export abstract class EditorDecorator {
|
||||
|
||||
/**
|
||||
* Render the decorator.
|
||||
* Can run on both creation and update for a node decorator.
|
||||
* If an element is returned, this will be appended to the element
|
||||
* that is being decorated.
|
||||
*/
|
||||
|
@ -157,21 +157,23 @@ export class EditorUIManager {
|
||||
|
||||
// Register our DOM decorate listener with the editor
|
||||
const domDecorateListener: DecoratorListener<EditorDecoratorAdapter> = (decorators: Record<NodeKey, EditorDecoratorAdapter>) => {
|
||||
const keys = Object.keys(decorators);
|
||||
for (const key of keys) {
|
||||
const decoratedEl = editor.getElementByKey(key);
|
||||
if (!decoratedEl) {
|
||||
continue;
|
||||
}
|
||||
editor.getEditorState().read(() => {
|
||||
const keys = Object.keys(decorators);
|
||||
for (const key of keys) {
|
||||
const decoratedEl = editor.getElementByKey(key);
|
||||
if (!decoratedEl) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const adapter = decorators[key];
|
||||
const decorator = this.getDecorator(adapter.type, key);
|
||||
decorator.setNode(adapter.getNode());
|
||||
const decoratorEl = decorator.render(this.getContext(), decoratedEl);
|
||||
if (decoratorEl) {
|
||||
decoratedEl.append(decoratorEl);
|
||||
const adapter = decorators[key];
|
||||
const decorator = this.getDecorator(adapter.type, key);
|
||||
decorator.setNode(adapter.getNode());
|
||||
const decoratorEl = decorator.render(this.getContext(), decoratedEl);
|
||||
if (decoratorEl) {
|
||||
decoratedEl.append(decoratorEl);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
editor.registerDecoratorListener(domDecorateListener);
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import {EditorButton} from "./framework/buttons";
|
||||
import {
|
||||
blockquote, bold, bulletList, clearFormating, code,
|
||||
blockquote, bold, bulletList, clearFormating, code, codeBlock,
|
||||
dangerCallout, details, fullscreen,
|
||||
h2, h3, h4, h5, highlightColor, horizontalRule, image,
|
||||
infoCallout, italic, link, numberList, paragraph,
|
||||
@ -68,6 +68,7 @@ export function getMainEditorFullToolbar(): EditorContainerUiElement {
|
||||
]),
|
||||
new EditorButton(image),
|
||||
new EditorButton(horizontalRule),
|
||||
new EditorButton(codeBlock),
|
||||
new EditorButton(details),
|
||||
]),
|
||||
|
||||
|
@ -244,6 +244,13 @@ body.editor-is-fullscreen {
|
||||
cursor: row-resize;
|
||||
}
|
||||
|
||||
.editor-code-block-wrap {
|
||||
user-select: none;
|
||||
> * {
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
// Editor theme styles
|
||||
.editor-theme-bold {
|
||||
font-weight: bold;
|
||||
|
Loading…
Reference in New Issue
Block a user