myaidev-method 0.2.12 → 0.2.16

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.
Files changed (32) hide show
  1. package/.env.example +40 -0
  2. package/CHANGELOG.md +96 -0
  3. package/CONTENT_CREATION_GUIDE.md +3399 -0
  4. package/DEVELOPER_USE_CASES.md +2085 -0
  5. package/README.md +209 -2
  6. package/VISUAL_GENERATION_FILE_ORGANIZATION.md +105 -0
  7. package/bin/cli.js +46 -0
  8. package/package.json +18 -3
  9. package/src/lib/asset-management.js +532 -0
  10. package/src/lib/visual-config-utils.js +424 -0
  11. package/src/lib/visual-generation-utils.js +880 -0
  12. package/src/scripts/configure-visual-apis.js +413 -0
  13. package/src/scripts/generate-visual-cli.js +279 -0
  14. package/src/templates/claude/agents/content-production-coordinator.md +111 -0
  15. package/src/templates/claude/agents/content-writer.md +209 -4
  16. package/src/templates/claude/agents/proprietary-content-verifier.md +96 -0
  17. package/src/templates/claude/agents/visual-content-generator.md +520 -0
  18. package/src/templates/claude/commands/myai-content-writer.md +33 -8
  19. package/src/templates/claude/commands/myai-coordinate-content.md +136 -0
  20. package/src/templates/claude/commands/myai-generate-visual.md +318 -0
  21. package/src/templates/codex/commands/myai-generate-visual.md +307 -0
  22. package/src/templates/gemini/commands/myai-generate-visual.md +200 -0
  23. package/.claude/CLAUDE.md +0 -52
  24. package/.claude/agents/content-writer.md +0 -155
  25. package/.claude/agents/wordpress-admin.md +0 -271
  26. package/.claude/commands/myai-configure.md +0 -44
  27. package/.claude/commands/myai-content-writer.md +0 -78
  28. package/.claude/commands/myai-wordpress-publish.md +0 -120
  29. package/.claude/mcp/gutenberg-converter.js +0 -447
  30. package/.claude/mcp/mcp-config.json +0 -184
  31. package/.claude/mcp/wordpress-server-simple.js +0 -182
  32. package/.claude/settings.local.json +0 -12
