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
|
// 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;
|
||||||
|
|||||||
Reference in New Issue
Block a user