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');
|
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
|
// Create a new note
|
||||||
const createNote = async (content: string) => {
|
const createNote = async (content: string) => {
|
||||||
try {
|
try {
|
||||||
@@ -397,6 +485,53 @@ const Index = () => {
|
|||||||
setNoteCache(notes);
|
setNoteCache(notes);
|
||||||
setCurrentNoteIndex(targetIndex);
|
setCurrentNoteIndex(targetIndex);
|
||||||
setPreviousNote(notes[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) {
|
} catch (error) {
|
||||||
console.error('Error loading notes around target:', error);
|
console.error('Error loading notes around target:', error);
|
||||||
@@ -483,9 +618,8 @@ const Index = () => {
|
|||||||
// Go to closest note by ISO8601 date
|
// Go to closest note by ISO8601 date
|
||||||
const goToClosestNoteToDate = async (iso8601: string) => {
|
const goToClosestNoteToDate = async (iso8601: string) => {
|
||||||
try {
|
try {
|
||||||
const target = new Date(iso8601);
|
const targetMs = parseFlexibleIsoDateToMs(iso8601);
|
||||||
const targetMs = target.getTime();
|
if (targetMs === null) {
|
||||||
if (Number.isNaN(targetMs)) {
|
|
||||||
toast({
|
toast({
|
||||||
title: 'Invalid date',
|
title: 'Invalid date',
|
||||||
description: 'Please enter a valid ISO8601 date.',
|
description: 'Please enter a valid ISO8601 date.',
|
||||||
|
Reference in New Issue
Block a user