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:
@@ -218,14 +218,11 @@ class Insertr {
|
||||
|
||||
// Update content based on type
|
||||
if (contentType === 'rich') {
|
||||
const html = this.markdownToHtml(content);
|
||||
originalContent.innerHTML = html + '<button class="insertr-edit-btn">✏️</button>';
|
||||
// For rich content, intelligently update existing HTML structure
|
||||
this.updateRichContent(originalContent, content);
|
||||
} else {
|
||||
// For simple content, try to maintain structure but update text
|
||||
const textNodes = this.getTextNodes(originalContent);
|
||||
if (textNodes.length > 0) {
|
||||
textNodes[0].textContent = content;
|
||||
}
|
||||
// For simple content, update text while preserving structure
|
||||
this.updateSimpleContent(originalContent, content);
|
||||
}
|
||||
|
||||
// Replace element content
|
||||
@@ -237,6 +234,176 @@ class Insertr {
|
||||
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) {
|
||||
const element = this.editablElements.get(contentId);
|
||||
if (!element) return;
|
||||
|
||||
Reference in New Issue
Block a user