Full document, spreadsheet, slideshow, and diagram tooling
1/**
2 * Document creation functions: new doc/sheet/form/slide/diagram,
3 * template-based creation, daily notes, and singleton calendar.
4 *
5 * Extracted from landing.ts for decomposition.
6 *
7 * Phase 1: All data stored locally in IndexedDB via local-store.
8 * No server calls — documents and keys are created client-side.
9 */
10
11import type { DocumentMeta, FolderAssignments } from './landing-types.js';
12import { generateKey, exportKey } from './lib/crypto.js';
13import { storeKey, getLocalKeys } from './lib/key-sync.js';
14import { createDocument as createLocalDoc, type DocType } from './lib/local-store.js';
15import { ensureWrappingKeyForStore } from './lib/key-passphrase.js';
16import { formatDailyNoteName, findDailyNote, getDailyNoteTemplate } from './daily-notes.js';
17import { getTemplate } from './templates.js';
18import { trackRecentDoc } from './landing-utils.js';
19import { moveToFolder } from './landing-utils.js';
20
21// --- Document type routing ---
22const DOC_PATH_MAP: Record<string, string> = { doc: '/docs', sheet: '/sheets', form: '/forms', slide: '/slides', diagram: '/diagrams', calendar: '/calendar' };
23function docPathLocal(type: string): string { return DOC_PATH_MAP[type] || '/sheets'; }
24
25// ── Deps interface ───────────────────────────────────────────
26
27export interface CreateDeps {
28 getCurrentFolderId: () => string | null;
29 getFolderAssignments: () => FolderAssignments;
30 setFolderAssignments: (a: FolderAssignments) => void;
31 getRecentIds: () => string[];
32 setRecentIds: (ids: string[]) => void;
33 getAllDocs: () => DocumentMeta[];
34}
35
36// ── Create document ──────────────────────────────────────────
37
38export async function createDocument(deps: CreateDeps, type: 'doc' | 'sheet' | 'form' | 'slide' | 'diagram' | 'calendar'): Promise<void> {
39 const key = await generateKey();
40 const keyStr = await exportKey(key);
41
42 const nameMap = { doc: 'Untitled Document', sheet: 'Untitled Spreadsheet', form: 'Untitled Form', slide: 'Untitled Presentation', diagram: 'Untitled Diagram', calendar: 'Untitled Calendar' };
43 const defaultName = nameMap[type];
44
45 // Store document and key in IndexedDB (local-first, no server)
46 const doc = await createLocalDoc(type as DocType, defaultName, new ArrayBuffer(0), key);
47 const id = doc.id;
48
49 // Store key in localStorage as secondary store
50 await ensureWrappingKeyForStore();
51 await storeKey(id, keyStr);
52
53 // If we're inside a folder, assign the new doc to it
54 const currentFolderId = deps.getCurrentFolderId();
55 if (currentFolderId) {
56 const updated = moveToFolder(deps.getFolderAssignments(), id, currentFolderId);
57 deps.setFolderAssignments(updated);
58 localStorage.setItem('atmos-folder-assignments', JSON.stringify(updated));
59 }
60
61 const updatedRecent = trackRecentDoc(deps.getRecentIds(), id);
62 deps.setRecentIds(updatedRecent);
63 localStorage.setItem('atmos-recent', JSON.stringify(updatedRecent));
64
65 window.location.href = `${docPathLocal(type)}/${id}#${keyStr}`;
66}
67
68// ── Create from template ─────────────────────────────────────
69
70export async function createFromTemplate(deps: CreateDeps, templateId: string): Promise<void> {
71 const template = getTemplate(templateId);
72 if (!template) return;
73
74 const key = await generateKey();
75 const keyStr = await exportKey(key);
76
77 // Store document and key in IndexedDB (local-first, no server)
78 const doc = await createLocalDoc(template.type as DocType, template.name, new ArrayBuffer(0), key);
79 const id = doc.id;
80
81 // Store key in localStorage as secondary store
82 await ensureWrappingKeyForStore();
83 await storeKey(id, keyStr);
84
85 const currentFolderId = deps.getCurrentFolderId();
86 if (currentFolderId) {
87 const updated = moveToFolder(deps.getFolderAssignments(), id, currentFolderId);
88 deps.setFolderAssignments(updated);
89 localStorage.setItem('atmos-folder-assignments', JSON.stringify(updated));
90 }
91
92 // Store template content for the editor to pick up
93 sessionStorage.setItem(`template-content-${id}`, template.content);
94 sessionStorage.setItem(`template-type-${id}`, template.type);
95
96 const updatedRecent = trackRecentDoc(deps.getRecentIds(), id);
97 deps.setRecentIds(updatedRecent);
98 localStorage.setItem('atmos-recent', JSON.stringify(updatedRecent));
99
100 const path = template.type === 'doc' ? '/docs' : '/sheets';
101 window.location.href = `${path}/${id}#${keyStr}`;
102}
103
104// ── Singleton calendar ───────────────────────────────────────
105
106/**
107 * Open the user's calendar — creates one if none exists.
108 * Calendar is a singleton: each user has exactly one.
109 */
110export async function openCalendar(deps: CreateDeps): Promise<void> {
111 // Fast path: check localStorage for a known calendar doc ID
112 const cachedId = localStorage.getItem('atmos-calendar-id');
113 if (cachedId) {
114 const keys = await getLocalKeys();
115 const keyStr = keys[cachedId];
116 if (keyStr) {
117 // Verify the doc still exists in the loaded list
118 const doc = deps.getAllDocs().find(d => d.id === cachedId && d.type === 'calendar');
119 if (doc) {
120 const updatedRecent = trackRecentDoc(deps.getRecentIds(), cachedId);
121 deps.setRecentIds(updatedRecent);
122 localStorage.setItem('atmos-recent', JSON.stringify(updatedRecent));
123 window.location.href = `/calendar/${cachedId}#${keyStr}`;
124 return;
125 }
126 }
127 }
128
129 // Check all docs for an existing calendar
130 const existing = deps.getAllDocs().find(d => d.type === 'calendar');
131 if (existing) {
132 const keys = await getLocalKeys();
133 const keyStr = keys[existing.id];
134 if (keyStr) {
135 localStorage.setItem('atmos-calendar-id', existing.id);
136 const updatedRecent = trackRecentDoc(deps.getRecentIds(), existing.id);
137 deps.setRecentIds(updatedRecent);
138 localStorage.setItem('atmos-recent', JSON.stringify(updatedRecent));
139 window.location.href = `/calendar/${existing.id}#${keyStr}`;
140 return;
141 }
142 }
143
144 // No calendar exists — create one
145 const key = await generateKey();
146 const keyStr = await exportKey(key);
147
148 // Store document and key in IndexedDB (local-first, no server)
149 const doc = await createLocalDoc('calendar', 'Untitled Calendar', new ArrayBuffer(0), key);
150 const id = doc.id;
151
152 // Store key in localStorage as secondary store
153 await ensureWrappingKeyForStore();
154 await storeKey(id, keyStr);
155
156 localStorage.setItem('atmos-calendar-id', id);
157
158 const updatedRecent = trackRecentDoc(deps.getRecentIds(), id);
159 deps.setRecentIds(updatedRecent);
160 localStorage.setItem('atmos-recent', JSON.stringify(updatedRecent));
161
162 window.location.href = `/calendar/${id}#${keyStr}`;
163}
164
165// ── Daily note ───────────────────────────────────────────────
166
167export async function openDailyNote(deps: CreateDeps): Promise<void> {
168 // Check if today's note already exists
169 const existingId = findDailyNote(deps.getAllDocs());
170 if (existingId) {
171 const keys = await getLocalKeys();
172 const keyStr = keys[existingId];
173 if (keyStr) {
174 const updatedRecent = trackRecentDoc(deps.getRecentIds(), existingId);
175 deps.setRecentIds(updatedRecent);
176 localStorage.setItem('atmos-recent', JSON.stringify(updatedRecent));
177 window.location.href = `/docs/${existingId}#${keyStr}`;
178 return;
179 }
180 }
181
182 // Create a new daily note
183 const key = await generateKey();
184 const keyStr = await exportKey(key);
185 const name = formatDailyNoteName();
186
187 // Store document and key in IndexedDB (local-first, no server)
188 const doc = await createLocalDoc('doc', name, new ArrayBuffer(0), key);
189 const id = doc.id;
190
191 // Store key in localStorage as secondary store
192 await ensureWrappingKeyForStore();
193 await storeKey(id, keyStr);
194
195 // Store template for the editor to pick up
196 const template = getDailyNoteTemplate();
197 sessionStorage.setItem('daily-note-template-' + id, template);
198
199 const updatedRecent = trackRecentDoc(deps.getRecentIds(), id);
200 deps.setRecentIds(updatedRecent);
201 localStorage.setItem('atmos-recent', JSON.stringify(updatedRecent));
202
203 window.location.href = `/docs/${id}#${keyStr}`;
204}