@churchapps/apphelper-markdown
Version:
ChurchApps markdown/lexical editor components
148 lines (147 loc) • 5.3 kB
JavaScript
import { createCommand, $isElementNode, $getSelection, $applyNodeReplacement, $isRangeSelection } from "lexical";
import { LinkNode } from "@lexical/link";
import { addClassNamesToElement } from "@lexical/utils";
export class CustomLinkNode extends LinkNode {
constructor(url, target, classNames, key) {
super(url, { target }, key);
this.__url = url || "https://";
this.__target = target || "_self";
this.__classNames = classNames || [];
}
static importJSON(serializedNode) {
return super.importJSON(serializedNode);
}
exportJSON() {
return super.exportJSON();
}
static getType() {
return "customlinknode";
}
static clone(node) {
const newLinkNode = new CustomLinkNode(node.__url, node.__target, node.__classNames, node.__key);
return $applyNodeReplacement(newLinkNode);
}
createDOM() {
const link = document.createElement("a");
link.href = this.__url;
link.setAttribute("target", this.__target || "_blank");
addClassNamesToElement(link, (this.__classNames || []).join(" "));
return link;
}
updateDOM() {
return false;
}
setClassNames(classNames) {
const writable = this.getWritable();
writable.__classNames = classNames;
}
}
export const TOGGLE_CUSTOM_LINK_NODE_COMMAND = createCommand();
export function $createCustomLinkNode(url, target, classNames) {
return $applyNodeReplacement(new CustomLinkNode(url, target, classNames));
}
export function $isCustomLinkNode(node) {
return node instanceof LinkNode;
}
export const toggleCustomLinkNode = ({ url, target = "_blank", classNames = [], getNodeByKey }) => {
const addAttributesToLinkNode = (linkNode, { url, target, classNames }) => {
const dom = getNodeByKey(linkNode.getKey());
if (!dom)
return;
const uniqueClassNames = classNames;
linkNode.setURL(url);
linkNode.setTarget(target);
linkNode.setClassNames(uniqueClassNames);
dom.setAttribute("href", url);
dom.setAttribute("target", target);
dom.setAttribute("class", uniqueClassNames.join(" "));
};
const selection = $getSelection();
if (!$isRangeSelection(selection)) {
return;
}
const nodes = selection.extract();
if (url === null) {
// Remove LinkNodes
nodes.forEach((node) => {
const parent = node.getParent();
if ($isCustomLinkNode(parent)) {
const children = parent.getChildren();
for (let i = 0; i < children.length; i++) {
parent.insertBefore(children[i]);
}
parent.remove();
}
});
}
else {
// Add or merge LinkNodes
if (nodes.length === 1) {
const firstNode = nodes[0];
// if the first node is a LinkNode or if its
// parent is a LinkNode, we update the URL, target and rel.
const linkNode = $isCustomLinkNode(firstNode)
? firstNode
: $getLinkAncestor(firstNode);
if (linkNode !== null && $isCustomLinkNode(linkNode)) {
addAttributesToLinkNode(linkNode, { url, target, classNames });
return;
}
}
let prevParent = null;
let linkNode = null;
nodes.forEach((node) => {
const parent = node.getParent();
if (parent === linkNode
|| parent === null
|| ($isElementNode(node) && !node.isInline())) {
return;
}
if ($isCustomLinkNode(parent)) {
linkNode = parent;
addAttributesToLinkNode(parent, { url, target, classNames });
return;
}
if (!parent.is(prevParent)) {
prevParent = parent;
linkNode = $createCustomLinkNode(url, target, classNames);
if ($isCustomLinkNode(parent)) {
if (node.getPreviousSibling() === null) {
parent.insertBefore(linkNode);
}
else {
parent.insertAfter(linkNode);
}
}
else {
node.insertBefore(linkNode);
}
}
if ($isCustomLinkNode(node)) {
if (node.is(linkNode)) {
return;
}
if (linkNode !== null) {
const children = node.getChildren();
for (let i = 0; i < children.length; i++) {
linkNode.append(children[i]);
}
}
node.remove();
return;
}
if (linkNode !== null) {
linkNode.append(node);
}
});
}
};
const $getLinkAncestor = (node) => ($getAncestor(node, (ancestor) => $isCustomLinkNode(ancestor)));
const $getAncestor = (node, predicate) => {
let parent = node;
while (parent !== null
&& (parent = parent.getParent()) !== null
&& !predicate(parent))
;
return parent;
};