feat: unify link editing interfaces with comprehensive polish

Multi-Property Editor Polish:
- Add comprehensive form styling (.insertr-form-group, .insertr-form-input, etc.)
- Professional layout with titles, validation, auto-focus, and help text
- Enhanced link/button/image editors with real-time validation
- Consistent spacing, colors, and visual hierarchy

Smart Default Formatting:
- Add Bold, Italic, Link options when not detected in content
- Intelligent detection respects existing developer styles
- Visual distinction for default vs detected styles with info-colored borders
- Content-aware: only adds to elements that benefit from text formatting

Link Interface Unification:
- Create shared createLinkConfigurationForm() component
- Eliminate code duplication between direct editing and popup creation
- Update createLinkEditor() and showLinkConfigPopup() to use shared component
- Fix link button styling to match other style buttons with preview content

Benefits:
- Consistent professional editing experience across all interfaces
- Reduced maintenance burden through code unification
- Enhanced UX with validation, keyboard shortcuts, and visual feedback
- Maintains CLASSES.md philosophy while improving out-of-box experience
This commit is contained in:
2025-09-21 20:47:22 +02:00
parent b75eda2a87
commit d44bdd41b4
3 changed files with 760 additions and 279 deletions

View File

@@ -442,6 +442,46 @@ body:not(.insertr-edit-mode) .insertr-editing-hover::after {
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1); box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
} }
/* Default formatting style buttons */
.insertr-style-btn.insertr-default-style {
border-color: var(--insertr-info);
background: rgba(23, 162, 184, 0.1);
position: relative;
}
.insertr-style-btn.insertr-default-style:hover {
border-color: var(--insertr-info);
background: rgba(23, 162, 184, 0.2);
transform: translateY(-1px);
box-shadow: 0 2px 4px rgba(23, 162, 184, 0.3);
}
.insertr-style-btn.insertr-default-style:active {
transform: translateY(0);
box-shadow: 0 1px 2px rgba(23, 162, 184, 0.2);
}
/* Default preview content styling */
.insertr-default-preview {
font-size: var(--insertr-font-size-sm);
font-weight: 500;
padding: var(--insertr-spacing-xs) var(--insertr-spacing-sm);
display: inline-block;
}
/* Small indicator for default styles */
.insertr-style-btn.insertr-default-style::after {
content: '';
position: absolute;
top: 2px;
right: 2px;
width: 6px;
height: 6px;
background: var(--insertr-info);
border-radius: 50%;
opacity: 0.7;
}
/* Editor components */ /* Editor components */
.insertr-simple-editor, .insertr-simple-editor,
.insertr-rich-editor, .insertr-rich-editor,
@@ -479,7 +519,126 @@ body:not(.insertr-edit-mode) .insertr-editing-hover::after {
overflow-y: auto; overflow-y: auto;
} }
/* =================================================================
MULTI-PROPERTY FORM COMPONENTS
Professional form styling for direct editors (links, buttons, images)
================================================================= */
/* Direct editor container */
.insertr-direct-editor {
background: var(--insertr-bg-primary);
border: 1px solid var(--insertr-border-color);
border-radius: var(--insertr-border-radius);
padding: var(--insertr-spacing-lg);
min-width: 400px;
max-width: 600px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
font-family: var(--insertr-font-family);
color: var(--insertr-text-primary);
}
/* Form groups */
.insertr-form-group {
margin-bottom: var(--insertr-spacing-md);
}
.insertr-form-group:last-child {
margin-bottom: 0;
}
/* Form labels */
.insertr-form-label {
display: block;
margin-bottom: var(--insertr-spacing-xs);
font-weight: 500;
font-size: var(--insertr-font-size-sm);
color: var(--insertr-text-primary);
line-height: 1.4;
}
/* Form inputs and selects */
.insertr-form-input,
.insertr-form-select {
width: 100%;
padding: var(--insertr-spacing-sm) var(--insertr-spacing-md);
border: 1px solid var(--insertr-border-color);
border-radius: var(--insertr-border-radius);
font-size: var(--insertr-font-size-base);
font-family: var(--insertr-font-family);
line-height: 1.4;
color: var(--insertr-text-primary);
background: var(--insertr-bg-primary);
transition: var(--insertr-transition);
box-sizing: border-box;
}
/* Focus states */
.insertr-form-input:focus,
.insertr-form-select:focus {
outline: none;
border-color: var(--insertr-primary);
box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.25);
}
/* Hover states for selects */
.insertr-form-select:hover {
border-color: var(--insertr-text-secondary);
}
/* Disabled states */
.insertr-form-input:disabled,
.insertr-form-select:disabled {
background: var(--insertr-bg-secondary);
color: var(--insertr-text-muted);
cursor: not-allowed;
opacity: 0.6;
}
/* Error states */
.insertr-form-input.insertr-error,
.insertr-form-select.insertr-error {
border-color: var(--insertr-danger);
box-shadow: 0 0 0 2px rgba(220, 53, 69, 0.25);
}
/* Success states */
.insertr-form-input.insertr-success,
.insertr-form-select.insertr-success {
border-color: var(--insertr-success);
box-shadow: 0 0 0 2px rgba(40, 167, 69, 0.25);
}
/* Form validation messages */
.insertr-form-message {
margin-top: var(--insertr-spacing-xs);
font-size: var(--insertr-font-size-sm);
line-height: 1.3;
}
.insertr-form-message.insertr-error {
color: var(--insertr-danger);
}
.insertr-form-message.insertr-success {
color: var(--insertr-success);
}
.insertr-form-message.insertr-info {
color: var(--insertr-info);
}
/* Specific editor variants */
.insertr-link-editor {
/* Link-specific styling if needed */
}
.insertr-button-editor {
/* Button-specific styling if needed */
}
.insertr-image-editor {
/* Image-specific styling if needed */
}
/* Form actions */ /* Form actions */
.insertr-form-actions { .insertr-form-actions {

File diff suppressed because it is too large Load Diff

View File

@@ -1,9 +1,6 @@
/** /**
* StyleDetectionEngine - Analyzes elements for nested styled children with position preservation * StyleDetectionEngine - Analyzes elements for nested styled children with position preservation
* *
* Implements the "one layer deep" analysis described in CLASSES.md line 27:
* "Only direct child elements are analyzed and preserved"
*
* Purpose: Extract styled nested elements as formatting options AND preserve their positions * Purpose: Extract styled nested elements as formatting options AND preserve their positions
*/ */
@@ -21,6 +18,7 @@ export class StyleDetectionEngine {
/** /**
* Analyze element for nested styled elements AND their positions (CLASSES.md line 26-29) * Analyze element for nested styled elements AND their positions (CLASSES.md line 26-29)
* Returns both detected styles and structured content that preserves positions * Returns both detected styles and structured content that preserves positions
* Enhanced with smart default formatting options when not present
* *
* @param {HTMLElement} element - The .insertr element to analyze * @param {HTMLElement} element - The .insertr element to analyze
* @returns {Object} - {styles: Map, structure: Array} * @returns {Object} - {styles: Map, structure: Array}
@@ -32,23 +30,15 @@ export class StyleDetectionEngine {
// Parse the element's content while preserving structure // Parse the element's content while preserving structure
this.parseContentStructure(element, styleMap, contentStructure); this.parseContentStructure(element, styleMap, contentStructure);
// Add smart default formatting options if not already present
this.addSmartDefaults(styleMap, element);
return { return {
styles: styleMap, styles: styleMap,
structure: contentStructure structure: contentStructure
}; };
} }
/**
* Legacy method for backward compatibility - only returns styles
*
* @param {HTMLElement} element - The .insertr element to analyze
* @returns {Map} - Map of styleId -> styleInfo objects
*/
detectStyles(element) {
const result = this.detectStylesAndStructure(element);
return result.styles;
}
/** /**
* Parse content structure while collecting style information * Parse content structure while collecting style information
* Creates a structure array that preserves text and styled element positions * Creates a structure array that preserves text and styled element positions
@@ -71,14 +61,14 @@ export class StyleDetectionEngine {
} else if (node.nodeType === (typeof Node !== 'undefined' ? Node.ELEMENT_NODE : NODE_TYPES.ELEMENT_NODE)) { } else if (node.nodeType === (typeof Node !== 'undefined' ? Node.ELEMENT_NODE : NODE_TYPES.ELEMENT_NODE)) {
// Element node - analyze for styling and extract editable properties // Element node - analyze for styling and extract editable properties
const styleInfo = this.analyzeElement(node); const styleInfo = this.analyzeElement(node);
if (styleInfo) { if (styleInfo) {
// Styled element - add to both style map and structure // Styled element - add to both style map and structure
styleMap.set(styleInfo.id, styleInfo); styleMap.set(styleInfo.id, styleInfo);
// Extract all editable properties for this element // Extract all editable properties for this element
const editableProperties = this.extractEditableProperties(node); const editableProperties = this.extractEditableProperties(node);
structure.push({ structure.push({
type: 'styled', type: 'styled',
styleId: styleInfo.id, styleId: styleInfo.id,
@@ -88,7 +78,7 @@ export class StyleDetectionEngine {
} else { } else {
// Unstyled element - treat as text // Unstyled element - treat as text
structure.push({ structure.push({
type: 'text', type: 'text',
content: node.textContent content: node.textContent
}); });
} }
@@ -106,10 +96,10 @@ export class StyleDetectionEngine {
extractEditableProperties(element) { extractEditableProperties(element) {
const tagName = element.tagName.toLowerCase(); const tagName = element.tagName.toLowerCase();
const properties = {}; const properties = {};
// Always include text content as the primary editable property // Always include text content as the primary editable property
properties.content = element.textContent; properties.content = element.textContent;
// Add element-specific editable properties // Add element-specific editable properties
switch (tagName) { switch (tagName) {
case 'a': case 'a':
@@ -119,13 +109,13 @@ export class StyleDetectionEngine {
properties.target = element.getAttribute('target'); properties.target = element.getAttribute('target');
} }
break; break;
case 'img': case 'img':
properties.src = element.src || ''; properties.src = element.src || '';
properties.alt = element.alt || ''; properties.alt = element.alt || '';
properties.title = element.title || ''; properties.title = element.title || '';
break; break;
case 'button': case 'button':
properties.content = element.textContent; properties.content = element.textContent;
if (element.type) { if (element.type) {
@@ -135,7 +125,7 @@ export class StyleDetectionEngine {
properties.disabled = element.disabled; properties.disabled = element.disabled;
} }
break; break;
case 'input': case 'input':
properties.value = element.value || ''; properties.value = element.value || '';
properties.placeholder = element.placeholder || ''; properties.placeholder = element.placeholder || '';
@@ -143,12 +133,12 @@ export class StyleDetectionEngine {
properties.type = element.type; properties.type = element.type;
} }
break; break;
default: default:
// For other elements, content is the main editable property // For other elements, content is the main editable property
break; break;
} }
return properties; return properties;
} }
@@ -162,16 +152,16 @@ export class StyleDetectionEngine {
const tagName = element.tagName.toLowerCase(); const tagName = element.tagName.toLowerCase();
const classes = Array.from(element.classList); const classes = Array.from(element.classList);
const attributes = this.extractElementAttributes(element); const attributes = this.extractElementAttributes(element);
// Skip elements without styling (no classes or special attributes) // Skip elements without styling (no classes or special attributes)
if (classes.length === 0 && !this.hasSignificantAttributes(attributes)) { if (classes.length === 0 && !this.hasSignificantAttributes(attributes)) {
return null; return null;
} }
// Generate unique style ID and human-readable name // Generate unique style ID and human-readable name
const styleId = this.generateStyleId(tagName, classes, attributes); const styleId = this.generateStyleId(tagName, classes, attributes);
const styleName = this.generateStyleName(tagName, classes, attributes); const styleName = this.generateStyleName(tagName, classes, attributes);
return { return {
id: styleId, id: styleId,
name: styleName, name: styleName,
@@ -193,18 +183,18 @@ export class StyleDetectionEngine {
extractElementAttributes(element) { extractElementAttributes(element) {
const attributes = {}; const attributes = {};
const skipAttributes = new Set(['class', 'id']); // These are handled separately const skipAttributes = new Set(['class', 'id']); // These are handled separately
for (const attr of element.attributes) { for (const attr of element.attributes) {
if (!skipAttributes.has(attr.name)) { if (!skipAttributes.has(attr.name)) {
attributes[attr.name] = attr.value; attributes[attr.name] = attr.value;
} }
} }
// Include ID if present (it's significant for styling) // Include ID if present (it's significant for styling)
if (element.id) { if (element.id) {
attributes.id = element.id; attributes.id = element.id;
} }
return attributes; return attributes;
} }
@@ -216,8 +206,8 @@ export class StyleDetectionEngine {
*/ */
hasSignificantAttributes(attributes) { hasSignificantAttributes(attributes) {
// Consider data-*, aria-*, href, rel, target, etc. as significant // Consider data-*, aria-*, href, rel, target, etc. as significant
return Object.keys(attributes).some(key => return Object.keys(attributes).some(key =>
key.startsWith('data-') || key.startsWith('data-') ||
key.startsWith('aria-') || key.startsWith('aria-') ||
key === 'href' || key === 'href' ||
key === 'rel' || key === 'rel' ||
@@ -255,17 +245,17 @@ export class StyleDetectionEngine {
if (this.styleNameMappings.has(key)) { if (this.styleNameMappings.has(key)) {
return this.styleNameMappings.get(key); return this.styleNameMappings.get(key);
} }
// Generate name from class names // Generate name from class names
if (classes.length > 0) { if (classes.length > 0) {
return this.classesToDisplayName(classes); return this.classesToDisplayName(classes);
} }
// Generate name from tag + attributes // Generate name from tag + attributes
if (attributes.id) { if (attributes.id) {
return this.tagToDisplayName(tagName) + ' (' + attributes.id + ')'; return this.tagToDisplayName(tagName) + ' (' + attributes.id + ')';
} }
// Fallback to tag name // Fallback to tag name
return this.tagToDisplayName(tagName); return this.tagToDisplayName(tagName);
} }
@@ -276,7 +266,7 @@ export class StyleDetectionEngine {
*/ */
initializeStyleMappings() { initializeStyleMappings() {
const mappings = new Map(); const mappings = new Map();
// From demo examples in simple/index.html // From demo examples in simple/index.html
mappings.set('strong.emph', 'Emphasis'); mappings.set('strong.emph', 'Emphasis');
mappings.set('strong.brand', 'Brand'); mappings.set('strong.brand', 'Brand');
@@ -289,14 +279,14 @@ export class StyleDetectionEngine {
mappings.set('blockquote.testimonial', 'Testimonial'); mappings.set('blockquote.testimonial', 'Testimonial');
mappings.set('i.icon-home', 'Home Icon'); mappings.set('i.icon-home', 'Home Icon');
mappings.set('i.icon-info', 'Info Icon'); mappings.set('i.icon-info', 'Info Icon');
// Common patterns // Common patterns
mappings.set('strong.highlight', 'Highlight Bold'); mappings.set('strong.highlight', 'Highlight Bold');
mappings.set('span.brand', 'Brand Style'); mappings.set('span.brand', 'Brand Style');
mappings.set('a.button', 'Button Link'); mappings.set('a.button', 'Button Link');
mappings.set('span.tag', 'Tag'); mappings.set('span.tag', 'Tag');
mappings.set('span.badge', 'Badge'); mappings.set('span.badge', 'Badge');
return mappings; return mappings;
} }
@@ -324,7 +314,7 @@ export class StyleDetectionEngine {
tagToDisplayName(tagName) { tagToDisplayName(tagName) {
const tagMappings = { const tagMappings = {
'strong': 'Bold', 'strong': 'Bold',
'em': 'Italic', 'em': 'Italic',
'span': 'Style', 'span': 'Style',
'a': 'Link', 'a': 'Link',
'button': 'Button', 'button': 'Button',
@@ -332,7 +322,7 @@ export class StyleDetectionEngine {
'code': 'Code', 'code': 'Code',
'blockquote': 'Quote' 'blockquote': 'Quote'
}; };
return tagMappings[tagName] || tagName.charAt(0).toUpperCase() + tagName.slice(1); return tagMappings[tagName] || tagName.charAt(0).toUpperCase() + tagName.slice(1);
} }
@@ -346,19 +336,19 @@ export class StyleDetectionEngine {
extractTemplate(element) { extractTemplate(element) {
const tagName = element.tagName.toLowerCase(); const tagName = element.tagName.toLowerCase();
const clone = element.cloneNode(false); // Clone without children const clone = element.cloneNode(false); // Clone without children
// Create template with placeholders for different element types // Create template with placeholders for different element types
const template = { const template = {
tagName: tagName, tagName: tagName,
attributes: {}, attributes: {},
editableProperties: [] editableProperties: []
}; };
// Copy all attributes // Copy all attributes
for (const attr of element.attributes) { for (const attr of element.attributes) {
template.attributes[attr.name] = attr.value; template.attributes[attr.name] = attr.value;
} }
// Define editable properties and their placeholders // Define editable properties and their placeholders
switch (tagName) { switch (tagName) {
case 'a': case 'a':
@@ -369,7 +359,7 @@ export class StyleDetectionEngine {
} }
clone.textContent = '{{CONTENT}}'; clone.textContent = '{{CONTENT}}';
break; break;
case 'img': case 'img':
template.editableProperties = ['src', 'alt', 'title']; template.editableProperties = ['src', 'alt', 'title'];
template.attributes.src = '{{SRC}}'; template.attributes.src = '{{SRC}}';
@@ -378,12 +368,12 @@ export class StyleDetectionEngine {
template.attributes.title = '{{TITLE}}'; template.attributes.title = '{{TITLE}}';
} }
break; break;
case 'button': case 'button':
template.editableProperties = ['content']; template.editableProperties = ['content'];
clone.textContent = '{{CONTENT}}'; clone.textContent = '{{CONTENT}}';
break; break;
case 'input': case 'input':
template.editableProperties = ['value', 'placeholder']; template.editableProperties = ['value', 'placeholder'];
if (template.attributes.value !== undefined) { if (template.attributes.value !== undefined) {
@@ -393,17 +383,17 @@ export class StyleDetectionEngine {
template.attributes.placeholder = '{{PLACEHOLDER}}'; template.attributes.placeholder = '{{PLACEHOLDER}}';
} }
break; break;
default: default:
// Default: only content is editable // Default: only content is editable
template.editableProperties = ['content']; template.editableProperties = ['content'];
clone.textContent = '{{CONTENT}}'; clone.textContent = '{{CONTENT}}';
break; break;
} }
// Store both the structured template and the HTML template for backward compatibility // Store both the structured template and the HTML template for backward compatibility
template.html = clone.outerHTML; template.html = clone.outerHTML;
return template; return template;
} }
@@ -417,10 +407,10 @@ export class StyleDetectionEngine {
*/ */
createElementFromTemplate(styleInfo, properties) { createElementFromTemplate(styleInfo, properties) {
const element = document.createElement(styleInfo.tagName); const element = document.createElement(styleInfo.tagName);
// Apply classes // Apply classes
styleInfo.classes.forEach(cls => element.classList.add(cls)); styleInfo.classes.forEach(cls => element.classList.add(cls));
// Apply base attributes (non-editable ones) // Apply base attributes (non-editable ones)
Object.entries(styleInfo.attributes).forEach(([key, value]) => { Object.entries(styleInfo.attributes).forEach(([key, value]) => {
// Skip attributes that will be set from properties // Skip attributes that will be set from properties
@@ -428,7 +418,7 @@ export class StyleDetectionEngine {
element.setAttribute(key, value); element.setAttribute(key, value);
} }
}); });
// Handle properties - support both object and string (backward compatibility) // Handle properties - support both object and string (backward compatibility)
if (typeof properties === 'string') { if (typeof properties === 'string') {
// Legacy support: treat as text content // Legacy support: treat as text content
@@ -437,7 +427,7 @@ export class StyleDetectionEngine {
// New multi-property support // New multi-property support
this.applyPropertiesToElement(element, properties, styleInfo.tagName); this.applyPropertiesToElement(element, properties, styleInfo.tagName);
} }
return element; return element;
} }
@@ -455,7 +445,7 @@ export class StyleDetectionEngine {
'input': ['value', 'placeholder'], 'input': ['value', 'placeholder'],
'button': [] 'button': []
}; };
return (editableAttributes[tagName] || []).includes(attributeName); return (editableAttributes[tagName] || []).includes(attributeName);
} }
@@ -482,7 +472,7 @@ export class StyleDetectionEngine {
element.target = properties.target; element.target = properties.target;
} }
break; break;
case 'img': case 'img':
if (properties.src !== undefined) { if (properties.src !== undefined) {
element.src = properties.src; element.src = properties.src;
@@ -494,7 +484,7 @@ export class StyleDetectionEngine {
element.title = properties.title; element.title = properties.title;
} }
break; break;
case 'button': case 'button':
if (properties.content !== undefined) { if (properties.content !== undefined) {
element.textContent = properties.content; element.textContent = properties.content;
@@ -506,7 +496,7 @@ export class StyleDetectionEngine {
element.disabled = properties.disabled; element.disabled = properties.disabled;
} }
break; break;
case 'input': case 'input':
if (properties.value !== undefined) { if (properties.value !== undefined) {
element.value = properties.value; element.value = properties.value;
@@ -518,7 +508,7 @@ export class StyleDetectionEngine {
element.type = properties.type; element.type = properties.type;
} }
break; break;
default: default:
// Default: set text content // Default: set text content
if (properties.content !== undefined) { if (properties.content !== undefined) {
@@ -561,7 +551,7 @@ export class StyleDetectionEngine {
*/ */
reconstructHTML(structure, styles, updatedProperties = {}) { reconstructHTML(structure, styles, updatedProperties = {}) {
let html = ''; let html = '';
structure.forEach((piece, index) => { structure.forEach((piece, index) => {
if (piece.type === 'text') { if (piece.type === 'text') {
// Check if any styles are applied to this text piece // Check if any styles are applied to this text piece
@@ -578,7 +568,7 @@ export class StyleDetectionEngine {
} else if (piece.type === 'styled') { } else if (piece.type === 'styled') {
// Use updated properties or original properties // Use updated properties or original properties
const properties = updatedProperties[index]?.properties || piece.properties; const properties = updatedProperties[index]?.properties || piece.properties;
if (styles.has(piece.styleId)) { if (styles.has(piece.styleId)) {
const styleInfo = styles.get(piece.styleId); const styleInfo = styles.get(piece.styleId);
const styledElement = this.createElementFromTemplate(styleInfo, properties); const styledElement = this.createElementFromTemplate(styleInfo, properties);
@@ -590,10 +580,88 @@ export class StyleDetectionEngine {
} }
} }
}); });
return html; return html;
} }
/**
* Add smart default formatting options when not already present
* Provides essential formatting (bold, italic, link) if developer hasn't defined them
*
* @param {Map} styleMap - Existing detected styles
* @param {HTMLElement} element - The element being analyzed
*/
addSmartDefaults(styleMap, element) {
// Only add defaults for content elements that benefit from text formatting
const contentTags = new Set(['p', 'div', 'section', 'article', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'li', 'td', 'th', 'blockquote']);
if (!contentTags.has(element.tagName.toLowerCase())) {
return; // Skip for non-content elements
}
// Check what's already available
const hasStrong = this.hasStyleTag(styleMap, 'strong');
const hasEm = this.hasStyleTag(styleMap, 'em');
const hasB = this.hasStyleTag(styleMap, 'b');
const hasI = this.hasStyleTag(styleMap, 'i');
const hasA = this.hasStyleTag(styleMap, 'a');
// Add Bold if not present (prefer <strong> for semantics)
if (!hasStrong && !hasB) {
styleMap.set('default-strong', {
name: 'Bold',
tagName: 'strong',
classes: [],
attributes: {},
element: null, // Virtual element - no DOM reference
isDefault: true,
description: 'Make text bold for emphasis'
});
}
// Add Italic if not present (prefer <em> for semantics)
if (!hasEm && !hasI) {
styleMap.set('default-em', {
name: 'Italic',
tagName: 'em',
classes: [],
attributes: {},
element: null,
isDefault: true,
description: 'Make text italic for emphasis'
});
}
// Add Link if not present
if (!hasA) {
styleMap.set('default-a', {
name: 'Link',
tagName: 'a',
classes: [],
attributes: { href: '' },
element: null,
isDefault: true,
description: 'Create a hyperlink'
});
}
}
/**
* Check if styleMap already contains a specific tag type
*
* @param {Map} styleMap - Style map to check
* @param {string} tagName - Tag name to look for
* @returns {boolean} - True if tag is already present
*/
hasStyleTag(styleMap, tagName) {
for (const [, styleInfo] of styleMap) {
if (styleInfo.tagName === tagName) {
return true;
}
}
return false;
}
/** /**
* Extract plain text from structure while preserving order * Extract plain text from structure while preserving order
* *
@@ -624,12 +692,12 @@ export class StyleDetectionEngine {
// Simple approach: if text length matches, assume same structure // Simple approach: if text length matches, assume same structure
// More sophisticated approaches could use diff algorithms // More sophisticated approaches could use diff algorithms
const originalText = this.extractTextFromStructure(originalStructure); const originalText = this.extractTextFromStructure(originalStructure);
if (newText === originalText) { if (newText === originalText) {
// No changes - return original structure // No changes - return original structure
return originalStructure; return originalStructure;
} }
// For now, create simple text structure // For now, create simple text structure
// TODO: Implement smarter text-to-structure mapping // TODO: Implement smarter text-to-structure mapping
return [{ return [{
@@ -640,4 +708,4 @@ export class StyleDetectionEngine {
} }
// Export singleton instance // Export singleton instance
export const styleDetectionEngine = new StyleDetectionEngine(); export const styleDetectionEngine = new StyleDetectionEngine();