244 lines
8.5 KiB
TypeScript
244 lines
8.5 KiB
TypeScript
![]() |
import { type IBlock } from './wysiwyg.types.js';
|
||
|
|
||
|
export class WysiwygConverters {
|
||
|
static escapeHtml(text: string): string {
|
||
|
const div = document.createElement('div');
|
||
|
div.textContent = text;
|
||
|
return div.innerHTML;
|
||
|
}
|
||
|
|
||
|
static getHtmlOutput(blocks: IBlock[]): string {
|
||
|
return blocks.map(block => {
|
||
|
switch (block.type) {
|
||
|
case 'paragraph':
|
||
|
return block.content ? `<p>${this.escapeHtml(block.content)}</p>` : '';
|
||
|
case 'heading-1':
|
||
|
return `<h1>${this.escapeHtml(block.content)}</h1>`;
|
||
|
case 'heading-2':
|
||
|
return `<h2>${this.escapeHtml(block.content)}</h2>`;
|
||
|
case 'heading-3':
|
||
|
return `<h3>${this.escapeHtml(block.content)}</h3>`;
|
||
|
case 'quote':
|
||
|
return `<blockquote>${this.escapeHtml(block.content)}</blockquote>`;
|
||
|
case 'code':
|
||
|
return `<pre><code>${this.escapeHtml(block.content)}</code></pre>`;
|
||
|
case 'list':
|
||
|
const items = block.content.split('\n').filter(item => item.trim());
|
||
|
if (items.length > 0) {
|
||
|
const listTag = block.metadata?.listType === 'ordered' ? 'ol' : 'ul';
|
||
|
return `<${listTag}>${items.map(item => `<li>${this.escapeHtml(item)}</li>`).join('')}</${listTag}>`;
|
||
|
}
|
||
|
return '';
|
||
|
case 'divider':
|
||
|
return '<hr>';
|
||
|
default:
|
||
|
return `<p>${this.escapeHtml(block.content)}</p>`;
|
||
|
}
|
||
|
}).filter(html => html !== '').join('\n');
|
||
|
}
|
||
|
|
||
|
static getMarkdownOutput(blocks: IBlock[]): string {
|
||
|
return blocks.map(block => {
|
||
|
switch (block.type) {
|
||
|
case 'paragraph':
|
||
|
return block.content;
|
||
|
case 'heading-1':
|
||
|
return `# ${block.content}`;
|
||
|
case 'heading-2':
|
||
|
return `## ${block.content}`;
|
||
|
case 'heading-3':
|
||
|
return `### ${block.content}`;
|
||
|
case 'quote':
|
||
|
return `> ${block.content}`;
|
||
|
case 'code':
|
||
|
return `\`\`\`\n${block.content}\n\`\`\``;
|
||
|
case 'list':
|
||
|
const items = block.content.split('\n').filter(item => item.trim());
|
||
|
if (block.metadata?.listType === 'ordered') {
|
||
|
return items.map((item, index) => `${index + 1}. ${item}`).join('\n');
|
||
|
} else {
|
||
|
return items.map(item => `- ${item}`).join('\n');
|
||
|
}
|
||
|
case 'divider':
|
||
|
return '---';
|
||
|
default:
|
||
|
return block.content;
|
||
|
}
|
||
|
}).filter(md => md !== '').join('\n\n');
|
||
|
}
|
||
|
|
||
|
static parseHtmlToBlocks(html: string): IBlock[] {
|
||
|
const parser = new DOMParser();
|
||
|
const doc = parser.parseFromString(html, 'text/html');
|
||
|
const blocks: IBlock[] = [];
|
||
|
|
||
|
const processNode = (node: Node) => {
|
||
|
if (node.nodeType === Node.TEXT_NODE && node.textContent?.trim()) {
|
||
|
blocks.push({
|
||
|
id: `block-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`,
|
||
|
type: 'paragraph',
|
||
|
content: node.textContent.trim(),
|
||
|
});
|
||
|
} else if (node.nodeType === Node.ELEMENT_NODE) {
|
||
|
const element = node as Element;
|
||
|
const tagName = element.tagName.toLowerCase();
|
||
|
|
||
|
switch (tagName) {
|
||
|
case 'p':
|
||
|
blocks.push({
|
||
|
id: `block-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`,
|
||
|
type: 'paragraph',
|
||
|
content: element.textContent || '',
|
||
|
});
|
||
|
break;
|
||
|
case 'h1':
|
||
|
blocks.push({
|
||
|
id: `block-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`,
|
||
|
type: 'heading-1',
|
||
|
content: element.textContent || '',
|
||
|
});
|
||
|
break;
|
||
|
case 'h2':
|
||
|
blocks.push({
|
||
|
id: `block-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`,
|
||
|
type: 'heading-2',
|
||
|
content: element.textContent || '',
|
||
|
});
|
||
|
break;
|
||
|
case 'h3':
|
||
|
blocks.push({
|
||
|
id: `block-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`,
|
||
|
type: 'heading-3',
|
||
|
content: element.textContent || '',
|
||
|
});
|
||
|
break;
|
||
|
case 'blockquote':
|
||
|
blocks.push({
|
||
|
id: `block-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`,
|
||
|
type: 'quote',
|
||
|
content: element.textContent || '',
|
||
|
});
|
||
|
break;
|
||
|
case 'pre':
|
||
|
case 'code':
|
||
|
blocks.push({
|
||
|
id: `block-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`,
|
||
|
type: 'code',
|
||
|
content: element.textContent || '',
|
||
|
});
|
||
|
break;
|
||
|
case 'ul':
|
||
|
case 'ol':
|
||
|
const listItems = Array.from(element.querySelectorAll('li'));
|
||
|
const content = listItems.map(li => li.textContent || '').join('\n');
|
||
|
blocks.push({
|
||
|
id: `block-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`,
|
||
|
type: 'list',
|
||
|
content: content,
|
||
|
metadata: { listType: tagName === 'ol' ? 'ordered' : 'bullet' }
|
||
|
});
|
||
|
break;
|
||
|
case 'hr':
|
||
|
blocks.push({
|
||
|
id: `block-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`,
|
||
|
type: 'divider',
|
||
|
content: ' ',
|
||
|
});
|
||
|
break;
|
||
|
default:
|
||
|
// Process children for other elements
|
||
|
element.childNodes.forEach(child => processNode(child));
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
|
||
|
doc.body.childNodes.forEach(node => processNode(node));
|
||
|
return blocks;
|
||
|
}
|
||
|
|
||
|
static parseMarkdownToBlocks(markdown: string): IBlock[] {
|
||
|
const lines = markdown.split('\n');
|
||
|
const blocks: IBlock[] = [];
|
||
|
let currentListItems: string[] = [];
|
||
|
|
||
|
for (let i = 0; i < lines.length; i++) {
|
||
|
const line = lines[i];
|
||
|
|
||
|
if (line.startsWith('# ')) {
|
||
|
blocks.push({
|
||
|
id: `block-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`,
|
||
|
type: 'heading-1',
|
||
|
content: line.substring(2),
|
||
|
});
|
||
|
} else if (line.startsWith('## ')) {
|
||
|
blocks.push({
|
||
|
id: `block-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`,
|
||
|
type: 'heading-2',
|
||
|
content: line.substring(3),
|
||
|
});
|
||
|
} else if (line.startsWith('### ')) {
|
||
|
blocks.push({
|
||
|
id: `block-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`,
|
||
|
type: 'heading-3',
|
||
|
content: line.substring(4),
|
||
|
});
|
||
|
} else if (line.startsWith('> ')) {
|
||
|
blocks.push({
|
||
|
id: `block-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`,
|
||
|
type: 'quote',
|
||
|
content: line.substring(2),
|
||
|
});
|
||
|
} else if (line.startsWith('```')) {
|
||
|
const codeLines: string[] = [];
|
||
|
i++;
|
||
|
while (i < lines.length && !lines[i].startsWith('```')) {
|
||
|
codeLines.push(lines[i]);
|
||
|
i++;
|
||
|
}
|
||
|
blocks.push({
|
||
|
id: `block-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`,
|
||
|
type: 'code',
|
||
|
content: codeLines.join('\n'),
|
||
|
});
|
||
|
} else if (line.match(/^(\*|-) /)) {
|
||
|
currentListItems.push(line.substring(2));
|
||
|
// Check if next line is not a list item
|
||
|
if (i === lines.length - 1 || (!lines[i + 1].match(/^(\*|-) /))) {
|
||
|
blocks.push({
|
||
|
id: `block-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`,
|
||
|
type: 'list',
|
||
|
content: currentListItems.join('\n'),
|
||
|
metadata: { listType: 'bullet' }
|
||
|
});
|
||
|
currentListItems = [];
|
||
|
}
|
||
|
} else if (line.match(/^\d+\. /)) {
|
||
|
currentListItems.push(line.replace(/^\d+\. /, ''));
|
||
|
// Check if next line is not a numbered list item
|
||
|
if (i === lines.length - 1 || (!lines[i + 1].match(/^\d+\. /))) {
|
||
|
blocks.push({
|
||
|
id: `block-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`,
|
||
|
type: 'list',
|
||
|
content: currentListItems.join('\n'),
|
||
|
metadata: { listType: 'ordered' }
|
||
|
});
|
||
|
currentListItems = [];
|
||
|
}
|
||
|
} else if (line === '---' || line === '***' || line === '___') {
|
||
|
blocks.push({
|
||
|
id: `block-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`,
|
||
|
type: 'divider',
|
||
|
content: ' ',
|
||
|
});
|
||
|
} else if (line.trim()) {
|
||
|
blocks.push({
|
||
|
id: `block-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`,
|
||
|
type: 'paragraph',
|
||
|
content: line,
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return blocks;
|
||
|
}
|
||
|
}
|