@axium/calendar 0.3.3 → 0.4.0
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 +5 -2
- package/routes/calendar/+page.svelte +127 -51
- 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.0",
|
|
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": {
|
|
@@ -43,13 +44,15 @@
|
|
|
43
44
|
},
|
|
44
45
|
"dependencies": {
|
|
45
46
|
"rrule": "^2.8.1",
|
|
47
|
+
"svelte-gestures": "^5.2.2",
|
|
46
48
|
"zod": "^4.0.5"
|
|
47
49
|
},
|
|
48
50
|
"axium": {
|
|
49
51
|
"server": {
|
|
50
52
|
"routes": "routes",
|
|
51
53
|
"hooks": "./dist/hooks.js",
|
|
52
|
-
"db": "./db.json"
|
|
54
|
+
"db": "./db.json",
|
|
55
|
+
"web_client_hooks": "./dist/web_hook.js"
|
|
53
56
|
},
|
|
54
57
|
"apps": [
|
|
55
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,11 +14,12 @@
|
|
|
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';
|
|
22
|
+
import { useSwipe } from 'svelte-gestures';
|
|
21
23
|
import { SvelteDate } from 'svelte/reactivity';
|
|
22
24
|
import { _throw } from 'utilium';
|
|
23
25
|
import * as z from 'zod';
|
|
@@ -69,6 +71,13 @@
|
|
|
69
71
|
eventEditId = $state<string>(),
|
|
70
72
|
eventEditCalId = $state<string>();
|
|
71
73
|
|
|
74
|
+
const defaultCalInit = {
|
|
75
|
+
color: null,
|
|
76
|
+
} as CalendarInit;
|
|
77
|
+
|
|
78
|
+
let calInit = $state<CalendarInit>(defaultCalInit),
|
|
79
|
+
calEditId = $state<string>();
|
|
80
|
+
|
|
72
81
|
const recurringEvents = $derived(
|
|
73
82
|
events
|
|
74
83
|
.filter(ev => ev.recurrence)
|
|
@@ -81,20 +90,26 @@
|
|
|
81
90
|
})
|
|
82
91
|
);
|
|
83
92
|
|
|
84
|
-
const
|
|
93
|
+
const defaultCalColor = encodeColor(colorHashHex(user.name));
|
|
94
|
+
const defaultEventColor = $derived((eventInit.calendar || calendars[0])?.color || defaultCalColor);
|
|
85
95
|
|
|
86
96
|
let calSidebar = $state<HTMLDivElement>();
|
|
87
97
|
</script>
|
|
88
98
|
|
|
89
99
|
<svelte:head>
|
|
90
|
-
<title>
|
|
100
|
+
<title>{text('app_name.calendar')}</title>
|
|
91
101
|
</svelte:head>
|
|
92
102
|
|
|
93
103
|
<div id="cal-app">
|
|
94
|
-
<
|
|
104
|
+
<div id="event-init-container">
|
|
105
|
+
<button class="event-init icon-text mobile-hide" command="show-modal" commandfor="event-init">
|
|
106
|
+
<Icon i="plus" />
|
|
107
|
+
<span>{text('calendar.new_event')}</span>
|
|
108
|
+
</button>
|
|
109
|
+
</div>
|
|
95
110
|
<div class="bar">
|
|
96
111
|
<!-- desktop -->
|
|
97
|
-
<button class="mobile-hide" onclick={() => start.setTime(today.getTime())}>
|
|
112
|
+
<button class="mobile-hide" onclick={() => start.setTime(today.getTime())}>{text('calendar.today')}</button>
|
|
98
113
|
<button
|
|
99
114
|
class="reset mobile-hide"
|
|
100
115
|
onclick={() => {
|
|
@@ -125,60 +140,66 @@
|
|
|
125
140
|
<div id="cal-sidebar" bind:this={calSidebar}>
|
|
126
141
|
<Cal.Select bind:start bind:end />
|
|
127
142
|
<div class="cal-sidebar-header">
|
|
128
|
-
<h4>
|
|
129
|
-
<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">
|
|
130
145
|
<Icon i="plus" />
|
|
131
146
|
</button>
|
|
132
147
|
</div>
|
|
133
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
|
+
}}
|
|
134
154
|
<div
|
|
135
155
|
class="cal-sidebar-item"
|
|
136
156
|
{@attach contextMenu(
|
|
137
|
-
{ i: 'pencil', text: '
|
|
138
|
-
{ i: 'user-group', text: '
|
|
139
|
-
{ 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() }
|
|
140
160
|
)}
|
|
141
161
|
>
|
|
142
162
|
<span>{cal.name}</span>
|
|
143
163
|
<Popover showToggle="hover">
|
|
144
|
-
<
|
|
145
|
-
|
|
146
|
-
|
|
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>
|
|
147
176
|
<div class="menu-item" onclick={() => dialogs['share:' + cal.id].showModal()}>
|
|
148
|
-
<Icon i="user-group" />
|
|
177
|
+
<Icon i="user-group" />
|
|
178
|
+
<span>{text('generic.share')}</span>
|
|
149
179
|
</div>
|
|
150
180
|
<div class="menu-item" onclick={() => dialogs['delete:' + cal.id].showModal()}>
|
|
151
|
-
<Icon i="trash" />
|
|
181
|
+
<Icon i="trash" />
|
|
182
|
+
<span>{text('generic.delete')}</span>
|
|
152
183
|
</div>
|
|
153
184
|
</Popover>
|
|
154
|
-
<FormDialog
|
|
155
|
-
bind:dialog={dialogs['rename:' + cal.id]}
|
|
156
|
-
submitText="Save"
|
|
157
|
-
submit={(input: { name: string }) =>
|
|
158
|
-
fetchAPI('PATCH', 'calendars/:id', input, cal.id).then(result => Object.assign(cal, result))}
|
|
159
|
-
>
|
|
160
|
-
<div>
|
|
161
|
-
<label for="name">Name</label>
|
|
162
|
-
<input name="name" type="text" required value={cal.name} />
|
|
163
|
-
</div>
|
|
164
|
-
</FormDialog>
|
|
165
185
|
<AccessControlDialog editable itemType="calendars" item={cal} bind:dialog={dialogs['share:' + cal.id]} />
|
|
166
186
|
<FormDialog
|
|
167
187
|
bind:dialog={dialogs['delete:' + cal.id]}
|
|
168
|
-
submitText=
|
|
188
|
+
submitText={text('generic.delete')}
|
|
169
189
|
submitDanger
|
|
170
190
|
submit={() => fetchAPI('DELETE', 'calendars/:id', null, cal.id).then(() => calendars.splice(calendars.indexOf(cal), 1))}
|
|
171
191
|
>
|
|
172
192
|
<p>
|
|
173
|
-
|
|
174
|
-
<
|
|
193
|
+
<span>{text('calendar.delete_confirm', { name: cal.name })}</span>
|
|
194
|
+
<br />
|
|
195
|
+
<strong>{text('generic.action_irreversible')}</strong>
|
|
175
196
|
</p>
|
|
176
197
|
</FormDialog>
|
|
177
198
|
</div>
|
|
178
199
|
{/each}
|
|
179
200
|
{#if calendars.some(cal => cal.userId != user.id)}
|
|
180
201
|
<div class="cal-sidebar-header">
|
|
181
|
-
<h4>
|
|
202
|
+
<h4>{text('calendar.list_shared')}</h4>
|
|
182
203
|
</div>
|
|
183
204
|
{#each calendars.filter(cal => cal.userId != user.id) as cal (cal.id)}
|
|
184
205
|
{@const { list, icon } = getCalPermissionsInfo(cal, user)}
|
|
@@ -188,7 +209,27 @@
|
|
|
188
209
|
{/each}
|
|
189
210
|
{/if}
|
|
190
211
|
</div>
|
|
191
|
-
<div
|
|
212
|
+
<div
|
|
213
|
+
id="cal"
|
|
214
|
+
{...useSwipe(
|
|
215
|
+
e => {
|
|
216
|
+
if (e.detail.pointerType != 'touch') return;
|
|
217
|
+
switch (e.detail.direction) {
|
|
218
|
+
case 'left':
|
|
219
|
+
start.setDate(start.getDate() + spanDays);
|
|
220
|
+
end.setDate(end.getDate() + spanDays);
|
|
221
|
+
break;
|
|
222
|
+
case 'right':
|
|
223
|
+
start.setDate(start.getDate() - spanDays);
|
|
224
|
+
end.setDate(end.getDate() - spanDays);
|
|
225
|
+
break;
|
|
226
|
+
case 'top':
|
|
227
|
+
case 'bottom':
|
|
228
|
+
}
|
|
229
|
+
},
|
|
230
|
+
() => ({ touchAction: 'pan-y' })
|
|
231
|
+
)}
|
|
232
|
+
>
|
|
192
233
|
<div id="hours" class="subtle">
|
|
193
234
|
{#each { length: 24 }, i}
|
|
194
235
|
{#if !i}
|
|
@@ -241,8 +282,12 @@
|
|
|
241
282
|
<FormDialog
|
|
242
283
|
id="event-init"
|
|
243
284
|
clearOnCancel
|
|
244
|
-
cancel={() =>
|
|
245
|
-
|
|
285
|
+
cancel={() => {
|
|
286
|
+
eventInit = defaultEventInit;
|
|
287
|
+
eventEditId = undefined;
|
|
288
|
+
eventEditCalId = undefined;
|
|
289
|
+
}}
|
|
290
|
+
submitText={text(eventEditId ? 'event_init.submit_edit' : 'generic.create')}
|
|
246
291
|
submit={async (data: EventInitFormData) => {
|
|
247
292
|
Object.assign(eventInit, data);
|
|
248
293
|
const calendar = calendars.find(cal => cal.id == eventInit.calId);
|
|
@@ -262,7 +307,7 @@
|
|
|
262
307
|
return;
|
|
263
308
|
}}
|
|
264
309
|
>
|
|
265
|
-
<input name="summary" type="text" required placeholder=
|
|
310
|
+
<input name="summary" type="text" required placeholder={text('event_init.title_placeholder')} bind:value={eventInit.summary} />
|
|
266
311
|
<div class="event-times-container">
|
|
267
312
|
<label for="eventInit.start"><Icon i="clock" /></label>
|
|
268
313
|
<div class="event-times">
|
|
@@ -287,22 +332,24 @@
|
|
|
287
332
|
<label for="eventInit.isAllDay:checkbox" class="checkbox">
|
|
288
333
|
{#if eventInit.isAllDay}<Icon i="check" --size="1.3em" />{/if}
|
|
289
334
|
</label>
|
|
290
|
-
<label for="eventInit.isAllDay:checkbox">
|
|
335
|
+
<label for="eventInit.isAllDay:checkbox">{text('event_init.all_day')}</label>
|
|
291
336
|
<div class="spacing"></div>
|
|
292
337
|
<select name="recurrence" bind:value={eventInit.recurrence}>
|
|
293
|
-
<option value="">
|
|
294
|
-
<option value="FREQ=DAILY">
|
|
338
|
+
<option value="">{text('event_init.recurrence.none')}</option>
|
|
339
|
+
<option value="FREQ=DAILY">{text('event_init.recurrence.daily')}</option>
|
|
295
340
|
<option value="FREQ=WEEKLY;BYDAY={toByDay(eventInit.start)}">
|
|
296
|
-
|
|
341
|
+
{text('event_init.recurrence.weekly', { day: longWeekDay(eventInit.start) })}
|
|
297
342
|
</option>
|
|
298
343
|
<option value="FREQ=MONTHLY;BYDAY={Math.ceil(eventInit.start.getDate() / 7) + toByDay(eventInit.start)}"
|
|
299
|
-
>
|
|
344
|
+
>{text('event_init.recurrence.monthly_on', { day: weekDayOfMonth(eventInit.start) })}
|
|
300
345
|
</option>
|
|
301
346
|
<option value="FREQ=MONTHLY;BYMONTHDAY={eventInit.start.getDate()}">
|
|
302
|
-
|
|
347
|
+
{text('event_init.recurrence.monthly_on', { day: withOrdinalSuffix(eventInit.start.getDate()) })}
|
|
303
348
|
</option>
|
|
304
349
|
<option value="FREQ=YEARLY;BYMONTH={eventInit.start.getMonth()};BYMONTHDAY={eventInit.start.getDate()}">
|
|
305
|
-
|
|
350
|
+
{text('event_init.recurrence.yearly', {
|
|
351
|
+
date: eventInit.start.toLocaleDateString('default', { month: 'long', day: 'numeric' }),
|
|
352
|
+
})}
|
|
306
353
|
</option>
|
|
307
354
|
<!-- @todo <option value="">Custom</option> -->
|
|
308
355
|
</select>
|
|
@@ -343,7 +390,12 @@
|
|
|
343
390
|
|
|
344
391
|
<div>
|
|
345
392
|
<label for="eventInit.location"><Icon i="location-dot" /></label>
|
|
346
|
-
<input
|
|
393
|
+
<input
|
|
394
|
+
name="location"
|
|
395
|
+
id="eventInit.location"
|
|
396
|
+
placeholder={text('event_init.location_placeholder')}
|
|
397
|
+
bind:value={eventInit.location}
|
|
398
|
+
/>
|
|
347
399
|
</div>
|
|
348
400
|
|
|
349
401
|
<div>
|
|
@@ -351,7 +403,7 @@
|
|
|
351
403
|
<textarea
|
|
352
404
|
name="description"
|
|
353
405
|
id="eventInit.description"
|
|
354
|
-
placeholder=
|
|
406
|
+
placeholder={text('event_init.description_placeholder')}
|
|
355
407
|
bind:value={eventInit.description}
|
|
356
408
|
{@attach dynamicRows()}
|
|
357
409
|
></textarea>
|
|
@@ -360,7 +412,7 @@
|
|
|
360
412
|
|
|
361
413
|
<FormDialog
|
|
362
414
|
id="event-delete"
|
|
363
|
-
submitText=
|
|
415
|
+
submitText={text('generic.delete')}
|
|
364
416
|
submitDanger
|
|
365
417
|
submit={async () => {
|
|
366
418
|
if (!eventEditId) throw new Error('No event to delete');
|
|
@@ -371,17 +423,41 @@
|
|
|
371
423
|
eventEditId = undefined;
|
|
372
424
|
}}
|
|
373
425
|
>
|
|
374
|
-
<p>
|
|
426
|
+
<p>{text('event_delete.confirm')}</p>
|
|
375
427
|
</FormDialog>
|
|
376
428
|
|
|
377
429
|
<FormDialog
|
|
378
|
-
id="
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
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
|
+
}}
|
|
382
453
|
>
|
|
383
454
|
<div>
|
|
384
|
-
<label for="name">
|
|
385
|
-
<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} />
|
|
386
462
|
</div>
|
|
387
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;
|