@axium/calendar 0.3.4 → 0.4.1

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/dist/common.d.ts CHANGED
@@ -117,15 +117,16 @@ export interface Event extends z.infer<typeof Event> {
117
117
  export declare function formatEventTimes(event: Event): string;
118
118
  export declare const CalendarInit: z.ZodObject<{
119
119
  name: z.ZodString;
120
+ color: z.ZodOptional<z.ZodNullable<z.ZodCustomStringFormat<"hex">>>;
120
121
  }, z.core.$strip>;
121
122
  export interface CalendarInit extends z.infer<typeof CalendarInit> {
122
123
  }
123
124
  export declare const Calendar: z.ZodObject<{
124
125
  name: z.ZodString;
126
+ color: z.ZodOptional<z.ZodNullable<z.ZodCustomStringFormat<"hex">>>;
125
127
  id: z.ZodUUID;
126
128
  userId: z.ZodUUID;
127
129
  created: z.ZodCoercedDate<unknown>;
128
- color: z.ZodOptional<z.ZodNullable<z.ZodCustomStringFormat<"hex">>>;
129
130
  acl: z.ZodOptional<z.ZodArray<z.ZodObject<{
130
131
  itemId: z.ZodUUID;
131
132
  userId: z.ZodOptional<z.ZodNullable<z.ZodUUID>>;
@@ -161,12 +162,13 @@ declare const CalendarAPI: {
161
162
  readonly 'users/:id/calendars': {
162
163
  readonly PUT: readonly [z.ZodObject<{
163
164
  name: z.ZodString;
165
+ color: z.ZodOptional<z.ZodNullable<z.ZodCustomStringFormat<"hex">>>;
164
166
  }, z.core.$strip>, z.ZodObject<{
165
167
  name: z.ZodString;
168
+ color: z.ZodOptional<z.ZodNullable<z.ZodCustomStringFormat<"hex">>>;
166
169
  id: z.ZodUUID;
167
170
  userId: z.ZodUUID;
168
171
  created: z.ZodCoercedDate<unknown>;
169
- color: z.ZodOptional<z.ZodNullable<z.ZodCustomStringFormat<"hex">>>;
170
172
  acl: z.ZodOptional<z.ZodArray<z.ZodObject<{
171
173
  itemId: z.ZodUUID;
172
174
  userId: z.ZodOptional<z.ZodNullable<z.ZodUUID>>;
@@ -192,10 +194,10 @@ declare const CalendarAPI: {
192
194
  }, z.core.$strip>];
193
195
  readonly GET: z.ZodArray<z.ZodObject<{
194
196
  name: z.ZodString;
197
+ color: z.ZodOptional<z.ZodNullable<z.ZodCustomStringFormat<"hex">>>;
195
198
  id: z.ZodUUID;
196
199
  userId: z.ZodUUID;
197
200
  created: z.ZodCoercedDate<unknown>;
198
- color: z.ZodOptional<z.ZodNullable<z.ZodCustomStringFormat<"hex">>>;
199
201
  acl: z.ZodNonOptional<z.ZodOptional<z.ZodArray<z.ZodObject<{
200
202
  itemId: z.ZodUUID;
201
203
  userId: z.ZodOptional<z.ZodNullable<z.ZodUUID>>;
@@ -223,10 +225,10 @@ declare const CalendarAPI: {
223
225
  readonly 'calendars/:id': {
224
226
  readonly GET: z.ZodObject<{
225
227
  name: z.ZodString;
228
+ color: z.ZodOptional<z.ZodNullable<z.ZodCustomStringFormat<"hex">>>;
226
229
  id: z.ZodUUID;
227
230
  userId: z.ZodUUID;
228
231
  created: z.ZodCoercedDate<unknown>;
229
- color: z.ZodOptional<z.ZodNullable<z.ZodCustomStringFormat<"hex">>>;
230
232
  acl: z.ZodNonOptional<z.ZodOptional<z.ZodArray<z.ZodObject<{
231
233
  itemId: z.ZodUUID;
232
234
  userId: z.ZodOptional<z.ZodNullable<z.ZodUUID>>;
@@ -252,12 +254,13 @@ declare const CalendarAPI: {
252
254
  }, z.core.$strip>;
253
255
  readonly PATCH: readonly [z.ZodObject<{
254
256
  name: z.ZodString;
257
+ color: z.ZodOptional<z.ZodNullable<z.ZodCustomStringFormat<"hex">>>;
255
258
  }, z.core.$strip>, z.ZodObject<{
256
259
  name: z.ZodString;
260
+ color: z.ZodOptional<z.ZodNullable<z.ZodCustomStringFormat<"hex">>>;
257
261
  id: z.ZodUUID;
258
262
  userId: z.ZodUUID;
259
263
  created: z.ZodCoercedDate<unknown>;
260
- color: z.ZodOptional<z.ZodNullable<z.ZodCustomStringFormat<"hex">>>;
261
264
  acl: z.ZodOptional<z.ZodArray<z.ZodObject<{
262
265
  itemId: z.ZodUUID;
263
266
  userId: z.ZodOptional<z.ZodNullable<z.ZodUUID>>;
@@ -283,10 +286,10 @@ declare const CalendarAPI: {
283
286
  }, z.core.$strip>];
284
287
  readonly DELETE: z.ZodObject<{
285
288
  name: z.ZodString;
289
+ color: z.ZodOptional<z.ZodNullable<z.ZodCustomStringFormat<"hex">>>;
286
290
  id: z.ZodUUID;
287
291
  userId: z.ZodUUID;
288
292
  created: z.ZodCoercedDate<unknown>;
289
- color: z.ZodOptional<z.ZodNullable<z.ZodCustomStringFormat<"hex">>>;
290
293
  acl: z.ZodOptional<z.ZodArray<z.ZodObject<{
291
294
  itemId: z.ZodUUID;
292
295
  userId: z.ZodOptional<z.ZodNullable<z.ZodUUID>>;
package/dist/common.js CHANGED
@@ -131,12 +131,12 @@ export function formatEventTimes(event) {
131
131
  }
132
132
  export const CalendarInit = z.object({
133
133
  name: z.string(),
134
+ color: Color.nullish(),
134
135
  });
135
136
  export const Calendar = CalendarInit.extend({
136
137
  id: z.uuid(),
137
138
  userId: z.uuid(),
138
139
  created: z.coerce.date(),
139
- color: Color.nullish(),
140
140
  acl: AccessControl.array().optional(),
141
141
  });
142
142
  export function getCalPermissionsInfo(cal, user) {
package/dist/server.js CHANGED
@@ -34,7 +34,7 @@ addRoute({
34
34
  await checkAuthForUser(request, userId);
35
35
  return withEncoded(await database
36
36
  .insertInto('calendars')
37
- .values({ ...init, userId })
37
+ .values({ ...withDecoded(init), userId })
38
38
  .returningAll()
39
39
  .executeTakeFirstOrThrow()
40
40
  .catch(withError('Could not create calendar')));
@@ -52,7 +52,7 @@ addRoute({
52
52
  await authRequestForItem(request, 'calendars', id, { edit: true });
53
53
  return withEncoded(await database
54
54
  .updateTable('calendars')
55
- .set(body)
55
+ .set(withDecoded(body))
56
56
  .where('id', '=', id)
57
57
  .returningAll()
58
58
  .executeTakeFirstOrThrow()
@@ -0,0 +1,8 @@
1
+ import en from '../locales/en.json';
2
+ import './common.js';
3
+ type en = typeof en;
4
+ declare module '@axium/client/locales' {
5
+ interface Locale extends en {
6
+ }
7
+ }
8
+ export {};
@@ -0,0 +1,4 @@
1
+ import { extendLocale } from '@axium/client';
2
+ import en from '../locales/en.json' with { type: 'json' };
3
+ import './common.js';
4
+ extendLocale('en', en);
package/lib/Event.svelte CHANGED
@@ -2,6 +2,7 @@
2
2
  import type { EventInitProp } from '@axium/calendar/client';
3
3
  import type { Event } from '@axium/calendar/common';
4
4
  import { eventToICS, formatEventTimes, toRRuleDate } from '@axium/calendar/common';
5
+ import { text } from '@axium/client';
5
6
  import { contextMenu } from '@axium/client/attachments';
6
7
  import { Icon, Popover } from '@axium/client/components';
7
8
  import { colorHashHex, decodeColor, encodeColor } from '@axium/core/color';
@@ -43,8 +44,8 @@
43
44
  {@attach contextMenu(
44
45
  {
45
46
  i: 'pencil',
46
- text: 'Edit',
47
- action: () => {
47
+ text: text('Event.edit'),
48
+ action() {
48
49
  eventEditId = event.id;
49
50
  eventEditCalId = event.calId;
50
51
  eventInit = initData;
@@ -53,13 +54,13 @@
53
54
  },
54
55
  {
55
56
  i: 'file-export',
56
- text: 'Export .ics',
57
+ text: text('Event.export'),
57
58
  action: () => download(event.summary + '.ics', eventToICS(event)),
58
59
  },
59
60
  {
60
61
  i: 'trash-can',
61
- text: 'Delete',
62
- action: () => {
62
+ text: text('Event.delete'),
63
+ action() {
63
64
  eventEditId = event.id;
64
65
  document.querySelector<HTMLDialogElement>('#event-delete')!.showModal();
65
66
  },
@@ -0,0 +1,40 @@
1
+ {
2
+ "app_name": {
3
+ "calendar": "Calendar"
4
+ },
5
+ "Event": {
6
+ "edit": "Edit",
7
+ "export": "Export .ics",
8
+ "delete": "Delete"
9
+ },
10
+ "calendar": {
11
+ "new_event": "New Event",
12
+ "today": "Today",
13
+ "list_owned": "My Calendars",
14
+ "list_shared": "Shared Calendars",
15
+ "delete_confirm": "Are you sure you want to delete the calendar \"{name}\"?",
16
+ "edit": "Edit"
17
+ },
18
+ "calendar_init": {
19
+ "name": "Name",
20
+ "color": "Color",
21
+ "submit_edit": "Save"
22
+ },
23
+ "event_delete": {
24
+ "confirm": "Are you sure you want to delete this event?"
25
+ },
26
+ "event_init": {
27
+ "all_day": "All day",
28
+ "description_placeholder": "Add description",
29
+ "location_placeholder": "Add location",
30
+ "recurrence": {
31
+ "daily": "Every day",
32
+ "monthly_on": "Every month on the {day}",
33
+ "none": "Does not repeat",
34
+ "weekly": "Every week on {day}",
35
+ "yearly": "Every year on {date}"
36
+ },
37
+ "submit_edit": "Update",
38
+ "title_placeholder": "Add title"
39
+ }
40
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@axium/calendar",
3
- "version": "0.3.4",
3
+ "version": "0.4.1",
4
4
  "author": "James Prevett <axium@jamespre.dev>",
5
5
  "description": "Calendar for Axium",
6
6
  "funding": {
@@ -29,6 +29,7 @@
29
29
  "dist",
30
30
  "lib",
31
31
  "routes",
32
+ "locales",
32
33
  "db.json"
33
34
  ],
34
35
  "scripts": {
@@ -50,7 +51,8 @@
50
51
  "server": {
51
52
  "routes": "routes",
52
53
  "hooks": "./dist/hooks.js",
53
- "db": "./db.json"
54
+ "db": "./db.json",
55
+ "web_client_hooks": "./dist/web_hook.js"
54
56
  },
55
57
  "apps": [
56
58
  {
@@ -2,6 +2,7 @@
2
2
  import { getEvents, type EventInitFormData, type EventInitProp } from '@axium/calendar/client';
3
3
  import type { Event } from '@axium/calendar/common';
4
4
  import {
5
+ CalendarInit,
5
6
  dateToInputValue,
6
7
  fromRRuleDate,
7
8
  getCalPermissionsInfo,
@@ -13,9 +14,9 @@
13
14
  withOrdinalSuffix,
14
15
  } from '@axium/calendar/common';
15
16
  import * as Cal from '@axium/calendar/components';
17
+ import { fetchAPI, text } from '@axium/client';
16
18
  import { contextMenu, dynamicRows } from '@axium/client/attachments';
17
19
  import { AccessControlDialog, ColorPicker, FormDialog, Icon, Popover, UserDiscovery } from '@axium/client/components';
18
- import { fetchAPI } from '@axium/client/requests';
19
20
  import { colorHashHex, encodeColor } from '@axium/core/color';
20
21
  import { rrulestr } from 'rrule';
21
22
  import { useSwipe } from 'svelte-gestures';
@@ -70,6 +71,13 @@
70
71
  eventEditId = $state<string>(),
71
72
  eventEditCalId = $state<string>();
72
73
 
74
+ const defaultCalInit = {
75
+ color: null,
76
+ } as CalendarInit;
77
+
78
+ let calInit = $state<CalendarInit>(defaultCalInit),
79
+ calEditId = $state<string>();
80
+
73
81
  const recurringEvents = $derived(
74
82
  events
75
83
  .filter(ev => ev.recurrence)
@@ -82,20 +90,26 @@
82
90
  })
83
91
  );
84
92
 
85
- const defaultEventColor = $derived((eventInit.calendar || calendars[0])?.color || encodeColor(colorHashHex(user.name)));
93
+ const defaultCalColor = encodeColor(colorHashHex(user.name));
94
+ const defaultEventColor = $derived((eventInit.calendar || calendars[0])?.color || defaultCalColor);
86
95
 
87
96
  let calSidebar = $state<HTMLDivElement>();
88
97
  </script>
89
98
 
90
99
  <svelte:head>
91
- <title>Calendar</title>
100
+ <title>{text('app_name.calendar')}</title>
92
101
  </svelte:head>
93
102
 
94
103
  <div id="cal-app">
95
- <button class="event-init icon-text mobile-hide" command="show-modal" commandfor="event-init"><Icon i="plus" /> New Event</button>
104
+ <div id="event-init-container" class="mobile-hide">
105
+ <button class="event-init icon-text" command="show-modal" commandfor="event-init">
106
+ <Icon i="plus" />
107
+ <span>{text('calendar.new_event')}</span>
108
+ </button>
109
+ </div>
96
110
  <div class="bar">
97
111
  <!-- desktop -->
98
- <button class="mobile-hide" onclick={() => start.setTime(today.getTime())}>Today</button>
112
+ <button class="mobile-hide" onclick={() => start.setTime(today.getTime())}>{text('calendar.today')}</button>
99
113
  <button
100
114
  class="reset mobile-hide"
101
115
  onclick={() => {
@@ -126,60 +140,66 @@
126
140
  <div id="cal-sidebar" bind:this={calSidebar}>
127
141
  <Cal.Select bind:start bind:end />
128
142
  <div class="cal-sidebar-header">
129
- <h4>My Calendars</h4>
130
- <button style:display="contents" command="show-modal" commandfor="add-calendar">
143
+ <h4>{text('calendar.list_owned')}</h4>
144
+ <button style:display="contents" command="show-modal" commandfor="cal-init">
131
145
  <Icon i="plus" />
132
146
  </button>
133
147
  </div>
134
148
  {#each calendars.filter(cal => cal.userId == user.id) as cal (cal.id)}
149
+ {@const edit = () => {
150
+ calEditId = cal.id;
151
+ calInit = cal;
152
+ document.querySelector<HTMLDialogElement>('#cal-init')!.showModal();
153
+ }}
135
154
  <div
136
155
  class="cal-sidebar-item"
137
156
  {@attach contextMenu(
138
- { i: 'pencil', text: 'Rename', action: () => dialogs['rename:' + cal.id].showModal() },
139
- { i: 'user-group', text: 'Share', action: () => dialogs['share:' + cal.id].showModal() },
140
- { i: 'trash', text: 'Delete', action: () => dialogs['delete:' + cal.id].showModal() }
157
+ { i: 'pencil', text: text('calendar.edit'), action: edit },
158
+ { i: 'user-group', text: text('generic.share'), action: () => dialogs['share:' + cal.id].showModal() },
159
+ { i: 'trash', text: text('generic.delete'), action: () => dialogs['delete:' + cal.id].showModal() }
141
160
  )}
142
161
  >
143
162
  <span>{cal.name}</span>
144
163
  <Popover showToggle="hover">
145
- <div class="menu-item" onclick={() => dialogs['rename:' + cal.id].showModal()}>
146
- <Icon i="pencil" /> Rename
147
- </div>
164
+ <button
165
+ class="reset menu-item"
166
+ command="show-modal"
167
+ commandfor="cal-init"
168
+ onclick={() => {
169
+ calEditId = cal.id;
170
+ calInit = { ...cal };
171
+ }}
172
+ >
173
+ <Icon i="pencil" />
174
+ <span>{text('calendar.edit')}</span>
175
+ </button>
148
176
  <div class="menu-item" onclick={() => dialogs['share:' + cal.id].showModal()}>
149
- <Icon i="user-group" /> Share
177
+ <Icon i="user-group" />
178
+ <span>{text('generic.share')}</span>
150
179
  </div>
151
180
  <div class="menu-item" onclick={() => dialogs['delete:' + cal.id].showModal()}>
152
- <Icon i="trash" /> Delete
181
+ <Icon i="trash" />
182
+ <span>{text('generic.delete')}</span>
153
183
  </div>
154
184
  </Popover>
155
- <FormDialog
156
- bind:dialog={dialogs['rename:' + cal.id]}
157
- submitText="Save"
158
- submit={(input: { name: string }) =>
159
- fetchAPI('PATCH', 'calendars/:id', input, cal.id).then(result => Object.assign(cal, result))}
160
- >
161
- <div>
162
- <label for="name">Name</label>
163
- <input name="name" type="text" required value={cal.name} />
164
- </div>
165
- </FormDialog>
166
185
  <AccessControlDialog editable itemType="calendars" item={cal} bind:dialog={dialogs['share:' + cal.id]} />
167
186
  <FormDialog
168
187
  bind:dialog={dialogs['delete:' + cal.id]}
169
- submitText="Delete"
188
+ submitText={text('generic.delete')}
170
189
  submitDanger
171
190
  submit={() => fetchAPI('DELETE', 'calendars/:id', null, cal.id).then(() => calendars.splice(calendars.indexOf(cal), 1))}
172
191
  >
173
192
  <p>
174
- Are you sure you want to delete the calendar "{cal.name}"?<br />
175
- <strong>This action cannot be undone.</strong>
193
+ <span>{text('calendar.delete_confirm', { name: cal.name })}</span>
194
+ <br />
195
+ <strong>{text('generic.action_irreversible')}</strong>
176
196
  </p>
177
197
  </FormDialog>
178
198
  </div>
179
199
  {/each}
180
200
  {#if calendars.some(cal => cal.userId != user.id)}
181
201
  <div class="cal-sidebar-header">
182
- <h4>Shared Calendars</h4>
202
+ <h4>{text('calendar.list_shared')}</h4>
183
203
  </div>
184
204
  {#each calendars.filter(cal => cal.userId != user.id) as cal (cal.id)}
185
205
  {@const { list, icon } = getCalPermissionsInfo(cal, user)}
@@ -262,8 +282,12 @@
262
282
  <FormDialog
263
283
  id="event-init"
264
284
  clearOnCancel
265
- cancel={() => (eventInit = defaultEventInit)}
266
- submitText={eventEditId ? 'Update' : 'Create'}
285
+ cancel={() => {
286
+ eventInit = defaultEventInit;
287
+ eventEditId = undefined;
288
+ eventEditCalId = undefined;
289
+ }}
290
+ submitText={text(eventEditId ? 'event_init.submit_edit' : 'generic.create')}
267
291
  submit={async (data: EventInitFormData) => {
268
292
  Object.assign(eventInit, data);
269
293
  const calendar = calendars.find(cal => cal.id == eventInit.calId);
@@ -283,7 +307,7 @@
283
307
  return;
284
308
  }}
285
309
  >
286
- <input name="summary" type="text" required placeholder="Add title" bind:value={eventInit.summary} />
310
+ <input name="summary" type="text" required placeholder={text('event_init.title_placeholder')} bind:value={eventInit.summary} />
287
311
  <div class="event-times-container">
288
312
  <label for="eventInit.start"><Icon i="clock" /></label>
289
313
  <div class="event-times">
@@ -308,22 +332,24 @@
308
332
  <label for="eventInit.isAllDay:checkbox" class="checkbox">
309
333
  {#if eventInit.isAllDay}<Icon i="check" --size="1.3em" />{/if}
310
334
  </label>
311
- <label for="eventInit.isAllDay:checkbox">All day</label>
335
+ <label for="eventInit.isAllDay:checkbox">{text('event_init.all_day')}</label>
312
336
  <div class="spacing"></div>
313
337
  <select name="recurrence" bind:value={eventInit.recurrence}>
314
- <option value="">Does not repeat</option>
315
- <option value="FREQ=DAILY">Every day</option>
338
+ <option value="">{text('event_init.recurrence.none')}</option>
339
+ <option value="FREQ=DAILY">{text('event_init.recurrence.daily')}</option>
316
340
  <option value="FREQ=WEEKLY;BYDAY={toByDay(eventInit.start)}">
317
- Every week on {longWeekDay(eventInit.start)}
341
+ {text('event_init.recurrence.weekly', { day: longWeekDay(eventInit.start) })}
318
342
  </option>
319
343
  <option value="FREQ=MONTHLY;BYDAY={Math.ceil(eventInit.start.getDate() / 7) + toByDay(eventInit.start)}"
320
- >Every month on the {weekDayOfMonth(eventInit.start)}
344
+ >{text('event_init.recurrence.monthly_on', { day: weekDayOfMonth(eventInit.start) })}
321
345
  </option>
322
346
  <option value="FREQ=MONTHLY;BYMONTHDAY={eventInit.start.getDate()}">
323
- Every month on the {withOrdinalSuffix(eventInit.start.getDate())}
347
+ {text('event_init.recurrence.monthly_on', { day: withOrdinalSuffix(eventInit.start.getDate()) })}
324
348
  </option>
325
349
  <option value="FREQ=YEARLY;BYMONTH={eventInit.start.getMonth()};BYMONTHDAY={eventInit.start.getDate()}">
326
- Every year on {eventInit.start.toLocaleDateString('default', { month: 'long', day: 'numeric' })}
350
+ {text('event_init.recurrence.yearly', {
351
+ date: eventInit.start.toLocaleDateString('default', { month: 'long', day: 'numeric' }),
352
+ })}
327
353
  </option>
328
354
  <!-- @todo <option value="">Custom</option> -->
329
355
  </select>
@@ -364,7 +390,12 @@
364
390
 
365
391
  <div>
366
392
  <label for="eventInit.location"><Icon i="location-dot" /></label>
367
- <input name="location" id="eventInit.location" placeholder="Add location" bind:value={eventInit.location} />
393
+ <input
394
+ name="location"
395
+ id="eventInit.location"
396
+ placeholder={text('event_init.location_placeholder')}
397
+ bind:value={eventInit.location}
398
+ />
368
399
  </div>
369
400
 
370
401
  <div>
@@ -372,7 +403,7 @@
372
403
  <textarea
373
404
  name="description"
374
405
  id="eventInit.description"
375
- placeholder="Add description"
406
+ placeholder={text('event_init.description_placeholder')}
376
407
  bind:value={eventInit.description}
377
408
  {@attach dynamicRows()}
378
409
  ></textarea>
@@ -381,7 +412,7 @@
381
412
 
382
413
  <FormDialog
383
414
  id="event-delete"
384
- submitText="Delete"
415
+ submitText={text('generic.delete')}
385
416
  submitDanger
386
417
  submit={async () => {
387
418
  if (!eventEditId) throw new Error('No event to delete');
@@ -392,17 +423,41 @@
392
423
  eventEditId = undefined;
393
424
  }}
394
425
  >
395
- <p>Are you sure you want to delete this event?</p>
426
+ <p>{text('event_delete.confirm')}</p>
396
427
  </FormDialog>
397
428
 
398
429
  <FormDialog
399
- id="add-calendar"
400
- submitText="Create"
401
- submit={(input: { name: string }) =>
402
- fetchAPI('PUT', 'users/:id/calendars', input, user.id).then(cal => calendars.push({ ...cal, acl: [] }))}
430
+ id="cal-init"
431
+ clearOnCancel
432
+ cancel={() => {
433
+ calInit = defaultCalInit;
434
+ calEditId = undefined;
435
+ }}
436
+ submitText={text(calEditId ? 'calendar_init.submit_edit' : 'generic.create')}
437
+ submit={async (data: Record<keyof CalendarInit, string>) => {
438
+ Object.assign(calInit, data);
439
+
440
+ if (!calEditId) {
441
+ const cal = await fetchAPI('PUT', 'users/:id/calendars', calInit, user.id);
442
+ calendars.push({ ...cal, acl: [] });
443
+ return;
444
+ }
445
+
446
+ const cal = calendars.find(cal => cal.id == calEditId);
447
+
448
+ const result = await fetchAPI('PATCH', 'calendars/:id', calInit, calEditId);
449
+
450
+ if (cal) Object.assign(cal, result);
451
+ else console.warn('Could not find calendar to update');
452
+ }}
403
453
  >
404
454
  <div>
405
- <label for="name">Name</label>
406
- <input name="name" type="text" required />
455
+ <label for="calInit.name">{text('calendar_init.name')}</label>
456
+ <input id="calInit.name" bind:value={calInit.name} required />
457
+ </div>
458
+
459
+ <div>
460
+ <label for="calInit.color">{text('calendar_init.color')}</label>
461
+ <ColorPicker bind:value={calInit.color} defaultValue={defaultCalColor} />
407
462
  </div>
408
463
  </FormDialog>
@@ -15,11 +15,18 @@ body:has(#cal-app) {
15
15
  }
16
16
  }
17
17
 
18
+ #event-init-container {
19
+ display: flex;
20
+ align-items: center;
21
+ justify-content: center;
22
+ padding: 0 1em;
23
+ }
24
+
18
25
  button.event-init {
19
- margin: 0.5em;
20
26
  background-color: var(--bg-alt);
21
27
  text-align: center;
22
28
  justify-content: center;
29
+ width: 100%;
23
30
  }
24
31
 
25
32
  form {
@@ -81,6 +88,10 @@ div.attendees {
81
88
  align-items: center;
82
89
  font-weight: bold;
83
90
 
91
+ button {
92
+ font-weight: normal;
93
+ }
94
+
84
95
  @media (width < 700px) {
85
96
  margin: 0 1em;
86
97
 
@@ -224,7 +235,6 @@ div.attendees {
224
235
  inset: 3em 0 0;
225
236
  z-index: 10;
226
237
  background-color: var(--bg-menu);
227
- font-size: clamp(0.95rem, 0.75rem + 2.5cqw, 1.35rem);
228
238
  translate: -100% 0;
229
239
  opacity: 0;
230
240
  visibility: hidden;
@@ -234,6 +244,12 @@ div.attendees {
234
244
  width: 100%;
235
245
  }
236
246
 
247
+ .CalendarSelect,
248
+ .cal-sidebar-header,
249
+ .cal-sidebar-item > span {
250
+ font-size: clamp(0.95rem, 0.75rem + 2.5cqw, 1.35rem);
251
+ }
252
+
237
253
  &.active {
238
254
  translate: 0 0;
239
255
  opacity: 1;