// Decides what a saved schedule needs after the catalog was read again. // Runs in the browser; kept free of DOM code so the rules are testable in Node. const escape = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); const time = (value) => Date.parse(value || "") || 0; /** "23-БА" matches "23-ЛГ_23-БА.docx" but not "23-БАз" or "123-БА". */ export function namesGroup(fileName, group) { if (!group) return false; const base = fileName.replace(/\.docx?$/i, ""); return new RegExp( `(^|[^0-9A-Za-zА-ЯЁа-яё])${escape(group)}(?![0-9A-Za-zА-ЯЁа-яё])`, "iu", ).test(base); } /** * changed: the same document was republished, or parsed by an older parser than the server's * (`catalog.parserVersion`, from API version 10) — reload it quietly. * newer: another document for this group appeared later, e.g. the next semester — offer it. * missing: the document left the catalog, e.g. a new academic year began. */ export function scheduleUpdate(saved, catalog, group, options = {}) { // A server without parserVersion never asks for a re-read: comparing with a guess could // reload the same document on every check. const { dismissed = [], schemaVersion = catalog?.parserVersion ?? 0 } = options; const fileId = saved?.source?.fileId; if (!fileId || !catalog?.files || catalog.stale) return null; const current = catalog.files.find((f) => f.id === fileId) || null; const savedTime = time(saved.source.modified) || time(saved.source.importedAt); const newer = catalog.files .filter( (f) => f.id !== fileId && !dismissed.includes(f.id) && namesGroup(f.name, group) && time(f.modified) > savedTime && (!saved.source.faculty || f.faculty === saved.source.faculty), ) .sort((a, b) => time(b.modified) - time(a.modified))[0] || null; const changed = current && (current.modified !== saved.source.modified || (saved.schemaVersion || 0) < schemaVersion) ? current : null; if (!changed && !newer && current) return null; return { changed, newer, missing: !current }; }