@acms-subir/core 0.3.52 → 0.3.54

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.
Files changed (66) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/cli/migrations.generated.mjs +18 -1
  3. package/docs/calendar.md +20 -2
  4. package/docs/mobile.md +1 -1
  5. package/docs/server.md +61 -1
  6. package/integration.mjs +11 -5
  7. package/mobile-template/recipes/events/files/components/RsvpPanel.tsx +2 -2
  8. package/mobile-template/recipes/rsvp/files/components/RsvpPanel.tsx +2 -2
  9. package/package.json +1 -1
  10. package/src/components/ThemeSections.astro +2 -2
  11. package/src/components/editor/PostEditor.tsx +2 -0
  12. package/src/components/editor/event/EventSheet.tsx +6 -2
  13. package/src/components/editor/event/RegistrationCard.tsx +3 -3
  14. package/src/components/editor/event/RegistrationFormEditor.tsx +6 -3
  15. package/src/components/forms/Canvas.tsx +31 -19
  16. package/src/components/forms/FormBuildPage.tsx +12 -6
  17. package/src/components/forms/FormBuilder.tsx +60 -11
  18. package/src/components/forms/Inspector.tsx +116 -8
  19. package/src/components/forms/TypeIcon.tsx +1 -0
  20. package/src/components/forms/meta.ts +71 -9
  21. package/src/lib/api.ts +3 -0
  22. package/src/lib/backup/content.ts +4 -2
  23. package/src/lib/calendar/api.ts +3 -1
  24. package/src/lib/calendar/host-requests.ts +247 -0
  25. package/src/lib/calendar/member-form.ts +17 -11
  26. package/src/lib/calendar/member-words.ts +473 -0
  27. package/src/lib/calendar/members-config.ts +2 -2
  28. package/src/lib/calendar/moves.ts +81 -33
  29. package/src/lib/calendar/request-migration.ts +17 -0
  30. package/src/lib/calendar/request-store.ts +104 -0
  31. package/src/lib/calendar/requests.ts +24 -24
  32. package/src/lib/dynamicBlocks.ts +4 -2
  33. package/src/lib/email/service.ts +12 -2
  34. package/src/lib/email/templates.ts +12 -2
  35. package/src/lib/forms/fields.ts +14 -9
  36. package/src/lib/forms/localize.ts +68 -0
  37. package/src/lib/forms/render.ts +8 -2
  38. package/src/lib/forms/schema.ts +31 -2
  39. package/src/lib/forms/service.ts +9 -1
  40. package/src/lib/forms/validate.ts +16 -4
  41. package/src/lib/forms/words.ts +32 -0
  42. package/src/lib/mobile/forms.ts +45 -13
  43. package/src/lib/mobile/openapi.ts +3 -3
  44. package/src/lib/mobile/rsvp.ts +4 -2
  45. package/src/lib/mobile/schemas.ts +3 -1
  46. package/src/lib/repo/migrations.ts +4 -0
  47. package/src/lib/repo/sql.ts +7 -0
  48. package/src/lib/repo/types.ts +6 -1
  49. package/src/lib/schema.ts +47 -0
  50. package/src/pages/[...slug].astro +1 -1
  51. package/src/pages/admin/calendar/events/index.astro +82 -6
  52. package/src/pages/admin/forms/[id].astro +6 -0
  53. package/src/pages/admin/index.astro +6 -2
  54. package/src/pages/blog/[slug].astro +1 -1
  55. package/src/pages/events/[slug].astro +9 -3
  56. package/src/pages/forms/[id].astro +22 -4
  57. package/src/pages/index.astro +1 -1
  58. package/src/pages/members/_EventForm.astro +33 -27
  59. package/src/pages/members/_Layout.astro +25 -5
  60. package/src/pages/members/_http.ts +29 -6
  61. package/src/pages/members/events/[id].astro +21 -20
  62. package/src/pages/members/events/index.astro +295 -101
  63. package/src/pages/members/events/new.astro +11 -10
  64. package/src/server/index.ts +53 -0
  65. package/src/styles/forms.css +8 -0
  66. package/src/virtual.d.ts +1 -1
@@ -6,16 +6,17 @@ import { currentUser } from '../../../server';
6
6
  import { readForm } from '../../../lib/forms/http';
7
7
  import { MEMBERS, memberEventsPath, submitEventRequest } from '../../../lib/calendar/requests';
8
8
  import { blankEventForm, eventRequestInput, memberEventFields, readEventForm } from '../../../lib/calendar/member-form';
9
- import { failure, keepPrivate, notFound, redirectTo, signInRedirect } from '../_http';
9
+ import { failure, keepPrivate, memberLanguage, notFound, redirectTo, signInRedirect } from '../_http';
10
10
 
11
11
  // "Request an event": a draft the member hosts, waiting for the team's
12
12
  // approval (submitEventRequest checks who may ask and what they sent).
13
13
  if (!MEMBERS.enabled) return notFound();
14
14
  const user = await currentUser(Astro);
