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
@@ -84,7 +84,7 @@ export const BacklinksSection: React.FC<BacklinksSectionProps> = ({ docId, title
|
||||
onClick={() => onOpen(l.docId)}
|
||||
className="flex items-center gap-2 min-w-0 max-w-full text-left cursor-pointer group"
|
||||
>
|
||||
<span className="shrink-0">{l.icon || '📝'}</span>
|
||||
<span className="shrink-0">{l.icon || '🗒️'}</span>
|
||||
<span className="text-sm font-semibold text-main truncate group-hover:text-accent">
|
||||
{l.title || 'Unbenannte Notiz'}
|
||||
</span>
|
||||
|
||||
@@ -1290,7 +1290,7 @@ export const DocumentEditor: React.FC<DocumentEditorProps> = ({
|
||||
{inJournal ? (
|
||||
<CalendarDays className="w-6 h-6 sm:w-7 sm:h-7 text-muted" strokeWidth={1.75} />
|
||||
) : (
|
||||
doc?.icon || '📝'
|
||||
doc?.icon || '🗒️'
|
||||
)}
|
||||
</button>
|
||||
{showEmojiPicker && canEdit && (
|
||||
|
||||
@@ -362,7 +362,7 @@ export const LinkModal: React.FC<LinkModalProps> = ({
|
||||
className="flex items-center justify-between p-2.5 bg-canvas border border-border rounded-card hover:border-accent/60 transition-colors group cursor-pointer"
|
||||
>
|
||||
<div className="flex items-center gap-2.5 min-w-0">
|
||||
<span className="text-base shrink-0">{d.icon || '📝'}</span>
|
||||
<span className="text-base shrink-0">{d.icon || '🗒️'}</span>
|
||||
<div className="min-w-0">
|
||||
<div className="text-xs font-semibold text-main truncate group-hover:text-accent">
|
||||
{d.title || 'Unbenannte Notiz'}
|
||||
|
||||
@@ -93,7 +93,7 @@ export const MergeDialog: React.FC<MergeDialogProps> = ({ docs, sourceId, target
|
||||
onClick={() => setTarget(d.id)}
|
||||
className="w-full min-h-[44px] sm:min-h-0 px-3 py-2 flex items-center gap-2 text-left text-xs text-main hover:bg-panel-elevated"
|
||||
>
|
||||
<span className="shrink-0">{d.icon || '📝'}</span>
|
||||
<span className="shrink-0">{d.icon || '🗒️'}</span>
|
||||
<span className="truncate flex-1">{d.title || 'Unbenannte Notiz'}</span>
|
||||
{d.folderPath && <span className="text-[10px] text-faint truncate max-w-[40%]">{d.folderPath}</span>}
|
||||
</button>
|
||||
|
||||
@@ -1115,7 +1115,8 @@ export const OutlinerBlock: React.FC<OutlinerBlockProps> = ({
|
||||
onClick={() =>
|
||||
flushDebounce(localContent, { collapsed: !block.collapsed })
|
||||
}
|
||||
className="p-0.5 text-muted hover:text-accent transition-[color,opacity] opacity-0 group-hover:opacity-100 focus-visible:opacity-100"
|
||||
// Same width as the spacer of rows without children (the padding is taken back), so bullets stay aligned
|
||||
className="p-0.5 -m-0.5 text-muted hover:text-accent transition-[color,opacity] opacity-0 group-hover:opacity-100 focus-visible:opacity-100"
|
||||
title={block.collapsed ? 'Unterpunkte ausklappen' : 'Unterpunkte einklappen'}
|
||||
>
|
||||
{block.collapsed ? (
|
||||
|
||||
@@ -88,7 +88,7 @@ export const TrashView: React.FC<TrashViewProps> = ({ items, onOpen, onRestore,
|
||||
className="flex items-center gap-2.5 flex-1 min-w-0 text-left cursor-pointer group"
|
||||
title="Schreibgeschützt ansehen"
|
||||
>
|
||||
<span className="text-lg shrink-0">{t.icon || '📝'}</span>
|
||||
<span className="text-lg shrink-0">{t.icon || '🗒️'}</span>
|
||||
<span className="min-w-0">
|
||||
<span className="block text-sm font-semibold text-main truncate group-hover:text-accent">
|
||||
{t.title || 'Unbenannte Notiz'}
|
||||
|
||||
@@ -282,7 +282,7 @@ const FolderTreeItem: React.FC<FolderTreeItemProps> = ({
|
||||
: 'border-transparent text-main hover:bg-panel-elevated'
|
||||
}`}
|
||||
>
|
||||
<span className="shrink-0">{d.icon || '📝'}</span>
|
||||
<span className="shrink-0">{d.icon || '🗒️'}</span>
|
||||
<span className="truncate flex-1">{d.title || 'Unbenannte Notiz'}</span>
|
||||
</div>
|
||||
))}
|
||||
@@ -455,6 +455,8 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
||||
const [draggingDocIds, setDraggingDocIds] = useState<string[]>([]);
|
||||
const [trashDragOver, setTrashDragOver] = useState(false);
|
||||
const lastMarkedRef = useRef<string | null>(null);
|
||||
// The row last clicked: a pinned note has two rows (pinned and in its folder), the range starts at this one
|
||||
const lastMarkedRowRef = useRef<HTMLElement | null>(null);
|
||||
const treeRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
// Merging notes: dialog (picker/confirmation) and a toast with undo afterwards
|
||||
@@ -580,14 +582,14 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
||||
}, [currentUser, reloadTrash]);
|
||||
|
||||
const handleCreateDocument = useCallback(
|
||||
async (folderPath = '', title = 'Neue Notiz', icon = '📝') => {
|
||||
async (folderPath = '', title = 'Neue Notiz', icon = '🗒️') => {
|
||||
if (isCreatingRef.current) return;
|
||||
isCreatingRef.current = true;
|
||||
triggerHaptic(10);
|
||||
try {
|
||||
const created = await api.createDocument({
|
||||
title: title.trim() || 'Neue Notiz',
|
||||
icon: icon.trim() || '📝',
|
||||
icon: icon.trim() || '🗒️',
|
||||
folderPath,
|
||||
blocks: [
|
||||
{
|
||||
@@ -624,7 +626,7 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
||||
// The desktop keeps creating at once.
|
||||
const [newNoteSheet, setNewNoteSheet] = useState<{ folderPath: string } | null>(null);
|
||||
const [newNoteTitle, setNewNoteTitle] = useState('');
|
||||
const [newNoteIcon, setNewNoteIcon] = useState('📝');
|
||||
const [newNoteIcon, setNewNoteIcon] = useState('🗒️');
|
||||
const openNewNote = useCallback(
|
||||
(folderPath = '') => {
|
||||
if (window.innerWidth >= 640) {
|
||||
@@ -632,7 +634,7 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
||||
return;
|
||||
}
|
||||
setNewNoteTitle('');
|
||||
setNewNoteIcon('📝');
|
||||
setNewNoteIcon('🗒️');
|
||||
setNewNoteSheet({ folderPath });
|
||||
},
|
||||
[handleCreateDocument]
|
||||
@@ -794,6 +796,7 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
||||
// Ctrl/Cmd+click toggles a note, Shift+click marks the range in the order shown (from the last marked
|
||||
// or the open note); a plain click clears the marks and opens the note
|
||||
const handleDocRowClick = (e: React.MouseEvent, id: string, open: () => void) => {
|
||||
const row = (e.currentTarget as HTMLElement).closest<HTMLElement>('[data-doc-row]');
|
||||
if (e.metaKey || e.ctrlKey) {
|
||||
setMarkedDocIds((prev) => {
|
||||
const next = new Set(prev);
|
||||
@@ -803,23 +806,32 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
||||
return next;
|
||||
});
|
||||
lastMarkedRef.current = id;
|
||||
lastMarkedRowRef.current = row;
|
||||
return;
|
||||
}
|
||||
if (e.shiftKey) {
|
||||
const order: string[] = [];
|
||||
treeRef.current?.querySelectorAll<HTMLElement>('[data-doc-row]').forEach((el) => {
|
||||
const rowId = el.dataset.docRow;
|
||||
if (rowId && !order.includes(rowId)) order.push(rowId);
|
||||
});
|
||||
const anchor = lastMarkedRef.current || selectedDocId || id;
|
||||
const a = order.indexOf(anchor);
|
||||
const b = order.indexOf(id);
|
||||
const range = a < 0 || b < 0 ? [id] : order.slice(Math.min(a, b), Math.max(a, b) + 1);
|
||||
// Rows in the order shown; a pinned note appears twice, so positions count, not IDs
|
||||
const rows = Array.from(treeRef.current?.querySelectorAll<HTMLElement>('[data-doc-row]') || []);
|
||||
const b = row ? rows.indexOf(row) : rows.findIndex((el) => el.dataset.docRow === id);
|
||||
const anchorId = lastMarkedRef.current || selectedDocId || id;
|
||||
let a = lastMarkedRowRef.current ? rows.indexOf(lastMarkedRowRef.current) : -1;
|
||||
if (a < 0 || rows[a].dataset.docRow !== anchorId) {
|
||||
// The open note without a click here: its row nearest to the clicked one
|
||||
a = -1;
|
||||
rows.forEach((el, i) => {
|
||||
if (el.dataset.docRow === anchorId && (a < 0 || Math.abs(i - b) < Math.abs(a - b))) a = i;
|
||||
});
|
||||
}
|
||||
const range =
|
||||
a < 0 || b < 0
|
||||
? [id]
|
||||
: rows.slice(Math.min(a, b), Math.max(a, b) + 1).map((el) => el.dataset.docRow || '').filter(Boolean);
|
||||
setMarkedDocIds((prev) => new Set([...prev, ...range]));
|
||||
return;
|
||||
}
|
||||
setMarkedDocIds(new Set());
|
||||
lastMarkedRef.current = id;
|
||||
lastMarkedRowRef.current = row;
|
||||
open();
|
||||
};
|
||||
|
||||
@@ -1408,7 +1420,7 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
||||
map.set(d.id, {
|
||||
docId: d.id,
|
||||
title: d.title || 'Unbenannte Notiz',
|
||||
icon: d.icon || '📝',
|
||||
icon: d.icon || '🗒️',
|
||||
folderPath: d.folderPath || '',
|
||||
matchedBlocks: matched,
|
||||
});
|
||||
@@ -1421,7 +1433,7 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
||||
map.set(dg.docId, {
|
||||
docId: dg.docId,
|
||||
title: dg.title || 'Unbenannte Notiz',
|
||||
icon: dg.icon || '📝',
|
||||
icon: dg.icon || '🗒️',
|
||||
folderPath: dg.folderPath || '',
|
||||
matchedBlocks: dg.matchedBlocks || [],
|
||||
});
|
||||
@@ -1978,7 +1990,7 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
||||
: 'border-transparent text-main hover:bg-panel-elevated'
|
||||
}`}
|
||||
>
|
||||
<span className="shrink-0">{d.icon || '📝'}</span>
|
||||
<span className="shrink-0">{d.icon || '🗒️'}</span>
|
||||
<span className="truncate flex-1">{d.title || 'Unbenannte Notiz'}</span>
|
||||
{d.sharedWith && d.sharedWith.length > 0 && (
|
||||
<Users className="w-3 h-3 text-status-ok shrink-0" />
|
||||
@@ -2049,7 +2061,7 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
||||
: 'border-transparent text-main hover:bg-panel-elevated'
|
||||
}`}
|
||||
>
|
||||
<span className="shrink-0">{d.icon || '📝'}</span>
|
||||
<span className="shrink-0">{d.icon || '🗒️'}</span>
|
||||
<span className="truncate flex-1">{d.title || 'Unbenannte Notiz'}</span>
|
||||
{d.sharedWith && d.sharedWith.length > 0 && (
|
||||
<Users className="w-3 h-3 text-status-ok shrink-0" />
|
||||
@@ -2351,7 +2363,7 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
||||
className="inline-flex items-center gap-1.5 text-[11px] px-2 py-0.5 rounded-btn bg-panel-elevated text-main truncate max-w-full"
|
||||
title={meta.docs[0].title}
|
||||
>
|
||||
<span>{meta.docs[0].icon || '📝'}</span>
|
||||
<span>{meta.docs[0].icon || '🗒️'}</span>
|
||||
<span className="truncate">{meta.docs[0].title || 'Unbenannte Notiz'}</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -2452,7 +2464,7 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
||||
onClick={() => handleJumpToDocBlock(docGroup.docId)}
|
||||
className="flex items-center gap-2 text-xs font-semibold text-accent hover:underline cursor-pointer"
|
||||
>
|
||||
<span>{docGroup.icon || '📝'}</span>
|
||||
<span>{docGroup.icon || '🗒️'}</span>
|
||||
<span>{docGroup.title}</span>
|
||||
{docGroup.folderPath && (
|
||||
<span className="badge-neutral text-[10px] font-normal">
|
||||
@@ -2702,7 +2714,7 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
||||
className="inline-flex items-center gap-1 text-[11px] px-2 py-0.5 rounded-btn bg-main/[0.06] text-muted hover:text-main"
|
||||
title={item.doc.title}
|
||||
>
|
||||
<span>{item.doc.icon || '📝'}</span>
|
||||
<span>{item.doc.icon || '🗒️'}</span>
|
||||
<span className="truncate max-w-[120px]">{item.doc.title}</span>
|
||||
{item.doc.folderPath && (
|
||||
<span className="text-faint text-[10px]">({item.doc.folderPath})</span>
|
||||
@@ -2803,7 +2815,7 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
||||
) : (
|
||||
<ChevronDown className="w-3 h-3 text-accent" />
|
||||
)}
|
||||
<span>{doc.icon || '📝'}</span>
|
||||
<span>{doc.icon || '🗒️'}</span>
|
||||
<span>{doc.title}</span>
|
||||
<span className="badge-neutral text-[9px] font-mono">
|
||||
{docOpenCount} offen
|
||||
@@ -2955,7 +2967,7 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
||||
}}
|
||||
>
|
||||
<div className="flex items-center gap-1.5">
|
||||
<EmojiIconButton value={newNoteIcon} onChange={setNewNoteIcon} fallback="📝" />
|
||||
<EmojiIconButton value={newNoteIcon} onChange={setNewNoteIcon} fallback="🗒️" />
|
||||
<input
|
||||
type="text"
|
||||
autoFocus
|
||||
|
||||
Reference in new issue
Block a user