diff --git a/package-lock.json b/package-lock.json index 54e7176c3..bd26aeae3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,6 +24,7 @@ "@typescript-eslint/eslint-plugin": "^4.31.1", "@typescript-eslint/parser": "^4.31.1", "axios": "^0.24.0", + "dayjs": "^1.10.8", "discord.js": "^13.2.0", "dotenv": "^10.0.0", "eslint": "^7.32.0", @@ -31,6 +32,7 @@ "node-fetch": "^3.0.0", "prettier": "^2.4.1", "prettier-plugin-svelte": "^2.4.0", + "query-string": "^7.1.1", "sass": "^1.42.1", "size-limit": "^7.0.4", "svelte": "^3.42.6", @@ -904,14 +906,20 @@ } }, "node_modules/data-uri-to-buffer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-3.0.1.tgz", - "integrity": "sha512-WboRycPNsVw3B3TL559F7kuBUM4d8CgMEvk6xEJlOp7OBPjt6G7z8WMWlD2rOFZLk6OYfFIUGsCOWzcQH9K2og==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.0.tgz", + "integrity": "sha512-Vr3mLBA8qWmcuschSLAOogKgQ/Jwxulv3RNE4FXnYWRGujzrRWQI4m12fQqRkwX06C0KanhLr4hK+GydchZsaA==", "dev": true, "engines": { - "node": ">= 6" + "node": ">= 12" } }, + "node_modules/dayjs": { + "version": "1.10.8", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.10.8.tgz", + "integrity": "sha512-wbNwDfBHHur9UOzNUjeKUOJ0fCb0a52Wx0xInmQ7Y8FstyajiV1NmK1e00cxsr9YrE9r7yAChE0VvpuY5Rnlow==", + "dev": true + }, "node_modules/debug": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", @@ -929,6 +937,15 @@ } } }, + "node_modules/decode-uri-component": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", + "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", + "dev": true, + "engines": { + "node": ">=0.10" + } + }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -1008,15 +1025,23 @@ } }, "node_modules/discord.js/node_modules/node-fetch": { - "version": "2.6.5", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.5.tgz", - "integrity": "sha512-mmlIVHJEu5rnIxgEgez6b9GgWXbkZj5YZ7fx+2r94a2E+Uirsp6HsPTPlomfdHtpt/B0cdKviwkoaM6pyvUOpQ==", + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", "dev": true, "dependencies": { "whatwg-url": "^5.0.0" }, "engines": { "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } } }, "node_modules/doctrine": { @@ -1675,9 +1700,9 @@ } }, "node_modules/fetch-blob": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.1.2.tgz", - "integrity": "sha512-hunJbvy/6OLjCD0uuhLdp0mMPzP/yd2ssd1t2FCJsaA7wkWhpbp9xfuNVpv7Ll4jFhzp6T4LAupSiV9uOeg0VQ==", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.1.4.tgz", + "integrity": "sha512-Eq5Xv5+VlSrYWEqKrusxY1C3Hm/hjeAsCGVG3ft7pZahlUAChpGZT/Ms1WmSLnEAisEXszjzu/s+ce6HZB2VHA==", "dev": true, "funding": [ { @@ -1690,6 +1715,7 @@ } ], "dependencies": { + "node-domexception": "^1.0.0", "web-streams-polyfill": "^3.0.3" }, "engines": { @@ -1720,6 +1746,15 @@ "node": ">=8" } }, + "node_modules/filter-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/filter-obj/-/filter-obj-1.1.0.tgz", + "integrity": "sha1-mzERErxsYSehbgFsbF1/GeCAXFs=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/flat-cache": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", @@ -1740,9 +1775,9 @@ "dev": true }, "node_modules/follow-redirects": { - "version": "1.14.5", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.5.tgz", - "integrity": "sha512-wtphSXy7d4/OR+MvIFbCVBDzZ5520qV8XfPklSN5QtxuMUJZ+b0Wnst1e1lCDocfzuCkHqj8k0FpZqO+UIaKNA==", + "version": "1.14.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.9.tgz", + "integrity": "sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w==", "dev": true, "funding": [ { @@ -1759,6 +1794,18 @@ } } }, + "node_modules/formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "dev": true, + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -2282,9 +2329,9 @@ "dev": true }, "node_modules/nanoid": { - "version": "3.1.29", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.29.tgz", - "integrity": "sha512-dW2pUSGZ8ZnCFIlBIA31SV8huOGCHb6OwzVCc7A69rb/a+SgPBwfmLvK5TKQ3INPbRkcI8a/Owo0XbiTNH19wg==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.1.tgz", + "integrity": "sha512-n6Vs/3KGyxPQd6uO0eH4Bv0ojGSUvuLlIHtC3Y0kEO23YRge8H9x1GCzLn28YX0H66pMkxuaeESFq4tKISKwdw==", "dev": true, "bin": { "nanoid": "bin/nanoid.cjs" @@ -2335,14 +2382,34 @@ "url": "https://nearley.js.org/#give-to-nearley" } }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "engines": { + "node": ">=10.5.0" + } + }, "node_modules/node-fetch": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.0.0.tgz", - "integrity": "sha512-bKMI+C7/T/SPU1lKnbQbwxptpCrG9ashG+VkytmXCPZyuM9jB6VU+hY0oi4lC8LxTtAeWdckNCTa3nrGsAdA3Q==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.2.1.tgz", + "integrity": "sha512-Ef3SPFtRWFCDyhvcwCSvacLpkwmYZcD57mmZzAsMiks9TpHpIghe32U9H06tMICnr+X7YCpzH7WvUlUoml2urA==", "dev": true, "dependencies": { - "data-uri-to-buffer": "^3.0.1", - "fetch-blob": "^3.1.2" + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" }, "engines": { "node": "^12.20.0 || ^14.13.1 || >=16.0.0" @@ -2566,6 +2633,24 @@ "node": ">=6" } }, + "node_modules/query-string": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/query-string/-/query-string-7.1.1.tgz", + "integrity": "sha512-MplouLRDHBZSG9z7fpuAAcI7aAYjDLhtsiVZsevsfaHWDS2IDdORKbSd1kWUA+V4zyva/HZoSfpwnYMMQDhb0w==", + "dev": true, + "dependencies": { + "decode-uri-component": "^0.2.0", + "filter-obj": "^1.1.0", + "split-on-first": "^1.0.0", + "strict-uri-encode": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -2993,12 +3078,30 @@ "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", "dev": true }, + "node_modules/split-on-first": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/split-on-first/-/split-on-first-1.1.0.tgz", + "integrity": "sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", "dev": true }, + "node_modules/strict-uri-encode": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz", + "integrity": "sha1-ucczDHBChi9rFC3CdLvMWGbONUY=", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", @@ -3363,9 +3466,9 @@ } }, "node_modules/web-streams-polyfill": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.1.1.tgz", - "integrity": "sha512-Czi3fG883e96T4DLEPRvufrF2ydhOOW1+1a6c3gNjH2aIh50DNFBdfwh2AKoOf1rXvpvavAoA11Qdq9+BKjE0Q==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.0.tgz", + "integrity": "sha512-EqPmREeOzttaLRm5HS7io98goBgZ7IVz79aDvqjD0kYXLtFZTc0T/U6wHTPKyIjb+MdN7DFIIX6hgdBEpWmfPA==", "dev": true, "engines": { "node": ">= 8" @@ -4079,9 +4182,15 @@ } }, "data-uri-to-buffer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-3.0.1.tgz", - "integrity": "sha512-WboRycPNsVw3B3TL559F7kuBUM4d8CgMEvk6xEJlOp7OBPjt6G7z8WMWlD2rOFZLk6OYfFIUGsCOWzcQH9K2og==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.0.tgz", + "integrity": "sha512-Vr3mLBA8qWmcuschSLAOogKgQ/Jwxulv3RNE4FXnYWRGujzrRWQI4m12fQqRkwX06C0KanhLr4hK+GydchZsaA==", + "dev": true + }, + "dayjs": { + "version": "1.10.8", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.10.8.tgz", + "integrity": "sha512-wbNwDfBHHur9UOzNUjeKUOJ0fCb0a52Wx0xInmQ7Y8FstyajiV1NmK1e00cxsr9YrE9r7yAChE0VvpuY5Rnlow==", "dev": true }, "debug": { @@ -4093,6 +4202,12 @@ "ms": "2.1.2" } }, + "decode-uri-component": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", + "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", + "dev": true + }, "deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -4153,9 +4268,9 @@ }, "dependencies": { "node-fetch": { - "version": "2.6.5", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.5.tgz", - "integrity": "sha512-mmlIVHJEu5rnIxgEgez6b9GgWXbkZj5YZ7fx+2r94a2E+Uirsp6HsPTPlomfdHtpt/B0cdKviwkoaM6pyvUOpQ==", + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", "dev": true, "requires": { "whatwg-url": "^5.0.0" @@ -4611,11 +4726,12 @@ } }, "fetch-blob": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.1.2.tgz", - "integrity": "sha512-hunJbvy/6OLjCD0uuhLdp0mMPzP/yd2ssd1t2FCJsaA7wkWhpbp9xfuNVpv7Ll4jFhzp6T4LAupSiV9uOeg0VQ==", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.1.4.tgz", + "integrity": "sha512-Eq5Xv5+VlSrYWEqKrusxY1C3Hm/hjeAsCGVG3ft7pZahlUAChpGZT/Ms1WmSLnEAisEXszjzu/s+ce6HZB2VHA==", "dev": true, "requires": { + "node-domexception": "^1.0.0", "web-streams-polyfill": "^3.0.3" } }, @@ -4637,6 +4753,12 @@ "to-regex-range": "^5.0.1" } }, + "filter-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/filter-obj/-/filter-obj-1.1.0.tgz", + "integrity": "sha1-mzERErxsYSehbgFsbF1/GeCAXFs=", + "dev": true + }, "flat-cache": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", @@ -4654,11 +4776,20 @@ "dev": true }, "follow-redirects": { - "version": "1.14.5", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.5.tgz", - "integrity": "sha512-wtphSXy7d4/OR+MvIFbCVBDzZ5520qV8XfPklSN5QtxuMUJZ+b0Wnst1e1lCDocfzuCkHqj8k0FpZqO+UIaKNA==", + "version": "1.14.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.9.tgz", + "integrity": "sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w==", "dev": true }, + "formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "dev": true, + "requires": { + "fetch-blob": "^3.1.2" + } + }, "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -5056,9 +5187,9 @@ "dev": true }, "nanoid": { - "version": "3.1.29", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.29.tgz", - "integrity": "sha512-dW2pUSGZ8ZnCFIlBIA31SV8huOGCHb6OwzVCc7A69rb/a+SgPBwfmLvK5TKQ3INPbRkcI8a/Owo0XbiTNH19wg==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.1.tgz", + "integrity": "sha512-n6Vs/3KGyxPQd6uO0eH4Bv0ojGSUvuLlIHtC3Y0kEO23YRge8H9x1GCzLn28YX0H66pMkxuaeESFq4tKISKwdw==", "dev": true }, "nanospinner": { @@ -5095,14 +5226,21 @@ "randexp": "0.4.6" } }, + "node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "dev": true + }, "node-fetch": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.0.0.tgz", - "integrity": "sha512-bKMI+C7/T/SPU1lKnbQbwxptpCrG9ashG+VkytmXCPZyuM9jB6VU+hY0oi4lC8LxTtAeWdckNCTa3nrGsAdA3Q==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.2.1.tgz", + "integrity": "sha512-Ef3SPFtRWFCDyhvcwCSvacLpkwmYZcD57mmZzAsMiks9TpHpIghe32U9H06tMICnr+X7YCpzH7WvUlUoml2urA==", "dev": true, "requires": { - "data-uri-to-buffer": "^3.0.1", - "fetch-blob": "^3.1.2" + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" } }, "normalize-path": { @@ -5257,6 +5395,18 @@ "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", "dev": true }, + "query-string": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/query-string/-/query-string-7.1.1.tgz", + "integrity": "sha512-MplouLRDHBZSG9z7fpuAAcI7aAYjDLhtsiVZsevsfaHWDS2IDdORKbSd1kWUA+V4zyva/HZoSfpwnYMMQDhb0w==", + "dev": true, + "requires": { + "decode-uri-component": "^0.2.0", + "filter-obj": "^1.1.0", + "split-on-first": "^1.0.0", + "strict-uri-encode": "^2.0.0" + } + }, "queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -5556,12 +5706,24 @@ "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", "dev": true }, + "split-on-first": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/split-on-first/-/split-on-first-1.1.0.tgz", + "integrity": "sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==", + "dev": true + }, "sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", "dev": true }, + "strict-uri-encode": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz", + "integrity": "sha1-ucczDHBChi9rFC3CdLvMWGbONUY=", + "dev": true + }, "string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", @@ -5790,9 +5952,9 @@ } }, "web-streams-polyfill": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.1.1.tgz", - "integrity": "sha512-Czi3fG883e96T4DLEPRvufrF2ydhOOW1+1a6c3gNjH2aIh50DNFBdfwh2AKoOf1rXvpvavAoA11Qdq9+BKjE0Q==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.0.tgz", + "integrity": "sha512-EqPmREeOzttaLRm5HS7io98goBgZ7IVz79aDvqjD0kYXLtFZTc0T/U6wHTPKyIjb+MdN7DFIIX6hgdBEpWmfPA==", "dev": true }, "webidl-conversions": { diff --git a/package.json b/package.json index 187fbd663..8303cff3e 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,7 @@ "@typescript-eslint/eslint-plugin": "^4.31.1", "@typescript-eslint/parser": "^4.31.1", "axios": "^0.24.0", + "dayjs": "^1.10.8", "discord.js": "^13.2.0", "dotenv": "^10.0.0", "eslint": "^7.32.0", @@ -46,6 +47,7 @@ "node-fetch": "^3.0.0", "prettier": "^2.4.1", "prettier-plugin-svelte": "^2.4.0", + "query-string": "^7.1.1", "sass": "^1.42.1", "size-limit": "^7.0.4", "svelte": "^3.42.6", diff --git a/src/lib/calendar-link/README.md b/src/lib/calendar-link/README.md new file mode 100644 index 000000000..8bae55f77 --- /dev/null +++ b/src/lib/calendar-link/README.md @@ -0,0 +1,6 @@ +# `calendar-link` + +## Credit + +- Demo: +- Repository: diff --git a/src/lib/calendar-link/calendar-link.ts b/src/lib/calendar-link/calendar-link.ts new file mode 100644 index 000000000..267b88855 --- /dev/null +++ b/src/lib/calendar-link/calendar-link.ts @@ -0,0 +1,178 @@ +import dayjs from 'dayjs'; +import utc from 'dayjs/plugin/utc.js'; +import { stringify } from 'query-string'; + +import type { CalendarEvent, NormalizedCalendarEvent, Google, Outlook, Yahoo } from './interfaces'; +import { TimeFormats } from './utils'; + +dayjs.extend(utc); + +function formatTimes( + { startUtc, endUtc }: NormalizedCalendarEvent, + dateTimeFormat: keyof typeof TimeFormats +): { start: string; end: string } { + const format = TimeFormats[dateTimeFormat]; + return { start: startUtc.format(format), end: endUtc.format(format) }; +} + +export function eventify(event: CalendarEvent): NormalizedCalendarEvent { + const { start, end, duration, ...rest } = event; + const startUtc = dayjs(start).utc(); + const endUtc = end + ? dayjs(end).utc() + : (() => { + if (event.allDay) { + return startUtc.add(1, 'day'); + } + if (duration && duration.length == 2) { + const value = Number(duration[0]); + const unit = duration[1]; + return startUtc.add(value, unit); + } + return dayjs().utc(); + })(); + return { + ...rest, + startUtc, + endUtc, + }; +} + +export function google(calendarEvent: CalendarEvent): string { + const event = eventify(calendarEvent); + const { start, end } = formatTimes(event, event.allDay ? 'allDay' : 'dateTimeUTC'); + const details: Google = { + action: 'TEMPLATE', + text: event.title, + details: event.description, + location: event.location, + trp: event.busy, + dates: start + '/' + end, + }; + if (event.guests && event.guests.length) { + details.add = event.guests.join(); + } + return `https://calendar.google.com/calendar/render?${stringify(details)}`; +} + +export function outlook(calendarEvent: CalendarEvent): string { + const event = eventify(calendarEvent); + const { start, end } = formatTimes(event, 'dateTimeWithOffset'); + const details: Outlook = { + path: '/calendar/action/compose', + rru: 'addevent', + startdt: start, + enddt: end, + subject: event.title, + body: event.description, + location: event.location, + allday: event.allDay || false, + }; + return `https://outlook.live.com/calendar/0/deeplink/compose?${stringify(details)}`; +} + +export function office365(calendarEvent: CalendarEvent): string { + const event = eventify(calendarEvent); + const { start, end } = formatTimes(event, 'dateTimeWithOffset'); + const details: Outlook = { + path: '/calendar/action/compose', + rru: 'addevent', + startdt: start, + enddt: end, + subject: event.title, + body: event.description, + location: event.location, + allday: event.allDay || false, + }; + return `https://outlook.office.com/calendar/0/deeplink/compose?${stringify(details)}`; +} + +export function yahoo(calendarEvent: CalendarEvent): string { + const event = eventify(calendarEvent); + const { start, end } = formatTimes(event, event.allDay ? 'allDay' : 'dateTimeUTC'); + const details: Yahoo = { + v: 60, + title: event.title, + st: start, + et: end, + desc: event.description, + in_loc: event.location, + dur: event.allDay ? 'allday' : false, + }; + return `https://calendar.yahoo.com/?${stringify(details)}`; +} + +export function ics(calendarEvent: CalendarEvent): string { + const event = eventify(calendarEvent); + const formattedDescription: string = (event.description || '') + .replace(/,/gm, ',') + .replace(/;/gm, ';') + .replace(/\n/gm, '\\n') + .replace(/(\\n)[\s\t]+/gm, '\\n'); + + const formattedLocation: string = (event.location || '') + .replace(/,/gm, ',') + .replace(/;/gm, ';') + .replace(/\n/gm, '\\n') + .replace(/(\\n)[\s\t]+/gm, '\\n'); + + const { start, end } = formatTimes(event, event.allDay ? 'allDay' : 'dateTimeUTC'); + const calendarChunks = [ + { + key: 'BEGIN', + value: 'VCALENDAR', + }, + { + key: 'VERSION', + value: '2.0', + }, + { + key: 'BEGIN', + value: 'VEVENT', + }, + { + key: 'URL', + value: event.url, + }, + { + key: 'DTSTART', + value: start, + }, + { + key: 'DTEND', + value: end, + }, + { + key: 'SUMMARY', + value: event.title, + }, + { + key: 'DESCRIPTION', + value: formattedDescription, + }, + { + key: 'LOCATION', + value: formattedLocation, + }, + { + key: 'END', + value: 'VEVENT', + }, + { + key: 'END', + value: 'VCALENDAR', + }, + ]; + + let calendarUrl = ''; + + calendarChunks.forEach((chunk) => { + if (chunk.value) { + calendarUrl += `${chunk.key}:${encodeURIComponent(`${chunk.value}\n`)}`; + } + }); + + return `data:text/calendar;charset=utf8,${calendarUrl}`; +} + +export type { CalendarEvent }; diff --git a/src/lib/calendar-link/index.ts b/src/lib/calendar-link/index.ts new file mode 100644 index 000000000..2c8019839 --- /dev/null +++ b/src/lib/calendar-link/index.ts @@ -0,0 +1 @@ +export * from './calendar-link'; diff --git a/src/lib/calendar-link/interfaces.ts b/src/lib/calendar-link/interfaces.ts new file mode 100644 index 000000000..fa6f5e5d2 --- /dev/null +++ b/src/lib/calendar-link/interfaces.ts @@ -0,0 +1,54 @@ +import type dayjs from 'dayjs'; + +interface CalendarEvent { + title: string; + start: Date; + end?: Date; + duration?: [number, dayjs.UnitType]; + allDay?: boolean; + description?: string; + location?: string; + busy?: boolean; + guests?: string[]; + url?: string; +} + +interface NormalizedCalendarEvent extends Omit { + startUtc: dayjs.Dayjs; + endUtc: dayjs.Dayjs; +} + +interface Google extends Record { + action: string; + text: string; + dates: string; + details?: string; + location?: string; + trp?: boolean; + sprop?: string; + add?: string; + src?: string; + recur?: string; +} + +interface Outlook extends Record { + path: string; + rru: string; + startdt: string; + enddt: string; + subject: string; + allday?: boolean; + body?: string; + location?: string; +} + +interface Yahoo extends Record { + v: number; + title: string; + st: string; + et: string; + desc?: string; + in_loc?: string; +} + +export type { CalendarEvent, NormalizedCalendarEvent, Outlook, Yahoo, Google }; diff --git a/src/lib/calendar-link/utils.ts b/src/lib/calendar-link/utils.ts new file mode 100644 index 000000000..5a78feb88 --- /dev/null +++ b/src/lib/calendar-link/utils.ts @@ -0,0 +1,5 @@ +export const TimeFormats = { + dateTimeWithOffset: 'YYYY-MM-DD[T]HH:mm:ssZ', + dateTimeUTC: 'YYYYMMDD[T]HHmmss[Z]', + allDay: 'YYYYMMDD', +}; diff --git a/src/lib/components/events/event-item.svelte b/src/lib/components/events/event-item.svelte index cc8a05191..c938142b6 100644 --- a/src/lib/components/events/event-item.svelte +++ b/src/lib/components/events/event-item.svelte @@ -1,11 +1,11 @@