Add Insights, Observe-first, compare, handout layouts, richer search, backup nudge
- Commentary and study aids wait for 15 words of your own Observe notes. - Insights page: this week's own work, themes across studies, words you keep studying, what slipped in recalls, open follow-ups. - Recall can record what slipped; session notes (teach it back, who came, group notes, follow-ups); "Pray about this" from Apply. - Compare any two passages side by side. - Handout layouts: standard, questions only, large print, note-taking. - Search understands "in <book>", "last week/month/year" and a year. - Home shows the last backup age and nudges after two weeks. - Phone: the stage bar scrolls instead of clipping Script and Export. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
2ddc4e1ff6
commit
d8e522dd06
24 files changed
+540
-28
No files matched your search
@@ -13,18 +13,55 @@ const snippetAround = (raw, at, len) => {
|
||||
return (start > 0 ? '…' : '') + raw.slice(start, end).replace(/\s+/g, ' ') + (end < raw.length ? '…' : '');
|
||||
};
|
||||
|
||||
const DAY = 86400000;
|
||||
|
||||
// "grace in Romans last year" -> { text: 'grace', book: 'Romans', since, until }. Words it understands:
|
||||
// "in <book>", "last|this week|month|year" (last = the past 7 / 30 / 365 days; this year = since 1 January) and a
|
||||
// four-digit year. Whatever is left is the text to look for.
|
||||
export function parseSearchQuery(query, now = Date.now()) {
|
||||
let text = ` ${query} `;
|
||||
let book = null;
|
||||
let since = null;
|
||||
let until = null;
|
||||
for (const b of [...bookOptions].sort((x, y) => y.name.length - x.name.length)) {
|
||||
const re = new RegExp(`\\s(?:in|from)\\s+${b.name.toLowerCase().replace(/\s+/g, '\\s+')}(?=\\s)`, 'i');
|
||||
if (re.test(text)) { book = b.name; text = text.replace(re, ' '); break; }
|
||||
}
|
||||
const rel = text.match(/\s(last|this)\s+(week|month|year)(?=\s)/i);
|
||||
if (rel) {
|
||||
const [, which, unit] = rel;
|
||||
const span = { week: 7, month: 30, year: 365 }[unit.toLowerCase()];
|
||||
since = which.toLowerCase() === 'last' ? now - span * DAY : (unit.toLowerCase() === 'year' ? new Date(new Date(now).getFullYear(), 0, 1).getTime() : now - span * DAY);
|
||||
text = text.replace(rel[0], ' ');
|
||||
}
|
||||
const year = text.match(/\s(20\d\d)(?=\s)/);
|
||||
if (year) {
|
||||
since = new Date(Number(year[1]), 0, 1).getTime();
|
||||
until = new Date(Number(year[1]) + 1, 0, 1).getTime();
|
||||
text = text.replace(year[0], ' ');
|
||||
}
|
||||
return { text: text.replace(/\s+/g, ' ').trim(), book, since, until };
|
||||
}
|
||||
|
||||
// projects: full project objects. -> [{ projectId, projectTitle, chunkId, ref, field, snippet }], at most
|
||||
// `limit`, one hit per chunk (the first field that matches).
|
||||
export function searchNotes(projects, query, { limit = 20 } = {}) {
|
||||
const q = query.trim().toLowerCase();
|
||||
if (q.length < 3) return [];
|
||||
export function searchNotes(projects, query, { limit = 20, now = Date.now() } = {}) {
|
||||
const parsed = parseSearchQuery(query, now);
|
||||
const q = parsed.text.toLowerCase();
|
||||
const filtered = !!(parsed.book || parsed.since);
|
||||
if (q.length < 3 && !(filtered && q.length === 0)) return [];
|
||||
const hits = [];
|
||||
for (const project of projects) {
|
||||
for (const [chapterIndex, chapter] of (project.chapters ?? []).entries()) {
|
||||
if (parsed.book && chapter.book !== parsed.book) continue;
|
||||
for (const chunk of chapter.chunks ?? []) {
|
||||
const when = chunk.editedAt ?? project.lastEdited ?? 0;
|
||||
if (parsed.since && when < parsed.since) continue;
|
||||
if (parsed.until && when >= parsed.until) continue;
|
||||
for (const [field, label] of NOTE_FIELDS) {
|
||||
const raw = chunk[field] ?? '';
|
||||
const at = raw.toLowerCase().indexOf(q);
|
||||
if (!q && !raw.trim()) continue; // a passage/date search lists chunks that have something written
|
||||
const at = q ? raw.toLowerCase().indexOf(q) : 0;
|
||||
if (at === -1) continue;
|
||||
hits.push({
|
||||
projectId: project.id, projectTitle: project.title, chunkId: chunk.id,
|
||||
|
||||
Reference in new issue
Block a user