refactor: Replace multi-property forms with popup-based link configuration
- Remove clunky multi-property editor with separate text + URL inputs
- Implement single rich text editor (contentEditable) for all content
- Add popup-based link configuration: select text → click 🔗 Link → configure
- Filter out link styles from formatting toolbar (links use popup, not buttons)
- Consolidate CSS: remove separate style-aware-editor.css, integrate into insertr.css
- Clean up 200+ lines of unused multi-property form code and styles
- Fix duplicate link style detection (no more 'Fancy Link' + 'Link' buttons)
Result: Much cleaner UX similar to modern editors where formatting uses
toolbar buttons and complex elements (links) use dedicated popups.
This commit is contained in:
@@ -321,88 +321,105 @@ body:not(.insertr-edit-mode) .insertr-editing-hover::after {
|
||||
}
|
||||
|
||||
/* =================================================================
|
||||
EDIT FORM STYLES
|
||||
STYLE-AWARE EDITOR STYLES
|
||||
Modern interface for content editing with style preservation
|
||||
================================================================= */
|
||||
|
||||
.insertr-edit-form {
|
||||
padding: var(--insertr-spacing-lg);
|
||||
margin: 0;
|
||||
font-family: var(--insertr-font-family);
|
||||
font-size: var(--insertr-font-size-base);
|
||||
line-height: var(--insertr-line-height);
|
||||
color: var(--insertr-text-primary);
|
||||
/* Main editor container */
|
||||
.insertr-style-aware-editor {
|
||||
background: var(--insertr-bg-primary);
|
||||
}
|
||||
|
||||
.insertr-form-header {
|
||||
margin: 0 0 var(--insertr-spacing-lg) 0;
|
||||
padding: 0 0 var(--insertr-spacing-md) 0;
|
||||
border-bottom: 1px solid var(--insertr-border-color);
|
||||
}
|
||||
|
||||
.insertr-form-title {
|
||||
margin: 0 0 var(--insertr-spacing-sm) 0;
|
||||
padding: 0;
|
||||
font-size: var(--insertr-font-size-lg);
|
||||
font-weight: 600;
|
||||
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);
|
||||
}
|
||||
|
||||
.insertr-form-help {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
/* Style toolbar */
|
||||
.insertr-style-toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--insertr-spacing-xs);
|
||||
padding: var(--insertr-spacing-sm);
|
||||
background: var(--insertr-bg-secondary);
|
||||
border: 1px solid var(--insertr-border-color);
|
||||
border-radius: var(--insertr-border-radius);
|
||||
margin-bottom: var(--insertr-spacing-md);
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.insertr-toolbar-title {
|
||||
font-size: var(--insertr-font-size-sm);
|
||||
font-weight: 500;
|
||||
color: var(--insertr-text-muted);
|
||||
margin-right: var(--insertr-spacing-xs);
|
||||
}
|
||||
|
||||
.insertr-form-body {
|
||||
margin: 0 0 var(--insertr-spacing-lg) 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.insertr-form-group {
|
||||
margin: 0 0 var(--insertr-spacing-md) 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.insertr-form-label {
|
||||
display: block;
|
||||
margin: 0 0 var(--insertr-spacing-xs) 0;
|
||||
padding: 0;
|
||||
.insertr-style-btn {
|
||||
background: var(--insertr-bg-primary);
|
||||
border: 1px solid var(--insertr-border-color);
|
||||
border-radius: var(--insertr-border-radius);
|
||||
padding: var(--insertr-spacing-xs) var(--insertr-spacing-sm);
|
||||
font-size: var(--insertr-font-size-sm);
|
||||
font-weight: 500;
|
||||
color: var(--insertr-text-primary);
|
||||
cursor: pointer;
|
||||
transition: var(--insertr-transition);
|
||||
}
|
||||
|
||||
.insertr-form-input,
|
||||
.insertr-form-textarea,
|
||||
.insertr-form-select {
|
||||
.insertr-style-btn:hover {
|
||||
background: var(--insertr-bg-secondary);
|
||||
border-color: var(--insertr-text-muted);
|
||||
}
|
||||
|
||||
.insertr-style-btn:active {
|
||||
background: var(--insertr-border-color);
|
||||
transform: translateY(1px);
|
||||
}
|
||||
|
||||
/* Editor components */
|
||||
.insertr-simple-editor,
|
||||
.insertr-rich-editor,
|
||||
.insertr-fallback-textarea {
|
||||
width: 100%;
|
||||
padding: var(--insertr-input-padding);
|
||||
margin: 0;
|
||||
border: var(--insertr-input-border);
|
||||
border-radius: var(--insertr-border-radius);
|
||||
background: var(--insertr-input-bg);
|
||||
font-family: var(--insertr-font-family);
|
||||
padding: var(--insertr-input-padding);
|
||||
font-size: var(--insertr-font-size-base);
|
||||
line-height: var(--insertr-line-height);
|
||||
font-family: var(--insertr-font-family);
|
||||
color: var(--insertr-text-primary);
|
||||
background: var(--insertr-input-bg);
|
||||
margin-bottom: var(--insertr-spacing-md);
|
||||
transition: var(--insertr-transition);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.insertr-form-input:focus,
|
||||
.insertr-form-textarea:focus,
|
||||
.insertr-form-select:focus {
|
||||
.insertr-simple-editor:focus,
|
||||
.insertr-rich-editor:focus,
|
||||
.insertr-fallback-textarea:focus {
|
||||
outline: none;
|
||||
border: var(--insertr-input-border-focus);
|
||||
box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.25);
|
||||
}
|
||||
|
||||
.insertr-form-textarea {
|
||||
min-height: 120px;
|
||||
.insertr-simple-editor,
|
||||
.insertr-fallback-textarea {
|
||||
resize: vertical;
|
||||
min-height: 120px;
|
||||
}
|
||||
|
||||
.insertr-rich-editor {
|
||||
min-height: 100px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* Form actions */
|
||||
.insertr-form-actions {
|
||||
display: flex;
|
||||
gap: var(--insertr-spacing-sm);
|
||||
@@ -466,6 +483,19 @@ body:not(.insertr-edit-mode) .insertr-editing-hover::after {
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
/* Fallback editor */
|
||||
.insertr-fallback-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: 500px;
|
||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||||
font-family: var(--insertr-font-family);
|
||||
color: var(--insertr-text-primary);
|
||||
}
|
||||
|
||||
/* =================================================================
|
||||
VERSION HISTORY MODAL
|
||||
================================================================= */
|
||||
@@ -694,10 +724,33 @@ body:not(.insertr-edit-mode) .insertr-editing-hover::after {
|
||||
top: -25px;
|
||||
}
|
||||
|
||||
/* StyleAware Editor responsive adjustments */
|
||||
.insertr-style-aware-editor,
|
||||
.insertr-fallback-editor {
|
||||
min-width: 300px;
|
||||
max-width: calc(100vw - 2rem);
|
||||
padding: var(--insertr-spacing-md);
|
||||
}
|
||||
|
||||
.insertr-style-toolbar {
|
||||
padding: var(--insertr-spacing-xs);
|
||||
gap: var(--insertr-spacing-xs);
|
||||
}
|
||||
|
||||
.insertr-style-btn {
|
||||
padding: var(--insertr-spacing-xs);
|
||||
font-size: var(--insertr-font-size-sm);
|
||||
}
|
||||
|
||||
.insertr-form-actions {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.insertr-btn-save,
|
||||
.insertr-btn-cancel {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.insertr-btn-history {
|
||||
margin-right: 0;
|
||||
order: -1;
|
||||
|
||||
@@ -321,88 +321,105 @@ body:not(.insertr-edit-mode) .insertr-editing-hover::after {
|
||||
}
|
||||
|
||||
/* =================================================================
|
||||
EDIT FORM STYLES
|
||||
STYLE-AWARE EDITOR STYLES
|
||||
Modern interface for content editing with style preservation
|
||||
================================================================= */
|
||||
|
||||
.insertr-edit-form {
|
||||
padding: var(--insertr-spacing-lg);
|
||||
margin: 0;
|
||||
font-family: var(--insertr-font-family);
|
||||
font-size: var(--insertr-font-size-base);
|
||||
line-height: var(--insertr-line-height);
|
||||
color: var(--insertr-text-primary);
|
||||
/* Main editor container */
|
||||
.insertr-style-aware-editor {
|
||||
background: var(--insertr-bg-primary);
|
||||
}
|
||||
|
||||
.insertr-form-header {
|
||||
margin: 0 0 var(--insertr-spacing-lg) 0;
|
||||
padding: 0 0 var(--insertr-spacing-md) 0;
|
||||
border-bottom: 1px solid var(--insertr-border-color);
|
||||
}
|
||||
|
||||
.insertr-form-title {
|
||||
margin: 0 0 var(--insertr-spacing-sm) 0;
|
||||
padding: 0;
|
||||
font-size: var(--insertr-font-size-lg);
|
||||
font-weight: 600;
|
||||
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);
|
||||
}
|
||||
|
||||
.insertr-form-help {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
/* Style toolbar */
|
||||
.insertr-style-toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--insertr-spacing-xs);
|
||||
padding: var(--insertr-spacing-sm);
|
||||
background: var(--insertr-bg-secondary);
|
||||
border: 1px solid var(--insertr-border-color);
|
||||
border-radius: var(--insertr-border-radius);
|
||||
margin-bottom: var(--insertr-spacing-md);
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.insertr-toolbar-title {
|
||||
font-size: var(--insertr-font-size-sm);
|
||||
font-weight: 500;
|
||||
color: var(--insertr-text-muted);
|
||||
margin-right: var(--insertr-spacing-xs);
|
||||
}
|
||||
|
||||
.insertr-form-body {
|
||||
margin: 0 0 var(--insertr-spacing-lg) 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.insertr-form-group {
|
||||
margin: 0 0 var(--insertr-spacing-md) 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.insertr-form-label {
|
||||
display: block;
|
||||
margin: 0 0 var(--insertr-spacing-xs) 0;
|
||||
padding: 0;
|
||||
.insertr-style-btn {
|
||||
background: var(--insertr-bg-primary);
|
||||
border: 1px solid var(--insertr-border-color);
|
||||
border-radius: var(--insertr-border-radius);
|
||||
padding: var(--insertr-spacing-xs) var(--insertr-spacing-sm);
|
||||
font-size: var(--insertr-font-size-sm);
|
||||
font-weight: 500;
|
||||
color: var(--insertr-text-primary);
|
||||
cursor: pointer;
|
||||
transition: var(--insertr-transition);
|
||||
}
|
||||
|
||||
.insertr-form-input,
|
||||
.insertr-form-textarea,
|
||||
.insertr-form-select {
|
||||
.insertr-style-btn:hover {
|
||||
background: var(--insertr-bg-secondary);
|
||||
border-color: var(--insertr-text-muted);
|
||||
}
|
||||
|
||||
.insertr-style-btn:active {
|
||||
background: var(--insertr-border-color);
|
||||
transform: translateY(1px);
|
||||
}
|
||||
|
||||
/* Editor components */
|
||||
.insertr-simple-editor,
|
||||
.insertr-rich-editor,
|
||||
.insertr-fallback-textarea {
|
||||
width: 100%;
|
||||
padding: var(--insertr-input-padding);
|
||||
margin: 0;
|
||||
border: var(--insertr-input-border);
|
||||
border-radius: var(--insertr-border-radius);
|
||||
background: var(--insertr-input-bg);
|
||||
font-family: var(--insertr-font-family);
|
||||
padding: var(--insertr-input-padding);
|
||||
font-size: var(--insertr-font-size-base);
|
||||
line-height: var(--insertr-line-height);
|
||||
font-family: var(--insertr-font-family);
|
||||
color: var(--insertr-text-primary);
|
||||
background: var(--insertr-input-bg);
|
||||
margin-bottom: var(--insertr-spacing-md);
|
||||
transition: var(--insertr-transition);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.insertr-form-input:focus,
|
||||
.insertr-form-textarea:focus,
|
||||
.insertr-form-select:focus {
|
||||
.insertr-simple-editor:focus,
|
||||
.insertr-rich-editor:focus,
|
||||
.insertr-fallback-textarea:focus {
|
||||
outline: none;
|
||||
border: var(--insertr-input-border-focus);
|
||||
box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.25);
|
||||
}
|
||||
|
||||
.insertr-form-textarea {
|
||||
min-height: 120px;
|
||||
.insertr-simple-editor,
|
||||
.insertr-fallback-textarea {
|
||||
resize: vertical;
|
||||
min-height: 120px;
|
||||
}
|
||||
|
||||
.insertr-rich-editor {
|
||||
min-height: 100px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* Form actions */
|
||||
.insertr-form-actions {
|
||||
display: flex;
|
||||
gap: var(--insertr-spacing-sm);
|
||||
@@ -466,6 +483,19 @@ body:not(.insertr-edit-mode) .insertr-editing-hover::after {
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
/* Fallback editor */
|
||||
.insertr-fallback-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: 500px;
|
||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||||
font-family: var(--insertr-font-family);
|
||||
color: var(--insertr-text-primary);
|
||||
}
|
||||
|
||||
/* =================================================================
|
||||
VERSION HISTORY MODAL
|
||||
================================================================= */
|
||||
@@ -694,268 +724,22 @@ body:not(.insertr-edit-mode) .insertr-editing-hover::after {
|
||||
top: -25px;
|
||||
}
|
||||
|
||||
.insertr-form-actions {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.insertr-btn-history {
|
||||
margin-right: 0;
|
||||
order: -1;
|
||||
}
|
||||
}/**
|
||||
* Styles for StyleAwareEditor
|
||||
* Clean, modern interface that integrates with existing Insertr styling
|
||||
*/
|
||||
|
||||
/* Main editor container */
|
||||
.insertr-style-aware-editor {
|
||||
background: white;
|
||||
border: 1px solid #d1d5db;
|
||||
border-radius: 8px;
|
||||
padding: 1rem;
|
||||
min-width: 400px;
|
||||
max-width: 600px;
|
||||
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1);
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
}
|
||||
|
||||
/* Style toolbar */
|
||||
.insertr-style-toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
padding: 0.75rem;
|
||||
background: #f9fafb;
|
||||
border: 1px solid #e5e7eb;
|
||||
border-radius: 6px;
|
||||
margin-bottom: 1rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.insertr-toolbar-title {
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
color: #6b7280;
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
.insertr-style-btn {
|
||||
background: white;
|
||||
border: 1px solid #d1d5db;
|
||||
border-radius: 4px;
|
||||
padding: 0.375rem 0.75rem;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
color: #374151;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.insertr-style-btn:hover {
|
||||
background: #f3f4f6;
|
||||
border-color: #9ca3af;
|
||||
}
|
||||
|
||||
.insertr-style-btn:active {
|
||||
background: #e5e7eb;
|
||||
transform: translateY(1px);
|
||||
}
|
||||
|
||||
/* Simple text editor */
|
||||
.insertr-simple-editor {
|
||||
width: 100%;
|
||||
border: 1px solid #d1d5db;
|
||||
border-radius: 6px;
|
||||
padding: 0.75rem;
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.5;
|
||||
resize: vertical;
|
||||
font-family: inherit;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.insertr-simple-editor:focus {
|
||||
outline: none;
|
||||
border-color: #3b82f6;
|
||||
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
|
||||
}
|
||||
|
||||
/* Rich text editor */
|
||||
.insertr-rich-editor {
|
||||
width: 100%;
|
||||
min-height: 100px;
|
||||
border: 1px solid #d1d5db;
|
||||
border-radius: 6px;
|
||||
padding: 0.75rem;
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.5;
|
||||
margin-bottom: 1rem;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.insertr-rich-editor:focus {
|
||||
outline: none;
|
||||
border-color: #3b82f6;
|
||||
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
|
||||
}
|
||||
|
||||
/* Multi-property editor */
|
||||
.insertr-multi-property-editor {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.insertr-styled-element-form {
|
||||
background: #f9fafb;
|
||||
border: 1px solid #e5e7eb;
|
||||
border-radius: 6px;
|
||||
padding: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.insertr-styled-element-form:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.insertr-form-header {
|
||||
margin: 0 0 0.75rem 0;
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
color: #374151;
|
||||
}
|
||||
|
||||
/* Property inputs */
|
||||
.insertr-property-input,
|
||||
.insertr-text-input {
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.insertr-property-input:last-child,
|
||||
.insertr-text-input:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.insertr-property-label,
|
||||
.insertr-input-label {
|
||||
display: block;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
color: #374151;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.insertr-property-field,
|
||||
.insertr-text-field {
|
||||
width: 100%;
|
||||
border: 1px solid #d1d5db;
|
||||
border-radius: 4px;
|
||||
padding: 0.5rem;
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.4;
|
||||
font-family: inherit;
|
||||
transition: border-color 0.2s ease;
|
||||
}
|
||||
|
||||
.insertr-property-field:focus,
|
||||
.insertr-text-field:focus {
|
||||
outline: none;
|
||||
border-color: #3b82f6;
|
||||
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
|
||||
}
|
||||
|
||||
/* URL inputs */
|
||||
.insertr-property-field[type="url"] {
|
||||
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
||||
font-size: 0.8125rem;
|
||||
}
|
||||
|
||||
/* Form actions */
|
||||
.insertr-form-actions {
|
||||
display: flex;
|
||||
gap: 0.75rem;
|
||||
justify-content: flex-end;
|
||||
padding-top: 1rem;
|
||||
border-top: 1px solid #e5e7eb;
|
||||
}
|
||||
|
||||
.insertr-btn-save,
|
||||
.insertr-btn-cancel {
|
||||
padding: 0.5rem 1rem;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
border: 1px solid;
|
||||
}
|
||||
|
||||
.insertr-btn-save {
|
||||
background: #3b82f6;
|
||||
border-color: #3b82f6;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.insertr-btn-save:hover {
|
||||
background: #2563eb;
|
||||
border-color: #2563eb;
|
||||
}
|
||||
|
||||
.insertr-btn-cancel {
|
||||
background: white;
|
||||
border-color: #d1d5db;
|
||||
color: #374151;
|
||||
}
|
||||
|
||||
.insertr-btn-cancel:hover {
|
||||
background: #f9fafb;
|
||||
border-color: #9ca3af;
|
||||
}
|
||||
|
||||
/* Fallback editor */
|
||||
.insertr-fallback-editor {
|
||||
background: white;
|
||||
border: 1px solid #d1d5db;
|
||||
border-radius: 8px;
|
||||
padding: 1rem;
|
||||
min-width: 400px;
|
||||
max-width: 500px;
|
||||
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.insertr-fallback-textarea {
|
||||
width: 100%;
|
||||
border: 1px solid #d1d5db;
|
||||
border-radius: 6px;
|
||||
padding: 0.75rem;
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.5;
|
||||
resize: vertical;
|
||||
font-family: inherit;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.insertr-fallback-textarea:focus {
|
||||
outline: none;
|
||||
border-color: #3b82f6;
|
||||
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
|
||||
}
|
||||
|
||||
/* Responsive adjustments */
|
||||
@media (max-width: 640px) {
|
||||
/* StyleAware Editor responsive adjustments */
|
||||
.insertr-style-aware-editor,
|
||||
.insertr-fallback-editor {
|
||||
min-width: 300px;
|
||||
max-width: calc(100vw - 2rem);
|
||||
margin: 1rem;
|
||||
padding: var(--insertr-spacing-md);
|
||||
}
|
||||
|
||||
.insertr-style-toolbar {
|
||||
padding: 0.5rem;
|
||||
gap: 0.375rem;
|
||||
padding: var(--insertr-spacing-xs);
|
||||
gap: var(--insertr-spacing-xs);
|
||||
}
|
||||
|
||||
.insertr-style-btn {
|
||||
padding: 0.25rem 0.5rem;
|
||||
font-size: 0.8125rem;
|
||||
padding: var(--insertr-spacing-xs);
|
||||
font-size: var(--insertr-font-size-sm);
|
||||
}
|
||||
|
||||
.insertr-form-actions {
|
||||
@@ -966,46 +750,9 @@ body:not(.insertr-edit-mode) .insertr-editing-hover::after {
|
||||
.insertr-btn-cancel {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
/* Dark mode support (if needed) */
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.insertr-style-aware-editor,
|
||||
.insertr-fallback-editor {
|
||||
background: #1f2937;
|
||||
border-color: #374151;
|
||||
color: #f9fafb;
|
||||
}
|
||||
|
||||
.insertr-style-toolbar,
|
||||
.insertr-styled-element-form {
|
||||
background: #111827;
|
||||
border-color: #374151;
|
||||
}
|
||||
|
||||
.insertr-style-btn {
|
||||
background: #374151;
|
||||
border-color: #4b5563;
|
||||
color: #f9fafb;
|
||||
}
|
||||
|
||||
.insertr-style-btn:hover {
|
||||
background: #4b5563;
|
||||
border-color: #6b7280;
|
||||
}
|
||||
|
||||
.insertr-property-field,
|
||||
.insertr-text-field,
|
||||
.insertr-simple-editor,
|
||||
.insertr-fallback-textarea {
|
||||
background: #374151;
|
||||
border-color: #4b5563;
|
||||
color: #f9fafb;
|
||||
}
|
||||
|
||||
.insertr-rich-editor {
|
||||
background: #374151;
|
||||
border-color: #4b5563;
|
||||
color: #f9fafb;
|
||||
.insertr-btn-history {
|
||||
margin-right: 0;
|
||||
order: -1;
|
||||
}
|
||||
}
|
||||
@@ -1,307 +0,0 @@
|
||||
/**
|
||||
* Styles for StyleAwareEditor
|
||||
* Clean, modern interface that integrates with existing Insertr styling
|
||||
*/
|
||||
|
||||
/* Main editor container */
|
||||
.insertr-style-aware-editor {
|
||||
background: white;
|
||||
border: 1px solid #d1d5db;
|
||||
border-radius: 8px;
|
||||
padding: 1rem;
|
||||
min-width: 400px;
|
||||
max-width: 600px;
|
||||
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1);
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
}
|
||||
|
||||
/* Style toolbar */
|
||||
.insertr-style-toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
padding: 0.75rem;
|
||||
background: #f9fafb;
|
||||
border: 1px solid #e5e7eb;
|
||||
border-radius: 6px;
|
||||
margin-bottom: 1rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.insertr-toolbar-title {
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
color: #6b7280;
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
.insertr-style-btn {
|
||||
background: white;
|
||||
border: 1px solid #d1d5db;
|
||||
border-radius: 4px;
|
||||
padding: 0.375rem 0.75rem;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
color: #374151;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.insertr-style-btn:hover {
|
||||
background: #f3f4f6;
|
||||
border-color: #9ca3af;
|
||||
}
|
||||
|
||||
.insertr-style-btn:active {
|
||||
background: #e5e7eb;
|
||||
transform: translateY(1px);
|
||||
}
|
||||
|
||||
/* Simple text editor */
|
||||
.insertr-simple-editor {
|
||||
width: 100%;
|
||||
border: 1px solid #d1d5db;
|
||||
border-radius: 6px;
|
||||
padding: 0.75rem;
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.5;
|
||||
resize: vertical;
|
||||
font-family: inherit;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.insertr-simple-editor:focus {
|
||||
outline: none;
|
||||
border-color: #3b82f6;
|
||||
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
|
||||
}
|
||||
|
||||
/* Rich text editor */
|
||||
.insertr-rich-editor {
|
||||
width: 100%;
|
||||
min-height: 100px;
|
||||
border: 1px solid #d1d5db;
|
||||
border-radius: 6px;
|
||||
padding: 0.75rem;
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.5;
|
||||
margin-bottom: 1rem;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.insertr-rich-editor:focus {
|
||||
outline: none;
|
||||
border-color: #3b82f6;
|
||||
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
|
||||
}
|
||||
|
||||
/* Multi-property editor */
|
||||
.insertr-multi-property-editor {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.insertr-styled-element-form {
|
||||
background: #f9fafb;
|
||||
border: 1px solid #e5e7eb;
|
||||
border-radius: 6px;
|
||||
padding: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.insertr-styled-element-form:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.insertr-form-header {
|
||||
margin: 0 0 0.75rem 0;
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
color: #374151;
|
||||
}
|
||||
|
||||
/* Property inputs */
|
||||
.insertr-property-input,
|
||||
.insertr-text-input {
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.insertr-property-input:last-child,
|
||||
.insertr-text-input:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.insertr-property-label,
|
||||
.insertr-input-label {
|
||||
display: block;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
color: #374151;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.insertr-property-field,
|
||||
.insertr-text-field {
|
||||
width: 100%;
|
||||
border: 1px solid #d1d5db;
|
||||
border-radius: 4px;
|
||||
padding: 0.5rem;
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.4;
|
||||
font-family: inherit;
|
||||
transition: border-color 0.2s ease;
|
||||
}
|
||||
|
||||
.insertr-property-field:focus,
|
||||
.insertr-text-field:focus {
|
||||
outline: none;
|
||||
border-color: #3b82f6;
|
||||
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
|
||||
}
|
||||
|
||||
/* URL inputs */
|
||||
.insertr-property-field[type="url"] {
|
||||
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
||||
font-size: 0.8125rem;
|
||||
}
|
||||
|
||||
/* Form actions */
|
||||
.insertr-form-actions {
|
||||
display: flex;
|
||||
gap: 0.75rem;
|
||||
justify-content: flex-end;
|
||||
padding-top: 1rem;
|
||||
border-top: 1px solid #e5e7eb;
|
||||
}
|
||||
|
||||
.insertr-btn-save,
|
||||
.insertr-btn-cancel {
|
||||
padding: 0.5rem 1rem;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
border: 1px solid;
|
||||
}
|
||||
|
||||
.insertr-btn-save {
|
||||
background: #3b82f6;
|
||||
border-color: #3b82f6;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.insertr-btn-save:hover {
|
||||
background: #2563eb;
|
||||
border-color: #2563eb;
|
||||
}
|
||||
|
||||
.insertr-btn-cancel {
|
||||
background: white;
|
||||
border-color: #d1d5db;
|
||||
color: #374151;
|
||||
}
|
||||
|
||||
.insertr-btn-cancel:hover {
|
||||
background: #f9fafb;
|
||||
border-color: #9ca3af;
|
||||
}
|
||||
|
||||
/* Fallback editor */
|
||||
.insertr-fallback-editor {
|
||||
background: white;
|
||||
border: 1px solid #d1d5db;
|
||||
border-radius: 8px;
|
||||
padding: 1rem;
|
||||
min-width: 400px;
|
||||
max-width: 500px;
|
||||
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.insertr-fallback-textarea {
|
||||
width: 100%;
|
||||
border: 1px solid #d1d5db;
|
||||
border-radius: 6px;
|
||||
padding: 0.75rem;
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.5;
|
||||
resize: vertical;
|
||||
font-family: inherit;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.insertr-fallback-textarea:focus {
|
||||
outline: none;
|
||||
border-color: #3b82f6;
|
||||
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
|
||||
}
|
||||
|
||||
/* Responsive adjustments */
|
||||
@media (max-width: 640px) {
|
||||
.insertr-style-aware-editor,
|
||||
.insertr-fallback-editor {
|
||||
min-width: 300px;
|
||||
max-width: calc(100vw - 2rem);
|
||||
margin: 1rem;
|
||||
}
|
||||
|
||||
.insertr-style-toolbar {
|
||||
padding: 0.5rem;
|
||||
gap: 0.375rem;
|
||||
}
|
||||
|
||||
.insertr-style-btn {
|
||||
padding: 0.25rem 0.5rem;
|
||||
font-size: 0.8125rem;
|
||||
}
|
||||
|
||||
.insertr-form-actions {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.insertr-btn-save,
|
||||
.insertr-btn-cancel {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
/* Dark mode support (if needed) */
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.insertr-style-aware-editor,
|
||||
.insertr-fallback-editor {
|
||||
background: #1f2937;
|
||||
border-color: #374151;
|
||||
color: #f9fafb;
|
||||
}
|
||||
|
||||
.insertr-style-toolbar,
|
||||
.insertr-styled-element-form {
|
||||
background: #111827;
|
||||
border-color: #374151;
|
||||
}
|
||||
|
||||
.insertr-style-btn {
|
||||
background: #374151;
|
||||
border-color: #4b5563;
|
||||
color: #f9fafb;
|
||||
}
|
||||
|
||||
.insertr-style-btn:hover {
|
||||
background: #4b5563;
|
||||
border-color: #6b7280;
|
||||
}
|
||||
|
||||
.insertr-property-field,
|
||||
.insertr-text-field,
|
||||
.insertr-simple-editor,
|
||||
.insertr-fallback-textarea {
|
||||
background: #374151;
|
||||
border-color: #4b5563;
|
||||
color: #f9fafb;
|
||||
}
|
||||
|
||||
.insertr-rich-editor {
|
||||
background: #374151;
|
||||
border-color: #4b5563;
|
||||
color: #f9fafb;
|
||||
}
|
||||
}
|
||||
@@ -104,12 +104,9 @@ export class StyleAwareEditor {
|
||||
}
|
||||
|
||||
const hasStyledElements = detection.structure.some(piece => piece.type === 'styled');
|
||||
const hasMultiProperty = this.hasMultiPropertyElements(detection.structure);
|
||||
|
||||
if (hasMultiProperty) {
|
||||
return 'multi-property'; // Complex elements with multiple editable properties
|
||||
} else if (hasStyledElements) {
|
||||
return 'rich'; // Rich text with styling
|
||||
if (hasStyledElements) {
|
||||
return 'rich'; // Rich text with styling (handles all styled content including links)
|
||||
} else {
|
||||
return 'simple'; // Plain text
|
||||
}
|
||||
@@ -147,14 +144,11 @@ export class StyleAwareEditor {
|
||||
case 'rich':
|
||||
this.createRichEditor(analysis);
|
||||
break;
|
||||
case 'multi-property':
|
||||
this.createMultiPropertyEditor(analysis);
|
||||
break;
|
||||
}
|
||||
|
||||
// Add toolbar if enabled and we have detected styles
|
||||
if (this.options.showToolbar && analysis.styles.size > 0) {
|
||||
this.createStyleToolbar(analysis.styles);
|
||||
// Add toolbar if enabled and we have any styled content
|
||||
if (this.options.showToolbar && (analysis.styles.size > 0 || analysis.hasMultiPropertyElements)) {
|
||||
this.createStyleToolbar(analysis.styles, analysis.structure);
|
||||
}
|
||||
|
||||
// Add form actions
|
||||
@@ -191,173 +185,19 @@ export class StyleAwareEditor {
|
||||
this.editorContainer.appendChild(editor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create multi-property editor for complex elements
|
||||
*
|
||||
* @param {Object} analysis - Analysis results
|
||||
*/
|
||||
createMultiPropertyEditor(analysis) {
|
||||
const container = document.createElement('div');
|
||||
container.className = 'insertr-multi-property-editor';
|
||||
|
||||
// Create fields for each structure piece
|
||||
analysis.structure.forEach((piece, index) => {
|
||||
if (piece.type === 'text') {
|
||||
// Simple text input
|
||||
const input = this.createTextInput(piece.content, `Text ${index + 1}`);
|
||||
input.dataset.structureIndex = index;
|
||||
container.appendChild(input);
|
||||
} else if (piece.type === 'styled') {
|
||||
// Multi-property form for styled element
|
||||
const form = this.createStyledElementForm(piece, index);
|
||||
container.appendChild(form);
|
||||
}
|
||||
});
|
||||
|
||||
this.contentEditor = container;
|
||||
this.editorContainer.appendChild(container);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Create form for editing styled element with multiple properties
|
||||
*
|
||||
* @param {Object} piece - Structure piece for styled element
|
||||
* @param {number} index - Index in structure array
|
||||
* @returns {HTMLElement} - Form element
|
||||
*/
|
||||
createStyledElementForm(piece, index) {
|
||||
const form = document.createElement('div');
|
||||
form.className = 'insertr-styled-element-form';
|
||||
form.dataset.structureIndex = index;
|
||||
form.dataset.styleId = piece.styleId;
|
||||
|
||||
const style = this.detectedStyles.get(piece.styleId);
|
||||
if (!style) {
|
||||
return form;
|
||||
}
|
||||
|
||||
// Form header
|
||||
const header = document.createElement('h4');
|
||||
header.textContent = style.name;
|
||||
header.className = 'insertr-form-header';
|
||||
form.appendChild(header);
|
||||
|
||||
// Create input for each editable property
|
||||
Object.entries(piece.properties).forEach(([property, value]) => {
|
||||
const input = this.createPropertyInput(property, value, style.tagName);
|
||||
form.appendChild(input);
|
||||
});
|
||||
|
||||
return form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create input field for a specific property
|
||||
*
|
||||
* @param {string} property - Property name (content, href, src, etc.)
|
||||
* @param {string} value - Current property value
|
||||
* @param {string} tagName - Element tag name for context
|
||||
* @returns {HTMLElement} - Input field container
|
||||
*/
|
||||
createPropertyInput(property, value, tagName) {
|
||||
const container = document.createElement('div');
|
||||
container.className = 'insertr-property-input';
|
||||
|
||||
const label = document.createElement('label');
|
||||
label.textContent = this.getPropertyLabel(property, tagName);
|
||||
label.className = 'insertr-property-label';
|
||||
|
||||
let input;
|
||||
|
||||
switch (property) {
|
||||
case 'content':
|
||||
input = document.createElement('textarea');
|
||||
input.rows = 2;
|
||||
break;
|
||||
case 'href':
|
||||
input = document.createElement('input');
|
||||
input.type = 'url';
|
||||
input.placeholder = 'https://example.com';
|
||||
break;
|
||||
case 'src':
|
||||
input = document.createElement('input');
|
||||
input.type = 'url';
|
||||
input.placeholder = 'https://example.com/image.jpg';
|
||||
break;
|
||||
case 'alt':
|
||||
case 'title':
|
||||
input = document.createElement('input');
|
||||
input.type = 'text';
|
||||
break;
|
||||
default:
|
||||
input = document.createElement('input');
|
||||
input.type = 'text';
|
||||
break;
|
||||
}
|
||||
|
||||
input.className = 'insertr-property-field';
|
||||
input.name = property;
|
||||
input.value = value || '';
|
||||
|
||||
container.appendChild(label);
|
||||
container.appendChild(input);
|
||||
|
||||
return container;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get human-readable label for property
|
||||
*
|
||||
* @param {string} property - Property name
|
||||
* @param {string} tagName - Element tag name
|
||||
* @returns {string} - Human-readable label
|
||||
*/
|
||||
getPropertyLabel(property, tagName) {
|
||||
const labels = {
|
||||
content: tagName === 'img' ? 'Description' : 'Text',
|
||||
href: 'URL',
|
||||
src: 'Image URL',
|
||||
alt: 'Alt Text',
|
||||
title: 'Title',
|
||||
target: 'Target',
|
||||
placeholder: 'Placeholder'
|
||||
};
|
||||
|
||||
return labels[property] || property.charAt(0).toUpperCase() + property.slice(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create simple text input
|
||||
*
|
||||
* @param {string} content - Current content
|
||||
* @param {string} label - Input label
|
||||
* @returns {HTMLElement} - Input container
|
||||
*/
|
||||
createTextInput(content, label) {
|
||||
const container = document.createElement('div');
|
||||
container.className = 'insertr-text-input';
|
||||
|
||||
const labelEl = document.createElement('label');
|
||||
labelEl.textContent = label;
|
||||
labelEl.className = 'insertr-input-label';
|
||||
|
||||
const input = document.createElement('textarea');
|
||||
input.className = 'insertr-text-field';
|
||||
input.value = content;
|
||||
input.rows = 2;
|
||||
|
||||
container.appendChild(labelEl);
|
||||
container.appendChild(input);
|
||||
|
||||
return container;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create formatting toolbar with detected style buttons
|
||||
* Create formatting toolbar with detected style buttons and link button
|
||||
*
|
||||
* @param {Map} styles - Detected styles
|
||||
* @param {Array} structure - Content structure (to detect links)
|
||||
*/
|
||||
createStyleToolbar(styles) {
|
||||
createStyleToolbar(styles, structure = []) {
|
||||
const toolbar = document.createElement('div');
|
||||
toolbar.className = 'insertr-style-toolbar';
|
||||
|
||||
@@ -366,12 +206,27 @@ export class StyleAwareEditor {
|
||||
title.className = 'insertr-toolbar-title';
|
||||
toolbar.appendChild(title);
|
||||
|
||||
// Add button for each detected style
|
||||
// Add button for each detected style (except links - those use the link popup)
|
||||
for (const [styleId, styleInfo] of styles) {
|
||||
// Skip link styles - they should be handled by the link popup, not toolbar buttons
|
||||
if (styleInfo.tagName.toLowerCase() === 'a') {
|
||||
continue;
|
||||
}
|
||||
|
||||
const button = this.createStyleButton(styleId, styleInfo);
|
||||
toolbar.appendChild(button);
|
||||
}
|
||||
|
||||
// Add link button if we have links in content or always for rich editor
|
||||
const hasLinks = structure.some(piece =>
|
||||
piece.type === 'styled' && piece.element && piece.element.tagName.toLowerCase() === 'a'
|
||||
);
|
||||
|
||||
if (hasLinks || styles.size > 0) {
|
||||
const linkButton = this.createLinkButton();
|
||||
toolbar.appendChild(linkButton);
|
||||
}
|
||||
|
||||
this.toolbar = toolbar;
|
||||
this.editorContainer.insertBefore(toolbar, this.contentEditor);
|
||||
}
|
||||
@@ -400,6 +255,27 @@ export class StyleAwareEditor {
|
||||
return button;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create link button for opening link configuration popup
|
||||
*
|
||||
* @returns {HTMLElement} - Link button
|
||||
*/
|
||||
createLinkButton() {
|
||||
const button = document.createElement('button');
|
||||
button.type = 'button';
|
||||
button.className = 'insertr-style-btn';
|
||||
button.textContent = '🔗 Link';
|
||||
button.title = 'Add/Edit Link';
|
||||
|
||||
// Add click handler for link configuration
|
||||
button.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
this.openLinkPopup();
|
||||
});
|
||||
|
||||
return button;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create form action buttons (Save, Cancel)
|
||||
*/
|
||||
@@ -553,63 +429,12 @@ export class StyleAwareEditor {
|
||||
type: 'html',
|
||||
content: this.contentEditor.innerHTML
|
||||
};
|
||||
} else if (this.contentEditor.className.includes('multi-property-editor')) {
|
||||
// Multi-property editor
|
||||
return this.extractMultiPropertyContent();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract content from multi-property editor
|
||||
*
|
||||
* @returns {Object} - Structured content with updated properties
|
||||
*/
|
||||
extractMultiPropertyContent() {
|
||||
const updatedStructure = [...this.contentStructure];
|
||||
const updatedProperties = {};
|
||||
|
||||
// Extract text inputs
|
||||
const textInputs = this.contentEditor.querySelectorAll('.insertr-text-input textarea');
|
||||
textInputs.forEach(input => {
|
||||
const index = parseInt(input.closest('.insertr-text-input').dataset.structureIndex);
|
||||
if (!isNaN(index)) {
|
||||
updatedStructure[index] = {
|
||||
...updatedStructure[index],
|
||||
content: input.value
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
// Extract styled element forms
|
||||
const styledForms = this.contentEditor.querySelectorAll('.insertr-styled-element-form');
|
||||
styledForms.forEach(form => {
|
||||
const index = parseInt(form.dataset.structureIndex);
|
||||
const styleId = form.dataset.styleId;
|
||||
|
||||
if (!isNaN(index)) {
|
||||
const properties = {};
|
||||
const propertyFields = form.querySelectorAll('.insertr-property-field');
|
||||
|
||||
propertyFields.forEach(field => {
|
||||
properties[field.name] = field.value;
|
||||
});
|
||||
|
||||
updatedProperties[index] = { properties };
|
||||
updatedStructure[index] = {
|
||||
...updatedStructure[index],
|
||||
properties
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
type: 'structured',
|
||||
structure: updatedStructure,
|
||||
updatedProperties: updatedProperties
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply extracted content to the original element
|
||||
@@ -657,6 +482,230 @@ export class StyleAwareEditor {
|
||||
return this.editorContainer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Open link configuration popup for selected text
|
||||
*/
|
||||
openLinkPopup() {
|
||||
// Get current selection
|
||||
const selection = window.getSelection();
|
||||
|
||||
// Check if we have a valid selection in our editor
|
||||
if (!selection.rangeCount || !this.contentEditor.contains(selection.anchorNode)) {
|
||||
alert('Please select some text to create a link');
|
||||
return;
|
||||
}
|
||||
|
||||
const range = selection.getRangeAt(0);
|
||||
const selectedText = range.toString().trim();
|
||||
|
||||
if (!selectedText) {
|
||||
alert('Please select some text to create a link');
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if selection is inside an existing link
|
||||
let existingLink = null;
|
||||
let currentNode = range.commonAncestorContainer;
|
||||
|
||||
// Walk up the DOM to find if we're inside a link
|
||||
while (currentNode && currentNode !== this.contentEditor) {
|
||||
if (currentNode.nodeType === Node.ELEMENT_NODE && currentNode.tagName.toLowerCase() === 'a') {
|
||||
existingLink = currentNode;
|
||||
break;
|
||||
}
|
||||
currentNode = currentNode.parentNode;
|
||||
}
|
||||
|
||||
// Get existing URL if editing a link
|
||||
const currentUrl = existingLink ? existingLink.href : '';
|
||||
const currentTarget = existingLink ? existingLink.target : '';
|
||||
|
||||
// Show popup
|
||||
this.showLinkConfigPopup(selectedText, currentUrl, currentTarget, (url, target) => {
|
||||
this.applyLinkToSelection(range, selectedText, url, target, existingLink);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Show link configuration popup
|
||||
*
|
||||
* @param {string} text - Selected text
|
||||
* @param {string} currentUrl - Current URL (if editing)
|
||||
* @param {string} currentTarget - Current target (if editing)
|
||||
* @param {Function} onSave - Callback when user saves
|
||||
*/
|
||||
showLinkConfigPopup(text, currentUrl, currentTarget, onSave) {
|
||||
// Create popup overlay
|
||||
const overlay = document.createElement('div');
|
||||
overlay.className = 'insertr-modal-overlay';
|
||||
overlay.style.zIndex = '999999';
|
||||
|
||||
// Create popup container
|
||||
const popup = document.createElement('div');
|
||||
popup.className = 'insertr-modal-container';
|
||||
popup.style.maxWidth = '400px';
|
||||
|
||||
// Create form
|
||||
const form = document.createElement('div');
|
||||
form.className = 'insertr-edit-form';
|
||||
|
||||
// Header
|
||||
const header = document.createElement('div');
|
||||
header.className = 'insertr-form-header';
|
||||
header.innerHTML = `
|
||||
<h3 class="insertr-form-title">${currentUrl ? 'Edit' : 'Add'} Link</h3>
|
||||
<p class="insertr-form-help">Configure link for: "${text}"</p>
|
||||
`;
|
||||
|
||||
// Form body
|
||||
const body = document.createElement('div');
|
||||
body.className = 'insertr-form-body';
|
||||
|
||||
// URL input
|
||||
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="${currentUrl}" placeholder="https://example.com" required>
|
||||
`;
|
||||
|
||||
// Target input
|
||||
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" ${currentTarget === '_blank' ? 'selected' : ''}>New window</option>
|
||||
</select>
|
||||
`;
|
||||
|
||||
body.appendChild(urlGroup);
|
||||
body.appendChild(targetGroup);
|
||||
|
||||
// Actions
|
||||
const actions = document.createElement('div');
|
||||
actions.className = 'insertr-form-actions';
|
||||
|
||||
const saveBtn = document.createElement('button');
|
||||
saveBtn.className = 'insertr-btn-save';
|
||||
saveBtn.textContent = 'Save Link';
|
||||
|
||||
const cancelBtn = document.createElement('button');
|
||||
cancelBtn.className = 'insertr-btn-cancel';
|
||||
cancelBtn.textContent = 'Cancel';
|
||||
|
||||
// Remove link button (if editing existing link)
|
||||
if (currentUrl) {
|
||||
const removeBtn = document.createElement('button');
|
||||
removeBtn.className = 'insertr-btn-cancel';
|
||||
removeBtn.textContent = 'Remove Link';
|
||||
removeBtn.style.marginRight = 'auto';
|
||||
actions.appendChild(removeBtn);
|
||||
|
||||
removeBtn.addEventListener('click', () => {
|
||||
onSave('', ''); // Empty URL removes the link
|
||||
document.body.removeChild(overlay);
|
||||
});
|
||||
}
|
||||
|
||||
actions.appendChild(cancelBtn);
|
||||
actions.appendChild(saveBtn);
|
||||
|
||||
// Assemble popup
|
||||
form.appendChild(header);
|
||||
form.appendChild(body);
|
||||
form.appendChild(actions);
|
||||
popup.appendChild(form);
|
||||
overlay.appendChild(popup);
|
||||
|
||||
// Event handlers
|
||||
saveBtn.addEventListener('click', () => {
|
||||
const url = document.getElementById('link-url').value.trim();
|
||||
const target = document.getElementById('link-target').value;
|
||||
|
||||
if (url) {
|
||||
onSave(url, target);
|
||||
document.body.removeChild(overlay);
|
||||
} else {
|
||||
alert('Please enter a valid URL');
|
||||
}
|
||||
});
|
||||
|
||||
cancelBtn.addEventListener('click', () => {
|
||||
document.body.removeChild(overlay);
|
||||
});
|
||||
|
||||
overlay.addEventListener('click', (e) => {
|
||||
if (e.target === overlay) {
|
||||
document.body.removeChild(overlay);
|
||||
}
|
||||
});
|
||||
|
||||
// Show popup
|
||||
document.body.appendChild(overlay);
|
||||
|
||||
// Focus URL input
|
||||
setTimeout(() => {
|
||||
document.getElementById('link-url').focus();
|
||||
}, 100);
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply link to the current selection
|
||||
*
|
||||
* @param {Range} range - Selection range
|
||||
* @param {string} text - Selected text
|
||||
* @param {string} url - Link URL
|
||||
* @param {string} target - Link target
|
||||
* @param {HTMLElement} existingLink - Existing link element (if editing)
|
||||
*/
|
||||
applyLinkToSelection(range, text, url, target, existingLink) {
|
||||
if (!url) {
|
||||
// Remove link if no URL provided
|
||||
if (existingLink) {
|
||||
const parent = existingLink.parentNode;
|
||||
while (existingLink.firstChild) {
|
||||
parent.insertBefore(existingLink.firstChild, existingLink);
|
||||
}
|
||||
parent.removeChild(existingLink);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (existingLink) {
|
||||
// Update existing link
|
||||
existingLink.href = url;
|
||||
if (target) {
|
||||
existingLink.target = target;
|
||||
} else {
|
||||
existingLink.removeAttribute('target');
|
||||
}
|
||||
} else {
|
||||
// Create new link
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
if (target) {
|
||||
link.target = target;
|
||||
}
|
||||
|
||||
try {
|
||||
range.surroundContents(link);
|
||||
} catch (e) {
|
||||
// Fallback for complex selections
|
||||
link.textContent = text;
|
||||
range.deleteContents();
|
||||
range.insertNode(link);
|
||||
}
|
||||
}
|
||||
|
||||
// Clear selection
|
||||
window.getSelection().removeAllRanges();
|
||||
|
||||
// Trigger change event
|
||||
this.onChange();
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy the editor and clean up
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user