feat: implement Phase 3 container transformation with CLASSES.md compliance
- Add backend container transformation in engine.go following syntactic sugar specification - Containers with .insertr get class removed and viable children get .insertr added - Remove incorrect frontend container expansion - frontend only finds enhanced elements - Fix StyleAwareEditor hasMultiPropertyElements runtime error - Add addClass/removeClass methods to ContentEngine for class manipulation - Update frontend to match HTML-first approach with no runtime container logic - Test verified: container <section class='insertr'> transforms to individual h1.insertr, p.insertr, button.insertr This completes the container expansion functionality per CLASSES.md: Developer convenience (one .insertr enables section editing) + granular control (individual element editing)
This commit is contained in:
@@ -10,111 +10,23 @@ export class InsertrCore {
|
||||
};
|
||||
}
|
||||
|
||||
// Find all enhanced elements on the page with container expansion
|
||||
// Find all enhanced elements on the page
|
||||
// Note: Container expansion is handled at build-time by the backend enhancer
|
||||
// Frontend should only find elements that already have .insertr class
|
||||
findEnhancedElements() {
|
||||
const directElements = document.querySelectorAll('.insertr');
|
||||
const expandedElements = [];
|
||||
|
||||
directElements.forEach(element => {
|
||||
if (this.isContainer(element) && !element.classList.contains('insertr-group')) {
|
||||
// Container element (.insertr) - expand to viable children
|
||||
const children = this.findViableChildren(element);
|
||||
expandedElements.push(...children);
|
||||
} else {
|
||||
// Regular element or group (.insertr-group)
|
||||
expandedElements.push(element);
|
||||
}
|
||||
});
|
||||
|
||||
return expandedElements;
|
||||
return document.querySelectorAll('.insertr');
|
||||
}
|
||||
|
||||
// Check if element is a container that should expand to children
|
||||
isContainer(element) {
|
||||
const containerTags = new Set([
|
||||
'div', 'section', 'article', 'header',
|
||||
'footer', 'main', 'aside', 'nav'
|
||||
]);
|
||||
|
||||
return containerTags.has(element.tagName.toLowerCase());
|
||||
}
|
||||
|
||||
// Find viable children for editing (elements with only text content)
|
||||
findViableChildren(containerElement) {
|
||||
const viable = [];
|
||||
|
||||
for (const child of containerElement.children) {
|
||||
// Skip elements that already have .insertr class
|
||||
if (child.classList.contains('insertr')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip self-closing elements
|
||||
if (this.isSelfClosing(child)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if element has only text content (no nested HTML elements)
|
||||
if (this.hasOnlyTextContent(child)) {
|
||||
viable.push(child);
|
||||
}
|
||||
}
|
||||
|
||||
return viable;
|
||||
}
|
||||
|
||||
// Check if element is viable for editing (allows simple formatting)
|
||||
hasOnlyTextContent(element) {
|
||||
// Allow elements with simple formatting tags
|
||||
const allowedTags = new Set(['strong', 'b', 'em', 'i', 'a', 'span', 'code']);
|
||||
|
||||
for (const child of element.children) {
|
||||
const tagName = child.tagName.toLowerCase();
|
||||
|
||||
// If child is not an allowed formatting tag, reject
|
||||
if (!allowedTags.has(tagName)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If formatting tag has nested complex elements, reject
|
||||
if (child.children.length > 0) {
|
||||
// Recursively check nested content isn't too complex
|
||||
for (const nestedChild of child.children) {
|
||||
const nestedTag = nestedChild.tagName.toLowerCase();
|
||||
if (!allowedTags.has(nestedTag)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Element has only text and/or simple formatting - this is viable
|
||||
return element.textContent.trim().length > 0;
|
||||
}
|
||||
|
||||
// Check if element is self-closing
|
||||
isSelfClosing(element) {
|
||||
const selfClosingTags = new Set([
|
||||
'img', 'input', 'br', 'hr', 'meta', 'link',
|
||||
'area', 'base', 'col', 'embed', 'source', 'track', 'wbr'
|
||||
]);
|
||||
|
||||
return selfClosingTags.has(element.tagName.toLowerCase());
|
||||
}
|
||||
// Note: All container expansion logic removed - handled by backend enhancer
|
||||
// Frontend only works with elements that already have .insertr class
|
||||
|
||||
// Get element metadata
|
||||
getElementMetadata(element) {
|
||||
const existingId = element.getAttribute('data-content-id');
|
||||
|
||||
// Ensure element has insertr class for server processing
|
||||
if (!element.classList.contains('insertr')) {
|
||||
element.classList.add('insertr');
|
||||
}
|
||||
|
||||
// Send HTML markup to server for unified ID generation
|
||||
// HTML-first approach: no content type needed, just HTML markup for ID generation
|
||||
return {
|
||||
contentId: existingId, // null if new content, existing ID if updating
|
||||
contentType: element.getAttribute('data-content-type') || this.detectContentType(element),
|
||||
element: element,
|
||||
htmlMarkup: element.outerHTML // Server will generate ID from this
|
||||
};
|
||||
@@ -130,38 +42,10 @@ export class InsertrCore {
|
||||
return path.replace(/^\//, '');
|
||||
}
|
||||
|
||||
// Detect content type for elements without data-content-type
|
||||
detectContentType(element) {
|
||||
const tag = element.tagName.toLowerCase();
|
||||
|
||||
// Only return database-valid types: 'text' or 'link'
|
||||
if (tag === 'a' || tag === 'button') {
|
||||
return 'link';
|
||||
}
|
||||
|
||||
// All other elements are text content
|
||||
return 'text';
|
||||
}
|
||||
|
||||
// Get all elements with their metadata, including group elements
|
||||
// Get all elements with their metadata
|
||||
// Note: Container expansion handled by backend - frontend finds enhanced elements only
|
||||
getAllElements() {
|
||||
const directElements = document.querySelectorAll('.insertr, .insertr-group');
|
||||
const processedElements = [];
|
||||
|
||||
directElements.forEach(element => {
|
||||
if (element.classList.contains('insertr-group')) {
|
||||
// Group element - treat as single editable unit
|
||||
processedElements.push(element);
|
||||
} else if (this.isContainer(element)) {
|
||||
// Container element - expand to children
|
||||
const children = this.findViableChildren(element);
|
||||
processedElements.push(...children);
|
||||
} else {
|
||||
// Regular element
|
||||
processedElements.push(element);
|
||||
}
|
||||
});
|
||||
|
||||
return Array.from(processedElements).map(el => this.getElementMetadata(el));
|
||||
const elements = this.findEnhancedElements();
|
||||
return Array.from(elements).map(el => this.getElementMetadata(el));
|
||||
}
|
||||
}
|
||||
@@ -370,16 +370,16 @@ body:not(.insertr-edit-mode) .insertr-editing-hover::after {
|
||||
transform: translateY(1px);
|
||||
}
|
||||
|
||||
/* Style preview buttons - styled dynamically via JavaScript */
|
||||
/* Style preview buttons - new approach using CSS isolation */
|
||||
.insertr-style-btn.insertr-style-preview {
|
||||
/* Preserve button structure */
|
||||
background: var(--insertr-bg-primary) !important;
|
||||
border: 1px solid var(--insertr-border-color) !important;
|
||||
border-radius: var(--insertr-border-radius) !important;
|
||||
padding: var(--insertr-spacing-xs) var(--insertr-spacing-sm) !important;
|
||||
font-size: var(--insertr-font-size-sm) !important;
|
||||
cursor: pointer !important;
|
||||
transition: var(--insertr-transition) !important;
|
||||
/* Clean button container - minimal styling */
|
||||
background: var(--insertr-bg-primary);
|
||||
border: 1px solid var(--insertr-border-color);
|
||||
border-radius: var(--insertr-border-radius);
|
||||
padding: 0; /* Remove padding - let preview content handle it */
|
||||
font-size: var(--insertr-font-size-sm);
|
||||
cursor: pointer;
|
||||
transition: var(--insertr-transition);
|
||||
|
||||
/* Button layout */
|
||||
min-height: 28px;
|
||||
@@ -387,37 +387,59 @@ body:not(.insertr-edit-mode) .insertr-editing-hover::after {
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
/* Ensure button remains clickable */
|
||||
position: relative;
|
||||
/* Reset any inherited text styles on the button container only */
|
||||
font-family: var(--insertr-font-family);
|
||||
text-decoration: none;
|
||||
font-weight: normal;
|
||||
text-transform: none;
|
||||
|
||||
/* Styles will be applied dynamically via JavaScript */
|
||||
/* Don't set color here - let the preview content inherit naturally */
|
||||
|
||||
/* Ensure content fits */
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Add subtle background to preview buttons to ensure they remain clickable-looking */
|
||||
.insertr-style-btn.insertr-style-preview::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: var(--insertr-bg-primary);
|
||||
opacity: 0.9;
|
||||
border-radius: inherit;
|
||||
z-index: -1;
|
||||
/* Preview content container - minimal interference with original styling */
|
||||
.insertr-preview-content {
|
||||
/* Allow the original classes to style this element completely naturally */
|
||||
display: inline-block;
|
||||
|
||||
/* Only set essential layout properties */
|
||||
box-sizing: border-box;
|
||||
|
||||
/* Ensure text fits within button */
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
max-width: 120px;
|
||||
|
||||
/* Inherit font size from button container */
|
||||
font-size: inherit;
|
||||
|
||||
/* Remove browser defaults that might interfere - but don't override intentional styling */
|
||||
border: none;
|
||||
margin: 0;
|
||||
|
||||
/* NO background, color, padding defaults - let the classes handle everything */
|
||||
}
|
||||
|
||||
/* Hover state for preview buttons */
|
||||
/* Minimal fallback styling when no meaningful classes are detected */
|
||||
.insertr-preview-content.insertr-fallback-style {
|
||||
padding: var(--insertr-spacing-xs) var(--insertr-spacing-sm);
|
||||
color: var(--insertr-text-primary);
|
||||
}
|
||||
|
||||
/* Hover state for preview buttons - subtle visual feedback */
|
||||
.insertr-style-btn.insertr-style-preview:hover {
|
||||
background: var(--insertr-bg-secondary) !important;
|
||||
border-color: var(--insertr-text-muted) !important;
|
||||
transform: none !important;
|
||||
border-color: var(--insertr-text-muted);
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
/* Active state for preview buttons */
|
||||
.insertr-style-btn.insertr-style-preview:active {
|
||||
background: var(--insertr-border-color) !important;
|
||||
transform: translateY(1px) !important;
|
||||
transform: translateY(0);
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
/* Editor components */
|
||||
|
||||
@@ -88,43 +88,41 @@ export class StyleAwareEditor {
|
||||
styles: detection.styles,
|
||||
structure: detection.structure,
|
||||
strategy: editingStrategy,
|
||||
hasMultiPropertyElements: this.hasMultiPropertyElements(detection.structure)
|
||||
hasMultiPropertyElements: false // Removed - not needed for HTML-first approach
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the best editing strategy based on content complexity
|
||||
* Determine the best editing interface based on element type
|
||||
* HTML-first approach: detect behavior from element tag, not attributes
|
||||
*
|
||||
* @param {Object} detection - Style detection results
|
||||
* @returns {string} - Editing strategy: 'simple', 'rich', 'multi-property'
|
||||
* @returns {string} - Editing interface: 'direct' or 'rich'
|
||||
*/
|
||||
determineEditingStrategy(detection) {
|
||||
if (detection.structure.length === 0) {
|
||||
return 'simple'; // No nested elements
|
||||
const tagName = this.element.tagName.toLowerCase();
|
||||
|
||||
// Multi-property elements get direct editing interface
|
||||
if (this.isMultiPropertyElement(tagName)) {
|
||||
return 'direct';
|
||||
}
|
||||
|
||||
const hasStyledElements = detection.structure.some(piece => piece.type === 'styled');
|
||||
|
||||
if (hasStyledElements) {
|
||||
return 'rich'; // Rich text with styling (handles all styled content including links)
|
||||
} else {
|
||||
return 'simple'; // Plain text
|
||||
}
|
||||
// All other elements get rich HTML editing with style preservation
|
||||
return 'rich';
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if element is a multi-property element requiring direct editing
|
||||
*
|
||||
* @param {string} tagName - Element tag name
|
||||
* @returns {boolean} - True if multi-property element
|
||||
*/
|
||||
isMultiPropertyElement(tagName) {
|
||||
const multiPropertyTags = new Set(['a', 'button', 'img']);
|
||||
return multiPropertyTags.has(tagName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if structure contains multi-property elements
|
||||
*
|
||||
* @param {Array} structure - Content structure array
|
||||
* @returns {boolean} - True if has multi-property elements
|
||||
*/
|
||||
hasMultiPropertyElements(structure) {
|
||||
return structure.some(piece =>
|
||||
piece.type === 'styled' &&
|
||||
piece.properties &&
|
||||
Object.keys(piece.properties).length > 1
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create editor interface based on analysis
|
||||
@@ -138,16 +136,16 @@ export class StyleAwareEditor {
|
||||
|
||||
// Create appropriate editor based on strategy
|
||||
switch (analysis.strategy) {
|
||||
case 'simple':
|
||||
this.createSimpleEditor();
|
||||
case 'direct':
|
||||
this.createDirectEditor(analysis);
|
||||
break;
|
||||
case 'rich':
|
||||
this.createRichEditor(analysis);
|
||||
break;
|
||||
}
|
||||
|
||||
// Add toolbar if enabled and we have any styled content
|
||||
if (this.options.showToolbar && (analysis.styles.size > 0 || analysis.hasMultiPropertyElements)) {
|
||||
// Add toolbar if enabled and we have styled content (rich editor only)
|
||||
if (this.options.showToolbar && analysis.strategy === 'rich' && analysis.styles.size > 0) {
|
||||
this.createStyleToolbar(analysis.styles, analysis.structure);
|
||||
}
|
||||
|
||||
@@ -156,17 +154,114 @@ export class StyleAwareEditor {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create simple text editor for plain content
|
||||
* Create direct property editor for multi-property elements (links, buttons, images)
|
||||
*/
|
||||
createSimpleEditor() {
|
||||
const textarea = document.createElement('textarea');
|
||||
textarea.className = 'insertr-simple-editor';
|
||||
textarea.value = this.originalContent.text;
|
||||
textarea.rows = 3;
|
||||
textarea.placeholder = 'Enter content...';
|
||||
createDirectEditor(analysis) {
|
||||
const tagName = this.element.tagName.toLowerCase();
|
||||
|
||||
this.contentEditor = textarea;
|
||||
this.editorContainer.appendChild(textarea);
|
||||
if (tagName === 'a') {
|
||||
this.createLinkEditor();
|
||||
} else if (tagName === 'button') {
|
||||
this.createButtonEditor();
|
||||
} else if (tagName === 'img') {
|
||||
this.createImageEditor();
|
||||
} else {
|
||||
// Fallback to rich editor
|
||||
this.createRichEditor(analysis);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create link editor with URL and text fields
|
||||
*/
|
||||
createLinkEditor() {
|
||||
const form = document.createElement('div');
|
||||
form.className = 'insertr-direct-editor insertr-link-editor';
|
||||
|
||||
// Text field
|
||||
const textGroup = document.createElement('div');
|
||||
textGroup.className = 'insertr-form-group';
|
||||
textGroup.innerHTML = `
|
||||
<label class="insertr-form-label">Link Text</label>
|
||||
<input type="text" class="insertr-form-input" id="link-text" value="${this.element.textContent}" placeholder="Link text">
|
||||
`;
|
||||
|
||||
// URL field
|
||||
const urlGroup = document.createElement('div');
|
||||
urlGroup.className = 'insertr-form-group';
|
||||
urlGroup.innerHTML = `
|
||||
<label class="insertr-form-label">URL</label>
|
||||
<input type="url" class="insertr-form-input" id="link-url" value="${this.element.href || ''}" placeholder="https://example.com">
|
||||
`;
|
||||
|
||||
// Target field
|
||||
const targetGroup = document.createElement('div');
|
||||
targetGroup.className = 'insertr-form-group';
|
||||
targetGroup.innerHTML = `
|
||||
<label class="insertr-form-label">Target</label>
|
||||
<select class="insertr-form-select" id="link-target">
|
||||
<option value="">Same window</option>
|
||||
<option value="_blank" ${this.element.target === '_blank' ? 'selected' : ''}>New window</option>
|
||||
</select>
|
||||
`;
|
||||
|
||||
form.appendChild(textGroup);
|
||||
form.appendChild(urlGroup);
|
||||
form.appendChild(targetGroup);
|
||||
|
||||
this.contentEditor = form;
|
||||
this.editorContainer.appendChild(form);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create button editor with text field
|
||||
*/
|
||||
createButtonEditor() {
|
||||
const form = document.createElement('div');
|
||||
form.className = 'insertr-direct-editor insertr-button-editor';
|
||||
|
||||
// Text field
|
||||
const textGroup = document.createElement('div');
|
||||
textGroup.className = 'insertr-form-group';
|
||||
textGroup.innerHTML = `
|
||||
<label class="insertr-form-label">Button Text</label>
|
||||
<input type="text" class="insertr-form-input" id="button-text" value="${this.element.textContent}" placeholder="Button text">
|
||||
`;
|
||||
|
||||
form.appendChild(textGroup);
|
||||
|
||||
this.contentEditor = form;
|
||||
this.editorContainer.appendChild(form);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create image editor with src and alt fields
|
||||
*/
|
||||
createImageEditor() {
|
||||
const form = document.createElement('div');
|
||||
form.className = 'insertr-direct-editor insertr-image-editor';
|
||||
|
||||
// Source field
|
||||
const srcGroup = document.createElement('div');
|
||||
srcGroup.className = 'insertr-form-group';
|
||||
srcGroup.innerHTML = `
|
||||
<label class="insertr-form-label">Image URL</label>
|
||||
<input type="url" class="insertr-form-input" id="image-src" value="${this.element.src || ''}" placeholder="https://example.com/image.jpg">
|
||||
`;
|
||||
|
||||
// Alt text field
|
||||
const altGroup = document.createElement('div');
|
||||
altGroup.className = 'insertr-form-group';
|
||||
altGroup.innerHTML = `
|
||||
<label class="insertr-form-label">Alt Text</label>
|
||||
<input type="text" class="insertr-form-input" id="image-alt" value="${this.element.alt || ''}" placeholder="Image description">
|
||||
`;
|
||||
|
||||
form.appendChild(srcGroup);
|
||||
form.appendChild(altGroup);
|
||||
|
||||
this.contentEditor = form;
|
||||
this.editorContainer.appendChild(form);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -242,46 +337,31 @@ export class StyleAwareEditor {
|
||||
const button = document.createElement('button');
|
||||
button.type = 'button';
|
||||
button.className = 'insertr-style-btn';
|
||||
button.textContent = styleInfo.name;
|
||||
button.title = `Apply ${styleInfo.name} style`;
|
||||
button.dataset.styleId = styleId;
|
||||
|
||||
// Apply preview styling by copying computed styles from the original element
|
||||
// Create preview content container
|
||||
const previewContent = document.createElement('span');
|
||||
previewContent.className = 'insertr-preview-content';
|
||||
previewContent.textContent = styleInfo.name;
|
||||
|
||||
// Apply the original classes to the preview content (not the button)
|
||||
if (styleInfo.element && styleInfo.classes && styleInfo.classes.length > 0) {
|
||||
// Add the detected classes first
|
||||
styleInfo.classes.forEach(className => {
|
||||
button.classList.add(className);
|
||||
});
|
||||
|
||||
// Add special button class
|
||||
// Add special preview button class to the button
|
||||
button.classList.add('insertr-style-preview');
|
||||
|
||||
// Copy specific style properties from the original element to ensure they show
|
||||
const computedStyle = window.getComputedStyle(styleInfo.element);
|
||||
|
||||
// Copy color (most important for visual preview)
|
||||
if (computedStyle.color && computedStyle.color !== 'rgb(0, 0, 0)') {
|
||||
button.style.setProperty('color', computedStyle.color, 'important');
|
||||
}
|
||||
|
||||
// Copy font-weight
|
||||
if (computedStyle.fontWeight && computedStyle.fontWeight !== '400') {
|
||||
button.style.setProperty('font-weight', computedStyle.fontWeight, 'important');
|
||||
}
|
||||
|
||||
// Copy text-decoration (for underlines, etc.)
|
||||
if (computedStyle.textDecoration && computedStyle.textDecoration !== 'none') {
|
||||
button.style.setProperty('text-decoration', computedStyle.textDecoration, 'important');
|
||||
}
|
||||
|
||||
// Copy text-transform (for uppercase, etc.)
|
||||
if (computedStyle.textTransform && computedStyle.textTransform !== 'none') {
|
||||
button.style.setProperty('text-transform', computedStyle.textTransform, 'important');
|
||||
}
|
||||
|
||||
// Don't copy background-color to keep button appearance
|
||||
// Add the detected classes to the preview content
|
||||
styleInfo.classes.forEach(className => {
|
||||
previewContent.classList.add(className);
|
||||
});
|
||||
} else {
|
||||
// No meaningful styles detected - use fallback
|
||||
previewContent.classList.add('insertr-fallback-style');
|
||||
}
|
||||
|
||||
// Add the preview content to the button
|
||||
button.appendChild(previewContent);
|
||||
|
||||
// Add click handler for style application
|
||||
button.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
@@ -449,18 +529,16 @@ export class StyleAwareEditor {
|
||||
|
||||
/**
|
||||
* Extract current content from editor
|
||||
* HTML-first approach: always return HTML content
|
||||
*
|
||||
* @returns {Object} - Extracted content
|
||||
*/
|
||||
extractContent() {
|
||||
if (this.contentEditor.className.includes('simple-editor')) {
|
||||
// Simple text editor
|
||||
return {
|
||||
type: 'text',
|
||||
content: this.contentEditor.value
|
||||
};
|
||||
if (this.contentEditor.className.includes('direct-editor')) {
|
||||
// Direct property editor - extract form values and generate HTML
|
||||
return this.extractDirectEditorContent();
|
||||
} else if (this.contentEditor.className.includes('rich-editor')) {
|
||||
// Rich text editor
|
||||
// Rich text editor - return HTML as-is
|
||||
return {
|
||||
type: 'html',
|
||||
content: this.contentEditor.innerHTML
|
||||
@@ -469,40 +547,74 @@ export class StyleAwareEditor {
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract content from direct property editors
|
||||
*
|
||||
* @returns {Object} - Content with generated HTML
|
||||
*/
|
||||
extractDirectEditorContent() {
|
||||
const tagName = this.element.tagName.toLowerCase();
|
||||
|
||||
if (tagName === 'a') {
|
||||
const text = document.getElementById('link-text').value;
|
||||
const url = document.getElementById('link-url').value;
|
||||
const target = document.getElementById('link-target').value;
|
||||
|
||||
return {
|
||||
type: 'html',
|
||||
content: text,
|
||||
properties: { href: url, target: target }
|
||||
};
|
||||
} else if (tagName === 'button') {
|
||||
const text = document.getElementById('button-text').value;
|
||||
|
||||
return {
|
||||
type: 'html',
|
||||
content: text
|
||||
};
|
||||
} else if (tagName === 'img') {
|
||||
const src = document.getElementById('image-src').value;
|
||||
const alt = document.getElementById('image-alt').value;
|
||||
|
||||
return {
|
||||
type: 'html',
|
||||
content: '',
|
||||
properties: { src: src, alt: alt }
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Apply extracted content to the original element
|
||||
* HTML-first approach: always use HTML preservation with optional property updates
|
||||
*
|
||||
* @param {Object} content - Content to apply
|
||||
* @returns {boolean} - Success status
|
||||
*/
|
||||
applyContentToElement(content) {
|
||||
try {
|
||||
switch (content.type) {
|
||||
case 'text':
|
||||
// Simple text - just update textContent
|
||||
this.element.textContent = content.content;
|
||||
return true;
|
||||
|
||||
case 'html':
|
||||
// Rich HTML - use HTML preservation engine
|
||||
return this.htmlEngine.applyFromEditing(this.element, content.content);
|
||||
|
||||
case 'structured':
|
||||
// Structured content - reconstruct using style engine
|
||||
const reconstructedHTML = this.styleEngine.reconstructHTML(
|
||||
content.structure,
|
||||
this.detectedStyles,
|
||||
content.updatedProperties
|
||||
);
|
||||
return this.htmlEngine.applyFromEditing(this.element, reconstructedHTML);
|
||||
|
||||
default:
|
||||
console.error('Unknown content type:', content.type);
|
||||
return false;
|
||||
// Apply properties if specified (for direct editors)
|
||||
if (content.properties) {
|
||||
for (const [property, value] of Object.entries(content.properties)) {
|
||||
if (value) {
|
||||
this.element.setAttribute(property, value);
|
||||
} else {
|
||||
this.element.removeAttribute(property);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Apply HTML content using preservation engine
|
||||
if (content.content !== undefined) {
|
||||
return this.htmlEngine.applyFromEditing(this.element, content.content);
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('Failed to apply content:', error);
|
||||
return false;
|
||||
|
||||
Reference in New Issue
Block a user