15
- if (!user) return signInRedirect(Astro.url);
15
+ if (!user) return signInRedirect(Astro.url, Astro.locals);
16
16
  keepPrivate(Astro.response.headers);
17
17
  const repo = await getRepo(Astro.locals);
18
- const home = memberEventsPath();
18
+ const { w, lang, here } = memberLanguage(Astro, await repo.getSettings());
19
+ const home = here(memberEventsPath());
19
20
  const fields = memberEventFields();
20
21
  const calendars = (await repo.listCalendars()).filter((c) => c.kind === 'native' && c.visibility === 'public');
21
22
 
@@ -28,7 +29,7 @@ if (Astro.request.method === 'POST') {
28
29
  await submitEventRequest(repo, user, eventRequestInput(values), { url: Astro.url, locals: Astro.locals });
29
30
  return redirectTo(`${home}?sent=1`);
30
31
  } catch (err) {
31
- const f = failure(err);
32
+ const f = failure(err, w, lang);
32
33
  error = f.message;
33
34
  Astro.response.status = f.status;
34
35
  }
@@ -36,13 +37,13 @@ if (Astro.request.method === 'POST') {
36
37
  const canRequest = user.can('events.request');
37
38
  ---
38
39
 
39
- <Layout title="Request an event" user={user}>
40
+ <Layout title={w.newTitle} user={user} w={w}>
40
41
  <div>
41
- <a class="pc-mem-back" href={home}>← My events</a>
42
- <div class="pc-mem-head"><h1>Request an event</h1></div>
43
- <p class="pc-mem-lede">Tell the team what you’d like to host. It goes on the calendar once they approve it.</p>
42
+ <a class="pc-mem-back" href={home}>{w.back}</a>
43
+ <div class="pc-mem-head"><h1>{w.newTitle}</h1></div>
44
+ <p class="pc-mem-lede">{w.newLede}</p>
44
45
  </div>
45
46
  {canRequest
46
- ? <EventForm values={values} calendars={calendars} fields={fields} op="submit" submitLabel="Submit for approval" error={error} />
47
- : <p class="pc-bk-error" role="alert">Your role doesn’t include “Request events”. Ask the site’s team if you need it.</p>}
47
+ ? <EventForm values={values} calendars={calendars} fields={fields} op="submit" submitLabel={w.submit} error={error} w={w} lang={lang} />
48
+ : <p class="pc-bk-error" role="alert">{w.noRequestRole}</p>}
48
49
  </Layout>
