mirror of
https://github.com/BookStackApp/BookStack.git
synced 2025-03-13 13:55:26 +08:00
Lexical: Extracted & merged heading & quote nodes
This commit is contained in:
parent
f3fa63a5ae
commit
36a4d79120
@ -8,11 +8,12 @@
|
||||
|
||||
import {$createLinkNode} from '@lexical/link';
|
||||
import {$createListItemNode, $createListNode} from '@lexical/list';
|
||||
import {$createHeadingNode, $createQuoteNode} from '@lexical/rich-text';
|
||||
import {$createTableNodeWithDimensions} from '@lexical/table';
|
||||
import {$createParagraphNode, $createTextNode, $getRoot} from 'lexical';
|
||||
|
||||
import {initializeUnitTest} from '../utils';
|
||||
import {$createHeadingNode} from "@lexical/rich-text/LexicalHeadingNode";
|
||||
import {$createQuoteNode} from "@lexical/rich-text/LexicalQuoteNode";
|
||||
|
||||
function $createEditorContent() {
|
||||
const root = $getRoot();
|
||||
|
@ -10,7 +10,6 @@ import {createHeadlessEditor} from '@lexical/headless';
|
||||
import {AutoLinkNode, LinkNode} from '@lexical/link';
|
||||
import {ListItemNode, ListNode} from '@lexical/list';
|
||||
|
||||
import {HeadingNode, QuoteNode} from '@lexical/rich-text';
|
||||
import {TableCellNode, TableNode, TableRowNode} from '@lexical/table';
|
||||
|
||||
import {
|
||||
@ -36,6 +35,8 @@ import {
|
||||
LexicalNodeReplacement,
|
||||
} from '../../LexicalEditor';
|
||||
import {resetRandomKey} from '../../LexicalUtils';
|
||||
import {HeadingNode} from "@lexical/rich-text/LexicalHeadingNode";
|
||||
import {QuoteNode} from "@lexical/rich-text/LexicalQuoteNode";
|
||||
|
||||
|
||||
type TestEnv = {
|
||||
|
@ -48,7 +48,7 @@ export class CommonBlockNode extends ElementNode {
|
||||
}
|
||||
|
||||
export function copyCommonBlockProperties(from: CommonBlockNode, to: CommonBlockNode): void {
|
||||
to.__id = from.__id;
|
||||
// to.__id = from.__id;
|
||||
to.__alignment = from.__alignment;
|
||||
to.__inset = from.__inset;
|
||||
}
|
@ -11,7 +11,7 @@ import {
|
||||
$insertDataTransferForRichText,
|
||||
} from '@lexical/clipboard';
|
||||
import {$createListItemNode, $createListNode} from '@lexical/list';
|
||||
import {$createHeadingNode, registerRichText} from '@lexical/rich-text';
|
||||
import {registerRichText} from '@lexical/rich-text';
|
||||
import {
|
||||
$createParagraphNode,
|
||||
$createRangeSelection,
|
||||
@ -32,6 +32,7 @@ import {
|
||||
initializeUnitTest,
|
||||
invariant,
|
||||
} from '../../../__tests__/utils';
|
||||
import {$createHeadingNode} from "@lexical/rich-text/LexicalHeadingNode";
|
||||
|
||||
describe('LexicalTabNode tests', () => {
|
||||
initializeUnitTest((testEnv) => {
|
||||
|
@ -13,13 +13,14 @@ import {createHeadlessEditor} from '@lexical/headless';
|
||||
import {$generateHtmlFromNodes, $generateNodesFromDOM} from '@lexical/html';
|
||||
import {LinkNode} from '@lexical/link';
|
||||
import {ListItemNode, ListNode} from '@lexical/list';
|
||||
import {HeadingNode, QuoteNode} from '@lexical/rich-text';
|
||||
import {
|
||||
$createParagraphNode,
|
||||
$createRangeSelection,
|
||||
$createTextNode,
|
||||
$getRoot,
|
||||
} from 'lexical';
|
||||
import {HeadingNode} from "@lexical/rich-text/LexicalHeadingNode";
|
||||
import {QuoteNode} from "@lexical/rich-text/LexicalQuoteNode";
|
||||
|
||||
describe('HTML', () => {
|
||||
type Input = Array<{
|
||||
|
202
resources/js/wysiwyg/lexical/rich-text/LexicalHeadingNode.ts
Normal file
202
resources/js/wysiwyg/lexical/rich-text/LexicalHeadingNode.ts
Normal file
@ -0,0 +1,202 @@
|
||||
import {
|
||||
$applyNodeReplacement,
|
||||
$createParagraphNode,
|
||||
type DOMConversionMap,
|
||||
DOMConversionOutput,
|
||||
type DOMExportOutput,
|
||||
type EditorConfig,
|
||||
isHTMLElement,
|
||||
type LexicalEditor,
|
||||
type LexicalNode,
|
||||
type NodeKey,
|
||||
type ParagraphNode,
|
||||
type RangeSelection,
|
||||
type SerializedElementNode,
|
||||
type Spread
|
||||
} from "lexical";
|
||||
import {addClassNamesToElement} from "@lexical/utils";
|
||||
import {CommonBlockNode, copyCommonBlockProperties} from "lexical/nodes/CommonBlockNode";
|
||||
import {
|
||||
commonPropertiesDifferent, deserializeCommonBlockNode,
|
||||
SerializedCommonBlockNode, setCommonBlockPropsFromElement,
|
||||
updateElementWithCommonBlockProps
|
||||
} from "../../nodes/_common";
|
||||
|
||||
export type HeadingTagType = 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6';
|
||||
|
||||
export type SerializedHeadingNode = Spread<
|
||||
{
|
||||
tag: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6';
|
||||
},
|
||||
SerializedCommonBlockNode
|
||||
>;
|
||||
|
||||
/** @noInheritDoc */
|
||||
export class HeadingNode extends CommonBlockNode {
|
||||
/** @internal */
|
||||
__tag: HeadingTagType;
|
||||
|
||||
static getType(): string {
|
||||
return 'heading';
|
||||
}
|
||||
|
||||
static clone(node: HeadingNode): HeadingNode {
|
||||
const clone = new HeadingNode(node.__tag, node.__key);
|
||||
copyCommonBlockProperties(node, clone);
|
||||
return clone;
|
||||
}
|
||||
|
||||
constructor(tag: HeadingTagType, key?: NodeKey) {
|
||||
super(key);
|
||||
this.__tag = tag;
|
||||
}
|
||||
|
||||
getTag(): HeadingTagType {
|
||||
return this.__tag;
|
||||
}
|
||||
|
||||
// View
|
||||
|
||||
createDOM(config: EditorConfig): HTMLElement {
|
||||
const tag = this.__tag;
|
||||
const element = document.createElement(tag);
|
||||
const theme = config.theme;
|
||||
const classNames = theme.heading;
|
||||
if (classNames !== undefined) {
|
||||
const className = classNames[tag];
|
||||
addClassNamesToElement(element, className);
|
||||
}
|
||||
updateElementWithCommonBlockProps(element, this);
|
||||
return element;
|
||||
}
|
||||
|
||||
updateDOM(prevNode: HeadingNode, dom: HTMLElement): boolean {
|
||||
return commonPropertiesDifferent(prevNode, this);
|
||||
}
|
||||
|
||||
static importDOM(): DOMConversionMap | null {
|
||||
return {
|
||||
h1: (node: Node) => ({
|
||||
conversion: $convertHeadingElement,
|
||||
priority: 0,
|
||||
}),
|
||||
h2: (node: Node) => ({
|
||||
conversion: $convertHeadingElement,
|
||||
priority: 0,
|
||||
}),
|
||||
h3: (node: Node) => ({
|
||||
conversion: $convertHeadingElement,
|
||||
priority: 0,
|
||||
}),
|
||||
h4: (node: Node) => ({
|
||||
conversion: $convertHeadingElement,
|
||||
priority: 0,
|
||||
}),
|
||||
h5: (node: Node) => ({
|
||||
conversion: $convertHeadingElement,
|
||||
priority: 0,
|
||||
}),
|
||||
h6: (node: Node) => ({
|
||||
conversion: $convertHeadingElement,
|
||||
priority: 0,
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
exportDOM(editor: LexicalEditor): DOMExportOutput {
|
||||
const {element} = super.exportDOM(editor);
|
||||
|
||||
if (element && isHTMLElement(element)) {
|
||||
if (this.isEmpty()) {
|
||||
element.append(document.createElement('br'));
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
element,
|
||||
};
|
||||
}
|
||||
|
||||
static importJSON(serializedNode: SerializedHeadingNode): HeadingNode {
|
||||
const node = $createHeadingNode(serializedNode.tag);
|
||||
deserializeCommonBlockNode(serializedNode, node);
|
||||
return node;
|
||||
}
|
||||
|
||||
exportJSON(): SerializedHeadingNode {
|
||||
return {
|
||||
...super.exportJSON(),
|
||||
tag: this.getTag(),
|
||||
type: 'heading',
|
||||
version: 1,
|
||||
};
|
||||
}
|
||||
|
||||
// Mutation
|
||||
insertNewAfter(
|
||||
selection?: RangeSelection,
|
||||
restoreSelection = true,
|
||||
): ParagraphNode | HeadingNode {
|
||||
const anchorOffet = selection ? selection.anchor.offset : 0;
|
||||
const lastDesc = this.getLastDescendant();
|
||||
const isAtEnd =
|
||||
!lastDesc ||
|
||||
(selection &&
|
||||
selection.anchor.key === lastDesc.getKey() &&
|
||||
anchorOffet === lastDesc.getTextContentSize());
|
||||
const newElement =
|
||||
isAtEnd || !selection
|
||||
? $createParagraphNode()
|
||||
: $createHeadingNode(this.getTag());
|
||||
const direction = this.getDirection();
|
||||
newElement.setDirection(direction);
|
||||
this.insertAfter(newElement, restoreSelection);
|
||||
if (anchorOffet === 0 && !this.isEmpty() && selection) {
|
||||
const paragraph = $createParagraphNode();
|
||||
paragraph.select();
|
||||
this.replace(paragraph, true);
|
||||
}
|
||||
return newElement;
|
||||
}
|
||||
|
||||
collapseAtStart(): true {
|
||||
const newElement = !this.isEmpty()
|
||||
? $createHeadingNode(this.getTag())
|
||||
: $createParagraphNode();
|
||||
const children = this.getChildren();
|
||||
children.forEach((child) => newElement.append(child));
|
||||
this.replace(newElement);
|
||||
return true;
|
||||
}
|
||||
|
||||
extractWithChild(): boolean {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
function $convertHeadingElement(element: HTMLElement): DOMConversionOutput {
|
||||
const nodeName = element.nodeName.toLowerCase();
|
||||
let node = null;
|
||||
if (
|
||||
nodeName === 'h1' ||
|
||||
nodeName === 'h2' ||
|
||||
nodeName === 'h3' ||
|
||||
nodeName === 'h4' ||
|
||||
nodeName === 'h5' ||
|
||||
nodeName === 'h6'
|
||||
) {
|
||||
node = $createHeadingNode(nodeName);
|
||||
setCommonBlockPropsFromElement(element, node);
|
||||
}
|
||||
return {node};
|
||||
}
|
||||
|
||||
export function $createHeadingNode(headingTag: HeadingTagType): HeadingNode {
|
||||
return $applyNodeReplacement(new HeadingNode(headingTag));
|
||||
}
|
||||
|
||||
export function $isHeadingNode(
|
||||
node: LexicalNode | null | undefined,
|
||||
): node is HeadingNode {
|
||||
return node instanceof HeadingNode;
|
||||
}
|
129
resources/js/wysiwyg/lexical/rich-text/LexicalQuoteNode.ts
Normal file
129
resources/js/wysiwyg/lexical/rich-text/LexicalQuoteNode.ts
Normal file
@ -0,0 +1,129 @@
|
||||
import {
|
||||
$applyNodeReplacement,
|
||||
$createParagraphNode,
|
||||
type DOMConversionMap,
|
||||
type DOMConversionOutput,
|
||||
type DOMExportOutput,
|
||||
type EditorConfig,
|
||||
ElementNode,
|
||||
isHTMLElement,
|
||||
type LexicalEditor,
|
||||
LexicalNode,
|
||||
type NodeKey,
|
||||
type ParagraphNode,
|
||||
type RangeSelection,
|
||||
SerializedElementNode
|
||||
} from "lexical";
|
||||
import {addClassNamesToElement} from "@lexical/utils";
|
||||
import {CommonBlockNode, copyCommonBlockProperties} from "lexical/nodes/CommonBlockNode";
|
||||
import {
|
||||
commonPropertiesDifferent, deserializeCommonBlockNode,
|
||||
SerializedCommonBlockNode, setCommonBlockPropsFromElement,
|
||||
updateElementWithCommonBlockProps
|
||||
} from "../../nodes/_common";
|
||||
|
||||
export type SerializedQuoteNode = SerializedCommonBlockNode;
|
||||
|
||||
/** @noInheritDoc */
|
||||
export class QuoteNode extends CommonBlockNode {
|
||||
static getType(): string {
|
||||
return 'quote';
|
||||
}
|
||||
|
||||
static clone(node: QuoteNode): QuoteNode {
|
||||
const clone = new QuoteNode(node.__key);
|
||||
copyCommonBlockProperties(node, clone);
|
||||
return clone;
|
||||
}
|
||||
|
||||
constructor(key?: NodeKey) {
|
||||
super(key);
|
||||
}
|
||||
|
||||
// View
|
||||
|
||||
createDOM(config: EditorConfig): HTMLElement {
|
||||
const element = document.createElement('blockquote');
|
||||
addClassNamesToElement(element, config.theme.quote);
|
||||
updateElementWithCommonBlockProps(element, this);
|
||||
return element;
|
||||
}
|
||||
|
||||
updateDOM(prevNode: QuoteNode, dom: HTMLElement): boolean {
|
||||
return commonPropertiesDifferent(prevNode, this);
|
||||
}
|
||||
|
||||
static importDOM(): DOMConversionMap | null {
|
||||
return {
|
||||
blockquote: (node: Node) => ({
|
||||
conversion: $convertBlockquoteElement,
|
||||
priority: 0,
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
exportDOM(editor: LexicalEditor): DOMExportOutput {
|
||||
const {element} = super.exportDOM(editor);
|
||||
|
||||
if (element && isHTMLElement(element)) {
|
||||
if (this.isEmpty()) {
|
||||
element.append(document.createElement('br'));
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
element,
|
||||
};
|
||||
}
|
||||
|
||||
static importJSON(serializedNode: SerializedQuoteNode): QuoteNode {
|
||||
const node = $createQuoteNode();
|
||||
deserializeCommonBlockNode(serializedNode, node);
|
||||
return node;
|
||||
}
|
||||
|
||||
exportJSON(): SerializedQuoteNode {
|
||||
return {
|
||||
...super.exportJSON(),
|
||||
type: 'quote',
|
||||
};
|
||||
}
|
||||
|
||||
// Mutation
|
||||
|
||||
insertNewAfter(_: RangeSelection, restoreSelection?: boolean): ParagraphNode {
|
||||
const newBlock = $createParagraphNode();
|
||||
const direction = this.getDirection();
|
||||
newBlock.setDirection(direction);
|
||||
this.insertAfter(newBlock, restoreSelection);
|
||||
return newBlock;
|
||||
}
|
||||
|
||||
collapseAtStart(): true {
|
||||
const paragraph = $createParagraphNode();
|
||||
const children = this.getChildren();
|
||||
children.forEach((child) => paragraph.append(child));
|
||||
this.replace(paragraph);
|
||||
return true;
|
||||
}
|
||||
|
||||
canMergeWhenEmpty(): true {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export function $createQuoteNode(): QuoteNode {
|
||||
return $applyNodeReplacement(new QuoteNode());
|
||||
}
|
||||
|
||||
export function $isQuoteNode(
|
||||
node: LexicalNode | null | undefined,
|
||||
): node is QuoteNode {
|
||||
return node instanceof QuoteNode;
|
||||
}
|
||||
|
||||
function $convertBlockquoteElement(element: HTMLElement): DOMConversionOutput {
|
||||
const node = $createQuoteNode();
|
||||
setCommonBlockPropsFromElement(element, node);
|
||||
return {node};
|
||||
}
|
@ -6,11 +6,6 @@
|
||||
*
|
||||
*/
|
||||
|
||||
import {
|
||||
$createHeadingNode,
|
||||
$isHeadingNode,
|
||||
HeadingNode,
|
||||
} from '@lexical/rich-text';
|
||||
import {
|
||||
$createTextNode,
|
||||
$getRoot,
|
||||
@ -19,6 +14,7 @@ import {
|
||||
RangeSelection,
|
||||
} from 'lexical';
|
||||
import {initializeUnitTest} from 'lexical/__tests__/utils';
|
||||
import {$createHeadingNode, $isHeadingNode, HeadingNode} from "@lexical/rich-text/LexicalHeadingNode";
|
||||
|
||||
const editorConfig = Object.freeze({
|
||||
namespace: '',
|
||||
|
@ -6,9 +6,9 @@
|
||||
*
|
||||
*/
|
||||
|
||||
import {$createQuoteNode, QuoteNode} from '@lexical/rich-text';
|
||||
import {$createRangeSelection, $getRoot, ParagraphNode} from 'lexical';
|
||||
import {initializeUnitTest} from 'lexical/__tests__/utils';
|
||||
import {$createQuoteNode, QuoteNode} from "@lexical/rich-text/LexicalQuoteNode";
|
||||
|
||||
const editorConfig = Object.freeze({
|
||||
namespace: '',
|
||||
|
@ -8,42 +8,14 @@
|
||||
|
||||
import type {
|
||||
CommandPayloadType,
|
||||
DOMConversionMap,
|
||||
DOMConversionOutput,
|
||||
DOMExportOutput,
|
||||
EditorConfig,
|
||||
ElementFormatType,
|
||||
LexicalCommand,
|
||||
LexicalEditor,
|
||||
LexicalNode,
|
||||
NodeKey,
|
||||
ParagraphNode,
|
||||
PasteCommandType,
|
||||
RangeSelection,
|
||||
SerializedElementNode,
|
||||
Spread,
|
||||
TextFormatType,
|
||||
} from 'lexical';
|
||||
|
||||
import {
|
||||
$insertDataTransferForRichText,
|
||||
copyToClipboard,
|
||||
} from '@lexical/clipboard';
|
||||
import {
|
||||
$moveCharacter,
|
||||
$shouldOverrideDefaultCharacterSelection,
|
||||
} from '@lexical/selection';
|
||||
import {
|
||||
$findMatchingParent,
|
||||
$getNearestBlockElementAncestorOrThrow,
|
||||
addClassNamesToElement,
|
||||
isHTMLElement,
|
||||
mergeRegister,
|
||||
objectKlassEquals,
|
||||
} from '@lexical/utils';
|
||||
import {
|
||||
$applyNodeReplacement,
|
||||
$createParagraphNode,
|
||||
$createRangeSelection,
|
||||
$createTabNode,
|
||||
$getAdjacentNode,
|
||||
@ -55,7 +27,6 @@ import {
|
||||
$isElementNode,
|
||||
$isNodeSelection,
|
||||
$isRangeSelection,
|
||||
$isRootNode,
|
||||
$isTextNode,
|
||||
$normalizeSelection__EXPERIMENTAL,
|
||||
$selectAll,
|
||||
@ -75,7 +46,6 @@ import {
|
||||
ElementNode,
|
||||
FORMAT_ELEMENT_COMMAND,
|
||||
FORMAT_TEXT_COMMAND,
|
||||
INDENT_CONTENT_COMMAND,
|
||||
INSERT_LINE_BREAK_COMMAND,
|
||||
INSERT_PARAGRAPH_COMMAND,
|
||||
INSERT_TAB_COMMAND,
|
||||
@ -88,327 +58,22 @@ import {
|
||||
KEY_DELETE_COMMAND,
|
||||
KEY_ENTER_COMMAND,
|
||||
KEY_ESCAPE_COMMAND,
|
||||
OUTDENT_CONTENT_COMMAND,
|
||||
PASTE_COMMAND,
|
||||
REMOVE_TEXT_COMMAND,
|
||||
SELECT_ALL_COMMAND,
|
||||
} from 'lexical';
|
||||
import caretFromPoint from 'lexical/shared/caretFromPoint';
|
||||
import {
|
||||
CAN_USE_BEFORE_INPUT,
|
||||
IS_APPLE_WEBKIT,
|
||||
IS_IOS,
|
||||
IS_SAFARI,
|
||||
} from 'lexical/shared/environment';
|
||||
|
||||
export type SerializedHeadingNode = Spread<
|
||||
{
|
||||
tag: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6';
|
||||
},
|
||||
SerializedElementNode
|
||||
>;
|
||||
import {$insertDataTransferForRichText, copyToClipboard,} from '@lexical/clipboard';
|
||||
import {$moveCharacter, $shouldOverrideDefaultCharacterSelection,} from '@lexical/selection';
|
||||
import {$findMatchingParent, mergeRegister, objectKlassEquals,} from '@lexical/utils';
|
||||
import caretFromPoint from 'lexical/shared/caretFromPoint';
|
||||
import {CAN_USE_BEFORE_INPUT, IS_APPLE_WEBKIT, IS_IOS, IS_SAFARI,} from 'lexical/shared/environment';
|
||||
|
||||
export const DRAG_DROP_PASTE: LexicalCommand<Array<File>> = createCommand(
|
||||
'DRAG_DROP_PASTE_FILE',
|
||||
);
|
||||
|
||||
export type SerializedQuoteNode = SerializedElementNode;
|
||||
|
||||
/** @noInheritDoc */
|
||||
export class QuoteNode extends ElementNode {
|
||||
static getType(): string {
|
||||
return 'quote';
|
||||
}
|
||||
|
||||
static clone(node: QuoteNode): QuoteNode {
|
||||
return new QuoteNode(node.__key);
|
||||
}
|
||||
|
||||
constructor(key?: NodeKey) {
|
||||
super(key);
|
||||
}
|
||||
|
||||
// View
|
||||
|
||||
createDOM(config: EditorConfig): HTMLElement {
|
||||
const element = document.createElement('blockquote');
|
||||
addClassNamesToElement(element, config.theme.quote);
|
||||
return element;
|
||||
}
|
||||
updateDOM(prevNode: QuoteNode, dom: HTMLElement): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
static importDOM(): DOMConversionMap | null {
|
||||
return {
|
||||
blockquote: (node: Node) => ({
|
||||
conversion: $convertBlockquoteElement,
|
||||
priority: 0,
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
exportDOM(editor: LexicalEditor): DOMExportOutput {
|
||||
const {element} = super.exportDOM(editor);
|
||||
|
||||
if (element && isHTMLElement(element)) {
|
||||
if (this.isEmpty()) {
|
||||
element.append(document.createElement('br'));
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
element,
|
||||
};
|
||||
}
|
||||
|
||||
static importJSON(serializedNode: SerializedQuoteNode): QuoteNode {
|
||||
const node = $createQuoteNode();
|
||||
return node;
|
||||
}
|
||||
|
||||
exportJSON(): SerializedElementNode {
|
||||
return {
|
||||
...super.exportJSON(),
|
||||
type: 'quote',
|
||||
};
|
||||
}
|
||||
|
||||
// Mutation
|
||||
|
||||
insertNewAfter(_: RangeSelection, restoreSelection?: boolean): ParagraphNode {
|
||||
const newBlock = $createParagraphNode();
|
||||
const direction = this.getDirection();
|
||||
newBlock.setDirection(direction);
|
||||
this.insertAfter(newBlock, restoreSelection);
|
||||
return newBlock;
|
||||
}
|
||||
|
||||
collapseAtStart(): true {
|
||||
const paragraph = $createParagraphNode();
|
||||
const children = this.getChildren();
|
||||
children.forEach((child) => paragraph.append(child));
|
||||
this.replace(paragraph);
|
||||
return true;
|
||||
}
|
||||
|
||||
canMergeWhenEmpty(): true {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export function $createQuoteNode(): QuoteNode {
|
||||
return $applyNodeReplacement(new QuoteNode());
|
||||
}
|
||||
|
||||
export function $isQuoteNode(
|
||||
node: LexicalNode | null | undefined,
|
||||
): node is QuoteNode {
|
||||
return node instanceof QuoteNode;
|
||||
}
|
||||
|
||||
export type HeadingTagType = 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6';
|
||||
|
||||
/** @noInheritDoc */
|
||||
export class HeadingNode extends ElementNode {
|
||||
/** @internal */
|
||||
__tag: HeadingTagType;
|
||||
|
||||
static getType(): string {
|
||||
return 'heading';
|
||||
}
|
||||
|
||||
static clone(node: HeadingNode): HeadingNode {
|
||||
return new HeadingNode(node.__tag, node.__key);
|
||||
}
|
||||
|
||||
constructor(tag: HeadingTagType, key?: NodeKey) {
|
||||
super(key);
|
||||
this.__tag = tag;
|
||||
}
|
||||
|
||||
getTag(): HeadingTagType {
|
||||
return this.__tag;
|
||||
}
|
||||
|
||||
// View
|
||||
|
||||
createDOM(config: EditorConfig): HTMLElement {
|
||||
const tag = this.__tag;
|
||||
const element = document.createElement(tag);
|
||||
const theme = config.theme;
|
||||
const classNames = theme.heading;
|
||||
if (classNames !== undefined) {
|
||||
const className = classNames[tag];
|
||||
addClassNamesToElement(element, className);
|
||||
}
|
||||
return element;
|
||||
}
|
||||
|
||||
updateDOM(prevNode: HeadingNode, dom: HTMLElement): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
static importDOM(): DOMConversionMap | null {
|
||||
return {
|
||||
h1: (node: Node) => ({
|
||||
conversion: $convertHeadingElement,
|
||||
priority: 0,
|
||||
}),
|
||||
h2: (node: Node) => ({
|
||||
conversion: $convertHeadingElement,
|
||||
priority: 0,
|
||||
}),
|
||||
h3: (node: Node) => ({
|
||||
conversion: $convertHeadingElement,
|
||||
priority: 0,
|
||||
}),
|
||||
h4: (node: Node) => ({
|
||||
conversion: $convertHeadingElement,
|
||||
priority: 0,
|
||||
}),
|
||||
h5: (node: Node) => ({
|
||||
conversion: $convertHeadingElement,
|
||||
priority: 0,
|
||||
}),
|
||||
h6: (node: Node) => ({
|
||||
conversion: $convertHeadingElement,
|
||||
priority: 0,
|
||||
}),
|
||||
p: (node: Node) => {
|
||||
// domNode is a <p> since we matched it by nodeName
|
||||
const paragraph = node as HTMLParagraphElement;
|
||||
const firstChild = paragraph.firstChild;
|
||||
if (firstChild !== null && isGoogleDocsTitle(firstChild)) {
|
||||
return {
|
||||
conversion: () => ({node: null}),
|
||||
priority: 3,
|
||||
};
|
||||
}
|
||||
return null;
|
||||
},
|
||||
span: (node: Node) => {
|
||||
if (isGoogleDocsTitle(node)) {
|
||||
return {
|
||||
conversion: (domNode: Node) => {
|
||||
return {
|
||||
node: $createHeadingNode('h1'),
|
||||
};
|
||||
},
|
||||
priority: 3,
|
||||
};
|
||||
}
|
||||
return null;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
exportDOM(editor: LexicalEditor): DOMExportOutput {
|
||||
const {element} = super.exportDOM(editor);
|
||||
|
||||
if (element && isHTMLElement(element)) {
|
||||
if (this.isEmpty()) {
|
||||
element.append(document.createElement('br'));
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
element,
|
||||
};
|
||||
}
|
||||
|
||||
static importJSON(serializedNode: SerializedHeadingNode): HeadingNode {
|
||||
return $createHeadingNode(serializedNode.tag);
|
||||
}
|
||||
|
||||
exportJSON(): SerializedHeadingNode {
|
||||
return {
|
||||
...super.exportJSON(),
|
||||
tag: this.getTag(),
|
||||
type: 'heading',
|
||||
version: 1,
|
||||
};
|
||||
}
|
||||
|
||||
// Mutation
|
||||
insertNewAfter(
|
||||
selection?: RangeSelection,
|
||||
restoreSelection = true,
|
||||
): ParagraphNode | HeadingNode {
|
||||
const anchorOffet = selection ? selection.anchor.offset : 0;
|
||||
const lastDesc = this.getLastDescendant();
|
||||
const isAtEnd =
|
||||
!lastDesc ||
|
||||
(selection &&
|
||||
selection.anchor.key === lastDesc.getKey() &&
|
||||
anchorOffet === lastDesc.getTextContentSize());
|
||||
const newElement =
|
||||
isAtEnd || !selection
|
||||
? $createParagraphNode()
|
||||
: $createHeadingNode(this.getTag());
|
||||
const direction = this.getDirection();
|
||||
newElement.setDirection(direction);
|
||||
this.insertAfter(newElement, restoreSelection);
|
||||
if (anchorOffet === 0 && !this.isEmpty() && selection) {
|
||||
const paragraph = $createParagraphNode();
|
||||
paragraph.select();
|
||||
this.replace(paragraph, true);
|
||||
}
|
||||
return newElement;
|
||||
}
|
||||
|
||||
collapseAtStart(): true {
|
||||
const newElement = !this.isEmpty()
|
||||
? $createHeadingNode(this.getTag())
|
||||
: $createParagraphNode();
|
||||
const children = this.getChildren();
|
||||
children.forEach((child) => newElement.append(child));
|
||||
this.replace(newElement);
|
||||
return true;
|
||||
}
|
||||
|
||||
extractWithChild(): boolean {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
function isGoogleDocsTitle(domNode: Node): boolean {
|
||||
if (domNode.nodeName.toLowerCase() === 'span') {
|
||||
return (domNode as HTMLSpanElement).style.fontSize === '26pt';
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function $convertHeadingElement(element: HTMLElement): DOMConversionOutput {
|
||||
const nodeName = element.nodeName.toLowerCase();
|
||||
let node = null;
|
||||
if (
|
||||
nodeName === 'h1' ||
|
||||
nodeName === 'h2' ||
|
||||
nodeName === 'h3' ||
|
||||
nodeName === 'h4' ||
|
||||
nodeName === 'h5' ||
|
||||
nodeName === 'h6'
|
||||
) {
|
||||
node = $createHeadingNode(nodeName);
|
||||
}
|
||||
return {node};
|
||||
}
|
||||
|
||||
function $convertBlockquoteElement(element: HTMLElement): DOMConversionOutput {
|
||||
const node = $createQuoteNode();
|
||||
return {node};
|
||||
}
|
||||
|
||||
export function $createHeadingNode(headingTag: HeadingTagType): HeadingNode {
|
||||
return $applyNodeReplacement(new HeadingNode(headingTag));
|
||||
}
|
||||
|
||||
export function $isHeadingNode(
|
||||
node: LexicalNode | null | undefined,
|
||||
): node is HeadingNode {
|
||||
return node instanceof HeadingNode;
|
||||
}
|
||||
|
||||
function onPasteForRichText(
|
||||
event: CommandPayloadType<typeof PASTE_COMMAND>,
|
||||
|
@ -8,7 +8,7 @@
|
||||
|
||||
import {$createLinkNode} from '@lexical/link';
|
||||
import {$createListItemNode, $createListNode} from '@lexical/list';
|
||||
import {$createHeadingNode, registerRichText} from '@lexical/rich-text';
|
||||
import {registerRichText} from '@lexical/rich-text';
|
||||
import {
|
||||
$addNodeStyle,
|
||||
$getSelectionStyleValueForProperty,
|
||||
@ -74,6 +74,7 @@ import {
|
||||
} from '../utils';
|
||||
import {createEmptyHistoryState, registerHistory} from "@lexical/history";
|
||||
import {mergeRegister} from "@lexical/utils";
|
||||
import {$createHeadingNode} from "@lexical/rich-text/LexicalHeadingNode";
|
||||
|
||||
interface ExpectedSelection {
|
||||
anchorPath: number[];
|
||||
|
@ -7,7 +7,6 @@
|
||||
*/
|
||||
|
||||
import {$createLinkNode} from '@lexical/link';
|
||||
import {$createHeadingNode, $isHeadingNode} from '@lexical/rich-text';
|
||||
import {
|
||||
$getSelectionStyleValueForProperty,
|
||||
$patchStyleText,
|
||||
@ -44,6 +43,7 @@ import {
|
||||
} from 'lexical/__tests__/utils';
|
||||
|
||||
import {$setAnchorPoint, $setFocusPoint} from '../utils';
|
||||
import {$createHeadingNode, $isHeadingNode} from "@lexical/rich-text/LexicalHeadingNode";
|
||||
|
||||
Range.prototype.getBoundingClientRect = function (): DOMRect {
|
||||
const rect = {
|
||||
|
@ -7,7 +7,7 @@
|
||||
*/
|
||||
import {AutoLinkNode, LinkNode} from '@lexical/link';
|
||||
import {ListItemNode, ListNode} from '@lexical/list';
|
||||
import {HeadingNode, QuoteNode, registerRichText} from '@lexical/rich-text';
|
||||
import {registerRichText} from '@lexical/rich-text';
|
||||
import {
|
||||
applySelectionInputs,
|
||||
pasteHTML,
|
||||
@ -15,6 +15,8 @@ import {
|
||||
import {TableCellNode, TableNode, TableRowNode} from '@lexical/table';
|
||||
import {$createParagraphNode, $insertNodes, LexicalEditor} from 'lexical';
|
||||
import {createTestEditor, initializeClipboard} from 'lexical/__tests__/utils';
|
||||
import {HeadingNode} from "@lexical/rich-text/LexicalHeadingNode";
|
||||
import {QuoteNode} from "@lexical/rich-text/LexicalQuoteNode";
|
||||
|
||||
jest.mock('lexical/shared/environment', () => {
|
||||
const originalModule = jest.requireActual('lexical/shared/environment');
|
||||
|
@ -1,146 +0,0 @@
|
||||
import {
|
||||
DOMConversionMap,
|
||||
DOMConversionOutput,
|
||||
LexicalNode,
|
||||
Spread
|
||||
} from "lexical";
|
||||
import {EditorConfig} from "lexical/LexicalEditor";
|
||||
import {HeadingNode, HeadingTagType, SerializedHeadingNode} from "@lexical/rich-text";
|
||||
import {
|
||||
CommonBlockAlignment, commonPropertiesDifferent, deserializeCommonBlockNode,
|
||||
SerializedCommonBlockNode,
|
||||
setCommonBlockPropsFromElement,
|
||||
updateElementWithCommonBlockProps
|
||||
} from "./_common";
|
||||
|
||||
|
||||
export type SerializedCustomHeadingNode = Spread<SerializedCommonBlockNode, SerializedHeadingNode>
|
||||
|
||||
export class CustomHeadingNode extends HeadingNode {
|
||||
__id: string = '';
|
||||
__alignment: CommonBlockAlignment = '';
|
||||
__inset: number = 0;
|
||||
|
||||
static getType() {
|
||||
return 'custom-heading';
|
||||
}
|
||||
|
||||
setId(id: string) {
|
||||
const self = this.getWritable();
|
||||
self.__id = id;
|
||||
}
|
||||
|
||||
getId(): string {
|
||||
const self = this.getLatest();
|
||||
return self.__id;
|
||||
}
|
||||
|
||||
setAlignment(alignment: CommonBlockAlignment) {
|
||||
const self = this.getWritable();
|
||||
self.__alignment = alignment;
|
||||
}
|
||||
|
||||
getAlignment(): CommonBlockAlignment {
|
||||
const self = this.getLatest();
|
||||
return self.__alignment;
|
||||
}
|
||||
|
||||
setInset(size: number) {
|
||||
const self = this.getWritable();
|
||||
self.__inset = size;
|
||||
}
|
||||
|
||||
getInset(): number {
|
||||
const self = this.getLatest();
|
||||
return self.__inset;
|
||||
}
|
||||
|
||||
static clone(node: CustomHeadingNode) {
|
||||
const newNode = new CustomHeadingNode(node.__tag, node.__key);
|
||||
newNode.__alignment = node.__alignment;
|
||||
newNode.__inset = node.__inset;
|
||||
return newNode;
|
||||
}
|
||||
|
||||
createDOM(config: EditorConfig): HTMLElement {
|
||||
const dom = super.createDOM(config);
|
||||
updateElementWithCommonBlockProps(dom, this);
|
||||
return dom;
|
||||
}
|
||||
|
||||
updateDOM(prevNode: CustomHeadingNode, dom: HTMLElement): boolean {
|
||||
return super.updateDOM(prevNode, dom)
|
||||
|| commonPropertiesDifferent(prevNode, this);
|
||||
}
|
||||
|
||||
exportJSON(): SerializedCustomHeadingNode {
|
||||
return {
|
||||
...super.exportJSON(),
|
||||
type: 'custom-heading',
|
||||
version: 1,
|
||||
id: this.__id,
|
||||
alignment: this.__alignment,
|
||||
inset: this.__inset,
|
||||
};
|
||||
}
|
||||
|
||||
static importJSON(serializedNode: SerializedCustomHeadingNode): CustomHeadingNode {
|
||||
const node = $createCustomHeadingNode(serializedNode.tag);
|
||||
deserializeCommonBlockNode(serializedNode, node);
|
||||
return node;
|
||||
}
|
||||
|
||||
static importDOM(): DOMConversionMap | null {
|
||||
return {
|
||||
h1: (node: Node) => ({
|
||||
conversion: $convertHeadingElement,
|
||||
priority: 0,
|
||||
}),
|
||||
h2: (node: Node) => ({
|
||||
conversion: $convertHeadingElement,
|
||||
priority: 0,
|
||||
}),
|
||||
h3: (node: Node) => ({
|
||||
conversion: $convertHeadingElement,
|
||||
priority: 0,
|
||||
}),
|
||||
h4: (node: Node) => ({
|
||||
conversion: $convertHeadingElement,
|
||||
priority: 0,
|
||||
}),
|
||||
h5: (node: Node) => ({
|
||||
conversion: $convertHeadingElement,
|
||||
priority: 0,
|
||||
}),
|
||||
h6: (node: Node) => ({
|
||||
conversion: $convertHeadingElement,
|
||||
priority: 0,
|
||||
}),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function $convertHeadingElement(element: HTMLElement): DOMConversionOutput {
|
||||
const nodeName = element.nodeName.toLowerCase();
|
||||
let node = null;
|
||||
if (
|
||||
nodeName === 'h1' ||
|
||||
nodeName === 'h2' ||
|
||||
nodeName === 'h3' ||
|
||||
nodeName === 'h4' ||
|
||||
nodeName === 'h5' ||
|
||||
nodeName === 'h6'
|
||||
) {
|
||||
node = $createCustomHeadingNode(nodeName);
|
||||
setCommonBlockPropsFromElement(element, node);
|
||||
}
|
||||
return {node};
|
||||
}
|
||||
|
||||
export function $createCustomHeadingNode(tag: HeadingTagType) {
|
||||
return new CustomHeadingNode(tag);
|
||||
}
|
||||
|
||||
export function $isCustomHeadingNode(node: LexicalNode | null | undefined): node is CustomHeadingNode {
|
||||
return node instanceof CustomHeadingNode;
|
||||
}
|
@ -1,115 +0,0 @@
|
||||
import {
|
||||
DOMConversionMap,
|
||||
DOMConversionOutput,
|
||||
LexicalNode,
|
||||
Spread
|
||||
} from "lexical";
|
||||
import {EditorConfig} from "lexical/LexicalEditor";
|
||||
import {QuoteNode, SerializedQuoteNode} from "@lexical/rich-text";
|
||||
import {
|
||||
CommonBlockAlignment, commonPropertiesDifferent, deserializeCommonBlockNode,
|
||||
SerializedCommonBlockNode,
|
||||
setCommonBlockPropsFromElement,
|
||||
updateElementWithCommonBlockProps
|
||||
} from "./_common";
|
||||
|
||||
|
||||
export type SerializedCustomQuoteNode = Spread<SerializedCommonBlockNode, SerializedQuoteNode>
|
||||
|
||||
export class CustomQuoteNode extends QuoteNode {
|
||||
__id: string = '';
|
||||
__alignment: CommonBlockAlignment = '';
|
||||
__inset: number = 0;
|
||||
|
||||
static getType() {
|
||||
return 'custom-quote';
|
||||
}
|
||||
|
||||
setId(id: string) {
|
||||
const self = this.getWritable();
|
||||
self.__id = id;
|
||||
}
|
||||
|
||||
getId(): string {
|
||||
const self = this.getLatest();
|
||||
return self.__id;
|
||||
}
|
||||
|
||||
setAlignment(alignment: CommonBlockAlignment) {
|
||||
const self = this.getWritable();
|
||||
self.__alignment = alignment;
|
||||
}
|
||||
|
||||
getAlignment(): CommonBlockAlignment {
|
||||
const self = this.getLatest();
|
||||
return self.__alignment;
|
||||
}
|
||||
|
||||
setInset(size: number) {
|
||||
const self = this.getWritable();
|
||||
self.__inset = size;
|
||||
}
|
||||
|
||||
getInset(): number {
|
||||
const self = this.getLatest();
|
||||
return self.__inset;
|
||||
}
|
||||
|
||||
static clone(node: CustomQuoteNode) {
|
||||
const newNode = new CustomQuoteNode(node.__key);
|
||||
newNode.__id = node.__id;
|
||||
newNode.__alignment = node.__alignment;
|
||||
newNode.__inset = node.__inset;
|
||||
return newNode;
|
||||
}
|
||||
|
||||
createDOM(config: EditorConfig): HTMLElement {
|
||||
const dom = super.createDOM(config);
|
||||
updateElementWithCommonBlockProps(dom, this);
|
||||
return dom;
|
||||
}
|
||||
|
||||
updateDOM(prevNode: CustomQuoteNode): boolean {
|
||||
return commonPropertiesDifferent(prevNode, this);
|
||||
}
|
||||
|
||||
exportJSON(): SerializedCustomQuoteNode {
|
||||
return {
|
||||
...super.exportJSON(),
|
||||
type: 'custom-quote',
|
||||
version: 1,
|
||||
id: this.__id,
|
||||
alignment: this.__alignment,
|
||||
inset: this.__inset,
|
||||
};
|
||||
}
|
||||
|
||||
static importJSON(serializedNode: SerializedCustomQuoteNode): CustomQuoteNode {
|
||||
const node = $createCustomQuoteNode();
|
||||
deserializeCommonBlockNode(serializedNode, node);
|
||||
return node;
|
||||
}
|
||||
|
||||
static importDOM(): DOMConversionMap | null {
|
||||
return {
|
||||
blockquote: (node: Node) => ({
|
||||
conversion: $convertBlockquoteElement,
|
||||
priority: 0,
|
||||
}),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function $convertBlockquoteElement(element: HTMLElement): DOMConversionOutput {
|
||||
const node = $createCustomQuoteNode();
|
||||
setCommonBlockPropsFromElement(element, node);
|
||||
return {node};
|
||||
}
|
||||
|
||||
export function $createCustomQuoteNode() {
|
||||
return new CustomQuoteNode();
|
||||
}
|
||||
|
||||
export function $isCustomQuoteNode(node: LexicalNode | null | undefined): node is CustomQuoteNode {
|
||||
return node instanceof CustomQuoteNode;
|
||||
}
|
@ -1,4 +1,3 @@
|
||||
import {HeadingNode, QuoteNode} from '@lexical/rich-text';
|
||||
import {CalloutNode} from './callout';
|
||||
import {
|
||||
ElementNode,
|
||||
@ -21,9 +20,9 @@ import {MediaNode} from "./media";
|
||||
import {CustomListItemNode} from "./custom-list-item";
|
||||
import {CustomTableCellNode} from "./custom-table-cell";
|
||||
import {CustomTableRowNode} from "./custom-table-row";
|
||||
import {CustomHeadingNode} from "./custom-heading";
|
||||
import {CustomQuoteNode} from "./custom-quote";
|
||||
import {CustomListNode} from "./custom-list";
|
||||
import {HeadingNode} from "@lexical/rich-text/LexicalHeadingNode";
|
||||
import {QuoteNode} from "@lexical/rich-text/LexicalQuoteNode";
|
||||
|
||||
/**
|
||||
* Load the nodes for lexical.
|
||||
@ -31,8 +30,8 @@ import {CustomListNode} from "./custom-list";
|
||||
export function getNodesForPageEditor(): (KlassConstructor<typeof LexicalNode> | LexicalNodeReplacement)[] {
|
||||
return [
|
||||
CalloutNode,
|
||||
CustomHeadingNode,
|
||||
CustomQuoteNode,
|
||||
HeadingNode,
|
||||
QuoteNode,
|
||||
CustomListNode,
|
||||
CustomListItemNode, // TODO - Alignment?
|
||||
CustomTableNode,
|
||||
@ -46,18 +45,6 @@ export function getNodesForPageEditor(): (KlassConstructor<typeof LexicalNode> |
|
||||
MediaNode, // TODO - Alignment
|
||||
ParagraphNode,
|
||||
LinkNode,
|
||||
{
|
||||
replace: HeadingNode,
|
||||
with: (node: HeadingNode) => {
|
||||
return new CustomHeadingNode(node.__tag);
|
||||
}
|
||||
},
|
||||
{
|
||||
replace: QuoteNode,
|
||||
with: (node: QuoteNode) => {
|
||||
return new CustomQuoteNode();
|
||||
}
|
||||
},
|
||||
{
|
||||
replace: ListNode,
|
||||
with: (node: ListNode) => {
|
||||
|
@ -6,12 +6,12 @@ import {
|
||||
toggleSelectionAsHeading, toggleSelectionAsList,
|
||||
toggleSelectionAsParagraph
|
||||
} from "../utils/formats";
|
||||
import {HeadingTagType} from "@lexical/rich-text";
|
||||
import {EditorUiContext} from "../ui/framework/core";
|
||||
import {$getNodeFromSelection} from "../utils/selection";
|
||||
import {$isLinkNode, LinkNode} from "@lexical/link";
|
||||
import {$showLinkForm} from "../ui/defaults/forms/objects";
|
||||
import {showLinkSelector} from "../utils/links";
|
||||
import {HeadingTagType} from "@lexical/rich-text/LexicalHeadingNode";
|
||||
|
||||
function headerHandler(editor: LexicalEditor, tag: HeadingTagType): boolean {
|
||||
toggleSelectionAsHeading(editor, tag);
|
||||
|
@ -2,18 +2,14 @@ import {$createCalloutNode, $isCalloutNodeOfCategory, CalloutCategory} from "../
|
||||
import {EditorButtonDefinition} from "../../framework/buttons";
|
||||
import {EditorUiContext} from "../../framework/core";
|
||||
import {$isParagraphNode, BaseSelection, LexicalNode} from "lexical";
|
||||
import {
|
||||
$isHeadingNode,
|
||||
$isQuoteNode,
|
||||
HeadingNode,
|
||||
HeadingTagType
|
||||
} from "@lexical/rich-text";
|
||||
import {$selectionContainsNodeType, $toggleSelectionBlockNodeType} from "../../../utils/selection";
|
||||
import {
|
||||
toggleSelectionAsBlockquote,
|
||||
toggleSelectionAsHeading,
|
||||
toggleSelectionAsParagraph
|
||||
} from "../../../utils/formats";
|
||||
import {$isHeadingNode, HeadingNode, HeadingTagType} from "@lexical/rich-text/LexicalHeadingNode";
|
||||
import {$isQuoteNode} from "@lexical/rich-text/LexicalQuoteNode";
|
||||
|
||||
function buildCalloutButton(category: CalloutCategory, name: string): EditorButtonDefinition {
|
||||
return {
|
||||
|
@ -1,14 +1,13 @@
|
||||
import {EditorContainerUiElement} from "../core";
|
||||
import {el} from "../../../utils/dom";
|
||||
import {EditorFormField} from "../forms";
|
||||
import {CustomHeadingNode} from "../../../nodes/custom-heading";
|
||||
import {$getAllNodesOfType} from "../../../utils/nodes";
|
||||
import {$isHeadingNode} from "@lexical/rich-text";
|
||||
import {uniqueIdSmall} from "../../../../services/util";
|
||||
import {$isHeadingNode, HeadingNode} from "@lexical/rich-text/LexicalHeadingNode";
|
||||
|
||||
export class LinkField extends EditorContainerUiElement {
|
||||
protected input: EditorFormField;
|
||||
protected headerMap = new Map<string, CustomHeadingNode>();
|
||||
protected headerMap = new Map<string, HeadingNode>();
|
||||
|
||||
constructor(input: EditorFormField) {
|
||||
super([input]);
|
||||
@ -43,7 +42,7 @@ export class LinkField extends EditorContainerUiElement {
|
||||
return container;
|
||||
}
|
||||
|
||||
updateFormFromHeader(header: CustomHeadingNode) {
|
||||
updateFormFromHeader(header: HeadingNode) {
|
||||
this.getHeaderIdAndText(header).then(({id, text}) => {
|
||||
console.log('updating form', id, text);
|
||||
const modal = this.getContext().manager.getActiveModal('link');
|
||||
@ -57,7 +56,7 @@ export class LinkField extends EditorContainerUiElement {
|
||||
});
|
||||
}
|
||||
|
||||
getHeaderIdAndText(header: CustomHeadingNode): Promise<{id: string, text: string}> {
|
||||
getHeaderIdAndText(header: HeadingNode): Promise<{id: string, text: string}> {
|
||||
return new Promise((res) => {
|
||||
this.getContext().editor.update(() => {
|
||||
let id = header.getId();
|
||||
@ -75,7 +74,7 @@ export class LinkField extends EditorContainerUiElement {
|
||||
|
||||
updateDataList(listEl: HTMLElement) {
|
||||
this.getContext().editor.getEditorState().read(() => {
|
||||
const headers = $getAllNodesOfType($isHeadingNode) as CustomHeadingNode[];
|
||||
const headers = $getAllNodesOfType($isHeadingNode) as HeadingNode[];
|
||||
|
||||
this.headerMap.clear();
|
||||
const listEls: HTMLElement[] = [];
|
||||
|
@ -1,4 +1,3 @@
|
||||
import {$isQuoteNode, HeadingNode, HeadingTagType} from "@lexical/rich-text";
|
||||
import {
|
||||
$createParagraphNode,
|
||||
$createTextNode,
|
||||
@ -15,23 +14,23 @@ import {
|
||||
$toggleSelectionBlockNodeType,
|
||||
getLastSelection
|
||||
} from "./selection";
|
||||
import {$createCustomHeadingNode, $isCustomHeadingNode} from "../nodes/custom-heading";
|
||||
import {$createCustomQuoteNode} from "../nodes/custom-quote";
|
||||
import {$createCodeBlockNode, $isCodeBlockNode, $openCodeEditorForNode, CodeBlockNode} from "../nodes/code-block";
|
||||
import {$createCalloutNode, $isCalloutNode, CalloutCategory} from "../nodes/callout";
|
||||
import {insertList, ListNode, ListType, removeList} from "@lexical/list";
|
||||
import {$isCustomListNode} from "../nodes/custom-list";
|
||||
import {$createLinkNode, $isLinkNode} from "@lexical/link";
|
||||
import {$createHeadingNode, $isHeadingNode, HeadingTagType} from "@lexical/rich-text/LexicalHeadingNode";
|
||||
import {$createQuoteNode, $isQuoteNode} from "@lexical/rich-text/LexicalQuoteNode";
|
||||
|
||||
const $isHeaderNodeOfTag = (node: LexicalNode | null | undefined, tag: HeadingTagType) => {
|
||||
return $isCustomHeadingNode(node) && (node as HeadingNode).getTag() === tag;
|
||||
return $isHeadingNode(node) && node.getTag() === tag;
|
||||
};
|
||||
|
||||
export function toggleSelectionAsHeading(editor: LexicalEditor, tag: HeadingTagType) {
|
||||
editor.update(() => {
|
||||
$toggleSelectionBlockNodeType(
|
||||
(node) => $isHeaderNodeOfTag(node, tag),
|
||||
() => $createCustomHeadingNode(tag),
|
||||
() => $createHeadingNode(tag),
|
||||
)
|
||||
});
|
||||
}
|
||||
@ -44,7 +43,7 @@ export function toggleSelectionAsParagraph(editor: LexicalEditor) {
|
||||
|
||||
export function toggleSelectionAsBlockquote(editor: LexicalEditor) {
|
||||
editor.update(() => {
|
||||
$toggleSelectionBlockNodeType($isQuoteNode, $createCustomQuoteNode);
|
||||
$toggleSelectionBlockNodeType($isQuoteNode, $createQuoteNode);
|
||||
});
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user