mirror of
https://github.com/BookStackApp/BookStack.git
synced 2025-06-06 10:44:33 +08:00
Lexical: Integrated diagram manager, added menu split button
This commit is contained in:
1
resources/icons/caret-down-large.svg
Normal file
1
resources/icons/caret-down-large.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="m2 6.9159 10 10.168 10-10.168z" stroke-width="2.0168"/></svg>
|
After Width: | Height: | Size: 131 B |
@ -10,7 +10,6 @@
|
|||||||
- Alignments: Handle inline block content (image, video)
|
- Alignments: Handle inline block content (image, video)
|
||||||
- Image paste upload
|
- Image paste upload
|
||||||
- Keyboard shortcuts support
|
- Keyboard shortcuts support
|
||||||
- Drawing gallery integration
|
|
||||||
- Support media src conversions (https://github.com/tinymce/tinymce/blob/release/6.6/modules/tinymce/src/plugins/media/main/ts/core/UrlPatterns.ts)
|
- Support media src conversions (https://github.com/tinymce/tinymce/blob/release/6.6/modules/tinymce/src/plugins/media/main/ts/core/UrlPatterns.ts)
|
||||||
- Media resize support (like images)
|
- Media resize support (like images)
|
||||||
- Table caption text support
|
- Table caption text support
|
||||||
|
@ -30,7 +30,7 @@ import {
|
|||||||
$insertNewBlockNodeAtSelection,
|
$insertNewBlockNodeAtSelection,
|
||||||
$selectionContainsNodeType
|
$selectionContainsNodeType
|
||||||
} from "../../../utils/selection";
|
} from "../../../utils/selection";
|
||||||
import {$isDiagramNode, $openDrawingEditorForNode} from "../../../utils/diagrams";
|
import {$isDiagramNode, $openDrawingEditorForNode, showDiagramManagerForInsert} from "../../../utils/diagrams";
|
||||||
import {$createLinkedImageNodeFromImageData, showImageManager} from "../../../utils/images";
|
import {$createLinkedImageNodeFromImageData, showImageManager} from "../../../utils/images";
|
||||||
import {$showImageForm} from "../forms/objects";
|
import {$showImageForm} from "../forms/objects";
|
||||||
|
|
||||||
@ -184,6 +184,16 @@ export const diagram: EditorButtonDefinition = {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const diagramManager: EditorButtonDefinition = {
|
||||||
|
label: 'Drawing manager',
|
||||||
|
action(context: EditorUiContext) {
|
||||||
|
showDiagramManagerForInsert(context);
|
||||||
|
},
|
||||||
|
isActive(): boolean {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export const media: EditorButtonDefinition = {
|
export const media: EditorButtonDefinition = {
|
||||||
label: 'Insert/edit Media',
|
label: 'Insert/edit Media',
|
||||||
icon: mediaIcon,
|
icon: mediaIcon,
|
||||||
|
31
resources/js/wysiwyg/ui/framework/blocks/button-with-menu.ts
Normal file
31
resources/js/wysiwyg/ui/framework/blocks/button-with-menu.ts
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import {EditorContainerUiElement, EditorUiElement} from "../core";
|
||||||
|
import {el} from "../../../utils/dom";
|
||||||
|
import {EditorButton} from "../buttons";
|
||||||
|
import {EditorDropdownButton} from "./dropdown-button";
|
||||||
|
import caretDownIcon from "@icons/caret-down-large.svg";
|
||||||
|
|
||||||
|
export class EditorButtonWithMenu extends EditorContainerUiElement {
|
||||||
|
protected button: EditorButton;
|
||||||
|
protected dropdownButton: EditorDropdownButton;
|
||||||
|
|
||||||
|
constructor(button: EditorButton, menuItems: EditorUiElement[]) {
|
||||||
|
super([button]);
|
||||||
|
|
||||||
|
this.button = button;
|
||||||
|
this.dropdownButton = new EditorDropdownButton({
|
||||||
|
button: {label: 'Menu', icon: caretDownIcon},
|
||||||
|
showOnHover: false,
|
||||||
|
direction: 'vertical',
|
||||||
|
}, menuItems);
|
||||||
|
this.addChildren(this.dropdownButton);
|
||||||
|
}
|
||||||
|
|
||||||
|
buildDOM(): HTMLElement {
|
||||||
|
return el('div', {
|
||||||
|
class: 'editor-button-with-menu-container',
|
||||||
|
}, [
|
||||||
|
this.button.getDOMElement(),
|
||||||
|
this.dropdownButton.getDOMElement()
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
@ -56,16 +56,15 @@ import {bulletList, numberList, taskList} from "./defaults/buttons/lists";
|
|||||||
import {
|
import {
|
||||||
codeBlock,
|
codeBlock,
|
||||||
details,
|
details,
|
||||||
diagram,
|
diagram, diagramManager,
|
||||||
editCodeBlock,
|
editCodeBlock,
|
||||||
horizontalRule,
|
horizontalRule,
|
||||||
image,
|
image,
|
||||||
link, media,
|
link, media,
|
||||||
unlink
|
unlink
|
||||||
} from "./defaults/buttons/objects";
|
} from "./defaults/buttons/objects";
|
||||||
import {$isTableNode} from "@lexical/table";
|
|
||||||
import {$selectionContainsNodeType} from "../utils/selection";
|
|
||||||
import {el} from "../utils/dom";
|
import {el} from "../utils/dom";
|
||||||
|
import {EditorButtonWithMenu} from "./framework/blocks/button-with-menu";
|
||||||
|
|
||||||
export function getMainEditorFullToolbar(): EditorContainerUiElement {
|
export function getMainEditorFullToolbar(): EditorContainerUiElement {
|
||||||
return new EditorSimpleClassContainer('editor-toolbar-main', [
|
return new EditorSimpleClassContainer('editor-toolbar-main', [
|
||||||
@ -166,7 +165,10 @@ export function getMainEditorFullToolbar(): EditorContainerUiElement {
|
|||||||
new EditorButton(image),
|
new EditorButton(image),
|
||||||
new EditorButton(horizontalRule),
|
new EditorButton(horizontalRule),
|
||||||
new EditorButton(codeBlock),
|
new EditorButton(codeBlock),
|
||||||
new EditorButton(diagram),
|
new EditorButtonWithMenu(
|
||||||
|
new EditorButton(diagram),
|
||||||
|
[new EditorButton(diagramManager)],
|
||||||
|
),
|
||||||
new EditorButton(media),
|
new EditorButton(media),
|
||||||
new EditorButton(details),
|
new EditorButton(details),
|
||||||
]),
|
]),
|
||||||
|
@ -1,8 +1,11 @@
|
|||||||
import {LexicalEditor, LexicalNode} from "lexical";
|
import {$getSelection, $insertNodes, LexicalEditor, LexicalNode} from "lexical";
|
||||||
import {HttpError} from "../../services/http";
|
import {HttpError} from "../../services/http";
|
||||||
import {EditorUiContext} from "../ui/framework/core";
|
import {EditorUiContext} from "../ui/framework/core";
|
||||||
import * as DrawIO from "../../services/drawio";
|
import * as DrawIO from "../../services/drawio";
|
||||||
import {DiagramNode} from "../nodes/diagram";
|
import {$createDiagramNode, DiagramNode} from "../nodes/diagram";
|
||||||
|
import {ImageManager} from "../../components";
|
||||||
|
import {EditorImageData} from "./images";
|
||||||
|
import {$getNodeFromSelection} from "./selection";
|
||||||
|
|
||||||
export function $isDiagramNode(node: LexicalNode | null | undefined): node is DiagramNode {
|
export function $isDiagramNode(node: LexicalNode | null | undefined): node is DiagramNode {
|
||||||
return node instanceof DiagramNode;
|
return node instanceof DiagramNode;
|
||||||
@ -67,4 +70,26 @@ export function $openDrawingEditorForNode(context: EditorUiContext, node: Diagra
|
|||||||
}, async (pngData: string) => {
|
}, async (pngData: string) => {
|
||||||
return updateDrawingNodeFromData(context, node, pngData, isNew);
|
return updateDrawingNodeFromData(context, node, pngData, isNew);
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function showDiagramManager(callback: (image: EditorImageData) => any) {
|
||||||
|
const imageManager: ImageManager = window.$components.first('image-manager') as ImageManager;
|
||||||
|
imageManager.show((image: EditorImageData) => {
|
||||||
|
callback(image);
|
||||||
|
}, 'drawio');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function showDiagramManagerForInsert(context: EditorUiContext) {
|
||||||
|
const selection = context.lastSelection;
|
||||||
|
showDiagramManager((image: EditorImageData) => {
|
||||||
|
context.editor.update(() => {
|
||||||
|
const diagramNode = $createDiagramNode(image.id, image.url);
|
||||||
|
const selectedDiagram = $getNodeFromSelection(selection, $isDiagramNode);
|
||||||
|
if ($isDiagramNode(selectedDiagram)) {
|
||||||
|
selectedDiagram.replace(diagramNode);
|
||||||
|
} else {
|
||||||
|
$insertNodes([diagramNode]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
@ -2,7 +2,8 @@ import {ImageManager} from "../../components";
|
|||||||
import {$createImageNode} from "../nodes/image";
|
import {$createImageNode} from "../nodes/image";
|
||||||
import {$createLinkNode, LinkNode} from "@lexical/link";
|
import {$createLinkNode, LinkNode} from "@lexical/link";
|
||||||
|
|
||||||
type EditorImageData = {
|
export type EditorImageData = {
|
||||||
|
id: string;
|
||||||
url: string;
|
url: string;
|
||||||
thumbs?: {display: string};
|
thumbs?: {display: string};
|
||||||
name: string;
|
name: string;
|
||||||
|
@ -82,6 +82,31 @@ body.editor-is-fullscreen {
|
|||||||
fill: currentColor;
|
fill: currentColor;
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
.editor-button-with-menu-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
gap: 0;
|
||||||
|
align-items: stretch;
|
||||||
|
border-radius: 4px;
|
||||||
|
.editor-dropdown-menu-container {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
.editor-dropdown-menu-container > .editor-dropdown-menu {
|
||||||
|
top: 100%;
|
||||||
|
}
|
||||||
|
.editor-dropdown-menu-container > .editor-button {
|
||||||
|
padding-inline: 4px;
|
||||||
|
margin-inline-start: -3px;
|
||||||
|
svg {
|
||||||
|
width: 12px;
|
||||||
|
height: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&:hover {
|
||||||
|
outline: 1px solid #DDD;
|
||||||
|
outline-offset: -3px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Containers
|
// Containers
|
||||||
.editor-dropdown-menu-container {
|
.editor-dropdown-menu-container {
|
||||||
|
Reference in New Issue
Block a user