feat: enhance empty line preservation and automatic paragraph separation
Implemented sophisticated paragraph handling for the markdown editor: Enhanced HTML-to-Markdown Conversion: - Replaced simple tag stripping with proper structural parsing - Preserves formatting for headers, emphasis, code, blockquotes - Maintains paragraph separation with proper spacing - Handles nested elements and mixed content correctly Dynamic Section Splitting: - Detects paragraph breaks (double newlines) when editing - Automatically creates separate editable sections for each paragraph - Enables independent editing of logically separate content - Maintains proper section indexing with sub-identifiers Visual Enhancements: - Added green styling for edited sections to distinguish from originals - Subtle borders and backgrounds indicate modified content - Hover effects provide clear feedback on editable areas Technical Improvements: - Enhanced blur handler to detect multiple paragraphs - Smart wrapper creation for single vs. multi-paragraph content - Proper DOM manipulation for section insertion and replacement - Preserves editing state and section relationships Benefits: - Empty lines between paragraphs are preserved accurately - Text separated by empty lines becomes independently editable - Better content organization and editing granularity - Improved user experience with clear visual feedback This resolves the empty line swallowing issue and provides intuitive paragraph-level editing that matches user expectations. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -618,6 +618,16 @@ class DocumentManager:
|
||||
background: rgba(0, 122, 204, 0.05);
|
||||
}
|
||||
|
||||
.markitect-section-editable[data-edited] {
|
||||
border-color: rgba(76, 175, 80, 0.3);
|
||||
background: rgba(76, 175, 80, 0.02);
|
||||
}
|
||||
|
||||
.markitect-section-editable[data-edited]:hover {
|
||||
border-color: #4caf50;
|
||||
background: rgba(76, 175, 80, 0.08);
|
||||
}
|
||||
|
||||
.edit-mode textarea {
|
||||
width: 100%;
|
||||
min-height: 100px;
|
||||
@@ -730,15 +740,40 @@ class DocumentManager:
|
||||
textarea.addEventListener('blur', () => {
|
||||
this.hasEdits = true; // Mark that edits have been made
|
||||
|
||||
// Create a wrapper div to contain the parsed content
|
||||
const wrapper = document.createElement('div');
|
||||
wrapper.innerHTML = marked.parse(textarea.value);
|
||||
wrapper.classList.add('markitect-section-editable');
|
||||
wrapper.setAttribute('data-section', section.getAttribute('data-section'));
|
||||
wrapper.setAttribute('data-edited', 'true'); // Mark as edited
|
||||
// Check if the content contains paragraph breaks that should create separate sections
|
||||
const content = textarea.value.trim();
|
||||
const paragraphs = content.split(/\\n\\s*\\n/).filter(p => p.trim());
|
||||
|
||||
// Replace the section with the wrapper
|
||||
section.parentNode.replaceChild(wrapper, section);
|
||||
if (paragraphs.length > 1) {
|
||||
// Multiple paragraphs - create separate sections
|
||||
const parentNode = section.parentNode;
|
||||
const sectionIndex = section.getAttribute('data-section');
|
||||
|
||||
// Remove the original section
|
||||
parentNode.removeChild(section);
|
||||
|
||||
// Create separate sections for each paragraph
|
||||
paragraphs.forEach((paragraph, index) => {
|
||||
const wrapper = document.createElement('div');
|
||||
wrapper.innerHTML = marked.parse(paragraph.trim());
|
||||
wrapper.classList.add('markitect-section-editable');
|
||||
wrapper.setAttribute('data-section', sectionIndex + '_' + index);
|
||||
wrapper.setAttribute('data-edited', 'true');
|
||||
|
||||
// Insert each new section
|
||||
parentNode.appendChild(wrapper);
|
||||
});
|
||||
} else {
|
||||
// Single content block - create one wrapper
|
||||
const wrapper = document.createElement('div');
|
||||
wrapper.innerHTML = marked.parse(content);
|
||||
wrapper.classList.add('markitect-section-editable');
|
||||
wrapper.setAttribute('data-section', section.getAttribute('data-section'));
|
||||
wrapper.setAttribute('data-edited', 'true');
|
||||
|
||||
// Replace the section with the wrapper
|
||||
section.parentNode.replaceChild(wrapper, section);
|
||||
}
|
||||
|
||||
// Re-mark sections in the entire document, but skip edited wrappers
|
||||
this.markSections(document.getElementById('markdown-content'));
|
||||
@@ -750,8 +785,52 @@ class DocumentManager:
|
||||
}
|
||||
|
||||
htmlToMarkdown(html) {
|
||||
// Simple HTML to Markdown conversion
|
||||
return html.replace(/<[^>]*>/g, '').trim();
|
||||
// Create a temporary element to parse the HTML
|
||||
const temp = document.createElement('div');
|
||||
temp.innerHTML = html;
|
||||
|
||||
// Better HTML to Markdown conversion that preserves structure
|
||||
let markdown = '';
|
||||
|
||||
const processNode = (node) => {
|
||||
if (node.nodeType === Node.TEXT_NODE) {
|
||||
return node.textContent;
|
||||
}
|
||||
|
||||
if (node.nodeType === Node.ELEMENT_NODE) {
|
||||
const tagName = node.tagName.toLowerCase();
|
||||
const childText = Array.from(node.childNodes).map(processNode).join('');
|
||||
|
||||
switch (tagName) {
|
||||
case 'h1': return '# ' + childText;
|
||||
case 'h2': return '## ' + childText;
|
||||
case 'h3': return '### ' + childText;
|
||||
case 'h4': return '#### ' + childText;
|
||||
case 'h5': return '##### ' + childText;
|
||||
case 'h6': return '###### ' + childText;
|
||||
case 'p': return childText;
|
||||
case 'strong': case 'b': return '**' + childText + '**';
|
||||
case 'em': case 'i': return '*' + childText + '*';
|
||||
case 'code': return '`' + childText + '`';
|
||||
case 'blockquote': return '> ' + childText;
|
||||
case 'br': return '\\n';
|
||||
default: return childText;
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
};
|
||||
|
||||
// Process each child node and add appropriate spacing
|
||||
Array.from(temp.childNodes).forEach((node, index) => {
|
||||
const result = processNode(node);
|
||||
if (result.trim()) {
|
||||
if (index > 0) markdown += '\\n\\n';
|
||||
markdown += result;
|
||||
}
|
||||
});
|
||||
|
||||
return markdown.trim();
|
||||
}
|
||||
|
||||
setupKeyboardShortcuts() {
|
||||
|
||||
Reference in New Issue
Block a user