mirror of
https://github.com/BookStackApp/BookStack.git
synced 2024-10-01 01:36:00 -04:00
Added base node/button for details/summary
This commit is contained in:
parent
0722960260
commit
5c343638b6
120
resources/js/wysiwyg/nodes/details.ts
Normal file
120
resources/js/wysiwyg/nodes/details.ts
Normal file
@ -0,0 +1,120 @@
|
||||
import {
|
||||
DOMConversion,
|
||||
DOMConversionMap, DOMConversionOutput,
|
||||
ElementNode,
|
||||
LexicalEditor,
|
||||
LexicalNode,
|
||||
SerializedElementNode,
|
||||
} from 'lexical';
|
||||
import type {EditorConfig} from "lexical/LexicalEditor";
|
||||
import {el} from "../helpers";
|
||||
|
||||
export class DetailsNode extends ElementNode {
|
||||
|
||||
static getType() {
|
||||
return 'details';
|
||||
}
|
||||
|
||||
static clone(node: DetailsNode) {
|
||||
return new DetailsNode(node.__key);
|
||||
}
|
||||
|
||||
createDOM(_config: EditorConfig, _editor: LexicalEditor) {
|
||||
return el('details');
|
||||
}
|
||||
|
||||
updateDOM(prevNode: DetailsNode, dom: HTMLElement) {
|
||||
return false;
|
||||
}
|
||||
|
||||
static importDOM(): DOMConversionMap|null {
|
||||
return {
|
||||
details(node: HTMLElement): DOMConversion|null {
|
||||
return {
|
||||
conversion: (element: HTMLElement): DOMConversionOutput|null => {
|
||||
return {
|
||||
node: new DetailsNode(),
|
||||
};
|
||||
},
|
||||
priority: 3,
|
||||
};
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
exportJSON(): SerializedElementNode {
|
||||
return {
|
||||
...super.exportJSON(),
|
||||
type: 'details',
|
||||
version: 1,
|
||||
};
|
||||
}
|
||||
|
||||
static importJSON(serializedNode: SerializedElementNode): DetailsNode {
|
||||
return $createDetailsNode();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export function $createDetailsNode() {
|
||||
return new DetailsNode();
|
||||
}
|
||||
|
||||
export function $isDetailsNode(node: LexicalNode | null | undefined) {
|
||||
return node instanceof DetailsNode;
|
||||
}
|
||||
|
||||
export class SummaryNode extends ElementNode {
|
||||
|
||||
static getType() {
|
||||
return 'summary';
|
||||
}
|
||||
|
||||
static clone(node: SummaryNode) {
|
||||
return new SummaryNode(node.__key);
|
||||
}
|
||||
|
||||
createDOM(_config: EditorConfig, _editor: LexicalEditor) {
|
||||
return el('summary');
|
||||
}
|
||||
|
||||
updateDOM(prevNode: DetailsNode, dom: HTMLElement) {
|
||||
return false;
|
||||
}
|
||||
|
||||
static importDOM(): DOMConversionMap|null {
|
||||
return {
|
||||
summary(node: HTMLElement): DOMConversion|null {
|
||||
return {
|
||||
conversion: (element: HTMLElement): DOMConversionOutput|null => {
|
||||
return {
|
||||
node: new SummaryNode(),
|
||||
};
|
||||
},
|
||||
priority: 3,
|
||||
};
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
exportJSON(): SerializedElementNode {
|
||||
return {
|
||||
...super.exportJSON(),
|
||||
type: 'summary',
|
||||
version: 1,
|
||||
};
|
||||
}
|
||||
|
||||
static importJSON(serializedNode: SerializedElementNode): DetailsNode {
|
||||
return $createSummaryNode();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export function $createSummaryNode() {
|
||||
return new SummaryNode();
|
||||
}
|
||||
|
||||
export function $isSummaryNode(node: LexicalNode | null | undefined) {
|
||||
return node instanceof SummaryNode;
|
||||
}
|
@ -4,6 +4,7 @@ import {ElementNode, KlassConstructor, LexicalNode, LexicalNodeReplacement, Para
|
||||
import {CustomParagraphNode} from "./custom-paragraph";
|
||||
import {LinkNode} from "@lexical/link";
|
||||
import {ImageNode} from "./image";
|
||||
import {DetailsNode, SummaryNode} from "./details";
|
||||
|
||||
/**
|
||||
* Load the nodes for lexical.
|
||||
@ -14,6 +15,7 @@ export function getNodesForPageEditor(): (KlassConstructor<typeof LexicalNode> |
|
||||
HeadingNode, // Todo - Create custom
|
||||
QuoteNode, // Todo - Create custom
|
||||
ImageNode,
|
||||
DetailsNode, SummaryNode,
|
||||
CustomParagraphNode,
|
||||
{
|
||||
replace: ParagraphNode,
|
||||
|
@ -1,9 +1,9 @@
|
||||
import {EditorButtonDefinition} from "../framework/buttons";
|
||||
import {
|
||||
$createNodeSelection,
|
||||
$createParagraphNode, $getSelection,
|
||||
$createParagraphNode, $getRoot, $getSelection, $insertNodes,
|
||||
$isParagraphNode, $setSelection,
|
||||
BaseSelection, FORMAT_TEXT_COMMAND,
|
||||
BaseSelection, ElementNode, FORMAT_TEXT_COMMAND,
|
||||
LexicalNode,
|
||||
REDO_COMMAND, TextFormatType,
|
||||
UNDO_COMMAND
|
||||
@ -26,6 +26,8 @@ import {
|
||||
import {$isLinkNode, $toggleLink, LinkNode} from "@lexical/link";
|
||||
import {EditorUiContext} from "../framework/core";
|
||||
import {$isImageNode, ImageNode} from "../../nodes/image";
|
||||
import {$createDetailsNode, $isDetailsNode} from "../../nodes/details";
|
||||
import {$insertNodeToNearestRoot} from "@lexical/utils";
|
||||
|
||||
export const undo: EditorButtonDefinition = {
|
||||
label: 'Undo',
|
||||
@ -201,3 +203,30 @@ export const image: EditorButtonDefinition = {
|
||||
}
|
||||
};
|
||||
|
||||
export const details: EditorButtonDefinition = {
|
||||
label: 'Insert collapsible block',
|
||||
action(context: EditorUiContext) {
|
||||
context.editor.update(() => {
|
||||
const selection = $getSelection();
|
||||
const detailsNode = $createDetailsNode();
|
||||
const selectionNodes = selection?.getNodes() || [];
|
||||
const topLevels = selectionNodes.map(n => n.getTopLevelElement())
|
||||
.filter(n => n !== null) as ElementNode[];
|
||||
const uniqueTopLevels = [...new Set(topLevels)];
|
||||
|
||||
if (uniqueTopLevels.length > 0) {
|
||||
uniqueTopLevels[0].insertAfter(detailsNode);
|
||||
} else {
|
||||
$getRoot().append(detailsNode);
|
||||
}
|
||||
|
||||
for (const node of uniqueTopLevels) {
|
||||
detailsNode.append(node);
|
||||
}
|
||||
});
|
||||
},
|
||||
isActive(selection: BaseSelection|null): boolean {
|
||||
return selectionContainsNodeType(selection, $isDetailsNode);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
import {EditorButton, FormatPreviewButton} from "./framework/buttons";
|
||||
import {
|
||||
blockquote, bold, code,
|
||||
dangerCallout,
|
||||
dangerCallout, details,
|
||||
h2, h3, h4, h5, image,
|
||||
infoCallout, italic, link, paragraph,
|
||||
redo, strikethrough, subscript,
|
||||
@ -41,5 +41,6 @@ export function getMainEditorFullToolbar(): EditorContainerUiElement {
|
||||
|
||||
new EditorButton(link),
|
||||
new EditorButton(image),
|
||||
new EditorButton(details),
|
||||
]);
|
||||
}
|
@ -16,6 +16,13 @@
|
||||
<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>
|
||||
|
Loading…
Reference in New Issue
Block a user