Merge dev into main (v0.31.0)
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 28s
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 28s
This commit is contained in:
commit
f2c92c4bec
11 files changed
+175
-33
No files matched your search
+7
-1
@@ -6,6 +6,11 @@ Das Format folgt [Keep a Changelog](https://keepachangelog.com/de/1.1.0/), die V
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
## [0.31.0] - 2026-11-01
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- Kanban: Die Farbe jeder Spalte lässt sich einstellen. In „Spalten anpassen“ öffnet der Farbpunkt vor dem Namen die Auswahl: Grau, Anthrazit, Grün, Gelb, Blau, Rot, Lila oder die Akzentfarbe des Themes. Die Farbe erscheint im Spaltenkopf, als Statuspunkt in der Listenansicht und im Detail-Fenster. Blau und Lila passen sich hellen und dunklen Themes an.
|
||||||
|
|
||||||
## [0.30.0] - 2026-11-01
|
## [0.30.0] - 2026-11-01
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
@@ -577,7 +582,8 @@ Das Format folgt [Keep a Changelog](https://keepachangelog.com/de/1.1.0/), die V
|
|||||||
- Geteilte Listen (Einkaufs-, Pack- und Checklisten) mit `#Kategorie`-Gruppierung, Mengenangaben und Echtzeit-Abhaken.
|
- Geteilte Listen (Einkaufs-, Pack- und Checklisten) mit `#Kategorie`-Gruppierung, Mengenangaben und Echtzeit-Abhaken.
|
||||||
- Benutzer-, Gruppen- und Admin-Verwaltung (Ersteinrichtungs-Assistent, Einladungscodes, Profilbilder, persönliche API-Keys & Webhook-Ingest `/api/v1/webhooks/ingest`, JSON-Datenexport) sowie CSS-Theme-Engine mit 4 Themes (`Dark`, `Light`, `High Contrast`, `Soft`) und PWA-Unterstützung.
|
- Benutzer-, Gruppen- und Admin-Verwaltung (Ersteinrichtungs-Assistent, Einladungscodes, Profilbilder, persönliche API-Keys & Webhook-Ingest `/api/v1/webhooks/ingest`, JSON-Datenexport) sowie CSS-Theme-Engine mit 4 Themes (`Dark`, `Light`, `High Contrast`, `Soft`) und PWA-Unterstützung.
|
||||||
|
|
||||||
[Unreleased]: https://git.dsit.me/sandrodach/MindSift/compare/v0.30.0...dev
|
[Unreleased]: https://git.dsit.me/sandrodach/MindSift/compare/v0.31.0...dev
|
||||||
|
[0.31.0]: https://git.dsit.me/sandrodach/MindSift/compare/v0.30.0...v0.31.0
|
||||||
[0.30.0]: https://git.dsit.me/sandrodach/MindSift/compare/v0.29.0...v0.30.0
|
[0.30.0]: https://git.dsit.me/sandrodach/MindSift/compare/v0.29.0...v0.30.0
|
||||||
[0.29.0]: https://git.dsit.me/sandrodach/MindSift/compare/v0.28.0...v0.29.0
|
[0.29.0]: https://git.dsit.me/sandrodach/MindSift/compare/v0.28.0...v0.29.0
|
||||||
[0.28.0]: https://git.dsit.me/sandrodach/MindSift/compare/v0.27.1...v0.28.0
|
[0.28.0]: https://git.dsit.me/sandrodach/MindSift/compare/v0.27.1...v0.28.0
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ Eine Aufgabe ist ein `thought` ([database.md](../database.md)) mit `kanbanStatus
|
|||||||
## Ansichten
|
## Ansichten
|
||||||
|
|
||||||
- **Liste** mit Filtern (offen, erledigt, Papierkorb), Gruppierung (z. B. nach Status oder Fälligkeit), Sortierung und Hashtag-Filter.
|
- **Liste** mit Filtern (offen, erledigt, Papierkorb), Gruppierung (z. B. nach Status oder Fälligkeit), Sortierung und Hashtag-Filter.
|
||||||
- **Kanban:** Spalten pro Liste frei konfigurierbar (Name, Farbe, „Erledigt“-Spalte). Standard: *Zu erledigen*, *In Bearbeitung*, *Wartet auf andere*, *Erledigt*. Eine Karte in eine „Erledigt“-Spalte zu ziehen setzt `isCompleted`.
|
- **Kanban:** Spalten pro Liste frei konfigurierbar (Name, Farbe, „Erledigt“-Spalte). Farben in „Spalten anpassen“ über den Punkt vor dem Namen: Grau, Anthrazit (Textfarbe des Themes), Grün, Gelb, Blau, Rot, Lila, Akzentfarbe (`columns[].color` = `gray`/`slate`/`green`/`yellow`/`blue`/`red`/`purple`/`accent`; ältere Werte `emerald`/`amber` gelten als Grün/Gelb, Spalten ohne Farbe behalten die ihrer Rolle). Der Punkt erscheint im Spaltenkopf, vor dem Status in der Listenansicht und im Detail-Fenster ([ManageColumnsModal.tsx](../../frontend/src/features/triage/ManageColumnsModal.tsx), `columnDotClass`). Blau und Lila kommen aus `--color-info` / `--color-purple`; Themes können sie setzen, sonst gelten Standardwerte aus `index.css`. Standard: *Zu erledigen*, *In Bearbeitung*, *Wartet auf andere*, *Erledigt*. Eine Karte in eine „Erledigt“-Spalte zu ziehen setzt `isCompleted`.
|
||||||
- Drag & Drop sortiert in der Liste und zwischen Spalten; ein Klick öffnet das Detail-Fenster (Titel, Status, Priorität, Fälligkeit mit optionaler Uhrzeit und Schnellwahl, Liste, Hashtags, Unteraufgaben mit Fortschrittsbalken, Notizen).
|
- Drag & Drop sortiert in der Liste und zwischen Spalten; ein Klick öffnet das Detail-Fenster (Titel, Status, Priorität, Fälligkeit mit optionaler Uhrzeit und Schnellwahl, Liste, Hashtags, Unteraufgaben mit Fortschrittsbalken, Notizen).
|
||||||
- Aufgaben lassen sich in eine Notiz umwandeln („Als Notiz vertiefen“). Aufgaben aus Notiz-Checkboxen (`enhanced-task`) bleiben verknüpft: Abhaken wirkt in beide Richtungen (`SyncDocBlockAndTask`), „In Notiz öffnen“ springt zum Block.
|
- Aufgaben lassen sich in eine Notiz umwandeln („Als Notiz vertiefen“). Aufgaben aus Notiz-Checkboxen (`enhanced-task`) bleiben verknüpft: Abhaken wirkt in beide Richtungen (`SyncDocBlockAndTask`), „In Notiz öffnen“ springt zum Block.
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
[](#)
|
[](#)
|
||||||
[](#-schnellstart)
|
[](#-schnellstart)
|
||||||
[](#-deine-daten-gehören-dir)
|
[](#-deine-daten-gehören-dir)
|
||||||
[](CHANGELOG.md)
|
[](CHANGELOG.md)
|
||||||
|
|
||||||
**MindSift** ist eine selbst gehostete Schaltstelle für spontane Gedanken, Aufgaben mit Kanban-Board, hierarchische Outliner-Notizen, die mehrere Personen gleichzeitig bearbeiten können, und gemeinsame Listen. Alles läuft als ein einzelner Container mit eingebetteter SQLite-Datenbank, auf deinem eigenen Server.
|
**MindSift** ist eine selbst gehostete Schaltstelle für spontane Gedanken, Aufgaben mit Kanban-Board, hierarchische Outliner-Notizen, die mehrere Personen gleichzeitig bearbeiten können, und gemeinsame Listen. Alles läuft als ein einzelner Container mit eingebetteter SQLite-Datenbank, auf deinem eigenen Server.
|
||||||
|
|
||||||
|
|||||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "mindsift-frontend",
|
"name": "mindsift-frontend",
|
||||||
"version": "0.30.0",
|
"version": "0.31.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "mindsift-frontend",
|
"name": "mindsift-frontend",
|
||||||
"version": "0.30.0",
|
"version": "0.31.0",
|
||||||
"license": "Unlicense",
|
"license": "Unlicense",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fontsource-variable/inter": "5.3.0",
|
"@fontsource-variable/inter": "5.3.0",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "mindsift-frontend",
|
"name": "mindsift-frontend",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.30.0",
|
"version": "0.31.0",
|
||||||
"license": "Unlicense",
|
"license": "Unlicense",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect, useRef } from 'react';
|
||||||
import {
|
import {
|
||||||
X,
|
X,
|
||||||
Plus,
|
Plus,
|
||||||
@@ -28,17 +28,32 @@ export const DEFAULT_KANBAN_COLUMNS: KanbanColumn[] = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
// Wrapper and content split so the hooks always run in the same number (see TaskDetailModal)
|
// Wrapper and content split so the hooks always run in the same number (see TaskDetailModal)
|
||||||
// Dot colour of a status column (Kanban header, list view); fixed by column role for now
|
// Colours a column can get; all come from the theme (blue and purple via --color-info / --color-purple)
|
||||||
|
export const COLUMN_COLORS: { id: string; label: string; cls: string }[] = [
|
||||||
|
{ id: 'gray', label: 'Grau', cls: 'bg-faint' },
|
||||||
|
{ id: 'slate', label: 'Anthrazit', cls: 'bg-main' },
|
||||||
|
{ id: 'green', label: 'Grün', cls: 'bg-status-ok' },
|
||||||
|
{ id: 'yellow', label: 'Gelb', cls: 'bg-status-warn' },
|
||||||
|
{ id: 'blue', label: 'Blau', cls: 'bg-tone-blue' },
|
||||||
|
{ id: 'red', label: 'Rot', cls: 'bg-status-err' },
|
||||||
|
{ id: 'purple', label: 'Lila', cls: 'bg-tone-purple' },
|
||||||
|
{ id: 'accent', label: 'Akzentfarbe', cls: 'bg-accent' },
|
||||||
|
];
|
||||||
|
// Names the server's default columns use
|
||||||
|
const COLOR_ALIASES: Record<string, string> = { emerald: 'green', amber: 'yellow' };
|
||||||
|
|
||||||
|
const columnColorId = (col: KanbanColumn): string => {
|
||||||
|
if (col.color) return COLOR_ALIASES[col.color] || col.color;
|
||||||
|
// Columns without a colour (older boards) keep the colour of their role
|
||||||
|
if (col.isDone) return 'green';
|
||||||
|
if (col.id === 'in_progress') return 'accent';
|
||||||
|
if (col.id === 'waiting') return 'yellow';
|
||||||
|
return 'gray';
|
||||||
|
};
|
||||||
|
|
||||||
|
// Dot colour of a status column (Kanban header, list view, task detail)
|
||||||
export const columnDotClass = (col: KanbanColumn | undefined): string =>
|
export const columnDotClass = (col: KanbanColumn | undefined): string =>
|
||||||
!col
|
(col && COLUMN_COLORS.find((c) => c.id === columnColorId(col))?.cls) || 'bg-faint';
|
||||||
? 'bg-faint'
|
|
||||||
: col.isDone
|
|
||||||
? 'bg-status-ok'
|
|
||||||
: col.id === 'in_progress'
|
|
||||||
? 'bg-accent'
|
|
||||||
: col.id === 'waiting'
|
|
||||||
? 'bg-status-warn'
|
|
||||||
: 'bg-faint';
|
|
||||||
|
|
||||||
export const ManageColumnsModal: React.FC<ManageColumnsModalProps> = (props) =>
|
export const ManageColumnsModal: React.FC<ManageColumnsModalProps> = (props) =>
|
||||||
props.isOpen ? <ManageColumnsModalContent {...props} /> : null;
|
props.isOpen ? <ManageColumnsModalContent {...props} /> : null;
|
||||||
@@ -56,6 +71,44 @@ const ManageColumnsModalContent: React.FC<ManageColumnsModalProps> = ({
|
|||||||
const [newColName, setNewColName] = useState('');
|
const [newColName, setNewColName] = useState('');
|
||||||
const [newColIsDone, setNewColIsDone] = useState(false);
|
const [newColIsDone, setNewColIsDone] = useState(false);
|
||||||
const [saving, setSaving] = useState(false);
|
const [saving, setSaving] = useState(false);
|
||||||
|
// Colour menu: opens at the column's dot like a context menu (fixed, so the scrolling list cannot clip it)
|
||||||
|
const [colorMenu, setColorMenu] = useState<{ id: string; left: number; top?: number; bottom?: number } | null>(null);
|
||||||
|
const colorMenuRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!colorMenu) return;
|
||||||
|
const close = (e: Event) => {
|
||||||
|
const target = e.target as HTMLElement;
|
||||||
|
if (colorMenuRef.current?.contains(target) || target.closest?.('[data-color-trigger]')) return;
|
||||||
|
setColorMenu(null);
|
||||||
|
};
|
||||||
|
const onKey = (e: KeyboardEvent) => e.key === 'Escape' && setColorMenu(null);
|
||||||
|
const dismiss = () => setColorMenu(null);
|
||||||
|
document.addEventListener('pointerdown', close);
|
||||||
|
document.addEventListener('keydown', onKey);
|
||||||
|
window.addEventListener('resize', dismiss);
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener('pointerdown', close);
|
||||||
|
document.removeEventListener('keydown', onKey);
|
||||||
|
window.removeEventListener('resize', dismiss);
|
||||||
|
};
|
||||||
|
}, [colorMenu]);
|
||||||
|
|
||||||
|
const openColorMenu = (id: string, trigger: HTMLElement) => {
|
||||||
|
if (colorMenu?.id === id) {
|
||||||
|
setColorMenu(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const rect = trigger.getBoundingClientRect();
|
||||||
|
const menuHeight = COLUMN_COLORS.length * 36 + 12;
|
||||||
|
const left = Math.min(rect.left, window.innerWidth - 200);
|
||||||
|
// Below the dot if there is room, else above it
|
||||||
|
setColorMenu(
|
||||||
|
rect.bottom + menuHeight + 8 < window.innerHeight
|
||||||
|
? { id, left, top: rect.bottom + 4 }
|
||||||
|
: { id, left, bottom: window.innerHeight - rect.top + 4 }
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setColumns(initialColumns && initialColumns.length > 0 ? initialColumns : DEFAULT_KANBAN_COLUMNS);
|
setColumns(initialColumns && initialColumns.length > 0 ? initialColumns : DEFAULT_KANBAN_COLUMNS);
|
||||||
@@ -78,6 +131,12 @@ const ManageColumnsModalContent: React.FC<ManageColumnsModalProps> = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleColor = (id: string, color: string) => {
|
||||||
|
triggerHaptic(10);
|
||||||
|
setColumns((prev) => prev.map((c) => (c.id === id ? { ...c, color } : c)));
|
||||||
|
setColorMenu(null);
|
||||||
|
};
|
||||||
|
|
||||||
const handleToggleDone = (id: string) => {
|
const handleToggleDone = (id: string) => {
|
||||||
triggerHaptic(10);
|
triggerHaptic(10);
|
||||||
setColumns((prev) =>
|
setColumns((prev) =>
|
||||||
@@ -172,12 +231,10 @@ const ManageColumnsModalContent: React.FC<ManageColumnsModalProps> = ({
|
|||||||
{/* Columns List */}
|
{/* Columns List */}
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<label className="block text-xs font-semibold text-main">Aktive Spalten ({columns.length}):</label>
|
<label className="block text-xs font-semibold text-main">Aktive Spalten ({columns.length}):</label>
|
||||||
<div className="space-y-1.5 max-h-60 overflow-y-auto pr-1">
|
<div className="space-y-1.5 max-h-[45vh] overflow-y-auto pr-1" onScroll={() => setColorMenu(null)}>
|
||||||
{columns.map((col, idx) => (
|
{columns.map((col, idx) => (
|
||||||
<div
|
<div key={col.id} className="p-2 bg-canvas border border-border rounded-card space-y-2">
|
||||||
key={col.id}
|
<div className="flex items-center gap-2">
|
||||||
className="p-2 bg-canvas border border-border rounded-card flex items-center gap-2"
|
|
||||||
>
|
|
||||||
<div className="flex flex-col gap-0.5">
|
<div className="flex flex-col gap-0.5">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@@ -199,6 +256,19 @@ const ManageColumnsModalContent: React.FC<ManageColumnsModalProps> = ({
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
data-color-trigger
|
||||||
|
onClick={(e) => openColorMenu(col.id, e.currentTarget)}
|
||||||
|
className="w-7 h-7 flex items-center justify-center rounded-btn border border-border bg-panel hover:border-strong flex-shrink-0"
|
||||||
|
title="Farbe der Spalte"
|
||||||
|
aria-label={`Farbe der Spalte ${col.name}`}
|
||||||
|
aria-haspopup="menu"
|
||||||
|
aria-expanded={colorMenu?.id === col.id}
|
||||||
|
>
|
||||||
|
<span className={`w-3 h-3 rounded-full ${columnDotClass(col)}`} />
|
||||||
|
</button>
|
||||||
|
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={col.name}
|
value={col.name}
|
||||||
@@ -232,6 +302,7 @@ const ManageColumnsModalContent: React.FC<ManageColumnsModalProps> = ({
|
|||||||
<Trash2 className="w-3.5 h-3.5" />
|
<Trash2 className="w-3.5 h-3.5" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -303,6 +374,42 @@ const ManageColumnsModalContent: React.FC<ManageColumnsModalProps> = ({
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{colorMenu &&
|
||||||
|
(() => {
|
||||||
|
const col = columns.find((c) => c.id === colorMenu.id);
|
||||||
|
if (!col) return null;
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
ref={colorMenuRef}
|
||||||
|
role="menu"
|
||||||
|
aria-label={`Farbe der Spalte ${col.name}`}
|
||||||
|
style={{ left: colorMenu.left, top: colorMenu.top, bottom: colorMenu.bottom }}
|
||||||
|
className="fixed z-[60] w-44 p-1 bg-panel border border-border rounded-card shadow-panel"
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
>
|
||||||
|
{COLUMN_COLORS.map((c) => {
|
||||||
|
const active = columnColorId(col) === c.id;
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
key={c.id}
|
||||||
|
type="button"
|
||||||
|
role="menuitemradio"
|
||||||
|
aria-checked={active}
|
||||||
|
onClick={() => handleColor(col.id, c.id)}
|
||||||
|
className={`w-full h-11 sm:h-8 px-2 rounded-btn text-xs flex items-center gap-2 text-left transition-colors ${
|
||||||
|
active ? 'bg-accent/10 text-main' : 'text-muted hover:text-main hover:bg-panel-hover'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<span className={`w-3 h-3 rounded-full flex-shrink-0 ${c.cls}`} />
|
||||||
|
<span className="flex-1">{c.label}</span>
|
||||||
|
{active && <Check className="w-3 h-3 text-accent stroke-[3]" />}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})()}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -20,6 +20,7 @@ import {
|
|||||||
import { Thought, TaskList, TaskListMember, KanbanColumn, User, Recurrence } from '../../lib/types';
|
import { Thought, TaskList, TaskListMember, KanbanColumn, User, Recurrence } from '../../lib/types';
|
||||||
import { UserAvatar } from '../../components/UserAvatar';
|
import { UserAvatar } from '../../components/UserAvatar';
|
||||||
import { OriginBadge, originDetails } from '../../components/OriginBadge';
|
import { OriginBadge, originDetails } from '../../components/OriginBadge';
|
||||||
|
import { columnDotClass } from './ManageColumnsModal';
|
||||||
import { UserAutocomplete } from '../UserAutocomplete';
|
import { UserAutocomplete } from '../UserAutocomplete';
|
||||||
import { api, triggerHaptic } from '../../lib/api';
|
import { api, triggerHaptic } from '../../lib/api';
|
||||||
import { MarkdownViewer } from '../../lib/markdown';
|
import { MarkdownViewer } from '../../lib/markdown';
|
||||||
@@ -409,17 +410,7 @@ const TaskDetailModalContent: React.FC<TaskDetailModalProps & { task: Thought }>
|
|||||||
: 'bg-canvas border-border text-muted hover:text-main hover:border-strong'
|
: 'bg-canvas border-border text-muted hover:text-main hover:border-strong'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<span
|
<span className={`w-2 h-2 rounded-full ${columnDotClass(col)}`} />
|
||||||
className={`w-2 h-2 rounded-full ${
|
|
||||||
col.isDone
|
|
||||||
? 'bg-status-ok'
|
|
||||||
: col.id === 'in_progress'
|
|
||||||
? 'bg-accent'
|
|
||||||
: col.id === 'waiting'
|
|
||||||
? 'bg-status-warn'
|
|
||||||
: 'bg-faint'
|
|
||||||
}`}
|
|
||||||
/>
|
|
||||||
<span>{col.name}</span>
|
<span>{col.name}</span>
|
||||||
{isActive && <Check className="w-3 h-3 text-accent stroke-[3] ml-0.5" />}
|
{isActive && <Check className="w-3 h-3 text-accent stroke-[3] ml-0.5" />}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import { describe, it, expect } from 'vitest';
|
||||||
|
import { columnDotClass } from '../ManageColumnsModal';
|
||||||
|
|
||||||
|
describe('columnDotClass', () => {
|
||||||
|
it('uses the chosen colour, also the names of the server defaults', () => {
|
||||||
|
expect(columnDotClass({ id: 'x', name: 'Review', color: 'purple' })).toBe('bg-tone-purple');
|
||||||
|
expect(columnDotClass({ id: 'x', name: 'Blockiert', color: 'red' })).toBe('bg-status-err');
|
||||||
|
expect(columnDotClass({ id: 'waiting', name: 'Wartet', color: 'amber' })).toBe('bg-status-warn');
|
||||||
|
expect(columnDotClass({ id: 'done', name: 'Erledigt', color: 'emerald', isDone: true })).toBe('bg-status-ok');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('keeps the colour of the role for columns without one', () => {
|
||||||
|
expect(columnDotClass({ id: 'in_progress', name: 'In Arbeit' })).toBe('bg-accent');
|
||||||
|
expect(columnDotClass({ id: 'x', name: 'Fertig', isDone: true })).toBe('bg-status-ok');
|
||||||
|
expect(columnDotClass({ id: 'x', name: 'Neu' })).toBe('bg-faint');
|
||||||
|
expect(columnDotClass(undefined)).toBe('bg-faint');
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -431,6 +431,17 @@ body {
|
|||||||
--code-variable: #9cdcfe;
|
--code-variable: #9cdcfe;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Extra tones (Kanban column colours). Fallbacks like the code tokens: a theme's own values win. */
|
||||||
|
:where(:root) {
|
||||||
|
--color-info: #60a5fa;
|
||||||
|
--color-purple: #a78bfa;
|
||||||
|
}
|
||||||
|
|
||||||
|
:where(:root:not(.dark)) {
|
||||||
|
--color-info: #2563eb;
|
||||||
|
--color-purple: #7c3aed;
|
||||||
|
}
|
||||||
|
|
||||||
:where(:root:not(.dark)) {
|
:where(:root:not(.dark)) {
|
||||||
--code-keyword: #0000ff;
|
--code-keyword: #0000ff;
|
||||||
--code-string: #a31515;
|
--code-string: #a31515;
|
||||||
|
|||||||
@@ -49,6 +49,10 @@
|
|||||||
--bg-warning: rgba(245, 158, 11, 0.12);
|
--bg-warning: rgba(245, 158, 11, 0.12);
|
||||||
--border-warning: rgba(245, 158, 11, 0.38);
|
--border-warning: rgba(245, 158, 11, 0.38);
|
||||||
|
|
||||||
|
/* Optional: Blau und Lila für Kanban-Spalten (ohne Angabe gelten Standardwerte) */
|
||||||
|
--color-info: #60a5fa;
|
||||||
|
--color-purple: #a78bfa;
|
||||||
|
|
||||||
/* --- 6. RUNDUNGEN (Geometrie) --- */
|
/* --- 6. RUNDUNGEN (Geometrie) --- */
|
||||||
--radius-card: 0px; /* Karten, Panels, Container (0px = eckig) */
|
--radius-card: 0px; /* Karten, Panels, Container (0px = eckig) */
|
||||||
--radius-btn: 2px; /* Buttons & Badges */
|
--radius-btn: 2px; /* Buttons & Badges */
|
||||||
|
|||||||
@@ -59,6 +59,11 @@ export default {
|
|||||||
info: v('--accent-hover'),
|
info: v('--accent-hover'),
|
||||||
danger: v('--color-danger'),
|
danger: v('--color-danger'),
|
||||||
},
|
},
|
||||||
|
// Extra tones for Kanban columns; fallbacks in index.css, themes may set their own
|
||||||
|
tone: {
|
||||||
|
blue: v('--color-info'),
|
||||||
|
purple: v('--color-purple'),
|
||||||
|
},
|
||||||
border: v('--border-color'),
|
border: v('--border-color'),
|
||||||
// Aliases for names used in components (e.g. avatar initials), mapped to theme colours
|
// Aliases for names used in components (e.g. avatar initials), mapped to theme colours
|
||||||
primary: v('--accent-primary'),
|
primary: v('--accent-primary'),
|
||||||
|
|||||||
Reference in new issue
Block a user