feat(Index.tsx): add flexible ISO8601 date parsing and fallback note fetching logic
This commit is contained in:
@@ -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.',
|
||||
|
Reference in New Issue
Block a user