diff --git a/src/pages/Index.tsx b/src/pages/Index.tsx index afcda49..24b84c4 100644 --- a/src/pages/Index.tsx +++ b/src/pages/Index.tsx @@ -157,6 +157,94 @@ const Index = () => { return trimmedLines.join('\n'); }; + // Flexible ISO8601 date parsing (supports YYYY, YYYY-MM, YYYY-MM-DD, full ISO) + const parseFlexibleIsoDateToMs = (input: string): number | null => { + const trimmed = input.trim(); + if (!trimmed) return null; + + if (/^\d{4}$/.test(trimmed)) { + const year = Number(trimmed); + const start = Date.UTC(year, 0, 1, 0, 0, 0, 0); + const end = Date.UTC(year + 1, 0, 1, 0, 0, 0, 0) - 1; + return Math.floor((start + end) / 2); + } + + const ym = trimmed.match(/^(\d{4})-(\d{2})$/); + if (ym) { + const year = Number(ym[1]); + const monthIndex = Number(ym[2]) - 1; // 0-based + if (monthIndex < 0 || monthIndex > 11) return null; + const start = Date.UTC(year, monthIndex, 1, 0, 0, 0, 0); + const nextMonthYear = monthIndex === 11 ? year + 1 : year; + const nextMonthIndex = (monthIndex + 1) % 12; + const end = Date.UTC(nextMonthYear, nextMonthIndex, 1, 0, 0, 0, 0) - 1; + return Math.floor((start + end) / 2); + } + + const ymd = trimmed.match(/^(\d{4})-(\d{2})-(\d{2})$/); + if (ymd) { + const year = Number(ymd[1]); + const monthIndex = Number(ymd[2]) - 1; // 0-based + const day = Number(ymd[3]); + if (monthIndex < 0 || monthIndex > 11) return null; + const start = Date.UTC(year, monthIndex, day, 0, 0, 0, 0); + const end = Date.UTC(year, monthIndex, day + 1, 0, 0, 0, 0) - 1; + return Math.floor((start + end) / 2); + } + + // YYYY-MM-DD HH + const ymdh = trimmed.match(/^(\d{4})-(\d{2})-(\d{2})[ T](\d{2})$/); + if (ymdh) { + const year = Number(ymdh[1]); + const monthIndex = Number(ymdh[2]) - 1; + const day = Number(ymdh[3]); + const hour = Number(ymdh[4]); + if (monthIndex < 0 || monthIndex > 11 || hour < 0 || hour > 23) return null; + const start = Date.UTC(year, monthIndex, day, hour, 0, 0, 0); + const end = Date.UTC(year, monthIndex, day, hour + 1, 0, 0, 0) - 1; + return Math.floor((start + end) / 2); + } + + // YYYY-MM-DD HH:mm + const ymdhm = trimmed.match(/^(\d{4})-(\d{2})-(\d{2})[ T](\d{2}):(\d{2})$/); + if (ymdhm) { + const year = Number(ymdhm[1]); + const monthIndex = Number(ymdhm[2]) - 1; + const day = Number(ymdhm[3]); + const hour = Number(ymdhm[4]); + const minute = Number(ymdhm[5]); + if (monthIndex < 0 || monthIndex > 11 || hour < 0 || hour > 23 || minute < 0 || minute > 59) return null; + const start = Date.UTC(year, monthIndex, day, hour, minute, 0, 0); + const end = Date.UTC(year, monthIndex, day, hour, minute + 1, 0, 0) - 1; + return Math.floor((start + end) / 2); + } + + // YYYY-MM-DD HH:mm:ss + const ymdhms = trimmed.match(/^(\d{4})-(\d{2})-(\d{2})[ T](\d{2}):(\d{2}):(\d{2})$/); + if (ymdhms) { + const year = Number(ymdhms[1]); + const monthIndex = Number(ymdhms[2]) - 1; + const day = Number(ymdhms[3]); + const hour = Number(ymdhms[4]); + const minute = Number(ymdhms[5]); + const second = Number(ymdhms[6]); + if ( + monthIndex < 0 || monthIndex > 11 || + hour < 0 || hour > 23 || + minute < 0 || minute > 59 || + second < 0 || second > 59 + ) return null; + const start = Date.UTC(year, monthIndex, day, hour, minute, second, 0); + const end = Date.UTC(year, monthIndex, day, hour, minute, second + 1, 0) - 1; + return Math.floor((start + end) / 2); + } + + const full = new Date(trimmed); + const ms = full.getTime(); + if (Number.isNaN(ms)) return null; + return ms; + }; + // Create a new note const createNote = async (content: string) => { try { @@ -397,6 +485,53 @@ const Index = () => { setNoteCache(notes); setCurrentNoteIndex(targetIndex); setPreviousNote(notes[targetIndex]); + } else { + // Fallback: fetch a date window around the target note + const targetMs = targetNote.epochTime; + const headers = { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${MEILISEARCH_API_KEY}`, + } as const; + let windowMs = 1000 * 60 * 60 * 24 * 90; // 90 days + for (let i = 0; i < 3; i++) { + const start = targetMs - windowMs; + const end = targetMs + windowMs; + const resp = await fetch(`${MEILISEARCH_ENDPOINT}/indexes/${NOTE_INDEX}/search`, { + method: 'POST', + headers, + body: JSON.stringify({ + q: '', + filter: `date >= ${start} AND date <= ${end}`, + sort: ['date:desc'], + limit: 1000, + }), + }); + if (resp.ok) { + const d = await resp.json(); + const aroundNotes: Note[] = d.hits.map((hit: any) => ({ + id: hit.id, + epochTime: hit.date, + dateISO: hit.dateISO, + content: hit.content, + topLetter: hit.topLetter, + topLetterFrequency: hit.topLetterFrequency, + letterCount: hit.letterCount || 0, + })); + if (aroundNotes.length > 0) { + const exactIdx = aroundNotes.findIndex(n => n.id === targetNote.id); + const pickIdx = exactIdx >= 0 + ? exactIdx + : aroundNotes.reduce((bestIdx, n, idx) => ( + Math.abs(n.epochTime - targetMs) < Math.abs(aroundNotes[bestIdx].epochTime - targetMs) ? idx : bestIdx + ), 0); + setNoteCache(aroundNotes); + setCurrentNoteIndex(pickIdx); + setPreviousNote(aroundNotes[pickIdx]); + return; + } + } + windowMs *= 2; + } } } catch (error) { console.error('Error loading notes around target:', error); @@ -483,9 +618,8 @@ const Index = () => { // Go to closest note by ISO8601 date const goToClosestNoteToDate = async (iso8601: string) => { try { - const target = new Date(iso8601); - const targetMs = target.getTime(); - if (Number.isNaN(targetMs)) { + const targetMs = parseFlexibleIsoDateToMs(iso8601); + if (targetMs === null) { toast({ title: 'Invalid date', description: 'Please enter a valid ISO8601 date.',