Skip to content

Instantly share code, notes, and snippets.

@hanford
Created May 31, 2022 16:26
Show Gist options
  • Save hanford/e15a0f3be981beb91602a6b1c82fa39a to your computer and use it in GitHub Desktop.
Save hanford/e15a0f3be981beb91602a6b1c82fa39a to your computer and use it in GitHub Desktop.
import type { ListNode, ListType } from '@lexical/list';
import type { ElementNode } from 'lexical';
import type { ElementTransformer } from '@lexical/markdown';
import {
BOLD_ITALIC_STAR,
BOLD_ITALIC_UNDERSCORE,
BOLD_STAR,
BOLD_UNDERSCORE,
CHECK_LIST,
CODE,
HEADING,
INLINE_CODE,
ITALIC_STAR,
ITALIC_UNDERSCORE,
LINK,
QUOTE,
STRIKETHROUGH,
TextFormatTransformer,
TextMatchTransformer,
Transformer,
} from '@lexical/markdown';
import { $createListItemNode, $createListNode, $isListItemNode, $isListNode } from '@lexical/list';
const listReplace = (listType: ListType, indentSize: number): ElementTransformer['replace'] => {
return (parentNode, children, match) => {
const previousNode = parentNode.getPreviousSibling();
const listItem = $createListItemNode(listType === 'check' ? match[3] === 'x' : undefined);
if ($isListNode(previousNode) && previousNode.getListType() === listType) {
previousNode.append(listItem);
parentNode.remove();
} else {
const list = $createListNode(listType, listType === 'number' ? Number(match[2]) : undefined);
list.append(listItem);
parentNode.replace(list);
}
listItem.append(...children);
listItem.select(0, 0);
const indent = Math.floor(match[1].length / indentSize);
if (indent) {
listItem.setIndent(indent);
}
};
};
const listExport = (
listNode: ListNode,
exportChildren: (node: ElementNode) => string,
depth: number,
indentSize: number,
): string => {
const output = [];
const children = listNode.getChildren();
let index = 0;
for (const listItemNode of children) {
if ($isListItemNode(listItemNode)) {
if (listItemNode.getChildrenSize() === 1) {
const firstChild = listItemNode.getFirstChild();
if ($isListNode(firstChild)) {
output.push(listExport(firstChild, exportChildren, depth + 1, indentSize));
continue;
}
}
const indent = ' '.repeat(depth * indentSize);
const listType = listNode.getListType();
const prefix =
listType === 'number'
? `${listNode.getStart() + index}. `
: listType === 'check'
? `- [${listItemNode.getChecked() ? 'x' : ' '}] `
: '- ';
output.push(indent + prefix + exportChildren(listItemNode));
index++;
}
}
return output.join('\n');
};
const UNORDERED_LIST_TRANSFORM: ElementTransformer = {
export: (node, exportChildren) => {
return $isListNode(node) ? listExport(node, exportChildren, 0, 2) : null;
},
regExp: /^(\s*)[-*+]\s/,
replace: listReplace('bullet', 2),
type: 'element',
};
const ORDERED_LIST_TRANSFORM: ElementTransformer = {
export: (node, exportChildren) => {
return $isListNode(node) ? listExport(node, exportChildren, 0, 3) : null;
},
regExp: /^(\s*)(\d{1,})\.\s/,
replace: listReplace('number', 3),
type: 'element',
};
const ELEMENT_TRANSFORMERS: ElementTransformer[] = [
HEADING,
QUOTE,
CODE,
ORDERED_LIST_TRANSFORM,
CHECK_LIST,
UNORDERED_LIST_TRANSFORM,
];
// Order of text format transformers matters:
//
// - code should go first as it prevents any transformations inside
// - then longer tags match (e.g. ** or __ should go before * or _)
const TEXT_FORMAT_TRANSFORMERS: TextFormatTransformer[] = [
INLINE_CODE,
BOLD_ITALIC_STAR,
BOLD_ITALIC_UNDERSCORE,
BOLD_STAR,
BOLD_UNDERSCORE,
ITALIC_STAR,
ITALIC_UNDERSCORE,
STRIKETHROUGH,
];
const TEXT_MATCH_TRANSFORMERS: TextMatchTransformer[] = [LINK];
export const TRANSFORMERS: Transformer[] = [
...ELEMENT_TRANSFORMERS,
...TEXT_FORMAT_TRANSFORMERS,
...TEXT_MATCH_TRANSFORMERS,
];
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment