Merge dev into main (v0.15.1)
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 26s
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 26s
This commit is contained in:
commit
3cd59b6ffa
6 files changed
+95
-51
No files matched your search
+9
-1
@@ -6,6 +6,13 @@ Das Format folgt [Keep a Changelog](https://keepachangelog.com/de/1.1.0/), die V
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [0.15.1] - 2026-10-31
|
||||
|
||||
### Fixed
|
||||
- Triage am Handy: Die Warteschlange lässt sich wieder zuklappen („Warteschlange ausblenden“) und fährt beim Auf- und Zuklappen weich auf bzw. zu.
|
||||
- Triage auf dem Desktop: Notizen, die auf einem anderen Gerät einsortiert oder gelöscht wurden, verschwinden jetzt sofort auch hier (bisher wurde nur der Aufgaben-Tab aktualisiert). Kommt der Tab wieder in den Vordergrund, lädt er außerdem neu, falls Änderungen im Hintergrund verpasst wurden.
|
||||
- Aufgaben: Ein Tipp irgendwo auf die Aufgabe öffnet sie jetzt zuverlässig, nicht nur auf den Text. Bisher schluckten die Zeile mit Datum/Tags und in Kanban die Pfeil-Leiste jeden Tipp, und am Handy konnte ein etwas längerer Tipp als Ziehen gewertet werden (Ziehen gibt es nur noch mit Maus oder Stift).
|
||||
|
||||
## [0.15.0] - 2026-10-31
|
||||
|
||||
### Changed
|
||||
@@ -358,7 +365,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.
|
||||
- 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.15.0...dev
|
||||
[Unreleased]: https://git.dsit.me/sandrodach/MindSift/compare/v0.15.1...dev
|
||||
[0.15.1]: https://git.dsit.me/sandrodach/MindSift/compare/v0.15.0...v0.15.1
|
||||
[0.15.0]: https://git.dsit.me/sandrodach/MindSift/compare/v0.14.3...v0.15.0
|
||||
[0.14.3]: https://git.dsit.me/sandrodach/MindSift/compare/v0.14.2...v0.14.3
|
||||
[0.14.2]: https://git.dsit.me/sandrodach/MindSift/compare/v0.14.1...v0.14.2
|
||||
|
||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "mindsift-frontend",
|
||||
"version": "0.15.0",
|
||||
"version": "0.15.1",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "mindsift-frontend",
|
||||
"version": "0.15.0",
|
||||
"version": "0.15.1",
|
||||
"license": "Unlicense",
|
||||
"dependencies": {
|
||||
"@fontsource-variable/inter": "5.3.0",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "mindsift-frontend",
|
||||
"private": true,
|
||||
"version": "0.15.0",
|
||||
"version": "0.15.1",
|
||||
"license": "Unlicense",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
+18
-2
@@ -628,8 +628,20 @@ export const App: React.FC = () => {
|
||||
}, 5000);
|
||||
}, []);
|
||||
|
||||
// Refresh stats on task/doc/list real-time events (counters in the navigation)
|
||||
// Refresh stats on task/doc/list real-time events (counters in the navigation). Task events also
|
||||
// reload the triage deck: a note sorted on another device must leave this deck too.
|
||||
useEffect(() => {
|
||||
let deckTimer: number | undefined;
|
||||
const reloadDeck = () => {
|
||||
window.clearTimeout(deckTimer);
|
||||
deckTimer = window.setTimeout(() => {
|
||||
api.getTriageDeck().then((res) => setDeck(Array.isArray(res) ? res : [])).catch(() => {});
|
||||
}, 300);
|
||||
};
|
||||
// Events missed while the tab slept (desktop in the background, connection dropped)
|
||||
const onVisible = () => {
|
||||
if (document.visibilityState === 'visible' && getToken()) refreshAll();
|
||||
};
|
||||
const handleWs = (e: any) => {
|
||||
const msg = e.detail;
|
||||
if (!msg) return;
|
||||
@@ -644,13 +656,17 @@ export const App: React.FC = () => {
|
||||
api.getMe().then((res) => {
|
||||
setStats(res.stats);
|
||||
}).catch(() => {});
|
||||
if (msg.event.startsWith('TASK_')) reloadDeck();
|
||||
}
|
||||
};
|
||||
window.addEventListener('mindsift:ws', handleWs);
|
||||
document.addEventListener('visibilitychange', onVisible);
|
||||
return () => {
|
||||
window.clearTimeout(deckTimer);
|
||||
window.removeEventListener('mindsift:ws', handleWs);
|
||||
document.removeEventListener('visibilitychange', onVisible);
|
||||
};
|
||||
}, []);
|
||||
}, [refreshAll]);
|
||||
|
||||
// Close profile dropdown on outside click
|
||||
useEffect(() => {
|
||||
|
||||
@@ -37,6 +37,17 @@ import { CountBadge } from '../../components/CountBadge';
|
||||
import { FormSheet } from '../../components/FormSheet';
|
||||
import { useMobileActions } from '../../context/MobileActionContext';
|
||||
|
||||
// Drag & drop needs a mouse or pen; on touch-only devices a draggable row can swallow a slightly
|
||||
// longer tap, so the row would not open
|
||||
const canDragTasks =
|
||||
typeof window !== 'undefined' && !!window.matchMedia?.('(any-pointer: fine)').matches;
|
||||
|
||||
// Rows inside a task card keep taps on their own controls; taps on empty space or plain labels
|
||||
// (due date, webhook) reach the card and open the task
|
||||
const stopOnControl = (e: React.MouseEvent) => {
|
||||
if ((e.target as HTMLElement).closest('button, select, a, input')) e.stopPropagation();
|
||||
};
|
||||
|
||||
interface TasksViewProps {
|
||||
currentUser?: User | null;
|
||||
onRefreshStats: () => void;
|
||||
@@ -1093,7 +1104,7 @@ export const TasksView: React.FC<TasksViewProps> = ({
|
||||
return (
|
||||
<div
|
||||
key={task.id}
|
||||
draggable={!readOnly}
|
||||
draggable={canDragTasks && !readOnly}
|
||||
onDragStart={(e) => handleTaskDragStart(e, task)}
|
||||
onDragEnd={() => {
|
||||
setDraggingTaskId(null);
|
||||
@@ -1143,13 +1154,7 @@ export const TasksView: React.FC<TasksViewProps> = ({
|
||||
</button>
|
||||
|
||||
{/* Clickable Title & Metadata */}
|
||||
<div
|
||||
onClick={() => {
|
||||
setDetailTaskId(task.id);
|
||||
setShowDetailModal(true);
|
||||
}}
|
||||
className="min-w-0 flex-1 cursor-pointer space-y-1"
|
||||
>
|
||||
<div className="min-w-0 flex-1 cursor-pointer space-y-1">
|
||||
<div className="flex items-center gap-2 flex-wrap">
|
||||
<p
|
||||
className={`text-sm break-words leading-tight ${
|
||||
@@ -1166,7 +1171,7 @@ export const TasksView: React.FC<TasksViewProps> = ({
|
||||
{/* Interactive Badges Bar */}
|
||||
<div
|
||||
className="flex flex-wrap items-center gap-1.5 pt-0.5 empty:hidden"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
onClick={stopOnControl}
|
||||
>
|
||||
{(task.convertedType === 'document_block' || task.docId) && (
|
||||
<button
|
||||
@@ -2163,7 +2168,7 @@ export const TasksView: React.FC<TasksViewProps> = ({
|
||||
return (
|
||||
<div
|
||||
key={task.id}
|
||||
draggable={!readOnly}
|
||||
draggable={canDragTasks && !readOnly}
|
||||
onDragStart={(e) => handleTaskDragStart(e, task)}
|
||||
onDragEnd={() => {
|
||||
setDraggingTaskId(null);
|
||||
@@ -2230,7 +2235,7 @@ export const TasksView: React.FC<TasksViewProps> = ({
|
||||
)}
|
||||
|
||||
{/* Badges: In Notiz, Priority, Status, Tags */}
|
||||
<div className="flex flex-wrap items-center gap-1 pt-0.5" onClick={(e) => e.stopPropagation()}>
|
||||
<div className="flex flex-wrap items-center gap-1 pt-0.5" onClick={stopOnControl}>
|
||||
{(task.convertedType === 'document_block' || task.docId) && (
|
||||
<button
|
||||
type="button"
|
||||
@@ -2315,7 +2320,7 @@ export const TasksView: React.FC<TasksViewProps> = ({
|
||||
{!readOnly && (
|
||||
<div
|
||||
className="flex items-center justify-between pt-1 border-t border-border text-[10px]"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
onClick={stopOnControl}
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
|
||||
@@ -916,41 +916,56 @@ export const TriageDeck: React.FC<TriageDeckProps> = ({
|
||||
{cards.length > 1 && (
|
||||
<div className="pt-2" ref={queueRef}>
|
||||
{/* Phones: the queue stays folded so the card and its buttons fit on the screen */}
|
||||
{!showQueueMobile && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setShowQueueMobile(true);
|
||||
window.setTimeout(() => queueRef.current?.scrollIntoView({ behavior: 'smooth', block: 'start' }), 50);
|
||||
}}
|
||||
className="sm:hidden w-full min-h-[48px] px-3 rounded-xl border border-border bg-panel text-sm font-medium text-main flex items-center justify-center gap-2 touch-manipulation cursor-pointer"
|
||||
>
|
||||
<Layers className="w-4 h-4 text-accent" />
|
||||
<span>Warteschlange anzeigen ({cards.length - 1})</span>
|
||||
<ChevronDown className="w-4 h-4 text-muted" />
|
||||
</button>
|
||||
)}
|
||||
<div className={`${showQueueMobile ? 'flex' : 'hidden sm:flex'} items-center justify-between mb-2 px-1`}>
|
||||
<span className="text-xs font-semibold uppercase tracking-wider text-muted flex items-center gap-1.5">
|
||||
<Layers className="w-3.5 h-3.5 text-accent" />
|
||||
Warteschlange ({cards.length - 1})
|
||||
<button
|
||||
type="button"
|
||||
aria-expanded={showQueueMobile}
|
||||
onClick={() => {
|
||||
const opening = !showQueueMobile;
|
||||
setShowQueueMobile(opening);
|
||||
if (opening) {
|
||||
window.setTimeout(() => queueRef.current?.scrollIntoView({ behavior: 'smooth', block: 'start' }), 320);
|
||||
}
|
||||
}}
|
||||
className="sm:hidden w-full min-h-[48px] px-3 rounded-xl border border-border bg-panel text-sm font-medium text-main flex items-center justify-center gap-2 touch-manipulation cursor-pointer"
|
||||
>
|
||||
<Layers className="w-4 h-4 text-accent" />
|
||||
<span>
|
||||
Warteschlange {showQueueMobile ? 'ausblenden' : 'anzeigen'} ({cards.length - 1})
|
||||
</span>
|
||||
<span className="text-[11px] text-faint">
|
||||
Tippen = Vorholen · 👈 Wegwerfen · 👉 Aufgabe
|
||||
</span>
|
||||
</div>
|
||||
<ChevronDown
|
||||
className={`w-4 h-4 text-muted transition-transform duration-300 ${showQueueMobile ? 'rotate-180' : ''}`}
|
||||
/>
|
||||
</button>
|
||||
{/* Folds open/closed on phones; always open from sm upwards */}
|
||||
<div
|
||||
className={`grid transition-[grid-template-rows,opacity,visibility] duration-300 ease-out sm:grid-rows-[1fr] sm:opacity-100 sm:visible ${
|
||||
showQueueMobile ? 'grid-rows-[1fr] opacity-100 visible' : 'grid-rows-[0fr] opacity-0 invisible'
|
||||
}`}
|
||||
>
|
||||
<div className="overflow-hidden min-h-0">
|
||||
<div className="flex items-center justify-between mb-2 px-1 pt-2 sm:pt-0">
|
||||
<span className="text-xs font-semibold uppercase tracking-wider text-muted flex items-center gap-1.5">
|
||||
<Layers className="w-3.5 h-3.5 text-accent" />
|
||||
Warteschlange ({cards.length - 1})
|
||||
</span>
|
||||
<span className="text-[11px] text-faint">
|
||||
Tippen = Vorholen · 👈 Wegwerfen · 👉 Aufgabe
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className={`${showQueueMobile ? 'block' : 'hidden sm:block'} panel-surface divide-y divide-border max-h-80 overflow-y-auto overflow-x-hidden`}>
|
||||
{cards.slice(1).map((item) => (
|
||||
<SwipeableQueueItem
|
||||
key={item.id}
|
||||
item={item}
|
||||
lists={writableLists}
|
||||
onBringToFront={handleBringToFront}
|
||||
onOpenActionSheet={openActionSheetForCard}
|
||||
onAction={handleAction}
|
||||
/>
|
||||
))}
|
||||
<div className="panel-surface divide-y divide-border max-h-80 overflow-y-auto overflow-x-hidden">
|
||||
{cards.slice(1).map((item) => (
|
||||
<SwipeableQueueItem
|
||||
key={item.id}
|
||||
item={item}
|
||||
lists={writableLists}
|
||||
onBringToFront={handleBringToFront}
|
||||
onOpenActionSheet={openActionSheetForCard}
|
||||
onAction={handleAction}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
Reference in new issue
Block a user