fix: Improve plugin ui entry points, Fix chat scroll, fix notifications, fix user rights

This commit is contained in:
2026-05-17 16:09:16 +02:00
parent 8e3ccf4157
commit 8631290c01
35 changed files with 1560 additions and 619 deletions

View File

@@ -0,0 +1,139 @@
import {
ElementRef,
Injectable,
inject
} from '@angular/core';
import {
ConnectedPosition,
Overlay,
OverlayRef
} from '@angular/cdk/overlay';
import { ComponentPortal } from '@angular/cdk/portal';
import {
Subscription,
filter,
fromEvent
} from 'rxjs';
import { PluginActionMenuComponent } from './plugin-action-menu.component';
const GAP = 10;
const VIEWPORT_MARGIN = 8;
const POSITIONS: ConnectedPosition[] = [
{ originX: 'end', originY: 'top', overlayX: 'start', overlayY: 'top', offsetX: GAP },
{ originX: 'end', originY: 'bottom', overlayX: 'start', overlayY: 'bottom', offsetX: GAP },
{ originX: 'start', originY: 'top', overlayX: 'end', overlayY: 'top', offsetX: -GAP },
{ originX: 'start', originY: 'bottom', overlayX: 'end', overlayY: 'bottom', offsetX: -GAP }
];
@Injectable({ providedIn: 'root' })
export class PluginActionMenuService {
private readonly overlay = inject(Overlay);
private currentOrigin: HTMLElement | null = null;
private overlayRef: OverlayRef | null = null;
private overlaySubscriptions: Subscription | null = null;
private scrollBlocker: (() => void) | null = null;
open(origin: ElementRef | HTMLElement): void {
const rawEl = origin instanceof ElementRef ? origin.nativeElement : origin;
if (this.overlayRef) {
const sameOrigin = rawEl === this.currentOrigin;
this.close();
if (sameOrigin) {
return;
}
}
const elementRef = origin instanceof ElementRef ? origin : new ElementRef(origin);
this.currentOrigin = rawEl;
const positionStrategy = this.overlay
.position()
.flexibleConnectedTo(elementRef)
.withPositions(POSITIONS)
.withViewportMargin(VIEWPORT_MARGIN)
.withPush(true);
this.overlayRef = this.overlay.create({
positionStrategy,
scrollStrategy: this.overlay.scrollStrategies.noop()
});
this.syncThemeVars();
const componentRef = this.overlayRef.attach(new ComponentPortal(PluginActionMenuComponent));
const subscriptions = new Subscription();
subscriptions.add(componentRef.instance.closed.subscribe(() => this.close()));
subscriptions.add(fromEvent<PointerEvent>(document, 'pointerdown')
.pipe(
filter((event) => {
const target = event.target as Node;
if (this.overlayRef?.overlayElement.contains(target)) {
return false;
}
if (this.currentOrigin?.contains(target)) {
return false;
}
return true;
})
)
.subscribe(() => this.close()));
this.overlaySubscriptions = subscriptions;
this.blockScroll();
}
close(): void {
this.scrollBlocker?.();
this.scrollBlocker = null;
this.overlaySubscriptions?.unsubscribe();
this.overlaySubscriptions = null;
if (this.overlayRef) {
this.overlayRef.dispose();
this.overlayRef = null;
this.currentOrigin = null;
}
}
private blockScroll(): void {
const handler = (event: Event): void => {
if (this.overlayRef?.overlayElement.contains(event.target as Node)) {
return;
}
event.preventDefault();
};
const opts: AddEventListenerOptions = { passive: false, capture: true };
document.addEventListener('wheel', handler, opts);
document.addEventListener('touchmove', handler, opts);
this.scrollBlocker = () => {
document.removeEventListener('wheel', handler, opts);
document.removeEventListener('touchmove', handler, opts);
};
}
private syncThemeVars(): void {
const appRoot = document.querySelector<HTMLElement>('[data-theme-key="appRoot"]');
const container = document.querySelector<HTMLElement>('.cdk-overlay-container');
if (!appRoot || !container) {
return;
}
for (const prop of Array.from(appRoot.style)) {
if (prop.startsWith('--')) {
container.style.setProperty(prop, appRoot.style.getPropertyValue(prop));
}
}
}
}