feat(workspace): journal with one note per day and a calendar
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 2m6s

- journal_days (migration 8) maps a person's day to an ordinary note;
  GET /journal lists the days, POST /journal/{date} opens or creates one
- the journal sits on top of the notes sidebar: days below each other
  (today first, older ones load while scrolling), a calendar with month
  and year picker follows the day in view and jumps to any day
- the header shares the day in view; journal notes stay out of the folder
  tree; can be switched off per device under Darstellung

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
sandrodachandClaude Opus 5.5 committed 2026-10-30 19:20:58 +01:00
1 parent 261579f0f2
commit 9008d7c915
19 files changed
+864 -19

No files matched your search

@@ -61,6 +61,8 @@ export interface DocumentEditorProps {
/** A note was created from this one (extract) or removed again (undo); keeps the sidebar current */
onDocCreated?: (doc: Document) => void;
onDocRemoved?: (docId: string) => void;
/** 'journal': one day inside the journal list – no own scrolling, header or backlinks, fixed title */
variant?: 'page' | 'journal';
}
import { EmojiPickerModal } from './EmojiPickerModal';
@@ -211,7 +213,9 @@ export const DocumentEditor: React.FC<DocumentEditorProps> = ({
onRequestMerge,
onDocCreated,
onDocRemoved,
variant = 'page',
}) => {
const inJournal = variant === 'journal';
const [doc, setDoc] = useState<Document | null>(initialDoc || null);
const [title, setTitle] = useState<string>(initialDoc?.title || '');
const [blocks, setBlocks] = useState<DocumentBlock[]>(() =>
@@ -245,7 +249,7 @@ export const DocumentEditor: React.FC<DocumentEditorProps> = ({
useEffect(() => {
const root = scrollRef.current;
const el = titleInputRef.current;
if (!root || !el || typeof IntersectionObserver === 'undefined') {
if (inJournal || !root || !el || typeof IntersectionObserver === 'undefined') {
setTitleScrolledAway(false);
return;
}
@@ -256,7 +260,7 @@ export const DocumentEditor: React.FC<DocumentEditorProps> = ({
);
observer.observe(el);
return () => observer.disconnect();
}, [zoomedBlockId, doc?.id]);
}, [zoomedBlockId, doc?.id, inJournal]);
useEffect(() => {
if (!loading && (autoFocusTitle || (doc?.title === 'Neue Notiz' && (!doc?.blocks || doc?.blocks.length <= 1)))) {
@@ -782,8 +786,13 @@ export const DocumentEditor: React.FC<DocumentEditorProps> = ({
}
return (
<div ref={scrollRef} className="flex-1 flex flex-col h-full min-h-0 bg-editor overflow-y-auto" data-sync-channel={engineState?.channel}>
{/* Top Document Action Bar */}
<div
ref={scrollRef}
className={inJournal ? 'flex flex-col bg-editor' : 'flex-1 flex flex-col h-full min-h-0 bg-editor overflow-y-auto'}
data-sync-channel={engineState?.channel}
>
{/* Top Document Action Bar (the journal has one for all days) */}
{!inJournal && (
<div className="sticky top-0 z-20 flex items-center justify-between gap-2 px-4 sm:px-6 py-2 bg-editor border-b border-border">
<div className="flex flex-1 items-center gap-2 min-w-0">
{onBackMobile && (
@@ -1017,6 +1026,7 @@ export const DocumentEditor: React.FC<DocumentEditorProps> = ({
</div>
</div>
</div>
)}
{/* In the trash: read-only with a way back */}
{inTrash && (
@@ -1047,7 +1057,11 @@ export const DocumentEditor: React.FC<DocumentEditorProps> = ({
{/* Main Content Area */}
{/* Phones: almost no side margin; very deep outlines scroll sideways (rows keep a minimum width) */}
{/* shrink-0: the sideways scroll area must keep its full height, so only the editor scrolls vertically */}
<div className="w-full pl-2 pr-1 sm:px-8 py-5 flex-auto shrink-0 overflow-x-auto sm:overflow-x-visible [-webkit-overflow-scrolling:touch]">
<div
className={`w-full pl-2 pr-1 sm:px-8 flex-auto shrink-0 overflow-x-auto sm:overflow-x-visible [-webkit-overflow-scrolling:touch] ${
inJournal ? 'py-3' : 'py-5'
}`}
>
{zoomedBlock ? (
/* =========================================================================
FOKUS-MODUS & ZOOM-IN (HOISTED SUBTREE VIEW)
@@ -1127,6 +1141,7 @@ export const DocumentEditor: React.FC<DocumentEditorProps> = ({
</div>
) : (
<OutlinerTree
compact={inJournal}
docId={docId}
blocks={childBlocks}
canEdit={canEdit}
@@ -1162,7 +1177,7 @@ export const DocumentEditor: React.FC<DocumentEditorProps> = ({
<div className="relative">
<button
type="button"
onClick={() => canEdit && setShowEmojiPicker((prev) => !prev)}
onClick={() => canEdit && !inJournal && setShowEmojiPicker((prev) => !prev)}
className="text-2xl sm:text-3xl hover:opacity-80 transition-opacity cursor-pointer select-none leading-none bg-transparent border-0 outline-none p-0 focus:outline-none focus:ring-0 shadow-none"
title="Icon ändern"
>
@@ -1187,7 +1202,7 @@ export const DocumentEditor: React.FC<DocumentEditorProps> = ({
}}
rows={1}
value={title}
readOnly={!canEdit}
readOnly={!canEdit || inJournal}
autoFocus={autoFocusTitle}
onChange={(e) => {
handleTitleChange(e);
@@ -1214,6 +1229,7 @@ export const DocumentEditor: React.FC<DocumentEditorProps> = ({
{/* Logseq Outliner Tree */}
<OutlinerTree
compact={inJournal}
ref={outlinerTreeRef}
docId={docId}
blocks={blocks}
@@ -1239,7 +1255,7 @@ export const DocumentEditor: React.FC<DocumentEditorProps> = ({
onRedo={handleRedo}
/>
{onOpenDocBlock && !inTrash && <BacklinksSection docId={docId} title={title} onOpen={onOpenDocBlock} />}
{onOpenDocBlock && !inTrash && !inJournal && <BacklinksSection docId={docId} title={title} onOpen={onOpenDocBlock} />}
</>
)}
</div>