refactor: Still trying to reorganize edit mode to be more robust
Some checks failed
Test Suite / code-quality (push) Has been cancelled
Test Suite / unit-tests (3.11) (push) Has been cancelled
Test Suite / unit-tests (3.12) (push) Has been cancelled
Test Suite / security-scan (push) Has been cancelled
Test Suite / integration-tests (push) Has been cancelled
Test Suite / e2e-tests (push) Has been cancelled
Test Suite / performance-tests (push) Has been cancelled
Test Suite / test-summary (push) Has been cancelled

This commit is contained in:
2025-11-04 21:59:22 +01:00
parent 85faf502c4
commit c5a5b26797
487 changed files with 94669 additions and 144 deletions

View File

@@ -32,6 +32,31 @@ class FloatingMenu {
const targetElement = this.renderer.findSectionElement(this.sectionId);
if (!targetElement) return null;
// Get content dimensions and position
const rect = targetElement.getBoundingClientRect();
const viewport = {
width: window.innerWidth,
height: window.innerHeight
};
// Calculate content width and responsive extension
const contentWidth = rect.width;
const buttonAreaWidth = 120; // Space needed for buttons
const minMenuWidth = Math.max(300, contentWidth); // At least content width or 300px
const preferredMenuWidth = contentWidth + buttonAreaWidth;
// Check if we have space to extend to the right
const spaceOnRight = viewport.width - rect.right;
const canExtendRight = spaceOnRight >= buttonAreaWidth + 20; // 20px margin
// Determine final menu width
let menuWidth;
if (canExtendRight && viewport.width >= 800) { // Only on wide screens
menuWidth = Math.min(preferredMenuWidth, viewport.width - rect.left - 20);
} else {
menuWidth = Math.min(minMenuWidth, viewport.width - 40); // 20px margins
}
// Create floating menu element
this.element = document.createElement('div');
this.element.className = 'ui-edit-floating-menu';
@@ -42,65 +67,101 @@ class FloatingMenu {
border: 1px solid #ddd;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
padding: 16px;
min-width: 300px;
padding: 0;
width: ${menuWidth}px;
box-sizing: border-box;
`;
// Smart positioning with viewport boundary detection
const rect = targetElement.getBoundingClientRect();
const viewport = {
width: window.innerWidth,
height: window.innerHeight
};
// Add headline
const headline = document.createElement('div');
headline.className = 'ui-edit-headline';
headline.textContent = `Editing ${this.type === 'image' ? 'Image' : 'Section'}`;
headline.style.cssText = `
background: #f8f9fa;
border-bottom: 1px solid #ddd;
padding: 8px 16px;
font-weight: 600;
font-size: 12px;
color: #495057;
border-radius: 8px 8px 0 0;
text-transform: uppercase;
letter-spacing: 0.5px;
`;
// Calculate initial position (below the section)
// Create content wrapper with padding
const contentWrapper = document.createElement('div');
contentWrapper.style.cssText = `
padding: 16px;
`;
this.element.appendChild(headline);
// Position directly over content (overlay positioning)
let left = rect.left;
let top = rect.bottom + 10;
let top = rect.top;
// Adjust horizontal position if menu would go off-screen
const menuWidth = 350; // Estimated menu width
// Ensure menu doesn't go off-screen horizontally
if (left + menuWidth > viewport.width) {
left = viewport.width - menuWidth - 20; // 20px margin from edge
left = viewport.width - menuWidth - 20;
}
if (left < 10) {
left = 10; // Minimum margin from left edge
left = 10;
}
// Adjust vertical position if menu would go off-screen
const menuHeight = 300; // Estimated menu height
if (top + menuHeight > viewport.height) {
// Position above the section instead
top = rect.top - menuHeight - 10;
if (top < 10) {
// If still off-screen, position at viewport top
top = 10;
}
// For vertical positioning, prefer staying on top of content
// Only move if absolutely necessary
const menuHeight = this.type === 'image' ? 350 : 200; // Better height estimates
const wouldGoOffBottom = top + menuHeight > viewport.height;
const wouldGoOffTop = top < 10;
if (wouldGoOffBottom && !wouldGoOffTop) {
// Try to fit by moving up, but keep some overlay if possible
const maxTop = viewport.height - menuHeight - 10;
top = Math.max(rect.top - 50, maxTop); // Prefer staying near original position
} else if (wouldGoOffTop) {
top = 10; // Minimum distance from top
}
// Otherwise, keep the original overlay position
this.element.style.left = `${left}px`;
this.element.style.top = `${top}px`;
// Add content
// Add content to wrapper
if (contentElement) {
this.element.appendChild(contentElement);
contentWrapper.appendChild(contentElement);
}
if (controlsElement) {
this.element.appendChild(controlsElement);
contentWrapper.appendChild(controlsElement);
}
// Add close button
this.element.appendChild(contentWrapper);
// Add close button to headline
const closeButton = document.createElement('button');
closeButton.textContent = '×';
closeButton.style.cssText = `
position: absolute;
top: 8px;
top: 4px;
right: 8px;
background: none;
border: none;
font-size: 18px;
cursor: pointer;
color: #666;
width: 24px;
height: 24px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 4px;
transition: background-color 0.2s ease;
`;
closeButton.addEventListener('mouseover', () => {
closeButton.style.backgroundColor = '#e9ecef';
});
closeButton.addEventListener('mouseout', () => {
closeButton.style.backgroundColor = 'transparent';
});
closeButton.addEventListener('click', (event) => {
event.stopPropagation();
this.hide();
@@ -360,13 +421,36 @@ class DOMRenderer {
// Create content area for text editing
const editorContent = document.createElement('div');
editorContent.className = 'ui-edit-editor-content';
editorContent.style.cssText = `
display: flex;
flex-direction: column;
gap: 12px;
flex: 1;
min-width: 0;
`;
// Check if we have space for side-by-side layout
const targetElement = this.findSectionElement(sectionId);
const rect = targetElement ? targetElement.getBoundingClientRect() : null;
const viewport = { width: window.innerWidth, height: window.innerHeight };
const hasWideLayout = rect && viewport.width >= 800 && (viewport.width - rect.right) >= 120;
if (hasWideLayout) {
// Side-by-side layout: textarea on left, controls on right
editorContent.style.cssText = `
display: flex;
gap: 16px;
flex: 1;
min-width: 0;
align-items: flex-start;
`;
} else {
// Stacked layout: textarea above, controls below
editorContent.style.cssText = `
display: flex;
flex-direction: column;
gap: 12px;
flex: 1;
min-width: 0;
`;
}
// Create textarea container
const textareaContainer = document.createElement('div');
textareaContainer.style.cssText = hasWideLayout ? 'flex: 1; min-width: 0;' : 'width: 100%;';
// Create textarea
const textarea = document.createElement('textarea');
@@ -381,55 +465,81 @@ class DOMRenderer {
font-size: 14px;
line-height: 1.5;
resize: vertical;
box-sizing: border-box;
`;
// Create controls
const controls = document.createElement('div');
controls.style.cssText = `
display: flex;
gap: 8px;
justify-content: flex-end;
`;
if (hasWideLayout) {
controls.style.cssText = `
display: flex;
flex-direction: column;
gap: 8px;
min-width: 100px;
flex-shrink: 0;
`;
} else {
controls.style.cssText = `
display: flex;
gap: 8px;
justify-content: flex-end;
flex-wrap: wrap;
`;
}
const acceptButton = document.createElement('button');
acceptButton.textContent = 'Accept';
acceptButton.textContent = hasWideLayout ? '✓' : 'Accept';
acceptButton.style.cssText = `
background: #28a745;
color: white;
border: none;
padding: 8px 16px;
padding: ${hasWideLayout ? '8px 12px' : '8px 16px'};
border-radius: 4px;
cursor: pointer;
${hasWideLayout ? 'width: 100%;' : ''}
font-size: ${hasWideLayout ? '14px' : '13px'};
`;
const cancelButton = document.createElement('button');
cancelButton.textContent = 'Cancel';
cancelButton.textContent = hasWideLayout ? '✗' : 'Cancel';
cancelButton.style.cssText = `
background: #dc3545;
color: white;
border: none;
padding: 8px 16px;
padding: ${hasWideLayout ? '8px 12px' : '8px 16px'};
border-radius: 4px;
cursor: pointer;
${hasWideLayout ? 'width: 100%;' : ''}
font-size: ${hasWideLayout ? '14px' : '13px'};
`;
const resetButton = document.createElement('button');
resetButton.textContent = '↺ Reset';
resetButton.textContent = hasWideLayout ? '↺' : '↺ Reset';
resetButton.style.cssText = `
background: #fd7e14;
color: white;
border: none;
padding: 8px 16px;
padding: ${hasWideLayout ? '8px 12px' : '8px 16px'};
border-radius: 4px;
cursor: pointer;
${hasWideLayout ? 'width: 100%;' : ''}
font-size: ${hasWideLayout ? '14px' : '13px'};
`;
controls.appendChild(acceptButton);
controls.appendChild(cancelButton);
controls.appendChild(resetButton);
editorContent.appendChild(textarea);
editorContent.appendChild(controls);
// Assemble the layout
textareaContainer.appendChild(textarea);
if (hasWideLayout) {
editorContent.appendChild(textareaContainer);
editorContent.appendChild(controls);
} else {
editorContent.appendChild(textareaContainer);
editorContent.appendChild(controls);
}
// Create floating menu
const floatingMenu = new FloatingMenu(sectionId, 'text', this);
@@ -494,16 +604,52 @@ class DOMRenderer {
stagingState.currentImageSrc = imageSrc;
}
// Check if we have space for side-by-side layout
const targetElement = this.findSectionElement(sectionId);
const rect = targetElement ? targetElement.getBoundingClientRect() : null;
const viewport = { width: window.innerWidth, height: window.innerHeight };
const hasWideLayout = rect && viewport.width >= 800 && (viewport.width - rect.right) >= 120;
// Create image editor content area
const editorContent = document.createElement('div');
editorContent.className = 'ui-edit-image-content';
editorContent.style.cssText = `
display: flex;
flex-direction: column;
gap: 15px;
flex: 1;
min-width: 0;
`;
if (hasWideLayout) {
// Side-by-side layout: content on left, controls on right
editorContent.style.cssText = `
display: flex;
gap: 16px;
flex: 1;
min-width: 0;
align-items: flex-start;
`;
} else {
// Stacked layout: content above, controls below
editorContent.style.cssText = `
display: flex;
flex-direction: column;
gap: 15px;
flex: 1;
min-width: 0;
`;
}
// Create content container for image and alt text
const contentContainer = document.createElement('div');
contentContainer.style.cssText = hasWideLayout ? 'flex: 1; min-width: 0;' : 'width: 100%;';
if (!hasWideLayout) {
contentContainer.style.cssText += `
display: flex;
flex-direction: column;
gap: 15px;
`;
} else {
contentContainer.style.cssText += `
display: flex;
flex-direction: column;
gap: 12px;
`;
}
// Image preview with drop zone
const imagePreview = document.createElement('div');
@@ -718,27 +864,37 @@ class DOMRenderer {
}
};
// Assemble content
editorContent.appendChild(imagePreview);
editorContent.appendChild(altTextContainer);
editorContent.appendChild(changeIndicator);
editorContent.appendChild(fileInput);
// Assemble content container
contentContainer.appendChild(imagePreview);
contentContainer.appendChild(altTextContainer);
contentContainer.appendChild(changeIndicator);
contentContainer.appendChild(fileInput);
// Create controls
const controls = document.createElement('div');
controls.className = 'ui-edit-controls';
controls.style.cssText = `
display: flex;
flex-direction: column;
gap: 8px;
width: 100%;
`;
if (hasWideLayout) {
controls.style.cssText = `
display: flex;
flex-direction: column;
gap: 8px;
min-width: 100px;
flex-shrink: 0;
`;
} else {
controls.style.cssText = `
display: flex;
flex-direction: column;
gap: 8px;
width: 100%;
`;
}
const acceptBtn = document.createElement('button');
acceptBtn.textContent = '✓ Accept';
acceptBtn.textContent = hasWideLayout ? '✓' : '✓ Accept';
acceptBtn.style.cssText = `
padding: 8px 12px;
font-size: 12px;
padding: ${hasWideLayout ? '8px 12px' : '8px 12px'};
font-size: ${hasWideLayout ? '14px' : '12px'};
border-radius: 6px;
border: none;
color: white;
@@ -751,10 +907,10 @@ class DOMRenderer {
`;
const cancelBtn = document.createElement('button');
cancelBtn.textContent = '✗ Cancel';
cancelBtn.textContent = hasWideLayout ? '✗' : '✗ Cancel';
cancelBtn.style.cssText = `
padding: 8px 12px;
font-size: 12px;
padding: ${hasWideLayout ? '8px 12px' : '8px 12px'};
font-size: ${hasWideLayout ? '14px' : '12px'};
border-radius: 6px;
border: none;
color: white;
@@ -767,10 +923,10 @@ class DOMRenderer {
`;
const resetBtn = document.createElement('button');
resetBtn.textContent = '↺ Reset';
resetBtn.textContent = hasWideLayout ? '↺' : '↺ Reset';
resetBtn.style.cssText = `
padding: 8px 12px;
font-size: 12px;
padding: ${hasWideLayout ? '8px 12px' : '8px 12px'};
font-size: ${hasWideLayout ? '14px' : '12px'};
border-radius: 6px;
border: none;
color: white;
@@ -871,12 +1027,21 @@ class DOMRenderer {
}
});
// Assemble the final layout
if (hasWideLayout) {
editorContent.appendChild(contentContainer);
editorContent.appendChild(controls);
} else {
editorContent.appendChild(contentContainer);
editorContent.appendChild(controls);
}
// Create floating menu
const floatingMenu = new FloatingMenu(sectionId, 'image', this);
this.currentFloatingMenu = floatingMenu;
this.editingSections.add(sectionId);
floatingMenu.show(editorContent, controls);
floatingMenu.show(editorContent);
}
/**