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 {CustomParagraphNode} from "./custom-paragraph";
|
||||||
import {LinkNode} from "@lexical/link";
|
import {LinkNode} from "@lexical/link";
|
||||||
import {ImageNode} from "./image";
|
import {ImageNode} from "./image";
|
||||||
|
import {DetailsNode, SummaryNode} from "./details";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Load the nodes for lexical.
|
* Load the nodes for lexical.
|
||||||
@ -14,6 +15,7 @@ export function getNodesForPageEditor(): (KlassConstructor<typeof LexicalNode> |
|
|||||||
HeadingNode, // Todo - Create custom
|
HeadingNode, // Todo - Create custom
|
||||||
QuoteNode, // Todo - Create custom
|
QuoteNode, // Todo - Create custom
|
||||||
ImageNode,
|
ImageNode,
|
||||||
|
DetailsNode, SummaryNode,
|
||||||
CustomParagraphNode,
|
CustomParagraphNode,
|
||||||
{
|
{
|
||||||
replace: ParagraphNode,
|
replace: ParagraphNode,
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import {EditorButtonDefinition} from "../framework/buttons";
|
import {EditorButtonDefinition} from "../framework/buttons";
|
||||||
import {
|
import {
|
||||||
$createNodeSelection,
|
$createNodeSelection,
|
||||||
$createParagraphNode, $getSelection,
|
$createParagraphNode, $getRoot, $getSelection, $insertNodes,
|
||||||
$isParagraphNode, $setSelection,
|
$isParagraphNode, $setSelection,
|
||||||
BaseSelection, FORMAT_TEXT_COMMAND,
|
BaseSelection, ElementNode, FORMAT_TEXT_COMMAND,
|
||||||
LexicalNode,
|
LexicalNode,
|
||||||
REDO_COMMAND, TextFormatType,
|
REDO_COMMAND, TextFormatType,
|
||||||
UNDO_COMMAND
|
UNDO_COMMAND
|
||||||
@ -26,6 +26,8 @@ import {
|
|||||||
import {$isLinkNode, $toggleLink, LinkNode} from "@lexical/link";
|
import {$isLinkNode, $toggleLink, LinkNode} from "@lexical/link";
|
||||||
import {EditorUiContext} from "../framework/core";
|
import {EditorUiContext} from "../framework/core";
|
||||||
import {$isImageNode, ImageNode} from "../../nodes/image";
|
import {$isImageNode, ImageNode} from "../../nodes/image";
|
||||||
|
import {$createDetailsNode, $isDetailsNode} from "../../nodes/details";
|
||||||
|
import {$insertNodeToNearestRoot} from "@lexical/utils";
|
||||||
|
|
||||||
export const undo: EditorButtonDefinition = {
|
export const undo: EditorButtonDefinition = {
|
||||||
label: 'Undo',
|
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 {EditorButton, FormatPreviewButton} from "./framework/buttons";
|
||||||
import {
|
import {
|
||||||
blockquote, bold, code,
|
blockquote, bold, code,
|
||||||
dangerCallout,
|
dangerCallout, details,
|
||||||
h2, h3, h4, h5, image,
|
h2, h3, h4, h5, image,
|
||||||
infoCallout, italic, link, paragraph,
|
infoCallout, italic, link, paragraph,
|
||||||
redo, strikethrough, subscript,
|
redo, strikethrough, subscript,
|
||||||
@ -41,5 +41,6 @@ export function getMainEditorFullToolbar(): EditorContainerUiElement {
|
|||||||
|
|
||||||
new EditorButton(link),
|
new EditorButton(link),
|
||||||
new EditorButton(image),
|
new EditorButton(image),
|
||||||
|
new EditorButton(details),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
@ -16,6 +16,13 @@
|
|||||||
<li>Hello</li>
|
<li>Hello</li>
|
||||||
</ul>
|
</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">
|
<p class="callout info">
|
||||||
Hello there, this is an info callout
|
Hello there, this is an info callout
|
||||||
</p>
|
</p>
|
||||||
|
Loading…
Reference in New Issue
Block a user