fix pre formatting
This commit is contained in:
@@ -44,7 +44,8 @@ function htmlToPortableTextBlocks(html: string): PortableTextBlock[] {
|
|||||||
const paragraphs = doc.querySelectorAll('p, h1, h2, h3, h4, h5, h6, blockquote, div');
|
const paragraphs = doc.querySelectorAll('p, h1, h2, h3, h4, h5, h6, blockquote, div');
|
||||||
|
|
||||||
if (paragraphs.length === 0) {
|
if (paragraphs.length === 0) {
|
||||||
// Fallback: treat as single paragraph
|
// Fallback: treat body text as single block, preserving newlines
|
||||||
|
const bodyText = doc.body.textContent || '';
|
||||||
return [{
|
return [{
|
||||||
_type: 'block',
|
_type: 'block',
|
||||||
_key: generateKey(),
|
_key: generateKey(),
|
||||||
@@ -53,12 +54,35 @@ function htmlToPortableTextBlocks(html: string): PortableTextBlock[] {
|
|||||||
children: [{
|
children: [{
|
||||||
_type: 'span',
|
_type: 'span',
|
||||||
_key: generateKey(),
|
_key: generateKey(),
|
||||||
text: doc.body.textContent || '',
|
text: bodyText, // Keep newlines in the text
|
||||||
marks: []
|
marks: []
|
||||||
}]
|
}]
|
||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if we have only one paragraph that might contain newlines
|
||||||
|
if (paragraphs.length === 1) {
|
||||||
|
const singleParagraph = paragraphs[0];
|
||||||
|
const textContent = singleParagraph.textContent || '';
|
||||||
|
|
||||||
|
// If this single paragraph contains newlines, preserve them in a single block
|
||||||
|
if (textContent.includes('\n')) {
|
||||||
|
const style = getStyleFromElement(singleParagraph);
|
||||||
|
return [{
|
||||||
|
_type: 'block',
|
||||||
|
_key: generateKey(),
|
||||||
|
style,
|
||||||
|
markDefs: [],
|
||||||
|
children: [{
|
||||||
|
_type: 'span',
|
||||||
|
_key: generateKey(),
|
||||||
|
text: textContent, // Keep newlines in the text
|
||||||
|
marks: []
|
||||||
|
}]
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Process all elements in document order to maintain sequence
|
// Process all elements in document order to maintain sequence
|
||||||
const allElements = Array.from(doc.body.querySelectorAll('*'));
|
const allElements = Array.from(doc.body.querySelectorAll('*'));
|
||||||
const processedElements = new Set<Element>();
|
const processedElements = new Set<Element>();
|
||||||
@@ -88,7 +112,10 @@ function htmlToPortableTextBlocks(html: string): PortableTextBlock[] {
|
|||||||
(element.tagName === 'PRE' && element.querySelector('code'))) {
|
(element.tagName === 'PRE' && element.querySelector('code'))) {
|
||||||
const codeEl = element.tagName === 'CODE' ? element : element.querySelector('code');
|
const codeEl = element.tagName === 'CODE' ? element : element.querySelector('code');
|
||||||
if (codeEl) {
|
if (codeEl) {
|
||||||
const code = codeEl.textContent || '';
|
// Use innerText to preserve newlines and whitespace formatting
|
||||||
|
// innerText respects CSS white-space property, so <pre> formatting is preserved
|
||||||
|
let code = (codeEl as HTMLElement).innerText || codeEl.textContent || '';
|
||||||
|
|
||||||
const language = codeEl.getAttribute('class')?.replace('language-', '') || '';
|
const language = codeEl.getAttribute('class')?.replace('language-', '') || '';
|
||||||
|
|
||||||
if (code.trim()) {
|
if (code.trim()) {
|
||||||
@@ -107,16 +134,33 @@ function htmlToPortableTextBlocks(html: string): PortableTextBlock[] {
|
|||||||
|
|
||||||
// Handle text blocks (paragraphs, headings, etc.)
|
// Handle text blocks (paragraphs, headings, etc.)
|
||||||
if (['P', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'BLOCKQUOTE', 'DIV'].includes(element.tagName)) {
|
if (['P', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'BLOCKQUOTE', 'DIV'].includes(element.tagName)) {
|
||||||
// Skip if this contains already processed elements
|
// Skip if this contains already processed elements (but allow inline code)
|
||||||
if (element.querySelector('img') || (element.querySelector('code') && element.querySelector('pre'))) {
|
if (element.querySelector('img')) {
|
||||||
processedElements.add(element);
|
processedElements.add(element);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if this is a standalone pre/code block that should be handled specially
|
||||||
|
const hasStandaloneCodeBlock = element.querySelector('pre') && element.children.length === 1 && element.children[0].tagName === 'PRE';
|
||||||
|
if (hasStandaloneCodeBlock) {
|
||||||
|
processedElements.add(element);
|
||||||
|
continue; // Let the pre/code block handler take care of this
|
||||||
|
}
|
||||||
|
|
||||||
const style = getStyleFromElement(element);
|
const style = getStyleFromElement(element);
|
||||||
const text = element.textContent || '';
|
|
||||||
|
// For text content that may contain inline code, preserve formatting better
|
||||||
|
let text: string;
|
||||||
|
if (element.querySelector('code') && !element.querySelector('pre')) {
|
||||||
|
// Has inline code - use innerText to preserve some formatting
|
||||||
|
text = (element as HTMLElement).innerText || element.textContent || '';
|
||||||
|
} else {
|
||||||
|
// Regular text - use textContent, but also check if this might have come from a pre block
|
||||||
|
text = (element as HTMLElement).innerText || element.textContent || '';
|
||||||
|
}
|
||||||
|
|
||||||
if (text.trim()) {
|
if (text.trim()) {
|
||||||
|
// Keep newlines within a single block - they'll be converted to <br> tags later
|
||||||
blocks.push({
|
blocks.push({
|
||||||
_type: 'block',
|
_type: 'block',
|
||||||
_key: generateKey(),
|
_key: generateKey(),
|
||||||
@@ -125,7 +169,7 @@ function htmlToPortableTextBlocks(html: string): PortableTextBlock[] {
|
|||||||
children: [{
|
children: [{
|
||||||
_type: 'span',
|
_type: 'span',
|
||||||
_key: generateKey(),
|
_key: generateKey(),
|
||||||
text,
|
text, // Keep newlines in the text
|
||||||
marks: []
|
marks: []
|
||||||
}]
|
}]
|
||||||
});
|
});
|
||||||
@@ -161,9 +205,10 @@ function portableTextToHtml(blocks: PortableTextBlock[]): string {
|
|||||||
.map(child => child._type === 'span' ? child.text || '' : '')
|
.map(child => child._type === 'span' ? child.text || '' : '')
|
||||||
.join('') || '';
|
.join('') || '';
|
||||||
|
|
||||||
if (text.trim() || block.style !== 'normal') {
|
// Convert any remaining newlines in text to <br> tags for proper display
|
||||||
htmlParts.push(`<${tag}>${text}</${tag}>`);
|
const textWithBreaks = text.replace(/\n/g, '<br>');
|
||||||
}
|
// Always include blocks, even empty ones (they represent line breaks/paragraph spacing)
|
||||||
|
htmlParts.push(`<${tag}>${textWithBreaks}</${tag}>`);
|
||||||
} else if (block._type === 'image' && isImageBlock(block)) {
|
} else if (block._type === 'image' && isImageBlock(block)) {
|
||||||
// Convert image blocks back to HTML
|
// Convert image blocks back to HTML
|
||||||
const attrs: string[] = [];
|
const attrs: string[] = [];
|
||||||
@@ -177,7 +222,14 @@ function portableTextToHtml(blocks: PortableTextBlock[]): string {
|
|||||||
} else if (block._type === 'codeBlock' && isCodeBlock(block)) {
|
} else if (block._type === 'codeBlock' && isCodeBlock(block)) {
|
||||||
// Convert code blocks back to HTML
|
// Convert code blocks back to HTML
|
||||||
const langClass = block.language ? ` class="language-${block.language}"` : '';
|
const langClass = block.language ? ` class="language-${block.language}"` : '';
|
||||||
htmlParts.push(`<pre><code${langClass}>${block.code || ''}</code></pre>`);
|
// Escape HTML entities in code content to prevent XSS and preserve formatting
|
||||||
|
const escapedCode = (block.code || '')
|
||||||
|
.replace(/&/g, '&')
|
||||||
|
.replace(/</g, '<')
|
||||||
|
.replace(/>/g, '>')
|
||||||
|
.replace(/"/g, '"')
|
||||||
|
.replace(/'/g, ''');
|
||||||
|
htmlParts.push(`<pre><code${langClass}>${escapedCode}</code></pre>`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -246,6 +298,7 @@ function generateKey(): string {
|
|||||||
return Math.random().toString(36).substring(2, 11);
|
return Math.random().toString(36).substring(2, 11);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Toolbar component
|
// Toolbar component
|
||||||
function EditorToolbar({
|
function EditorToolbar({
|
||||||
isScrollable,
|
isScrollable,
|
||||||
|
|||||||
Reference in New Issue
Block a user