diff --git a/CHANGELOG.md b/CHANGELOG.md index daaca9a..059e9e2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ Diese Liste beschreibt in einfachen Worten, was sich von Version zu Version an T ## Unveröffentlicht +### Geändert + +- Eigene Module: Die Seite füllt jetzt den ganzen Inhaltsbereich. Name und Hinweiszeile darüber sind weggefallen – der Name steht ohnehin oben in der Leiste, und „In neuem Tab öffnen“ sitzt jetzt dort rechts. + ### Behoben - Desktop-App: „Auf Version … aktualisieren“ im Menü des Tessera-Symbols scheiterte mit „Signaturprüfung fehlgeschlagen“ und öffnete stattdessen die Download-Seite, wenn der Server seit der letzten Update-Prüfung der App eine neuere Version bekommen hatte. Die App fragt jetzt beim Klick zuerst frisch nach und installiert genau die Version, die der Server in diesem Moment anbietet. diff --git a/apps/web/src/components/modules/custom-module-view.test.tsx b/apps/web/src/components/modules/custom-module-view.test.tsx index b0721e1..422abdb 100644 --- a/apps/web/src/components/modules/custom-module-view.test.tsx +++ b/apps/web/src/components/modules/custom-module-view.test.tsx @@ -6,7 +6,6 @@ vi.mock('next-intl', () => ({ useTranslations: () => (key: string) => { const map: Record = { openInNewTab: 'In neuem Tab öffnen', - embedHint: 'Manche Seiten lassen sich nicht einbetten.', notFound: 'Dieses Modul gibt es nicht mehr.', invalidUrl: 'Ungültige Adresse', loading: 'Wird geladen', @@ -59,7 +58,7 @@ describe('CustomModuleView', () => { expect(mockGetCustomModule).toHaveBeenCalledWith('abc'); }); - it('zeigt Namen und immer sichtbar den Link „In neuem Tab öffnen“', async () => { + it('zeigt immer den Link „In neuem Tab öffnen“, aber keine Name- oder Hinweiszeile', async () => { mockGetCustomModule.mockResolvedValue(mod('https://example.com/wiki')); render(); @@ -67,7 +66,21 @@ describe('CustomModuleView', () => { expect(link).toHaveAttribute('href', 'https://example.com/wiki'); expect(link).toHaveAttribute('target', '_blank'); expect(link).toHaveAttribute('rel', 'noopener noreferrer'); - expect(screen.getByRole('heading', { name: 'Wiki' })).toBeInTheDocument(); + expect(screen.queryByRole('heading')).toBeNull(); + }); + + it('setzt den Link in die App-Leiste, wenn deren Einhaengepunkt da ist', async () => { + const slot = document.createElement('div'); + slot.id = 'header-actions-slot'; + document.body.appendChild(slot); + try { + mockGetCustomModule.mockResolvedValue(mod('https://example.com/wiki')); + render(); + const link = await screen.findByRole('link', { name: 'In neuem Tab öffnen' }); + expect(slot.contains(link)).toBe(true); + } finally { + slot.remove(); + } }); it.each([ diff --git a/apps/web/src/components/modules/custom-module-view.tsx b/apps/web/src/components/modules/custom-module-view.tsx index 6d8d163..43376ee 100644 --- a/apps/web/src/components/modules/custom-module-view.tsx +++ b/apps/web/src/components/modules/custom-module-view.tsx @@ -1,16 +1,20 @@ 'use client'; import { useEffect, useState } from 'react'; +import { createPortal } from 'react-dom'; import { useTranslations } from 'next-intl'; +import { HEADER_ACTIONS_SLOT_ID } from '@/components/layout/header-slot'; import { XFRAME_SANDBOX } from '@/components/dashboard/widgets/xframe-config'; import { type CustomModule, checkCustomModuleUrl, getCustomModule } from '@/lib/custom-modules-api'; type LoadState = { status: 'loading' } | { status: 'missing' } | { status: 'ready'; mod: CustomModule }; /** - * Rahmen-Ansicht eines eigenen Moduls (quick-260929-9wc, D-06): schmale - * Leiste mit Name, Hinweis und dem immer sichtbaren Knopf „In neuem Tab - * oeffnen“, darunter der flaechenfuellende Rahmen. Sandbox-Wert und + * Rahmen-Ansicht eines eigenen Moduls (quick-260929-9wc, D-06): der Rahmen + * fuellt den ganzen Inhaltsbereich. Der Name steht ohnehin in der App-Leiste, + * der immer sichtbare Knopf „In neuem Tab oeffnen“ sitzt dort rechts (Portal + * in HEADER_ACTIONS_SLOT_ID, wie die Dashboard-Aktionen); Name- und + * Hinweiszeile ueber dem Rahmen entfielen auf Nutzerwunsch (29.09.2026). Sandbox-Wert und * Attribute wie im XFrame-Widget (`XFRAME_SANDBOX`, kein `allow`, kein * Referrer). Iframe und Link entstehen NUR bei einer https-Adresse ohne * Zugangsdaten — auch eine von Hand veraenderte Datenbankzeile mit @@ -19,6 +23,10 @@ type LoadState = { status: 'loading' } | { status: 'missing' } | { status: 'read export function CustomModuleView({ id }: { id: string }) { const t = useTranslations('customModules'); const [state, setState] = useState({ status: 'loading' }); + const [actionsSlot, setActionsSlot] = useState(null); + useEffect(() => { + setActionsSlot(document.getElementById(HEADER_ACTIONS_SLOT_ID)); + }, []); useEffect(() => { let cancelled = false; @@ -46,24 +54,36 @@ export function CustomModuleView({ id }: { id: string }) { const { mod } = state; const urlOk = checkCustomModuleUrl(mod.url) === 'ok'; + const openLink = urlOk ? ( + + + {t('openInNewTab')} + + ) : null; + return (
-
-
-

{mod.name}

- {urlOk &&

{t('embedHint')}

} -
- {urlOk && ( - - {t('openInNewTab')} - - )} -
+ {openLink && (actionsSlot ? createPortal(openLink, actionsSlot) : openLink)} {urlOk ? (