Hey is a decentralized and permissionless social media app built with Lens Protocol 馃尶
1import type { Element, Node, Parent, Root } from "hast";
2
3import type { Plugin } from "unified";
4import { visitParents } from "unist-util-visit-parents";
5
6const isParent = (node: Node): node is Parent => {
7 return "children" in node && Array.isArray(node.children);
8};
9
10interface Paragraph extends Element {
11 tagName: "p";
12}
13
14const isElement = (node: Node): node is Element => {
15 return node.type === "element";
16};
17
18const isParagraph = (node: Node): node is Paragraph => {
19 return isElement(node) && node.tagName.toLowerCase() === "p";
20};
21
22const isParagraphEmpty = (node: Paragraph): boolean => {
23 return node.children.length === 0;
24};
25
26const joinParagraphs = (a: Paragraph, b: Paragraph): Paragraph => {
27 const br: Element = {
28 children: [],
29 properties: {},
30 tagName: "br",
31 type: "element"
32 };
33
34 return {
35 ...a,
36 ...b,
37 children: [...a.children, br, ...b.children],
38 properties: { ...a.properties, ...b.properties }
39 };
40};
41
42const joinChildren = <T extends Node>(children: T[]): T[] => {
43 const result: T[] = [];
44
45 for (const child of children) {
46 const previous = result.at(-1);
47 if (
48 previous &&
49 isParagraph(previous) &&
50 isParagraph(child) &&
51 !isParagraphEmpty(previous) &&
52 !isParagraphEmpty(child)
53 ) {
54 result[result.length - 1] = joinParagraphs(previous, child) as Node as T;
55 } else {
56 result.push(child);
57 }
58 }
59
60 return result;
61};
62
63const rehypeJoinParagraphTransformer = (root: Root): Root => {
64 visitParents(root, (node) => {
65 if (isParent(node)) {
66 node.children = joinChildren(node.children);
67 }
68 });
69
70 return root;
71};
72
73/**
74 * A rehype (HTML) plugin that joins adjacent non-empty <p> elements into a
75 * single <p> element with a <br> element between them.
76 */
77export const rehypeJoinParagraph: Plugin<[], Root> = () =>
78 rehypeJoinParagraphTransformer;