@svar-ui/calendar-ical 2.6.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.
@@ -0,0 +1,18 @@
1
+ //#region src/types.d.ts
2
+ type EventID = string | number;
3
+ interface CalendarEvent {
4
+ id: EventID;
5
+ start: Date;
6
+ end: Date;
7
+ allDay?: boolean;
8
+ text?: string;
9
+ [key: string]: any;
10
+ }
11
+ //#endregion
12
+ //#region src/parse.d.ts
13
+ declare function parseICal(ics: string): CalendarEvent[];
14
+ //#endregion
15
+ //#region src/serialize.d.ts
16
+ declare function serializeICal(events: CalendarEvent[]): string;
17
+ //#endregion
18
+ export { type CalendarEvent, type EventID, parseICal, serializeICal };
package/dist/index.mjs ADDED
@@ -0,0 +1,119 @@
1
+ //#region src/parse.ts
2
+ function unfold(ics) {
3
+ return ics.replace(/\r?\n[ \t]/g, "");
4
+ }
5
+ function parseDate(value) {
6
+ if (/^\d{8}$/.test(value)) {
7
+ const y = +value.slice(0, 4);
8
+ const m = +value.slice(4, 6) - 1;
9
+ const d = +value.slice(6, 8);
10
+ return {
11
+ date: new Date(y, m, d),
12
+ allDay: true
13
+ };
14
+ }
15
+ if (value.endsWith("Z")) {
16
+ const y = +value.slice(0, 4);
17
+ const mo = +value.slice(4, 6) - 1;
18
+ const d = +value.slice(6, 8);
19
+ const h = +value.slice(9, 11);
20
+ const mi = +value.slice(11, 13);
21
+ const s = +value.slice(13, 15);
22
+ return {
23
+ date: new Date(Date.UTC(y, mo, d, h, mi, s)),
24
+ allDay: false
25
+ };
26
+ }
27
+ const y = +value.slice(0, 4);
28
+ const mo = +value.slice(4, 6) - 1;
29
+ const d = +value.slice(6, 8);
30
+ const h = +value.slice(9, 11);
31
+ const mi = +value.slice(11, 13);
32
+ const s = +value.slice(13, 15);
33
+ return {
34
+ date: new Date(y, mo, d, h, mi, s),
35
+ allDay: false
36
+ };
37
+ }
38
+ function unescapeText(value) {
39
+ return value.replace(/\\n/g, "\n").replace(/\\,/g, ",").replace(/\\;/g, ";").replace(/\\\\/g, "\\");
40
+ }
41
+ function parseICal(ics) {
42
+ const lines = unfold(ics).split(/\r?\n/);
43
+ const events = [];
44
+ let current = null;
45
+ for (const line of lines) if (line === "BEGIN:VEVENT") current = {};
46
+ else if (line === "END:VEVENT" && current) {
47
+ const uid = current["UID"];
48
+ const startRaw = current["DTSTART"] ?? "";
49
+ const endRaw = current["DTEND"] ?? "";
50
+ if (startRaw) {
51
+ const { date: start, allDay } = parseDate(startRaw);
52
+ const { date: end } = endRaw ? parseDate(endRaw) : { date: start };
53
+ const event = {
54
+ id: uid ? isNaN(Number(uid)) ? uid : Number(uid) : `${Date.now()}-${Math.random().toString(36).slice(2)}`,
55
+ start,
56
+ end
57
+ };
58
+ if (allDay) event.allDay = true;
59
+ if (current["SUMMARY"]) event.text = unescapeText(current["SUMMARY"]);
60
+ if (current["DESCRIPTION"]) event.description = unescapeText(current["DESCRIPTION"]);
61
+ events.push(event);
62
+ }
63
+ current = null;
64
+ } else if (current !== null) {
65
+ const colonIdx = line.indexOf(":");
66
+ if (colonIdx === -1) continue;
67
+ const baseKey = line.slice(0, colonIdx).split(";")[0];
68
+ const value = line.slice(colonIdx + 1);
69
+ current[baseKey] = value;
70
+ }
71
+ return events;
72
+ }
73
+ //#endregion
74
+ //#region src/serialize.ts
75
+ function formatDate(date, allDay) {
76
+ if (allDay) return `${date.getFullYear()}${String(date.getMonth() + 1).padStart(2, "0")}${String(date.getDate()).padStart(2, "0")}`;
77
+ return `${date.getUTCFullYear()}${String(date.getUTCMonth() + 1).padStart(2, "0")}${String(date.getUTCDate()).padStart(2, "0")}T${String(date.getUTCHours()).padStart(2, "0")}${String(date.getUTCMinutes()).padStart(2, "0")}${String(date.getUTCSeconds()).padStart(2, "0")}Z`;
78
+ }
79
+ function escapeText(value) {
80
+ return value.replace(/\\/g, "\\\\").replace(/,/g, "\\,").replace(/;/g, "\\;").replace(/\n/g, "\\n");
81
+ }
82
+ function foldLine(line) {
83
+ if (line.length <= 75) return line;
84
+ const chunks = [line.slice(0, 75)];
85
+ let i = 75;
86
+ while (i < line.length) {
87
+ chunks.push(" " + line.slice(i, i + 74));
88
+ i += 74;
89
+ }
90
+ return chunks.join("\r\n");
91
+ }
92
+ function serializeICal(events) {
93
+ const now = formatDate(/* @__PURE__ */ new Date(), false);
94
+ const lines = [
95
+ "BEGIN:VCALENDAR",
96
+ "VERSION:2.0",
97
+ "PRODID:-//wx//calendar-ical//EN"
98
+ ];
99
+ for (const ev of events) {
100
+ const allDay = ev.allDay ?? false;
101
+ lines.push("BEGIN:VEVENT");
102
+ lines.push(`UID:${ev.id}`);
103
+ lines.push(`DTSTAMP:${now}`);
104
+ if (allDay) {
105
+ lines.push(`DTSTART;VALUE=DATE:${formatDate(ev.start, true)}`);
106
+ lines.push(`DTEND;VALUE=DATE:${formatDate(ev.end, true)}`);
107
+ } else {
108
+ lines.push(`DTSTART:${formatDate(ev.start, false)}`);
109
+ lines.push(`DTEND:${formatDate(ev.end, false)}`);
110
+ }
111
+ if (ev.text) lines.push(`SUMMARY:${escapeText(ev.text)}`);
112
+ if (ev.description) lines.push(`DESCRIPTION:${escapeText(String(ev.description))}`);
113
+ lines.push("END:VEVENT");
114
+ }
115
+ lines.push("END:VCALENDAR");
116
+ return lines.map(foldLine).join("\r\n") + "\r\n";
117
+ }
118
+ //#endregion
119
+ export { parseICal, serializeICal };
package/package.json ADDED
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "@svar-ui/calendar-ical",
3
+ "version": "2.6.0",
4
+ "description": "iCal import/export for SVAR Calendar",
5
+ "license": "MIT",
6
+ "files": [
7
+ "dist",
8
+ "license.txt"
9
+ ],
10
+ "type": "module",
11
+ "exports": {
12
+ ".": "./dist/index.mjs",
13
+ "./package.json": "./package.json"
14
+ },
15
+ "publishConfig": {
16
+ "access": "public"
17
+ },
18
+ "devDependencies": {
19
+ "@typescript/native-preview": "7.0.0-dev.20260316.1",
20
+ "typescript": "^5.9.3",
21
+ "vite-plus": "0.1.16"
22
+ },
23
+ "scripts": {
24
+ "build": "vp pack",
25
+ "watch": "vp pack --watch",
26
+ "test": "vp test",
27
+ "check": "vp check"
28
+ }
29
+ }