@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 +9 -6
- package/dist/common.js +1 -1
- package/dist/server.js +2 -2
- package/dist/web_hook.d.ts +8 -0
- package/dist/web_hook.js +4 -0
- package/lib/Event.svelte +6 -5
- package/locales/en.json +40 -0
- package/package.json +4 -2
- package/routes/calendar/+page.svelte +105 -50
- package/routes/calendar/cal.css +18 -2
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()
|
package/dist/web_hook.js
ADDED
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: '
|
|
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: '
|
|
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: '
|
|
62
|
-
action
|
|
62
|
+
text: text('Event.delete'),
|
|
63
|
+
action() {
|
|
63
64
|
eventEditId = event.id;
|
|
64
65
|
document.querySelector<HTMLDialogElement>('#event-delete')!.showModal();
|
|
65
66
|
},
|
package/locales/en.json
ADDED
|
@@ -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
|
+
"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
|
|
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>
|
|
100
|
+
<title>{text('app_name.calendar')}</title>
|
|
92
101
|
</svelte:head>
|
|
93
102
|
|
|
94
103
|
<div id="cal-app">
|
|
95
|
-
<
|
|
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())}>
|
|
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>
|
|
130
|
-
<button style:display="contents" command="show-modal" commandfor="
|
|
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: '
|
|
139
|
-
{ i: 'user-group', text: '
|
|
140
|
-
{ i: 'trash', text: '
|
|
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
|
-
<
|
|
146
|
-
|
|
147
|
-
|
|
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" />
|
|
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" />
|
|
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=
|
|
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
|
-
|
|
175
|
-
<
|
|
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>
|
|
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={() =>
|
|
266
|
-
|
|
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=
|
|
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">
|
|
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="">
|
|
315
|
-
<option value="FREQ=DAILY">
|
|
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
|
-
|
|
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
|
-
>
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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=
|
|
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=
|
|
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>
|
|
426
|
+
<p>{text('event_delete.confirm')}</p>
|
|
396
427
|
</FormDialog>
|
|
397
428
|
|
|
398
429
|
<FormDialog
|
|
399
|
-
id="
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
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">
|
|
406
|
-
<input
|
|
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>
|
package/routes/calendar/cal.css
CHANGED
|
@@ -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;
|