myaidev-method 0.3.2 → 0.3.4
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/.claude-plugin/plugin.json +52 -48
- package/DEV_WORKFLOW_GUIDE.md +6 -6
- package/MCP_INTEGRATION.md +4 -4
- package/README.md +81 -64
- package/TECHNICAL_ARCHITECTURE.md +112 -18
- package/USER_GUIDE.md +57 -40
- package/bin/cli.js +49 -127
- package/dist/mcp/gutenberg-converter.js +667 -413
- package/dist/mcp/wordpress-server.js +1558 -1181
- package/extension.json +3 -3
- package/package.json +2 -1
- package/skills/content-writer/SKILL.md +130 -178
- package/skills/infographic/SKILL.md +191 -0
- package/skills/myaidev-analyze/SKILL.md +242 -0
- package/skills/myaidev-analyze/agents/dependency-mapper-agent.md +236 -0
- package/skills/myaidev-analyze/agents/pattern-detector-agent.md +240 -0
- package/skills/myaidev-analyze/agents/structure-scanner-agent.md +171 -0
- package/skills/myaidev-analyze/agents/tech-profiler-agent.md +291 -0
- package/skills/myaidev-architect/SKILL.md +389 -0
- package/skills/myaidev-architect/agents/compliance-checker-agent.md +287 -0
- package/skills/myaidev-architect/agents/requirements-analyst-agent.md +194 -0
- package/skills/myaidev-architect/agents/system-designer-agent.md +315 -0
- package/skills/myaidev-coder/SKILL.md +291 -0
- package/skills/myaidev-coder/agents/implementer-agent.md +185 -0
- package/skills/myaidev-coder/agents/integration-agent.md +168 -0
- package/skills/myaidev-coder/agents/pattern-scanner-agent.md +161 -0
- package/skills/myaidev-coder/agents/self-reviewer-agent.md +168 -0
- package/skills/myaidev-debug/SKILL.md +308 -0
- package/skills/myaidev-debug/agents/fix-agent-debug.md +317 -0
- package/skills/myaidev-debug/agents/hypothesis-agent.md +226 -0
- package/skills/myaidev-debug/agents/investigator-agent.md +250 -0
- package/skills/myaidev-debug/agents/symptom-collector-agent.md +231 -0
- package/skills/myaidev-documenter/SKILL.md +194 -0
- package/skills/myaidev-documenter/agents/code-reader-agent.md +172 -0
- package/skills/myaidev-documenter/agents/doc-validator-agent.md +174 -0
- package/skills/myaidev-documenter/agents/doc-writer-agent.md +379 -0
- package/skills/myaidev-migrate/SKILL.md +300 -0
- package/skills/myaidev-migrate/agents/migration-planner-agent.md +237 -0
- package/skills/myaidev-migrate/agents/migration-writer-agent.md +248 -0
- package/skills/myaidev-migrate/agents/schema-analyzer-agent.md +190 -0
- package/skills/myaidev-performance/SKILL.md +270 -0
- package/skills/myaidev-performance/agents/benchmark-agent.md +281 -0
- package/skills/myaidev-performance/agents/optimizer-agent.md +277 -0
- package/skills/myaidev-performance/agents/profiler-agent.md +252 -0
- package/skills/myaidev-refactor/SKILL.md +296 -0
- package/skills/myaidev-refactor/agents/refactor-executor-agent.md +221 -0
- package/skills/myaidev-refactor/agents/refactor-planner-agent.md +213 -0
- package/skills/myaidev-refactor/agents/regression-guard-agent.md +242 -0
- package/skills/myaidev-refactor/agents/smell-detector-agent.md +233 -0
- package/skills/myaidev-reviewer/SKILL.md +385 -0
- package/skills/myaidev-reviewer/agents/auto-fixer-agent.md +238 -0
- package/skills/myaidev-reviewer/agents/code-analyst-agent.md +220 -0
- package/skills/myaidev-reviewer/agents/security-scanner-agent.md +262 -0
- package/skills/myaidev-tester/SKILL.md +331 -0
- package/skills/myaidev-tester/agents/coverage-analyst-agent.md +163 -0
- package/skills/myaidev-tester/agents/tdd-driver-agent.md +242 -0
- package/skills/myaidev-tester/agents/test-runner-agent.md +176 -0
- package/skills/myaidev-tester/agents/test-strategist-agent.md +154 -0
- package/skills/myaidev-tester/agents/test-writer-agent.md +242 -0
- package/skills/myaidev-workflow/SKILL.md +567 -0
- package/skills/myaidev-workflow/agents/analyzer-agent.md +317 -0
- package/skills/myaidev-workflow/agents/coordinator-agent.md +253 -0
- package/skills/security-auditor/SKILL.md +1 -1
- package/skills/skill-builder/SKILL.md +417 -0
- package/src/cli/commands/addon.js +146 -135
- package/src/cli/commands/auth.js +9 -1
- package/src/config/workflows.js +11 -6
- package/src/lib/ascii-banner.js +3 -3
- package/src/lib/update-manager.js +120 -61
- package/src/mcp/gutenberg-converter.js +667 -413
- package/src/mcp/wordpress-server.js +1558 -1181
- package/src/statusline/statusline.sh +279 -0
- package/src/templates/claude/CLAUDE.md +124 -0
- package/skills/sparc-architect/SKILL.md +0 -127
- package/skills/sparc-coder/SKILL.md +0 -90
- package/skills/sparc-documenter/SKILL.md +0 -155
- package/skills/sparc-reviewer/SKILL.md +0 -138
- package/skills/sparc-tester/SKILL.md +0 -100
- package/skills/sparc-workflow/SKILL.md +0 -130
- /package/{marketplace.json → .claude-plugin/marketplace.json} +0 -0
|
@@ -5,443 +5,697 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
export class GutenbergConverter {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
8
|
+
/**
|
|
9
|
+
* Convert HTML content to Gutenberg blocks
|
|
10
|
+
* @param {string} html - HTML content to convert
|
|
11
|
+
* @returns {string} - Gutenberg block formatted content
|
|
12
|
+
*/
|
|
13
|
+
static htmlToGutenberg(html) {
|
|
14
|
+
// Parse HTML and convert to blocks
|
|
15
|
+
let gutenbergContent = "";
|
|
16
|
+
|
|
17
|
+
// Split content into sections for processing
|
|
18
|
+
const sections = this.parseHTMLSections(html);
|
|
19
|
+
|
|
20
|
+
sections.forEach((section) => {
|
|
21
|
+
const block = this.createBlock(section);
|
|
22
|
+
if (block) {
|
|
23
|
+
gutenbergContent += block + "\n\n";
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
return gutenbergContent.trim();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Parse HTML into sections for block conversion
|
|
32
|
+
*/
|
|
33
|
+
static parseHTMLSections(html) {
|
|
34
|
+
const sections = [];
|
|
35
|
+
|
|
36
|
+
// Remove excess whitespace and normalize
|
|
37
|
+
const normalizedHtml = html.replace(/\n\s*\n/g, "\n").trim();
|
|
38
|
+
|
|
39
|
+
// Regular expressions for different HTML elements
|
|
40
|
+
const patterns = {
|
|
41
|
+
heading: /<h([1-6])(?:\s[^>]*)?>(.+?)<\/h\1>/gi,
|
|
42
|
+
paragraph: /<p(?:\s[^>]*)?>(.+?)<\/p>/gi,
|
|
43
|
+
list: /<(ul|ol)(?:\s[^>]*)?>(.+?)<\/\1>/gis,
|
|
44
|
+
blockquote: /<blockquote(?:\s[^>]*)?>(.+?)<\/blockquote>/gis,
|
|
45
|
+
pre: /<pre(?:\s[^>]*)?><code(?:\s[^>]*)?>(.+?)<\/code><\/pre>/gis,
|
|
46
|
+
image: /<img\s+([^>]+)>/gi,
|
|
47
|
+
hr: /<hr(?:\s[^>]*)?>/gi,
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
// Process the HTML string sequentially
|
|
51
|
+
let lastIndex = 0;
|
|
52
|
+
const processedParts = [];
|
|
53
|
+
|
|
54
|
+
// Create a combined pattern to find all blocks
|
|
55
|
+
const combinedPattern = new RegExp(
|
|
56
|
+
"(" +
|
|
57
|
+
"<h[1-6](?:\\s[^>]*)?>.*?</h[1-6]>|" +
|
|
58
|
+
"<p(?:\\s[^>]*)?>.*?</p>|" +
|
|
59
|
+
"<(?:ul|ol)(?:\\s[^>]*)?>.*?</(?:ul|ol)>|" +
|
|
60
|
+
"<blockquote(?:\\s[^>]*)?>.*?</blockquote>|" +
|
|
61
|
+
"<pre(?:\\s[^>]*)?><code(?:\\s[^>]*)?>.*?</code></pre>|" +
|
|
62
|
+
"<img\\s+[^>]+>|" +
|
|
63
|
+
"<hr(?:\\s[^>]*)?>" +
|
|
64
|
+
")",
|
|
65
|
+
"gis",
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
let match;
|
|
69
|
+
while ((match = combinedPattern.exec(normalizedHtml)) !== null) {
|
|
70
|
+
// Add any text between matches as a paragraph
|
|
71
|
+
if (match.index > lastIndex) {
|
|
72
|
+
const text = normalizedHtml.substring(lastIndex, match.index).trim();
|
|
73
|
+
if (text && !text.match(/^\s*$/)) {
|
|
74
|
+
sections.push({ type: "paragraph", content: this.stripTags(text) });
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const fullMatch = match[0];
|
|
79
|
+
|
|
80
|
+
// Determine block type and extract content
|
|
81
|
+
if (fullMatch.match(/<h([1-6])/i)) {
|
|
82
|
+
const level = fullMatch.match(/<h([1-6])/i)[1];
|
|
83
|
+
const content = fullMatch.replace(/<\/?h[1-6](?:\s[^>]*)?>/gi, "");
|
|
84
|
+
sections.push({
|
|
85
|
+
type: "heading",
|
|
86
|
+
level: parseInt(level),
|
|
87
|
+
content: this.stripTags(content),
|
|
88
|
+
});
|
|
89
|
+
} else if (fullMatch.match(/<p(?:\s|>)/i)) {
|
|
90
|
+
const content = fullMatch.replace(/<\/?p(?:\s[^>]*)?>/gi, "");
|
|
91
|
+
sections.push({ type: "paragraph", content: this.stripTags(content) });
|
|
92
|
+
} else if (fullMatch.match(/<(ul|ol)/i)) {
|
|
93
|
+
const listType = fullMatch.match(/<(ul|ol)/i)[1];
|
|
94
|
+
const items = this.parseListItems(fullMatch);
|
|
95
|
+
sections.push({ type: "list", ordered: listType === "ol", items });
|
|
96
|
+
} else if (fullMatch.match(/<blockquote/i)) {
|
|
97
|
+
const content = fullMatch.replace(/<\/?blockquote(?:\s[^>]*)?>/gi, "");
|
|
98
|
+
sections.push({ type: "quote", content: this.stripTags(content) });
|
|
99
|
+
} else if (fullMatch.match(/<pre/i)) {
|
|
100
|
+
const content = fullMatch.replace(
|
|
101
|
+
/<\/?(?:pre|code)(?:\s[^>]*)?>/gi,
|
|
102
|
+
"",
|
|
103
|
+
);
|
|
104
|
+
sections.push({ type: "code", content: this.decodeHtml(content) });
|
|
105
|
+
} else if (fullMatch.match(/<img/i)) {
|
|
106
|
+
const attrs = this.parseImageAttributes(fullMatch);
|
|
107
|
+
sections.push({ type: "image", ...attrs });
|
|
108
|
+
} else if (fullMatch.match(/<hr/i)) {
|
|
109
|
+
sections.push({ type: "separator" });
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
lastIndex = match.index + fullMatch.length;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Add any remaining content
|
|
116
|
+
if (lastIndex < normalizedHtml.length) {
|
|
117
|
+
const text = normalizedHtml.substring(lastIndex).trim();
|
|
118
|
+
if (text && !text.match(/^\s*$/)) {
|
|
119
|
+
sections.push({ type: "paragraph", content: this.stripTags(text) });
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return sections;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Parse list items from HTML list
|
|
128
|
+
*/
|
|
129
|
+
static parseListItems(listHtml) {
|
|
130
|
+
const items = [];
|
|
131
|
+
const itemPattern = /<li(?:\s[^>]*)?>(.+?)<\/li>/gis;
|
|
132
|
+
let match;
|
|
133
|
+
|
|
134
|
+
while ((match = itemPattern.exec(listHtml)) !== null) {
|
|
135
|
+
items.push(this.stripTags(match[1].trim()));
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return items;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Parse image attributes from img tag
|
|
143
|
+
*/
|
|
144
|
+
static parseImageAttributes(imgTag) {
|
|
145
|
+
const attrs = {};
|
|
146
|
+
|
|
147
|
+
// Extract src
|
|
148
|
+
const srcMatch = imgTag.match(/src=["']([^"']+)["']/i);
|
|
149
|
+
if (srcMatch) attrs.url = srcMatch[1];
|
|
150
|
+
|
|
151
|
+
// Extract alt text
|
|
152
|
+
const altMatch = imgTag.match(/alt=["']([^"']+)["']/i);
|
|
153
|
+
if (altMatch) attrs.alt = altMatch[1];
|
|
154
|
+
|
|
155
|
+
// Extract title
|
|
156
|
+
const titleMatch = imgTag.match(/title=["']([^"']+)["']/i);
|
|
157
|
+
if (titleMatch) attrs.caption = titleMatch[1];
|
|
158
|
+
|
|
159
|
+
return attrs;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Create a Gutenberg block from a section
|
|
164
|
+
*/
|
|
165
|
+
static createBlock(section) {
|
|
166
|
+
switch (section.type) {
|
|
167
|
+
case "heading":
|
|
168
|
+
return this.createHeadingBlock(section.level, section.content);
|
|
169
|
+
|
|
170
|
+
case "paragraph":
|
|
171
|
+
return this.createParagraphBlock(section.content);
|
|
172
|
+
|
|
173
|
+
case "list":
|
|
174
|
+
return this.createListBlock(section.items, section.ordered);
|
|
175
|
+
|
|
176
|
+
case "quote":
|
|
177
|
+
return this.createQuoteBlock(section.content);
|
|
178
|
+
|
|
179
|
+
case "code":
|
|
180
|
+
return this.createCodeBlock(section.content);
|
|
181
|
+
|
|
182
|
+
case "image":
|
|
183
|
+
return this.createImageBlock(section.url, section.alt, section.caption);
|
|
184
|
+
|
|
185
|
+
case "separator":
|
|
186
|
+
return this.createSeparatorBlock();
|
|
187
|
+
|
|
188
|
+
default:
|
|
189
|
+
return this.createParagraphBlock(section.content || "");
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Create heading block
|
|
195
|
+
*/
|
|
196
|
+
static createHeadingBlock(level, content) {
|
|
197
|
+
return `<!-- wp:heading {"level":${level}} -->
|
|
197
198
|
<h${level} class="wp-block-heading">${this.escapeHtml(content)}</h${level}>
|
|
198
199
|
<!-- /wp:heading -->`;
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Create paragraph block
|
|
204
|
+
*/
|
|
205
|
+
static createParagraphBlock(content) {
|
|
206
|
+
// Handle empty paragraphs
|
|
207
|
+
if (!content || content.trim() === "") {
|
|
208
|
+
return "";
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return `<!-- wp:paragraph -->
|
|
211
212
|
<p>${this.escapeHtml(content)}</p>
|
|
212
213
|
<!-- /wp:paragraph -->`;
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Create list block
|
|
218
|
+
*/
|
|
219
|
+
static createListBlock(items, ordered = false) {
|
|
220
|
+
const tag = ordered ? "ol" : "ul";
|
|
221
|
+
const blockName = ordered ? "list" : "list";
|
|
222
|
+
const listItems = items
|
|
223
|
+
.map((item) => `<li>${this.escapeHtml(item)}</li>`)
|
|
224
|
+
.join("\n");
|
|
225
|
+
|
|
226
|
+
const attributes = ordered ? ' {"ordered":true}' : "";
|
|
227
|
+
|
|
228
|
+
return `<!-- wp:list${attributes} -->
|
|
226
229
|
<${tag} class="wp-block-list">${listItems}</${tag}>
|
|
227
230
|
<!-- /wp:list -->`;
|
|
228
|
-
|
|
231
|
+
}
|
|
229
232
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
233
|
+
/**
|
|
234
|
+
* Create quote block
|
|
235
|
+
*/
|
|
236
|
+
static createQuoteBlock(content) {
|
|
237
|
+
return `<!-- wp:quote -->
|
|
235
238
|
<blockquote class="wp-block-quote">
|
|
236
239
|
<p>${this.escapeHtml(content)}</p>
|
|
237
240
|
</blockquote>
|
|
238
241
|
<!-- /wp:quote -->`;
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Create code block
|
|
246
|
+
*/
|
|
247
|
+
static createCodeBlock(code) {
|
|
248
|
+
// Escape the code content for HTML
|
|
249
|
+
const escapedCode = this.escapeHtml(code);
|
|
250
|
+
|
|
251
|
+
return `<!-- wp:code -->
|
|
249
252
|
<pre class="wp-block-code"><code>${escapedCode}</code></pre>
|
|
250
253
|
<!-- /wp:code -->`;
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Create image block
|
|
258
|
+
*/
|
|
259
|
+
static createImageBlock(url, alt = "", caption = "") {
|
|
260
|
+
let attributes = {};
|
|
261
|
+
if (alt) attributes.alt = alt;
|
|
262
|
+
|
|
263
|
+
const attributesJson =
|
|
264
|
+
Object.keys(attributes).length > 0
|
|
265
|
+
? " " + JSON.stringify(attributes)
|
|
266
|
+
: "";
|
|
267
|
+
|
|
268
|
+
let imageHtml = `<!-- wp:image${attributesJson} -->
|
|
269
|
+
<figure class="wp-block-image"><img src="${url}"${alt ? ` alt="${this.escapeHtml(alt)}"` : ""}/>`;
|
|
270
|
+
|
|
271
|
+
if (caption) {
|
|
272
|
+
imageHtml += `<figcaption class="wp-element-caption">${this.escapeHtml(caption)}</figcaption>`;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
imageHtml += `</figure>
|
|
272
276
|
<!-- /wp:image -->`;
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
277
|
+
|
|
278
|
+
return imageHtml;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Create separator block
|
|
283
|
+
*/
|
|
284
|
+
static createSeparatorBlock() {
|
|
285
|
+
return `<!-- wp:separator -->
|
|
282
286
|
<hr class="wp-block-separator has-alpha-channel-opacity"/>
|
|
283
287
|
<!-- /wp:separator -->`;
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Create columns block for advanced layouts
|
|
292
|
+
*/
|
|
293
|
+
static createColumnsBlock(columns) {
|
|
294
|
+
const columnCount = columns.length;
|
|
295
|
+
let columnsHtml = `<!-- wp:columns {"columns":${columnCount}} -->\n<div class="wp-block-columns">`;
|
|
296
|
+
|
|
297
|
+
columns.forEach((column) => {
|
|
298
|
+
columnsHtml += `\n<!-- wp:column -->\n<div class="wp-block-column">`;
|
|
299
|
+
columnsHtml += `\n${column}`;
|
|
300
|
+
columnsHtml += `\n</div>\n<!-- /wp:column -->`;
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
columnsHtml += `\n</div>\n<!-- /wp:columns -->`;
|
|
304
|
+
return columnsHtml;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Create button block
|
|
309
|
+
*/
|
|
310
|
+
static createButtonBlock(text, url = "#", align = "none") {
|
|
311
|
+
return `<!-- wp:buttons {"layout":{"type":"flex","justifyContent":"${align}"}} -->
|
|
308
312
|
<div class="wp-block-buttons">
|
|
309
313
|
<!-- wp:button -->
|
|
310
314
|
<div class="wp-block-button"><a class="wp-block-button__link wp-element-button" href="${url}">${this.escapeHtml(text)}</a></div>
|
|
311
315
|
<!-- /wp:button -->
|
|
312
316
|
</div>
|
|
313
317
|
<!-- /wp:buttons -->`;
|
|
314
|
-
|
|
318
|
+
}
|
|
315
319
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
320
|
+
/**
|
|
321
|
+
* Create table block
|
|
322
|
+
*/
|
|
323
|
+
static createTableBlock(headers, rows) {
|
|
324
|
+
let tableHtml = `<!-- wp:table -->
|
|
321
325
|
<figure class="wp-block-table"><table class="wp-block-table">`;
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
326
|
+
|
|
327
|
+
// Add headers
|
|
328
|
+
if (headers && headers.length > 0) {
|
|
329
|
+
tableHtml += "\n<thead>\n<tr>";
|
|
330
|
+
headers.forEach((header) => {
|
|
331
|
+
tableHtml += `<th>${this.escapeHtml(header)}</th>`;
|
|
332
|
+
});
|
|
333
|
+
tableHtml += "</tr>\n</thead>";
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// Add rows
|
|
337
|
+
tableHtml += "\n<tbody>";
|
|
338
|
+
rows.forEach((row) => {
|
|
339
|
+
tableHtml += "\n<tr>";
|
|
340
|
+
row.forEach((cell) => {
|
|
341
|
+
tableHtml += `<td>${this.escapeHtml(cell)}</td>`;
|
|
342
|
+
});
|
|
343
|
+
tableHtml += "</tr>";
|
|
344
|
+
});
|
|
345
|
+
tableHtml += "\n</tbody>";
|
|
346
|
+
|
|
347
|
+
tableHtml += `\n</table></figure>
|
|
344
348
|
<!-- /wp:table -->`;
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
349
|
+
|
|
350
|
+
return tableHtml;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* Utility: Strip HTML tags from text
|
|
355
|
+
*/
|
|
356
|
+
static stripTags(text) {
|
|
357
|
+
return text.replace(/<[^>]+>/g, "").trim();
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* Utility: Escape HTML special characters
|
|
362
|
+
*/
|
|
363
|
+
static escapeHtml(text) {
|
|
364
|
+
const map = {
|
|
365
|
+
"&": "&",
|
|
366
|
+
"<": "<",
|
|
367
|
+
">": ">",
|
|
368
|
+
'"': """,
|
|
369
|
+
"'": "'",
|
|
370
|
+
"/": "/",
|
|
371
|
+
};
|
|
372
|
+
|
|
373
|
+
return text.replace(/[&<>"'/]/g, (char) => map[char]);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* Utility: Decode HTML entities
|
|
378
|
+
*/
|
|
379
|
+
static decodeHtml(text) {
|
|
380
|
+
const entities = {
|
|
381
|
+
"&": "&",
|
|
382
|
+
"<": "<",
|
|
383
|
+
">": ">",
|
|
384
|
+
""": '"',
|
|
385
|
+
"'": "'",
|
|
386
|
+
"/": "/",
|
|
387
|
+
"'": "'",
|
|
388
|
+
" ": " ",
|
|
389
|
+
};
|
|
390
|
+
|
|
391
|
+
return text.replace(
|
|
392
|
+
/&[#a-z0-9]+;/gi,
|
|
393
|
+
(entity) => entities[entity] || entity,
|
|
394
|
+
);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* Strip YAML frontmatter from markdown content
|
|
399
|
+
*/
|
|
400
|
+
static stripFrontmatter(markdown) {
|
|
401
|
+
const frontmatterPattern = /^---\n[\s\S]*?\n---\n*/;
|
|
402
|
+
return markdown.replace(frontmatterPattern, "").trim();
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/**
|
|
406
|
+
* Detect whether content is markdown or HTML
|
|
407
|
+
*/
|
|
408
|
+
static isMarkdown(content) {
|
|
409
|
+
const trimmed = content.trim();
|
|
410
|
+
// If it starts with frontmatter, it's markdown
|
|
411
|
+
if (trimmed.startsWith("---\n")) return true;
|
|
412
|
+
// If first meaningful line starts with # heading
|
|
413
|
+
const firstLine = trimmed.split("\n").find((l) => l.trim() !== "");
|
|
414
|
+
if (firstLine && /^#{1,6}\s/.test(firstLine)) return true;
|
|
415
|
+
// If it has markdown links but no HTML tags
|
|
416
|
+
if (/\[.+?\]\(.+?\)/.test(trimmed) && !/<[a-z][\s\S]*>/i.test(trimmed))
|
|
417
|
+
return true;
|
|
418
|
+
// If it has markdown-style bold/italic but no HTML
|
|
419
|
+
if (
|
|
420
|
+
/\*\*.+?\*\*/.test(trimmed) &&
|
|
421
|
+
!/<(strong|b|em|i|p|div|h[1-6])[\s>]/i.test(trimmed)
|
|
422
|
+
)
|
|
423
|
+
return true;
|
|
424
|
+
return false;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
/**
|
|
428
|
+
* Convert content to Gutenberg blocks, auto-detecting markdown vs HTML
|
|
429
|
+
*/
|
|
430
|
+
static toGutenberg(content) {
|
|
431
|
+
if (this.isMarkdown(content)) {
|
|
432
|
+
return this.markdownToGutenberg(content);
|
|
433
|
+
}
|
|
434
|
+
return this.htmlToGutenberg(content);
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
/**
|
|
438
|
+
* Convert inline markdown formatting to HTML (bold, italic, links, code)
|
|
439
|
+
*/
|
|
440
|
+
static convertInlineMarkdown(text) {
|
|
441
|
+
return (
|
|
442
|
+
text
|
|
443
|
+
// Links: [text](url)
|
|
444
|
+
.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2">$1</a>')
|
|
445
|
+
// Bold: **text**
|
|
446
|
+
.replace(/\*\*(.+?)\*\*/g, "<strong>$1</strong>")
|
|
447
|
+
// Italic: *text* (but not inside URLs or already-converted tags)
|
|
448
|
+
.replace(/(?<!\w)\*(?!\*)(.+?)(?<!\*)\*(?!\w)/g, "<em>$1</em>")
|
|
449
|
+
// Inline code: `text`
|
|
450
|
+
.replace(/`([^`]+)`/g, "<code>$1</code>")
|
|
451
|
+
);
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
/**
|
|
455
|
+
* Parse a markdown table into headers and rows
|
|
456
|
+
*/
|
|
457
|
+
static parseMarkdownTable(lines) {
|
|
458
|
+
if (lines.length < 2) return null;
|
|
459
|
+
|
|
460
|
+
const parseCells = (line) => {
|
|
461
|
+
return line
|
|
462
|
+
.replace(/^\|/, "")
|
|
463
|
+
.replace(/\|$/, "")
|
|
464
|
+
.split("|")
|
|
465
|
+
.map((cell) => cell.trim());
|
|
466
|
+
};
|
|
467
|
+
|
|
468
|
+
// Check if second line is a separator (|---|---|)
|
|
469
|
+
if (!/^\|?\s*[-:]+\s*\|/.test(lines[1])) return null;
|
|
470
|
+
|
|
471
|
+
const headers = parseCells(lines[0]);
|
|
472
|
+
const rows = [];
|
|
473
|
+
|
|
474
|
+
for (let i = 2; i < lines.length; i++) {
|
|
475
|
+
if (!lines[i].includes("|")) break;
|
|
476
|
+
rows.push(parseCells(lines[i]));
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
return { headers, rows, lineCount: 2 + rows.length };
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
/**
|
|
483
|
+
* Build a Gutenberg block wrapper (inline HTML preserved, no escaping)
|
|
484
|
+
*/
|
|
485
|
+
static wrapBlock(blockType, innerHtml, attrs = {}) {
|
|
486
|
+
const attrStr =
|
|
487
|
+
Object.keys(attrs).length > 0 ? " " + JSON.stringify(attrs) : "";
|
|
488
|
+
return `<!-- wp:${blockType}${attrStr} -->\n${innerHtml}\n<!-- /wp:${blockType} -->`;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
/**
|
|
492
|
+
* Convert markdown to Gutenberg blocks
|
|
493
|
+
* Builds block HTML directly to preserve inline formatting (links, bold, etc.)
|
|
494
|
+
*/
|
|
495
|
+
static markdownToGutenberg(markdown) {
|
|
496
|
+
// Strip frontmatter if present
|
|
497
|
+
const content = this.stripFrontmatter(markdown);
|
|
498
|
+
const lines = content.split("\n");
|
|
499
|
+
const blocks = [];
|
|
500
|
+
let i = 0;
|
|
501
|
+
|
|
502
|
+
while (i < lines.length) {
|
|
503
|
+
const line = lines[i];
|
|
504
|
+
const trimmed = line.trim();
|
|
505
|
+
|
|
506
|
+
// Skip empty lines
|
|
507
|
+
if (trimmed === "") {
|
|
508
|
+
i++;
|
|
509
|
+
continue;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
// Headings: # through ######
|
|
513
|
+
const headingMatch = trimmed.match(/^(#{1,6})\s+(.+)$/);
|
|
514
|
+
if (headingMatch) {
|
|
515
|
+
const level = headingMatch[1].length;
|
|
516
|
+
const text = this.convertInlineMarkdown(headingMatch[2]);
|
|
517
|
+
blocks.push(
|
|
518
|
+
this.wrapBlock(
|
|
519
|
+
"heading",
|
|
520
|
+
`<h${level} class="wp-block-heading">${text}</h${level}>`,
|
|
521
|
+
{ level },
|
|
522
|
+
),
|
|
523
|
+
);
|
|
524
|
+
i++;
|
|
525
|
+
continue;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
// Horizontal rule: --- or *** or ___
|
|
529
|
+
if (/^(-{3,}|\*{3,}|_{3,})$/.test(trimmed)) {
|
|
530
|
+
blocks.push(
|
|
531
|
+
this.wrapBlock(
|
|
532
|
+
"separator",
|
|
533
|
+
'<hr class="wp-block-separator has-alpha-channel-opacity"/>',
|
|
534
|
+
),
|
|
535
|
+
);
|
|
536
|
+
i++;
|
|
537
|
+
continue;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
// Code blocks: ```
|
|
541
|
+
if (trimmed.startsWith("```")) {
|
|
542
|
+
const codeLines = [];
|
|
543
|
+
i++;
|
|
544
|
+
while (i < lines.length && !lines[i].trim().startsWith("```")) {
|
|
545
|
+
codeLines.push(lines[i]);
|
|
546
|
+
i++;
|
|
547
|
+
}
|
|
548
|
+
i++; // skip closing ```
|
|
549
|
+
const code = this.escapeHtml(codeLines.join("\n"));
|
|
550
|
+
blocks.push(
|
|
551
|
+
this.wrapBlock(
|
|
552
|
+
"code",
|
|
553
|
+
`<pre class="wp-block-code"><code>${code}</code></pre>`,
|
|
554
|
+
),
|
|
555
|
+
);
|
|
556
|
+
continue;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
// Tables: | header | header |
|
|
560
|
+
if (
|
|
561
|
+
trimmed.startsWith("|") ||
|
|
562
|
+
(trimmed.includes("|") &&
|
|
563
|
+
i + 1 < lines.length &&
|
|
564
|
+
/^\|?\s*[-:]+/.test(lines[i + 1].trim()))
|
|
565
|
+
) {
|
|
566
|
+
const tableLines = [];
|
|
567
|
+
let j = i;
|
|
568
|
+
while (j < lines.length && lines[j].trim().includes("|")) {
|
|
569
|
+
tableLines.push(lines[j].trim());
|
|
570
|
+
j++;
|
|
571
|
+
}
|
|
572
|
+
const table = this.parseMarkdownTable(tableLines);
|
|
573
|
+
if (table) {
|
|
574
|
+
const headers = table.headers.map((h) =>
|
|
575
|
+
this.convertInlineMarkdown(h),
|
|
576
|
+
);
|
|
577
|
+
const rows = table.rows.map((row) =>
|
|
578
|
+
row.map((cell) => this.convertInlineMarkdown(cell)),
|
|
579
|
+
);
|
|
580
|
+
let tableHtml =
|
|
581
|
+
'<figure class="wp-block-table"><table class="wp-block-table">';
|
|
582
|
+
tableHtml += "\n<thead>\n<tr>";
|
|
583
|
+
headers.forEach((h) => {
|
|
584
|
+
tableHtml += `<th>${h}</th>`;
|
|
585
|
+
});
|
|
586
|
+
tableHtml += "</tr>\n</thead>";
|
|
587
|
+
tableHtml += "\n<tbody>";
|
|
588
|
+
rows.forEach((row) => {
|
|
589
|
+
tableHtml += "\n<tr>";
|
|
590
|
+
row.forEach((cell) => {
|
|
591
|
+
tableHtml += `<td>${cell}</td>`;
|
|
592
|
+
});
|
|
593
|
+
tableHtml += "</tr>";
|
|
594
|
+
});
|
|
595
|
+
tableHtml += "\n</tbody>\n</table></figure>";
|
|
596
|
+
blocks.push(this.wrapBlock("table", tableHtml));
|
|
597
|
+
i += table.lineCount;
|
|
598
|
+
continue;
|
|
599
|
+
}
|
|
600
|
+
// If table parsing failed, fall through to paragraph
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
// Blockquotes: > text
|
|
604
|
+
if (trimmed.startsWith("> ")) {
|
|
605
|
+
const quoteLines = [];
|
|
606
|
+
while (i < lines.length && lines[i].trim().startsWith("> ")) {
|
|
607
|
+
quoteLines.push(lines[i].trim().slice(2));
|
|
608
|
+
i++;
|
|
609
|
+
}
|
|
610
|
+
const quoteText = this.convertInlineMarkdown(quoteLines.join(" "));
|
|
611
|
+
blocks.push(
|
|
612
|
+
this.wrapBlock(
|
|
613
|
+
"quote",
|
|
614
|
+
`<blockquote class="wp-block-quote">\n<p>${quoteText}</p>\n</blockquote>`,
|
|
615
|
+
),
|
|
616
|
+
);
|
|
617
|
+
continue;
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
// Unordered lists: - item or * item
|
|
621
|
+
if (/^[-*]\s+/.test(trimmed)) {
|
|
622
|
+
const items = [];
|
|
623
|
+
while (i < lines.length && /^[-*]\s+/.test(lines[i].trim())) {
|
|
624
|
+
const itemText = lines[i].trim().replace(/^[-*]\s+/, "");
|
|
625
|
+
items.push(this.convertInlineMarkdown(itemText));
|
|
626
|
+
i++;
|
|
627
|
+
}
|
|
628
|
+
const listItems = items.map((item) => `<li>${item}</li>`).join("\n");
|
|
629
|
+
blocks.push(
|
|
630
|
+
this.wrapBlock("list", `<ul class="wp-block-list">${listItems}</ul>`),
|
|
631
|
+
);
|
|
632
|
+
continue;
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
// Ordered lists: 1. item
|
|
636
|
+
if (/^\d+\.\s+/.test(trimmed)) {
|
|
637
|
+
const items = [];
|
|
638
|
+
while (i < lines.length && /^\d+\.\s+/.test(lines[i].trim())) {
|
|
639
|
+
const itemText = lines[i].trim().replace(/^\d+\.\s+/, "");
|
|
640
|
+
items.push(this.convertInlineMarkdown(itemText));
|
|
641
|
+
i++;
|
|
642
|
+
}
|
|
643
|
+
const listItems = items.map((item) => `<li>${item}</li>`).join("\n");
|
|
644
|
+
blocks.push(
|
|
645
|
+
this.wrapBlock(
|
|
646
|
+
"list",
|
|
647
|
+
`<ol class="wp-block-list">${listItems}</ol>`,
|
|
648
|
+
{ ordered: true },
|
|
649
|
+
),
|
|
650
|
+
);
|
|
651
|
+
continue;
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
// Images: 
|
|
655
|
+
const imageMatch = trimmed.match(/^!\[([^\]]*)\]\(([^)]+)\)$/);
|
|
656
|
+
if (imageMatch) {
|
|
657
|
+
const [, alt, url] = imageMatch;
|
|
658
|
+
const imgAttrs = alt ? ` alt="${this.escapeHtml(alt)}"` : "";
|
|
659
|
+
blocks.push(
|
|
660
|
+
this.wrapBlock(
|
|
661
|
+
"image",
|
|
662
|
+
`<figure class="wp-block-image"><img src="${url}"${imgAttrs}/></figure>`,
|
|
663
|
+
),
|
|
664
|
+
);
|
|
665
|
+
i++;
|
|
666
|
+
continue;
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
// Paragraph: everything else (collect consecutive non-empty lines)
|
|
670
|
+
const paraLines = [];
|
|
671
|
+
while (
|
|
672
|
+
i < lines.length &&
|
|
673
|
+
lines[i].trim() !== "" &&
|
|
674
|
+
!lines[i].trim().match(/^#{1,6}\s/) &&
|
|
675
|
+
!lines[i].trim().match(/^(-{3,}|\*{3,}|_{3,})$/) &&
|
|
676
|
+
!lines[i].trim().startsWith("```") &&
|
|
677
|
+
!lines[i].trim().startsWith("> ") &&
|
|
678
|
+
!lines[i].trim().match(/^[-*]\s+/) &&
|
|
679
|
+
!lines[i].trim().match(/^\d+\.\s+/) &&
|
|
680
|
+
!lines[i].trim().match(/^!\[/) &&
|
|
681
|
+
!(
|
|
682
|
+
lines[i].trim().startsWith("|") &&
|
|
683
|
+
i + 1 < lines.length &&
|
|
684
|
+
lines[i + 1] &&
|
|
685
|
+
/^\|?\s*[-:]/.test(lines[i + 1].trim())
|
|
686
|
+
)
|
|
687
|
+
) {
|
|
688
|
+
paraLines.push(lines[i].trim());
|
|
689
|
+
i++;
|
|
690
|
+
}
|
|
691
|
+
if (paraLines.length > 0) {
|
|
692
|
+
const paraText = this.convertInlineMarkdown(paraLines.join(" "));
|
|
693
|
+
blocks.push(this.wrapBlock("paragraph", `<p>${paraText}</p>`));
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
return blocks.join("\n\n");
|
|
698
|
+
}
|
|
445
699
|
}
|
|
446
700
|
|
|
447
|
-
export default GutenbergConverter;
|
|
701
|
+
export default GutenbergConverter;
|