onchain-lexical-markdown 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ */
8
+ import {HeadingTagType} from '@lexical/rich-text';
9
+ import {
10
+ $createBaseInstanceNode,
11
+ $createInstanceHeadingNode,
12
+ $isInstanceHeadingNode,
13
+ $isInstanceNode,
14
+ $isInstanceParagraphNode,
15
+ $isInstanceTitleNode,
16
+ InstanceHeadingNode,
17
+ InstanceNode,
18
+ InstanceParagraphNode,
19
+ InstanceTitleNode,
20
+ } from 'onchain-lexical-instance';
21
+
22
+ import {createBlockNode, ElementTransformer} from '../MarkdownTransformers';
23
+ import {$convertToMarkdownString} from '../toMarkdownString';
24
+ import {TransFormerGather} from '.';
25
+ import {HEADING_REGEX, INS_SYMBOL, INSTANCE_START_REGEX} from './const';
26
+
27
+ export const InstanceTransformer: ElementTransformer = {
28
+ dependencies: [InstanceNode],
29
+ export: (node, exportChildren) => {
30
+ if (!$isInstanceNode(node)) {
31
+ return null;
32
+ }
33
+ const serialNumber = node.getSerialNumber();
34
+ const level = serialNumber.split('-');
35
+ const markdown =
36
+ `<!--${INS_SYMBOL.repeat(level.length)}-->` +
37
+ $convertToMarkdownString(TransFormerGather.value, node) +
38
+ `\n<!---->`;
39
+ return markdown;
40
+ },
41
+ regExp: INSTANCE_START_REGEX,
42
+ replace: createBlockNode((match) => {
43
+ return $createBaseInstanceNode();
44
+ }),
45
+ type: 'element',
46
+ };
47
+
48
+ export const InstanceHeadingTransformer: ElementTransformer = {
49
+ dependencies: [InstanceHeadingNode, InstanceTitleNode, InstanceParagraphNode],
50
+ export: (node, exportChildren) => {
51
+ if ($isInstanceParagraphNode(node) && node.isTitle) {
52
+ const str = $convertToMarkdownString(TransFormerGather.value, node);
53
+ return str;
54
+ } else {
55
+ if (!$isInstanceHeadingNode(node)) {
56
+ return null;
57
+ }
58
+ const level = Number(node.getTag().slice(1));
59
+ let number = '';
60
+ const instanceNode = $isInstanceTitleNode(node) && node.getInstanceNode();
61
+ if (instanceNode && instanceNode.__instance) {
62
+ number = `${instanceNode.__instance.number} / `;
63
+ }
64
+ return '#'.repeat(level) + ' ' + number + exportChildren(node);
65
+ }
66
+ },
67
+ regExp: HEADING_REGEX,
68
+ replace: createBlockNode((match) => {
69
+ const tag = ('h' + match[1].length) as HeadingTagType;
70
+ return $createInstanceHeadingNode(tag);
71
+ }),
72
+ type: 'element',
73
+ };
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ */
8
+
9
+ import {ElementNode} from 'lexical';
10
+ import {
11
+ $isInstanceHeadingNode,
12
+ $isInstanceNode,
13
+ $isNumberDecoratorNode,
14
+ Instance,
15
+ } from 'onchain-lexical-instance';
16
+
17
+ export default class LevelBasedControl {
18
+ ancestor: Map<number, ElementNode> = new Map();
19
+ stack: ElementNode[] = [];
20
+ level1: Set<ElementNode> = new Set();
21
+ instanceMap?: Map<string, Instance>;
22
+ get children() {
23
+ return this.level1;
24
+ }
25
+
26
+ private collectLevel1(level: number, node: ElementNode) {
27
+ if (level === 1) {
28
+ this.level1.add(node);
29
+ }
30
+ return node;
31
+ }
32
+
33
+ reductionNodeHierarchy({
34
+ level,
35
+ isInstanceEnd,
36
+ nodes,
37
+ }: {
38
+ isInstanceEnd: boolean;
39
+ level: number;
40
+ nodes: ElementNode[];
41
+ }) {
42
+ if (isInstanceEnd) {
43
+ this.unstack();
44
+ } else {
45
+ this.insert(level, nodes);
46
+ }
47
+ }
48
+
49
+ private insert(level: number, nodes: ElementNode[]) {
50
+ if (level !== 0) {
51
+ const node = this.collectLevel1(level, nodes[0]);
52
+ this.stack.push(node);
53
+ this.ancestor.set(level, node);
54
+ const parent = this.ancestor.get(level - 1);
55
+ if (parent) {
56
+ parent.append(node);
57
+ }
58
+ } else {
59
+ const current = this.stack[this.stack.length - 1];
60
+ if (current) {
61
+ this.initTitle(current, nodes);
62
+ current.append(...nodes);
63
+ }
64
+ }
65
+ }
66
+
67
+ private unstack() {
68
+ this.stack.pop();
69
+ }
70
+
71
+ private initTitle(node: ElementNode, nodes: ElementNode[]) {
72
+ if ($isInstanceNode(node) && !node.__instance) {
73
+ const numberNode = node.getChildAtIndex(1);
74
+ const titleNode = nodes[0];
75
+ const title = titleNode.getTextContent().trim();
76
+ const [number, insDesc] = title.split(' / ');
77
+ let instance: Instance | undefined;
78
+ if (this.instanceMap && this.instanceMap.has(number)) {
79
+ instance = this.instanceMap.get(number)!;
80
+ } else if (number) {
81
+ instance = {
82
+ insDesc,
83
+ number,
84
+ } as Instance;
85
+ }
86
+ if ($isInstanceHeadingNode(titleNode)) {
87
+ nodes.shift();
88
+ }
89
+ if ($isNumberDecoratorNode(numberNode)) {
90
+ numberNode.__instance = instance;
91
+ }
92
+ node.__instance = instance;
93
+ }
94
+ }
95
+ }
@@ -0,0 +1,182 @@
1
+ /**
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ */
8
+
9
+ import {ElementTransformer} from '@lexical/markdown';
10
+ import {
11
+ $createTableCellNode,
12
+ $createTableNode,
13
+ $createTableRowNode,
14
+ $isTableCellNode,
15
+ $isTableNode,
16
+ $isTableRowNode,
17
+ TableCellHeaderStates,
18
+ TableCellNode,
19
+ TableNode,
20
+ TableRowNode,
21
+ } from '@lexical/table';
22
+ import {$isParagraphNode, $isTextNode, LexicalNode} from 'lexical';
23
+
24
+ import {$convertFromMarkdownString, TransFormerGather} from '..';
25
+ import {$convertToMarkdownString} from '../toMarkdownString';
26
+
27
+ const TABLE_ROW_REG_EXP = /^(?:\|)(.+)(?:\|)\s?$/;
28
+ const TABLE_ROW_DIVIDER_REG_EXP = /^(\| ?:?-*:? ?)+\|\s?$/;
29
+
30
+ export const TABLE: ElementTransformer = {
31
+ dependencies: [TableNode, TableRowNode, TableCellNode],
32
+ export: (node: LexicalNode) => {
33
+ if (!$isTableNode(node)) {
34
+ return null;
35
+ }
36
+
37
+ const output: string[] = [];
38
+
39
+ for (const row of node.getChildren()) {
40
+ const rowOutput = [];
41
+ if (!$isTableRowNode(row)) {
42
+ continue;
43
+ }
44
+
45
+ let isHeaderRow = false;
46
+ for (const cell of row.getChildren()) {
47
+ // It's TableCellNode so it's just to make flow happy
48
+ if ($isTableCellNode(cell)) {
49
+ rowOutput.push(
50
+ $convertToMarkdownString(TransFormerGather.value, cell)
51
+ .replace(/\n/g, '\\n')
52
+ .trim(),
53
+ );
54
+ if (cell.__headerState === TableCellHeaderStates.ROW) {
55
+ isHeaderRow = true;
56
+ }
57
+ }
58
+ }
59
+
60
+ output.push(`| ${rowOutput.join(' | ')} |`);
61
+ if (isHeaderRow) {
62
+ output.push(`| ${rowOutput.map((_) => '---').join(' | ')} |`);
63
+ }
64
+ }
65
+
66
+ return output.join('\n');
67
+ },
68
+ regExp: TABLE_ROW_REG_EXP,
69
+ replace: (parentNode, _1, match) => {
70
+ // Header row
71
+ if (TABLE_ROW_DIVIDER_REG_EXP.test(match[0])) {
72
+ const table = parentNode.getPreviousSibling();
73
+ if (!table || !$isTableNode(table)) {
74
+ return;
75
+ }
76
+
77
+ const rows = table.getChildren();
78
+ const lastRow = rows[rows.length - 1];
79
+ if (!lastRow || !$isTableRowNode(lastRow)) {
80
+ return;
81
+ }
82
+
83
+ // Add header state to row cells
84
+ lastRow.getChildren().forEach((cell) => {
85
+ if (!$isTableCellNode(cell)) {
86
+ return;
87
+ }
88
+ cell.setHeaderStyles(
89
+ TableCellHeaderStates.ROW,
90
+ TableCellHeaderStates.ROW,
91
+ );
92
+ });
93
+
94
+ // Remove line
95
+ parentNode.remove();
96
+ return;
97
+ }
98
+
99
+ const matchCells = mapToTableCells(match[0]);
100
+
101
+ if (matchCells == null) {
102
+ return;
103
+ }
104
+
105
+ const rows = [matchCells];
106
+ let sibling = parentNode.getPreviousSibling();
107
+ let maxCells = matchCells.length;
108
+
109
+ while (sibling) {
110
+ if (!$isParagraphNode(sibling)) {
111
+ break;
112
+ }
113
+
114
+ if (sibling.getChildrenSize() !== 1) {
115
+ break;
116
+ }
117
+
118
+ const firstChild = sibling.getFirstChild();
119
+
120
+ if (!$isTextNode(firstChild)) {
121
+ break;
122
+ }
123
+
124
+ const cells = mapToTableCells(firstChild.getTextContent());
125
+
126
+ if (cells == null) {
127
+ break;
128
+ }
129
+
130
+ maxCells = Math.max(maxCells, cells.length);
131
+ rows.unshift(cells);
132
+ const previousSibling = sibling.getPreviousSibling();
133
+ sibling.remove();
134
+ sibling = previousSibling;
135
+ }
136
+
137
+ const table = $createTableNode();
138
+
139
+ for (const cells of rows) {
140
+ const tableRow = $createTableRowNode();
141
+ table.append(tableRow);
142
+
143
+ for (let i = 0; i < maxCells; i++) {
144
+ tableRow.append(i < cells.length ? cells[i] : $createTableCell(''));
145
+ }
146
+ }
147
+
148
+ const previousSibling = parentNode.getPreviousSibling();
149
+ if (
150
+ $isTableNode(previousSibling) &&
151
+ getTableColumnsSize(previousSibling) === maxCells
152
+ ) {
153
+ previousSibling.append(...table.getChildren());
154
+ parentNode.remove();
155
+ } else {
156
+ parentNode.replace(table);
157
+ }
158
+
159
+ table.selectEnd();
160
+ },
161
+ type: 'element',
162
+ };
163
+
164
+ function getTableColumnsSize(table: TableNode) {
165
+ const row = table.getFirstChild();
166
+ return $isTableRowNode(row) ? row.getChildrenSize() : 0;
167
+ }
168
+
169
+ const $createTableCell = (textContent: string): TableCellNode => {
170
+ textContent = textContent.replace(/\\n/g, '\n');
171
+ const cell = $createTableCellNode(TableCellHeaderStates.NO_STATUS);
172
+ $convertFromMarkdownString(textContent, TransFormerGather.value, cell);
173
+ return cell;
174
+ };
175
+
176
+ const mapToTableCells = (textContent: string): Array<TableCellNode> | null => {
177
+ const match = textContent.match(TABLE_ROW_REG_EXP);
178
+ if (!match || !match[1]) {
179
+ return null;
180
+ }
181
+ return match[1].split('|').map((text) => $createTableCell(text));
182
+ };
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ */
8
+
9
+ export function getInstanceLevel(match?: string[] | null) {
10
+ if (match && match.length) {
11
+ const level = match[2].length;
12
+ return {
13
+ isInstance: true,
14
+ level,
15
+ };
16
+ }
17
+ return {
18
+ isInstance: false,
19
+ level: 0,
20
+ };
21
+ }