142 lines
3.7 KiB
TypeScript
142 lines
3.7 KiB
TypeScript
import { create } from 'zustand';
|
|
import { createNote as persistNewNote, getNote, listNotes, updateNote as persistNote, type MobileNote } from '../api/notes';
|
|
import { enqueueNoteCreate, enqueueNoteUpdate } from '../lib/offline-queue';
|
|
|
|
function createOfflineNoteId(): string {
|
|
const randomUUID = globalThis.crypto && typeof globalThis.crypto.randomUUID === 'function'
|
|
? globalThis.crypto.randomUUID.bind(globalThis.crypto)
|
|
: null;
|
|
|
|
if (randomUUID) {
|
|
return randomUUID();
|
|
}
|
|
|
|
return `offline-${Date.now()}`;
|
|
}
|
|
|
|
export type NotesState = {
|
|
notes: MobileNote[];
|
|
selectedNote: MobileNote | null;
|
|
isLoading: boolean;
|
|
hydrate: () => Promise<void>;
|
|
openNote: (id: string) => Promise<void>;
|
|
saveDraft: (workspaceId: string | null, title: string, body: string) => Promise<boolean>;
|
|
updateNote: (id: string, title: string, body: string) => Promise<void>;
|
|
};
|
|
|
|
export const useNotesStore = create<NotesState>((set, get) => ({
|
|
notes: [],
|
|
selectedNote: null,
|
|
isLoading: false,
|
|
async hydrate() {
|
|
set({ isLoading: true });
|
|
const notes = await listNotes();
|
|
set({ notes, isLoading: false });
|
|
},
|
|
async openNote(id: string) {
|
|
set({ isLoading: true });
|
|
const current = get().notes.find((note: MobileNote) => note.id === id);
|
|
if (!current) {
|
|
set({ selectedNote: null, isLoading: false });
|
|
return;
|
|
}
|
|
|
|
const note = await getNote(id, current.workspaceId);
|
|
set({ selectedNote: note, isLoading: false });
|
|
},
|
|
async saveDraft(workspaceId: string | null, title: string, body: string) {
|
|
if (!workspaceId) {
|
|
return false;
|
|
}
|
|
|
|
set({ isLoading: true });
|
|
try {
|
|
const created = await persistNewNote(workspaceId, title, body);
|
|
set({
|
|
notes: [created, ...get().notes],
|
|
selectedNote: created,
|
|
isLoading: false,
|
|
});
|
|
} catch {
|
|
const queuedNote: MobileNote = {
|
|
id: createOfflineNoteId(),
|
|
workspaceId,
|
|
workspaceName: workspaceId,
|
|
title: title.trim() || 'Untitled draft',
|
|
body,
|
|
status: 'draft',
|
|
updatedAt: new Date().toISOString(),
|
|
};
|
|
|
|
enqueueNoteCreate({
|
|
id: queuedNote.id,
|
|
workspaceId,
|
|
title: queuedNote.title,
|
|
body,
|
|
});
|
|
|
|
set({
|
|
notes: [queuedNote, ...get().notes],
|
|
selectedNote: queuedNote,
|
|
isLoading: false,
|
|
});
|
|
}
|
|
|
|
return true;
|
|
},
|
|
async updateNote(id: string, title: string, body: string) {
|
|
const nextTitle = title.trim() || 'Untitled draft';
|
|
const current = get().notes.find((note: MobileNote) => note.id === id);
|
|
|
|
if (!current) {
|
|
return;
|
|
}
|
|
|
|
if (current.workspaceId === 'drafts') {
|
|
const updated: MobileNote = {
|
|
...current,
|
|
title: nextTitle,
|
|
body,
|
|
updatedAt: new Date().toISOString(),
|
|
};
|
|
|
|
set({
|
|
notes: get().notes.map((note: MobileNote) => (note.id === id ? updated : note)),
|
|
selectedNote: updated,
|
|
});
|
|
return;
|
|
}
|
|
|
|
set({ isLoading: true });
|
|
try {
|
|
const updated = await persistNote(id, current.workspaceId, nextTitle, body);
|
|
|
|
set({
|
|
notes: get().notes.map((note: MobileNote) => (note.id === id ? updated : note)),
|
|
selectedNote: updated,
|
|
isLoading: false,
|
|
});
|
|
} catch {
|
|
const updated: MobileNote = {
|
|
...current,
|
|
title: nextTitle,
|
|
body,
|
|
updatedAt: new Date().toISOString(),
|
|
};
|
|
|
|
enqueueNoteUpdate({
|
|
id,
|
|
workspaceId: current.workspaceId,
|
|
title: nextTitle,
|
|
body,
|
|
});
|
|
|
|
set({
|
|
notes: get().notes.map((note: MobileNote) => (note.id === id ? updated : note)),
|
|
selectedNote: updated,
|
|
isLoading: false,
|
|
});
|
|
}
|
|
},
|
|
}));
|