Compare commits
3 Commits
4ff9a239fd
...
v1.7.0
| Author | SHA1 | Date | |
|---|---|---|---|
| d0e649baa1 | |||
| cbc89d9810 | |||
| 3cb43d6cc0 |
@@ -4,6 +4,8 @@ Diese Liste beschreibt in einfachen Worten, was sich von Version zu Version an T
|
|||||||
|
|
||||||
## Unveröffentlicht
|
## Unveröffentlicht
|
||||||
|
|
||||||
|
## 1.7.0 – 2026-09-29
|
||||||
|
|
||||||
### Neu
|
### Neu
|
||||||
|
|
||||||
- Eigene Module: Als Kategorie steht jetzt auch „Eigene Module“ zur Auswahl. Einträge dort erscheinen gesammelt in einer eigenen Gruppe ganz unten in der Seitenleiste; die Gruppe ist nur zu sehen, solange ein Eintrag darin liegt. Bestehende Einträge verschieben Sie über „Bearbeiten“ dorthin.
|
- Eigene Module: Als Kategorie steht jetzt auch „Eigene Module“ zur Auswahl. Einträge dort erscheinen gesammelt in einer eigenen Gruppe ganz unten in der Seitenleiste; die Gruppe ist nur zu sehen, solange ein Eintrag darin liegt. Bestehende Einträge verschieben Sie über „Bearbeiten“ dorthin.
|
||||||
@@ -11,6 +13,7 @@ Diese Liste beschreibt in einfachen Worten, was sich von Version zu Version an T
|
|||||||
### Geändert
|
### Geändert
|
||||||
|
|
||||||
- Seitenleiste: Ist sie eingeklappt, sind die Symbole etwas größer und stehen etwas enger beieinander.
|
- Seitenleiste: Ist sie eingeklappt, sind die Symbole etwas größer und stehen etwas enger beieinander.
|
||||||
|
- Dashboard: Im Such-Widget haben die Auswahl der Suchmaschine und das Suchfeld keine helle Linie an der Unterkante mehr.
|
||||||
|
|
||||||
## 1.6.0 – 2026-09-29
|
## 1.6.0 – 2026-09-29
|
||||||
|
|
||||||
|
|||||||
@@ -255,11 +255,13 @@ body {
|
|||||||
Rahmen plus 2 px Akzentlinie unten. Gilt fuer Textfelder und Auswahllisten
|
Rahmen plus 2 px Akzentlinie unten. Gilt fuer Textfelder und Auswahllisten
|
||||||
mit dem ueblichen `border-input`/`border-border`-Rahmen; Fehlerzustaende
|
mit dem ueblichen `border-input`/`border-border`-Rahmen; Fehlerzustaende
|
||||||
(aria-invalid, border-destructive) behalten ihre Farbe. Bewusst ausserhalb
|
(aria-invalid, border-destructive) behalten ihre Farbe. Bewusst ausserhalb
|
||||||
eines @layer, damit die Regel die Tailwind-Utilities schlaegt. */
|
eines @layer, damit die Regel die Tailwind-Utilities schlaegt.
|
||||||
:is(input:not([type="checkbox"], [type="radio"], [type="range"], [type="color"], [type="file"]), select, textarea):is(.border-input, .border-border):not([aria-invalid="true"], .border-destructive) {
|
`.field-plain` nimmt ein Feld aus (Such-Widget, Nutzerwunsch 29.09.: die
|
||||||
|
helle Unterkante wirkte im Dunkelmodus wie eine weisse Linie). */
|
||||||
|
:is(input:not([type="checkbox"], [type="radio"], [type="range"], [type="color"], [type="file"]), select, textarea):is(.border-input, .border-border):not([aria-invalid="true"], .border-destructive, .field-plain) {
|
||||||
border-bottom-color: var(--input-strong);
|
border-bottom-color: var(--input-strong);
|
||||||
}
|
}
|
||||||
:is(input:not([type="checkbox"], [type="radio"], [type="range"], [type="color"], [type="file"]), select, textarea):is(.border-input, .border-border):not([aria-invalid="true"], .border-destructive):focus {
|
:is(input:not([type="checkbox"], [type="radio"], [type="range"], [type="color"], [type="file"]), select, textarea):is(.border-input, .border-border):not([aria-invalid="true"], .border-destructive, .field-plain):focus {
|
||||||
outline: none;
|
outline: none;
|
||||||
border-color: var(--input-strong);
|
border-color: var(--input-strong);
|
||||||
border-bottom-color: var(--primary);
|
border-bottom-color: var(--primary);
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ export function SearchWidget({ instanceId, config }: WidgetProps) {
|
|||||||
<div className="flex h-full items-center gap-2 px-1.5">
|
<div className="flex h-full items-center gap-2 px-1.5">
|
||||||
{/* Provider dropdown (D-14: ~120px left) */}
|
{/* Provider dropdown (D-14: ~120px left) */}
|
||||||
<select
|
<select
|
||||||
className="h-8 w-[120px] shrink-0 rounded border border-border bg-card px-2 text-sm text-foreground"
|
className="field-plain h-8 w-[120px] shrink-0 rounded border border-border bg-card px-2 text-sm text-foreground"
|
||||||
value={selectedProviderId}
|
value={selectedProviderId}
|
||||||
onChange={(e) => handleProviderChange(e.target.value)}
|
onChange={(e) => handleProviderChange(e.target.value)}
|
||||||
>
|
>
|
||||||
@@ -103,7 +103,7 @@ export function SearchWidget({ instanceId, config }: WidgetProps) {
|
|||||||
{/* Search input (flex-1 center) */}
|
{/* Search input (flex-1 center) */}
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
className="h-8 flex-1 rounded border border-border bg-background px-3 text-sm text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-primary"
|
className="field-plain h-8 flex-1 rounded border border-border bg-background px-3 text-sm text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-primary"
|
||||||
placeholder={t('search.placeholder')}
|
placeholder={t('search.placeholder')}
|
||||||
value={query}
|
value={query}
|
||||||
onChange={(e) => setQuery(e.target.value)}
|
onChange={(e) => setQuery(e.target.value)}
|
||||||
|
|||||||
@@ -40,9 +40,13 @@ vi.mock('next-intl', () => ({
|
|||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
let mockCollapsed = false;
|
||||||
|
function mockSidebarCollapsed(value: boolean) {
|
||||||
|
mockCollapsed = value;
|
||||||
|
}
|
||||||
vi.mock('@/lib/stores/sidebar-store', () => ({
|
vi.mock('@/lib/stores/sidebar-store', () => ({
|
||||||
useSidebarStore: () => ({
|
useSidebarStore: () => ({
|
||||||
isCollapsed: false,
|
isCollapsed: mockCollapsed,
|
||||||
isMobileOpen: false,
|
isMobileOpen: false,
|
||||||
toggle: vi.fn(),
|
toggle: vi.fn(),
|
||||||
setMobileOpen: vi.fn(),
|
setMobileOpen: vi.fn(),
|
||||||
@@ -108,6 +112,7 @@ afterEach(() => {
|
|||||||
mockPathname = '/';
|
mockPathname = '/';
|
||||||
mockRefreshKey = 0;
|
mockRefreshKey = 0;
|
||||||
mockCustomModules = [];
|
mockCustomModules = [];
|
||||||
|
mockCollapsed = false;
|
||||||
});
|
});
|
||||||
|
|
||||||
function customModule(overrides: Partial<(typeof mockCustomModules)[number]> = {}) {
|
function customModule(overrides: Partial<(typeof mockCustomModules)[number]> = {}) {
|
||||||
@@ -325,6 +330,24 @@ describe('Sidebar', () => {
|
|||||||
expect(screen.queryByText('custom-modules')).toBeNull();
|
expect(screen.queryByText('custom-modules')).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('eingeklappt stehen Eintraege aus „Eigene Module“ zuletzt', async () => {
|
||||||
|
mockCustomModules = [
|
||||||
|
customModule({ id: 'cm-1', category: 'custom-modules', name: 'Mein Wiki' }),
|
||||||
|
customModule({ id: 'cm-2', category: 'fleet', name: 'Tankkarte' }),
|
||||||
|
];
|
||||||
|
mockSidebarCollapsed(true);
|
||||||
|
const Sidebar = await importSidebar();
|
||||||
|
render(<Sidebar />);
|
||||||
|
|
||||||
|
const hrefs = () =>
|
||||||
|
Array.from(document.querySelectorAll('aside ul.border-t a')).map((a) => a.getAttribute('href'));
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(hrefs()).toContain('/modules/custom/cm-1');
|
||||||
|
});
|
||||||
|
expect(hrefs().at(-1)).toBe('/modules/custom/cm-1');
|
||||||
|
expect(hrefs().indexOf('/modules/custom/cm-2')).toBeLessThan(hrefs().indexOf('/modules/custom/cm-1'));
|
||||||
|
});
|
||||||
|
|
||||||
it('eingebaute Module stehen innerhalb einer Kategorie vor eigenen', async () => {
|
it('eingebaute Module stehen innerhalb einer Kategorie vor eigenen', async () => {
|
||||||
mockCustomModules = [customModule({ category: 'Domain-Tools', name: 'Aaa Eigenes' })];
|
mockCustomModules = [customModule({ category: 'Domain-Tools', name: 'Aaa Eigenes' })];
|
||||||
const Sidebar = await importSidebar();
|
const Sidebar = await importSidebar();
|
||||||
|
|||||||
@@ -186,6 +186,17 @@ export function Sidebar() {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Eingeklappt in derselben Gruppenfolge wie ausgeklappt — „Eigene Module“
|
||||||
|
// also auch hier immer zuletzt.
|
||||||
|
const collapsedEntries = useMemo(
|
||||||
|
() =>
|
||||||
|
[...entries].sort(
|
||||||
|
(a, b) =>
|
||||||
|
Number(a.category === CUSTOM_MODULE_CATEGORY) - Number(b.category === CUSTOM_MODULE_CATEGORY),
|
||||||
|
),
|
||||||
|
[entries],
|
||||||
|
);
|
||||||
|
|
||||||
const filteredCategories = useMemo(() => {
|
const filteredCategories = useMemo(() => {
|
||||||
const categories = new Map<string, SidebarEntry[]>();
|
const categories = new Map<string, SidebarEntry[]>();
|
||||||
for (const entry of entries) {
|
for (const entry of entries) {
|
||||||
@@ -253,7 +264,7 @@ export function Sidebar() {
|
|||||||
{/* Eingeklappt: nur die Modul-Kacheln untereinander (Name als Tooltip). */}
|
{/* Eingeklappt: nur die Modul-Kacheln untereinander (Name als Tooltip). */}
|
||||||
{isCollapsed && entries.length > 0 && (
|
{isCollapsed && entries.length > 0 && (
|
||||||
<ul className="mt-3 flex flex-col gap-px border-t border-sidebar-border pt-3">
|
<ul className="mt-3 flex flex-col gap-px border-t border-sidebar-border pt-3">
|
||||||
{entries.map((entry) => {
|
{collapsedEntries.map((entry) => {
|
||||||
const active = isEntryActive(entry);
|
const active = isEntryActive(entry);
|
||||||
return (
|
return (
|
||||||
<li key={entry.key}>
|
<li key={entry.key}>
|
||||||
|
|||||||
Reference in New Issue
Block a user