From 93655512d07298052976a541947a8ee9cf595beb Mon Sep 17 00:00:00 2001 From: tegwick Date: Sat, 25 Oct 2025 21:23:34 +0200 Subject: [PATCH] fix: resolve section duplication issue when saving edited content MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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

+

, 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 --- markitect/document_manager.py | 124 ++++++++++++++++++++++++++-------- 1 file changed, 96 insertions(+), 28 deletions(-) diff --git a/markitect/document_manager.py b/markitect/document_manager.py index a7055964..3ce41f57 100644 --- a/markitect/document_manager.py +++ b/markitect/document_manager.py @@ -687,10 +687,30 @@ class DocumentManager: } 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'); - 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.setAttribute('data-section', index); + section.setAttribute('data-section', sectionIndex++); }); } @@ -709,8 +729,19 @@ class DocumentManager: textarea.addEventListener('blur', () => { 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 = ''; @@ -800,32 +831,69 @@ class DocumentManager: let reconstructed = ''; sections.forEach(section => {{ - const tagName = section.tagName.toLowerCase(); - const text = section.textContent.trim(); + // Handle edited wrappers differently + 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')) {{ - 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'; + 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 = child.querySelectorAll('li'); + items.forEach(item => {{ + reconstructed += '- ' + item.textContent.trim() + '\\n'; + }}); + reconstructed += '\\n'; + }} else if (tagName === 'ol') {{ + const items = child.querySelectorAll('li'); + items.forEach((item, index) => {{ + reconstructed += (index + 1) + '. ' + item.textContent.trim() + '\\n'; + }}); + reconstructed += '\\n'; + }} else {{ + reconstructed += text + '\\n\\n'; + }} + }} }} 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'; + }} }} }});