- Parser 14 (server/places.js): "Занятия в Полоцке/Новополоцке/г. Новополоцк" over a day's first row is no longer a lesson but a note of the day for its column (`places`); lessons of that day get `place`. A lesson below the note in the same cell keeps its own subject.
- A town on its own line ("Новополоцк", "г.Новополоцк") or after a room ("246(Новополоцк)") becomes the lesson's `place` and leaves the subject and room. "История Полоцка" is not a town.
- API 14: `place` on lessons and `places` on days of /api/timetable, `place` in the directory. The day notes are shared by the server and the PWA (shared/places.js); the same note in every subgroup reads as the whole group's.
- Android, PWA and iOS: a "Занятия в Полоцке" card above the day's lessons (day pages, calendar, overview, week view, desk stand), "Полоцк" under the date in the week grid, "249Ж · Полоцк" on lesson cards, a "Город" row in details, the town in widgets, tile, reminders and change lists.
- Snapshot lines show the town and the day notes; tests for notes, towns in cells and the API.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
47 lines
2.2 KiB
JavaScript
47 lines
2.2 KiB
JavaScript
// One readable line per parsed lesson, the form both snapshots compare: what a student sees
|
||
// (day, time, weeks or dates, subject, kind, room, teacher, groups), not ids or source colours.
|
||
const DAYS = ["", "пн", "вт", "ср", "чт", "пт", "сб", "вс"];
|
||
|
||
const weeks = (ranges) =>
|
||
ranges?.map(([a, b]) => (b === null ? `с ${a}` : a === b ? `${a}` : `${a}-${b}`)).join(",") || "";
|
||
|
||
export const lessonLine = (l) =>
|
||
[
|
||
`${l.date || DAYS[l.day]} ${l.start}${l.end ? `–${l.end}` : ""}`,
|
||
(l.date ? "один раз" : l.dates?.length ? `даты ${l.dates.join(",")}` : l.weekRanges ? `нед. ${weeks(l.weekRanges)}` : "каждую неделю") +
|
||
(l.variant === "upper" ? ", верхняя строка" : l.variant === "lower" ? ", нижняя строка" : ""),
|
||
l.subject,
|
||
l.type,
|
||
// The town only when the cell names one or the day's note puts the lesson there.
|
||
[l.room, l.place].filter(Boolean).join(", "),
|
||
l.teacher,
|
||
[...l.groups, ...l.subgroups.map((s) => `${s.group}/${s.label.replace("Подгруппа ", "п")}`)].join(","),
|
||
]
|
||
.map((part) => String(part ?? "").replace(/\s+/g, " ").trim())
|
||
.join(" | ");
|
||
|
||
// "Занятия в Полоцке": a day of a group or subgroup in another town, not a lesson.
|
||
const placeLine = (p) =>
|
||
[
|
||
`@ ${p.date || DAYS[p.day]}`,
|
||
p.text,
|
||
[...p.groups, ...p.subgroups.map((s) => `${s.group}/${s.label.replace("Подгруппа ", "п")}`)].join(","),
|
||
].join(" | ");
|
||
|
||
/** Sorted lesson lines of a parsed document, with its groups and period on top. */
|
||
export const snapshotOf = (schedule) => [
|
||
`# ${schedule.period} · ${schedule.groups.join(", ")}`,
|
||
...(schedule.places || []).map(placeLine).sort(),
|
||
...schedule.lessons.map(lessonLine).sort(),
|
||
];
|
||
|
||
/** Lines only in `before` (removed) and only in `after` (added), duplicates counted. */
|
||
export function diffLines(before, after) {
|
||
const count = (lines) => lines.reduce((m, x) => m.set(x, (m.get(x) || 0) + 1), new Map());
|
||
const a = count(before), b = count(after);
|
||
const removed = [], added = [];
|
||
for (const [line, n] of a) for (let i = b.get(line) || 0; i < n; i++) removed.push(line);
|
||
for (const [line, n] of b) for (let i = a.get(line) || 0; i < n; i++) added.push(line);
|
||
return { removed, added };
|
||
}
|