Full document, spreadsheet, slideshow, and diagram tooling
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at main 204 lines 8.4 kB view raw
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}