@takazudo/mdx-formatter 0.1.0
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/LICENSE +21 -0
- package/README.md +72 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +128 -0
- package/dist/html-block-formatter.d.ts +46 -0
- package/dist/html-block-formatter.js +370 -0
- package/dist/hybrid-formatter.d.ts +59 -0
- package/dist/hybrid-formatter.js +977 -0
- package/dist/indent-detector.d.ts +62 -0
- package/dist/indent-detector.js +358 -0
- package/dist/index.d.ts +28 -0
- package/dist/index.js +57 -0
- package/dist/load-config.d.ts +13 -0
- package/dist/load-config.js +71 -0
- package/dist/plugins/docusaurus-admonitions.d.ts +5 -0
- package/dist/plugins/docusaurus-admonitions.js +42 -0
- package/dist/plugins/fix-autolink-output.d.ts +4 -0
- package/dist/plugins/fix-autolink-output.js +24 -0
- package/dist/plugins/fix-formatting-issues.d.ts +4 -0
- package/dist/plugins/fix-formatting-issues.js +42 -0
- package/dist/plugins/fix-paragraph-spacing.d.ts +5 -0
- package/dist/plugins/fix-paragraph-spacing.js +96 -0
- package/dist/plugins/html-definition-list.d.ts +5 -0
- package/dist/plugins/html-definition-list.js +64 -0
- package/dist/plugins/japanese-text.d.ts +5 -0
- package/dist/plugins/japanese-text.js +79 -0
- package/dist/plugins/normalize-lists.d.ts +5 -0
- package/dist/plugins/normalize-lists.js +58 -0
- package/dist/plugins/preprocess-japanese.d.ts +7 -0
- package/dist/plugins/preprocess-japanese.js +15 -0
- package/dist/plugins/preserve-image-alt.d.ts +8 -0
- package/dist/plugins/preserve-image-alt.js +19 -0
- package/dist/plugins/preserve-jsx.d.ts +6 -0
- package/dist/plugins/preserve-jsx.js +48 -0
- package/dist/settings.d.ts +7 -0
- package/dist/settings.js +91 -0
- package/dist/specific-formatter.d.ts +30 -0
- package/dist/specific-formatter.js +469 -0
- package/dist/types.d.ts +226 -0
- package/dist/types.js +4 -0
- package/dist/utils.d.ts +12 -0
- package/dist/utils.js +47 -0
- package/format-stdin.js +36 -0
- package/package.json +107 -0
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Post-processing fixes for common formatting issues
|
|
3
|
+
*/
|
|
4
|
+
export function fixFormattingIssues(content) {
|
|
5
|
+
let fixed = content;
|
|
6
|
+
// Fix 1: Restore spaces between bold elements and operators
|
|
7
|
+
// When bold text is followed by operators like +, =, etc., ensure there's a space
|
|
8
|
+
// This must be done BEFORE fixing the internal spaces to preserve the pattern
|
|
9
|
+
// Use negative lookahead to avoid matching operators that already have a space
|
|
10
|
+
fixed = fixed.replace(/(\*\*[^*]+\*\*)([+\-=])/g, '$1 $2');
|
|
11
|
+
// Fix 2: Restore spaces before bold elements after operators
|
|
12
|
+
// Ensure there's a space after operators before bold text
|
|
13
|
+
fixed = fixed.replace(/([+\-=])(\*\*[^*]+\*\*)/g, '$1 $2');
|
|
14
|
+
// Fix 3: Restore spaces between word characters and bold elements
|
|
15
|
+
fixed = fixed.replace(/([a-zA-Z])\s*(\*\*[^*]+\*\*)/g, '$1 $2');
|
|
16
|
+
// Fix 4: Remove extra spaces inside bold asterisks (do this LAST)
|
|
17
|
+
// Sometimes remark adds a space before the closing ** for short words
|
|
18
|
+
// Pattern: **word ** -> **word**
|
|
19
|
+
fixed = fixed.replace(/\*\*([^*]+)\s+\*\*/g, '**$1**');
|
|
20
|
+
// Fix 5: Remove extra trailing spaces inside HTML tags (2+ spaces only)
|
|
21
|
+
// Pattern: <tag>content </tag> -> <tag>content </tag> (preserves single space)
|
|
22
|
+
fixed = fixed.replace(/(<[^/>]+>)([^<]*)\s{2,}(<\/[^>]+>)/g, '$1$2 $3');
|
|
23
|
+
// Fix 6: Collapse multiple spaces before self-closing HTML tags to single space
|
|
24
|
+
// Pattern: text <br/> -> text <br/> (preserves meaningful single space)
|
|
25
|
+
fixed = fixed.replace(/\s{2,}(<[^>]+\/>)/g, ' $1');
|
|
26
|
+
// Fix 7: Fix HTML entity encoding for Japanese characters
|
|
27
|
+
// Specifically fix the common case of を being encoded as を
|
|
28
|
+
fixed = fixed.replace(/を/g, 'を');
|
|
29
|
+
// Fix other common Japanese character encodings if they appear
|
|
30
|
+
fixed = fixed.replace(/&#x([0-9a-fA-F]{4});/g, (match, hex) => {
|
|
31
|
+
const code = parseInt(hex, 16);
|
|
32
|
+
// Only decode Japanese characters (Hiragana, Katakana, Kanji ranges)
|
|
33
|
+
if ((code >= 0x3040 && code <= 0x309f) || // Hiragana
|
|
34
|
+
(code >= 0x30a0 && code <= 0x30ff) || // Katakana
|
|
35
|
+
(code >= 0x4e00 && code <= 0x9faf)) {
|
|
36
|
+
// CJK Unified Ideographs
|
|
37
|
+
return String.fromCharCode(code);
|
|
38
|
+
}
|
|
39
|
+
return match;
|
|
40
|
+
});
|
|
41
|
+
return fixed;
|
|
42
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plugin to fix paragraph and JSX spacing issues
|
|
3
|
+
* This runs as a post-processing step after stringify
|
|
4
|
+
*/
|
|
5
|
+
export function fixParagraphSpacing(content) {
|
|
6
|
+
let fixed = content;
|
|
7
|
+
// Fix 0: Split collapsed JSX components (most critical fix)
|
|
8
|
+
// Pattern: /><Component should be />\n\n<Component
|
|
9
|
+
// Use a loop to handle all occurrences (in case of multiple collapsed components)
|
|
10
|
+
let previousFixed = '';
|
|
11
|
+
while (previousFixed !== fixed) {
|
|
12
|
+
previousFixed = fixed;
|
|
13
|
+
fixed = fixed.replace(/(\/>)(<[A-Z][^>]*>)/g, '$1\n\n$2');
|
|
14
|
+
}
|
|
15
|
+
// Fix 1: Ensure blank lines between paragraphs
|
|
16
|
+
// Split content by lines and process
|
|
17
|
+
const lines = fixed.split('\n');
|
|
18
|
+
const processedLines = [];
|
|
19
|
+
for (let i = 0; i < lines.length; i++) {
|
|
20
|
+
const line = lines[i];
|
|
21
|
+
const nextLine = lines[i + 1];
|
|
22
|
+
processedLines.push(line);
|
|
23
|
+
// Check if we need to add a blank line
|
|
24
|
+
if (line && nextLine && i < lines.length - 1) {
|
|
25
|
+
const currentEndsWithText = /[。!?\.!?)\]】a-zA-Z0-9]$/.test(line.trim());
|
|
26
|
+
const nextStartsWithJSX = /^<[A-Z]/.test(nextLine.trim());
|
|
27
|
+
// Add blank line between text and JSX components
|
|
28
|
+
if (currentEndsWithText && nextStartsWithJSX) {
|
|
29
|
+
processedLines.push('');
|
|
30
|
+
}
|
|
31
|
+
// Add blank line between heading and content
|
|
32
|
+
else if (/^#{1,6} /.test(line.trim()) && nextLine.trim() && !lines[i - 1]?.trim()) {
|
|
33
|
+
processedLines.push('');
|
|
34
|
+
}
|
|
35
|
+
// Add blank line between import/export and other content
|
|
36
|
+
else if ((line.trim().startsWith('import ') || line.trim().startsWith('export ')) &&
|
|
37
|
+
nextLine.trim() &&
|
|
38
|
+
!nextLine.trim().startsWith('import ') &&
|
|
39
|
+
!nextLine.trim().startsWith('export ')) {
|
|
40
|
+
processedLines.push('');
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
fixed = processedLines.join('\n');
|
|
45
|
+
// Fix 2: Ensure JSX components are not merged with text on same line
|
|
46
|
+
// Pattern: text<Component should be text\n\n<Component
|
|
47
|
+
fixed = fixed.replace(/([。!?\.!?)\]】a-zA-Z0-9])(<[A-Z][^>]*>)/g, '$1\n\n$2');
|
|
48
|
+
// Fix 3: Ensure import statements are not merged with JSX
|
|
49
|
+
fixed = fixed.replace(/(import [^;]+;)(<[A-Z])/g, '$1\n\n$2');
|
|
50
|
+
// Fix 4: Ensure headings are not merged with content
|
|
51
|
+
fixed = fixed.replace(/(^#{1,6} [^\n]+)(<[A-Z])/gm, '$1\n\n$2');
|
|
52
|
+
// Fix 5: Split merged paragraphs (Japanese text specific)
|
|
53
|
+
// Pattern: 。text should be 。\ntext when it's a new sentence
|
|
54
|
+
fixed = fixed.replace(/([。!?])([ぁ-んァ-ヶー一-龠]+)/g, (match, punct, text) => {
|
|
55
|
+
// Check if the text after punctuation looks like a new sentence
|
|
56
|
+
// (starts with a capital-like character or common sentence starters)
|
|
57
|
+
if (/^[こそたなはまやらわがざだばぱ]/.test(text)) {
|
|
58
|
+
return `${punct}\n${text}`;
|
|
59
|
+
}
|
|
60
|
+
return match;
|
|
61
|
+
});
|
|
62
|
+
// Fix 6: Preserve space after question mark in Japanese
|
|
63
|
+
fixed = fixed.replace(/([?!])([^ \n])/g, '$1 $2');
|
|
64
|
+
// Fix 7: Clean up excessive blank lines (more than 2 consecutive)
|
|
65
|
+
fixed = fixed.replace(/\n{4,}/g, '\n\n\n');
|
|
66
|
+
// Fix 8: Fix JSX indentation issues
|
|
67
|
+
// When we have closing tags that lost one space of indentation
|
|
68
|
+
fixed = fixed.replace(/\n( +)<\/([A-Z][^>]+)>/g, (match, spaces, tag) => {
|
|
69
|
+
// Check if the opening tag had more indentation
|
|
70
|
+
const openingPattern = new RegExp(`\\n(\\s+)<${tag}[^>]*>`, 'g');
|
|
71
|
+
const openingMatch = fixed.match(openingPattern);
|
|
72
|
+
if (openingMatch && openingMatch[0]) {
|
|
73
|
+
const openingSpacesMatch = openingMatch[0].match(/\n(\s+)</);
|
|
74
|
+
if (openingSpacesMatch) {
|
|
75
|
+
const openingSpaces = openingSpacesMatch[1];
|
|
76
|
+
if (openingSpaces.length === spaces.length + 1) {
|
|
77
|
+
// The opening tag has one more space, so add it to closing tag
|
|
78
|
+
return '\n' + spaces + ' </' + tag + '>';
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return match;
|
|
83
|
+
});
|
|
84
|
+
// Fix 9: Fix Outro component spacing specifically
|
|
85
|
+
// Ensure content inside Outro has proper spacing
|
|
86
|
+
fixed = fixed.replace(/<Outro>([\s\S]*?)<\/Outro>/g, (match, content) => {
|
|
87
|
+
// Check if there's content
|
|
88
|
+
const trimmedContent = content.trim();
|
|
89
|
+
if (trimmedContent) {
|
|
90
|
+
// Ensure double newlines before and after content
|
|
91
|
+
return '<Outro>\n\n' + trimmedContent + '\n\n</Outro>';
|
|
92
|
+
}
|
|
93
|
+
return match;
|
|
94
|
+
});
|
|
95
|
+
return fixed;
|
|
96
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { visit } from 'unist-util-visit';
|
|
2
|
+
/**
|
|
3
|
+
* Plugin to convert HTML definition lists to markdown
|
|
4
|
+
*/
|
|
5
|
+
export function htmlDefinitionListPlugin() {
|
|
6
|
+
return (tree) => {
|
|
7
|
+
// In MDX, raw HTML can appear as either 'html' or 'mdxFlowExpression' nodes
|
|
8
|
+
// We need to handle both cases
|
|
9
|
+
visit(tree, (node, index, parent) => {
|
|
10
|
+
if (node.type !== 'html' && node.type !== 'raw')
|
|
11
|
+
return;
|
|
12
|
+
const htmlNode = node;
|
|
13
|
+
const parentNode = parent;
|
|
14
|
+
// For 'raw' nodes (MDX), the content is in node.value
|
|
15
|
+
// For 'html' nodes (regular markdown), it's also in node.value
|
|
16
|
+
if (!htmlNode.value)
|
|
17
|
+
return;
|
|
18
|
+
// Check if this is a definition list
|
|
19
|
+
const dlMatch = htmlNode.value.match(/^<dl[^>]*>([\s\S]*?)<\/dl>$/);
|
|
20
|
+
if (!dlMatch)
|
|
21
|
+
return;
|
|
22
|
+
const content = dlMatch[1];
|
|
23
|
+
const items = [];
|
|
24
|
+
// Parse dt/dd pairs
|
|
25
|
+
const regex = /<dt[^>]*>([\s\S]*?)<\/dt>\s*<dd[^>]*>([\s\S]*?)<\/dd>/g;
|
|
26
|
+
let match;
|
|
27
|
+
while ((match = regex.exec(content)) !== null) {
|
|
28
|
+
const term = cleanHtml(match[1].trim());
|
|
29
|
+
const definition = cleanHtml(match[2].trim());
|
|
30
|
+
// Create markdown definition list nodes
|
|
31
|
+
items.push({
|
|
32
|
+
type: 'paragraph',
|
|
33
|
+
children: [
|
|
34
|
+
{
|
|
35
|
+
type: 'strong',
|
|
36
|
+
children: [{ type: 'text', value: term }],
|
|
37
|
+
},
|
|
38
|
+
],
|
|
39
|
+
});
|
|
40
|
+
items.push({
|
|
41
|
+
type: 'paragraph',
|
|
42
|
+
children: [{ type: 'text', value: ': ' + definition }],
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
// Replace the HTML node with markdown nodes
|
|
46
|
+
if (items.length > 0 && parentNode && typeof index === 'number') {
|
|
47
|
+
parentNode.children.splice(index, 1, ...items);
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Clean HTML tags from text content
|
|
54
|
+
*/
|
|
55
|
+
function cleanHtml(html) {
|
|
56
|
+
return html
|
|
57
|
+
.replace(/<code[^>]*>(.*?)<\/code>/g, '`$1`')
|
|
58
|
+
.replace(/<strong[^>]*>(.*?)<\/strong>/g, '**$1**')
|
|
59
|
+
.replace(/<b[^>]*>(.*?)<\/b>/g, '**$1**')
|
|
60
|
+
.replace(/<em[^>]*>(.*?)<\/em>/g, '*$1*')
|
|
61
|
+
.replace(/<i[^>]*>(.*?)<\/i>/g, '*$1*')
|
|
62
|
+
.replace(/<[^>]+>/g, '')
|
|
63
|
+
.trim();
|
|
64
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { visit } from 'unist-util-visit';
|
|
2
|
+
/**
|
|
3
|
+
* Plugin to handle Japanese text formatting rules
|
|
4
|
+
*/
|
|
5
|
+
export function japaneseTextPlugin() {
|
|
6
|
+
return (tree) => {
|
|
7
|
+
// Note: Japanese URLs are now pre-processed in the main format function
|
|
8
|
+
// to prevent GFM from incorrectly parsing them
|
|
9
|
+
// Clean up Japanese text
|
|
10
|
+
visit(tree, 'text', (node, index, parent) => {
|
|
11
|
+
const textNode = node;
|
|
12
|
+
const parentNode = parent;
|
|
13
|
+
if (!textNode.value)
|
|
14
|
+
return;
|
|
15
|
+
// Check if this text node is adjacent to inline code or strong/emphasis
|
|
16
|
+
// If so, preserve spaces around it
|
|
17
|
+
const isBeforeInlineCode = parentNode &&
|
|
18
|
+
parentNode.children &&
|
|
19
|
+
typeof index === 'number' &&
|
|
20
|
+
index < parentNode.children.length - 1 &&
|
|
21
|
+
parentNode.children[index + 1].type === 'inlineCode';
|
|
22
|
+
const isAfterInlineCode = parentNode &&
|
|
23
|
+
parentNode.children &&
|
|
24
|
+
typeof index === 'number' &&
|
|
25
|
+
index > 0 &&
|
|
26
|
+
parentNode.children[index - 1].type === 'inlineCode';
|
|
27
|
+
// Check if adjacent to strong or emphasis elements
|
|
28
|
+
const isBeforeStrong = parentNode &&
|
|
29
|
+
parentNode.children &&
|
|
30
|
+
typeof index === 'number' &&
|
|
31
|
+
index < parentNode.children.length - 1 &&
|
|
32
|
+
(parentNode.children[index + 1].type === 'strong' ||
|
|
33
|
+
parentNode.children[index + 1].type === 'emphasis');
|
|
34
|
+
const isAfterStrong = parentNode &&
|
|
35
|
+
parentNode.children &&
|
|
36
|
+
typeof index === 'number' &&
|
|
37
|
+
index > 0 &&
|
|
38
|
+
(parentNode.children[index - 1].type === 'strong' ||
|
|
39
|
+
parentNode.children[index - 1].type === 'emphasis');
|
|
40
|
+
// Preserve Japanese punctuation and spacing
|
|
41
|
+
// Don't add extra spaces around Japanese punctuation
|
|
42
|
+
textNode.value = textNode.value
|
|
43
|
+
.replace(/\s+([、。!?])/g, '$1') // Remove spaces before Japanese punctuation
|
|
44
|
+
.replace(/([、。!?])\s+/g, '$1'); // Remove spaces after Japanese punctuation
|
|
45
|
+
// Only trim trailing spaces if:
|
|
46
|
+
// 1. Not adjacent to code or strong/emphasis elements
|
|
47
|
+
// 2. The text node doesn't contain operators that need spacing
|
|
48
|
+
// This preserves spaces between bold elements like "**VCA** + **Envelope**"
|
|
49
|
+
const hasOperators = /[+\-=*/<>]/.test(textNode.value);
|
|
50
|
+
if (!isBeforeInlineCode &&
|
|
51
|
+
!isAfterInlineCode &&
|
|
52
|
+
!isBeforeStrong &&
|
|
53
|
+
!isAfterStrong &&
|
|
54
|
+
!hasOperators) {
|
|
55
|
+
// Only trim if the entire value is whitespace or ends with multiple spaces
|
|
56
|
+
if (textNode.value.trim() === '') {
|
|
57
|
+
textNode.value = '';
|
|
58
|
+
}
|
|
59
|
+
else if (/\s{2,}$/.test(textNode.value)) {
|
|
60
|
+
// Only trim if there are 2+ trailing spaces
|
|
61
|
+
textNode.value = textNode.value.replace(/\s+$/g, ' ');
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
// Handle spacing around Japanese headings
|
|
66
|
+
visit(tree, 'heading', (node) => {
|
|
67
|
+
const heading = node;
|
|
68
|
+
if (heading.children && heading.children[0] && heading.children[0].type === 'text') {
|
|
69
|
+
const textChild = heading.children[0];
|
|
70
|
+
const text = textChild.value;
|
|
71
|
+
// Check if the heading contains Japanese characters
|
|
72
|
+
if (/[\u3000-\u303f\u3040-\u309f\u30a0-\u30ff\uff00-\uffef\u4e00-\u9faf]/.test(text)) {
|
|
73
|
+
// Ensure proper formatting for Japanese headings
|
|
74
|
+
textChild.value = text.trim();
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
};
|
|
79
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { visit } from 'unist-util-visit';
|
|
2
|
+
/**
|
|
3
|
+
* Plugin to normalize list markers and merge adjacent lists
|
|
4
|
+
*/
|
|
5
|
+
export function normalizeListsPlugin() {
|
|
6
|
+
return (tree) => {
|
|
7
|
+
// First, merge consecutive lists of the same type
|
|
8
|
+
visit(tree, (node) => {
|
|
9
|
+
const parent = node;
|
|
10
|
+
// Check if this node has children array
|
|
11
|
+
if (!parent.children || !Array.isArray(parent.children))
|
|
12
|
+
return;
|
|
13
|
+
const newChildren = [];
|
|
14
|
+
let i = 0;
|
|
15
|
+
while (i < parent.children.length) {
|
|
16
|
+
const child = parent.children[i];
|
|
17
|
+
// Check if this is a list and the next child is also a list of the same type
|
|
18
|
+
if (child.type === 'list' && i + 1 < parent.children.length) {
|
|
19
|
+
const nextChild = parent.children[i + 1];
|
|
20
|
+
if (nextChild.type === 'list' &&
|
|
21
|
+
child.ordered === nextChild.ordered &&
|
|
22
|
+
child.start === nextChild.start) {
|
|
23
|
+
// Merge the lists
|
|
24
|
+
child.children = [...child.children, ...nextChild.children];
|
|
25
|
+
i++; // Skip the next child since we merged it
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
newChildren.push(child);
|
|
29
|
+
i++;
|
|
30
|
+
}
|
|
31
|
+
parent.children = newChildren;
|
|
32
|
+
});
|
|
33
|
+
// Normalize all unordered list items to use - as marker
|
|
34
|
+
visit(tree, 'listItem', (node, _index, parent) => {
|
|
35
|
+
const listItem = node;
|
|
36
|
+
const parentList = parent;
|
|
37
|
+
if (parentList && parentList.ordered === false) {
|
|
38
|
+
// This is an unordered list item
|
|
39
|
+
// The marker is controlled by stringify options, but we ensure consistency
|
|
40
|
+
if (listItem.data) {
|
|
41
|
+
delete listItem.data.marker; // Remove any specific marker data
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
// Ensure list structure is correct
|
|
46
|
+
visit(tree, 'list', (node) => {
|
|
47
|
+
const list = node;
|
|
48
|
+
// Ensure ordered property is set correctly
|
|
49
|
+
if (list.ordered === undefined) {
|
|
50
|
+
list.ordered = false; // Default to unordered
|
|
51
|
+
}
|
|
52
|
+
// Mark as tight list if items are not spread
|
|
53
|
+
if (list.children && list.children.every((item) => !item.spread)) {
|
|
54
|
+
list.spread = false;
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
};
|
|
58
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Root } from 'mdast';
|
|
2
|
+
import type { VFile } from 'vfile';
|
|
3
|
+
/**
|
|
4
|
+
* Pre-processing plugin to normalize Japanese text before parsing
|
|
5
|
+
* This runs on the raw text before the markdown parser
|
|
6
|
+
*/
|
|
7
|
+
export declare function preprocessJapanesePlugin(): (_tree: Root, file: VFile) => void;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pre-processing plugin to normalize Japanese text before parsing
|
|
3
|
+
* This runs on the raw text before the markdown parser
|
|
4
|
+
*/
|
|
5
|
+
export function preprocessJapanesePlugin() {
|
|
6
|
+
return function (_tree, file) {
|
|
7
|
+
// Work on the raw content before parsing
|
|
8
|
+
const content = String(file);
|
|
9
|
+
// Convert Japanese parentheses with URLs to standard markdown links
|
|
10
|
+
const urlInParensPattern = /([^()]+)((https?:\/\/[^)]+))/g;
|
|
11
|
+
const processed = content.replace(urlInParensPattern, '[$1]($2)');
|
|
12
|
+
// Update the file content
|
|
13
|
+
file.value = processed;
|
|
14
|
+
};
|
|
15
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pre-process content to preserve image alt text from directive parsing
|
|
3
|
+
*/
|
|
4
|
+
export declare function protectImageAltText(content: string): string;
|
|
5
|
+
/**
|
|
6
|
+
* Post-process content to restore image alt text
|
|
7
|
+
*/
|
|
8
|
+
export declare function restoreImageAltText(content: string): string;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pre-process content to preserve image alt text from directive parsing
|
|
3
|
+
*/
|
|
4
|
+
export function protectImageAltText(content) {
|
|
5
|
+
// Replace colons in image alt text with a placeholder that won't be parsed as a directive
|
|
6
|
+
// Match ![...] patterns and protect ALL colons within them
|
|
7
|
+
return content.replace(/!\[([^\]]*)\]/g, (_match, altText) => {
|
|
8
|
+
// Replace all colons in the alt text with a placeholder
|
|
9
|
+
const protectedAlt = altText.replace(/:/g, '___COLON___');
|
|
10
|
+
return `![${protectedAlt}]`;
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Post-process content to restore image alt text
|
|
15
|
+
*/
|
|
16
|
+
export function restoreImageAltText(content) {
|
|
17
|
+
// Restore the colons in image alt text - handle both escaped and unescaped versions
|
|
18
|
+
return content.replace(/___COLON___/g, ':').replace(/\\_\\_\\_COLON\\_\\_\\_/g, ':'); // In case underscores got escaped
|
|
19
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { visit } from 'unist-util-visit';
|
|
2
|
+
/**
|
|
3
|
+
* Plugin to preserve JSX/MDX component formatting and indentation
|
|
4
|
+
*/
|
|
5
|
+
export function preserveJsxPlugin() {
|
|
6
|
+
return (tree, file) => {
|
|
7
|
+
const originalContent = String(file);
|
|
8
|
+
const lines = originalContent.split('\n');
|
|
9
|
+
visit(tree, ['mdxJsxFlowElement', 'mdxJsxTextElement'], (node) => {
|
|
10
|
+
const jsxNode = node;
|
|
11
|
+
// Store the original JSX content to preserve formatting
|
|
12
|
+
if (jsxNode.position && jsxNode.position.start && jsxNode.position.end) {
|
|
13
|
+
const startLine = jsxNode.position.start.line - 1;
|
|
14
|
+
const startCol = jsxNode.position.start.column - 1;
|
|
15
|
+
const endLine = jsxNode.position.end.line - 1;
|
|
16
|
+
const endCol = jsxNode.position.end.column;
|
|
17
|
+
// Extract the original JSX content preserving indentation
|
|
18
|
+
let originalJsx = '';
|
|
19
|
+
if (startLine === endLine) {
|
|
20
|
+
// Single line JSX
|
|
21
|
+
originalJsx = lines[startLine].substring(startCol, endCol);
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
// Multi-line JSX - preserve exact formatting
|
|
25
|
+
for (let i = startLine; i <= endLine; i++) {
|
|
26
|
+
if (i === startLine) {
|
|
27
|
+
originalJsx += lines[i].substring(startCol);
|
|
28
|
+
}
|
|
29
|
+
else if (i === endLine) {
|
|
30
|
+
originalJsx += '\n' + lines[i].substring(0, endCol);
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
originalJsx += '\n' + lines[i];
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
// Store the original content in node data
|
|
38
|
+
jsxNode.data = jsxNode.data || {};
|
|
39
|
+
jsxNode.data.originalJsx = originalJsx;
|
|
40
|
+
jsxNode.data.preserveOriginal = true;
|
|
41
|
+
}
|
|
42
|
+
// Handle self-closing tags
|
|
43
|
+
if (!jsxNode.children || jsxNode.children.length === 0) {
|
|
44
|
+
jsxNode.selfClosing = true;
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
};
|
|
48
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Markdown Formatter Settings - 7 Core Rules
|
|
3
|
+
* Each option can be toggled on/off independently
|
|
4
|
+
*/
|
|
5
|
+
import type { FormatterSettings } from './types.js';
|
|
6
|
+
export declare const formatterSettings: FormatterSettings;
|
|
7
|
+
export declare function getEnabledRules(): Partial<FormatterSettings>;
|
package/dist/settings.js
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Markdown Formatter Settings - 7 Core Rules
|
|
3
|
+
* Each option can be toggled on/off independently
|
|
4
|
+
*/
|
|
5
|
+
export const formatterSettings = {
|
|
6
|
+
// Rule 1: Add 1 empty line between markdown elements (headings, paragraphs, lists)
|
|
7
|
+
addEmptyLineBetweenElements: {
|
|
8
|
+
enabled: true,
|
|
9
|
+
description: 'Add single empty line between markdown elements',
|
|
10
|
+
},
|
|
11
|
+
// Rule 2: Format multi-line JSX/HTML components with proper indentation
|
|
12
|
+
formatMultiLineJsx: {
|
|
13
|
+
enabled: true,
|
|
14
|
+
description: 'Format JSX/HTML with proper indentation',
|
|
15
|
+
indentSize: 2,
|
|
16
|
+
// Components to ignore (preserve their formatting completely)
|
|
17
|
+
ignoreComponents: [],
|
|
18
|
+
},
|
|
19
|
+
// Rule 3: Format all HTML blocks within MDX using Prettier
|
|
20
|
+
formatHtmlBlocksInMdx: {
|
|
21
|
+
enabled: true,
|
|
22
|
+
description: 'Format all HTML blocks within MDX using Prettier',
|
|
23
|
+
formatterConfig: {
|
|
24
|
+
parser: 'html',
|
|
25
|
+
tabWidth: 2,
|
|
26
|
+
useTabs: false,
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
// Rule 4: Convert single-line JSX with multiple props to multi-line format
|
|
30
|
+
expandSingleLineJsx: {
|
|
31
|
+
enabled: false, // disabled until inline JSX content-loss bug is fixed
|
|
32
|
+
description: 'Expand single-line JSX components with multiple props to multi-line',
|
|
33
|
+
propsThreshold: 2, // Expand if this many props or more
|
|
34
|
+
// Also respects ignoreComponents from formatMultiLineJsx
|
|
35
|
+
},
|
|
36
|
+
// Rule 5: Indent content inside JSX container components (DISABLED)
|
|
37
|
+
// Disabled: We prefer flat formatting for simplicity in MDX files
|
|
38
|
+
indentJsxContent: {
|
|
39
|
+
enabled: false,
|
|
40
|
+
description: 'Add indentation to content inside JSX components like <Outro>',
|
|
41
|
+
indentSize: 2,
|
|
42
|
+
// Components that should have their content indented
|
|
43
|
+
containerComponents: [],
|
|
44
|
+
},
|
|
45
|
+
// Rule 6: Add empty lines inside block JSX components
|
|
46
|
+
// Adds empty lines after opening tag and before closing tag for better readability
|
|
47
|
+
addEmptyLinesInBlockJsx: {
|
|
48
|
+
enabled: true,
|
|
49
|
+
description: 'Add empty lines after opening and before closing tags in block JSX components',
|
|
50
|
+
// Components that should have empty lines added
|
|
51
|
+
blockComponents: [],
|
|
52
|
+
},
|
|
53
|
+
// Rule 7: Format YAML frontmatter
|
|
54
|
+
formatYamlFrontmatter: {
|
|
55
|
+
enabled: true,
|
|
56
|
+
description: 'Format YAML frontmatter using proper YAML formatting rules',
|
|
57
|
+
// YAML formatting options
|
|
58
|
+
indent: 2, // Number of spaces for indentation
|
|
59
|
+
lineWidth: 100, // Maximum line width for folded strings
|
|
60
|
+
quotingType: '"', // Quote type for strings that need quoting: '"' or "'"
|
|
61
|
+
forceQuotes: false, // Force quotes on all string values
|
|
62
|
+
noCompatMode: true, // Use YAML 1.2 spec (not 1.1)
|
|
63
|
+
},
|
|
64
|
+
// Rule 8: Preserve Docusaurus admonitions
|
|
65
|
+
preserveAdmonitions: {
|
|
66
|
+
enabled: true,
|
|
67
|
+
description: 'Keep Docusaurus admonitions (:::note, :::tip, etc.) intact',
|
|
68
|
+
},
|
|
69
|
+
// Rule 9: Error handling behavior
|
|
70
|
+
errorHandling: {
|
|
71
|
+
throwOnError: false, // Don't throw error, return original content with warning
|
|
72
|
+
description: 'How to handle parsing errors - return original or throw',
|
|
73
|
+
},
|
|
74
|
+
// Rule 10: Auto-detect indentation from file content
|
|
75
|
+
autoDetectIndent: {
|
|
76
|
+
enabled: false,
|
|
77
|
+
description: 'Automatically detect indentation style from file content',
|
|
78
|
+
fallbackIndentSize: 2, // Default indent size if detection fails
|
|
79
|
+
fallbackIndentType: 'space', // Default indent type ('space' or 'tab')
|
|
80
|
+
minConfidence: 0.7, // Minimum confidence score to use detected indentation
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
// Export function to get only enabled rules
|
|
84
|
+
export function getEnabledRules() {
|
|
85
|
+
return Object.entries(formatterSettings)
|
|
86
|
+
.filter(([_, config]) => config.enabled)
|
|
87
|
+
.reduce((acc, [key, config]) => {
|
|
88
|
+
acc[key] = config;
|
|
89
|
+
return acc;
|
|
90
|
+
}, {});
|
|
91
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SpecificFormatter - Applies only specific formatting rules to MDX content
|
|
3
|
+
* Does NOT reformat everything, only applies targeted fixes
|
|
4
|
+
*/
|
|
5
|
+
import type { FormatterSettings } from './types.js';
|
|
6
|
+
export declare class SpecificFormatter {
|
|
7
|
+
private content;
|
|
8
|
+
private lines;
|
|
9
|
+
settings: FormatterSettings;
|
|
10
|
+
constructor(content: string, settings?: FormatterSettings | null);
|
|
11
|
+
format(): Promise<string>;
|
|
12
|
+
/**
|
|
13
|
+
* Rule 1: Add 1 empty line between elements
|
|
14
|
+
*/
|
|
15
|
+
addEmptyLineBetweenElements(content: string): string;
|
|
16
|
+
shouldAddEmptyLine(currentLine: string, nextLine: string): boolean;
|
|
17
|
+
/**
|
|
18
|
+
* Combined JSX formatting - handles structure and content indentation together
|
|
19
|
+
*/
|
|
20
|
+
formatJsxStructure(content: string): string;
|
|
21
|
+
/**
|
|
22
|
+
* Rule 4: Expand single-line JSX with 2+ props
|
|
23
|
+
*/
|
|
24
|
+
expandSingleLineJsx(content: string): string;
|
|
25
|
+
parseJsxProps(propsString: string): string[];
|
|
26
|
+
/**
|
|
27
|
+
* Rule 7: Validate MDX and throw errors on invalid content
|
|
28
|
+
*/
|
|
29
|
+
validateMdx(content: string): boolean;
|
|
30
|
+
}
|