lexical-remark
Version:
This package contains Markdown helpers and functionality for Lexical using remark-parse.
144 lines (118 loc) • 3.65 kB
text/typescript
/* eslint-disable @typescript-eslint/no-use-before-define */
import lexicalUtils from '@lexical/utils';
import lexical, { type LexicalEditor, type LexicalNode, type RangeSelection } from 'lexical';
export type SerializedAttachmentNode = lexical.Spread<
{
filename: string;
url: string;
},
lexical.SerializedElementNode
>;
function convertAttachmentElement(domNode: HTMLElement): lexical.DOMConversionOutput | null {
return {
node: $createAttachmentNode(domNode.getAttribute('href') ?? '', domNode.getAttribute('download') ?? ''),
};
}
export class AttachmentNode extends lexical.ElementNode {
__filename: string;
__url: string;
constructor(url: string, filename: string, key?: lexical.NodeKey) {
super(key);
this.__url = url;
this.__filename = filename;
}
static getType(): string {
return 'attachment';
}
static clone(node: AttachmentNode): AttachmentNode {
return new AttachmentNode(node.__url, node.__filename, node.__key);
}
createDOM(config: lexical.EditorConfig, editor: LexicalEditor): HTMLAnchorElement {
const dom = document.createElement('a');
dom.href = this.__url;
dom.download = this.__filename;
return dom;
}
updateDOM(prevNode: AttachmentNode, dom: HTMLAnchorElement, config: lexical.EditorConfig): boolean {
const newText = this.getTextContent()
.replace(/^(?:📎)?\s*([^\s]|$)/, '$1')
.trim();
if (!newText.length) {
this.remove();
return false;
}
if (!this.getTextContent().startsWith('📎 ') || newText !== this.__filename) {
this.setFilename(newText);
this.getChildAtIndex(0)?.setTextContent(`📎 ${newText}`);
dom.download = newText;
}
if (prevNode.__url !== this.__url) {
dom.href = this.__url;
}
return false;
}
static importDOM(): lexical.DOMConversionMap {
return {
a: (domNode) => {
if (!lexicalUtils.isHTMLAnchorElement(domNode)) {
return null;
}
if (!domNode.getAttribute('download')) {
return null;
}
return {
conversion: convertAttachmentElement,
priority: lexical.COMMAND_PRIORITY_HIGH,
};
},
};
}
static importJSON(serializedNode: SerializedAttachmentNode): AttachmentNode {
return $createAttachmentNode(serializedNode.url, serializedNode.filename);
}
exportJSON(): SerializedAttachmentNode {
return {
...super.exportJSON(),
filename: this.getFilename(),
type: 'attachment',
url: this.getURL(),
version: 1,
};
}
insertNewAfter(selection: RangeSelection, restoreSelection?: boolean): LexicalNode | null {
const element = this.getParentOrThrow().insertNewAfter(selection, restoreSelection);
return element;
}
canInsertTextBefore(): false {
return false;
}
canInsertTextAfter(): false {
return false;
}
canBeEmpty(): false {
return false;
}
isInline(): true {
return true;
}
getFilename(): string {
return this.getLatest().__filename;
}
setFilename(filename: string): void {
const writable = this.getWritable();
writable.__filename = filename;
}
getURL(): string {
return this.getLatest().__url;
}
setURL(url: string): void {
const writable = this.getWritable();
writable.__url = url;
}
}
export function $createAttachmentNode(url: string, filename: string, key?: lexical.NodeKey): AttachmentNode {
return new AttachmentNode(url, filename, key);
}
export function $isAttachmentNode(node: lexical.LexicalNode): node is AttachmentNode {
return node instanceof AttachmentNode;
}