diff --git a/resources/js/wysiwyg/index.ts b/resources/js/wysiwyg/index.ts index 9da646a77..a07fbd789 100644 --- a/resources/js/wysiwyg/index.ts +++ b/resources/js/wysiwyg/index.ts @@ -82,6 +82,11 @@ export function createPageEditorInstance(container: HTMLElement, htmlContent: st } }); + // @ts-ignore + window.debugEditorState = () => { + console.log(editor.getEditorState().toJSON()); + }; + const context: EditorUiContext = buildEditorUI(container, editArea, editWrap, editor, options); registerCommonNodeMutationListeners(context); diff --git a/resources/js/wysiwyg/nodes/callout.ts b/resources/js/wysiwyg/nodes/callout.ts index e39dcc3ee..b720b5c43 100644 --- a/resources/js/wysiwyg/nodes/callout.ts +++ b/resources/js/wysiwyg/nodes/callout.ts @@ -9,15 +9,17 @@ import { } from 'lexical'; import type {EditorConfig} from "lexical/LexicalEditor"; import type {RangeSelection} from "lexical/LexicalSelection"; +import {el} from "../utils/dom"; export type CalloutCategory = 'info' | 'danger' | 'warning' | 'success'; export type SerializedCalloutNode = Spread<{ category: CalloutCategory; + id: string; }, SerializedElementNode> export class CalloutNode extends ElementNode { - + __id: string = ''; __category: CalloutCategory = 'info'; static getType() { @@ -25,7 +27,9 @@ export class CalloutNode extends ElementNode { } static clone(node: CalloutNode) { - return new CalloutNode(node.__category, node.__key); + const newNode = new CalloutNode(node.__category, node.__key); + newNode.__id = node.__id; + return newNode; } constructor(category: CalloutCategory, key?: string) { @@ -43,9 +47,22 @@ export class CalloutNode extends ElementNode { return self.__category; } + setId(id: string) { + const self = this.getWritable(); + self.__id = id; + } + + getId(): string { + const self = this.getLatest(); + return self.__id; + } + createDOM(_config: EditorConfig, _editor: LexicalEditor) { const element = document.createElement('p'); element.classList.add('callout', this.__category || ''); + if (this.__id) { + element.setAttribute('id', this.__id); + } return element; } @@ -88,8 +105,13 @@ export class CalloutNode extends ElementNode { } } + const node = new CalloutNode(category); + if (element.id) { + node.setId(element.id); + } + return { - node: new CalloutNode(category), + node, }; }, priority: 3, @@ -106,11 +128,14 @@ export class CalloutNode extends ElementNode { type: 'callout', version: 1, category: this.__category, + id: this.__id, }; } static importJSON(serializedNode: SerializedCalloutNode): CalloutNode { - return $createCalloutNode(serializedNode.category); + const node = $createCalloutNode(serializedNode.category); + node.setId(serializedNode.id); + return node; } } @@ -119,7 +144,7 @@ export function $createCalloutNode(category: CalloutCategory = 'info') { return new CalloutNode(category); } -export function $isCalloutNode(node: LexicalNode | null | undefined) { +export function $isCalloutNode(node: LexicalNode | null | undefined): node is CalloutNode { return node instanceof CalloutNode; } diff --git a/resources/js/wysiwyg/nodes/code-block.ts b/resources/js/wysiwyg/nodes/code-block.ts index e240a3887..a71e21e2e 100644 --- a/resources/js/wysiwyg/nodes/code-block.ts +++ b/resources/js/wysiwyg/nodes/code-block.ts @@ -2,7 +2,7 @@ import { DecoratorNode, DOMConversion, DOMConversionMap, - DOMConversionOutput, + DOMConversionOutput, DOMExportOutput, LexicalEditor, LexicalNode, SerializedLexicalNode, Spread @@ -33,7 +33,9 @@ export class CodeBlockNode extends DecoratorNode { } static clone(node: CodeBlockNode): CodeBlockNode { - return new CodeBlockNode(node.__language, node.__code); + const newNode = new CodeBlockNode(node.__language, node.__code); + newNode.__id = node.__id; + return newNode; } constructor(language: string = '', code: string = '', key?: string) { @@ -118,6 +120,13 @@ export class CodeBlockNode extends DecoratorNode { return false; } + exportDOM(editor: LexicalEditor): DOMExportOutput { + const dom = this.createDOM(editor._config, editor); + return { + element: dom.querySelector('pre') as HTMLElement, + }; + } + static importDOM(): DOMConversionMap|null { return { pre(node: HTMLElement): DOMConversion|null { @@ -130,10 +139,13 @@ export class CodeBlockNode extends DecoratorNode { || ''; const code = codeEl ? (codeEl.textContent || '').trim() : (element.textContent || '').trim(); + const node = $createCodeBlockNode(language, code); - return { - node: $createCodeBlockNode(language, code), - }; + if (element.id) { + node.setId(element.id); + } + + return { node }; }, priority: 3, }; diff --git a/resources/js/wysiwyg/nodes/custom-heading.ts b/resources/js/wysiwyg/nodes/custom-heading.ts new file mode 100644 index 000000000..dba49898c --- /dev/null +++ b/resources/js/wysiwyg/nodes/custom-heading.ts @@ -0,0 +1,120 @@ +import { + DOMConversionMap, + DOMConversionOutput, ElementFormatType, + LexicalNode, + Spread +} from "lexical"; +import {EditorConfig} from "lexical/LexicalEditor"; +import {HeadingNode, HeadingTagType, SerializedHeadingNode} from "@lexical/rich-text"; + + +export type SerializedCustomHeadingNode = Spread<{ + id: string; +}, SerializedHeadingNode> + +export class CustomHeadingNode extends HeadingNode { + __id: string = ''; + + static getType() { + return 'custom-heading'; + } + + setId(id: string) { + const self = this.getWritable(); + self.__id = id; + } + + getId(): string { + const self = this.getLatest(); + return self.__id; + } + + static clone(node: CustomHeadingNode) { + const newNode = new CustomHeadingNode(node.__tag, node.__key); + newNode.__id = node.__id; + return newNode; + } + + createDOM(config: EditorConfig): HTMLElement { + const dom = super.createDOM(config); + if (this.__id) { + dom.setAttribute('id', this.__id); + } + + return dom; + } + + exportJSON(): SerializedCustomHeadingNode { + return { + ...super.exportJSON(), + type: 'custom-heading', + version: 1, + id: this.__id, + }; + } + + static importJSON(serializedNode: SerializedCustomHeadingNode): CustomHeadingNode { + const node = $createCustomHeadingNode(serializedNode.tag); + node.setId(serializedNode.id); + 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); + if (element.style !== null) { + node.setFormat(element.style.textAlign as ElementFormatType); + } + if (element.id) { + node.setId(element.id); + } + } + 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; +} \ No newline at end of file diff --git a/resources/js/wysiwyg/nodes/custom-list.ts b/resources/js/wysiwyg/nodes/custom-list.ts new file mode 100644 index 000000000..953bcb8cd --- /dev/null +++ b/resources/js/wysiwyg/nodes/custom-list.ts @@ -0,0 +1,92 @@ +import { + DOMConversionFn, + DOMConversionMap, + LexicalNode, + Spread +} from "lexical"; +import {EditorConfig} from "lexical/LexicalEditor"; +import {ListNode, ListType, SerializedListNode} from "@lexical/list"; + + +export type SerializedCustomListNode = Spread<{ + id: string; +}, SerializedListNode> + +export class CustomListNode extends ListNode { + __id: string = ''; + + static getType() { + return 'custom-list'; + } + + setId(id: string) { + const self = this.getWritable(); + self.__id = id; + } + + getId(): string { + const self = this.getLatest(); + return self.__id; + } + + static clone(node: CustomListNode) { + const newNode = new CustomListNode(node.__listType, 0, node.__key); + newNode.__id = node.__id; + return newNode; + } + + createDOM(config: EditorConfig): HTMLElement { + const dom = super.createDOM(config); + if (this.__id) { + dom.setAttribute('id', this.__id); + } + + return dom; + } + + exportJSON(): SerializedCustomListNode { + return { + ...super.exportJSON(), + type: 'custom-list', + version: 1, + id: this.__id, + }; + } + + static importJSON(serializedNode: SerializedCustomListNode): CustomListNode { + const node = $createCustomListNode(serializedNode.listType); + node.setId(serializedNode.id); + return node; + } + + static importDOM(): DOMConversionMap | null { + // @ts-ignore + const converter = super.importDOM().ol().conversion as DOMConversionFn; + const customConvertFunction = (element: HTMLElement) => { + const baseResult = converter(element); + if (element.id && baseResult?.node) { + (baseResult.node as CustomListNode).setId(element.id); + } + return baseResult; + }; + + return { + ol: () => ({ + conversion: customConvertFunction, + priority: 0, + }), + ul: () => ({ + conversion: customConvertFunction, + priority: 0, + }), + }; + } +} + +export function $createCustomListNode(type: ListType): CustomListNode { + return new CustomListNode(type, 0); +} + +export function $isCustomListNode(node: LexicalNode | null | undefined): node is CustomListNode { + return node instanceof CustomListNode; +} \ No newline at end of file diff --git a/resources/js/wysiwyg/nodes/custom-paragraph.ts b/resources/js/wysiwyg/nodes/custom-paragraph.ts index 97647bf5e..cb936a559 100644 --- a/resources/js/wysiwyg/nodes/custom-paragraph.ts +++ b/resources/js/wysiwyg/nodes/custom-paragraph.ts @@ -31,7 +31,7 @@ export class CustomParagraphNode extends ParagraphNode { return self.__id; } - static clone(node: CustomParagraphNode) { + static clone(node: CustomParagraphNode): CustomParagraphNode { const newNode = new CustomParagraphNode(node.__key); newNode.__id = node.__id; return newNode; @@ -39,9 +39,8 @@ export class CustomParagraphNode extends ParagraphNode { createDOM(config: EditorConfig): HTMLElement { const dom = super.createDOM(config); - const id = this.getId(); - if (id) { - dom.setAttribute('id', id); + if (this.__id) { + dom.setAttribute('id', this.__id); } return dom; @@ -89,7 +88,7 @@ export class CustomParagraphNode extends ParagraphNode { } } -export function $createCustomParagraphNode() { +export function $createCustomParagraphNode(): CustomParagraphNode { return new CustomParagraphNode(); } diff --git a/resources/js/wysiwyg/nodes/custom-quote.ts b/resources/js/wysiwyg/nodes/custom-quote.ts new file mode 100644 index 000000000..58c62f769 --- /dev/null +++ b/resources/js/wysiwyg/nodes/custom-quote.ts @@ -0,0 +1,89 @@ +import { + DOMConversionMap, + DOMConversionOutput, ElementFormatType, + LexicalNode, + Spread +} from "lexical"; +import {EditorConfig} from "lexical/LexicalEditor"; +import {QuoteNode, SerializedQuoteNode} from "@lexical/rich-text"; + + +export type SerializedCustomQuoteNode = Spread<{ + id: string; +}, SerializedQuoteNode> + +export class CustomQuoteNode extends QuoteNode { + __id: string = ''; + + static getType() { + return 'custom-quote'; + } + + setId(id: string) { + const self = this.getWritable(); + self.__id = id; + } + + getId(): string { + const self = this.getLatest(); + return self.__id; + } + + static clone(node: CustomQuoteNode) { + const newNode = new CustomQuoteNode(node.__key); + newNode.__id = node.__id; + return newNode; + } + + createDOM(config: EditorConfig): HTMLElement { + const dom = super.createDOM(config); + if (this.__id) { + dom.setAttribute('id', this.__id); + } + + return dom; + } + + exportJSON(): SerializedCustomQuoteNode { + return { + ...super.exportJSON(), + type: 'custom-quote', + version: 1, + id: this.__id, + }; + } + + static importJSON(serializedNode: SerializedCustomQuoteNode): CustomQuoteNode { + const node = $createCustomQuoteNode(); + node.setId(serializedNode.id); + return node; + } + + static importDOM(): DOMConversionMap | null { + return { + blockquote: (node: Node) => ({ + conversion: $convertBlockquoteElement, + priority: 0, + }), + }; + } +} + +function $convertBlockquoteElement(element: HTMLElement): DOMConversionOutput { + const node = $createCustomQuoteNode(); + if (element.style !== null) { + node.setFormat(element.style.textAlign as ElementFormatType); + } + if (element.id) { + node.setId(element.id); + } + return {node}; +} + +export function $createCustomQuoteNode() { + return new CustomQuoteNode(); +} + +export function $isCustomQuoteNode(node: LexicalNode | null | undefined): node is CustomQuoteNode { + return node instanceof CustomQuoteNode; +} \ No newline at end of file diff --git a/resources/js/wysiwyg/nodes/details.ts b/resources/js/wysiwyg/nodes/details.ts index 8071d5e8f..119619da6 100644 --- a/resources/js/wysiwyg/nodes/details.ts +++ b/resources/js/wysiwyg/nodes/details.ts @@ -4,28 +4,50 @@ import { ElementNode, LexicalEditor, LexicalNode, - SerializedElementNode, + SerializedElementNode, Spread, } from 'lexical'; import type {EditorConfig} from "lexical/LexicalEditor"; import {el} from "../utils/dom"; +export type SerializedDetailsNode = Spread<{ + id: string; +}, SerializedElementNode> + export class DetailsNode extends ElementNode { + __id: string = ''; static getType() { return 'details'; } - static clone(node: DetailsNode) { - return new DetailsNode(node.__key); + setId(id: string) { + const self = this.getWritable(); + self.__id = id; + } + + getId(): string { + const self = this.getLatest(); + return self.__id; + } + + static clone(node: DetailsNode): DetailsNode { + const newNode = new DetailsNode(node.__key); + newNode.__id = node.__id; + return newNode; } createDOM(_config: EditorConfig, _editor: LexicalEditor) { - return el('details'); + const el = document.createElement('details'); + if (this.__id) { + el.setAttribute('id', this.__id); + } + + return el; } updateDOM(prevNode: DetailsNode, dom: HTMLElement) { - return false; + return prevNode.__id !== this.__id; } static importDOM(): DOMConversionMap|null { @@ -33,9 +55,12 @@ export class DetailsNode extends ElementNode { details(node: HTMLElement): DOMConversion|null { return { conversion: (element: HTMLElement): DOMConversionOutput|null => { - return { - node: new DetailsNode(), - }; + const node = new DetailsNode(); + if (element.id) { + node.setId(element.id); + } + + return {node}; }, priority: 3, }; @@ -43,16 +68,19 @@ export class DetailsNode extends ElementNode { }; } - exportJSON(): SerializedElementNode { + exportJSON(): SerializedDetailsNode { return { ...super.exportJSON(), type: 'details', version: 1, + id: this.__id, }; } - static importJSON(serializedNode: SerializedElementNode): DetailsNode { - return $createDetailsNode(); + static importJSON(serializedNode: SerializedDetailsNode): DetailsNode { + const node = $createDetailsNode(); + node.setId(serializedNode.id); + return node; } } @@ -61,7 +89,7 @@ export function $createDetailsNode() { return new DetailsNode(); } -export function $isDetailsNode(node: LexicalNode | null | undefined) { +export function $isDetailsNode(node: LexicalNode | null | undefined): node is DetailsNode { return node instanceof DetailsNode; } @@ -106,16 +134,16 @@ export class SummaryNode extends ElementNode { }; } - static importJSON(serializedNode: SerializedElementNode): DetailsNode { + static importJSON(serializedNode: SerializedElementNode): SummaryNode { return $createSummaryNode(); } } -export function $createSummaryNode() { +export function $createSummaryNode(): SummaryNode { return new SummaryNode(); } -export function $isSummaryNode(node: LexicalNode | null | undefined) { +export function $isSummaryNode(node: LexicalNode | null | undefined): node is SummaryNode { return node instanceof SummaryNode; } diff --git a/resources/js/wysiwyg/nodes/diagram.ts b/resources/js/wysiwyg/nodes/diagram.ts index 76d939248..e2ffeaadd 100644 --- a/resources/js/wysiwyg/nodes/diagram.ts +++ b/resources/js/wysiwyg/nodes/diagram.ts @@ -30,7 +30,9 @@ export class DiagramNode extends DecoratorNode { } static clone(node: DiagramNode): DiagramNode { - return new DiagramNode(node.__drawingId, node.__drawingUrl); + const newNode = new DiagramNode(node.__drawingId, node.__drawingUrl); + newNode.__id = node.__id; + return newNode; } constructor(drawingId: string, drawingUrl: string, key?: string) { @@ -120,10 +122,13 @@ export class DiagramNode extends DecoratorNode { const img = element.querySelector('img'); const drawingUrl = img?.getAttribute('src') || ''; const drawingId = element.getAttribute('drawio-diagram') || ''; + const node = $createDiagramNode(drawingId, drawingUrl); - return { - node: $createDiagramNode(drawingId, drawingUrl), - }; + if (element.id) { + node.setId(element.id); + } + + return { node }; }, priority: 3, }; @@ -152,7 +157,7 @@ export function $createDiagramNode(drawingId: string = '', drawingUrl: string = return new DiagramNode(drawingId, drawingUrl); } -export function $isDiagramNode(node: LexicalNode | null | undefined) { +export function $isDiagramNode(node: LexicalNode | null | undefined): node is DiagramNode { return node instanceof DiagramNode; } diff --git a/resources/js/wysiwyg/nodes/horizontal-rule.ts b/resources/js/wysiwyg/nodes/horizontal-rule.ts index fbd019e72..e881d4688 100644 --- a/resources/js/wysiwyg/nodes/horizontal-rule.ts +++ b/resources/js/wysiwyg/nodes/horizontal-rule.ts @@ -4,26 +4,48 @@ import { ElementNode, LexicalEditor, LexicalNode, - SerializedElementNode, + SerializedElementNode, Spread, } from 'lexical'; import type {EditorConfig} from "lexical/LexicalEditor"; +export type SerializedHorizontalRuleNode = Spread<{ + id: string; +}, SerializedElementNode> + export class HorizontalRuleNode extends ElementNode { + __id: string = ''; static getType() { return 'horizontal-rule'; } + setId(id: string) { + const self = this.getWritable(); + self.__id = id; + } + + getId(): string { + const self = this.getLatest(); + return self.__id; + } + static clone(node: HorizontalRuleNode): HorizontalRuleNode { - return new HorizontalRuleNode(node.__key); + const newNode = new HorizontalRuleNode(node.__key); + newNode.__id = node.__id; + return newNode; } - createDOM(_config: EditorConfig, _editor: LexicalEditor) { - return document.createElement('hr'); + createDOM(_config: EditorConfig, _editor: LexicalEditor): HTMLElement { + const el = document.createElement('hr'); + if (this.__id) { + el.setAttribute('id', this.__id); + } + + return el; } - updateDOM(prevNode: unknown, dom: HTMLElement) { - return false; + updateDOM(prevNode: HorizontalRuleNode, dom: HTMLElement) { + return prevNode.__id !== this.__id; } static importDOM(): DOMConversionMap|null { @@ -31,9 +53,12 @@ export class HorizontalRuleNode extends ElementNode { hr(node: HTMLElement): DOMConversion|null { return { conversion: (element: HTMLElement): DOMConversionOutput|null => { - return { - node: new HorizontalRuleNode(), - }; + const node = new HorizontalRuleNode(); + if (element.id) { + node.setId(element.id); + } + + return {node}; }, priority: 3, }; @@ -41,24 +66,27 @@ export class HorizontalRuleNode extends ElementNode { }; } - exportJSON(): SerializedElementNode { + exportJSON(): SerializedHorizontalRuleNode { return { ...super.exportJSON(), type: 'horizontal-rule', version: 1, + id: this.__id, }; } - static importJSON(serializedNode: SerializedElementNode): HorizontalRuleNode { - return $createHorizontalRuleNode(); + static importJSON(serializedNode: SerializedHorizontalRuleNode): HorizontalRuleNode { + const node = $createHorizontalRuleNode(); + node.setId(serializedNode.id); + return node; } } -export function $createHorizontalRuleNode() { +export function $createHorizontalRuleNode(): HorizontalRuleNode { return new HorizontalRuleNode(); } -export function $isHorizontalRuleNode(node: LexicalNode | null | undefined) { +export function $isHorizontalRuleNode(node: LexicalNode | null | undefined): node is HorizontalRuleNode { return node instanceof HorizontalRuleNode; } \ No newline at end of file diff --git a/resources/js/wysiwyg/nodes/index.ts b/resources/js/wysiwyg/nodes/index.ts index 81a0c1a0d..8cbec20da 100644 --- a/resources/js/wysiwyg/nodes/index.ts +++ b/resources/js/wysiwyg/nodes/index.ts @@ -22,16 +22,19 @@ 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"; /** * Load the nodes for lexical. */ export function getNodesForPageEditor(): (KlassConstructor | LexicalNodeReplacement)[] { return [ - CalloutNode, // Todo - Create custom - HeadingNode, // Todo - Create custom - QuoteNode, // Todo - Create custom - ListNode, // Todo - Create custom + CalloutNode, + CustomHeadingNode, + CustomQuoteNode, + CustomListNode, CustomListItemNode, CustomTableNode, CustomTableRowNode, @@ -42,7 +45,7 @@ export function getNodesForPageEditor(): (KlassConstructor | CodeBlockNode, DiagramNode, MediaNode, - CustomParagraphNode, + CustomParagraphNode, // TODO - ID LinkNode, { replace: ParagraphNode, @@ -50,6 +53,24 @@ export function getNodesForPageEditor(): (KlassConstructor | return new CustomParagraphNode(); } }, + { + replace: HeadingNode, + with: (node: HeadingNode) => { + return new CustomHeadingNode(node.__tag); + } + }, + { + replace: QuoteNode, + with: (node: QuoteNode) => { + return new CustomQuoteNode(); + } + }, + { + replace: ListNode, + with: (node: ListNode) => { + return new CustomListNode(node.getListType(), node.getStart()); + } + }, { replace: ListItemNode, with: (node: ListItemNode) => { diff --git a/resources/js/wysiwyg/nodes/media.ts b/resources/js/wysiwyg/nodes/media.ts index aba4f6c37..73208cb2e 100644 --- a/resources/js/wysiwyg/nodes/media.ts +++ b/resources/js/wysiwyg/nodes/media.ts @@ -66,7 +66,6 @@ function domElementToNode(tag: MediaNodeTag, element: Element): MediaNode { } export class MediaNode extends ElementNode { - __tag: MediaNodeTag; __attributes: Record = {}; __sources: MediaNodeSource[] = []; @@ -76,7 +75,10 @@ export class MediaNode extends ElementNode { } static clone(node: MediaNode) { - return new MediaNode(node.__tag, node.__key); + const newNode = new MediaNode(node.__tag, node.__key); + newNode.__attributes = Object.assign({}, node.__attributes); + newNode.__sources = node.__sources.map(s => Object.assign({}, s)); + return newNode; } constructor(tag: MediaNodeTag, key?: string) { @@ -226,10 +228,10 @@ export function $createMediaNodeFromSrc(src: string): MediaNode { return new MediaNode(nodeTag); } -export function $isMediaNode(node: LexicalNode | null | undefined) { +export function $isMediaNode(node: LexicalNode | null | undefined): node is MediaNode { return node instanceof MediaNode; } -export function $isMediaNodeOfTag(node: LexicalNode | null | undefined, tag: MediaNodeTag) { +export function $isMediaNodeOfTag(node: LexicalNode | null | undefined, tag: MediaNodeTag): boolean { return node instanceof MediaNode && (node as MediaNode).getTag() === tag; } \ No newline at end of file diff --git a/resources/js/wysiwyg/todo.md b/resources/js/wysiwyg/todo.md index 9e501fb24..c8a0293d5 100644 --- a/resources/js/wysiwyg/todo.md +++ b/resources/js/wysiwyg/todo.md @@ -2,13 +2,14 @@ ## In progress +// + ## Main Todo - Alignments: Use existing classes for blocks (including table cells) - Alignments: Handle inline block content (image, video) - Image paste upload - Keyboard shortcuts support -- Add ID support to all block types - Link popup menu for cross-content reference - Link heading-based ID reference menu - Image gallery integration for insert