Merge dev into main (v0.59.0)
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 30s

This commit is contained in:
sandrodach committed 2026-11-02 17:07:54 +01:00
commit 1ce9d67e00
5 files changed
+49 -13

No files matched your search

+7 -1
View File
@@ -6,6 +6,11 @@ Das Format folgt [Keep a Changelog](https://keepachangelog.com/de/1.1.0/), die V
## [Unreleased] ## [Unreleased]
## [0.59.0] - 2026-11-02
### Added
- Aufgaben: Jede Aufgabenliste merkt sich, ob sie als Liste oder als Kanban-Board angezeigt wird, „Alle Aufgaben“ ebenso (pro Gerät).
## [0.58.0] - 2026-11-02 ## [0.58.0] - 2026-11-02
### Added ### Added
@@ -848,7 +853,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.58.0...dev [Unreleased]: https://git.dsit.me/sandrodach/MindSift/compare/v0.59.0...dev
[0.59.0]: https://git.dsit.me/sandrodach/MindSift/compare/v0.58.0...v0.59.0
[0.58.0]: https://git.dsit.me/sandrodach/MindSift/compare/v0.57.0...v0.58.0 [0.58.0]: https://git.dsit.me/sandrodach/MindSift/compare/v0.57.0...v0.58.0
[0.57.0]: https://git.dsit.me/sandrodach/MindSift/compare/v0.56.0...v0.57.0 [0.57.0]: https://git.dsit.me/sandrodach/MindSift/compare/v0.56.0...v0.57.0
[0.56.0]: https://git.dsit.me/sandrodach/MindSift/compare/v0.55.0...v0.56.0 [0.56.0]: https://git.dsit.me/sandrodach/MindSift/compare/v0.55.0...v0.56.0
+1 -1
View File
@@ -6,7 +6,7 @@
[![Self-Hosted](https://img.shields.io/badge/100%25-Self--Hosted-2563eb?style=flat-square)](#) [![Self-Hosted](https://img.shields.io/badge/100%25-Self--Hosted-2563eb?style=flat-square)](#)
[![Docker](https://img.shields.io/badge/Deployment-Single%20Container-2496ed?style=flat-square&logo=docker&logoColor=white)](#-schnellstart) [![Docker](https://img.shields.io/badge/Deployment-Single%20Container-2496ed?style=flat-square&logo=docker&logoColor=white)](#-schnellstart)
[![SQLite](https://img.shields.io/badge/Daten-SQLite-003b57?style=flat-square&logo=sqlite&logoColor=white)](#-deine-daten-gehören-dir) [![SQLite](https://img.shields.io/badge/Daten-SQLite-003b57?style=flat-square&logo=sqlite&logoColor=white)](#-deine-daten-gehören-dir)
[![Version](https://img.shields.io/badge/Version-v0.58.0-16a34a?style=flat-square)](CHANGELOG.md) [![Version](https://img.shields.io/badge/Version-v0.59.0-16a34a?style=flat-square)](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.
+2 -2
View File
@@ -1,12 +1,12 @@
{ {
"name": "mindsift-frontend", "name": "mindsift-frontend",
"version": "0.58.0", "version": "0.59.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "mindsift-frontend", "name": "mindsift-frontend",
"version": "0.58.0", "version": "0.59.0",
"license": "AGPL-3.0-only", "license": "AGPL-3.0-only",
"dependencies": { "dependencies": {
"@fontsource-variable/inter": "5.3.0", "@fontsource-variable/inter": "5.3.0",
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"name": "mindsift-frontend", "name": "mindsift-frontend",
"private": true, "private": true,
"version": "0.58.0", "version": "0.59.0",
"license": "AGPL-3.0-only", "license": "AGPL-3.0-only",
"type": "module", "type": "module",
"scripts": { "scripts": {
+38 -8
View File
@@ -92,7 +92,15 @@ const kanbanBoardStyle = (wide: boolean, columns: number): React.CSSProperties =
}; };
type GroupBy = 'none' | 'list' | 'tag' | 'status' | 'priority' | 'due'; type GroupBy = 'none' | 'list' | 'tag' | 'status' | 'priority' | 'due';
type TasksSession = { listId: string; viewMode: 'list' | 'kanban'; groupAll: GroupBy; groupList: GroupBy }; type ViewMode = 'list' | 'kanban';
// viewModes: list or Kanban per task list ("all" for Alle Aufgaben); viewMode is the old single value
type TasksSession = {
listId: string;
viewModes: Record<string, ViewMode>;
viewMode?: ViewMode;
groupAll: GroupBy;
groupList: GroupBy;
};
// "Alle Aufgaben" is grouped by task list unless the user picks something else; a single list has no // "Alle Aufgaben" is grouped by task list unless the user picks something else; a single list has no
// grouping by default (grouping it by list would give one group) // grouping by default (grouping it by list would give one group)
@@ -123,11 +131,20 @@ export const TasksView: React.FC<TasksViewProps> = ({
onInitialTaskShown, onInitialTaskShown,
openTasksCount, openTasksCount,
}) => { }) => {
// Open list, view and grouping are remembered on this device (new tab, restart of the installed app) // Open list, view per list and grouping are remembered on this device (new tab, restart of the
// installed app)
const remembered = useMemo(() => readViewMemory<TasksSession>('tasks'), []); const remembered = useMemo(() => readViewMemory<TasksSession>('tasks'), []);
const [viewMode, setViewMode] = useState<'list' | 'kanban'>(() => const [viewModes, setViewModes] = useState<Record<string, ViewMode>>(() => {
remembered.viewMode === 'kanban' ? 'kanban' : 'list' const out: Record<string, ViewMode> = {};
); if (remembered.viewModes && typeof remembered.viewModes === 'object') {
for (const [id, mode] of Object.entries(remembered.viewModes)) {
if (mode === 'kanban') out[id] = 'kanban';
}
} else if (remembered.viewMode === 'kanban' && remembered.listId) {
out[remembered.listId] = 'kanban'; // stored before views were kept per list
}
return out;
});
const [groupAll, setGroupAll] = useState<GroupBy>(() => const [groupAll, setGroupAll] = useState<GroupBy>(() =>
remembered.groupAll && remembered.groupAll in GROUP_LABELS ? remembered.groupAll : DEFAULT_GROUP_ALL remembered.groupAll && remembered.groupAll in GROUP_LABELS ? remembered.groupAll : DEFAULT_GROUP_ALL
); );
@@ -153,13 +170,25 @@ export const TasksView: React.FC<TasksViewProps> = ({
// only a convenience // only a convenience
} }
}; };
const isKanbanWide = viewMode === 'kanban' && kanbanWide;
const [filter, setFilter] = useState<'open' | 'done' | 'discarded'>('open'); const [filter, setFilter] = useState<'open' | 'done' | 'discarded'>('open');
const [tasks, setTasks] = useState<Thought[]>([]); const [tasks, setTasks] = useState<Thought[]>([]);
const [taskLists, setTaskLists] = useState<TaskList[]>([]); const [taskLists, setTaskLists] = useState<TaskList[]>([]);
const [selectedListId, setSelectedListId] = useState<string>( const [selectedListId, setSelectedListId] = useState<string>(
() => remembered.listId || 'all' () => remembered.listId || 'all'
); );
const viewMode: ViewMode = viewModes[selectedListId] || 'list';
// Only Kanban is stored; the list view is the default
const setViewMode = useCallback(
(mode: ViewMode) =>
setViewModes((prev) => {
const next = { ...prev };
if (mode === 'kanban') next[selectedListId] = 'kanban';
else delete next[selectedListId];
return next;
}),
[selectedListId]
);
const isKanbanWide = viewMode === 'kanban' && kanbanWide;
const isAllTasks = selectedListId === 'all'; const isAllTasks = selectedListId === 'all';
const groupBy: GroupBy = isAllTasks ? groupAll : groupList; const groupBy: GroupBy = isAllTasks ? groupAll : groupList;
const setGroupBy = (g: GroupBy) => (isAllTasks ? setGroupAll(g) : setGroupList(g)); const setGroupBy = (g: GroupBy) => (isAllTasks ? setGroupAll(g) : setGroupList(g));
@@ -169,8 +198,8 @@ export const TasksView: React.FC<TasksViewProps> = ({
.filter((id) => isAllTasks || id !== 'list') .filter((id) => isAllTasks || id !== 'list')
.map((id) => ({ id, label: GROUP_LABELS[id] })); .map((id) => ({ id, label: GROUP_LABELS[id] }));
useEffect(() => { useEffect(() => {
writeViewMemory<TasksSession>('tasks', { listId: selectedListId, viewMode, groupAll, groupList }); writeViewMemory<TasksSession>('tasks', { listId: selectedListId, viewModes, groupAll, groupList });
}, [selectedListId, viewMode, groupAll, groupList]); }, [selectedListId, viewModes, groupAll, groupList]);
const [teams, setTeams] = useState<Team[]>([]); const [teams, setTeams] = useState<Team[]>([]);
const [collaborators, setCollaborators] = useState<User[]>([]); const [collaborators, setCollaborators] = useState<User[]>([]);
// Task trash of the selected list (or all): deleted tasks and cleared or cleaned-up completed ones // Task trash of the selected list (or all): deleted tasks and cleared or cleaned-up completed ones
@@ -1538,6 +1567,7 @@ export const TasksView: React.FC<TasksViewProps> = ({
selectedTag, selectedTag,
groupBy, groupBy,
defaultGroupBy, defaultGroupBy,
setViewMode,
sortBy, sortBy,
openTasks.length, openTasks.length,
doneTasks.length, doneTasks.length,