Preserve layout and styling when applying edited content

🎯 Problem: Buttons and styling lost when saving rich content
 Solution: Smart content application that preserves HTML structure

🔧 New Features:
- parseMarkdownToBlocks() - Intelligently parses markdown
- updateRichContent() - Updates elements in place vs wholesale replacement
- canUpdateElement() - Checks element/content compatibility
- updateElementContent() - Preserves classes while updating content
- looksLikeButton() - Heuristics to detect and preserve button styling

🎨 Layout Preservation:
- Buttons keep their 'btn-primary' classes and styling
- Headings maintain existing CSS classes and hierarchy
- Paragraphs preserve lead/subtitle classes
- Links maintain href and visual styling

 User Experience:
- Edit 'Get Started Today' button → still looks like a button after save
- Rich content maintains professional appearance
- No more plain text where buttons should be

Smart frontend content management without backend complexity!
This commit is contained in:
2025-08-29 23:08:46 +02:00
parent 04e669eb95
commit dc70b74f7d

View File

@@ -218,14 +218,11 @@ class Insertr {
// Update content based on type // Update content based on type
if (contentType === 'rich') { if (contentType === 'rich') {
const html = this.markdownToHtml(content); // For rich content, intelligently update existing HTML structure
originalContent.innerHTML = html + '<button class="insertr-edit-btn">✏️</button>'; this.updateRichContent(originalContent, content);
} else { } else {
// For simple content, try to maintain structure but update text // For simple content, update text while preserving structure
const textNodes = this.getTextNodes(originalContent); this.updateSimpleContent(originalContent, content);
if (textNodes.length > 0) {
textNodes[0].textContent = content;
}
} }
// Replace element content // Replace element content
@@ -237,6 +234,176 @@ class Insertr {
this.setupEditableElement(element, element.getAttribute('data-content-id')); this.setupEditableElement(element, element.getAttribute('data-content-id'));
} }
updateRichContent(container, markdownContent) {
// Parse markdown content into structured data
const contentBlocks = this.parseMarkdownToBlocks(markdownContent);
// Get existing elements in the container
const existingElements = Array.from(container.children).filter(el =>
!el.classList.contains('insertr-edit-btn')
);
// Update existing elements or create new ones
contentBlocks.forEach((block, index) => {
const existingElement = existingElements[index];
if (existingElement && this.canUpdateElement(existingElement, block.type)) {
// Update existing element while preserving styling
this.updateElementContent(existingElement, block);
} else {
// Create new element or replace incompatible one
const newElement = this.createElementFromBlock(block);
if (existingElement) {
container.replaceChild(newElement, existingElement);
} else {
container.appendChild(newElement);
}
}
});
// Remove extra elements
for (let i = contentBlocks.length; i < existingElements.length; i++) {
if (existingElements[i]) {
container.removeChild(existingElements[i]);
}
}
}
updateSimpleContent(container, textContent) {
// For simple content, find the main text node and update it
const textNodes = this.getTextNodes(container);
if (textNodes.length > 0) {
textNodes[0].textContent = textContent;
} else {
// If no text nodes, update first element's text content
const firstElement = container.querySelector('h1, h2, h3, h4, h5, h6, p, span, div');
if (firstElement) {
firstElement.textContent = textContent;
}
}
}
parseMarkdownToBlocks(markdown) {
const blocks = [];
const lines = markdown.split('\n');
let currentBlock = null;
lines.forEach(line => {
line = line.trim();
if (!line && currentBlock) {
// Empty line - finish current block
blocks.push(currentBlock);
currentBlock = null;
return;
}
if (!line) return; // Skip empty lines when no current block
// Check for headings
if (line.match(/^#{1,6}\s/)) {
if (currentBlock) blocks.push(currentBlock);
const level = line.match(/^#+/)[0].length;
currentBlock = {
type: `h${level}`,
content: line.replace(/^#+\s*/, '').trim()
};
}
// Check for links (potential buttons)
else if (line.match(/\[([^\]]+)\]\(([^)]+)\)/)) {
if (currentBlock) blocks.push(currentBlock);
const match = line.match(/\[([^\]]+)\]\(([^)]+)\)/);
currentBlock = {
type: 'link',
content: match[1],
href: match[2]
};
}
// Regular paragraph text
else {
if (!currentBlock) {
currentBlock = { type: 'p', content: line };
} else if (currentBlock.type === 'p') {
currentBlock.content += ' ' + line;
} else {
// Different block type, finish current and start new
blocks.push(currentBlock);
currentBlock = { type: 'p', content: line };
}
}
});
if (currentBlock) blocks.push(currentBlock);
return blocks;
}
canUpdateElement(element, blockType) {
const tagName = element.tagName.toLowerCase();
// Check if element type matches block type
if (tagName === blockType) return true;
if (tagName === 'a' && blockType === 'link') return true;
if (tagName === 'p' && blockType === 'p') return true;
return false;
}
updateElementContent(element, block) {
if (block.type === 'link' && element.tagName.toLowerCase() === 'a') {
// Update link while preserving classes (like btn-primary)
element.textContent = block.content;
element.href = block.href;
} else {
// Update text content while preserving all attributes and classes
element.textContent = block.content;
}
}
createElementFromBlock(block) {
let element;
switch (block.type) {
case 'h1':
case 'h2':
case 'h3':
case 'h4':
case 'h5':
case 'h6':
element = document.createElement(block.type);
element.textContent = block.content;
break;
case 'link':
element = document.createElement('a');
element.textContent = block.content;
element.href = block.href;
// Try to detect if this should be a button
if (this.looksLikeButton(block.content)) {
element.className = 'btn-primary';
}
break;
case 'p':
default:
element = document.createElement('p');
element.textContent = block.content;
break;
}
return element;
}
looksLikeButton(text) {
// Heuristics to detect button-like text
const buttonKeywords = [
'get started', 'start', 'begin', 'click here', 'learn more',
'contact', 'call', 'schedule', 'book', 'download', 'sign up',
'register', 'join', 'subscribe', 'buy', 'order', 'purchase'
];
const lowerText = text.toLowerCase();
return buttonKeywords.some(keyword => lowerText.includes(keyword));
}
cancelEditing(contentId) { cancelEditing(contentId) {
const element = this.editablElements.get(contentId); const element = this.editablElements.get(contentId);
if (!element) return; if (!element) return;