v3/src/editor/nodes/link_node.tsx

184 lines
4.5 KiB
TypeScript

import { DecoratorNode, ElementNode, type LexicalNode, type SerializedElementNode, type SerializedLexicalNode, type Spread } from "lexical";
import { FocusableNode } from "./focusable_node";
import type { ReactNode } from "react";
import { LinkIcon } from "~/components/editor/link_icon";
type SerializedLinkNode = Spread<SerializedElementNode, {
url: string;
}>;
export class LinkNode extends FocusableNode {
__url: string;
getUrl(): string {
const self = this.getLatest();
return self.__url;
}
static getType() {
return "link";
}
static clone(node: LinkNode) {
return new LinkNode(node.__url, node.__key, node.__hasFocus);
}
constructor(url: string, key?: string, hasFocus?: boolean) {
super(key, hasFocus);
this.__url = url;
}
isInline(): boolean {
return true;
}
createDOM(): HTMLElement {
const element = super.createDOM();
element.classList.add("link");
return element;
}
updateDOM(old: LinkNode, dom: HTMLElement): boolean {
return super.updateDOM(old, dom);
}
exportJSON(): SerializedLinkNode {
return {
...super.exportJSON(),
type: "link",
url: this.__url,
};
}
static importJSON(serializedNode: SerializedLinkNode) {
return $createLinkNode(serializedNode.url);
}
}
export function $createLinkNode(url: string) {
return new LinkNode(url);
}
export function $isLinkNode(node?: LexicalNode | null): node is LinkNode {
return node instanceof LinkNode;
}
export class LinkMarkerNode extends ElementNode {
static getType() {
return "link-marker";
}
static clone(node: LinkMarkerNode) {
return new LinkMarkerNode(node.__key);
}
isInline(): boolean {
return true;
}
createDOM(): HTMLElement {
const element = document.createElement("span");
element.classList.add("link-marker");
return element;
}
updateDOM(): boolean {
return false;
}
exportJSON(): SerializedElementNode {
return {
...super.exportJSON(),
type: "link-marker",
};
}
static importJSON() {
return $createLinkMarkerNode();
}
}
export function $createLinkMarkerNode() {
return new LinkMarkerNode();
}
export function $isLinkMarkerNode(node?: LexicalNode | null): node is LinkMarkerNode {
return node instanceof LinkMarkerNode;
}
export class LinkUrlNode extends ElementNode {
static getType() {
return "link-url";
}
static clone(node: LinkUrlNode) {
return new LinkUrlNode(node.__key);
}
isInline(): boolean {
return true;
}
createDOM(): HTMLElement {
const element = document.createElement("span");
element.classList.add("link-url");
return element;
}
updateDOM(): boolean {
return false;
}
exportJSON(): SerializedElementNode {
return {
...super.exportJSON(),
type: "link-url",
};
}
static importJSON() {
return $createLinkUrlNode();
}
}
export function $createLinkUrlNode() {
return new LinkUrlNode();
}
export function $isLinkUrlNode(node?: LexicalNode | null): node is LinkUrlNode {
return node instanceof LinkUrlNode;
}
type SerializedLinkIconNode = Spread<SerializedLexicalNode, {
url: string;
}>;
export class LinkIconNode extends DecoratorNode<ReactNode> {
__url: string;
getUrl() {
const self = this.getLatest();
return self.__url;
}
static getType() {
return "link-icon";
}
static clone(node: LinkIconNode) {
return new LinkIconNode(node.__url, node.__key);
}
constructor(url: string, key?: string) {
super(key);
this.__url = url;
}
createDOM(): HTMLElement {
return document.createElement("span");
}
updateDOM(): boolean {
return false;
}
decorate(): ReactNode {
return (<LinkIcon url={this.__url} />);
}
exportJSON(): SerializedLinkIconNode {
return {
...super.exportJSON(),
type: "link-icon",
url: this.__url,
}
}
static importJSON(serializedNode: SerializedLinkIconNode) {
return $createLinkIconNode(serializedNode.url);
}
}
export function $createLinkIconNode(url: string) {
return new LinkIconNode(url);
}
export function $isLinkIconNode(node?: LexicalNode | null): node is LinkIconNode {
return node instanceof LinkIconNode;
}