@udx/mq 0.1.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.
- package/examples/analyze-document.js +191 -0
- package/examples/cross-linker.js +47 -0
- package/examples/demo-architecture.js +93 -0
- package/examples/demo.js +200 -0
- package/examples/filter-code-blocks.js +64 -0
- package/examples/generate-toc.js +71 -0
- package/examples/make-collapsible.js +61 -0
- package/examples/query-headings.js +56 -0
- package/examples/toc-generator.js +44 -0
- package/lib/core.js +347 -0
- package/lib/integrations/mcurl.js +125 -0
- package/lib/operations/analysis.js +344 -0
- package/lib/operations/extractors.js +247 -0
- package/lib/operations/index.js +151 -0
- package/lib/operations/transformers.js +411 -0
- package/lib/utils/parser.js +165 -0
- package/mq.js +656 -0
- package/package.json +67 -0
- package/readme.md +242 -0
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Operations index for Markdown Query
|
|
3
|
+
*
|
|
4
|
+
* Entry point for all query and transform operations
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { visit } from 'unist-util-visit';
|
|
8
|
+
import { toMarkdown } from 'mdast-util-to-markdown';
|
|
9
|
+
import _ from 'lodash';
|
|
10
|
+
|
|
11
|
+
// Import all operations
|
|
12
|
+
import {
|
|
13
|
+
extractHeadings,
|
|
14
|
+
extractCodeBlocks,
|
|
15
|
+
extractLinks,
|
|
16
|
+
generateToc,
|
|
17
|
+
extractSections
|
|
18
|
+
} from './extractors.js';
|
|
19
|
+
|
|
20
|
+
import {
|
|
21
|
+
analyzeDocument,
|
|
22
|
+
showDocumentStructure,
|
|
23
|
+
countDocumentElements
|
|
24
|
+
} from './analysis.js';
|
|
25
|
+
|
|
26
|
+
import {
|
|
27
|
+
makeCodeBlocksCollapsible,
|
|
28
|
+
makeDescriptiveToc,
|
|
29
|
+
addCrossLinks,
|
|
30
|
+
fixHeadingHierarchy,
|
|
31
|
+
moveSection,
|
|
32
|
+
updateTOCNumbers,
|
|
33
|
+
insertTOC,
|
|
34
|
+
convertHTMLToMarkdown
|
|
35
|
+
} from './transformers.js';
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Query operations map
|
|
39
|
+
*
|
|
40
|
+
* Maps operation names to their implementing functions
|
|
41
|
+
*/
|
|
42
|
+
const queryOperations = {
|
|
43
|
+
headings: extractHeadings,
|
|
44
|
+
codeblocks: extractCodeBlocks,
|
|
45
|
+
links: extractLinks,
|
|
46
|
+
toc: generateToc,
|
|
47
|
+
sections: extractSections,
|
|
48
|
+
structure: showDocumentStructure,
|
|
49
|
+
analyze: analyzeDocument,
|
|
50
|
+
count: countDocumentElements
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Transform operations map
|
|
55
|
+
*
|
|
56
|
+
* Maps transform operation names to their implementing functions
|
|
57
|
+
*/
|
|
58
|
+
const transformOperations = {
|
|
59
|
+
'collapsible-code': makeCodeBlocksCollapsible,
|
|
60
|
+
'descriptive-toc': makeDescriptiveToc,
|
|
61
|
+
'add-cross-links': addCrossLinks,
|
|
62
|
+
'fix-headings': fixHeadingHierarchy,
|
|
63
|
+
'move-section': moveSection,
|
|
64
|
+
'update-toc-numbers': updateTOCNumbers,
|
|
65
|
+
'insert-toc': insertTOC,
|
|
66
|
+
'html-to-md': convertHTMLToMarkdown
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Get operation by name
|
|
71
|
+
*
|
|
72
|
+
* Retrieves a query or transform operation by its name
|
|
73
|
+
*
|
|
74
|
+
* @param {string} name - Operation name
|
|
75
|
+
* @param {string} type - Operation type (query or transform)
|
|
76
|
+
* @returns {Function} Operation function
|
|
77
|
+
*/
|
|
78
|
+
function getOperation(name, type = 'query') {
|
|
79
|
+
const operations = type === 'query' ? queryOperations : transformOperations;
|
|
80
|
+
return operations[name];
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Register new operation
|
|
85
|
+
*
|
|
86
|
+
* Adds a new operation to the operations map
|
|
87
|
+
*
|
|
88
|
+
* @param {string} name - Operation name
|
|
89
|
+
* @param {Function} fn - Operation implementation
|
|
90
|
+
* @param {string} type - Operation type (query or transform)
|
|
91
|
+
*/
|
|
92
|
+
function registerOperation(name, fn, type = 'query') {
|
|
93
|
+
if (type === 'query') {
|
|
94
|
+
queryOperations[name] = fn;
|
|
95
|
+
} else {
|
|
96
|
+
transformOperations[name] = fn;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* List all available operations
|
|
102
|
+
*
|
|
103
|
+
* @param {string} type - Operation type (query, transform, or all)
|
|
104
|
+
* @returns {Object} Map of operation names to descriptions
|
|
105
|
+
*/
|
|
106
|
+
function listOperations(type = 'all') {
|
|
107
|
+
const operations = {};
|
|
108
|
+
|
|
109
|
+
if (type === 'query' || type === 'all') {
|
|
110
|
+
Object.keys(queryOperations).forEach(name => {
|
|
111
|
+
operations[name] = `Query operation: ${name}`;
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (type === 'transform' || type === 'all') {
|
|
116
|
+
Object.keys(transformOperations).forEach(name => {
|
|
117
|
+
operations[name] = `Transform operation: ${name}`;
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return operations;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Execute operation by name
|
|
126
|
+
*
|
|
127
|
+
* @param {string} name - Operation name
|
|
128
|
+
* @param {Object} ast - Markdown AST
|
|
129
|
+
* @param {string} selector - Query selector
|
|
130
|
+
* @param {Array} args - Additional arguments for the operation
|
|
131
|
+
* @param {string} type - Operation type (query or transform)
|
|
132
|
+
* @returns {*} Operation result
|
|
133
|
+
*/
|
|
134
|
+
function executeOperation(name, ast, selector, args = [], type = 'query') {
|
|
135
|
+
const operation = getOperation(name, type);
|
|
136
|
+
|
|
137
|
+
if (!operation) {
|
|
138
|
+
throw new Error(`Operation "${name}" not found`);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return operation(ast, selector, ...args);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export {
|
|
145
|
+
queryOperations,
|
|
146
|
+
transformOperations,
|
|
147
|
+
getOperation,
|
|
148
|
+
registerOperation,
|
|
149
|
+
listOperations,
|
|
150
|
+
executeOperation
|
|
151
|
+
};
|
|
@@ -0,0 +1,411 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transformer operations for Markdown Query
|
|
3
|
+
*
|
|
4
|
+
* Functions for transforming markdown content in various ways
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { toString } from 'mdast-util-to-string';
|
|
8
|
+
import { visit } from 'unist-util-visit';
|
|
9
|
+
import { toMarkdown } from 'mdast-util-to-markdown';
|
|
10
|
+
import _ from 'lodash';
|
|
11
|
+
import { extractHeadings, extractSections } from './extractors.js';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Make code blocks collapsible
|
|
15
|
+
*
|
|
16
|
+
* Transforms code blocks in markdown to use HTML details/summary tags for collapsible sections
|
|
17
|
+
*
|
|
18
|
+
* @param {Object} ast - Markdown AST
|
|
19
|
+
* @param {string} selector - Optional selector (not used in this function)
|
|
20
|
+
* @returns {string} Transformed markdown with collapsible code blocks
|
|
21
|
+
*/
|
|
22
|
+
function makeCodeBlocksCollapsible(ast, selector) {
|
|
23
|
+
let markdown = toMarkdown(ast);
|
|
24
|
+
|
|
25
|
+
// Find code blocks with regex
|
|
26
|
+
const codeBlockRegex = /```([a-zA-Z]*)\n([\s\S]*?)```/g;
|
|
27
|
+
|
|
28
|
+
// Replace each code block with a collapsible version
|
|
29
|
+
markdown = markdown.replace(codeBlockRegex, (match, language, code) => {
|
|
30
|
+
// Create a summary based on the language
|
|
31
|
+
const summary = language ? `${language} code` : 'Code block';
|
|
32
|
+
|
|
33
|
+
// Create the collapsible HTML
|
|
34
|
+
return `<details>
|
|
35
|
+
<summary>${summary}</summary>
|
|
36
|
+
|
|
37
|
+
\`\`\`${language}
|
|
38
|
+
${code}
|
|
39
|
+
\`\`\`
|
|
40
|
+
</details>`;
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
return markdown;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Make TOC descriptive
|
|
48
|
+
*
|
|
49
|
+
* Enhances the table of contents by adding descriptions from the first sentence
|
|
50
|
+
* of paragraphs following each heading
|
|
51
|
+
*
|
|
52
|
+
* @param {Object} ast - Markdown AST
|
|
53
|
+
* @param {string} selector - Optional selector (not used in this function)
|
|
54
|
+
* @returns {Object} Modified AST with heading descriptions
|
|
55
|
+
*/
|
|
56
|
+
function makeDescriptiveToc(ast, selector) {
|
|
57
|
+
visit(ast, 'heading', (node) => {
|
|
58
|
+
// Find the next paragraph for description
|
|
59
|
+
const headingIndex = ast.children.indexOf(node);
|
|
60
|
+
if (headingIndex >= 0 && headingIndex < ast.children.length - 1) {
|
|
61
|
+
const nextNode = ast.children[headingIndex + 1];
|
|
62
|
+
if (nextNode.type === 'paragraph') {
|
|
63
|
+
// Add description to heading data
|
|
64
|
+
node.data = node.data || {};
|
|
65
|
+
node.data.description = toString(nextNode).split('.')[0]; // First sentence
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
return ast;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Add cross-links section
|
|
75
|
+
*
|
|
76
|
+
* Adds a "Related Documents" section with links to other markdown documents
|
|
77
|
+
* after the table of contents section
|
|
78
|
+
*
|
|
79
|
+
* @param {Object} ast - Markdown AST
|
|
80
|
+
* @param {string} selector - Optional selector (not used in this function)
|
|
81
|
+
* @param {Array} args - Array of document paths to link to
|
|
82
|
+
* @returns {Object} Modified AST with cross-links section
|
|
83
|
+
*/
|
|
84
|
+
function addCrossLinks(ast, selector, args) {
|
|
85
|
+
const docs = args || [];
|
|
86
|
+
|
|
87
|
+
// Find the TOC section to add after
|
|
88
|
+
let tocIndex = -1;
|
|
89
|
+
visit(ast, 'heading', (node, index) => {
|
|
90
|
+
if (toString(node).toLowerCase() === 'table of contents') {
|
|
91
|
+
tocIndex = index;
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
if (tocIndex >= 0) {
|
|
96
|
+
// Find the next heading after TOC
|
|
97
|
+
let nextHeadingIndex = ast.children.length;
|
|
98
|
+
for (let i = tocIndex + 1; i < ast.children.length; i++) {
|
|
99
|
+
if (ast.children[i].type === 'heading') {
|
|
100
|
+
nextHeadingIndex = i;
|
|
101
|
+
break;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Create cross-links section
|
|
106
|
+
const crossLinksHeading = {
|
|
107
|
+
type: 'heading',
|
|
108
|
+
depth: 3,
|
|
109
|
+
children: [{ type: 'text', value: 'Related Documents' }]
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
const crossLinksList = {
|
|
113
|
+
type: 'list',
|
|
114
|
+
ordered: false,
|
|
115
|
+
children: docs.map(doc => ({
|
|
116
|
+
type: 'listItem',
|
|
117
|
+
children: [{
|
|
118
|
+
type: 'paragraph',
|
|
119
|
+
children: [{
|
|
120
|
+
type: 'link',
|
|
121
|
+
url: doc,
|
|
122
|
+
children: [{
|
|
123
|
+
type: 'text',
|
|
124
|
+
value: doc.replace('.md', '').split('-').map(
|
|
125
|
+
word => word.charAt(0).toUpperCase() + word.slice(1)
|
|
126
|
+
).join(' ')
|
|
127
|
+
}]
|
|
128
|
+
}]
|
|
129
|
+
}]
|
|
130
|
+
}))
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
// Insert the cross-links section
|
|
134
|
+
ast.children.splice(nextHeadingIndex, 0, crossLinksHeading, crossLinksList);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return ast;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Fix heading hierarchy
|
|
142
|
+
*
|
|
143
|
+
* Ensures that heading levels in a document follow a proper hierarchy without skipping levels
|
|
144
|
+
*
|
|
145
|
+
* @param {Object} ast - Markdown AST
|
|
146
|
+
* @param {string} selector - Optional selector (not used in this function)
|
|
147
|
+
* @returns {Object} Modified AST with corrected heading hierarchy
|
|
148
|
+
*/
|
|
149
|
+
function fixHeadingHierarchy(ast, selector) {
|
|
150
|
+
let lastLevel = 0;
|
|
151
|
+
visit(ast, 'heading', (node) => {
|
|
152
|
+
if (node.depth > lastLevel + 1 && lastLevel > 0) {
|
|
153
|
+
node.depth = lastLevel + 1;
|
|
154
|
+
}
|
|
155
|
+
lastLevel = node.depth;
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
return ast;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Move a section from one position to another
|
|
163
|
+
*
|
|
164
|
+
* Moves a section identified by its title to a new position in the document
|
|
165
|
+
*
|
|
166
|
+
* @param {Object} ast - Markdown AST
|
|
167
|
+
* @param {string} fromTitle - Title of the section to move
|
|
168
|
+
* @param {number} toPosition - Position to move the section to
|
|
169
|
+
* @returns {Object} New AST with the section moved
|
|
170
|
+
*/
|
|
171
|
+
function moveSection(ast, fromTitle, toPosition) {
|
|
172
|
+
const sections = extractSections(ast);
|
|
173
|
+
const fromIndex = sections.findIndex(section => section.title === fromTitle);
|
|
174
|
+
|
|
175
|
+
if (fromIndex === -1) {
|
|
176
|
+
throw new Error(`Section "${fromTitle}" not found`);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const section = sections.splice(fromIndex, 1)[0];
|
|
180
|
+
sections.splice(toPosition, 0, section);
|
|
181
|
+
|
|
182
|
+
// Rebuild AST from sections
|
|
183
|
+
const newAst = {
|
|
184
|
+
type: 'root',
|
|
185
|
+
children: []
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
sections.forEach(section => {
|
|
189
|
+
newAst.children.push(...section.content);
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
return newAst;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Update TOC numbers
|
|
197
|
+
*
|
|
198
|
+
* Updates the table of contents with section numbers for better navigation
|
|
199
|
+
*
|
|
200
|
+
* @param {Object} ast - Markdown AST
|
|
201
|
+
* @param {string} selector - Optional selector (not used in this function)
|
|
202
|
+
* @returns {Object} Modified AST with numbered TOC
|
|
203
|
+
*/
|
|
204
|
+
function updateTOCNumbers(ast, selector) {
|
|
205
|
+
// Find the TOC section
|
|
206
|
+
let tocIndex = -1;
|
|
207
|
+
visit(ast, 'heading', (node, index) => {
|
|
208
|
+
if (toString(node).toLowerCase() === 'table of contents') {
|
|
209
|
+
tocIndex = index;
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
if (tocIndex >= 0) {
|
|
214
|
+
// Find the list that follows the TOC heading
|
|
215
|
+
let listIndex = -1;
|
|
216
|
+
for (let i = tocIndex + 1; i < ast.children.length; i++) {
|
|
217
|
+
if (ast.children[i].type === 'list') {
|
|
218
|
+
listIndex = i;
|
|
219
|
+
break;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if (listIndex >= 0) {
|
|
224
|
+
// Update the list items with numbers
|
|
225
|
+
const list = ast.children[listIndex];
|
|
226
|
+
let currentNumber = 1;
|
|
227
|
+
|
|
228
|
+
visit(list, 'listItem', (node) => {
|
|
229
|
+
// Add number to the first paragraph in the list item
|
|
230
|
+
if (node.children && node.children.length > 0 && node.children[0].type === 'paragraph') {
|
|
231
|
+
const paragraph = node.children[0];
|
|
232
|
+
const firstChild = paragraph.children[0];
|
|
233
|
+
|
|
234
|
+
if (firstChild.type === 'text') {
|
|
235
|
+
firstChild.value = `${currentNumber}. ${firstChild.value}`;
|
|
236
|
+
currentNumber++;
|
|
237
|
+
} else if (firstChild.type === 'link') {
|
|
238
|
+
// Insert a text node with the number before the link
|
|
239
|
+
paragraph.children.unshift({
|
|
240
|
+
type: 'text',
|
|
241
|
+
value: `${currentNumber}. `
|
|
242
|
+
});
|
|
243
|
+
currentNumber++;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
return ast;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Insert TOC at the beginning of document
|
|
255
|
+
*
|
|
256
|
+
* Automatically generates and inserts a table of contents at the beginning
|
|
257
|
+
* of the document, after the title heading
|
|
258
|
+
*
|
|
259
|
+
* @param {Object} ast - Markdown AST
|
|
260
|
+
* @param {string} selector - Optional selector (not used in this function)
|
|
261
|
+
* @param {Object} options - Options for TOC generation
|
|
262
|
+
* @returns {Object} Modified AST with TOC inserted
|
|
263
|
+
*/
|
|
264
|
+
function insertTOC(ast, selector, options = {}) {
|
|
265
|
+
// Generate TOC content
|
|
266
|
+
const headings = [];
|
|
267
|
+
visit(ast, 'heading', (node) => {
|
|
268
|
+
// Skip level 1 headings (title) and TOC heading itself
|
|
269
|
+
if (node.depth > 1 && toString(node).toLowerCase() !== 'table of contents') {
|
|
270
|
+
headings.push({
|
|
271
|
+
level: node.depth,
|
|
272
|
+
text: toString(node),
|
|
273
|
+
anchor: toString(node).toLowerCase().replace(/[^\w]+/g, '-')
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
// Create TOC heading
|
|
279
|
+
const tocHeading = {
|
|
280
|
+
type: 'heading',
|
|
281
|
+
depth: 2,
|
|
282
|
+
children: [{ type: 'text', value: 'Table of Contents' }]
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
// Create TOC list
|
|
286
|
+
const tocItems = headings.map(heading => {
|
|
287
|
+
const indent = ' '.repeat(heading.level - 2);
|
|
288
|
+
return {
|
|
289
|
+
type: 'listItem',
|
|
290
|
+
children: [{
|
|
291
|
+
type: 'paragraph',
|
|
292
|
+
children: [{
|
|
293
|
+
type: 'link',
|
|
294
|
+
url: `#${heading.anchor}`,
|
|
295
|
+
children: [{ type: 'text', value: heading.text }]
|
|
296
|
+
}]
|
|
297
|
+
}]
|
|
298
|
+
};
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
const tocList = {
|
|
302
|
+
type: 'list',
|
|
303
|
+
ordered: options.ordered || false,
|
|
304
|
+
children: tocItems
|
|
305
|
+
};
|
|
306
|
+
|
|
307
|
+
// Find position to insert TOC (after title)
|
|
308
|
+
let insertPosition = 0;
|
|
309
|
+
if (ast.children.length > 0 && ast.children[0].type === 'heading' && ast.children[0].depth === 1) {
|
|
310
|
+
insertPosition = 1;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// Insert TOC
|
|
314
|
+
ast.children.splice(insertPosition, 0, tocHeading, tocList);
|
|
315
|
+
|
|
316
|
+
return ast;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Convert HTML to Markdown
|
|
321
|
+
*
|
|
322
|
+
* Transforms HTML content to markdown format
|
|
323
|
+
*
|
|
324
|
+
* @param {Object} ast - Markdown AST (not used in this function)
|
|
325
|
+
* @param {string} selector - Optional selector (not used in this function)
|
|
326
|
+
* @param {string} html - HTML content to convert
|
|
327
|
+
* @returns {string} Converted markdown content
|
|
328
|
+
*/
|
|
329
|
+
function convertHTMLToMarkdown(ast, selector, html) {
|
|
330
|
+
if (!html) {
|
|
331
|
+
return '';
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// Simple HTML to markdown conversion
|
|
335
|
+
let markdown = html;
|
|
336
|
+
|
|
337
|
+
// We'll handle HTML entities separately to prevent them from being treated as tags
|
|
338
|
+
const containsEntities = /<|>|&|"|'/.test(markdown);
|
|
339
|
+
|
|
340
|
+
// Convert headings
|
|
341
|
+
markdown = markdown.replace(/<h1[^>]*>(.*?)<\/h1>/gi, '# $1\n\n');
|
|
342
|
+
markdown = markdown.replace(/<h2[^>]*>(.*?)<\/h2>/gi, '## $1\n\n');
|
|
343
|
+
markdown = markdown.replace(/<h3[^>]*>(.*?)<\/h3>/gi, '### $1\n\n');
|
|
344
|
+
markdown = markdown.replace(/<h4[^>]*>(.*?)<\/h4>/gi, '#### $1\n\n');
|
|
345
|
+
markdown = markdown.replace(/<h5[^>]*>(.*?)<\/h5>/gi, '##### $1\n\n');
|
|
346
|
+
markdown = markdown.replace(/<h6[^>]*>(.*?)<\/h6>/gi, '###### $1\n\n');
|
|
347
|
+
|
|
348
|
+
// Convert paragraphs
|
|
349
|
+
markdown = markdown.replace(/<p[^>]*>(.*?)<\/p>/gi, '$1\n\n');
|
|
350
|
+
|
|
351
|
+
// Convert links
|
|
352
|
+
markdown = markdown.replace(/<a[^>]*href="(.*?)"[^>]*>(.*?)<\/a>/gi, '[$2]($1)');
|
|
353
|
+
|
|
354
|
+
// Convert strong/bold
|
|
355
|
+
markdown = markdown.replace(/<(strong|b)[^>]*>(.*?)<\/(strong|b)>/gi, '**$2**');
|
|
356
|
+
|
|
357
|
+
// Convert emphasis/italic
|
|
358
|
+
markdown = markdown.replace(/<(em|i)[^>]*>(.*?)<\/(em|i)>/gi, '*$2*');
|
|
359
|
+
|
|
360
|
+
// Convert unordered lists
|
|
361
|
+
markdown = markdown.replace(/<ul[^>]*>(.*?)<\/ul>/gis, (match, content) => {
|
|
362
|
+
return content.replace(/<li[^>]*>(.*?)<\/li>/gi, '- $1\n');
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
// Convert ordered lists
|
|
366
|
+
markdown = markdown.replace(/<ol[^>]*>(.*?)<\/ol>/gis, (match, content) => {
|
|
367
|
+
let index = 1;
|
|
368
|
+
return content.replace(/<li[^>]*>(.*?)<\/li>/gi, (match, item) => {
|
|
369
|
+
return `${index++}. ${item}\n`;
|
|
370
|
+
});
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
// Convert code blocks
|
|
374
|
+
markdown = markdown.replace(/<pre[^>]*><code[^>]*>(.*?)<\/code><\/pre>/gis, '```\n$1\n```\n\n');
|
|
375
|
+
|
|
376
|
+
// Convert inline code
|
|
377
|
+
markdown = markdown.replace(/<code[^>]*>(.*?)<\/code>/gi, '`$1`');
|
|
378
|
+
|
|
379
|
+
// Convert blockquotes
|
|
380
|
+
markdown = markdown.replace(/<blockquote[^>]*>(.*?)<\/blockquote>/gis, '> $1\n\n');
|
|
381
|
+
|
|
382
|
+
// Convert horizontal rules
|
|
383
|
+
markdown = markdown.replace(/<hr[^>]*>/gi, '---\n\n');
|
|
384
|
+
|
|
385
|
+
// Remove remaining HTML tags
|
|
386
|
+
markdown = markdown.replace(/<[^>]*>/g, '');
|
|
387
|
+
|
|
388
|
+
// Decode HTML entities after tags are removed
|
|
389
|
+
markdown = markdown
|
|
390
|
+
.replace(/</g, '<')
|
|
391
|
+
.replace(/>/g, '>')
|
|
392
|
+
.replace(/&/g, '&')
|
|
393
|
+
.replace(/"/g, '"')
|
|
394
|
+
.replace(/'/g, '\'');
|
|
395
|
+
|
|
396
|
+
// Fix extra newlines
|
|
397
|
+
markdown = markdown.replace(/\n{3,}/g, '\n\n');
|
|
398
|
+
|
|
399
|
+
return markdown.trim();
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
export {
|
|
403
|
+
makeCodeBlocksCollapsible,
|
|
404
|
+
makeDescriptiveToc,
|
|
405
|
+
addCrossLinks,
|
|
406
|
+
fixHeadingHierarchy,
|
|
407
|
+
moveSection,
|
|
408
|
+
updateTOCNumbers,
|
|
409
|
+
insertTOC,
|
|
410
|
+
convertHTMLToMarkdown
|
|
411
|
+
};
|