@@ -300,3 +300,56 @@ export async function declineEventRequest(repo: Repo, who: Who, eventId: string,
300
300
  export async function setHostedOccurrence(repo: Repo, who: Who, eventId: string, occurrenceKey: string, cancelled: boolean, context: WriteContext): Promise<CalEvent> {
301
301
  return (await requests()).setHostedOccurrence(internal(repo), who, eventId, occurrenceKey, cancelled, context);
302
302
  }
303
+
304
+ // ---- hosts moving dates ------------------------------------------------------
305
+ // A host moves one date of an event they host (no permission beyond being in
306
+ // hostIds). acms({ members: { hostMoves } }) decides how: 'direct' (default)
307
+ // moves it at once while its rooms are free (409 room-taken otherwise, and
308
+ // the same move can then be sent with requestDateMove); 'approve' turns every
309
+ // host move into a request. Requests are RequestRecord rows (kind 'move')
310
+ // that staff who can publish approve or decline; both sides are emailed.
311
+
312
+ const hostRequests = () => import('../lib/calendar/host-requests');
313
+ export type { HostMoveResult, MoveRequestData } from '../lib/calendar/host-requests';
314
+ export type { RequestRecord } from '../lib/schema';
315
+
316
+ /** A host moves one date: `{ moved }` at once, or `{ request }` when moves need approval. Errors as moveOccurrence's, plus `code` room-not-allowed (a room the event doesn't use and nobody may request). */
317
+ export async function moveHostedDate(repo: Repo, who: Who, eventId: string, occurrenceKey: string, input: import('../lib/calendar/moves').MoveInput, context: WriteContext): Promise<import('../lib/calendar/host-requests').HostMoveResult> {
318
+ return (await hostRequests()).moveHostedDate(internal(repo), who, eventId, occurrenceKey, input, context);
319
+ }
320
+
321
+ /** A host puts a date they moved back where the series has it: at once, or as a request. */
322
+ export async function moveHostedDateBack(repo: Repo, who: Who, eventId: string, occurrenceKey: string, context: WriteContext): Promise<import('../lib/calendar/host-requests').HostMoveResult> {
323
+ return (await hostRequests()).moveHostedDateBack(internal(repo), who, eventId, occurrenceKey, context);
324
+ }
325
+
326
+ /**
327
+ * A host asks staff to move one date (`input`), or to move it back (null),
328
+ * with an optional `message` for staff. Checked like the move, except that a
329
+ * taken room is allowed: staff decide. Asking again about the same date
330
+ * replaces the waiting request. Staff are emailed.
331
+ */
332
+ export async function requestDateMove(repo: Repo, who: Who, eventId: string, occurrenceKey: string, input: import('../lib/calendar/moves').MoveInput | null, message: string, context: WriteContext): Promise<import('../lib/schema').RequestRecord> {
333
+ return (await hostRequests()).requestDateMove(internal(repo), who, eventId, occurrenceKey, input, message, context);
334
+ }
335
+
336
+ /** A host's own date requests (waiting, approved, declined and withdrawn), newest first. */
337
+ export async function listMyDateRequests(repo: Repo, who: Who, eventId?: string): Promise<import('../lib/schema').RequestRecord[]> {
338
+ if (!who) return [];
339
+ return (await hostRequests()).myDateRequests(internal(repo), who.id, eventId);
340
+ }
341
+
342
+ /** A host withdraws a waiting date request of theirs. */
343
+ export async function withdrawDateRequest(repo: Repo, who: Who, requestId: string): Promise<void> {
344
+ return (await hostRequests()).withdrawDateRequest(internal(repo), who, requestId);
345
+ }
346
+
347
+ /** Staff ("Publish content"): approve a host's date move. It runs as them (room clashes come back as warnings) and is recorded as the host's. Pass `seen`, the request's `updatedAt` you showed. */
348
+ export async function approveDateRequest(repo: Repo, who: Who, requestId: string, context: WriteContext, seen?: number): Promise<{ request: import('../lib/schema').RequestRecord; moved: import('../lib/calendar/moves').MoveResult }> {
349
+ return (await hostRequests()).approveDateRequest(internal(repo), who, requestId, context, seen);
350
+ }
351
+
352
+ /** Staff: decline a host's date request, with a note the host reads. */
353
+ export async function declineDateRequest(repo: Repo, who: Who, requestId: string, reply: string, context: WriteContext, seen?: number): Promise<import('../lib/schema').RequestRecord> {
354
+ return (await hostRequests()).declineDateRequest(internal(repo), who, requestId, reply, context, seen);
355
+ }
@@ -81,6 +81,7 @@
81
81
  .fb.is-preview .fb-canvas { border: 0; }
82
82
  .fb-wide .fb-canvas:has(> .fb-float) { padding-top: 48px; }
83
83
  .fb-float { position: absolute; top: 8px; right: 10px; z-index: 3; display: flex; gap: 2px; padding: 2px; border-radius: 10px; background: color-mix(in srgb, var(--panel-2) 85%, transparent); }
84
+ .fb-float-l { right: auto; left: 10px; }
84
85
  .fb-paper {
85
86
  /* the public form's tokens, mapped onto the admin palette */
86
87
  --ink: var(--text); --line-2: var(--line-strong); --btn: var(--text); --btn-ink: var(--bg); --accent-text: var(--accent-deep);
@@ -195,6 +196,13 @@
195
196
  .fb-choice.dragging input { box-shadow: var(--shadow-pop); }
196
197
  .fb-choice input { min-height: 34px; }
197
198
  .fb-choice-add { margin: 2px 0 0; font-size: 12px; color: var(--muted); }
199
+ /* another language: the main language's words above each box, choices side by side */
200
+ .fb-src { display: block; padding: 6px 9px; border-radius: var(--r-sm); background: var(--panel-2); color: var(--text-2); font-size: 12.5px; line-height: 1.45; white-space: pre-line; overflow-wrap: anywhere; max-height: 7.5em; overflow: auto; }
201
+ .fb-src > span { display: block; font-size: 10.5px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.06em; color: var(--muted); margin-bottom: 1px; }
202
+ .fb-tr-choices { display: grid; gap: 6px; }
203
+ .fb-tr-choice { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1.4fr); gap: 8px; align-items: center; }
204
+ .fb-tr-choice > span { font-size: 12.5px; color: var(--text-2); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
205
+ .fb-tr-choice input { min-height: 34px; }
198
206
 
199
207
  /* menus */
200
208
  .fb-more, .fb-q-more { position: relative; flex: none; }
package/src/virtual.d.ts CHANGED
@@ -22,7 +22,7 @@ declare module 'virtual:acms-config' {
22
22
  // resolved by integration.mjs (lib/roles.ts and lib/permissions/catalog.ts read them)
23
23
  roles?: { key: string; label: string; summary: string; admin: boolean; home: string }[];
24
24
  permissions?: { key: string; label: string; description: string; defaults: string[]; sensitive: boolean }[];
25
- members?: { enabled: boolean; base: string; notify: string[] };
25
+ members?: { enabled: boolean; base: string; notify: string[]; hostMoves?: 'direct' | 'approve' };
26
26
  announcements?: { enabled: boolean; base: string; pages: boolean; topics: Partial<Record<'event' | 'registration' | 'help' | 'info', string>> };
27
27
  // set in astro:config:setup (lib/origin-check.ts): off only when the site turned security.checkOrigin off
28
28
  originCheck?: { enabled: boolean; signedRoutes: string[] };