feat(mobile): workspace views in the top menu, taller note rows, equal header bars
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 1m31s
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 1m31s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
66d6b97aff
commit
21dd5e504e
7 files changed
+99
-50
No files matched your search
+8
-1
@@ -6,6 +6,12 @@ Das Format folgt [Keep a Changelog](https://keepachangelog.com/de/1.1.0/), die V
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
## [0.17.2] - 2026-10-31
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Workspace am Handy: Notizen, To-Dos und Hashtags wechselt man jetzt wie bei Aufgaben und Einkaufslisten über das Menü oben (Logo); die Reiterzeile entfällt dort. Ordner und Notizen in der Liste sind so hoch wie das Suchfeld (44 px) und damit leichter zu treffen.
|
||||||
|
- Aufgaben am Handy: Die Kopfleiste ist bei „Alle Aufgaben“ genauso hoch wie bei einzelnen Listen und wie bei den Einkaufslisten.
|
||||||
|
|
||||||
## [0.17.1] - 2026-10-31
|
## [0.17.1] - 2026-10-31
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
@@ -407,7 +413,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.17.1...dev
|
[Unreleased]: https://git.dsit.me/sandrodach/MindSift/compare/v0.17.2...dev
|
||||||
|
[0.17.2]: https://git.dsit.me/sandrodach/MindSift/compare/v0.17.1...v0.17.2
|
||||||
[0.17.1]: https://git.dsit.me/sandrodach/MindSift/compare/v0.17.0...v0.17.1
|
[0.17.1]: https://git.dsit.me/sandrodach/MindSift/compare/v0.17.0...v0.17.1
|
||||||
[0.17.0]: https://git.dsit.me/sandrodach/MindSift/compare/v0.16.1...v0.17.0
|
[0.17.0]: https://git.dsit.me/sandrodach/MindSift/compare/v0.16.1...v0.17.0
|
||||||
[0.16.1]: https://git.dsit.me/sandrodach/MindSift/compare/v0.16.0...v0.16.1
|
[0.16.1]: https://git.dsit.me/sandrodach/MindSift/compare/v0.16.0...v0.16.1
|
||||||
|
|||||||
@@ -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.17.1",
|
"version": "0.17.2",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "mindsift-frontend",
|
"name": "mindsift-frontend",
|
||||||
"version": "0.17.1",
|
"version": "0.17.2",
|
||||||
"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.17.1",
|
"version": "0.17.2",
|
||||||
"license": "Unlicense",
|
"license": "Unlicense",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -1022,7 +1022,8 @@ export const SharedListsView: React.FC<SharedListsViewProps> = ({ onRefreshGloba
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Mobile Compact Header Bar (List switching & actions live in MindSift Logo Menu) */}
|
{/* Mobile Compact Header Bar (List switching & actions live in MindSift Logo Menu) */}
|
||||||
<div className="flex sm:hidden items-center justify-between gap-2 px-3 py-2 rounded-btn bg-panel border border-border !mt-0">
|
{/* Same height with or without the 36 px share button (Tasks, Shopping lists) */}
|
||||||
|
<div className="flex sm:hidden items-center justify-between gap-2 px-3 py-2 min-h-[54px] rounded-btn bg-panel border border-border !mt-0">
|
||||||
<div className="flex items-center gap-2 min-w-0">
|
<div className="flex items-center gap-2 min-w-0">
|
||||||
<span className="text-sm font-semibold text-main truncate">
|
<span className="text-sm font-semibold text-main truncate">
|
||||||
{activeList ? `${activeList.icon} ${activeList.title}` : '🛒 Einkaufsliste'}
|
{activeList ? `${activeList.icon} ${activeList.title}` : '🛒 Einkaufsliste'}
|
||||||
|
|||||||
@@ -1589,7 +1589,8 @@ export const TasksView: React.FC<TasksViewProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Mobile Compact Context Bar (replaces cluttered button rows; options live in MindSift Logo Menu) */}
|
{/* Mobile Compact Context Bar (replaces cluttered button rows; options live in MindSift Logo Menu) */}
|
||||||
<div className="flex sm:hidden items-center justify-between gap-2 px-3 py-2 rounded-btn bg-panel border border-border !mt-0">
|
{/* Same height with or without the 36 px share button (Tasks, Shopping lists) */}
|
||||||
|
<div className="flex sm:hidden items-center justify-between gap-2 px-3 py-2 min-h-[54px] rounded-btn bg-panel border border-border !mt-0">
|
||||||
<div className="flex items-center gap-2 min-w-0">
|
<div className="flex items-center gap-2 min-w-0">
|
||||||
<span className="text-sm font-semibold text-main truncate">
|
<span className="text-sm font-semibold text-main truncate">
|
||||||
{currentSelectedList ? `${currentSelectedList.icon} ${currentSelectedList.title}` : '📋 Alle Aufgaben'}
|
{currentSelectedList ? `${currentSelectedList.icon} ${currentSelectedList.title}` : '📋 Alle Aufgaben'}
|
||||||
|
|||||||
@@ -141,7 +141,7 @@ const FolderTreeItem: React.FC<FolderTreeItemProps> = ({
|
|||||||
}}
|
}}
|
||||||
onDrop={(e) => handleDropOnFolder(node.fullPath, e)}
|
onDrop={(e) => handleDropOnFolder(node.fullPath, e)}
|
||||||
style={{ paddingLeft: `${depth * 12 + 8}px` }}
|
style={{ paddingLeft: `${depth * 12 + 8}px` }}
|
||||||
className={`group flex items-center justify-between pr-1.5 h-7 rounded-btn text-xs cursor-pointer border-l-2 transition-colors ${
|
className={`group flex items-center justify-between pr-1.5 h-11 sm:h-7 rounded-btn text-sm sm:text-xs cursor-pointer border-l-2 transition-colors ${
|
||||||
isDragOver
|
isDragOver
|
||||||
? 'bg-accent/20 border-accent text-main font-semibold'
|
? 'bg-accent/20 border-accent text-main font-semibold'
|
||||||
: 'border-transparent hover:bg-panel-elevated text-muted'
|
: 'border-transparent hover:bg-panel-elevated text-muted'
|
||||||
@@ -262,7 +262,7 @@ const FolderTreeItem: React.FC<FolderTreeItemProps> = ({
|
|||||||
{...docDropProps(d.id)}
|
{...docDropProps(d.id)}
|
||||||
onClick={() => onSelectDoc(d.id)}
|
onClick={() => onSelectDoc(d.id)}
|
||||||
style={{ paddingLeft: `${(depth + 1) * 12 + 16}px` }}
|
style={{ paddingLeft: `${(depth + 1) * 12 + 16}px` }}
|
||||||
className={`w-full flex items-center gap-2 pr-2 py-1.5 rounded-btn text-left text-xs cursor-pointer border transition-colors ${
|
className={`w-full flex items-center gap-2 pr-2 py-1.5 min-h-11 sm:min-h-0 rounded-btn text-left text-sm sm:text-xs cursor-pointer border transition-colors ${
|
||||||
mergeHoverId === d.id
|
mergeHoverId === d.id
|
||||||
? 'border-accent bg-accent/15 ring-2 ring-accent/30 text-main'
|
? 'border-accent bg-accent/15 ring-2 ring-accent/30 text-main'
|
||||||
: selectedDocId === d.id && subView === 'editor'
|
: selectedDocId === d.id && subView === 'editor'
|
||||||
@@ -654,43 +654,6 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
|||||||
setMobileShowDetail(true);
|
setMobileShowDetail(true);
|
||||||
}, [reloadTrash]);
|
}, [reloadTrash]);
|
||||||
|
|
||||||
// Register mobile FAB actions
|
|
||||||
useEffect(() => {
|
|
||||||
setPrimaryAction(() => handleCreateDocument(''));
|
|
||||||
setGroups([
|
|
||||||
{
|
|
||||||
id: 'workspace-actions',
|
|
||||||
title: 'Notizen & Ordner',
|
|
||||||
items: [
|
|
||||||
{
|
|
||||||
id: 'new-note',
|
|
||||||
label: 'Neue Notiz',
|
|
||||||
icon: <FilePlus className="w-4 h-4" />,
|
|
||||||
variant: 'accent',
|
|
||||||
onClick: () => handleCreateDocument(''),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'new-folder',
|
|
||||||
label: 'Neuer Ordner',
|
|
||||||
icon: <FolderPlus className="w-4 h-4" />,
|
|
||||||
onClick: () => setShowNewFolderModal(true),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'trash',
|
|
||||||
label: 'Papierkorb',
|
|
||||||
icon: <Trash2 className="w-4 h-4" />,
|
|
||||||
badge: trash.length,
|
|
||||||
onClick: openTrash,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
return () => {
|
|
||||||
setGroups([]);
|
|
||||||
setPrimaryAction(undefined);
|
|
||||||
};
|
|
||||||
}, [handleCreateDocument, setGroups, setPrimaryAction, trash.length, openTrash]);
|
|
||||||
|
|
||||||
const handleDeleteDocument = useCallback(
|
const handleDeleteDocument = useCallback(
|
||||||
async (docId: string) => {
|
async (docId: string) => {
|
||||||
try {
|
try {
|
||||||
@@ -1176,6 +1139,82 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
|||||||
return count;
|
return count;
|
||||||
}, [docs]);
|
}, [docs]);
|
||||||
|
|
||||||
|
// Register mobile FAB actions and the menu at the top; on phones the menu also switches between
|
||||||
|
// notes, to-dos and hashtags (like the lists in Tasks and Shopping lists), the tab row is hidden there
|
||||||
|
useEffect(() => {
|
||||||
|
setPrimaryAction(() => handleCreateDocument(''));
|
||||||
|
setGroups([
|
||||||
|
{
|
||||||
|
id: 'workspace-views',
|
||||||
|
title: 'Ansicht',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
id: 'view-notes',
|
||||||
|
label: 'Notizen',
|
||||||
|
icon: <FileText className="w-4 h-4" />,
|
||||||
|
active: subView === 'editor' || subView === 'journal' || subView === 'trash',
|
||||||
|
onClick: () => {
|
||||||
|
setSubView('editor');
|
||||||
|
setMobileShowDetail(false);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'view-todos',
|
||||||
|
label: 'To-Dos',
|
||||||
|
icon: <Check className="w-4 h-4" />,
|
||||||
|
badge: totalOpenTodos || undefined,
|
||||||
|
active: subView === 'todos',
|
||||||
|
onClick: () => {
|
||||||
|
setSubView('todos');
|
||||||
|
setMobileShowDetail(true);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'view-tags',
|
||||||
|
label: 'Hashtags',
|
||||||
|
icon: <Hash className="w-4 h-4" />,
|
||||||
|
badge: tagsSummary.length || undefined,
|
||||||
|
active: subView === 'tags' || subView === 'tag_detail',
|
||||||
|
onClick: () => {
|
||||||
|
setSubView('tags');
|
||||||
|
setMobileShowDetail(true);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'workspace-actions',
|
||||||
|
title: 'Notizen & Ordner',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
id: 'new-note',
|
||||||
|
label: 'Neue Notiz',
|
||||||
|
icon: <FilePlus className="w-4 h-4" />,
|
||||||
|
variant: 'accent',
|
||||||
|
onClick: () => handleCreateDocument(''),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'new-folder',
|
||||||
|
label: 'Neuer Ordner',
|
||||||
|
icon: <FolderPlus className="w-4 h-4" />,
|
||||||
|
onClick: () => setShowNewFolderModal(true),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'trash',
|
||||||
|
label: 'Papierkorb',
|
||||||
|
icon: <Trash2 className="w-4 h-4" />,
|
||||||
|
badge: trash.length,
|
||||||
|
onClick: openTrash,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
return () => {
|
||||||
|
setGroups([]);
|
||||||
|
setPrimaryAction(undefined);
|
||||||
|
};
|
||||||
|
}, [handleCreateDocument, setGroups, setPrimaryAction, trash.length, openTrash, subView, totalOpenTodos, tagsSummary.length]);
|
||||||
|
|
||||||
// Real-time matched blocks and all nested children for the selected hashtag
|
// Real-time matched blocks and all nested children for the selected hashtag
|
||||||
const activeTagGroups = useMemo(() => {
|
const activeTagGroups = useMemo(() => {
|
||||||
if (!selectedTag) return [];
|
if (!selectedTag) return [];
|
||||||
@@ -1355,8 +1394,9 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
|||||||
return (
|
return (
|
||||||
<div className="w-full h-full min-h-0 flex flex-col gap-3">
|
<div className="w-full h-full min-h-0 flex flex-col gap-3">
|
||||||
{/* Top Header: Workspace Tabs & Actions */}
|
{/* Top Header: Workspace Tabs & Actions */}
|
||||||
<div className="flex flex-wrap items-center justify-between gap-2 border-b border-border pb-2">
|
{/* Phones: the views are switched in the menu at the top, so the row is hidden there */}
|
||||||
{/* Workspace selector tabs & left sidebar toggle; on phones the three tabs share the full width */}
|
<div className="hidden sm:flex flex-wrap items-center justify-between gap-2 border-b border-border pb-2">
|
||||||
|
{/* Workspace selector tabs & left sidebar toggle */}
|
||||||
<div className="grid grid-cols-3 w-full gap-1 sm:flex sm:flex-wrap sm:items-center sm:w-auto">
|
<div className="grid grid-cols-3 w-full gap-1 sm:flex sm:flex-wrap sm:items-center sm:w-auto">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@@ -1774,7 +1814,7 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
|||||||
setSubView('editor');
|
setSubView('editor');
|
||||||
setMobileShowDetail(true);
|
setMobileShowDetail(true);
|
||||||
}}
|
}}
|
||||||
className={`w-full flex items-center gap-2 px-2 py-1.5 rounded-btn text-left text-xs cursor-pointer border transition-colors ${
|
className={`w-full flex items-center gap-2 px-2 py-1.5 min-h-11 sm:min-h-0 rounded-btn text-left text-sm sm:text-xs cursor-pointer border transition-colors ${
|
||||||
mergeHoverId === d.id
|
mergeHoverId === d.id
|
||||||
? 'border-accent bg-accent/15 ring-2 ring-accent/30 text-main'
|
? 'border-accent bg-accent/15 ring-2 ring-accent/30 text-main'
|
||||||
: selectedDocId === d.id && subView === 'editor'
|
: selectedDocId === d.id && subView === 'editor'
|
||||||
@@ -1837,7 +1877,7 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
|||||||
setSubView('editor');
|
setSubView('editor');
|
||||||
setMobileShowDetail(true);
|
setMobileShowDetail(true);
|
||||||
}}
|
}}
|
||||||
className={`w-full flex items-center gap-2 px-2 py-1.5 rounded-btn text-left text-xs cursor-pointer border transition-colors ${
|
className={`w-full flex items-center gap-2 px-2 py-1.5 min-h-11 sm:min-h-0 rounded-btn text-left text-sm sm:text-xs cursor-pointer border transition-colors ${
|
||||||
mergeHoverId === d.id
|
mergeHoverId === d.id
|
||||||
? 'border-accent bg-accent/15 ring-2 ring-accent/30 text-main'
|
? 'border-accent bg-accent/15 ring-2 ring-accent/30 text-main'
|
||||||
: selectedDocId === d.id && subView === 'editor'
|
: selectedDocId === d.id && subView === 'editor'
|
||||||
|
|||||||
Reference in new issue
Block a user