fix: mobile layout, task clicks, list tab corners, hashtags in URLs
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 2m25s
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 2m25s
Mobile
- Plus button in triage did nothing: the stored action returned the
capture function instead of calling it
- Workspace: tabs share the full width; save status sits as an icon next
to the note title instead of wrapping into its own row; "Neue Notiz" /
"Neuer Ordner" only in the top menu (and +), not repeated in the list
- Outliner fields measured while hidden stayed one line high (cut-off
callout); they are measured again when their width changes
- Task dialog: scrolls in a div instead of a fieldset, height in dvh,
notes editor grows with its text, so long notes are fully reachable
Desktop
- Delete button of the selected task/shopping list sits inside the tab as
its own rounded button (matches rounded themes like Soft)
Both
- Tasks sometimes did not open: every reload after an action replaced the
list with a loading placeholder, swallowing clicks; reloads are now
silent after the first load of a list, stale responses are dropped
- Hashtags need whitespace or "(" before "#", so URL anchors are no
longer tags (server, browser); one-time cleanup of stored anchor tags
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
41b02b9564
commit
f82ab2fd32
16 files changed
+283
-47
No files matched your search
@@ -14,11 +14,15 @@ import {
|
||||
Sparkles,
|
||||
Plus,
|
||||
Search,
|
||||
Check,
|
||||
CloudOff,
|
||||
AlertTriangle,
|
||||
} from 'lucide-react';
|
||||
import { Document, DocumentBlock, User } from '../../lib/types';
|
||||
import { api } from '../../lib/api';
|
||||
import { DocSyncEngine, EngineState } from '../../lib/docSync/engine';
|
||||
import { OutlinerTree, OutlinerTreeHandle } from './OutlinerTree';
|
||||
import { extractHashtags } from '../../lib/hashtags';
|
||||
|
||||
export type DocSaveStatus = 'saved' | 'saving' | 'unsaved' | 'error';
|
||||
|
||||
@@ -314,12 +318,7 @@ export const DocumentEditor: React.FC<DocumentEditorProps> = ({
|
||||
const extractedTags = useMemo(() => {
|
||||
const tagSet = new Set<string>();
|
||||
for (const b of blocks) {
|
||||
const matches = b.content?.match(/#([a-zA-Z0-9äöüÄÖÜß_-]+)/g);
|
||||
if (matches) {
|
||||
for (const m of matches) {
|
||||
tagSet.add(m.slice(1).toLowerCase());
|
||||
}
|
||||
}
|
||||
for (const tag of extractHashtags(b.content)) tagSet.add(tag);
|
||||
}
|
||||
return Array.from(tagSet);
|
||||
}, [blocks]);
|
||||
@@ -755,6 +754,33 @@ export const DocumentEditor: React.FC<DocumentEditorProps> = ({
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Save status on phones (the desktop shows it above the workspace) */}
|
||||
{engineState && (
|
||||
<span
|
||||
className="sm:hidden inline-flex items-center shrink-0"
|
||||
title={
|
||||
engineState.status === 'saving'
|
||||
? 'Speichert…'
|
||||
: engineState.status === 'offline'
|
||||
? 'Offline – Änderungen sind lokal gesichert'
|
||||
: engineState.status === 'error'
|
||||
? engineState.errorMessage || 'Fehler beim Speichern'
|
||||
: 'Gespeichert'
|
||||
}
|
||||
aria-label="Speicherstatus"
|
||||
>
|
||||
{engineState.status === 'saving' ? (
|
||||
<Loader2 className="w-3.5 h-3.5 animate-spin text-accent" />
|
||||
) : engineState.status === 'offline' ? (
|
||||
<CloudOff className="w-3.5 h-3.5 text-status-warn" />
|
||||
) : engineState.status === 'error' ? (
|
||||
<AlertTriangle className="w-3.5 h-3.5 text-status-err" />
|
||||
) : (
|
||||
<Check className="w-3.5 h-3.5 text-status-ok" />
|
||||
)}
|
||||
</span>
|
||||
)}
|
||||
|
||||
{/* Read-only indicator */}
|
||||
{!canEdit && (
|
||||
<span className="inline-flex items-center gap-1 px-2 py-0.5 rounded-btn text-[11px] font-medium bg-status-warn/10 text-status-warn border border-status-warn/30">
|
||||
|
||||
@@ -288,8 +288,25 @@ export const OutlinerBlock: React.FC<OutlinerBlockProps> = ({
|
||||
|
||||
useEffect(() => {
|
||||
window.addEventListener('resize', adjustHeight);
|
||||
return () => window.removeEventListener('resize', adjustHeight);
|
||||
}, [adjustHeight]);
|
||||
// The field can be measured while hidden (phone layout shows the editor only after selecting a note);
|
||||
// measure again whenever its width changes
|
||||
const el = textareaRef.current;
|
||||
let lastWidth = el?.clientWidth ?? 0;
|
||||
const observer =
|
||||
el && typeof ResizeObserver !== 'undefined'
|
||||
? new ResizeObserver(() => {
|
||||
if (el.clientWidth !== lastWidth) {
|
||||
lastWidth = el.clientWidth;
|
||||
adjustHeight();
|
||||
}
|
||||
})
|
||||
: null;
|
||||
if (el) observer?.observe(el);
|
||||
return () => {
|
||||
window.removeEventListener('resize', adjustHeight);
|
||||
observer?.disconnect();
|
||||
};
|
||||
}, [adjustHeight, block.type]);
|
||||
|
||||
const filteredSlashCommands = useMemo(() => {
|
||||
if (slashSearch === null) return [];
|
||||
|
||||
@@ -52,6 +52,7 @@ import { UnifiedShareModal } from '../../components/UnifiedShareModal';
|
||||
import { sendDocOps } from '../../lib/docSync/oneoff';
|
||||
import { useMobileActions } from '../../context/MobileActionContext';
|
||||
import { DocumentEditor, DocSaveStatus } from './DocumentEditor';
|
||||
import { extractHashtags } from '../../lib/hashtags';
|
||||
|
||||
interface WorkspaceViewProps {
|
||||
currentUser?: User | null;
|
||||
@@ -289,12 +290,8 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
||||
const tagMap = new Map<string, number>();
|
||||
for (const d of docs) {
|
||||
for (const b of d.blocks || []) {
|
||||
const matches = b.content?.match(/#([a-zA-Z0-9äöüÄÖÜß_-]+)/g);
|
||||
if (matches) {
|
||||
for (const m of matches) {
|
||||
const clean = m.slice(1).toLowerCase();
|
||||
tagMap.set(clean, (tagMap.get(clean) || 0) + 1);
|
||||
}
|
||||
for (const clean of extractHashtags(b.content)) {
|
||||
tagMap.set(clean, (tagMap.get(clean) || 0) + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1117,8 +1114,8 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
||||
<div className="w-full space-y-3">
|
||||
{/* Top Header: Workspace Tabs & Actions */}
|
||||
<div className="flex flex-wrap items-center justify-between gap-2 border-b border-border pb-2">
|
||||
{/* Workspace selector tabs & left sidebar toggle */}
|
||||
<div className="flex flex-wrap items-center gap-1">
|
||||
{/* Workspace selector tabs & left sidebar toggle; on phones the three tabs share the full width */}
|
||||
<div className="grid grid-cols-3 w-full gap-1 sm:flex sm:flex-wrap sm:items-center sm:w-auto">
|
||||
<button
|
||||
type="button"
|
||||
disabled={subView !== 'editor'}
|
||||
@@ -1155,7 +1152,7 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setSubView('editor')}
|
||||
className={`h-8 px-3 text-xs font-medium border rounded-btn transition-colors flex items-center gap-1.5 cursor-pointer ${
|
||||
className={`h-8 px-2 sm:px-3 text-xs font-medium border rounded-btn transition-colors flex items-center justify-center gap-1.5 cursor-pointer min-w-0 ${
|
||||
subView === 'editor'
|
||||
? 'bg-panel border-accent text-main'
|
||||
: 'bg-canvas border-border text-muted hover:text-main'
|
||||
@@ -1172,7 +1169,7 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
||||
setSubView('todos');
|
||||
setMobileShowDetail(true);
|
||||
}}
|
||||
className={`h-8 px-3 text-xs font-medium border rounded-btn transition-colors flex items-center gap-1.5 cursor-pointer ${
|
||||
className={`h-8 px-2 sm:px-3 text-xs font-medium border rounded-btn transition-colors flex items-center justify-center gap-1.5 cursor-pointer min-w-0 ${
|
||||
subView === 'todos'
|
||||
? 'bg-panel border-accent text-main'
|
||||
: 'bg-canvas border-border text-muted hover:text-main'
|
||||
@@ -1189,7 +1186,7 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
||||
setSubView('tags');
|
||||
setMobileShowDetail(true);
|
||||
}}
|
||||
className={`h-8 px-3 text-xs font-medium border rounded-btn transition-colors flex items-center gap-1.5 cursor-pointer ${
|
||||
className={`h-8 px-2 sm:px-3 text-xs font-medium border rounded-btn transition-colors flex items-center justify-center gap-1.5 cursor-pointer min-w-0 ${
|
||||
subView === 'tags' || subView === 'tag_detail'
|
||||
? 'bg-panel border-accent text-main'
|
||||
: 'bg-canvas border-border text-muted hover:text-main'
|
||||
@@ -1201,8 +1198,8 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Right Header Section: Save Status & Live Collaboration Button */}
|
||||
<div className="flex items-center gap-2">
|
||||
{/* Right Header Section: Save Status (on phones it sits compactly in the note's own header) */}
|
||||
<div className="hidden sm:flex items-center gap-2">
|
||||
{subView === 'editor' && selectedDocId ? (
|
||||
/* Save Status Pill */
|
||||
<div
|
||||
@@ -1272,7 +1269,8 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
||||
<h2 className="text-xs font-bold text-main uppercase tracking-wider">
|
||||
Notizen & Outliner
|
||||
</h2>
|
||||
<div className="flex items-center gap-1">
|
||||
{/* On phones these are in the menu at the top (and the + button), so not repeated here */}
|
||||
<div className="hidden sm:flex items-center gap-1">
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => handleOpenNewFolder('', e)}
|
||||
|
||||
Reference in new issue
Block a user