@@ -1,44 +0,0 @@
1
- ---
2
- name: myai-configure
3
- description: Configure MyAI Method settings including WordPress credentials
4
- tools: Read, Write, Edit
5
- ---
6
-
7
- Configure MyAI Method settings based on the parameter: $ARGUMENTS
8
-
9
- ## Available Configurations
10
-
11
- ### wordpress
12
- Set up WordPress connection:
13
- 1. Prompt for WordPress site URL
14
- 2. Prompt for username
15
- 3. Guide through Application Password creation
16
- 4. Ask if Gutenberg block format should be default (yes/no)
17
- 5. Save to `.env` file in project root
18
-
19
- ### defaults
20
- Configure default content settings:
21
- - Default word count
22
- - Default post status
23
- - Default tone
24
- - Default audience
25
-
26
- ### agents
27
- List and manage available agents:
28
- - Show installed agents
29
- - Edit agent configurations
30
- - Test agent functionality
31
-
32
- ## Process
33
-
34
- 1. Parse $ARGUMENTS to determine configuration type
35
- 2. Interactive prompts for required values
36
- 3. Validate configuration
37
- 4. Save to appropriate location (.env or .claude/config/)
38
- 5. Confirm successful configuration
39
-
40
- ## Security
41
-
42
- - Never display passwords in plain text
43
- - Use environment variables for sensitive data
44
- - Validate URLs and credentials before saving
@@ -1,78 +0,0 @@
1
- ---
2
- name: myai-content-writer
3
- description: Professional content writer for SEO-optimized articles and blog posts
4
- tools: Read, Write, Edit, WebSearch, WebFetch, Task
5
- ---
6
-
7
- You are a professional content writer specializing in creating high-quality, engaging, and SEO-optimized content.
8
-
9
- ## Task
10
- Create comprehensive, well-researched content based on the provided topic: $ARGUMENTS
11
-
12
- ## Core Competencies
13
-
14
- 1. **Writing Excellence**
15
- - Clear, concise, and engaging prose
16
- - Proper grammar, spelling, and punctuation
17
- - Varied sentence structure and rhythm
18
- - Active voice preference
19
- - Storytelling when appropriate
20
-
21
- 2. **SEO Optimization**
22
- - Natural keyword integration
23
- - Proper heading hierarchy (H1, H2, H3)
24
- - Meta descriptions (150-160 characters)
25
- - Internal and external linking suggestions
26
- - URL-friendly slugs
27
-
28
- 3. **Content Structure**
29
- - Compelling introductions with hooks
30
- - Logical flow and transitions
31
- - Scannable formatting (bullets, lists, short paragraphs)
32
- - Strong conclusions with CTAs when appropriate
33
-
34
- ## Writing Process
35
-
36
- 1. **Understanding**: Analyze the topic, audience, and goals from $ARGUMENTS
37
- 2. **Research**: Use WebSearch to gather current, relevant information
38
- 3. **Planning**: Create a detailed outline with logical structure
39
- 4. **Writing**: Craft the content following SEO best practices
40
- 5. **Optimization**: Review for clarity, accuracy, and SEO
41
- 6. **Formatting**: Ensure proper Markdown formatting
42
-
43
- ## Output Requirements
44
-
45
- Save the content as a markdown file with the following structure:
46
-
47
- ```markdown
48
- ---
49
- title: [Compelling, keyword-rich title]
50
- meta_description: [150-160 character summary]
51
- slug: [url-friendly-version]
52
- tags: [relevant, topic, tags]
53
- category: [Primary category]
54
- ---
55
-
56
- # [Title]
57
-
58
- [Content with proper heading hierarchy]
59
- ```
60
-
61
- ## Parameters Supported
62
-
63
- - Topic (required): Main subject matter
64
- - Word count: Target length (default: 800)
65
- - Tone: professional, casual, technical, conversational, academic
66
- - Audience: Target reader demographic
67
- - SEO keywords: Primary and secondary keywords
68
- - Publish to WordPress: Auto-publish as draft (true/false)
69
-
70
- ## WordPress Integration
71
-
72
- If publish_to_wordpress is requested:
73
- 1. Save the content locally first
74
- 2. Check for WordPress configuration in environment variables
75
- 3. Use the WordPress MCP to create a draft post
76
- 4. Report the draft URL to the user
77
-
78
- Always prioritize user value over keyword density. Write for humans first, search engines second.
@@ -1,120 +0,0 @@
1
- ---
2
- name: myai-wordpress-publish
3
- description: Publish markdown content to WordPress using enhanced MCP server with session management
4
- tools: Read, Task, mcp__myaidev__wp_create_post, mcp__myaidev__wp_session_create, mcp__myaidev__wp_health_check
5
- ---
6
-
7
- Publish the specified markdown file to WordPress using the enhanced MCP integration with session management and memory persistence.
8
-
9
- ## Task
10
- Read and publish the file specified in $ARGUMENTS to WordPress using the enhanced MCP server.
11
-
12
- ## Enhanced Process
13
-
14
- 1. **Initialize WordPress session** using `mcp__myaidev__wp_session_create`
15
- 2. **Perform health check** with `mcp__myaidev__wp_health_check` to validate connectivity
16
- 3. **Read the markdown file** from the path provided in $ARGUMENTS
17
- 4. **Parse the frontmatter** to extract:
18
- - title
19
- - meta_description (use as excerpt)
20
- - slug
21
- - tags (convert to tag names/IDs)
22
- - category (convert to category names/IDs)
23
- - use_gutenberg (optional, inherits from environment)
24
- - author (optional)
25
- - comment_status (optional, default: "open")
26
- - ping_status (optional, default: "open")
27
- 5. **Validate content** and prepare for publishing:
28
- - Check required fields (title, content)
29
- - Validate status value
30
- - Prepare category and tag mappings
31
- 6. **Create the post** using `mcp__myaidev__wp_create_post` with:
32
- - Enhanced error handling and validation
33
- - Automatic Gutenberg conversion if enabled
34
- - Session tracking for operation logging
35
- - Memory storage for operation persistence
36
- 7. **Report comprehensive results** including:
37
- - Post ID and URLs (public, edit)
38
- - Session ID for tracking
39
- - Format used (Classic or Gutenberg)
40
- - Operation metrics (response time, warnings)
41
- - Memory storage confirmation
42
-
43
- ## Parameters
44
-
45
- - File path (required): Path to markdown file to publish
46
- - --status: draft (default), publish, pending, private
47
- - --gutenberg: Use Gutenberg block format (inherits from environment if not specified)
48
- - --classic: Force classic editor format (overrides defaults)
49
- - --batch: Process multiple files (experimental)
50
- - --dry-run: Validate without actually publishing
51
- - --session-id: Use existing session ID (optional)
52
-
53
- ## Enhanced Error Handling
54
-
55
- **Configuration Issues:**
56
- - Use enhanced health check to validate WordPress connectivity
57
- - Provide detailed error messages from MCP server
58
- - Guide user through `/myai-configure wordpress` setup if needed
59
-
60
- **File Processing:**
61
- - Validate file existence and readability before processing
62
- - Report specific parsing errors for frontmatter
63
- - Suggest corrections for invalid field values
64
-
65
- **Publishing Errors:**
66
- - Detailed API error reporting from enhanced MCP server
67
- - Session-based error tracking and recovery
68
- - Automatic retry mechanisms for transient failures
69
-
70
- ## Enhanced Success Response
71
-
72
- ```
73
- ✅ Successfully published to WordPress via Enhanced MCP Server!
74
-
75
- 📄 Post Details:
76
- - Post ID: [id]
77
- - Title: [title]
78
- - Status: [draft/published]
79
- - Format: [Classic/Gutenberg]
80
-
81
- 🔗 URLs:
82
- - Preview: [preview_url]
83
- - Edit: [admin_edit_url]
84
-
85
- 📊 Operation Details:
86
- - Session ID: [session_id]
87
- - Response Time: [duration]ms
88
- - Memory Stored: [stored_in_memory]
89
- - Server Version: 2.0.0
90
-
91
- 📈 Next Steps:
92
- - View session status: Use session ID to track operations
93
- - Edit in WordPress: Click edit URL above
94
- - Publish live: Change status from draft to publish if needed
95
- ```
96
-
97
- ## MCP Tools Integration
98
-
99
- This command leverages the enhanced WordPress MCP server tools:
100
-
101
- 1. **wp_session_create**: Initialize tracking session
102
- 2. **wp_health_check**: Validate WordPress connectivity
103
- 3. **wp_create_post**: Create post with enhanced features:
104
- - Automatic Gutenberg conversion
105
- - Memory persistence
106
- - Session logging
107
- - Comprehensive validation
108
- 4. **wp_memory_store**: Store operation results for later reference
109
-
110
- ## Troubleshooting
111
-
112
- **MCP Server Issues:**
113
- - Ensure WordPress MCP server is running
114
- - Check environment variables are set correctly
115
- - Use health check tool to diagnose connectivity
116
-
117
- **Publishing Problems:**
118
- - Check session status for detailed error logs
119
- - Use dry-run mode to validate before publishing
120
- - Review memory stored data for operation history
@@ -1,447 +0,0 @@
1
- /**
2
- * Gutenberg Block Converter
3
- * Converts HTML/Markdown content to WordPress Gutenberg block format
4
- * Following WordPress Block Editor Handbook specifications
5
- */
6
-
7
- export class GutenbergConverter {
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({ type: 'heading', level: parseInt(level), content: this.stripTags(content) });
85
- }
86
- else if (fullMatch.match(/<p(?:\s|>)/i)) {
87
- const content = fullMatch.replace(/<\/?p(?:\s[^>]*)?>/gi, '');
88
- sections.push({ type: 'paragraph', content: this.stripTags(content) });
89
- }
90
- else if (fullMatch.match(/<(ul|ol)/i)) {
91
- const listType = fullMatch.match(/<(ul|ol)/i)[1];
92
- const items = this.parseListItems(fullMatch);
93
- sections.push({ type: 'list', ordered: listType === 'ol', items });
94
- }
95
- else if (fullMatch.match(/<blockquote/i)) {
96
- const content = fullMatch.replace(/<\/?blockquote(?:\s[^>]*)?>/gi, '');
97
- sections.push({ type: 'quote', content: this.stripTags(content) });
98
- }
99
- else if (fullMatch.match(/<pre/i)) {
100
- const content = fullMatch.replace(/<\/?(?:pre|code)(?:\s[^>]*)?>/gi, '');
101
- sections.push({ type: 'code', content: this.decodeHtml(content) });
102
- }
103
- else if (fullMatch.match(/<img/i)) {
104
- const attrs = this.parseImageAttributes(fullMatch);
105
- sections.push({ type: 'image', ...attrs });
106
- }
107
- else if (fullMatch.match(/<hr/i)) {
108
- sections.push({ type: 'separator' });
109
- }
110
-
111
- lastIndex = match.index + fullMatch.length;
112
- }
113
-
114
- // Add any remaining content
115
- if (lastIndex < normalizedHtml.length) {
116
- const text = normalizedHtml.substring(lastIndex).trim();
117
- if (text && !text.match(/^\s*$/)) {
118
- sections.push({ type: 'paragraph', content: this.stripTags(text) });
119
- }
120
- }
121
-
122
- return sections;
123
- }
124
-
125
- /**
126
- * Parse list items from HTML list
127
- */
128
- static parseListItems(listHtml) {
129
- const items = [];
130
- const itemPattern = /<li(?:\s[^>]*)?>(.+?)<\/li>/gis;
131
- let match;
132
-
133
- while ((match = itemPattern.exec(listHtml)) !== null) {
134
- items.push(this.stripTags(match[1].trim()));
135
- }
136
-
137
- return items;
138
- }
139
-
140
- /**
141
- * Parse image attributes from img tag
142
- */
143
- static parseImageAttributes(imgTag) {
144
- const attrs = {};
145
-
146
- // Extract src
147
- const srcMatch = imgTag.match(/src=["']([^"']+)["']/i);
148
- if (srcMatch) attrs.url = srcMatch[1];
149
-
150
- // Extract alt text
151
- const altMatch = imgTag.match(/alt=["']([^"']+)["']/i);
152
- if (altMatch) attrs.alt = altMatch[1];
153
-
154
- // Extract title
155
- const titleMatch = imgTag.match(/title=["']([^"']+)["']/i);
156
- if (titleMatch) attrs.caption = titleMatch[1];
157
-
158
- return attrs;
159
- }
160
-
161
- /**
162
- * Create a Gutenberg block from a section
163
- */
164
- static createBlock(section) {
165
- switch (section.type) {
166
- case 'heading':
167
- return this.createHeadingBlock(section.level, section.content);
168
-
169
- case 'paragraph':
170
- return this.createParagraphBlock(section.content);
171
-
172
- case 'list':
173
- return this.createListBlock(section.items, section.ordered);
174
-
175
- case 'quote':
176
- return this.createQuoteBlock(section.content);
177
-
178
- case 'code':
179
- return this.createCodeBlock(section.content);
180
-
181
- case 'image':
182
- return this.createImageBlock(section.url, section.alt, section.caption);
183
-
184
- case 'separator':
185
- return this.createSeparatorBlock();
186
-
187
- default:
188
- return this.createParagraphBlock(section.content || '');
189
- }
190
- }
191
-
192
- /**
193
- * Create heading block
194
- */
195
- static createHeadingBlock(level, content) {
196
- return `<!-- wp:heading {"level":${level}} -->
197
- <h${level} class="wp-block-heading">${this.escapeHtml(content)}</h${level}>
198
- <!-- /wp:heading -->`;
199
- }
200
-
201
- /**
202
- * Create paragraph block
203
- */
204
- static createParagraphBlock(content) {
205
- // Handle empty paragraphs
206
- if (!content || content.trim() === '') {
207
- return '';
208
- }
209
-
210
- return `<!-- wp:paragraph -->
211
- <p>${this.escapeHtml(content)}</p>
212
- <!-- /wp:paragraph -->`;
213
- }
214
-
215
- /**
216
- * Create list block
217
- */
218
- static createListBlock(items, ordered = false) {
219
- const tag = ordered ? 'ol' : 'ul';
220
- const blockName = ordered ? 'list' : 'list';
221
- const listItems = items.map(item => `<li>${this.escapeHtml(item)}</li>`).join('\n');
222
-
223
- const attributes = ordered ? ' {"ordered":true}' : '';
224
-
225
- return `<!-- wp:list${attributes} -->
226
- <${tag} class="wp-block-list">${listItems}</${tag}>
227
- <!-- /wp:list -->`;
228
- }
229
-
230
- /**
231
- * Create quote block
232
- */
233
- static createQuoteBlock(content) {
234
- return `<!-- wp:quote -->
235
- <blockquote class="wp-block-quote">
236
- <p>${this.escapeHtml(content)}</p>
237
- </blockquote>
238
- <!-- /wp:quote -->`;
239
- }
240
-
241
- /**
242
- * Create code block
243
- */
244
- static createCodeBlock(code) {
245
- // Escape the code content for HTML
246
- const escapedCode = this.escapeHtml(code);
247
-
248
- return `<!-- wp:code -->
249
- <pre class="wp-block-code"><code>${escapedCode}</code></pre>
250
- <!-- /wp:code -->`;
251
- }
252
-
253
- /**
254
- * Create image block
255
- */
256
- static createImageBlock(url, alt = '', caption = '') {
257
- let attributes = {};
258
- if (alt) attributes.alt = alt;
259
-
260
- const attributesJson = Object.keys(attributes).length > 0
261
- ? ' ' + JSON.stringify(attributes)
262
- : '';
263
-
264
- let imageHtml = `<!-- wp:image${attributesJson} -->
265
- <figure class="wp-block-image"><img src="${url}"${alt ? ` alt="${this.escapeHtml(alt)}"` : ''}/>`;
266
-
267
- if (caption) {
268
- imageHtml += `<figcaption class="wp-element-caption">${this.escapeHtml(caption)}</figcaption>`;
269
- }
270
-
271
- imageHtml += `</figure>
272
- <!-- /wp:image -->`;
273
-
274
- return imageHtml;
275
- }
276
-
277
- /**
278
- * Create separator block
279
- */
280
- static createSeparatorBlock() {
281
- return `<!-- wp:separator -->
282
- <hr class="wp-block-separator has-alpha-channel-opacity"/>
283
- <!-- /wp:separator -->`;
284
- }
285
-
286
- /**
287
- * Create columns block for advanced layouts
288
- */
289
- static createColumnsBlock(columns) {
290
- const columnCount = columns.length;
291
- let columnsHtml = `<!-- wp:columns {"columns":${columnCount}} -->\n<div class="wp-block-columns">`;
292
-
293
- columns.forEach(column => {
294
- columnsHtml += `\n<!-- wp:column -->\n<div class="wp-block-column">`;
295
- columnsHtml += `\n${column}`;
296
- columnsHtml += `\n</div>\n<!-- /wp:column -->`;
297
- });
298
-
299
- columnsHtml += `\n</div>\n<!-- /wp:columns -->`;
300
- return columnsHtml;
301
- }
302
-
303
- /**
304
- * Create button block
305
- */
306
- static createButtonBlock(text, url = '#', align = 'none') {
307
- return `<!-- wp:buttons {"layout":{"type":"flex","justifyContent":"${align}"}} -->
308
- <div class="wp-block-buttons">
309
- <!-- wp:button -->
310
- <div class="wp-block-button"><a class="wp-block-button__link wp-element-button" href="${url}">${this.escapeHtml(text)}</a></div>
311
- <!-- /wp:button -->
312
- </div>
313
- <!-- /wp:buttons -->`;
314
- }
315
-
316
- /**
317
- * Create table block
318
- */
319
- static createTableBlock(headers, rows) {
320
- let tableHtml = `<!-- wp:table -->
321
- <figure class="wp-block-table"><table class="wp-block-table">`;
322
-
323
- // Add headers
324
- if (headers && headers.length > 0) {
325
- tableHtml += '\n<thead>\n<tr>';
326
- headers.forEach(header => {
327
- tableHtml += `<th>${this.escapeHtml(header)}</th>`;
328
- });
329
- tableHtml += '</tr>\n</thead>';
330
- }
331
-
332
- // Add rows
333
- tableHtml += '\n<tbody>';
334
- rows.forEach(row => {
335
- tableHtml += '\n<tr>';
336
- row.forEach(cell => {
337
- tableHtml += `<td>${this.escapeHtml(cell)}</td>`;
338
- });
339
- tableHtml += '</tr>';
340
- });
341
- tableHtml += '\n</tbody>';
342
-
343
- tableHtml += `\n</table></figure>
344
- <!-- /wp:table -->`;
345
-
346
- return tableHtml;
347
- }
348
-
349
- /**
350
- * Utility: Strip HTML tags from text
351
- */
352
- static stripTags(text) {
353
- return text.replace(/<[^>]+>/g, '').trim();
354
- }
355
-
356
- /**
357
- * Utility: Escape HTML special characters
358
- */
359
- static escapeHtml(text) {
360
- const map = {
361
- '&': '&amp;',
362
- '<': '&lt;',
363
- '>': '&gt;',
364
- '"': '&quot;',
365
- "'": '&#x27;',
366
- "/": '&#x2F;'
367
- };
368
-
369
- return text.replace(/[&<>"'/]/g, char => map[char]);
370
- }
371
-
372
- /**
373
- * Utility: Decode HTML entities
374
- */
375
- static decodeHtml(text) {
376
- const entities = {
377
- '&amp;': '&',
378
- '&lt;': '<',
379
- '&gt;': '>',
380
- '&quot;': '"',
381
- '&#x27;': "'",
382
- '&#x2F;': '/',
383
- '&#39;': "'",
384
- '&nbsp;': ' '
385
- };
386
-
387
- return text.replace(/&[#a-z0-9]+;/gi, entity => entities[entity] || entity);
388
- }
389
-
390
- /**
391
- * Convert markdown to Gutenberg blocks (bonus feature)
392
- */
393
- static markdownToGutenberg(markdown) {
394
- // First convert markdown to HTML (simplified version)
395
- let html = markdown
396
- // Headers
397
- .replace(/^### (.+)$/gm, '<h3>$1</h3>')
398
- .replace(/^## (.+)$/gm, '<h2>$1</h2>')
399
- .replace(/^# (.+)$/gm, '<h1>$1</h1>')
400
- // Bold
401
- .replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>')
402
- // Italic
403
- .replace(/\*(.+?)\*/g, '<em>$1</em>')
404
- // Code blocks
405
- .replace(/```[\s\S]*?```/g, match => {
406
- const code = match.slice(3, -3).trim();
407
- return `<pre><code>${code}</code></pre>`;
408
- })
409
- // Inline code
410
- .replace(/`(.+?)`/g, '<code>$1</code>')
411
- // Blockquotes
412
- .replace(/^> (.+)$/gm, '<blockquote>$1</blockquote>')
413
- // Horizontal rules
414
- .replace(/^---$/gm, '<hr>')
415
- // Lists (simplified)
416
- .replace(/^- (.+)$/gm, '<li>$1</li>')
417
- .replace(/^(\d+)\. (.+)$/gm, '<li>$2</li>');
418
-
419
- // Wrap consecutive li elements in ul/ol
420
- html = html.replace(/(<li>.*<\/li>\n?)+/g, match => {
421
- return `<ul>${match}</ul>`;
422
- });
423
-
424
- // Convert paragraphs
425
- const lines = html.split('\n');
426
- const processedLines = [];
427
- let inBlock = false;
428
-
429
- lines.forEach(line => {
430
- const trimmed = line.trim();
431
- if (trimmed === '') {
432
- inBlock = false;
433
- } else if (!trimmed.startsWith('<')) {
434
- processedLines.push(`<p>${trimmed}</p>`);
435
- } else {
436
- processedLines.push(trimmed);
437
- }
438
- });
439
-
440
- html = processedLines.join('\n');
441
-
442
- // Now convert HTML to Gutenberg
443
- return this.htmlToGutenberg(html);
444
- }
445
- }
446
-
447
- export default GutenbergConverter;