@acms-subir/core 0.3.50 → 0.3.52

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,29 @@
1
1
  # @acms-subir/core
2
2
 
3
+ ## 0.3.52
4
+
5
+ ### Patch Changes
6
+
7
+ - b8ffed8: Saved TV screens: **Admin → Calendar → Screens** (for people who manage rooms and calendars).
8
+
9
+ Each screen has a name, the display settings (view, hours with "Show all hours" or "Fit the events", days, first day, calendars, rooms in column order, tags, theme, text size, what shows, title, language) beside a live 16:9 preview, and a long random key that is its whole address: `/display/s/<key>`. **Copy link** copies it, and **Rotate link** makes a new one; the old link stops working at once. **Last check-in** shows when the screen last fetched its events (a TV that's on checks in every 5 minutes), so staff can tell a TV is off. A change made in the admin reaches the TV within 5 minutes. The same editor builds quick links, can start from a pasted one, and saves any quick link as a screen.
10
+
11
+ **Show private calendars and room bookings** (off by default) lets a screen show the private calendars it names and the confirmed bookings of the rooms it names (or of rooms set to show on the calendar): titles, rooms, hosts and the name on each booking ("Birthday party · Rivera"), never emails, phone numbers, booking notes or RSVP lists. A Google booking keeps "Booked" with its own title under it. Turning it on, or previewing it, also needs **Manage bookings**. Saved screens are never cached (`private, no-store`) and send no referrer. Wrong keys are rate-limited: after 60 in 15 minutes from one address, further wrong keys from it get a 429, while a screen's own key always opens it (bad keys can't lock a church's TVs out).
12
+
13
+ Migration 67 adds the `display_screens` table. Screens go into backups without their keys; after a restore each screen has a new link to put on its TV. New: `/display/s/[key]`, `/admin/calendar/screens`, `/admin/calendar/screens/preview` and `/api/calendar/screens` (staff only). `npx acms docs calendar` covers screens, keys and private details.
14
+
15
+ The display list now keeps each day in one column when it fits, so a column never starts without its day.
16
+
17
+ Moved and cancelled dates on the display use the event pages' own words ("Cambio de horario" in Spanish), so a TV says what the site and its emails say.
18
+
19
+ ## 0.3.51
20
+
21
+ ### Patch Changes
22
+
23
+ - 1efe99e: Section event items (an events source, as theme sections and the apps' native sections receive them) carry `rooms`: the public rooms each date holds, as `{ slug, name, color }`. A moved date lists its own rooms, and only rooms set to "Name it on public events" are included, with `color` `null` when a room has none. A Classes card can now name the room without staff typing it into the place. `upcomingEvents()` in the theme SDK returns `rooms` too, as `{ id, slug, name, color }`. The library's Upcoming events section shows "Moved · " before a moved date, with "Moved from …" and the note. `EventItem` and its sibling types now live in `lib/section-items.ts`, which has no imports, and `EventItemRoom` is exported from `@acms-subir/core/theme`.
24
+
25
+ The app API's `event.repeats` (`/api/v1/events`, `/api/v1/events/:slug` and search) now follows `?locale=`: "Todos los sábados, hasta el 1 jun 2030" instead of always English. Without a locale it uses the site's main language, and the until date is written in that language too. `eventToJson` takes a `locale` option for it. The REST API and webhooks keep English.
26
+
3
27
  ## 0.3.50
4
28
 
5
29
  ### Patch Changes
@@ -434,6 +434,19 @@ var ANNOUNCEMENT_VISIBLE_COLUMNS = [
434
434
  "published_at"
435
435
  ];
436
436
 
437
+ // src/lib/display/store.ts
438
+ var SCREENS_MIGRATION = {
439
+ version: 67,
440
+ statements: [
441
+ `CREATE TABLE display_screens (
442
+ id TEXT PRIMARY KEY, name TEXT NOT NULL DEFAULT '', params TEXT NOT NULL DEFAULT '', locale TEXT NOT NULL DEFAULT '',
443
+ private_details INTEGER NOT NULL DEFAULT 0, screen_key TEXT NOT NULL DEFAULT '', last_seen_at INTEGER,
444
+ ord INTEGER NOT NULL DEFAULT 0, created_at INTEGER NOT NULL, updated_at INTEGER NOT NULL
445
+ )`,
446
+ `CREATE UNIQUE INDEX display_screens_key ON display_screens (screen_key) WHERE screen_key <> ''`
447
+ ]
448
+ };
449
+
437
450
  // src/lib/repo/migrations.ts
438
451
  var CONTENT_VERSION_COLUMNS = {
439
452
  settings: [
@@ -1266,7 +1279,9 @@ var MIGRATIONS = [
1266
1279
  EVENT_CHANGES_MIGRATION,
1267
1280
  // single dates of a series moved (CalEvent.movedDates): a column on the
1268
1281
  // row, so the triggers on events (content version, event_changes) see a move
1269
- MOVED_DATES_MIGRATION(contentVersionTriggers("events", [...CONTENT_VERSION_COLUMNS.events, "cancelled_dates", "moved_dates"])[2])
1282
+ MOVED_DATES_MIGRATION(contentVersionTriggers("events", [...CONTENT_VERSION_COLUMNS.events, "cancelled_dates", "moved_dates"])[2]),
1283
+ // saved TV screens (Admin → Calendar → Screens, lib/display/store.ts)
1284
+ SCREENS_MIGRATION
1270
1285
  ];
1271
1286
  var CURRENT_CONTENT_VERSION_COLUMNS = {
1272
1287
  ...CONTENT_VERSION_COLUMNS,
package/docs/calendar.md CHANGED
@@ -195,9 +195,12 @@ client has `roomFree({ room, start, end })`, and the native package has
195
195
 
196
196
  A full-screen calendar for a TV in a lobby or on a room's door. It shows a week, day, month, list, rooms or "now and next" view, with your own hours (8 AM to 10 PM by default). TVs have no mouse, so everything about a screen lives in its link. The page has nothing to focus or follow (no links, buttons or tab stops), because a remote's presses go to the page.
197
197
 
198
- A display link spells out its settings in the address: `/display/calendar?view=week&hours=8-22`. Nothing is saved. Whatever its parameters, it shows only what `calendar.ics` shows: published events in public calendars, or in an unlisted calendar named in `cal=`. It never shows subscribed calendars, booking blocks, hosts, or rooms that aren't shown on public events.
198
+ There are two kinds of link:
199
199
 
200
- Spanish (or any other site language): `/es/display/calendar?…`.
200
+ - **Quick links** spell out their settings in the address: `/display/calendar?view=week&hours=8-22`. Nothing is saved. Whatever their parameters, they show only what `calendar.ics` shows: published events in public calendars, or in an unlisted calendar named in `cal=`. There are no subscribed calendars, booking blocks, room names (until rooms are shown on public events) or hosts.
201
+ - **Saved screens** live in **Admin → Calendar → Screens**. Each has a name, the same settings and a long random key, which is the whole address: `/display/s/<key>`. A screen can show private details (below).
202
+
203
+ Spanish (or any other site language): `/es/display/calendar?…`, or the screen's Language setting.
201
204
 
202
205
  ### Parameters
203
206
 
@@ -209,12 +212,12 @@ Each has a default; leave out what you don't need. Lists are comma-separated or
209
212
  | `hours` | `8-22` · `8:30-21` · `8-24` · `0-24` (or `all`) · `auto` | The hour grids' visible hours. `8-24` ends at midnight. `auto` fits the events shown, never less than 6 hours. Events outside the hours are cut and marked (▲ ▼), never left out. Default `8-22`. |
210
213
  | `days` | `7` · `5` · `mon-fri` · `mon-sat` · `sun,wed,fri` | A number is that many days from the first day; names pick weekdays. For `list`, a number is how many days the list covers. Default 7. |
211
214
  | `start` | `sun` · `mon` · `today` | The week's first day. `today` gives a rolling week. Default: Calendar settings → Defaults. |
212
- | `cal` | calendar slugs | Only these calendars. Default: every public calendar. Private calendars never show. |
213
- | `tag` / `notag` | tag names | Only events with one of these tags, or none of them. `tag=Class` makes a class board. Internal `#` tags never match. |
214
- | `room` | room slugs | Only events held in these rooms. With `view=rooms` it also sets the columns and their order (otherwise every room of kind "Room"). Only rooms shown on public events count; any other room shows nothing. |
215
+ | `cal` | calendar slugs | Only these calendars. Default: every public calendar. Private calendars need a saved screen with private details. |
216
+ | `tag` / `notag` | tag names | Only events with one of these tags, or none of them. `tag=Class` makes a class board. Internal `#` tags only match on a screen with private details. |
217
+ | `room` | room slugs | Only events held in these rooms. With `view=rooms` it also sets the columns and their order (otherwise every room of kind "Room"). On a quick link, only rooms named on public events count; any other room shows nothing. |
215
218
  | `theme` | `dark` · `light` | Default `dark`. |
216
219
  | `size` | `s` · `m` · `l` · `xl` | Text size for the viewing distance. Default `m`. |
217
- | `show` | `time,room,host,clock,legend,qr` (or `none`) | What each event and the page show. Default `time,room,clock`. `legend` is a colour key; `qr` is a code that opens the calendar on a phone (with the same filters). `host` is for saved screens with private details, which are coming next. |
220
+ | `show` | `time,room,host,clock,legend,qr` (or `none`) | What each event and the page show. Default `time,room,clock`. `legend` is a colour key; `qr` is a code that opens the calendar on a phone (with the same public filters). Hosts only show on a screen with private details. |
218
221
  | `title` | text | Replaces the heading ("This week", "Today"…). |
219
222
 
220
223
  Examples:
@@ -223,19 +226,30 @@ Examples:
223
226
  - `/display/calendar?view=list&days=14&size=l`: the next two weeks, in large text.
224
227
  - `/display/calendar?view=rooms&room=chapel,hall,youth&hours=auto`: today, one column per room.
225
228
 
229
+ The editor in Admin → Calendar → Screens builds these links (**Quick link**), shows a live preview and can save any quick link as a screen.
230
+
226
231
  ### What a board does
227
232
 
228
233
  - The first view is rendered on the server. A small script moves the now line, dims what has ended, and turns the list's pages (the list runs in columns, then pages, instead of scrolling).
229
234
  - Every 5 minutes it fetches the board again from the same address, so a change made in the admin reaches the TV within 5 minutes. It also fetches at midnight, so the board rolls to the next day, week or month. It reloads once a night (between 3:00 and 3:40, site time).
230
- - Offline, it keeps the last board and shows "Updated 7:41 PM". A small service worker (scope `/display/`) keeps a copy of the page, so a reload during an outage shows the last events instead of an error page.
235
+ - Offline, it keeps the last board and shows "Updated 7:41 PM". A small service worker (scope `/display/`) keeps a copy of the page, so a reload during an outage or a server error shows the last events instead of an error page (a copy older than a week is never shown).
231
236
  - Events take their room's colour, else their calendar's. Appointments from Google booking pages (tagged `#google-booking`) show as "Booked". A cancelled date is struck through, and a moved date shows at its new time, marked "Moved" with its old time (and day, when that changed).
232
- - The page may be framed anywhere (`frame-ancestors *`), sends no referrer, is never indexed, and needs no cookie. It may sit in a shared cache for a minute.
237
+ - The page may be framed anywhere (`frame-ancestors *`), sends no referrer, is never indexed, and needs no cookie. Quick links may sit in a shared cache for a minute; saved screens never do.
238
+
239
+ ### Saved screens, keys and private details
240
+
241
+ **Show private calendars and room bookings** (each screen's switch, off by default) lets the screen show the private calendars it names in `cal=` and the confirmed bookings of the rooms it names in `room=` (or of rooms set to show on the calendar). It shows their titles, times, rooms, hosts and the name on each booking ("Birthday party · Rivera"). It never shows emails, phone numbers, booking notes or RSVP lists. Turning it on (or previewing it) needs both **Manage rooms and calendars** and **Manage bookings**. Anyone with the screen's link can see what it shows, so keep the link in the TV's playlist and nowhere public. A booking isn't in any calendar, so a `cal=` filter leaves bookings out.
242
+
243
+ - **Copy link** copies the screen's address. **Rotate link** makes a new key; the old address stops working at once (the TV shows "This screen isn't available" until it gets the new link).
244
+ - **Last check-in** is when the screen last fetched its events. A TV that is on checks in every 5 minutes, so an old check-in means it's off or offline.
245
+ - Wrong keys are rate-limited per network address: after 60 wrong keys in 15 minutes, further wrong keys from that address get "Too many" until the window ends. A screen's own key always opens it.
246
+ - Screens go into backups without their keys. After a restore, each screen has a new link: copy it into each TV again.
233
247
 
234
248
  ### Setting up a TV with AbleSign
235
249
 
236
250
  AbleSign (app.ablesign.tv/screens) plays a playlist on each screen; a website is one item.
237
251
 
238
- 1. In AbleSign, open **Websites → Add website** and paste the display link.
252
+ 1. In AbleSign, open **Websites → Add website** and paste the screen's link (or a quick link).
239
253
  2. Add the website to the screen's playlist with a long duration (AbleSign reloads a website every time the playlist plays it; the page refreshes itself).
240
254
  3. Put a short image before it in the playlist, so the page loads in the background.
241
255
  4. The Fire TV Stick needs Fire OS 6 or later (not Vega OS) and AbleSign 2.2.2 or later.
@@ -253,4 +267,4 @@ A theme's `theme.css` loads after the board's own styles. The simplest way to re
253
267
  .acms-display[data-theme='light'] { --display-bg: #fffaf2; --display-fg: #1d1a16; }
254
268
  ```
255
269
 
256
- Keep blocks flat: the built-in look has no accent stripes or gradients. The display leaves out the site's header, footer, head code and analytics. A site file at `src/pages/display/calendar.astro` replaces that page.
270
+ Keep blocks flat: the built-in look has no accent stripes or gradients. The display leaves out the site's header, footer, head code and analytics. A site file at `src/pages/display/calendar.astro` (or `display/s/[key].astro`) replaces that page.
@@ -55,7 +55,7 @@ const date = new Intl.DateTimeFormat('en', { dateStyle: 'medium', timeZone: Astr
55
55
  </section>}
56
56
  ```
57
57
 
58
- `calendarConfig()` returns a fresh `{ enabled, base, eventsBase }` object; `eventUrl(slug)` uses the configured event base. `upcomingEvents(Astro, options?)` resolves storage through `Astro.locals` and loads the site's timezone settings. It returns the next or ongoing occurrence of each distinct published, publicly listed native event, soonest first, including recurring and all-day events. Private/unlisted calendars and external feed entries are excluded; cancelled public events remain visible so templates can label them, and a date moved to another time carries `moved` (`{ from: { start, end }, note }`) so templates can say "Moved from …". `tag` keeps only events with that tag and `excludeTag` leaves them out (a tag's name in any case, or its slug), e.g. `upcomingEvents(Astro, { tag: 'Class' })` for a Classes page. Disabled calendars and a zero limit return `[]` without accessing storage. Storage errors propagate to the template.
58
+ `calendarConfig()` returns a fresh `{ enabled, base, eventsBase }` object; `eventUrl(slug)` uses the configured event base. `upcomingEvents(Astro, options?)` resolves storage through `Astro.locals` and loads the site's timezone settings. It returns the next or ongoing occurrence of each distinct published, publicly listed native event, soonest first, including recurring and all-day events. Private/unlisted calendars and external feed entries are excluded; cancelled public events remain visible so templates can label them, and a date moved to another time carries `moved` (`{ from: { start, end }, note }`) so templates can say "Moved from …". `rooms` lists the public rooms each date holds (`{ id, slug, name, color }`, only rooms set to show on public events). Section `EventItem`s from an events source carry `moved` (`{ from: "Thu, Oct 1 · 6:00 PM", note }` or `null`) and `rooms` (`{ slug, name, color }[]`, `color` `null` when the room has none). `tag` keeps only events with that tag and `excludeTag` leaves them out (a tag's name in any case, or its slug), e.g. `upcomingEvents(Astro, { tag: 'Class' })` for a Classes page. Disabled calendars and a zero limit return `[]` without accessing storage. Storage errors propagate to the template.
59
59
 
60
60
  Options default to `{ limit: 6, horizonDays: 400 }`. The limit must be a non-negative safe integer and the horizon a positive finite number. Each `UpcomingEvent` contains `{ event, start, end, allDay, days }`, with start/end in Unix milliseconds. Format occurrence timestamps using the site's timezone rather than the authored `event.start` when displaying recurring events. `UpcomingEvent`, `UpcomingEventsOptions`, and `ThemeCalendarConfig` are exported types.
61
61
 
package/integration.mjs CHANGED
@@ -863,6 +863,11 @@ export default function acms(options = {}) {
863
863
  // TV displays: quick links (everything in the address) and their offline worker
864
864
  ['/display/calendar', 'display/calendar.astro'],
865
865
  ['/display/sw.js', 'display/sw.js.ts'],
866
+ // saved screens: a long random key is the whole address (Admin → Calendar → Screens)
867
+ ['/display/s/[key]', 'display/s/[key].astro'],
868
+ ['/admin/calendar/screens', 'admin/calendar/screens/index.astro'],
869
+ ['/admin/calendar/screens/preview', 'admin/calendar/screens/preview.astro'],
870
+ ['/api/calendar/screens', 'api/calendar/screens.ts'],
866
871
  ['/api/v1/events', 'api/v1/events.ts'],
867
872
  ['/api/v1/calendars', 'api/v1/calendars.ts'],
868
873
  // RSVPs from apps: a date's status, then POST /bookings (room bookings there need bookings on)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@acms-subir/core",
3
- "version": "0.3.50",
3
+ "version": "0.3.52",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -24,8 +24,9 @@ const fit = data.imageFit === 'contain' ? 'contain' : 'cover';
24
24
  ? <a class="upev-media" href={e.url} tabindex="-1" aria-hidden="true"><img src={image.src} alt="" loading="lazy" /></a>
25
25
  : <span class="upev-date" aria-hidden="true"><b>{e.day}</b>{e.month}</span>}
26
26
  <div class="upev-body">
27
- <span class="upev-when">{e.cancelled ? 'Cancelled · ' : ''}{e.when}</span>
27
+ <span class="upev-when">{e.cancelled ? 'Cancelled · ' : e.moved ? 'Moved · ' : ''}{e.when}</span>
28
28
  <h3 class="upev-title"><a href={e.url}>{e.title}</a></h3>
29
+ {!e.cancelled && e.moved && <span class="upev-moved">Moved from {e.moved.from}{e.moved.note ? ` · ${e.moved.note}` : ''}</span>}
29
30
  {data.showSummary && e.summary && <p class="upev-summary">{e.summary}</p>}
30
31
  {data.showLocation !== false && e.location && <span class="upev-loc">{e.location}</span>}
31
32
  </div>
@@ -59,5 +60,6 @@ const fit = data.imageFit === 'contain' ? 'contain' : 'cover';
59
60
  .upev-title a { color: inherit; text-decoration: none; }
60
61
  .upev-title a:hover { text-decoration: underline; }
61
62
  .upev-loc { font-size: 0.9rem; color: var(--muted); }
63
+ .upev-moved { font-size: 0.9rem; color: var(--accent, inherit); }
62
64
  .is-cancelled .upev-title { text-decoration: line-through; opacity: 0.7; }
63
65
  </style>