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
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:
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user