Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.
import { addDays } from "./dates_add_days.ts"; ← from dates.add-days ^1.0.0 · built alongside by fune
import { dayOfWeek } from "./dates_day_of_week.ts"; ← from dates.day-of-week ^1.0.0 · built alongside by fune
import { type Recurrence } from "./todo_item.ts"; ← from todo.item ^1.0.0 · built alongside by fune
import { parseDatePhrase } from "./todo_parse_date_phrase.ts"; ← from todo.parse-date-phrase ^1.0.0 · built alongside by fune
import { parseQuickAdd } from "./todo_parse_quick_add.ts"; ← from todo.parse-quick-add ^1.0.0 · built alongside by fune
function lowerAscii(text: string): string {
return text.replace(/[A-Z]/g, (c) => c.toLowerCase());
}
/**
* A Repeat box: an "every ..." phrase exactly as todo.parse-quick-add reads
* it, then optionally a date phrase for the start. Anything else, words left
* over included, is null. The series starts on the typed date, else on
* `due`, else where quick-add would start it; for "every <weekday>" and
* "every weekday" the start moves forward to the first day the pattern
* allows. The anchor is that first date.
*/
export function parseRepeat(text: string, due: string | null, today: string): Recurrence | null {
// addDays(x, 0) is the date check: it throws dates.add-days's message.
addDays(today, 0);
if (due !== null) addDays(due, 0);
const words = text.split(/[ \t\n\r\v\f]+/).filter((w) => w !== "");
if (words.length < 2 || lowerAscii(words[0]) !== "every") return null;
// The repeat is 2 words ("every week") or 3 ("every 2 weeks"); no 2-word
// form starts a 3-word one, so the first that parses as a bare repeat wins.
let repeat: Recurrence | null = null;
let length = 0;
for (const n of [2, 3]) {
if (n > words.length) break;
const draft = parseQuickAdd(words.slice(0, n).join(" "), today);
if (draft.recurrence !== null && draft.title === "") {
repeat = draft.recurrence;
length = n;
break;
}
}
if (repeat === null) return null;
let start = due;
if (words.length > length) {
start = parseDatePhrase(words.slice(length).join(" "), today);
if (start === null) return null;
}
if (start === null) return repeat;
if (repeat.frequency === "weekdays") {
const day = dayOfWeek(start);
if (day >= 6) start = addDays(start, 8 - day);
} else if (repeat.frequency === "weekly" && length === 2 && parseDatePhrase(words[1], today) !== null) {
// "every monday": quick-add anchored it on the next Monday, so that
// anchor's weekday is the one the series keeps.
start = addDays(start, (dayOfWeek(repeat.anchor) - dayOfWeek(start) + 7) % 7);
}
return { ...repeat, anchor: start };
}