fix: shift range from a pinned note in its folder; kanban fits the window; list toolbar; quick dump targets only select; aligned bullets; steady search field; browser voice waits 5 s
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 2m28s
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 2m28s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
5a9c5bac37
commit
e2b9daba75
26 files changed
+208
-138
No files matched your search
@@ -428,6 +428,35 @@ export const TasksView: React.FC<TasksViewProps> = ({
|
||||
[ownColumnOf, boardIdOf]
|
||||
);
|
||||
|
||||
// Desktop Kanban in one row: the columns fill the screen height and scroll on their own, so the page
|
||||
// itself does not scroll as well (like the workspace). Several rows of columns keep the page scroll.
|
||||
const kanbanBoardRef = useRef<HTMLDivElement>(null);
|
||||
const [kanbanColHeight, setKanbanColHeight] = useState<number | null>(null);
|
||||
const kanbanOneRow = viewMode === 'kanban' && (isKanbanWide || boardColumns.length <= 4);
|
||||
useEffect(() => {
|
||||
if (!kanbanOneRow) {
|
||||
setKanbanColHeight(null);
|
||||
return;
|
||||
}
|
||||
const fit = () => {
|
||||
const el = kanbanBoardRef.current;
|
||||
if (!el || window.innerWidth < 1024) {
|
||||
setKanbanColHeight(null);
|
||||
return;
|
||||
}
|
||||
// From the board's top to the window's bottom; then take back what still overflows (footer, paddings)
|
||||
const top = el.getBoundingClientRect().top + window.scrollY;
|
||||
setKanbanColHeight(Math.max(320, window.innerHeight - top - 16));
|
||||
requestAnimationFrame(() => {
|
||||
const over = document.documentElement.scrollHeight - window.innerHeight;
|
||||
if (over > 0) setKanbanColHeight((h) => (h ? Math.max(320, h - over) : h));
|
||||
});
|
||||
};
|
||||
fit();
|
||||
window.addEventListener('resize', fit);
|
||||
return () => window.removeEventListener('resize', fit);
|
||||
}, [kanbanOneRow, selectedListId, loading]);
|
||||
|
||||
const safeTasks = useMemo(() => {
|
||||
if (!Array.isArray(tasks)) return [];
|
||||
const seen = new Set<string>();
|
||||
@@ -1976,9 +2005,9 @@ export const TasksView: React.FC<TasksViewProps> = ({
|
||||
})}
|
||||
</div>
|
||||
|
||||
{/* Group By & Sort By Dropdowns & Manage Columns button */}
|
||||
{/* Kanban: manage columns and width (the list view has grouping and sorting above the list) */}
|
||||
<div className="flex items-center gap-2 flex-shrink-0 ml-auto">
|
||||
{viewMode === 'kanban' ? (
|
||||
{viewMode === 'kanban' && (
|
||||
<>
|
||||
{!isSelectedListReadOnly && (
|
||||
<button
|
||||
@@ -2004,40 +2033,6 @@ export const TasksView: React.FC<TasksViewProps> = ({
|
||||
)}
|
||||
</button>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
{/* Group By */}
|
||||
<div className="flex items-center gap-1 text-xs text-muted">
|
||||
<span className="text-[11px]">Gruppe:</span>
|
||||
<select
|
||||
value={groupBy}
|
||||
onChange={(e) => setGroupBy(e.target.value as any)}
|
||||
className="h-7 px-2 input-field text-xs bg-panel"
|
||||
>
|
||||
<option value="none">Keine</option>
|
||||
<option value="tag">Nach Hashtag</option>
|
||||
<option value="status">Nach Status</option>
|
||||
<option value="priority">Nach Priorität</option>
|
||||
<option value="due">Nach Fälligkeit</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{/* Sort By */}
|
||||
<div className="flex items-center gap-1 text-xs text-muted">
|
||||
<span className="text-[11px]">Sortierung:</span>
|
||||
<select
|
||||
value={sortBy}
|
||||
onChange={(e) => setSortBy(e.target.value as any)}
|
||||
className="h-7 px-2 input-field text-xs bg-panel"
|
||||
>
|
||||
<option value="custom">Manuell (Drag & Drop)</option>
|
||||
<option value="created">Erstelldatum</option>
|
||||
<option value="due">Fälligkeitsdatum</option>
|
||||
<option value="priority">Priorität</option>
|
||||
<option value="alpha">Titel (A-Z)</option>
|
||||
</select>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
@@ -2464,6 +2459,7 @@ export const TasksView: React.FC<TasksViewProps> = ({
|
||||
<div className="space-y-2">
|
||||
{kanbanTouch.ghost}
|
||||
<div
|
||||
ref={kanbanBoardRef}
|
||||
className={`grid grid-cols-1 md:grid-cols-2 gap-3 pt-1 lg:w-[var(--kanban-w)] lg:ml-[var(--kanban-ml)] transition-[width,margin] duration-300 ease-out ${
|
||||
isKanbanWide ? 'lg:grid-cols-[repeat(var(--kanban-cols),minmax(0,1fr))]' : 'lg:grid-cols-4'
|
||||
}`}
|
||||
@@ -2486,9 +2482,10 @@ export const TasksView: React.FC<TasksViewProps> = ({
|
||||
onDragLeave={(e) => handleKanbanColDragLeave(e, col.id)}
|
||||
onDrop={(e) => handleKanbanDrop(e, col.id, colTasks)}
|
||||
data-kanban-col={col.id}
|
||||
className={`bg-canvas border border-border flex flex-col min-h-[420px] overflow-hidden transition-all ${
|
||||
dragOverKanbanCol === col.id ? 'ring-2 ring-accent/60 bg-accent/5' : ''
|
||||
} ${blockedForDrag ? 'opacity-40' : ''}`}
|
||||
style={kanbanColHeight ? { height: kanbanColHeight } : undefined}
|
||||
className={`bg-canvas border border-border flex flex-col overflow-hidden transition-all ${
|
||||
kanbanColHeight ? '' : 'min-h-[420px]'
|
||||
} ${dragOverKanbanCol === col.id ? 'ring-2 ring-accent/60 bg-accent/5' : ''} ${blockedForDrag ? 'opacity-40' : ''}`}
|
||||
title={blockedForDrag ? 'Diese Spalte gibt es in der Liste der Aufgabe nicht' : undefined}
|
||||
>
|
||||
{/* Column Header */}
|
||||
@@ -2501,7 +2498,7 @@ export const TasksView: React.FC<TasksViewProps> = ({
|
||||
</div>
|
||||
|
||||
{/* Tasks List in Column */}
|
||||
<div className="p-2 space-y-2 flex-1 overflow-y-auto max-h-[70vh]">
|
||||
<div className={`p-2 space-y-2 flex-1 min-h-0 overflow-y-auto ${kanbanColHeight ? '' : 'max-h-[70vh]'}`}>
|
||||
{colTasks.map((task) => {
|
||||
const isDragging = draggingTaskId === task.id;
|
||||
const readOnly = isTaskReadOnly(task);
|
||||
@@ -2748,16 +2745,56 @@ export const TasksView: React.FC<TasksViewProps> = ({
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Grouping and sorting right above the list, then clearing as a plain trash icon (desktop; phones
|
||||
use the menu) */}
|
||||
<div className="hidden sm:flex items-center gap-2 ml-auto">
|
||||
<div className="flex items-center gap-1 text-xs text-muted">
|
||||
<span className="text-[11px]">Gruppe:</span>
|
||||
<select
|
||||
value={groupBy}
|
||||
onChange={(e) => setGroupBy(e.target.value as any)}
|
||||
className="h-8 px-2 input-field text-xs bg-panel"
|
||||
>
|
||||
<option value="none">Keine</option>
|
||||
<option value="tag">Nach Hashtag</option>
|
||||
<option value="status">Nach Status</option>
|
||||
<option value="priority">Nach Priorität</option>
|
||||
<option value="due">Nach Fälligkeit</option>
|
||||
</select>
|
||||
</div>
|
||||
<div className="flex items-center gap-1 text-xs text-muted">
|
||||
<span className="text-[11px]">Sortierung:</span>
|
||||
<select
|
||||
value={sortBy}
|
||||
onChange={(e) => setSortBy(e.target.value as any)}
|
||||
className="h-8 px-2 input-field text-xs bg-panel"
|
||||
>
|
||||
<option value="custom">Manuell (Drag & Drop)</option>
|
||||
<option value="created">Erstelldatum</option>
|
||||
<option value="due">Fälligkeitsdatum</option>
|
||||
<option value="priority">Priorität</option>
|
||||
<option value="alpha">Titel (A-Z)</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
{filter === 'discarded' && taskTrash.some((t) => t.canEdit) && (
|
||||
<button onClick={handleEmptyTaskTrash} className="btn-action min-h-[38px] sm:min-h-0 ml-auto">
|
||||
<button
|
||||
onClick={handleEmptyTaskTrash}
|
||||
className="btn-action h-8 w-8 p-0 justify-center min-h-[38px] min-w-[38px] sm:min-h-0 sm:min-w-0 ml-auto sm:ml-0"
|
||||
title="Papierkorb leeren"
|
||||
aria-label="Papierkorb leeren"
|
||||
>
|
||||
<Trash2 className="w-3.5 h-3.5" />
|
||||
Papierkorb leeren
|
||||
</button>
|
||||
)}
|
||||
{filter === 'done' && doneTasks.length > 0 && !isSelectedListReadOnly && (
|
||||
<button onClick={handleClearDone} className="btn-action min-h-[38px] sm:min-h-0 ml-auto">
|
||||
<button
|
||||
onClick={handleClearDone}
|
||||
className="btn-action h-8 w-8 p-0 justify-center min-h-[38px] min-w-[38px] sm:min-h-0 sm:min-w-0 ml-auto sm:ml-0"
|
||||
title="Erledigte in den Papierkorb"
|
||||
aria-label="Erledigte in den Papierkorb"
|
||||
>
|
||||
<Trash2 className="w-3.5 h-3.5" />
|
||||
Erledigte leeren
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
Reference in new issue
Block a user