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' {
|
declare module '*.svg' {
|
||||||
const content: string;
|
const content: string;
|
||||||
export default content;
|
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 {
|
decorate(editor: LexicalEditor, config: EditorConfig): EditorDecoratorAdapter {
|
||||||
// TODO
|
|
||||||
return {
|
return {
|
||||||
type: 'code',
|
type: 'code',
|
||||||
getNode: () => this,
|
getNode: () => this,
|
||||||
@ -165,4 +164,22 @@ export function $createCodeBlockNode(language: string = '', code: string = ''):
|
|||||||
|
|
||||||
export function $isCodeBlockNode(node: LexicalNode | null | undefined) {
|
export function $isCodeBlockNode(node: LexicalNode | null | undefined) {
|
||||||
return node instanceof CodeBlockNode;
|
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 {EditorDecorator} from "../framework/decorator";
|
||||||
import {el} from "../../helpers";
|
|
||||||
import {EditorUiContext} from "../framework/core";
|
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 {
|
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 codeNode = this.getNode() as CodeBlockNode;
|
||||||
const preEl = element.querySelector('pre');
|
const preEl = element.querySelector('pre');
|
||||||
|
if (!preEl) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (preEl) {
|
if (preEl) {
|
||||||
preEl.hidden = true;
|
preEl.hidden = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const code = codeNode.__code;
|
this.latestCode = codeNode.__code;
|
||||||
const language = codeNode.__language;
|
this.latestLanguage = codeNode.__language;
|
||||||
const lines = code.split('\n').length;
|
const lines = this.latestCode.split('\n').length;
|
||||||
const height = (lines * 19.2) + 18 + 24;
|
const height = (lines * 19.2) + 18 + 24;
|
||||||
element.style.height = `${height}px`;
|
element.style.height = `${height}px`;
|
||||||
|
|
||||||
let editor = null;
|
|
||||||
const startTime = Date.now();
|
const startTime = Date.now();
|
||||||
|
|
||||||
// Todo - Handling click/edit control
|
element.addEventListener('dblclick', event => {
|
||||||
// Todo - Add toolbar button for code
|
context.editor.getEditorState().read(() => {
|
||||||
|
$openCodeEditorForNode(context.editor, (this.getNode() as CodeBlockNode));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const renderEditor = (Code) => {
|
const renderEditor = (Code) => {
|
||||||
editor = Code.wysiwygView(element, document, code, language);
|
this.editor = Code.wysiwygView(element, document, this.latestCode, this.latestLanguage);
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
element.style.height = '';
|
element.style.height = '';
|
||||||
}, 12);
|
}, 12);
|
||||||
@ -38,5 +51,32 @@ export class CodeBlockDecorator extends EditorDecorator {
|
|||||||
const timeout = (Date.now() - startTime < 20) ? 20 : 0;
|
const timeout = (Date.now() - startTime < 20) ? 20 : 0;
|
||||||
setTimeout(() => renderEditor(Code), timeout);
|
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 tableIcon from "@icons/editor/table.svg"
|
||||||
import imageIcon from "@icons/editor/image.svg"
|
import imageIcon from "@icons/editor/image.svg"
|
||||||
import horizontalRuleIcon from "@icons/editor/horizontal-rule.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 detailsIcon from "@icons/editor/details.svg"
|
||||||
import sourceIcon from "@icons/editor/source-view.svg"
|
import sourceIcon from "@icons/editor/source-view.svg"
|
||||||
import fullscreenIcon from "@icons/editor/fullscreen.svg"
|
import fullscreenIcon from "@icons/editor/fullscreen.svg"
|
||||||
import {$createHorizontalRuleNode, $isHorizontalRuleNode} from "../../nodes/horizontal-rule";
|
import {$createHorizontalRuleNode, $isHorizontalRuleNode} from "../../nodes/horizontal-rule";
|
||||||
|
import {$createCodeBlockNode, $isCodeBlockNode, $openCodeEditorForNode, CodeBlockNode} from "../../nodes/code-block";
|
||||||
|
|
||||||
export const undo: EditorButtonDefinition = {
|
export const undo: EditorButtonDefinition = {
|
||||||
label: 'Undo',
|
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 = {
|
export const details: EditorButtonDefinition = {
|
||||||
label: 'Insert collapsible block',
|
label: 'Insert collapsible block',
|
||||||
icon: detailsIcon,
|
icon: detailsIcon,
|
||||||
|
@ -29,6 +29,7 @@ export abstract class EditorDecorator {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Render the decorator.
|
* 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
|
* If an element is returned, this will be appended to the element
|
||||||
* that is being decorated.
|
* that is being decorated.
|
||||||
*/
|
*/
|
||||||
|
@ -157,21 +157,23 @@ export class EditorUIManager {
|
|||||||
|
|
||||||
// Register our DOM decorate listener with the editor
|
// Register our DOM decorate listener with the editor
|
||||||
const domDecorateListener: DecoratorListener<EditorDecoratorAdapter> = (decorators: Record<NodeKey, EditorDecoratorAdapter>) => {
|
const domDecorateListener: DecoratorListener<EditorDecoratorAdapter> = (decorators: Record<NodeKey, EditorDecoratorAdapter>) => {
|
||||||
const keys = Object.keys(decorators);
|
editor.getEditorState().read(() => {
|
||||||
for (const key of keys) {
|
const keys = Object.keys(decorators);
|
||||||
const decoratedEl = editor.getElementByKey(key);
|
for (const key of keys) {
|
||||||
if (!decoratedEl) {
|
const decoratedEl = editor.getElementByKey(key);
|
||||||
continue;
|
if (!decoratedEl) {
|
||||||
}
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
const adapter = decorators[key];
|
const adapter = decorators[key];
|
||||||
const decorator = this.getDecorator(adapter.type, key);
|
const decorator = this.getDecorator(adapter.type, key);
|
||||||
decorator.setNode(adapter.getNode());
|
decorator.setNode(adapter.getNode());
|
||||||
const decoratorEl = decorator.render(this.getContext(), decoratedEl);
|
const decoratorEl = decorator.render(this.getContext(), decoratedEl);
|
||||||
if (decoratorEl) {
|
if (decoratorEl) {
|
||||||
decoratedEl.append(decoratorEl);
|
decoratedEl.append(decoratorEl);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
editor.registerDecoratorListener(domDecorateListener);
|
editor.registerDecoratorListener(domDecorateListener);
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import {EditorButton} from "./framework/buttons";
|
import {EditorButton} from "./framework/buttons";
|
||||||
import {
|
import {
|
||||||
blockquote, bold, bulletList, clearFormating, code,
|
blockquote, bold, bulletList, clearFormating, code, codeBlock,
|
||||||
dangerCallout, details, fullscreen,
|
dangerCallout, details, fullscreen,
|
||||||
h2, h3, h4, h5, highlightColor, horizontalRule, image,
|
h2, h3, h4, h5, highlightColor, horizontalRule, image,
|
||||||
infoCallout, italic, link, numberList, paragraph,
|
infoCallout, italic, link, numberList, paragraph,
|
||||||
@ -68,6 +68,7 @@ export function getMainEditorFullToolbar(): EditorContainerUiElement {
|
|||||||
]),
|
]),
|
||||||
new EditorButton(image),
|
new EditorButton(image),
|
||||||
new EditorButton(horizontalRule),
|
new EditorButton(horizontalRule),
|
||||||
|
new EditorButton(codeBlock),
|
||||||
new EditorButton(details),
|
new EditorButton(details),
|
||||||
]),
|
]),
|
||||||
|
|
||||||
|
@ -244,6 +244,13 @@ body.editor-is-fullscreen {
|
|||||||
cursor: row-resize;
|
cursor: row-resize;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.editor-code-block-wrap {
|
||||||
|
user-select: none;
|
||||||
|
> * {
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Editor theme styles
|
// Editor theme styles
|
||||||
.editor-theme-bold {
|
.editor-theme-bold {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
|
Loading…
Reference in New Issue
Block a user