fix: resolve section duplication issue when saving edited content
Fixed critical bug where editing a section would cause content duplication in the saved file due to improper handling of DOM reconstruction. Problem: - marked.parse() creates multiple HTML elements from single markdown section - markSections() marked all new elements as individual editable sections - getMarkdownContent() processed all marked sections, causing duplication - Example: editing "## Header\nText" created <h2> + <p>, both saved separately Solution: - Wrap edited content in container div with data-edited attribute - Update markSections() to skip elements inside edited wrappers - Enhanced getMarkdownContent() to handle edited wrappers as single units - Process child elements within edited wrappers correctly - Maintain section indexing while preventing double-marking Technical Changes: - editSection() now creates wrapper div for parsed content - markSections() skips content inside [data-edited] containers - getMarkdownContent() handles edited vs regular sections differently - Proper cleanup and re-indexing of section markers This ensures edited sections are treated as cohesive units and saved exactly once, eliminating content duplication while maintaining full editing functionality. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -687,10 +687,30 @@ class DocumentManager:
|
|||||||
}
|
}
|
||||||
|
|
||||||
markSections(element) {
|
markSections(element) {
|
||||||
|
// Clear existing section markers (except edited ones)
|
||||||
|
const existingSections = element.querySelectorAll('.markitect-section-editable:not([data-edited])');
|
||||||
|
existingSections.forEach(section => {
|
||||||
|
section.classList.remove('markitect-section-editable');
|
||||||
|
section.removeAttribute('data-section');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Mark new sections (skip elements inside edited wrappers)
|
||||||
const sections = element.querySelectorAll('h1, h2, h3, h4, h5, h6, p, blockquote, pre, ul, ol');
|
const sections = element.querySelectorAll('h1, h2, h3, h4, h5, h6, p, blockquote, pre, ul, ol');
|
||||||
sections.forEach((section, index) => {
|
let sectionIndex = 0;
|
||||||
|
|
||||||
|
sections.forEach((section) => {
|
||||||
|
// Skip if this element is inside an edited wrapper
|
||||||
|
if (section.closest('[data-edited]')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip if already marked as edited wrapper
|
||||||
|
if (section.hasAttribute('data-edited')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
section.classList.add('markitect-section-editable');
|
section.classList.add('markitect-section-editable');
|
||||||
section.setAttribute('data-section', index);
|
section.setAttribute('data-section', sectionIndex++);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -709,8 +729,19 @@ class DocumentManager:
|
|||||||
|
|
||||||
textarea.addEventListener('blur', () => {
|
textarea.addEventListener('blur', () => {
|
||||||
this.hasEdits = true; // Mark that edits have been made
|
this.hasEdits = true; // Mark that edits have been made
|
||||||
section.innerHTML = marked.parse(textarea.value);
|
|
||||||
this.markSections(section.parentElement);
|
// 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
|
||||||
|
|
||||||
|
// 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'));
|
||||||
});
|
});
|
||||||
|
|
||||||
section.innerHTML = '';
|
section.innerHTML = '';
|
||||||
@@ -800,32 +831,69 @@ class DocumentManager:
|
|||||||
let reconstructed = '';
|
let reconstructed = '';
|
||||||
|
|
||||||
sections.forEach(section => {{
|
sections.forEach(section => {{
|
||||||
const tagName = section.tagName.toLowerCase();
|
// Handle edited wrappers differently
|
||||||
const text = section.textContent.trim();
|
if (section.hasAttribute('data-edited')) {{
|
||||||
|
// For edited sections, convert the child elements back to markdown
|
||||||
|
const childElements = section.children;
|
||||||
|
for (let i = 0; i < childElements.length; i++) {{
|
||||||
|
const child = childElements[i];
|
||||||
|
const tagName = child.tagName.toLowerCase();
|
||||||
|
const text = child.textContent.trim();
|
||||||
|
|
||||||
if (tagName.startsWith('h')) {{
|
if (tagName.startsWith('h')) {{
|
||||||
const level = parseInt(tagName.charAt(1));
|
const level = parseInt(tagName.charAt(1));
|
||||||
reconstructed += '#'.repeat(level) + ' ' + text + '\\n\\n';
|
reconstructed += '#'.repeat(level) + ' ' + text + '\\n\\n';
|
||||||
}} else if (tagName === 'p') {{
|
}} else if (tagName === 'p') {{
|
||||||
reconstructed += text + '\\n\\n';
|
reconstructed += text + '\\n\\n';
|
||||||
}} else if (tagName === 'blockquote') {{
|
}} else if (tagName === 'blockquote') {{
|
||||||
reconstructed += '> ' + text + '\\n\\n';
|
reconstructed += '> ' + text + '\\n\\n';
|
||||||
}} else if (tagName === 'pre') {{
|
}} else if (tagName === 'pre') {{
|
||||||
reconstructed += '```\\n' + text + '\\n```\\n\\n';
|
reconstructed += '```\\n' + text + '\\n```\\n\\n';
|
||||||
}} else if (tagName === 'ul') {{
|
}} else if (tagName === 'ul') {{
|
||||||
const items = section.querySelectorAll('li');
|
const items = child.querySelectorAll('li');
|
||||||
items.forEach(item => {{
|
items.forEach(item => {{
|
||||||
reconstructed += '- ' + item.textContent.trim() + '\\n';
|
reconstructed += '- ' + item.textContent.trim() + '\\n';
|
||||||
}});
|
}});
|
||||||
reconstructed += '\\n';
|
reconstructed += '\\n';
|
||||||
}} else if (tagName === 'ol') {{
|
}} else if (tagName === 'ol') {{
|
||||||
const items = section.querySelectorAll('li');
|
const items = child.querySelectorAll('li');
|
||||||
items.forEach((item, index) => {{
|
items.forEach((item, index) => {{
|
||||||
reconstructed += (index + 1) + '. ' + item.textContent.trim() + '\\n';
|
reconstructed += (index + 1) + '. ' + item.textContent.trim() + '\\n';
|
||||||
}});
|
}});
|
||||||
reconstructed += '\\n';
|
reconstructed += '\\n';
|
||||||
|
}} else {{
|
||||||
|
reconstructed += text + '\\n\\n';
|
||||||
|
}}
|
||||||
|
}}
|
||||||
}} else {{
|
}} else {{
|
||||||
reconstructed += text + '\\n\\n';
|
// Handle regular sections
|
||||||
|
const tagName = section.tagName.toLowerCase();
|
||||||
|
const text = section.textContent.trim();
|
||||||
|
|
||||||
|
if (tagName.startsWith('h')) {{
|
||||||
|
const level = parseInt(tagName.charAt(1));
|
||||||
|
reconstructed += '#'.repeat(level) + ' ' + text + '\\n\\n';
|
||||||
|
}} else if (tagName === 'p') {{
|
||||||
|
reconstructed += text + '\\n\\n';
|
||||||
|
}} else if (tagName === 'blockquote') {{
|
||||||
|
reconstructed += '> ' + text + '\\n\\n';
|
||||||
|
}} else if (tagName === 'pre') {{
|
||||||
|
reconstructed += '```\\n' + text + '\\n```\\n\\n';
|
||||||
|
}} else if (tagName === 'ul') {{
|
||||||
|
const items = section.querySelectorAll('li');
|
||||||
|
items.forEach(item => {{
|
||||||
|
reconstructed += '- ' + item.textContent.trim() + '\\n';
|
||||||
|
}});
|
||||||
|
reconstructed += '\\n';
|
||||||
|
}} else if (tagName === 'ol') {{
|
||||||
|
const items = section.querySelectorAll('li');
|
||||||
|
items.forEach((item, index) => {{
|
||||||
|
reconstructed += (index + 1) + '. ' + item.textContent.trim() + '\\n';
|
||||||
|
}});
|
||||||
|
reconstructed += '\\n';
|
||||||
|
}} else {{
|
||||||
|
reconstructed += text + '\\n\\n';
|
||||||
|
}}
|
||||||
}}
|
}}
|
||||||
}});
|
}});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user