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:
@@ -442,6 +442,46 @@ body:not(.insertr-edit-mode) .insertr-editing-hover::after {
|
||||
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 */
|
||||
.insertr-simple-editor,
|
||||
.insertr-rich-editor,
|
||||
@@ -479,7 +519,126 @@ body:not(.insertr-edit-mode) .insertr-editing-hover::after {
|
||||
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 */
|
||||
.insertr-form-actions {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,9 +1,6 @@
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
||||
@@ -21,6 +18,7 @@ export class StyleDetectionEngine {
|
||||
/**
|
||||
* Analyze element for nested styled elements AND their positions (CLASSES.md line 26-29)
|
||||
* 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
|
||||
* @returns {Object} - {styles: Map, structure: Array}
|
||||
@@ -32,23 +30,15 @@ export class StyleDetectionEngine {
|
||||
// Parse the element's content while preserving structure
|
||||
this.parseContentStructure(element, styleMap, contentStructure);
|
||||
|
||||
// Add smart default formatting options if not already present
|
||||
this.addSmartDefaults(styleMap, element);
|
||||
|
||||
return {
|
||||
styles: styleMap,
|
||||
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
|
||||
* 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)) {
|
||||
// Element node - analyze for styling and extract editable properties
|
||||
const styleInfo = this.analyzeElement(node);
|
||||
|
||||
|
||||
if (styleInfo) {
|
||||
// Styled element - add to both style map and structure
|
||||
styleMap.set(styleInfo.id, styleInfo);
|
||||
|
||||
|
||||
// Extract all editable properties for this element
|
||||
const editableProperties = this.extractEditableProperties(node);
|
||||
|
||||
|
||||
structure.push({
|
||||
type: 'styled',
|
||||
styleId: styleInfo.id,
|
||||
@@ -88,7 +78,7 @@ export class StyleDetectionEngine {
|
||||
} else {
|
||||
// Unstyled element - treat as text
|
||||
structure.push({
|
||||
type: 'text',
|
||||
type: 'text',
|
||||
content: node.textContent
|
||||
});
|
||||
}
|
||||
@@ -106,10 +96,10 @@ export class StyleDetectionEngine {
|
||||
extractEditableProperties(element) {
|
||||
const tagName = element.tagName.toLowerCase();
|
||||
const properties = {};
|
||||
|
||||
|
||||
// Always include text content as the primary editable property
|
||||
properties.content = element.textContent;
|
||||
|
||||
|
||||
// Add element-specific editable properties
|
||||
switch (tagName) {
|
||||
case 'a':
|
||||
@@ -119,13 +109,13 @@ export class StyleDetectionEngine {
|
||||
properties.target = element.getAttribute('target');
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
case 'img':
|
||||
properties.src = element.src || '';
|
||||
properties.alt = element.alt || '';
|
||||
properties.title = element.title || '';
|
||||
break;
|
||||
|
||||
|
||||
case 'button':
|
||||
properties.content = element.textContent;
|
||||
if (element.type) {
|
||||
@@ -135,7 +125,7 @@ export class StyleDetectionEngine {
|
||||
properties.disabled = element.disabled;
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
case 'input':
|
||||
properties.value = element.value || '';
|
||||
properties.placeholder = element.placeholder || '';
|
||||
@@ -143,12 +133,12 @@ export class StyleDetectionEngine {
|
||||
properties.type = element.type;
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
default:
|
||||
// For other elements, content is the main editable property
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
return properties;
|
||||
}
|
||||
|
||||
@@ -162,16 +152,16 @@ export class StyleDetectionEngine {
|
||||
const tagName = element.tagName.toLowerCase();
|
||||
const classes = Array.from(element.classList);
|
||||
const attributes = this.extractElementAttributes(element);
|
||||
|
||||
|
||||
// Skip elements without styling (no classes or special attributes)
|
||||
if (classes.length === 0 && !this.hasSignificantAttributes(attributes)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
// Generate unique style ID and human-readable name
|
||||
const styleId = this.generateStyleId(tagName, classes, attributes);
|
||||
const styleName = this.generateStyleName(tagName, classes, attributes);
|
||||
|
||||
|
||||
return {
|
||||
id: styleId,
|
||||
name: styleName,
|
||||
@@ -193,18 +183,18 @@ export class StyleDetectionEngine {
|
||||
extractElementAttributes(element) {
|
||||
const attributes = {};
|
||||
const skipAttributes = new Set(['class', 'id']); // These are handled separately
|
||||
|
||||
|
||||
for (const attr of element.attributes) {
|
||||
if (!skipAttributes.has(attr.name)) {
|
||||
attributes[attr.name] = attr.value;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Include ID if present (it's significant for styling)
|
||||
if (element.id) {
|
||||
attributes.id = element.id;
|
||||
}
|
||||
|
||||
|
||||
return attributes;
|
||||
}
|
||||
|
||||
@@ -216,8 +206,8 @@ export class StyleDetectionEngine {
|
||||
*/
|
||||
hasSignificantAttributes(attributes) {
|
||||
// Consider data-*, aria-*, href, rel, target, etc. as significant
|
||||
return Object.keys(attributes).some(key =>
|
||||
key.startsWith('data-') ||
|
||||
return Object.keys(attributes).some(key =>
|
||||
key.startsWith('data-') ||
|
||||
key.startsWith('aria-') ||
|
||||
key === 'href' ||
|
||||
key === 'rel' ||
|
||||
@@ -255,17 +245,17 @@ export class StyleDetectionEngine {
|
||||
if (this.styleNameMappings.has(key)) {
|
||||
return this.styleNameMappings.get(key);
|
||||
}
|
||||
|
||||
|
||||
// Generate name from class names
|
||||
if (classes.length > 0) {
|
||||
return this.classesToDisplayName(classes);
|
||||
}
|
||||
|
||||
|
||||
// Generate name from tag + attributes
|
||||
if (attributes.id) {
|
||||
return this.tagToDisplayName(tagName) + ' (' + attributes.id + ')';
|
||||
}
|
||||
|
||||
|
||||
// Fallback to tag name
|
||||
return this.tagToDisplayName(tagName);
|
||||
}
|
||||
@@ -276,7 +266,7 @@ export class StyleDetectionEngine {
|
||||
*/
|
||||
initializeStyleMappings() {
|
||||
const mappings = new Map();
|
||||
|
||||
|
||||
// From demo examples in simple/index.html
|
||||
mappings.set('strong.emph', 'Emphasis');
|
||||
mappings.set('strong.brand', 'Brand');
|
||||
@@ -289,14 +279,14 @@ export class StyleDetectionEngine {
|
||||
mappings.set('blockquote.testimonial', 'Testimonial');
|
||||
mappings.set('i.icon-home', 'Home Icon');
|
||||
mappings.set('i.icon-info', 'Info Icon');
|
||||
|
||||
|
||||
// Common patterns
|
||||
mappings.set('strong.highlight', 'Highlight Bold');
|
||||
mappings.set('span.brand', 'Brand Style');
|
||||
mappings.set('a.button', 'Button Link');
|
||||
mappings.set('span.tag', 'Tag');
|
||||
mappings.set('span.badge', 'Badge');
|
||||
|
||||
|
||||
return mappings;
|
||||
}
|
||||
|
||||
@@ -324,7 +314,7 @@ export class StyleDetectionEngine {
|
||||
tagToDisplayName(tagName) {
|
||||
const tagMappings = {
|
||||
'strong': 'Bold',
|
||||
'em': 'Italic',
|
||||
'em': 'Italic',
|
||||
'span': 'Style',
|
||||
'a': 'Link',
|
||||
'button': 'Button',
|
||||
@@ -332,7 +322,7 @@ export class StyleDetectionEngine {
|
||||
'code': 'Code',
|
||||
'blockquote': 'Quote'
|
||||
};
|
||||
|
||||
|
||||
return tagMappings[tagName] || tagName.charAt(0).toUpperCase() + tagName.slice(1);
|
||||
}
|
||||
|
||||
@@ -346,19 +336,19 @@ export class StyleDetectionEngine {
|
||||
extractTemplate(element) {
|
||||
const tagName = element.tagName.toLowerCase();
|
||||
const clone = element.cloneNode(false); // Clone without children
|
||||
|
||||
|
||||
// Create template with placeholders for different element types
|
||||
const template = {
|
||||
tagName: tagName,
|
||||
attributes: {},
|
||||
editableProperties: []
|
||||
};
|
||||
|
||||
|
||||
// Copy all attributes
|
||||
for (const attr of element.attributes) {
|
||||
template.attributes[attr.name] = attr.value;
|
||||
}
|
||||
|
||||
|
||||
// Define editable properties and their placeholders
|
||||
switch (tagName) {
|
||||
case 'a':
|
||||
@@ -369,7 +359,7 @@ export class StyleDetectionEngine {
|
||||
}
|
||||
clone.textContent = '{{CONTENT}}';
|
||||
break;
|
||||
|
||||
|
||||
case 'img':
|
||||
template.editableProperties = ['src', 'alt', 'title'];
|
||||
template.attributes.src = '{{SRC}}';
|
||||
@@ -378,12 +368,12 @@ export class StyleDetectionEngine {
|
||||
template.attributes.title = '{{TITLE}}';
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
case 'button':
|
||||
template.editableProperties = ['content'];
|
||||
clone.textContent = '{{CONTENT}}';
|
||||
break;
|
||||
|
||||
|
||||
case 'input':
|
||||
template.editableProperties = ['value', 'placeholder'];
|
||||
if (template.attributes.value !== undefined) {
|
||||
@@ -393,17 +383,17 @@ export class StyleDetectionEngine {
|
||||
template.attributes.placeholder = '{{PLACEHOLDER}}';
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
default:
|
||||
// Default: only content is editable
|
||||
template.editableProperties = ['content'];
|
||||
clone.textContent = '{{CONTENT}}';
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
// Store both the structured template and the HTML template for backward compatibility
|
||||
template.html = clone.outerHTML;
|
||||
|
||||
|
||||
return template;
|
||||
}
|
||||
|
||||
@@ -417,10 +407,10 @@ export class StyleDetectionEngine {
|
||||
*/
|
||||
createElementFromTemplate(styleInfo, properties) {
|
||||
const element = document.createElement(styleInfo.tagName);
|
||||
|
||||
|
||||
// Apply classes
|
||||
styleInfo.classes.forEach(cls => element.classList.add(cls));
|
||||
|
||||
|
||||
// Apply base attributes (non-editable ones)
|
||||
Object.entries(styleInfo.attributes).forEach(([key, value]) => {
|
||||
// Skip attributes that will be set from properties
|
||||
@@ -428,7 +418,7 @@ export class StyleDetectionEngine {
|
||||
element.setAttribute(key, value);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// Handle properties - support both object and string (backward compatibility)
|
||||
if (typeof properties === 'string') {
|
||||
// Legacy support: treat as text content
|
||||
@@ -437,7 +427,7 @@ export class StyleDetectionEngine {
|
||||
// New multi-property support
|
||||
this.applyPropertiesToElement(element, properties, styleInfo.tagName);
|
||||
}
|
||||
|
||||
|
||||
return element;
|
||||
}
|
||||
|
||||
@@ -455,7 +445,7 @@ export class StyleDetectionEngine {
|
||||
'input': ['value', 'placeholder'],
|
||||
'button': []
|
||||
};
|
||||
|
||||
|
||||
return (editableAttributes[tagName] || []).includes(attributeName);
|
||||
}
|
||||
|
||||
@@ -482,7 +472,7 @@ export class StyleDetectionEngine {
|
||||
element.target = properties.target;
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
case 'img':
|
||||
if (properties.src !== undefined) {
|
||||
element.src = properties.src;
|
||||
@@ -494,7 +484,7 @@ export class StyleDetectionEngine {
|
||||
element.title = properties.title;
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
case 'button':
|
||||
if (properties.content !== undefined) {
|
||||
element.textContent = properties.content;
|
||||
@@ -506,7 +496,7 @@ export class StyleDetectionEngine {
|
||||
element.disabled = properties.disabled;
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
case 'input':
|
||||
if (properties.value !== undefined) {
|
||||
element.value = properties.value;
|
||||
@@ -518,7 +508,7 @@ export class StyleDetectionEngine {
|
||||
element.type = properties.type;
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
default:
|
||||
// Default: set text content
|
||||
if (properties.content !== undefined) {
|
||||
@@ -561,7 +551,7 @@ export class StyleDetectionEngine {
|
||||
*/
|
||||
reconstructHTML(structure, styles, updatedProperties = {}) {
|
||||
let html = '';
|
||||
|
||||
|
||||
structure.forEach((piece, index) => {
|
||||
if (piece.type === 'text') {
|
||||
// Check if any styles are applied to this text piece
|
||||
@@ -578,7 +568,7 @@ export class StyleDetectionEngine {
|
||||
} else if (piece.type === 'styled') {
|
||||
// Use updated properties or original properties
|
||||
const properties = updatedProperties[index]?.properties || piece.properties;
|
||||
|
||||
|
||||
if (styles.has(piece.styleId)) {
|
||||
const styleInfo = styles.get(piece.styleId);
|
||||
const styledElement = this.createElementFromTemplate(styleInfo, properties);
|
||||
@@ -590,10 +580,88 @@ export class StyleDetectionEngine {
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
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
|
||||
*
|
||||
@@ -624,12 +692,12 @@ export class StyleDetectionEngine {
|
||||
// Simple approach: if text length matches, assume same structure
|
||||
// More sophisticated approaches could use diff algorithms
|
||||
const originalText = this.extractTextFromStructure(originalStructure);
|
||||
|
||||
|
||||
if (newText === originalText) {
|
||||
// No changes - return original structure
|
||||
return originalStructure;
|
||||
}
|
||||
|
||||
|
||||
// For now, create simple text structure
|
||||
// TODO: Implement smarter text-to-structure mapping
|
||||
return [{
|
||||
@@ -640,4 +708,4 @@ export class StyleDetectionEngine {
|
||||
}
|
||||
|
||||
// Export singleton instance
|
||||
export const styleDetectionEngine = new StyleDetectionEngine();
|
||||
export const styleDetectionEngine = new StyleDetectionEngine();
|
||||
|
||||
Reference in New Issue
Block a user