fix: DockerSecretsResolver - don't normalize absolute paths like /var/www/html/...
Some checks failed
Deploy Application / deploy (push) Has been cancelled

This commit is contained in:
2025-11-24 21:28:25 +01:00
parent 4eb7134853
commit 77abc65cd7
1327 changed files with 91915 additions and 9909 deletions

View File

@@ -0,0 +1,346 @@
/**
* Popover Manager for LiveComponents
*
* Manages popover components with:
* - Native Popover API support (Chrome 114+)
* - Fallback for older browsers
* - Position calculation relative to anchor
* - Auto-repositioning on scroll/resize
* - Light-dismiss handling
*/
/**
* Check if Popover API is supported
* @returns {boolean}
*/
function supportsPopoverAPI() {
// Check for popover property
if (HTMLElement.prototype.hasOwnProperty('popover')) {
return true;
}
// Check for showPopover method
if ('showPopover' in HTMLElement.prototype) {
return true;
}
// Check for CSS @supports (only in browser environment)
if (typeof CSS !== 'undefined' && CSS.supports) {
try {
return CSS.supports('popover', 'auto');
} catch (e) {
return false;
}
}
return false;
}
export class PopoverManager {
constructor() {
this.popovers = new Map(); // componentId → popover element
this.usePopoverAPI = supportsPopoverAPI();
this.resizeHandler = null;
this.scrollHandler = null;
if (this.usePopoverAPI) {
console.log('[PopoverManager] Using native Popover API');
} else {
console.log('[PopoverManager] Using fallback implementation');
this.setupGlobalHandlers();
}
}
/**
* Show popover
* @param {string} componentId - Component ID
* @param {Object} options - Popover options
* @returns {Object} Popover instance
*/
show(componentId, options = {}) {
const {
content = '',
title = '',
anchorId = null,
position = 'top',
showArrow = true,
offset = 8,
closeOnOutsideClick = true,
zIndex = 1060
} = options;
if (!anchorId) {
console.warn('[PopoverManager] Popover requires anchorId');
return null;
}
const anchor = document.getElementById(anchorId);
if (!anchor) {
console.warn(`[PopoverManager] Anchor element not found: ${anchorId}`);
return null;
}
// Remove existing popover for this component
this.hide(componentId);
if (this.usePopoverAPI) {
return this.showWithPopoverAPI(componentId, anchor, content, title, position, showArrow, offset, zIndex);
} else {
return this.showWithFallback(componentId, anchor, content, title, position, showArrow, offset, closeOnOutsideClick, zIndex);
}
}
/**
* Show popover using native Popover API
*/
showWithPopoverAPI(componentId, anchor, content, title, position, showArrow, offset, zIndex) {
const popover = document.createElement('div');
popover.setAttribute('popover', 'auto'); // 'auto' = light-dismiss enabled
popover.className = `livecomponent-popover popover--${position}`;
popover.id = `popover-${componentId}`;
popover.style.zIndex = zIndex.toString();
popover.innerHTML = `
${showArrow ? `<div class="popover-arrow popover-arrow--${position}"></div>` : ''}
${title ? `<div class="popover-header"><h4>${title}</h4></div>` : ''}
<div class="popover-body">${content}</div>
`;
// Set anchor using anchor attribute (when supported)
if (popover.setPopoverAnchor) {
popover.setPopoverAnchor(anchor);
} else {
// Fallback: position manually
this.positionPopover(popover, anchor, position, offset);
}
document.body.appendChild(popover);
popover.showPopover();
this.popovers.set(componentId, popover);
// Setup repositioning on scroll/resize
this.setupRepositioning(popover, anchor, position, offset);
return {
element: popover,
hide: () => this.hide(componentId),
isVisible: () => popover.matches(':popover-open')
};
}
/**
* Show popover using fallback implementation
*/
showWithFallback(componentId, anchor, content, title, position, showArrow, offset, closeOnOutsideClick, zIndex) {
const popover = document.createElement('div');
popover.className = `livecomponent-popover popover-fallback popover--${position}`;
popover.id = `popover-${componentId}`;
popover.style.zIndex = zIndex.toString();
popover.setAttribute('role', 'tooltip');
popover.setAttribute('aria-hidden', 'false');
popover.innerHTML = `
${showArrow ? `<div class="popover-arrow popover-arrow--${position}"></div>` : ''}
${title ? `<div class="popover-header"><h4>${title}</h4></div>` : ''}
<div class="popover-body">${content}</div>
`;
document.body.appendChild(popover);
// Position popover
this.positionPopover(popover, anchor, position, offset);
// Show popover
popover.style.display = 'block';
popover.classList.add('popover--show');
this.popovers.set(componentId, popover);
// Setup click-outside handling
if (closeOnOutsideClick) {
const clickHandler = (e) => {
if (!popover.contains(e.target) && !anchor.contains(e.target)) {
this.hide(componentId);
}
};
setTimeout(() => {
document.addEventListener('click', clickHandler);
popover._clickHandler = clickHandler;
}, 0);
}
// Setup repositioning on scroll/resize
this.setupRepositioning(popover, anchor, position, offset);
return {
element: popover,
hide: () => this.hide(componentId),
isVisible: () => popover.classList.contains('popover--show')
};
}
/**
* Hide popover
* @param {string} componentId - Component ID
*/
hide(componentId) {
const popover = this.popovers.get(componentId);
if (!popover) {
return;
}
if (this.usePopoverAPI) {
if (popover.hidePopover) {
popover.hidePopover();
}
} else {
popover.classList.remove('popover--show');
popover.style.display = 'none';
// Remove click handler
if (popover._clickHandler) {
document.removeEventListener('click', popover._clickHandler);
delete popover._clickHandler;
}
}
// Remove from DOM
if (popover.parentNode) {
popover.parentNode.removeChild(popover);
}
this.popovers.delete(componentId);
}
/**
* Position popover relative to anchor
* @param {HTMLElement} popover - Popover element
* @param {HTMLElement} anchor - Anchor element
* @param {string} position - Position (top, bottom, left, right)
* @param {number} offset - Offset in pixels
*/
positionPopover(popover, anchor, position, offset) {
const anchorRect = anchor.getBoundingClientRect();
const popoverRect = popover.getBoundingClientRect();
const viewportWidth = window.innerWidth;
const viewportHeight = window.innerHeight;
let top = 0;
let left = 0;
switch (position) {
case 'top':
top = anchorRect.top - popoverRect.height - offset;
left = anchorRect.left + (anchorRect.width / 2) - (popoverRect.width / 2);
break;
case 'bottom':
top = anchorRect.bottom + offset;
left = anchorRect.left + (anchorRect.width / 2) - (popoverRect.width / 2);
break;
case 'left':
top = anchorRect.top + (anchorRect.height / 2) - (popoverRect.height / 2);
left = anchorRect.left - popoverRect.width - offset;
break;
case 'right':
top = anchorRect.top + (anchorRect.height / 2) - (popoverRect.height / 2);
left = anchorRect.right + offset;
break;
case 'auto':
// Auto-position: choose best position based on viewport
const positions = ['bottom', 'top', 'right', 'left'];
for (const pos of positions) {
const testPos = this.calculatePosition(anchorRect, popoverRect, pos, offset);
if (this.isPositionValid(testPos, popoverRect, viewportWidth, viewportHeight)) {
position = pos;
({ top, left } = testPos);
break;
}
}
break;
}
// Ensure popover stays within viewport
left = Math.max(8, Math.min(left, viewportWidth - popoverRect.width - 8));
top = Math.max(8, Math.min(top, viewportHeight - popoverRect.height - 8));
popover.style.position = 'fixed';
popover.style.top = `${top}px`;
popover.style.left = `${left}px`;
}
/**
* Calculate position for a given direction
*/
calculatePosition(anchorRect, popoverRect, position, offset) {
let top = 0;
let left = 0;
switch (position) {
case 'top':
top = anchorRect.top - popoverRect.height - offset;
left = anchorRect.left + (anchorRect.width / 2) - (popoverRect.width / 2);
break;
case 'bottom':
top = anchorRect.bottom + offset;
left = anchorRect.left + (anchorRect.width / 2) - (popoverRect.width / 2);
break;
case 'left':
top = anchorRect.top + (anchorRect.height / 2) - (popoverRect.height / 2);
left = anchorRect.left - popoverRect.width - offset;
break;
case 'right':
top = anchorRect.top + (anchorRect.height / 2) - (popoverRect.height / 2);
left = anchorRect.right + offset;
break;
}
return { top, left };
}
/**
* Check if position is valid (within viewport)
*/
isPositionValid({ top, left }, popoverRect, viewportWidth, viewportHeight) {
return top >= 0 &&
left >= 0 &&
top + popoverRect.height <= viewportHeight &&
left + popoverRect.width <= viewportWidth;
}
/**
* Setup repositioning on scroll/resize
*/
setupRepositioning(popover, anchor, position, offset) {
const reposition = () => {
if (this.popovers.has(popover.id.replace('popover-', ''))) {
this.positionPopover(popover, anchor, position, offset);
}
};
window.addEventListener('scroll', reposition, { passive: true });
window.addEventListener('resize', reposition);
popover._repositionHandlers = {
scroll: reposition,
resize: reposition
};
}
/**
* Setup global handlers for fallback mode
*/
setupGlobalHandlers() {
// Global handlers are set up per-popover in showWithFallback
}
/**
* Cleanup all popovers
*/
destroy() {
for (const componentId of this.popovers.keys()) {
this.hide(componentId);
}
}
}