aboutsummaryrefslogtreecommitdiffstats
path: root/apps/kit
diff options
context:
space:
mode:
authorivarlovlie <git@ivarlovlie.no>2022-09-20 09:24:27 +0200
committerivarlovlie <git@ivarlovlie.no>2022-09-20 09:24:27 +0200
commita9072370ca1eb9a5cce928b1d487db0f307edea6 (patch)
tree59c3c23df930a8b5f888dc7813923abf4ceefed4 /apps/kit
parent56fa963a1d63cbe0bf28e29e717cceaa417c45c1 (diff)
downloadgreatoffice-a9072370ca1eb9a5cce928b1d487db0f307edea6.tar.xz
greatoffice-a9072370ca1eb9a5cce928b1d487db0f307edea6.zip
feat: Move old apps into it's own directory
Diffstat (limited to 'apps/kit')
-rw-r--r--apps/kit/.gitignore8
-rw-r--r--apps/kit/.npmrc1
-rw-r--r--apps/kit/.typesafe-i18n.json5
-rwxr-xr-xapps/kit/bun.lockbbin0 -> 100851 bytes
-rw-r--r--apps/kit/package.json45
-rw-r--r--apps/kit/playwright.config.ts10
-rw-r--r--apps/kit/pnpm-lock.yaml2815
-rw-r--r--apps/kit/postcss.config.cjs13
-rw-r--r--apps/kit/src/app.d.ts9
-rw-r--r--apps/kit/src/app.html12
-rw-r--r--apps/kit/src/app.pcss21
-rw-r--r--apps/kit/src/hooks/index.server.ts52
-rw-r--r--apps/kit/src/lib/api/internal-fetch.ts170
-rw-r--r--apps/kit/src/lib/api/root.ts6
-rw-r--r--apps/kit/src/lib/api/time-entry.ts83
-rw-r--r--apps/kit/src/lib/api/user.ts47
-rw-r--r--apps/kit/src/lib/colors.ts47
-rw-r--r--apps/kit/src/lib/components/alert.svelte149
-rw-r--r--apps/kit/src/lib/components/button.svelte72
-rw-r--r--apps/kit/src/lib/components/icons/adjustments.svelte5
-rw-r--r--apps/kit/src/lib/components/icons/database.svelte3
-rw-r--r--apps/kit/src/lib/components/icons/home.svelte5
-rw-r--r--apps/kit/src/lib/components/icons/index.ts13
-rw-r--r--apps/kit/src/lib/components/icons/menu.svelte4
-rw-r--r--apps/kit/src/lib/components/icons/x.svelte4
-rw-r--r--apps/kit/src/lib/components/locale-switcher.svelte52
-rw-r--r--apps/kit/src/lib/configuration.ts45
-rw-r--r--apps/kit/src/lib/helpers.ts493
-rw-r--r--apps/kit/src/lib/i18n/en/index.ts136
-rw-r--r--apps/kit/src/lib/i18n/formatters.ts11
-rw-r--r--apps/kit/src/lib/i18n/i18n-svelte.ts12
-rw-r--r--apps/kit/src/lib/i18n/i18n-types.ts890
-rw-r--r--apps/kit/src/lib/i18n/i18n-util.async.ts27
-rw-r--r--apps/kit/src/lib/i18n/i18n-util.sync.ts26
-rw-r--r--apps/kit/src/lib/i18n/i18n-util.ts33
-rw-r--r--apps/kit/src/lib/i18n/nb/index.ts136
-rw-r--r--apps/kit/src/lib/locale.ts20
-rw-r--r--apps/kit/src/lib/logger.ts87
-rw-r--r--apps/kit/src/lib/models/CreateAccountPayload.ts4
-rw-r--r--apps/kit/src/lib/models/ErrorResult.ts4
-rw-r--r--apps/kit/src/lib/models/IInternalFetchRequest.ts6
-rw-r--r--apps/kit/src/lib/models/IInternalFetchResponse.ts6
-rw-r--r--apps/kit/src/lib/models/ISession.ts7
-rw-r--r--apps/kit/src/lib/models/IValidationResult.ts31
-rw-r--r--apps/kit/src/lib/models/LoginPayload.ts4
-rw-r--r--apps/kit/src/lib/models/TimeCategoryDto.ts9
-rw-r--r--apps/kit/src/lib/models/TimeEntryDto.ts13
-rw-r--r--apps/kit/src/lib/models/TimeEntryQuery.ts27
-rw-r--r--apps/kit/src/lib/models/TimeLabelDto.ts8
-rw-r--r--apps/kit/src/lib/models/TimeQueryDto.ts29
-rw-r--r--apps/kit/src/lib/models/UnwrappedEntryDateTime.ts9
-rw-r--r--apps/kit/src/lib/models/UpdateProfilePayload.ts4
-rw-r--r--apps/kit/src/lib/persistent-store.ts102
-rw-r--r--apps/kit/src/lib/session.ts69
-rw-r--r--apps/kit/src/params/guid.ts5
-rw-r--r--apps/kit/src/params/integer.ts3
-rw-r--r--apps/kit/src/routes/(app)/+layout.svelte215
-rw-r--r--apps/kit/src/routes/(app)/home/+page.svelte1
-rw-r--r--apps/kit/src/routes/(public)/+layout.svelte6
-rw-r--r--apps/kit/src/routes/(public)/login/+page.svelte95
-rw-r--r--apps/kit/src/routes/(public)/reset/+page.svelte29
-rw-r--r--apps/kit/src/routes/(public)/signup/+page.svelte38
-rw-r--r--apps/kit/src/routes/+layout.server.ts13
-rw-r--r--apps/kit/src/routes/+layout.svelte23
-rw-r--r--apps/kit/static/preload.js13
-rw-r--r--apps/kit/svelte.config.js16
-rw-r--r--apps/kit/tailwind.config.cjs13
-rw-r--r--apps/kit/tests/test.ts6
-rw-r--r--apps/kit/tsconfig.json35
-rw-r--r--apps/kit/vite.config.js14
70 files changed, 6424 insertions, 0 deletions
diff --git a/apps/kit/.gitignore b/apps/kit/.gitignore
new file mode 100644
index 0000000..f4401a3
--- /dev/null
+++ b/apps/kit/.gitignore
@@ -0,0 +1,8 @@
+.DS_Store
+node_modules
+/build
+/.svelte-kit
+/package
+.env
+.env.*
+!.env.example
diff --git a/apps/kit/.npmrc b/apps/kit/.npmrc
new file mode 100644
index 0000000..b6f27f1
--- /dev/null
+++ b/apps/kit/.npmrc
@@ -0,0 +1 @@
+engine-strict=true
diff --git a/apps/kit/.typesafe-i18n.json b/apps/kit/.typesafe-i18n.json
new file mode 100644
index 0000000..e2a0d73
--- /dev/null
+++ b/apps/kit/.typesafe-i18n.json
@@ -0,0 +1,5 @@
+{
+ "adapter": "svelte",
+ "$schema": "https://unpkg.com/typesafe-i18n@5.13.0/schema/typesafe-i18n.json",
+ "outputPath": "src/lib/i18n"
+} \ No newline at end of file
diff --git a/apps/kit/bun.lockb b/apps/kit/bun.lockb
new file mode 100755
index 0000000..25eeab8
--- /dev/null
+++ b/apps/kit/bun.lockb
Binary files differ
diff --git a/apps/kit/package.json b/apps/kit/package.json
new file mode 100644
index 0000000..32b3219
--- /dev/null
+++ b/apps/kit/package.json
@@ -0,0 +1,45 @@
+{
+ "name": "greatoffice-kit",
+ "version": "0.0.1",
+ "private": true,
+ "type": "module",
+ "scripts": {
+ "dev": "npm-run-all --parallel vite typesafe-i18n",
+ "typesafe-i18n": "typesafe-i18n",
+ "vite": "vite",
+ "build": "vite build",
+ "preview": "vite preview",
+ "test": "playwright test",
+ "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
+ "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch"
+ },
+ "devDependencies": {
+ "@leveluptuts/bookit": "^0.0.25",
+ "@playwright/test": "^1.25.2",
+ "@rgossiaux/svelte-headlessui": "^1.0.2",
+ "@sveltejs/kit": "1.0.0-next.489",
+ "@sveltestack/svelte-query": "^1.6.0",
+ "@tailwindcss/forms": "^0.5.3",
+ "@types/cookie": "^0.5.1",
+ "autoprefixer": "^10.4.11",
+ "bun-types": "^0.1.11",
+ "cookie": "^0.5.0",
+ "dotenv": "^16.0.2",
+ "fuzzysort": "^2.0.1",
+ "npm-run-all": "^4.1.5",
+ "pino": "^8.6.0",
+ "pino-pretty": "^9.1.0",
+ "postcss": "^8.4.16",
+ "postcss-load-config": "^4.0.1",
+ "svelte": "^3.50.1",
+ "svelte-adapter-bun": "^0.3.1",
+ "svelte-check": "^2.9.0",
+ "svelte-preprocess": "^4.10.7",
+ "tailwindcss": "^3.1.8",
+ "temporal-polyfill": "^0.0.8",
+ "tslib": "^2.4.0",
+ "typesafe-i18n": "^5.13.0",
+ "typescript": "^4.8.3",
+ "vite": "^3.1.3"
+ }
+} \ No newline at end of file
diff --git a/apps/kit/playwright.config.ts b/apps/kit/playwright.config.ts
new file mode 100644
index 0000000..6ad3a7f
--- /dev/null
+++ b/apps/kit/playwright.config.ts
@@ -0,0 +1,10 @@
+import type { PlaywrightTestConfig } from '@playwright/test';
+
+const config: PlaywrightTestConfig = {
+ webServer: {
+ command: 'npm run build && npm run preview',
+ port: 4173
+ }
+};
+
+export default config;
diff --git a/apps/kit/pnpm-lock.yaml b/apps/kit/pnpm-lock.yaml
new file mode 100644
index 0000000..b36ac59
--- /dev/null
+++ b/apps/kit/pnpm-lock.yaml
@@ -0,0 +1,2815 @@
+lockfileVersion: 5.4
+
+specifiers:
+ '@leveluptuts/bookit': ^0.0.25
+ '@playwright/test': ^1.25.2
+ '@rgossiaux/svelte-headlessui': ^1.0.2
+ '@sveltejs/kit': 1.0.0-next.489
+ '@sveltestack/svelte-query': ^1.6.0
+ '@tailwindcss/forms': ^0.5.3
+ '@types/cookie': ^0.5.1
+ autoprefixer: ^10.4.11
+ bun-types: ^0.1.11
+ cookie: ^0.5.0
+ dotenv: ^16.0.2
+ fuzzysort: ^2.0.1
+ npm-run-all: ^4.1.5
+ pino: ^8.6.0
+ pino-pretty: ^9.1.0
+ postcss: ^8.4.16
+ postcss-load-config: ^4.0.1
+ svelte: ^3.50.1
+ svelte-adapter-bun: ^0.3.1
+ svelte-check: ^2.9.0
+ svelte-preprocess: ^4.10.7
+ tailwindcss: ^3.1.8
+ temporal-polyfill: ^0.0.8
+ tslib: ^2.4.0
+ typesafe-i18n: ^5.13.0
+ typescript: ^4.8.3
+ vite: ^3.1.3
+
+devDependencies:
+ '@leveluptuts/bookit': 0.0.25
+ '@playwright/test': 1.25.2
+ '@rgossiaux/svelte-headlessui': 1.0.2_svelte@3.50.1
+ '@sveltejs/kit': 1.0.0-next.489_svelte@3.50.1+vite@3.1.3
+ '@sveltestack/svelte-query': 1.6.0
+ '@tailwindcss/forms': 0.5.3_tailwindcss@3.1.8
+ '@types/cookie': 0.5.1
+ autoprefixer: 10.4.11_postcss@8.4.16
+ bun-types: 0.1.11
+ cookie: 0.5.0
+ dotenv: 16.0.2
+ fuzzysort: 2.0.1
+ npm-run-all: 4.1.5
+ pino: 8.6.0
+ pino-pretty: 9.1.0
+ postcss: 8.4.16
+ postcss-load-config: 4.0.1_postcss@8.4.16
+ svelte: 3.50.1
+ svelte-adapter-bun: 0.3.1
+ svelte-check: 2.9.0_zy537mcxrfn3wq4nghluxnsi5a
+ svelte-preprocess: 4.10.7_sxt3mkf5m54vn3maobdwhqzitu
+ tailwindcss: 3.1.8_postcss@8.4.16
+ temporal-polyfill: 0.0.8
+ tslib: 2.4.0
+ typesafe-i18n: 5.13.0_typescript@4.8.3
+ typescript: 4.8.3
+ vite: 3.1.3
+
+packages:
+
+ /@esbuild/android-arm/0.15.8:
+ resolution: {integrity: sha512-CyEWALmn+no/lbgbAJsbuuhT8s2J19EJGHkeyAwjbFJMrj80KJ9zuYsoAvidPTU7BgBf87r/sgae8Tw0dbOc4Q==}
+ engines: {node: '>=12'}
+ cpu: [arm]
+ os: [android]
+ requiresBuild: true
+ dependencies:
+ esbuild-wasm: 0.15.8
+ dev: true
+ optional: true
+
+ /@esbuild/linux-loong64/0.15.8:
+ resolution: {integrity: sha512-pE5RQsOTSERCtfZdfCT25wzo7dfhOSlhAXcsZmuvRYhendOv7djcdvtINdnDp2DAjP17WXlBB4nBO6sHLczmsg==}
+ engines: {node: '>=12'}
+ cpu: [loong64]
+ os: [linux]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@jridgewell/resolve-uri/3.1.0:
+ resolution: {integrity: sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==}
+ engines: {node: '>=6.0.0'}
+ dev: true
+
+ /@jridgewell/sourcemap-codec/1.4.14:
+ resolution: {integrity: sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==}
+ dev: true
+
+ /@jridgewell/trace-mapping/0.3.15:
+ resolution: {integrity: sha512-oWZNOULl+UbhsgB51uuZzglikfIKSUBO/M9W2OfEjn7cmqoAiCgmv9lyACTUacZwBz0ITnJ2NqjU8Tx0DHL88g==}
+ dependencies:
+ '@jridgewell/resolve-uri': 3.1.0
+ '@jridgewell/sourcemap-codec': 1.4.14
+ dev: true
+
+ /@leveluptuts/bookit/0.0.25:
+ resolution: {integrity: sha512-myzMQXm8nFg7GvqY3uU4OT29L6imUaWe/ZptRSroFtK2XchDSSyiQVk1xr05ivZjeyTG6QiG9lGFRrTGgmPcfw==}
+ dependencies:
+ '@leveluptuts/svelte-side-menu': 1.0.2
+ '@leveluptuts/svelte-toy': 2.0.2
+ just-safe-set: 4.1.1
+ svelte-splitpanes: 0.7.3
+ transitivePeerDependencies:
+ - supports-color
+ dev: true
+
+ /@leveluptuts/svelte-side-menu/1.0.2:
+ resolution: {integrity: sha512-ZaR2euPXm7SFhcKvIBA6ug0Yd2tpoS3MUJed3sI5WxBk4UlbrLRk+RVrftxYGJr0hPQ6LY+WPBhen6TNHtZb5Q==}
+ dev: true
+
+ /@leveluptuts/svelte-toy/2.0.2:
+ resolution: {integrity: sha512-DHFsRL3/p6qNgnOla2EeF+3tZTCoYMWpMy7uJRYUjBAYfog8BNziLgenFIKhcRlDgLOBPUqXB8FRB2SdNmkfAg==}
+ dependencies:
+ lodash.set: 4.3.2
+ dev: true
+
+ /@nodelib/fs.scandir/2.1.5:
+ resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
+ engines: {node: '>= 8'}
+ dependencies:
+ '@nodelib/fs.stat': 2.0.5
+ run-parallel: 1.2.0
+ dev: true
+
+ /@nodelib/fs.stat/2.0.5:
+ resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==}
+ engines: {node: '>= 8'}
+ dev: true
+
+ /@nodelib/fs.walk/1.2.8:
+ resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==}
+ engines: {node: '>= 8'}
+ dependencies:
+ '@nodelib/fs.scandir': 2.1.5
+ fastq: 1.13.0
+ dev: true
+
+ /@playwright/test/1.24.2:
+ resolution: {integrity: sha512-Q4X224pRHw4Dtkk5PoNJplZCokLNvVbXD9wDQEMrHcEuvWpJWEQDeJ9gEwkZ3iCWSFSWBshIX177B231XW4wOQ==}
+ engines: {node: '>=14'}
+ hasBin: true
+ requiresBuild: true
+ dependencies:
+ '@types/node': 18.7.18
+ playwright-core: 1.24.2
+ dev: true
+ optional: true
+
+ /@playwright/test/1.25.2:
+ resolution: {integrity: sha512-6qPznIR4Fw02OMbqXUPMG6bFFg1hDVNEdihKy0t9K0dmRbus1DyP5Q5XFQhGwEHQkLG5hrSfBuu9CW/foqhQHQ==}
+ engines: {node: '>=14'}
+ hasBin: true
+ dependencies:
+ '@types/node': 18.7.18
+ playwright-core: 1.25.2
+ dev: true
+
+ /@polka/url/1.0.0-next.21:
+ resolution: {integrity: sha512-a5Sab1C4/icpTZVzZc5Ghpz88yQtGOyNqYXcZgOssB2uuAr+wF/MvN6bgtW32q7HHrvBki+BsZ0OuNv6EV3K9g==}
+ dev: true
+
+ /@rgossiaux/svelte-headlessui/1.0.2_svelte@3.50.1:
+ resolution: {integrity: sha512-sauopYTSivhzXe1kAvgawkhyYJcQlK8Li3p0d2OtcCIVprOzdbard5lbqWB4xHDv83zAobt2mR08oizO2poHLQ==}
+ peerDependencies:
+ svelte: ^3.44.0
+ dependencies:
+ svelte: 3.50.1
+ dev: true
+
+ /@rollup/pluginutils/4.2.1:
+ resolution: {integrity: sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==}
+ engines: {node: '>= 8.0.0'}
+ dependencies:
+ estree-walker: 2.0.2
+ picomatch: 2.3.1
+ dev: true
+
+ /@sveltejs/kit/1.0.0-next.489_svelte@3.50.1+vite@3.1.3:
+ resolution: {integrity: sha512-7oag1fimI9f8e8mNy2wQRgMCXSov5dASqeTdXffgnptWBpuEjr8myffJ9djoRVQXoFjCja39lCHBo239iY8+KA==}
+ engines: {node: '>=16.14'}
+ hasBin: true
+ requiresBuild: true
+ peerDependencies:
+ svelte: ^3.44.0
+ vite: ^3.1.0
+ dependencies:
+ '@sveltejs/vite-plugin-svelte': 1.0.7_svelte@3.50.1+vite@3.1.3
+ '@types/cookie': 0.5.1
+ cookie: 0.5.0
+ devalue: 3.1.3
+ kleur: 4.1.5
+ magic-string: 0.26.3
+ mime: 3.0.0
+ node-fetch: 3.2.10
+ sade: 1.8.1
+ set-cookie-parser: 2.5.1
+ sirv: 2.0.2
+ svelte: 3.50.1
+ tiny-glob: 0.2.9
+ undici: 5.10.0
+ vite: 3.1.3
+ transitivePeerDependencies:
+ - diff-match-patch
+ - supports-color
+ dev: true
+
+ /@sveltejs/vite-plugin-svelte/1.0.7_svelte@3.50.1+vite@3.1.3:
+ resolution: {integrity: sha512-bf3/xrpKP5Sj9I6hT0slYwY4rVElocWZ79zLPc/bPFCOjjuty0jW4hmC4Uehb7yifjf3I6QnT3eIs2EKqw+Kig==}
+ engines: {node: ^14.18.0 || >= 16}
+ peerDependencies:
+ diff-match-patch: ^1.0.5
+ svelte: ^3.44.0
+ vite: ^3.0.0
+ peerDependenciesMeta:
+ diff-match-patch:
+ optional: true
+ dependencies:
+ '@rollup/pluginutils': 4.2.1
+ debug: 4.3.4
+ deepmerge: 4.2.2
+ kleur: 4.1.5
+ magic-string: 0.26.3
+ svelte: 3.50.1
+ svelte-hmr: 0.15.0_svelte@3.50.1
+ vite: 3.1.3
+ transitivePeerDependencies:
+ - supports-color
+ dev: true
+
+ /@sveltestack/svelte-query/1.6.0:
+ resolution: {integrity: sha512-C0wWuh6av1zu3Pzwrg6EQmX3BhDZQ4gMAdYu6Tfv4bjbEZTB00uEDz52z92IZdONh+iUKuyo0xRZ2e16k2Xifg==}
+ peerDependencies:
+ broadcast-channel: ^4.5.0
+ peerDependenciesMeta:
+ broadcast-channel:
+ optional: true
+ dev: true
+
+ /@tailwindcss/forms/0.5.3_tailwindcss@3.1.8:
+ resolution: {integrity: sha512-y5mb86JUoiUgBjY/o6FJSFZSEttfb3Q5gllE4xoKjAAD+vBrnIhE4dViwUuow3va8mpH4s9jyUbUbrRGoRdc2Q==}
+ peerDependencies:
+ tailwindcss: '>=3.0.0 || >= 3.0.0-alpha.1'
+ dependencies:
+ mini-svg-data-uri: 1.4.4
+ tailwindcss: 3.1.8_postcss@8.4.16
+ dev: true
+
+ /@types/cookie/0.5.1:
+ resolution: {integrity: sha512-COUnqfB2+ckwXXSFInsFdOAWQzCCx+a5hq2ruyj+Vjund94RJQd4LG2u9hnvJrTgunKAaax7ancBYlDrNYxA0g==}
+ dev: true
+
+ /@types/node/18.7.18:
+ resolution: {integrity: sha512-m+6nTEOadJZuTPkKR/SYK3A2d7FZrgElol9UP1Kae90VVU4a6mxnPuLiIW1m4Cq4gZ/nWb9GrdVXJCoCazDAbg==}
+ dev: true
+
+ /@types/pug/2.0.6:
+ resolution: {integrity: sha512-SnHmG9wN1UVmagJOnyo/qkk0Z7gejYxOYYmaAwr5u2yFYfsupN3sg10kyzN8Hep/2zbHxCnsumxOoRIRMBwKCg==}
+ dev: true
+
+ /@types/sass/1.43.1:
+ resolution: {integrity: sha512-BPdoIt1lfJ6B7rw35ncdwBZrAssjcwzI5LByIrYs+tpXlj/CAkuVdRsgZDdP4lq5EjyWzwxZCqAoFyHKFwp32g==}
+ dependencies:
+ '@types/node': 18.7.18
+ dev: true
+
+ /@zeit/schemas/2.21.0:
+ resolution: {integrity: sha512-/J4WBTpWtQ4itN1rb3ao8LfClmVcmz2pO6oYb7Qd4h7VSqUhIbJIvrykz9Ew1WMg6eFWsKdsMHc5uPbFxqlCpg==}
+ dev: true
+ optional: true
+
+ /abort-controller/3.0.0:
+ resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==}
+ engines: {node: '>=6.5'}
+ dependencies:
+ event-target-shim: 5.0.1
+ dev: true
+
+ /accepts/1.3.8:
+ resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==}
+ engines: {node: '>= 0.6'}
+ dependencies:
+ mime-types: 2.1.35
+ negotiator: 0.6.3
+ dev: true
+ optional: true
+
+ /acorn-node/1.8.2:
+ resolution: {integrity: sha512-8mt+fslDufLYntIoPAaIMUe/lrbrehIiwmR3t2k9LljIzoigEPF27eLk2hy8zSGzmR/ogr7zbRKINMo1u0yh5A==}
+ dependencies:
+ acorn: 7.4.1
+ acorn-walk: 7.2.0
+ xtend: 4.0.2
+ dev: true
+
+ /acorn-walk/7.2.0:
+ resolution: {integrity: sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==}
+ engines: {node: '>=0.4.0'}
+ dev: true
+
+ /acorn/7.4.1:
+ resolution: {integrity: sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==}
+ engines: {node: '>=0.4.0'}
+ hasBin: true
+ dev: true
+
+ /ajv/8.11.0:
+ resolution: {integrity: sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==}
+ dependencies:
+ fast-deep-equal: 3.1.3
+ json-schema-traverse: 1.0.0
+ require-from-string: 2.0.2
+ uri-js: 4.4.1
+ dev: true
+ optional: true
+
+ /ansi-align/3.0.1:
+ resolution: {integrity: sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==}
+ dependencies:
+ string-width: 4.2.3
+ dev: true
+ optional: true
+
+ /ansi-regex/5.0.1:
+ resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
+ engines: {node: '>=8'}
+ dev: true
+ optional: true
+
+ /ansi-regex/6.0.1:
+ resolution: {integrity: sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==}
+ engines: {node: '>=12'}
+ dev: true
+ optional: true
+
+ /ansi-styles/3.2.1:
+ resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==}
+ engines: {node: '>=4'}
+ dependencies:
+ color-convert: 1.9.3
+ dev: true
+
+ /ansi-styles/4.3.0:
+ resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
+ engines: {node: '>=8'}
+ dependencies:
+ color-convert: 2.0.1
+ dev: true
+ optional: true
+
+ /ansi-styles/6.1.1:
+ resolution: {integrity: sha512-qDOv24WjnYuL+wbwHdlsYZFy+cgPtrYw0Tn7GLORicQp9BkQLzrgI3Pm4VyR9ERZ41YTn7KlMPuL1n05WdZvmg==}
+ engines: {node: '>=12'}
+ dev: true
+ optional: true
+
+ /anymatch/3.1.2:
+ resolution: {integrity: sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==}
+ engines: {node: '>= 8'}
+ dependencies:
+ normalize-path: 3.0.0
+ picomatch: 2.3.1
+ dev: true
+
+ /arch/2.2.0:
+ resolution: {integrity: sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ==}
+ dev: true
+ optional: true
+
+ /arg/5.0.2:
+ resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==}
+ dev: true
+
+ /atomic-sleep/1.0.0:
+ resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==}
+ engines: {node: '>=8.0.0'}
+ dev: true
+
+ /autoprefixer/10.4.11_postcss@8.4.16:
+ resolution: {integrity: sha512-5lHp6DgRodxlBLSkzHOTcufWFflH1ewfy2hvFQyjrblBFlP/0Yh4O/Wrg4ow8WRlN3AAUFFLAQwX8hTptzqVHg==}
+ engines: {node: ^10 || ^12 || >=14}
+ hasBin: true
+ peerDependencies:
+ postcss: ^8.1.0
+ dependencies:
+ browserslist: 4.21.4
+ caniuse-lite: 1.0.30001407
+ fraction.js: 4.2.0
+ normalize-range: 0.1.2
+ picocolors: 1.0.0
+ postcss: 8.4.16
+ postcss-value-parser: 4.2.0
+ dev: true
+
+ /balanced-match/1.0.2:
+ resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
+ dev: true
+
+ /binary-extensions/2.2.0:
+ resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==}
+ engines: {node: '>=8'}
+ dev: true
+
+ /boxen/7.0.0:
+ resolution: {integrity: sha512-j//dBVuyacJbvW+tvZ9HuH03fZ46QcaKvvhZickZqtB271DxJ7SNRSNxrV/dZX0085m7hISRZWbzWlJvx/rHSg==}
+ engines: {node: '>=14.16'}
+ dependencies:
+ ansi-align: 3.0.1
+ camelcase: 7.0.0
+ chalk: 5.0.1
+ cli-boxes: 3.0.0
+ string-width: 5.1.2
+ type-fest: 2.19.0
+ widest-line: 4.0.1
+ wrap-ansi: 8.0.1
+ dev: true
+ optional: true
+
+ /brace-expansion/1.1.11:
+ resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==}
+ dependencies:
+ balanced-match: 1.0.2
+ concat-map: 0.0.1
+ dev: true
+
+ /brace-expansion/2.0.1:
+ resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==}
+ dependencies:
+ balanced-match: 1.0.2
+ dev: true
+
+ /braces/3.0.2:
+ resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==}
+ engines: {node: '>=8'}
+ dependencies:
+ fill-range: 7.0.1
+ dev: true
+
+ /browserslist/4.21.4:
+ resolution: {integrity: sha512-CBHJJdDmgjl3daYjN5Cp5kbTf1mUhZoS+beLklHIvkOWscs83YAhLlF3Wsh/lciQYAcbBJgTOD44VtG31ZM4Hw==}
+ engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
+ hasBin: true
+ dependencies:
+ caniuse-lite: 1.0.30001407
+ electron-to-chromium: 1.4.255
+ node-releases: 2.0.6
+ update-browserslist-db: 1.0.9_browserslist@4.21.4
+ dev: true
+
+ /buffer-crc32/0.2.13:
+ resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==}
+ dev: true
+
+ /bun-types/0.1.11:
+ resolution: {integrity: sha512-a5G9LZtF5qBsDMjGnS5FfTvIeg1bS0PzI3Q2LqShEjJWdhZTB56xO8ewqZ83J+3my5rW/PPPWT/udAofRQ3zdg==}
+ dev: true
+
+ /bytes/3.0.0:
+ resolution: {integrity: sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==}
+ engines: {node: '>= 0.8'}
+ dev: true
+ optional: true
+
+ /call-bind/1.0.2:
+ resolution: {integrity: sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==}
+ dependencies:
+ function-bind: 1.1.1
+ get-intrinsic: 1.1.3
+ dev: true
+
+ /callsites/3.1.0:
+ resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}
+ engines: {node: '>=6'}
+ dev: true
+
+ /camelcase-css/2.0.1:
+ resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==}
+ engines: {node: '>= 6'}
+ dev: true
+
+ /camelcase/7.0.0:
+ resolution: {integrity: sha512-JToIvOmz6nhGsUhAYScbo2d6Py5wojjNfoxoc2mEVLUdJ70gJK2gnd+ABY1Tc3sVMyK7QDPtN0T/XdlCQWITyQ==}
+ engines: {node: '>=14.16'}
+ dev: true
+ optional: true
+
+ /caniuse-lite/1.0.30001407:
+ resolution: {integrity: sha512-4ydV+t4P7X3zH83fQWNDX/mQEzYomossfpViCOx9zHBSMV+rIe3LFqglHHtVyvNl1FhTNxPxs3jei82iqOW04w==}
+ dev: true
+
+ /chalk-template/0.4.0:
+ resolution: {integrity: sha512-/ghrgmhfY8RaSdeo43hNXxpoHAtxdbskUHjPpfqUWGttFgycUhYPGx3YZBCnUCvOa7Doivn1IZec3DEGFoMgLg==}
+ engines: {node: '>=12'}
+ dependencies:
+ chalk: 4.1.2
+ dev: true
+ optional: true
+
+ /chalk/2.4.2:
+ resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==}
+ engines: {node: '>=4'}
+ dependencies:
+ ansi-styles: 3.2.1
+ escape-string-regexp: 1.0.5
+ supports-color: 5.5.0
+ dev: true
+
+ /chalk/4.1.2:
+ resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
+ engines: {node: '>=10'}
+ dependencies:
+ ansi-styles: 4.3.0
+ supports-color: 7.2.0
+ dev: true
+ optional: true
+
+ /chalk/5.0.1:
+ resolution: {integrity: sha512-Fo07WOYGqMfCWHOzSXOt2CxDbC6skS/jO9ynEcmpANMoPrD+W1r1K6Vx7iNm+AQmETU1Xr2t+n8nzkV9t6xh3w==}
+ engines: {node: ^12.17.0 || ^14.13 || >=16.0.0}
+ dev: true
+ optional: true
+
+ /chokidar/3.5.3:
+ resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==}
+ engines: {node: '>= 8.10.0'}
+ dependencies:
+ anymatch: 3.1.2
+ braces: 3.0.2
+ glob-parent: 5.1.2
+ is-binary-path: 2.1.0
+ is-glob: 4.0.3
+ normalize-path: 3.0.0
+ readdirp: 3.6.0
+ optionalDependencies:
+ fsevents: 2.3.2
+ dev: true
+
+ /cli-boxes/3.0.0:
+ resolution: {integrity: sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==}
+ engines: {node: '>=10'}
+ dev: true
+ optional: true
+
+ /clipboardy/3.0.0:
+ resolution: {integrity: sha512-Su+uU5sr1jkUy1sGRpLKjKrvEOVXgSgiSInwa/qeID6aJ07yh+5NWc3h2QfjHjBnfX4LhtFcuAWKUsJ3r+fjbg==}
+ engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
+ dependencies:
+ arch: 2.2.0
+ execa: 5.1.1
+ is-wsl: 2.2.0
+ dev: true
+ optional: true
+
+ /color-convert/1.9.3:
+ resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==}
+ dependencies:
+ color-name: 1.1.3
+ dev: true
+
+ /color-convert/2.0.1:
+ resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
+ engines: {node: '>=7.0.0'}
+ dependencies:
+ color-name: 1.1.4
+ dev: true
+ optional: true
+
+ /color-name/1.1.3:
+ resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==}
+ dev: true
+
+ /color-name/1.1.4:
+ resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
+ dev: true
+
+ /colorette/2.0.19:
+ resolution: {integrity: sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==}
+ dev: true
+
+ /compressible/2.0.18:
+ resolution: {integrity: sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==}
+ engines: {node: '>= 0.6'}
+ dependencies:
+ mime-db: 1.52.0
+ dev: true
+ optional: true
+
+ /compression/1.7.4:
+ resolution: {integrity: sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==}
+ engines: {node: '>= 0.8.0'}
+ dependencies:
+ accepts: 1.3.8
+ bytes: 3.0.0
+ compressible: 2.0.18
+ debug: 2.6.9
+ on-headers: 1.0.2
+ safe-buffer: 5.1.2
+ vary: 1.1.2
+ transitivePeerDependencies:
+ - supports-color
+ dev: true
+ optional: true
+
+ /concat-map/0.0.1:
+ resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
+ dev: true
+
+ /content-disposition/0.5.2:
+ resolution: {integrity: sha512-kRGRZw3bLlFISDBgwTSA1TMBFN6J6GWDeubmDE3AF+3+yXL8hTWv8r5rkLbqYXY4RjPk/EzHnClI3zQf1cFmHA==}
+ engines: {node: '>= 0.6'}
+ dev: true
+ optional: true
+
+ /cookie/0.5.0:
+ resolution: {integrity: sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==}
+ engines: {node: '>= 0.6'}
+ dev: true
+
+ /cross-spawn/6.0.5:
+ resolution: {integrity: sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==}
+ engines: {node: '>=4.8'}
+ dependencies:
+ nice-try: 1.0.5
+ path-key: 2.0.1
+ semver: 5.7.1
+ shebang-command: 1.2.0
+ which: 1.3.1
+ dev: true
+
+ /cross-spawn/7.0.3:
+ resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==}
+ engines: {node: '>= 8'}
+ dependencies:
+ path-key: 3.1.1
+ shebang-command: 2.0.0
+ which: 2.0.2
+ dev: true
+ optional: true
+
+ /cssesc/3.0.0:
+ resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==}
+ engines: {node: '>=4'}
+ hasBin: true
+ dev: true
+
+ /data-uri-to-buffer/4.0.0:
+ resolution: {integrity: sha512-Vr3mLBA8qWmcuschSLAOogKgQ/Jwxulv3RNE4FXnYWRGujzrRWQI4m12fQqRkwX06C0KanhLr4hK+GydchZsaA==}
+ engines: {node: '>= 12'}
+ dev: true
+
+ /dateformat/4.6.3:
+ resolution: {integrity: sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==}
+ dev: true
+
+ /debug/2.6.9:
+ resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==}
+ peerDependencies:
+ supports-color: '*'
+ peerDependenciesMeta:
+ supports-color:
+ optional: true
+ dependencies:
+ ms: 2.0.0
+ dev: true
+ optional: true
+
+ /debug/4.3.4:
+ resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==}
+ engines: {node: '>=6.0'}
+ peerDependencies:
+ supports-color: '*'
+ peerDependenciesMeta:
+ supports-color:
+ optional: true
+ dependencies:
+ ms: 2.1.2
+ dev: true
+
+ /deep-extend/0.6.0:
+ resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==}
+ engines: {node: '>=4.0.0'}
+ dev: true
+ optional: true
+
+ /deepmerge/4.2.2:
+ resolution: {integrity: sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==}
+ engines: {node: '>=0.10.0'}
+ dev: true
+
+ /define-properties/1.1.4:
+ resolution: {integrity: sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ has-property-descriptors: 1.0.0
+ object-keys: 1.1.1
+ dev: true
+
+ /defined/1.0.0:
+ resolution: {integrity: sha512-Y2caI5+ZwS5c3RiNDJ6u53VhQHv+hHKwhkI1iHvceKUHw9Df6EK2zRLfjejRgMuCuxK7PfSWIMwWecceVvThjQ==}
+ dev: true
+
+ /detect-indent/6.1.0:
+ resolution: {integrity: sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==}
+ engines: {node: '>=8'}
+ dev: true
+
+ /detective/5.2.1:
+ resolution: {integrity: sha512-v9XE1zRnz1wRtgurGu0Bs8uHKFSTdteYZNbIPFVhUZ39L/S79ppMpdmVOZAnoz1jfEFodc48n6MX483Xo3t1yw==}
+ engines: {node: '>=0.8.0'}
+ hasBin: true
+ dependencies:
+ acorn-node: 1.8.2
+ defined: 1.0.0
+ minimist: 1.2.6
+ dev: true
+
+ /devalue/3.1.3:
+ resolution: {integrity: sha512-9KO89Cb+qjzf2CqdrH+NuLaqdk9GhDP5EhR4zlkR51dvuIaiqtlkDkGzLMShDemwUy21raSMdu+kpX8Enw3yGQ==}
+ dev: true
+
+ /didyoumean/1.2.2:
+ resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==}
+ dev: true
+
+ /dlv/1.1.3:
+ resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==}
+ dev: true
+
+ /dotenv/16.0.2:
+ resolution: {integrity: sha512-JvpYKUmzQhYoIFgK2MOnF3bciIZoItIIoryihy0rIA+H4Jy0FmgyKYAHCTN98P5ybGSJcIFbh6QKeJdtZd1qhA==}
+ engines: {node: '>=12'}
+ dev: true
+
+ /eastasianwidth/0.2.0:
+ resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
+ dev: true
+ optional: true
+
+ /electron-to-chromium/1.4.255:
+ resolution: {integrity: sha512-H+mFNKow6gi2P5Gi2d1Fvd3TUEJlB9CF7zYaIV9T83BE3wP1xZ0mRPbNTm0KUjyd1QiVy7iKXuIcjlDtBQMiAQ==}
+ dev: true
+
+ /emoji-regex/8.0.0:
+ resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
+ dev: true
+ optional: true
+
+ /emoji-regex/9.2.2:
+ resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==}
+ dev: true
+ optional: true
+
+ /end-of-stream/1.4.4:
+ resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==}
+ dependencies:
+ once: 1.4.0
+ dev: true
+
+ /error-ex/1.3.2:
+ resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==}
+ dependencies:
+ is-arrayish: 0.2.1
+ dev: true
+
+ /es-abstract/1.20.2:
+ resolution: {integrity: sha512-XxXQuVNrySBNlEkTYJoDNFe5+s2yIOpzq80sUHEdPdQr0S5nTLz4ZPPPswNIpKseDDUS5yghX1gfLIHQZ1iNuQ==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ call-bind: 1.0.2
+ es-to-primitive: 1.2.1
+ function-bind: 1.1.1
+ function.prototype.name: 1.1.5
+ get-intrinsic: 1.1.3
+ get-symbol-description: 1.0.0
+ has: 1.0.3
+ has-property-descriptors: 1.0.0
+ has-symbols: 1.0.3
+ internal-slot: 1.0.3
+ is-callable: 1.2.6
+ is-negative-zero: 2.0.2
+ is-regex: 1.1.4
+ is-shared-array-buffer: 1.0.2
+ is-string: 1.0.7
+ is-weakref: 1.0.2
+ object-inspect: 1.12.2
+ object-keys: 1.1.1
+ object.assign: 4.1.4
+ regexp.prototype.flags: 1.4.3
+ string.prototype.trimend: 1.0.5
+ string.prototype.trimstart: 1.0.5
+ unbox-primitive: 1.0.2
+ dev: true
+
+ /es-to-primitive/1.2.1:
+ resolution: {integrity: sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ is-callable: 1.2.6
+ is-date-object: 1.0.5
+ is-symbol: 1.0.4
+ dev: true
+
+ /es6-promise/3.3.1:
+ resolution: {integrity: sha512-SOp9Phqvqn7jtEUxPWdWfWoLmyt2VaJ6MpvP9Comy1MceMXqE6bxvaTu4iaxpYYPzhny28Lc+M87/c2cPK6lDg==}
+ dev: true
+
+ /esbuild-android-64/0.15.8:
+ resolution: {integrity: sha512-bVh8FIKOolF7/d4AMzt7xHlL0Ljr+mYKSHI39TJWDkybVWHdn6+4ODL3xZGHOxPpdRpitemXA1WwMKYBsw8dGw==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [android]
+ requiresBuild: true
+ dependencies:
+ esbuild-wasm: 0.15.8
+ dev: true
+ optional: true
+
+ /esbuild-android-arm64/0.15.8:
+ resolution: {integrity: sha512-ReAMDAHuo0H1h9LxRabI6gwYPn8k6WiUeyxuMvx17yTrJO+SCnIfNc/TSPFvDwtK9MiyiKG/2dBYHouT/M0BXQ==}
+ engines: {node: '>=12'}
+ cpu: [arm64]
+ os: [android]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /esbuild-darwin-64/0.15.8:
+ resolution: {integrity: sha512-KaKcGfJ+yto7Fo5gAj3xwxHMd1fBIKatpCHK8znTJLVv+9+NN2/tIPBqA4w5rBwjX0UqXDeIE2v1xJP+nGEXgA==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [darwin]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /esbuild-darwin-arm64/0.15.8:
+ resolution: {integrity: sha512-8tjEaBgAKnXCkP7bhEJmEqdG9HEV6oLkF36BrMzpfW2rgaw0c48Zrxe+9RlfeGvs6gDF4w+agXyTjikzsS3izw==}
+ engines: {node: '>=12'}
+ cpu: [arm64]
+ os: [darwin]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /esbuild-freebsd-64/0.15.8:
+ resolution: {integrity: sha512-jaxcsGHYzn2L0/lffON2WfH4Nc+d/EwozVTP5K2v016zxMb5UQMhLoJzvLgBqHT1SG0B/mO+a+THnJCMVg15zw==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [freebsd]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /esbuild-freebsd-arm64/0.15.8:
+ resolution: {integrity: sha512-2xp2UlljMvX8HExtcg7VHaeQk8OBU0CSl1j18B5CcZmSDkLF9p3utuMXIopG3a08fr9Hv+Dz6+seSXUow/G51w==}
+ engines: {node: '>=12'}
+ cpu: [arm64]
+ os: [freebsd]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /esbuild-linux-32/0.15.8:
+ resolution: {integrity: sha512-9u1E54BRz1FQMl86iaHK146+4ID2KYNxL3trLZT4QLLx3M7Q9n4lGG3lrzqUatGR2cKy8c33b0iaCzsItZWkFg==}
+ engines: {node: '>=12'}
+ cpu: [ia32]
+ os: [linux]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /esbuild-linux-64/0.15.8:
+ resolution: {integrity: sha512-4HxrsN9eUzJXdVGMTYA5Xler82FuZUu21bXKN42zcLHHNKCAMPUzD62I+GwDhsdgUBAUj0tRXDdsQHgaP6v0HA==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [linux]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /esbuild-linux-arm/0.15.8:
+ resolution: {integrity: sha512-7DVBU9SFjX4+vBwt8tHsUCbE6Vvl6y6FQWHAgyw1lybC5gULqn/WnjHYHN2/LJaZRsDBvxWT4msEgwLGq1Wd3Q==}
+ engines: {node: '>=12'}
+ cpu: [arm]
+ os: [linux]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /esbuild-linux-arm64/0.15.8:
+ resolution: {integrity: sha512-1OCm7Aq0tEJT70PbxmHSGYDLYP8DKH8r4Nk7/XbVzWaduo9beCjGBB+tGZIHK6DdTQ3h00/4Tb/70YMH/bOtKg==}
+ engines: {node: '>=12'}
+ cpu: [arm64]
+ os: [linux]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /esbuild-linux-mips64le/0.15.8:
+ resolution: {integrity: sha512-yeFoNPVFPEzZvFYBfUQNG2TjGRaCyV1E27OcOg4LOtnGrxb2wA+mkW3luckyv1CEyd00mpAg7UdHx8nlx3ghgA==}
+ engines: {node: '>=12'}
+ cpu: [mips64el]
+ os: [linux]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /esbuild-linux-ppc64le/0.15.8:
+ resolution: {integrity: sha512-CEyMMUUNabXibw8OSNmBXhOIGhnjNVl5Lpseiuf00iKN0V47oqDrbo4dsHz1wH62m49AR8iG8wpDlTqfYgKbtg==}
+ engines: {node: '>=12'}
+ cpu: [ppc64]
+ os: [linux]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /esbuild-linux-riscv64/0.15.8:
+ resolution: {integrity: sha512-OCGSOaspMUjexSCU8ZiA0UnV/NiRU+s2vIfEcAQWQ6u32R+2luyfh/4ZaY6jFbylJE07Esc/yRvb9Q5fXuClXA==}
+ engines: {node: '>=12'}
+ cpu: [riscv64]
+ os: [linux]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /esbuild-linux-s390x/0.15.8:
+ resolution: {integrity: sha512-RHdpdfxRTSrZXZJlFSLazFU4YwXLB5Rgf6Zr5rffqSsO4y9JybgtKO38bFwxZNlDXliYISXN/YROKrG9s7mZQA==}
+ engines: {node: '>=12'}
+ cpu: [s390x]
+ os: [linux]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /esbuild-netbsd-64/0.15.8:
+ resolution: {integrity: sha512-VolFFRatBH09T5QMWhiohAWCOien1R1Uz9K0BRVVTBgBaVBt7eArsXTKxVhUgRf2vwu2c2SXkuP0r7HLG0eozw==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [netbsd]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /esbuild-openbsd-64/0.15.8:
+ resolution: {integrity: sha512-HTAPlg+n4kUeE/isQxlCfsOz0xJGNoT5LJ9oYZWFKABfVf4Ycu7Zlf5ITgOnrdheTkz8JeL/gISIOCFAoOXrSA==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [openbsd]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /esbuild-sunos-64/0.15.8:
+ resolution: {integrity: sha512-qMP/jR/FzcIOwKj+W+Lb+8Cfr8GZHbHUJxAPi7DUhNZMQ/6y7sOgRzlOSpRrbbUntrRZh0MqOyDhJ3Gpo6L1QA==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [sunos]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /esbuild-wasm/0.15.8:
+ resolution: {integrity: sha512-Y7uCl5RNO4URjlemjdx++ukVHEMt5s5AfMWYUnMiK4Sry+pPCvQIctzXq6r6FKCyGKjX6/NGMCqR2OX6aLxj0w==}
+ engines: {node: '>=12'}
+ hasBin: true
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /esbuild-windows-32/0.15.8:
+ resolution: {integrity: sha512-RKR1QHh4iWzjUhkP8Yqi75PPz/KS+b8zw3wUrzw6oAkj+iU5Qtyj61ZDaSG3Qf2vc6hTIUiPqVTqBH0NpXFNwg==}
+ engines: {node: '>=12'}
+ cpu: [ia32]
+ os: [win32]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /esbuild-windows-64/0.15.8:
+ resolution: {integrity: sha512-ag9ptYrsizgsR+PQE8QKeMqnosLvAMonQREpLw4evA4FFgOBMLEat/dY/9txbpozTw9eEOYyD3a4cE9yTu20FA==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [win32]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /esbuild-windows-arm64/0.15.8:
+ resolution: {integrity: sha512-dbpAb0VyPaUs9mgw65KRfQ9rqiWCHpNzrJusoPu+LpEoswosjt/tFxN7cd2l68AT4qWdBkzAjDLRon7uqMeWcg==}
+ engines: {node: '>=12'}
+ cpu: [arm64]
+ os: [win32]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /esbuild/0.15.8:
+ resolution: {integrity: sha512-Remsk2dmr1Ia65sU+QasE6svJbsHe62lzR+CnjpUvbZ+uSYo1SitiOWPRfZQkCu82YWZBBKXiD/j0i//XWMZ+Q==}
+ engines: {node: '>=12'}
+ hasBin: true
+ requiresBuild: true
+ optionalDependencies:
+ '@esbuild/android-arm': 0.15.8
+ '@esbuild/linux-loong64': 0.15.8
+ esbuild-android-64: 0.15.8
+ esbuild-android-arm64: 0.15.8
+ esbuild-darwin-64: 0.15.8
+ esbuild-darwin-arm64: 0.15.8
+ esbuild-freebsd-64: 0.15.8
+ esbuild-freebsd-arm64: 0.15.8
+ esbuild-linux-32: 0.15.8
+ esbuild-linux-64: 0.15.8
+ esbuild-linux-arm: 0.15.8
+ esbuild-linux-arm64: 0.15.8
+ esbuild-linux-mips64le: 0.15.8
+ esbuild-linux-ppc64le: 0.15.8
+ esbuild-linux-riscv64: 0.15.8
+ esbuild-linux-s390x: 0.15.8
+ esbuild-netbsd-64: 0.15.8
+ esbuild-openbsd-64: 0.15.8
+ esbuild-sunos-64: 0.15.8
+ esbuild-windows-32: 0.15.8
+ esbuild-windows-64: 0.15.8
+ esbuild-windows-arm64: 0.15.8
+ dev: true
+
+ /escalade/3.1.1:
+ resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==}
+ engines: {node: '>=6'}
+ dev: true
+
+ /escape-string-regexp/1.0.5:
+ resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==}
+ engines: {node: '>=0.8.0'}
+ dev: true
+
+ /estree-walker/2.0.2:
+ resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==}
+ dev: true
+
+ /event-target-shim/5.0.1:
+ resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==}
+ engines: {node: '>=6'}
+ dev: true
+
+ /execa/5.1.1:
+ resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==}
+ engines: {node: '>=10'}
+ dependencies:
+ cross-spawn: 7.0.3
+ get-stream: 6.0.1
+ human-signals: 2.1.0
+ is-stream: 2.0.1
+ merge-stream: 2.0.0
+ npm-run-path: 4.0.1
+ onetime: 5.1.2
+ signal-exit: 3.0.7
+ strip-final-newline: 2.0.0
+ dev: true
+ optional: true
+
+ /fast-copy/2.1.3:
+ resolution: {integrity: sha512-LDzYKNTHhD+XOp8wGMuCkY4eTxFZOOycmpwLBiuF3r3OjOmZnURRD8t2dUAbmKuXGbo/MGggwbSjcBdp8QT0+g==}
+ dev: true
+
+ /fast-deep-equal/3.1.3:
+ resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
+ dev: true
+ optional: true
+
+ /fast-glob/3.2.12:
+ resolution: {integrity: sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==}
+ engines: {node: '>=8.6.0'}
+ dependencies:
+ '@nodelib/fs.stat': 2.0.5
+ '@nodelib/fs.walk': 1.2.8
+ glob-parent: 5.1.2
+ merge2: 1.4.1
+ micromatch: 4.0.5
+ dev: true
+
+ /fast-redact/3.1.2:
+ resolution: {integrity: sha512-+0em+Iya9fKGfEQGcd62Yv6onjBmmhV1uh86XVfOU8VwAe6kaFdQCWI9s0/Nnugx5Vd9tdbZ7e6gE2tR9dzXdw==}
+ engines: {node: '>=6'}
+ dev: true
+
+ /fast-safe-stringify/2.1.1:
+ resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==}
+ dev: true
+
+ /fast-url-parser/1.1.3:
+ resolution: {integrity: sha512-5jOCVXADYNuRkKFzNJ0dCCewsZiYo0dz8QNYljkOpFC6r2U4OBmKtvm/Tsuh4w1YYdDqDb31a8TVhBJ2OJKdqQ==}
+ dependencies:
+ punycode: 1.4.1
+ dev: true
+ optional: true
+
+ /fastq/1.13.0:
+ resolution: {integrity: sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==}
+ dependencies:
+ reusify: 1.0.4
+ dev: true
+
+ /fetch-blob/3.2.0:
+ resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==}
+ engines: {node: ^12.20 || >= 14.13}
+ dependencies:
+ node-domexception: 1.0.0
+ web-streams-polyfill: 3.2.1
+ dev: true
+
+ /fill-range/7.0.1:
+ resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==}
+ engines: {node: '>=8'}
+ dependencies:
+ to-regex-range: 5.0.1
+ dev: true
+
+ /formdata-polyfill/4.0.10:
+ resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==}
+ engines: {node: '>=12.20.0'}
+ dependencies:
+ fetch-blob: 3.2.0
+ dev: true
+
+ /fraction.js/4.2.0:
+ resolution: {integrity: sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==}
+ dev: true
+
+ /fs.realpath/1.0.0:
+ resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==}
+ dev: true
+
+ /fsevents/2.3.2:
+ resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==}
+ engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
+ os: [darwin]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /function-bind/1.1.1:
+ resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==}
+ dev: true
+
+ /function.prototype.name/1.1.5:
+ resolution: {integrity: sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ call-bind: 1.0.2
+ define-properties: 1.1.4
+ es-abstract: 1.20.2
+ functions-have-names: 1.2.3
+ dev: true
+
+ /functions-have-names/1.2.3:
+ resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==}
+ dev: true
+
+ /fuzzysort/2.0.1:
+ resolution: {integrity: sha512-SlgbPAq0eQ6JQ1h3l4MNeGH/t9DHKH8GGM0RD/6RhmJrNnSoWt3oIVaiQm9g9BPB+wAhRMeMqlUTbhbd7+Ufcg==}
+ dev: true
+
+ /get-intrinsic/1.1.3:
+ resolution: {integrity: sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A==}
+ dependencies:
+ function-bind: 1.1.1
+ has: 1.0.3
+ has-symbols: 1.0.3
+ dev: true
+
+ /get-stream/6.0.1:
+ resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==}
+ engines: {node: '>=10'}
+ dev: true
+ optional: true
+
+ /get-symbol-description/1.0.0:
+ resolution: {integrity: sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ call-bind: 1.0.2
+ get-intrinsic: 1.1.3
+ dev: true
+
+ /glob-parent/5.1.2:
+ resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
+ engines: {node: '>= 6'}
+ dependencies:
+ is-glob: 4.0.3
+ dev: true
+
+ /glob-parent/6.0.2:
+ resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==}
+ engines: {node: '>=10.13.0'}
+ dependencies:
+ is-glob: 4.0.3
+ dev: true
+
+ /glob/7.2.3:
+ resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==}
+ dependencies:
+ fs.realpath: 1.0.0
+ inflight: 1.0.6
+ inherits: 2.0.4
+ minimatch: 3.1.2
+ once: 1.4.0
+ path-is-absolute: 1.0.1
+ dev: true
+
+ /glob/8.0.3:
+ resolution: {integrity: sha512-ull455NHSHI/Y1FqGaaYFaLGkNMMJbavMrEGFXG/PGrg6y7sutWHUHrz6gy6WEBH6akM1M414dWKCNs+IhKdiQ==}
+ engines: {node: '>=12'}
+ dependencies:
+ fs.realpath: 1.0.0
+ inflight: 1.0.6
+ inherits: 2.0.4
+ minimatch: 5.1.0
+ once: 1.4.0
+ dev: true
+
+ /globalyzer/0.1.0:
+ resolution: {integrity: sha512-40oNTM9UfG6aBmuKxk/giHn5nQ8RVz/SS4Ir6zgzOv9/qC3kKZ9v4etGTcJbEl/NyVQH7FGU7d+X1egr57Md2Q==}
+ dev: true
+
+ /globrex/0.1.2:
+ resolution: {integrity: sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==}
+ dev: true
+
+ /graceful-fs/4.2.10:
+ resolution: {integrity: sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==}
+ dev: true
+
+ /has-bigints/1.0.2:
+ resolution: {integrity: sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==}
+ dev: true
+
+ /has-flag/3.0.0:
+ resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==}
+ engines: {node: '>=4'}
+ dev: true
+
+ /has-flag/4.0.0:
+ resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
+ engines: {node: '>=8'}
+ dev: true
+ optional: true
+
+ /has-property-descriptors/1.0.0:
+ resolution: {integrity: sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==}
+ dependencies:
+ get-intrinsic: 1.1.3
+ dev: true
+
+ /has-symbols/1.0.3:
+ resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==}
+ engines: {node: '>= 0.4'}
+ dev: true
+
+ /has-tostringtag/1.0.0:
+ resolution: {integrity: sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ has-symbols: 1.0.3
+ dev: true
+
+ /has/1.0.3:
+ resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==}
+ engines: {node: '>= 0.4.0'}
+ dependencies:
+ function-bind: 1.1.1
+ dev: true
+
+ /help-me/4.1.0:
+ resolution: {integrity: sha512-5HMrkOks2j8Fpu2j5nTLhrBhT7VwHwELpqnSnx802ckofys5MO2SkLpgSz3dgNFHV7IYFX2igm5CM75SmuYidw==}
+ dependencies:
+ glob: 8.0.3
+ readable-stream: 3.6.0
+ dev: true
+
+ /hosted-git-info/2.8.9:
+ resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==}
+ dev: true
+
+ /human-signals/2.1.0:
+ resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==}
+ engines: {node: '>=10.17.0'}
+ dev: true
+ optional: true
+
+ /import-fresh/3.3.0:
+ resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==}
+ engines: {node: '>=6'}
+ dependencies:
+ parent-module: 1.0.1
+ resolve-from: 4.0.0
+ dev: true
+
+ /inflight/1.0.6:
+ resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==}
+ dependencies:
+ once: 1.4.0
+ wrappy: 1.0.2
+ dev: true
+
+ /inherits/2.0.4:
+ resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
+ dev: true
+
+ /ini/1.3.8:
+ resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==}
+ dev: true
+ optional: true
+
+ /internal-slot/1.0.3:
+ resolution: {integrity: sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ get-intrinsic: 1.1.3
+ has: 1.0.3
+ side-channel: 1.0.4
+ dev: true
+
+ /is-arrayish/0.2.1:
+ resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==}
+ dev: true
+
+ /is-bigint/1.0.4:
+ resolution: {integrity: sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==}
+ dependencies:
+ has-bigints: 1.0.2
+ dev: true
+
+ /is-binary-path/2.1.0:
+ resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==}
+ engines: {node: '>=8'}
+ dependencies:
+ binary-extensions: 2.2.0
+ dev: true
+
+ /is-boolean-object/1.1.2:
+ resolution: {integrity: sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ call-bind: 1.0.2
+ has-tostringtag: 1.0.0
+ dev: true
+
+ /is-callable/1.2.6:
+ resolution: {integrity: sha512-krO72EO2NptOGAX2KYyqbP9vYMlNAXdB53rq6f8LXY6RY7JdSR/3BD6wLUlPHSAesmY9vstNrjvqGaCiRK/91Q==}
+ engines: {node: '>= 0.4'}
+ dev: true
+
+ /is-core-module/2.10.0:
+ resolution: {integrity: sha512-Erxj2n/LDAZ7H8WNJXd9tw38GYM3dv8rk8Zcs+jJuxYTW7sozH+SS8NtrSjVL1/vpLvWi1hxy96IzjJ3EHTJJg==}
+ dependencies:
+ has: 1.0.3
+ dev: true
+
+ /is-date-object/1.0.5:
+ resolution: {integrity: sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ has-tostringtag: 1.0.0
+ dev: true
+
+ /is-docker/2.2.1:
+ resolution: {integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==}
+ engines: {node: '>=8'}
+ hasBin: true
+ dev: true
+ optional: true
+
+ /is-extglob/2.1.1:
+ resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
+ engines: {node: '>=0.10.0'}
+ dev: true
+
+ /is-fullwidth-code-point/3.0.0:
+ resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==}
+ engines: {node: '>=8'}
+ dev: true
+ optional: true
+
+ /is-glob/4.0.3:
+ resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
+ engines: {node: '>=0.10.0'}
+ dependencies:
+ is-extglob: 2.1.1
+ dev: true
+
+ /is-negative-zero/2.0.2:
+ resolution: {integrity: sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==}
+ engines: {node: '>= 0.4'}
+ dev: true
+
+ /is-number-object/1.0.7:
+ resolution: {integrity: sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ has-tostringtag: 1.0.0
+ dev: true
+
+ /is-number/7.0.0:
+ resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
+ engines: {node: '>=0.12.0'}
+ dev: true
+
+ /is-port-reachable/4.0.0:
+ resolution: {integrity: sha512-9UoipoxYmSk6Xy7QFgRv2HDyaysmgSG75TFQs6S+3pDM7ZhKTF/bskZV+0UlABHzKjNVhPjYCLfeZUEg1wXxig==}
+ engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
+ dev: true
+ optional: true
+
+ /is-regex/1.1.4:
+ resolution: {integrity: sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ call-bind: 1.0.2
+ has-tostringtag: 1.0.0
+ dev: true
+
+ /is-shared-array-buffer/1.0.2:
+ resolution: {integrity: sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==}
+ dependencies:
+ call-bind: 1.0.2
+ dev: true
+
+ /is-stream/2.0.1:
+ resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==}
+ engines: {node: '>=8'}
+ dev: true
+ optional: true
+
+ /is-string/1.0.7:
+ resolution: {integrity: sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ has-tostringtag: 1.0.0
+ dev: true
+
+ /is-symbol/1.0.4:
+ resolution: {integrity: sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ has-symbols: 1.0.3
+ dev: true
+
+ /is-weakref/1.0.2:
+ resolution: {integrity: sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==}
+ dependencies:
+ call-bind: 1.0.2
+ dev: true
+
+ /is-wsl/2.2.0:
+ resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==}
+ engines: {node: '>=8'}
+ dependencies:
+ is-docker: 2.2.1
+ dev: true
+ optional: true
+
+ /isexe/2.0.0:
+ resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
+ dev: true
+
+ /joycon/3.1.1:
+ resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==}
+ engines: {node: '>=10'}
+ dev: true
+
+ /json-parse-better-errors/1.0.2:
+ resolution: {integrity: sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==}
+ dev: true
+
+ /json-schema-traverse/1.0.0:
+ resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==}
+ dev: true
+ optional: true
+
+ /just-safe-set/4.1.1:
+ resolution: {integrity: sha512-3tQtDVCvZfWc64yEbh2D8R80Zlz+x9LJVpkQ4K3ppdiO7iI1Jzf6wYgsAs1o/EMSwucRbaNb6JHex/24TbSaKw==}
+ dev: true
+
+ /kleur/4.1.5:
+ resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==}
+ engines: {node: '>=6'}
+ dev: true
+
+ /lilconfig/2.0.6:
+ resolution: {integrity: sha512-9JROoBW7pobfsx+Sq2JsASvCo6Pfo6WWoUW79HuB1BCoBXD4PLWJPqDF6fNj67pqBYTbAHkE57M1kS/+L1neOg==}
+ engines: {node: '>=10'}
+ dev: true
+
+ /load-json-file/4.0.0:
+ resolution: {integrity: sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==}
+ engines: {node: '>=4'}
+ dependencies:
+ graceful-fs: 4.2.10
+ parse-json: 4.0.0
+ pify: 3.0.0
+ strip-bom: 3.0.0
+ dev: true
+
+ /lodash.set/4.3.2:
+ resolution: {integrity: sha512-4hNPN5jlm/N/HLMCO43v8BXKq9Z7QdAGc/VGrRD61w8gN9g/6jF9A4L1pbUgBLCffi0w9VsXfTOij5x8iTyFvg==}
+ dev: true
+
+ /magic-string/0.25.9:
+ resolution: {integrity: sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==}
+ dependencies:
+ sourcemap-codec: 1.4.8
+ dev: true
+
+ /magic-string/0.26.3:
+ resolution: {integrity: sha512-u1Po0NDyFcwdg2nzHT88wSK0+Rih0N1M+Ph1Sp08k8yvFFU3KR72wryS7e1qMPJypt99WB7fIFVCA92mQrMjrg==}
+ engines: {node: '>=12'}
+ dependencies:
+ sourcemap-codec: 1.4.8
+ dev: true
+
+ /memorystream/0.3.1:
+ resolution: {integrity: sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==}
+ engines: {node: '>= 0.10.0'}
+ dev: true
+
+ /merge-stream/2.0.0:
+ resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==}
+ dev: true
+ optional: true
+
+ /merge2/1.4.1:
+ resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
+ engines: {node: '>= 8'}
+ dev: true
+
+ /micromatch/4.0.5:
+ resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==}
+ engines: {node: '>=8.6'}
+ dependencies:
+ braces: 3.0.2
+ picomatch: 2.3.1
+ dev: true
+
+ /mime-db/1.33.0:
+ resolution: {integrity: sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==}
+ engines: {node: '>= 0.6'}
+ dev: true
+ optional: true
+
+ /mime-db/1.52.0:
+ resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==}
+ engines: {node: '>= 0.6'}
+ dev: true
+ optional: true
+
+ /mime-types/2.1.18:
+ resolution: {integrity: sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==}
+ engines: {node: '>= 0.6'}
+ dependencies:
+ mime-db: 1.33.0
+ dev: true
+ optional: true
+
+ /mime-types/2.1.35:
+ resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
+ engines: {node: '>= 0.6'}
+ dependencies:
+ mime-db: 1.52.0
+ dev: true
+ optional: true
+
+ /mime/3.0.0:
+ resolution: {integrity: sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==}
+ engines: {node: '>=10.0.0'}
+ hasBin: true
+ dev: true
+
+ /mimic-fn/2.1.0:
+ resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==}
+ engines: {node: '>=6'}
+ dev: true
+ optional: true
+
+ /min-indent/1.0.1:
+ resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==}
+ engines: {node: '>=4'}
+ dev: true
+
+ /mini-svg-data-uri/1.4.4:
+ resolution: {integrity: sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg==}
+ hasBin: true
+ dev: true
+
+ /minimatch/3.0.4:
+ resolution: {integrity: sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==}
+ dependencies:
+ brace-expansion: 1.1.11
+ dev: true
+ optional: true
+
+ /minimatch/3.1.2:
+ resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
+ dependencies:
+ brace-expansion: 1.1.11
+ dev: true
+
+ /minimatch/5.1.0:
+ resolution: {integrity: sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==}
+ engines: {node: '>=10'}
+ dependencies:
+ brace-expansion: 2.0.1
+ dev: true
+
+ /minimist/1.2.6:
+ resolution: {integrity: sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==}
+ dev: true
+
+ /mkdirp/0.5.6:
+ resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==}
+ hasBin: true
+ dependencies:
+ minimist: 1.2.6
+ dev: true
+
+ /mri/1.2.0:
+ resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==}
+ engines: {node: '>=4'}
+ dev: true
+
+ /mrmime/1.0.1:
+ resolution: {integrity: sha512-hzzEagAgDyoU1Q6yg5uI+AorQgdvMCur3FcKf7NhMKWsaYg+RnbTyHRa/9IlLF9rf455MOCtcqqrQQ83pPP7Uw==}
+ engines: {node: '>=10'}
+ dev: true
+
+ /ms/2.0.0:
+ resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==}
+ dev: true
+ optional: true
+
+ /ms/2.1.2:
+ resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==}
+ dev: true
+
+ /nanoid/3.3.4:
+ resolution: {integrity: sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==}
+ engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
+ hasBin: true
+ dev: true
+
+ /negotiator/0.6.3:
+ resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==}
+ engines: {node: '>= 0.6'}
+ dev: true
+ optional: true
+
+ /nice-try/1.0.5:
+ resolution: {integrity: sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==}
+ dev: true
+
+ /node-domexception/1.0.0:
+ resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==}
+ engines: {node: '>=10.5.0'}
+ dev: true
+
+ /node-fetch/3.2.10:
+ resolution: {integrity: sha512-MhuzNwdURnZ1Cp4XTazr69K0BTizsBroX7Zx3UgDSVcZYKF/6p0CBe4EUb/hLqmzVhl0UpYfgRljQ4yxE+iCxA==}
+ engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
+ dependencies:
+ data-uri-to-buffer: 4.0.0
+ fetch-blob: 3.2.0
+ formdata-polyfill: 4.0.10
+ dev: true
+
+ /node-releases/2.0.6:
+ resolution: {integrity: sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==}
+ dev: true
+
+ /normalize-package-data/2.5.0:
+ resolution: {integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==}
+ dependencies:
+ hosted-git-info: 2.8.9
+ resolve: 1.22.1
+ semver: 5.7.1
+ validate-npm-package-license: 3.0.4
+ dev: true
+
+ /normalize-path/3.0.0:
+ resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
+ engines: {node: '>=0.10.0'}
+ dev: true
+
+ /normalize-range/0.1.2:
+ resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==}
+ engines: {node: '>=0.10.0'}
+ dev: true
+
+ /npm-run-all/4.1.5:
+ resolution: {integrity: sha512-Oo82gJDAVcaMdi3nuoKFavkIHBRVqQ1qvMb+9LHk/cF4P6B2m8aP04hGf7oL6wZ9BuGwX1onlLhpuoofSyoQDQ==}
+ engines: {node: '>= 4'}
+ hasBin: true
+ dependencies:
+ ansi-styles: 3.2.1
+ chalk: 2.4.2
+ cross-spawn: 6.0.5
+ memorystream: 0.3.1
+ minimatch: 3.1.2
+ pidtree: 0.3.1
+ read-pkg: 3.0.0
+ shell-quote: 1.7.3
+ string.prototype.padend: 3.1.3
+ dev: true
+
+ /npm-run-path/4.0.1:
+ resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==}
+ engines: {node: '>=8'}
+ dependencies:
+ path-key: 3.1.1
+ dev: true
+ optional: true
+
+ /object-hash/3.0.0:
+ resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==}
+ engines: {node: '>= 6'}
+ dev: true
+
+ /object-inspect/1.12.2:
+ resolution: {integrity: sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==}
+ dev: true
+
+ /object-keys/1.1.1:
+ resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==}
+ engines: {node: '>= 0.4'}
+ dev: true
+
+ /object.assign/4.1.4:
+ resolution: {integrity: sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ call-bind: 1.0.2
+ define-properties: 1.1.4
+ has-symbols: 1.0.3
+ object-keys: 1.1.1
+ dev: true
+
+ /on-exit-leak-free/2.1.0:
+ resolution: {integrity: sha512-VuCaZZAjReZ3vUwgOB8LxAosIurDiAW0s13rI1YwmaP++jvcxP77AWoQvenZebpCA2m8WC1/EosPYPMjnRAp/w==}
+ dev: true
+
+ /on-headers/1.0.2:
+ resolution: {integrity: sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==}
+ engines: {node: '>= 0.8'}
+ dev: true
+ optional: true
+
+ /once/1.4.0:
+ resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
+ dependencies:
+ wrappy: 1.0.2
+ dev: true
+
+ /onetime/5.1.2:
+ resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==}
+ engines: {node: '>=6'}
+ dependencies:
+ mimic-fn: 2.1.0
+ dev: true
+ optional: true
+
+ /parent-module/1.0.1:
+ resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==}
+ engines: {node: '>=6'}
+ dependencies:
+ callsites: 3.1.0
+ dev: true
+
+ /parse-json/4.0.0:
+ resolution: {integrity: sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==}
+ engines: {node: '>=4'}
+ dependencies:
+ error-ex: 1.3.2
+ json-parse-better-errors: 1.0.2
+ dev: true
+
+ /path-is-absolute/1.0.1:
+ resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==}
+ engines: {node: '>=0.10.0'}
+ dev: true
+
+ /path-is-inside/1.0.2:
+ resolution: {integrity: sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w==}
+ dev: true
+ optional: true
+
+ /path-key/2.0.1:
+ resolution: {integrity: sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==}
+ engines: {node: '>=4'}
+ dev: true
+
+ /path-key/3.1.1:
+ resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
+ engines: {node: '>=8'}
+ dev: true
+ optional: true
+
+ /path-parse/1.0.7:
+ resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
+ dev: true
+
+ /path-to-regexp/2.2.1:
+ resolution: {integrity: sha512-gu9bD6Ta5bwGrrU8muHzVOBFFREpp2iRkVfhBJahwJ6p6Xw20SjT0MxLnwkjOibQmGSYhiUnf2FLe7k+jcFmGQ==}
+ dev: true
+ optional: true
+
+ /path-type/3.0.0:
+ resolution: {integrity: sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==}
+ engines: {node: '>=4'}
+ dependencies:
+ pify: 3.0.0
+ dev: true
+
+ /picocolors/1.0.0:
+ resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==}
+ dev: true
+
+ /picomatch/2.3.1:
+ resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
+ engines: {node: '>=8.6'}
+ dev: true
+
+ /pidtree/0.3.1:
+ resolution: {integrity: sha512-qQbW94hLHEqCg7nhby4yRC7G2+jYHY4Rguc2bjw7Uug4GIJuu1tvf2uHaZv5Q8zdt+WKJ6qK1FOI6amaWUo5FA==}
+ engines: {node: '>=0.10'}
+ hasBin: true
+ dev: true
+
+ /pify/2.3.0:
+ resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==}
+ engines: {node: '>=0.10.0'}
+ dev: true
+
+ /pify/3.0.0:
+ resolution: {integrity: sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==}
+ engines: {node: '>=4'}
+ dev: true
+
+ /pino-abstract-transport/1.0.0:
+ resolution: {integrity: sha512-c7vo5OpW4wIS42hUVcT5REsL8ZljsUfBjqV/e2sFxmFEFZiq1XLUp5EYLtuDH6PEHq9W1egWqRbnLUP5FuZmOA==}
+ dependencies:
+ readable-stream: 4.1.0
+ split2: 4.1.0
+ dev: true
+
+ /pino-pretty/9.1.0:
+ resolution: {integrity: sha512-IM6NY9LLo/dVgY7/prJhCh4rAJukafdt0ibxeNOWc2fxKMyTk90SOB9Ao2HfbtShT9QPeP0ePpJktksMhSQMYA==}
+ hasBin: true
+ dependencies:
+ colorette: 2.0.19
+ dateformat: 4.6.3
+ fast-copy: 2.1.3
+ fast-safe-stringify: 2.1.1
+ help-me: 4.1.0
+ joycon: 3.1.1
+ minimist: 1.2.6
+ on-exit-leak-free: 2.1.0
+ pino-abstract-transport: 1.0.0
+ pump: 3.0.0
+ readable-stream: 4.1.0
+ secure-json-parse: 2.5.0
+ sonic-boom: 3.2.0
+ strip-json-comments: 3.1.1
+ dev: true
+
+ /pino-std-serializers/6.0.0:
+ resolution: {integrity: sha512-mMMOwSKrmyl+Y12Ri2xhH1lbzQxwwpuru9VjyJpgFIH4asSj88F2csdMwN6+M5g1Ll4rmsYghHLQJw81tgZ7LQ==}
+ dev: true
+
+ /pino/8.6.0:
+ resolution: {integrity: sha512-gCEOs6XpgiM8mSFjiLXQejDJ1PZww8AUmHowQ16QpqpXQDIm3mFwn/29+Y6CJxd6i+x3uXduuerjq+IqWoABbA==}
+ hasBin: true
+ dependencies:
+ atomic-sleep: 1.0.0
+ fast-redact: 3.1.2
+ on-exit-leak-free: 2.1.0
+ pino-abstract-transport: 1.0.0
+ pino-std-serializers: 6.0.0
+ process-warning: 2.0.0
+ quick-format-unescaped: 4.0.4
+ real-require: 0.2.0
+ safe-stable-stringify: 2.4.0
+ sonic-boom: 3.2.0
+ thread-stream: 2.2.0
+ dev: true
+
+ /playwright-core/1.24.2:
+ resolution: {integrity: sha512-zfAoDoPY/0sDLsgSgLZwWmSCevIg1ym7CppBwllguVBNiHeixZkc1AdMuYUPZC6AdEYc4CxWEyLMBTw2YcmRrA==}
+ engines: {node: '>=14'}
+ hasBin: true
+ dev: true
+ optional: true
+
+ /playwright-core/1.25.2:
+ resolution: {integrity: sha512-0yTbUE9lIddkEpLHL3u8PoCL+pWiZtj5A/j3U7YoNjcmKKDGBnCrgHJMzwd2J5vy6l28q4ki3JIuz7McLHhl1A==}
+ engines: {node: '>=14'}
+ hasBin: true
+ dev: true
+
+ /postcss-import/14.1.0_postcss@8.4.16:
+ resolution: {integrity: sha512-flwI+Vgm4SElObFVPpTIT7SU7R3qk2L7PyduMcokiaVKuWv9d/U+Gm/QAd8NDLuykTWTkcrjOeD2Pp1rMeBTGw==}
+ engines: {node: '>=10.0.0'}
+ peerDependencies:
+ postcss: ^8.0.0
+ dependencies:
+ postcss: 8.4.16
+ postcss-value-parser: 4.2.0
+ read-cache: 1.0.0
+ resolve: 1.22.1
+ dev: true
+
+ /postcss-js/4.0.0_postcss@8.4.16:
+ resolution: {integrity: sha512-77QESFBwgX4irogGVPgQ5s07vLvFqWr228qZY+w6lW599cRlK/HmnlivnnVUxkjHnCu4J16PDMHcH+e+2HbvTQ==}
+ engines: {node: ^12 || ^14 || >= 16}
+ peerDependencies:
+ postcss: ^8.3.3
+ dependencies:
+ camelcase-css: 2.0.1
+ postcss: 8.4.16
+ dev: true
+
+ /postcss-load-config/3.1.4_postcss@8.4.16:
+ resolution: {integrity: sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==}
+ engines: {node: '>= 10'}
+ peerDependencies:
+ postcss: '>=8.0.9'
+ ts-node: '>=9.0.0'
+ peerDependenciesMeta:
+ postcss:
+ optional: true
+ ts-node:
+ optional: true
+ dependencies:
+ lilconfig: 2.0.6
+ postcss: 8.4.16
+ yaml: 1.10.2
+ dev: true
+
+ /postcss-load-config/4.0.1_postcss@8.4.16:
+ resolution: {integrity: sha512-vEJIc8RdiBRu3oRAI0ymerOn+7rPuMvRXslTvZUKZonDHFIczxztIyJ1urxM1x9JXEikvpWWTUUqal5j/8QgvA==}
+ engines: {node: '>= 14'}
+ peerDependencies:
+ postcss: '>=8.0.9'
+ ts-node: '>=9.0.0'
+ peerDependenciesMeta:
+ postcss:
+ optional: true
+ ts-node:
+ optional: true
+ dependencies:
+ lilconfig: 2.0.6
+ postcss: 8.4.16
+ yaml: 2.1.1
+ dev: true
+
+ /postcss-nested/5.0.6_postcss@8.4.16:
+ resolution: {integrity: sha512-rKqm2Fk0KbA8Vt3AdGN0FB9OBOMDVajMG6ZCf/GoHgdxUJ4sBFp0A/uMIRm+MJUdo33YXEtjqIz8u7DAp8B7DA==}
+ engines: {node: '>=12.0'}
+ peerDependencies:
+ postcss: ^8.2.14
+ dependencies:
+ postcss: 8.4.16
+ postcss-selector-parser: 6.0.10
+ dev: true
+
+ /postcss-selector-parser/6.0.10:
+ resolution: {integrity: sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==}
+ engines: {node: '>=4'}
+ dependencies:
+ cssesc: 3.0.0
+ util-deprecate: 1.0.2
+ dev: true
+
+ /postcss-value-parser/4.2.0:
+ resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==}
+ dev: true
+
+ /postcss/8.4.16:
+ resolution: {integrity: sha512-ipHE1XBvKzm5xI7hiHCZJCSugxvsdq2mPnsq5+UF+VHCjiBvtDrlxJfMBToWaP9D5XlgNmcFGqoHmUn0EYEaRQ==}
+ engines: {node: ^10 || ^12 || >=14}
+ dependencies:
+ nanoid: 3.3.4
+ picocolors: 1.0.0
+ source-map-js: 1.0.2
+ dev: true
+
+ /process-warning/2.0.0:
+ resolution: {integrity: sha512-+MmoAXoUX+VTHAlwns0h+kFUWFs/3FZy+ZuchkgjyOu3oioLAo2LB5aCfKPh2+P9O18i3m43tUEv3YqttSy0Ww==}
+ dev: true
+
+ /pump/3.0.0:
+ resolution: {integrity: sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==}
+ dependencies:
+ end-of-stream: 1.4.4
+ once: 1.4.0
+ dev: true
+
+ /punycode/1.4.1:
+ resolution: {integrity: sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==}
+ dev: true
+ optional: true
+
+ /punycode/2.1.1:
+ resolution: {integrity: sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==}
+ engines: {node: '>=6'}
+ dev: true
+ optional: true
+
+ /queue-microtask/1.2.3:
+ resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
+ dev: true
+
+ /quick-format-unescaped/4.0.4:
+ resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==}
+ dev: true
+
+ /quick-lru/5.1.1:
+ resolution: {integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==}
+ engines: {node: '>=10'}
+ dev: true
+
+ /range-parser/1.2.0:
+ resolution: {integrity: sha512-kA5WQoNVo4t9lNx2kQNFCxKeBl5IbbSNBl1M/tLkw9WCn+hxNBAW5Qh8gdhs63CJnhjJ2zQWFoqPJP2sK1AV5A==}
+ engines: {node: '>= 0.6'}
+ dev: true
+ optional: true
+
+ /rc/1.2.8:
+ resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==}
+ hasBin: true
+ dependencies:
+ deep-extend: 0.6.0
+ ini: 1.3.8
+ minimist: 1.2.6
+ strip-json-comments: 2.0.1
+ dev: true
+ optional: true
+
+ /read-cache/1.0.0:
+ resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==}
+ dependencies:
+ pify: 2.3.0
+ dev: true
+
+ /read-pkg/3.0.0:
+ resolution: {integrity: sha512-BLq/cCO9two+lBgiTYNqD6GdtK8s4NpaWrl6/rCO9w0TUS8oJl7cmToOZfRYllKTISY6nt1U7jQ53brmKqY6BA==}
+ engines: {node: '>=4'}
+ dependencies:
+ load-json-file: 4.0.0
+ normalize-package-data: 2.5.0
+ path-type: 3.0.0
+ dev: true
+
+ /readable-stream/3.6.0:
+ resolution: {integrity: sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==}
+ engines: {node: '>= 6'}
+ dependencies:
+ inherits: 2.0.4
+ string_decoder: 1.3.0
+ util-deprecate: 1.0.2
+ dev: true
+
+ /readable-stream/4.1.0:
+ resolution: {integrity: sha512-sVisi3+P2lJ2t0BPbpK629j8wRW06yKGJUcaLAGXPAUhyUxVJm7VsCTit1PFgT4JHUDMrGNR+ZjSKpzGaRF3zw==}
+ engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+ dependencies:
+ abort-controller: 3.0.0
+ dev: true
+
+ /readdirp/3.6.0:
+ resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
+ engines: {node: '>=8.10.0'}
+ dependencies:
+ picomatch: 2.3.1
+ dev: true
+
+ /real-require/0.2.0:
+ resolution: {integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==}
+ engines: {node: '>= 12.13.0'}
+ dev: true
+
+ /regexp.prototype.flags/1.4.3:
+ resolution: {integrity: sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ call-bind: 1.0.2
+ define-properties: 1.1.4
+ functions-have-names: 1.2.3
+ dev: true
+
+ /registry-auth-token/3.3.2:
+ resolution: {integrity: sha512-JL39c60XlzCVgNrO+qq68FoNb56w/m7JYvGR2jT5iR1xBrUA3Mfx5Twk5rqTThPmQKMWydGmq8oFtDlxfrmxnQ==}
+ dependencies:
+ rc: 1.2.8
+ safe-buffer: 5.2.1
+ dev: true
+ optional: true
+
+ /registry-url/3.1.0:
+ resolution: {integrity: sha512-ZbgR5aZEdf4UKZVBPYIgaglBmSF2Hi94s2PcIHhRGFjKYu+chjJdYfHn4rt3hB6eCKLJ8giVIIfgMa1ehDfZKA==}
+ engines: {node: '>=0.10.0'}
+ dependencies:
+ rc: 1.2.8
+ dev: true
+ optional: true
+
+ /require-from-string/2.0.2:
+ resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==}
+ engines: {node: '>=0.10.0'}
+ dev: true
+ optional: true
+
+ /resolve-from/4.0.0:
+ resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
+ engines: {node: '>=4'}
+ dev: true
+
+ /resolve/1.22.1:
+ resolution: {integrity: sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==}
+ hasBin: true
+ dependencies:
+ is-core-module: 2.10.0
+ path-parse: 1.0.7
+ supports-preserve-symlinks-flag: 1.0.0
+ dev: true
+
+ /reusify/1.0.4:
+ resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==}
+ engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
+ dev: true
+
+ /rimraf/2.7.1:
+ resolution: {integrity: sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==}
+ hasBin: true
+ dependencies:
+ glob: 7.2.3
+ dev: true
+
+ /rollup/2.78.1:
+ resolution: {integrity: sha512-VeeCgtGi4P+o9hIg+xz4qQpRl6R401LWEXBmxYKOV4zlF82lyhgh2hTZnheFUbANE8l2A41F458iwj2vEYaXJg==}
+ engines: {node: '>=10.0.0'}
+ hasBin: true
+ optionalDependencies:
+ fsevents: 2.3.2
+ dev: true
+
+ /run-parallel/1.2.0:
+ resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
+ dependencies:
+ queue-microtask: 1.2.3
+ dev: true
+
+ /sade/1.8.1:
+ resolution: {integrity: sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==}
+ engines: {node: '>=6'}
+ dependencies:
+ mri: 1.2.0
+ dev: true
+
+ /safe-buffer/5.1.2:
+ resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==}
+ dev: true
+ optional: true
+
+ /safe-buffer/5.2.1:
+ resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
+ dev: true
+
+ /safe-stable-stringify/2.4.0:
+ resolution: {integrity: sha512-eehKHKpab6E741ud7ZIMcXhKcP6TSIezPkNZhy5U8xC6+VvrRdUA2tMgxGxaGl4cz7c2Ew5+mg5+wNB16KQqrA==}
+ engines: {node: '>=10'}
+ dev: true
+
+ /sander/0.5.1:
+ resolution: {integrity: sha512-3lVqBir7WuKDHGrKRDn/1Ye3kwpXaDOMsiRP1wd6wpZW56gJhsbp5RqQpA6JG/P+pkXizygnr1dKR8vzWaVsfA==}
+ dependencies:
+ es6-promise: 3.3.1
+ graceful-fs: 4.2.10
+ mkdirp: 0.5.6
+ rimraf: 2.7.1
+ dev: true
+
+ /secure-json-parse/2.5.0:
+ resolution: {integrity: sha512-ZQruFgZnIWH+WyO9t5rWt4ZEGqCKPwhiw+YbzTwpmT9elgLrLcfuyUiSnwwjUiVy9r4VM3urtbNF1xmEh9IL2w==}
+ dev: true
+
+ /semver/5.7.1:
+ resolution: {integrity: sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==}
+ hasBin: true
+ dev: true
+
+ /serve-handler/6.1.3:
+ resolution: {integrity: sha512-FosMqFBNrLyeiIDvP1zgO6YoTzFYHxLDEIavhlmQ+knB2Z7l1t+kGLHkZIDN7UVWqQAmKI3D20A6F6jo3nDd4w==}
+ dependencies:
+ bytes: 3.0.0
+ content-disposition: 0.5.2
+ fast-url-parser: 1.1.3
+ mime-types: 2.1.18
+ minimatch: 3.0.4
+ path-is-inside: 1.0.2
+ path-to-regexp: 2.2.1
+ range-parser: 1.2.0
+ dev: true
+ optional: true
+
+ /serve/14.0.1:
+ resolution: {integrity: sha512-tNGwxl27FwA8TbmMQqN0jTaSx8/trL532qZsJHX1VdiEIjjtMJHCs7AFS6OvtC7cTHOvmjXqt5yczejU6CV2Xg==}
+ engines: {node: '>= 14'}
+ hasBin: true
+ requiresBuild: true
+ dependencies:
+ '@zeit/schemas': 2.21.0
+ ajv: 8.11.0
+ arg: 5.0.2
+ boxen: 7.0.0
+ chalk: 5.0.1
+ chalk-template: 0.4.0
+ clipboardy: 3.0.0
+ compression: 1.7.4
+ is-port-reachable: 4.0.0
+ serve-handler: 6.1.3
+ update-check: 1.5.4
+ transitivePeerDependencies:
+ - supports-color
+ dev: true
+ optional: true
+
+ /set-cookie-parser/2.5.1:
+ resolution: {integrity: sha512-1jeBGaKNGdEq4FgIrORu/N570dwoPYio8lSoYLWmX7sQ//0JY08Xh9o5pBcgmHQ/MbsYp/aZnOe1s1lIsbLprQ==}
+ dev: true
+
+ /shebang-command/1.2.0:
+ resolution: {integrity: sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==}
+ engines: {node: '>=0.10.0'}
+ dependencies:
+ shebang-regex: 1.0.0
+ dev: true
+
+ /shebang-command/2.0.0:
+ resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
+ engines: {node: '>=8'}
+ dependencies:
+ shebang-regex: 3.0.0
+ dev: true
+ optional: true
+
+ /shebang-regex/1.0.0:
+ resolution: {integrity: sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==}
+ engines: {node: '>=0.10.0'}
+ dev: true
+
+ /shebang-regex/3.0.0:
+ resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==}
+ engines: {node: '>=8'}
+ dev: true
+ optional: true
+
+ /shell-quote/1.7.3:
+ resolution: {integrity: sha512-Vpfqwm4EnqGdlsBFNmHhxhElJYrdfcxPThu+ryKS5J8L/fhAwLazFZtq+S+TWZ9ANj2piSQLGj6NQg+lKPmxrw==}
+ dev: true
+
+ /side-channel/1.0.4:
+ resolution: {integrity: sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==}
+ dependencies:
+ call-bind: 1.0.2
+ get-intrinsic: 1.1.3
+ object-inspect: 1.12.2
+ dev: true
+
+ /signal-exit/3.0.7:
+ resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==}
+ dev: true
+ optional: true
+
+ /sirv/2.0.2:
+ resolution: {integrity: sha512-4Qog6aE29nIjAOKe/wowFTxOdmbEZKb+3tsLljaBRzJwtqto0BChD2zzH0LhgCSXiI+V7X+Y45v14wBZQ1TK3w==}
+ engines: {node: '>= 10'}
+ dependencies:
+ '@polka/url': 1.0.0-next.21
+ mrmime: 1.0.1
+ totalist: 3.0.0
+ dev: true
+
+ /sonic-boom/3.2.0:
+ resolution: {integrity: sha512-SbbZ+Kqj/XIunvIAgUZRlqd6CGQYq71tRRbXR92Za8J/R3Yh4Av+TWENiSiEgnlwckYLyP0YZQWVfyNC0dzLaA==}
+ dependencies:
+ atomic-sleep: 1.0.0
+ dev: true
+
+ /sorcery/0.10.0:
+ resolution: {integrity: sha512-R5ocFmKZQFfSTstfOtHjJuAwbpGyf9qjQa1egyhvXSbM7emjrtLXtGdZsDJDABC85YBfVvrOiGWKSYXPKdvP1g==}
+ hasBin: true
+ dependencies:
+ buffer-crc32: 0.2.13
+ minimist: 1.2.6
+ sander: 0.5.1
+ sourcemap-codec: 1.4.8
+ dev: true
+
+ /source-map-js/1.0.2:
+ resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==}
+ engines: {node: '>=0.10.0'}
+ dev: true
+
+ /sourcemap-codec/1.4.8:
+ resolution: {integrity: sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==}
+ dev: true
+
+ /spdx-correct/3.1.1:
+ resolution: {integrity: sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==}
+ dependencies:
+ spdx-expression-parse: 3.0.1
+ spdx-license-ids: 3.0.12
+ dev: true
+
+ /spdx-exceptions/2.3.0:
+ resolution: {integrity: sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==}
+ dev: true
+
+ /spdx-expression-parse/3.0.1:
+ resolution: {integrity: sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==}
+ dependencies:
+ spdx-exceptions: 2.3.0
+ spdx-license-ids: 3.0.12
+ dev: true
+
+ /spdx-license-ids/3.0.12:
+ resolution: {integrity: sha512-rr+VVSXtRhO4OHbXUiAF7xW3Bo9DuuF6C5jH+q/x15j2jniycgKbxU09Hr0WqlSLUs4i4ltHGXqTe7VHclYWyA==}
+ dev: true
+
+ /split2/4.1.0:
+ resolution: {integrity: sha512-VBiJxFkxiXRlUIeyMQi8s4hgvKCSjtknJv/LVYbrgALPwf5zSKmEwV9Lst25AkvMDnvxODugjdl6KZgwKM1WYQ==}
+ engines: {node: '>= 10.x'}
+ dev: true
+
+ /string-width/4.2.3:
+ resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
+ engines: {node: '>=8'}
+ dependencies:
+ emoji-regex: 8.0.0
+ is-fullwidth-code-point: 3.0.0
+ strip-ansi: 6.0.1
+ dev: true
+ optional: true
+
+ /string-width/5.1.2:
+ resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==}
+ engines: {node: '>=12'}
+ dependencies:
+ eastasianwidth: 0.2.0
+ emoji-regex: 9.2.2
+ strip-ansi: 7.0.1
+ dev: true
+ optional: true
+
+ /string.prototype.padend/3.1.3:
+ resolution: {integrity: sha512-jNIIeokznm8SD/TZISQsZKYu7RJyheFNt84DUPrh482GC8RVp2MKqm2O5oBRdGxbDQoXrhhWtPIWQOiy20svUg==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ call-bind: 1.0.2
+ define-properties: 1.1.4
+ es-abstract: 1.20.2
+ dev: true
+
+ /string.prototype.trimend/1.0.5:
+ resolution: {integrity: sha512-I7RGvmjV4pJ7O3kdf+LXFpVfdNOxtCW/2C8f6jNiW4+PQchwxkCDzlk1/7p+Wl4bqFIZeF47qAHXLuHHWKAxog==}
+ dependencies:
+ call-bind: 1.0.2
+ define-properties: 1.1.4
+ es-abstract: 1.20.2
+ dev: true
+
+ /string.prototype.trimstart/1.0.5:
+ resolution: {integrity: sha512-THx16TJCGlsN0o6dl2o6ncWUsdgnLRSA23rRE5pyGBw/mLr3Ej/R2LaqCtgP8VNMGZsvMWnf9ooZPyY2bHvUFg==}
+ dependencies:
+ call-bind: 1.0.2
+ define-properties: 1.1.4
+ es-abstract: 1.20.2
+ dev: true
+
+ /string_decoder/1.3.0:
+ resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==}
+ dependencies:
+ safe-buffer: 5.2.1
+ dev: true
+
+ /strip-ansi/6.0.1:
+ resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==}
+ engines: {node: '>=8'}
+ dependencies:
+ ansi-regex: 5.0.1
+ dev: true
+ optional: true
+
+ /strip-ansi/7.0.1:
+ resolution: {integrity: sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==}
+ engines: {node: '>=12'}
+ dependencies:
+ ansi-regex: 6.0.1
+ dev: true
+ optional: true
+
+ /strip-bom/3.0.0:
+ resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==}
+ engines: {node: '>=4'}
+ dev: true
+
+ /strip-final-newline/2.0.0:
+ resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==}
+ engines: {node: '>=6'}
+ dev: true
+ optional: true
+
+ /strip-indent/3.0.0:
+ resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==}
+ engines: {node: '>=8'}
+ dependencies:
+ min-indent: 1.0.1
+ dev: true
+
+ /strip-json-comments/2.0.1:
+ resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==}
+ engines: {node: '>=0.10.0'}
+ dev: true
+ optional: true
+
+ /strip-json-comments/3.1.1:
+ resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
+ engines: {node: '>=8'}
+ dev: true
+
+ /supports-color/5.5.0:
+ resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==}
+ engines: {node: '>=4'}
+ dependencies:
+ has-flag: 3.0.0
+ dev: true
+
+ /supports-color/7.2.0:
+ resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
+ engines: {node: '>=8'}
+ dependencies:
+ has-flag: 4.0.0
+ dev: true
+ optional: true
+
+ /supports-preserve-symlinks-flag/1.0.0:
+ resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
+ engines: {node: '>= 0.4'}
+ dev: true
+
+ /svelte-adapter-bun/0.3.1:
+ resolution: {integrity: sha512-IoxRX6VYd7JlX+g5NVRH2Uc6WadQMPEOF2Cz4y7XasyY1XC8DriD8osdhm8ENQYf8/ou8fCjuddh1MiHqGFQDA==}
+ dependencies:
+ tiny-glob: 0.2.9
+ dev: true
+
+ /svelte-check/2.9.0_zy537mcxrfn3wq4nghluxnsi5a:
+ resolution: {integrity: sha512-9AVrtP7WbfDgCdqTZNPdj5CCCy1OrYMxFVWAWzNw7fl93c9klFJFtqzVXa6fovfQ050CcpUyJE2dPFL9TFAREw==}
+ hasBin: true
+ peerDependencies:
+ svelte: ^3.24.0
+ dependencies:
+ '@jridgewell/trace-mapping': 0.3.15
+ chokidar: 3.5.3
+ fast-glob: 3.2.12
+ import-fresh: 3.3.0
+ picocolors: 1.0.0
+ sade: 1.8.1
+ svelte: 3.50.1
+ svelte-preprocess: 4.10.7_sxt3mkf5m54vn3maobdwhqzitu
+ typescript: 4.8.3
+ transitivePeerDependencies:
+ - '@babel/core'
+ - coffeescript
+ - less
+ - node-sass
+ - postcss
+ - postcss-load-config
+ - pug
+ - sass
+ - stylus
+ - sugarss
+ dev: true
+
+ /svelte-hmr/0.15.0_svelte@3.50.1:
+ resolution: {integrity: sha512-Aw21SsyoohyVn4yiKXWPNCSW2DQNH/76kvUnE9kpt4h9hcg9tfyQc6xshx9hzgMfGF0kVx0EGD8oBMWSnATeOg==}
+ engines: {node: ^12.20 || ^14.13.1 || >= 16}
+ peerDependencies:
+ svelte: '>=3.19.0'
+ dependencies:
+ svelte: 3.50.1
+ dev: true
+
+ /svelte-preprocess/4.10.7_sxt3mkf5m54vn3maobdwhqzitu:
+ resolution: {integrity: sha512-sNPBnqYD6FnmdBrUmBCaqS00RyCsCpj2BG58A1JBswNF7b0OKviwxqVrOL/CKyJrLSClrSeqQv5BXNg2RUbPOw==}
+ engines: {node: '>= 9.11.2'}
+ requiresBuild: true
+ peerDependencies:
+ '@babel/core': ^7.10.2
+ coffeescript: ^2.5.1
+ less: ^3.11.3 || ^4.0.0
+ node-sass: '*'
+ postcss: ^7 || ^8
+ postcss-load-config: ^2.1.0 || ^3.0.0 || ^4.0.0
+ pug: ^3.0.0
+ sass: ^1.26.8
+ stylus: ^0.55.0
+ sugarss: ^2.0.0
+ svelte: ^3.23.0
+ typescript: ^3.9.5 || ^4.0.0
+ peerDependenciesMeta:
+ '@babel/core':
+ optional: true
+ coffeescript:
+ optional: true
+ less:
+ optional: true
+ node-sass:
+ optional: true
+ postcss:
+ optional: true
+ postcss-load-config:
+ optional: true
+ pug:
+ optional: true
+ sass:
+ optional: true
+ stylus:
+ optional: true
+ sugarss:
+ optional: true
+ typescript:
+ optional: true
+ dependencies:
+ '@types/pug': 2.0.6
+ '@types/sass': 1.43.1
+ detect-indent: 6.1.0
+ magic-string: 0.25.9
+ postcss: 8.4.16
+ postcss-load-config: 4.0.1_postcss@8.4.16
+ sorcery: 0.10.0
+ strip-indent: 3.0.0
+ svelte: 3.50.1
+ typescript: 4.8.3
+ dev: true
+
+ /svelte-splitpanes/0.7.3:
+ resolution: {integrity: sha512-IsByD11xPI/KnI6T8xMWL+YHr+dATt4tVVoFJR676o0w7Lw3lUFq6l4zdibg3Apli7duFfCicGhT6/7NPblnyQ==}
+ optionalDependencies:
+ '@playwright/test': 1.24.2
+ serve: 14.0.1
+ transitivePeerDependencies:
+ - supports-color
+ dev: true
+
+ /svelte/3.50.1:
+ resolution: {integrity: sha512-bS4odcsdj5D5jEg6riZuMg5NKelzPtmsCbD9RG+8umU03TeNkdWnP6pqbCm0s8UQNBkqk29w/Bdubn3C+HWSwA==}
+ engines: {node: '>= 8'}
+ dev: true
+
+ /tailwindcss/3.1.8_postcss@8.4.16:
+ resolution: {integrity: sha512-YSneUCZSFDYMwk+TGq8qYFdCA3yfBRdBlS7txSq0LUmzyeqRe3a8fBQzbz9M3WS/iFT4BNf/nmw9mEzrnSaC0g==}
+ engines: {node: '>=12.13.0'}
+ hasBin: true
+ peerDependencies:
+ postcss: ^8.0.9
+ dependencies:
+ arg: 5.0.2
+ chokidar: 3.5.3
+ color-name: 1.1.4
+ detective: 5.2.1
+ didyoumean: 1.2.2
+ dlv: 1.1.3
+ fast-glob: 3.2.12
+ glob-parent: 6.0.2
+ is-glob: 4.0.3
+ lilconfig: 2.0.6
+ normalize-path: 3.0.0
+ object-hash: 3.0.0
+ picocolors: 1.0.0
+ postcss: 8.4.16
+ postcss-import: 14.1.0_postcss@8.4.16
+ postcss-js: 4.0.0_postcss@8.4.16
+ postcss-load-config: 3.1.4_postcss@8.4.16
+ postcss-nested: 5.0.6_postcss@8.4.16
+ postcss-selector-parser: 6.0.10
+ postcss-value-parser: 4.2.0
+ quick-lru: 5.1.1
+ resolve: 1.22.1
+ transitivePeerDependencies:
+ - ts-node
+ dev: true
+
+ /temporal-polyfill/0.0.8:
+ resolution: {integrity: sha512-IuA8GhS1PRC04H/zVNAIxJvCZQum6V5HjqFj7gz1a3SMUf/Kf1xIXILNYtxrWYnGqIU/RrDRxlCKCm/vmqnBvw==}
+ dependencies:
+ temporal-spec: 0.0.3
+ dev: true
+
+ /temporal-spec/0.0.3:
+ resolution: {integrity: sha512-gJu7QRqn5c2vTSkYWGC4qz1i+FZ9C+Cz16UIBMRcjgXOsHfXeSIgaWUKeq/2rz1iNfFxvmF/ywqbfC6ggTpjkA==}
+ dev: true
+
+ /thread-stream/2.2.0:
+ resolution: {integrity: sha512-rUkv4/fnb4rqy/gGy7VuqK6wE1+1DOCOWy4RMeaV69ZHMP11tQKZvZSip1yTgrKCMZzEMcCL/bKfHvSfDHx+iQ==}
+ dependencies:
+ real-require: 0.2.0
+ dev: true
+
+ /tiny-glob/0.2.9:
+ resolution: {integrity: sha512-g/55ssRPUjShh+xkfx9UPDXqhckHEsHr4Vd9zX55oSdGZc/MD0m3sferOkwWtp98bv+kcVfEHtRJgBVJzelrzg==}
+ dependencies:
+ globalyzer: 0.1.0
+ globrex: 0.1.2
+ dev: true
+
+ /to-regex-range/5.0.1:
+ resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
+ engines: {node: '>=8.0'}
+ dependencies:
+ is-number: 7.0.0
+ dev: true
+
+ /totalist/3.0.0:
+ resolution: {integrity: sha512-eM+pCBxXO/njtF7vdFsHuqb+ElbxqtI4r5EAvk6grfAFyJ6IvWlSkfZ5T9ozC6xWw3Fj1fGoSmrl0gUs46JVIw==}
+ engines: {node: '>=6'}
+ dev: true
+
+ /tslib/2.4.0:
+ resolution: {integrity: sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==}
+ dev: true
+
+ /type-fest/2.19.0:
+ resolution: {integrity: sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==}
+ engines: {node: '>=12.20'}
+ dev: true
+ optional: true
+
+ /typesafe-i18n/5.13.0_typescript@4.8.3:
+ resolution: {integrity: sha512-Q72l+LqB37kNT2R39mkTwQy1tuQ7URAahD1QXbR84itO864xvVgdoS8xaRAatp0y2/oU7f+2EzpAK3YGp0g+eA==}
+ hasBin: true
+ peerDependencies:
+ typescript: '>=3.5.1'
+ dependencies:
+ typescript: 4.8.3
+ dev: true
+
+ /typescript/4.8.3:
+ resolution: {integrity: sha512-goMHfm00nWPa8UvR/CPSvykqf6dVV8x/dp0c5mFTMTIu0u0FlGWRioyy7Nn0PGAdHxpJZnuO/ut+PpQ8UiHAig==}
+ engines: {node: '>=4.2.0'}
+ hasBin: true
+ dev: true
+
+ /unbox-primitive/1.0.2:
+ resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==}
+ dependencies:
+ call-bind: 1.0.2
+ has-bigints: 1.0.2
+ has-symbols: 1.0.3
+ which-boxed-primitive: 1.0.2
+ dev: true
+
+ /undici/5.10.0:
+ resolution: {integrity: sha512-c8HsD3IbwmjjbLvoZuRI26TZic+TSEe8FPMLLOkN1AfYRhdjnKBU6yL+IwcSCbdZiX4e5t0lfMDLDCqj4Sq70g==}
+ engines: {node: '>=12.18'}
+ dev: true
+
+ /update-browserslist-db/1.0.9_browserslist@4.21.4:
+ resolution: {integrity: sha512-/xsqn21EGVdXI3EXSum1Yckj3ZVZugqyOZQ/CxYPBD/R+ko9NSUScf8tFF4dOKY+2pvSSJA/S+5B8s4Zr4kyvg==}
+ hasBin: true
+ peerDependencies:
+ browserslist: '>= 4.21.0'
+ dependencies:
+ browserslist: 4.21.4
+ escalade: 3.1.1
+ picocolors: 1.0.0
+ dev: true
+
+ /update-check/1.5.4:
+ resolution: {integrity: sha512-5YHsflzHP4t1G+8WGPlvKbJEbAJGCgw+Em+dGR1KmBUbr1J36SJBqlHLjR7oob7sco5hWHGQVcr9B2poIVDDTQ==}
+ dependencies:
+ registry-auth-token: 3.3.2
+ registry-url: 3.1.0
+ dev: true
+ optional: true
+
+ /uri-js/4.4.1:
+ resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
+ dependencies:
+ punycode: 2.1.1
+ dev: true
+ optional: true
+
+ /util-deprecate/1.0.2:
+ resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
+ dev: true
+
+ /validate-npm-package-license/3.0.4:
+ resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==}
+ dependencies:
+ spdx-correct: 3.1.1
+ spdx-expression-parse: 3.0.1
+ dev: true
+
+ /vary/1.1.2:
+ resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==}
+ engines: {node: '>= 0.8'}
+ dev: true
+ optional: true
+
+ /vite/3.1.3:
+ resolution: {integrity: sha512-/3XWiktaopByM5bd8dqvHxRt5EEgRikevnnrpND0gRfNkrMrPaGGexhtLCzv15RcCMtV2CLw+BPas8YFeSG0KA==}
+ engines: {node: ^14.18.0 || >=16.0.0}
+ hasBin: true
+ peerDependencies:
+ less: '*'
+ sass: '*'
+ stylus: '*'
+ terser: ^5.4.0
+ peerDependenciesMeta:
+ less:
+ optional: true
+ sass:
+ optional: true
+ stylus:
+ optional: true
+ terser:
+ optional: true
+ dependencies:
+ esbuild: 0.15.8
+ postcss: 8.4.16
+ resolve: 1.22.1
+ rollup: 2.78.1
+ optionalDependencies:
+ fsevents: 2.3.2
+ dev: true
+
+ /web-streams-polyfill/3.2.1:
+ resolution: {integrity: sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==}
+ engines: {node: '>= 8'}
+ dev: true
+
+ /which-boxed-primitive/1.0.2:
+ resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==}
+ dependencies:
+ is-bigint: 1.0.4
+ is-boolean-object: 1.1.2
+ is-number-object: 1.0.7
+ is-string: 1.0.7
+ is-symbol: 1.0.4
+ dev: true
+
+ /which/1.3.1:
+ resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==}
+ hasBin: true
+ dependencies:
+ isexe: 2.0.0
+ dev: true
+
+ /which/2.0.2:
+ resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
+ engines: {node: '>= 8'}
+ hasBin: true
+ dependencies:
+ isexe: 2.0.0
+ dev: true
+ optional: true
+
+ /widest-line/4.0.1:
+ resolution: {integrity: sha512-o0cyEG0e8GPzT4iGHphIOh0cJOV8fivsXxddQasHPHfoZf1ZexrfeA21w2NaEN1RHE+fXlfISmOE8R9N3u3Qig==}
+ engines: {node: '>=12'}
+ dependencies:
+ string-width: 5.1.2
+ dev: true
+ optional: true
+
+ /wrap-ansi/8.0.1:
+ resolution: {integrity: sha512-QFF+ufAqhoYHvoHdajT/Po7KoXVBPXS2bgjIam5isfWJPfIOnQZ50JtUiVvCv/sjgacf3yRrt2ZKUZ/V4itN4g==}
+ engines: {node: '>=12'}
+ dependencies:
+ ansi-styles: 6.1.1
+ string-width: 5.1.2
+ strip-ansi: 7.0.1
+ dev: true
+ optional: true
+
+ /wrappy/1.0.2:
+ resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
+ dev: true
+
+ /xtend/4.0.2:
+ resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==}
+ engines: {node: '>=0.4'}
+ dev: true
+
+ /yaml/1.10.2:
+ resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==}
+ engines: {node: '>= 6'}
+ dev: true
+
+ /yaml/2.1.1:
+ resolution: {integrity: sha512-o96x3OPo8GjWeSLF+wOAbrPfhFOGY0W00GNaxCDv+9hkcDJEnev1yh8S7pgHF0ik6zc8sQLuL8hjHjJULZp8bw==}
+ engines: {node: '>= 14'}
+ dev: true
diff --git a/apps/kit/postcss.config.cjs b/apps/kit/postcss.config.cjs
new file mode 100644
index 0000000..e48cff5
--- /dev/null
+++ b/apps/kit/postcss.config.cjs
@@ -0,0 +1,13 @@
+const tailwindcss = require("tailwindcss");
+const autoprefixer = require("autoprefixer");
+
+const config = {
+ plugins: [
+ //Some plugins, like tailwindcss/nesting, need to run before Tailwind,
+ tailwindcss(),
+ //But others, like autoprefixer, need to run after,
+ autoprefixer,
+ ],
+};
+
+module.exports = config;
diff --git a/apps/kit/src/app.d.ts b/apps/kit/src/app.d.ts
new file mode 100644
index 0000000..4ab4e43
--- /dev/null
+++ b/apps/kit/src/app.d.ts
@@ -0,0 +1,9 @@
+// See https://kit.svelte.dev/docs/types#app
+// for information about these interfaces
+// and what to do when importing types
+declare namespace App {
+ interface Locals {}
+ interface Platform {}
+ interface PrivateEnv {}
+ interface PublicEnv {}
+}
diff --git a/apps/kit/src/app.html b/apps/kit/src/app.html
new file mode 100644
index 0000000..3df27c1
--- /dev/null
+++ b/apps/kit/src/app.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html class="h-full" lang="en">
+ <head>
+ <meta charset="utf-8" />
+ <meta name="viewport" content="width=device-width" />
+ <script src="%sveltekit.assets%/preload.js"></script>
+ %sveltekit.head%
+ </head>
+ <body class="h-full">
+ <div>%sveltekit.body%</div>
+ </body>
+</html>
diff --git a/apps/kit/src/app.pcss b/apps/kit/src/app.pcss
new file mode 100644
index 0000000..f9c290c
--- /dev/null
+++ b/apps/kit/src/app.pcss
@@ -0,0 +1,21 @@
+/* Write your global styles here, in PostCSS syntax */
+@tailwind base;
+@tailwind components;
+@tailwind utilities;
+pre {
+ font-family: monospace !important;
+}
+
+*:focus-visible {
+ outline: 1px auto;
+}
+
+.c-disabled {
+ cursor: not-allowed !important;
+ filter: opacity(.45);
+ pointer-events: none !important;
+}
+
+.c-disabled.loading {
+ cursor: wait !important;
+}
diff --git a/apps/kit/src/hooks/index.server.ts b/apps/kit/src/hooks/index.server.ts
new file mode 100644
index 0000000..414318d
--- /dev/null
+++ b/apps/kit/src/hooks/index.server.ts
@@ -0,0 +1,52 @@
+import { CookieNames } from "$lib/configuration";
+import { detectLocale, locales } from '$lib/i18n/i18n-util'
+import type { Handle, RequestEvent } from '@sveltejs/kit'
+import { sequence } from "@sveltejs/kit/hooks";
+import { initAcceptLanguageHeaderDetector } from 'typesafe-i18n/detectors'
+import { parse, serialize } from "cookie";
+import { logDebug } from "$lib/logger";
+
+const handleLocale: Handle = async ({ event, resolve }) => {
+ const cookies = parse(event.request.headers.get("Cookie") ?? '');
+ const localeCookie = cookies[CookieNames.locale];
+ const preferredLocale = getPreferredLocale(event);
+ let finalLocale = localeCookie ?? preferredLocale;
+
+ logDebug("Handling locale", {
+ locales,
+ localeCookie,
+ preferredLocale,
+ finalLocale
+ });
+
+ if (locales.findIndex((locale) => locale === finalLocale) === -1) finalLocale = "en";
+ if (!localeCookie) {
+ // Set a locale cookie
+ event.setHeaders({
+ "Set-Cookie": serialize(CookieNames.locale, finalLocale, {
+ path: "/",
+ expires: new Date(2099, 1, 1, 0, 0, 0, 0),
+ sameSite: "strict"
+ })
+ });
+ }
+ // replace html lang attribute with correct language
+ return resolve(event, { transformPageChunk: ({ html }) => html.replace('%lang%', finalLocale) });
+}
+
+function getPreferredLocale(event: RequestEvent) {
+ // detect the preferred language the user has configured in it's browser
+ // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Language
+ const headers = transformHeaders(event)
+ const acceptLanguageDetector = initAcceptLanguageHeaderDetector({ headers })
+
+ return detectLocale(acceptLanguageDetector)
+}
+
+function transformHeaders({ request }: RequestEvent) {
+ const headers: Record<string, string> = {}
+ request.headers.forEach((value, key) => (headers[key] = value))
+ return headers
+}
+
+export const handle = sequence(handleLocale);
diff --git a/apps/kit/src/lib/api/internal-fetch.ts b/apps/kit/src/lib/api/internal-fetch.ts
new file mode 100644
index 0000000..b21d669
--- /dev/null
+++ b/apps/kit/src/lib/api/internal-fetch.ts
@@ -0,0 +1,170 @@
+import { Temporal } from "temporal-polyfill";
+import { clear_session_data } from "$lib/session";
+import { resolve_references } from "$lib/helpers";
+import type { IInternalFetchResponse } from "$lib/models/IInternalFetchResponse";
+import type { IInternalFetchRequest } from "$lib/models/IInternalFetchRequest";
+import { redirect } from "@sveltejs/kit";
+
+export async function http_post(url: string, body?: object | string, timeout = -1, skip_401_check = false, abort_signal?: AbortSignal): Promise<IInternalFetchResponse> {
+ const init = {
+ method: "post",
+ } as RequestInit;
+
+ if (abort_signal) {
+ init.signal = abort_signal;
+ }
+
+ if (body) {
+ init.headers = {
+ "Content-Type": "application/json;charset=UTF-8",
+ };
+ init.body = JSON.stringify(body);
+ }
+
+ const response = await internal_fetch({ url, init, timeout });
+ const result = {} as IInternalFetchResponse;
+
+ if (!skip_401_check && await is_401(response)) return result;
+
+ result.ok = response.ok;
+ result.status = response.status;
+ result.http_response = response;
+
+ if (response.status !== 204) {
+ try {
+ const ct = response.headers.get("Content-Type")?.toString() ?? "";
+ if (ct.startsWith("application/json")) {
+ const data = await response.json();
+ result.data = resolve_references(data);
+ } else if (ct.startsWith("text/plain")) {
+ const text = await response.text();
+ result.data = text as string;
+ }
+ } catch {
+ // Ignored
+ }
+ }
+
+ return result;
+}
+
+export async function http_get(url: string, timeout = -1, skip_401_check = false, abort_signal?: AbortSignal): Promise<IInternalFetchResponse> {
+ const init = {
+ method: "get",
+ } as RequestInit;
+
+ if (abort_signal) {
+ init.signal = abort_signal;
+ }
+
+ const response = await internal_fetch({ url, init, timeout });
+ const result = {} as IInternalFetchResponse;
+
+ if (!skip_401_check && await is_401(response)) return result;
+
+ result.ok = response.ok;
+ result.status = response.status;
+ result.http_response = response;
+
+ if (response.status !== 204) {
+ try {
+ const ct = response.headers.get("Content-Type")?.toString() ?? "";
+ if (ct.startsWith("application/json")) {
+ const data = await response.json();
+ result.data = resolve_references(data);
+ } else if (ct.startsWith("text/plain")) {
+ const text = await response.text();
+ result.data = text as string;
+ }
+ } catch {
+ // Ignored
+ }
+ }
+
+ return result;
+}
+
+export async function http_delete(url: string, body?: object | string, timeout = -1, skip_401_check = false, abort_signal?: AbortSignal): Promise<IInternalFetchResponse> {
+ const init = {
+ method: "delete",
+ } as RequestInit;
+
+ if (abort_signal) {
+ init.signal = abort_signal;
+ }
+
+ if (body) {
+ init.headers = {
+ "Content-Type": "application/json;charset=UTF-8",
+ };
+ init.body = JSON.stringify(body);
+ }
+
+ const response = await internal_fetch({ url, init, timeout });
+ const result = {} as IInternalFetchResponse;
+
+ if (!skip_401_check && await is_401(response)) return result;
+
+ result.ok = response.ok;
+ result.status = response.status;
+ result.http_response = response;
+
+ if (response.status !== 204) {
+ try {
+ const ct = response.headers.get("Content-Type")?.toString() ?? "";
+ if (ct.startsWith("application/json")) {
+ const data = await response.json();
+ result.data = resolve_references(data);
+ } else if (ct.startsWith("text/plain")) {
+ const text = await response.text();
+ result.data = text as string;
+ }
+ } catch (error) {
+ // ignored
+ }
+ }
+
+ return result;
+}
+
+async function internal_fetch(request: IInternalFetchRequest): Promise<Response> {
+ if (!request.init) request.init = {};
+ request.init.credentials = "include";
+ request.init.headers = {
+ "X-TimeZone": Temporal.Now.timeZone().id,
+ ...request.init.headers
+ };
+
+ const fetch_request = new Request(request.url, request.init);
+ let response: any;
+
+ try {
+ if (request.timeout && request.timeout > 500) {
+ response = await Promise.race([
+ fetch(fetch_request),
+ new Promise((_, reject) => setTimeout(() => reject(new Error("Timeout")), request.timeout))
+ ]);
+ } else {
+ response = await fetch(fetch_request);
+ }
+ } catch (error: any) {
+ console.log(error);
+ if (error.message === "Timeout") {
+ console.error("Request timed out");
+ } else if (error.message === "Network request failed") {
+ console.error("No internet connection");
+ } else {
+ throw error; // rethrow other unexpected errors
+ }
+ }
+
+ return response;
+}
+
+async function is_401(response: Response): Promise<boolean> {
+ if (response.status === 401) {
+ clear_session_data();
+ throw redirect(307, "/login");
+ }
+ return false;
+}
diff --git a/apps/kit/src/lib/api/root.ts b/apps/kit/src/lib/api/root.ts
new file mode 100644
index 0000000..3e5bda2
--- /dev/null
+++ b/apps/kit/src/lib/api/root.ts
@@ -0,0 +1,6 @@
+import {http_post} from "$lib/api/internal-fetch";
+import {api_base} from "$lib/configuration";
+
+export function server_log(message: string): void {
+ http_post(api_base("_/api/log"), message);
+}
diff --git a/apps/kit/src/lib/api/time-entry.ts b/apps/kit/src/lib/api/time-entry.ts
new file mode 100644
index 0000000..a40b0c2
--- /dev/null
+++ b/apps/kit/src/lib/api/time-entry.ts
@@ -0,0 +1,83 @@
+import {api_base} from "$lib/configuration";
+import {is_guid} from "$lib/helpers";
+import {http_delete, http_get, http_post} from "./internal-fetch";
+import type {TimeCategoryDto} from "$lib/models/TimeCategoryDto";
+import type {TimeLabelDto} from "$lib/models/TimeLabelDto";
+import type {TimeEntryDto} from "$lib/models/TimeEntryDto";
+import type {TimeEntryQuery} from "$lib/models/TimeEntryQuery";
+import type {IInternalFetchResponse} from "$lib/models/IInternalFetchResponse";
+
+
+// ENTRIES
+
+export async function create_time_entry(payload: TimeEntryDto): Promise<IInternalFetchResponse> {
+ return http_post(api_base("v1/entries/create"), payload);
+}
+
+export async function get_time_entry(entryId: string): Promise<IInternalFetchResponse> {
+ if (is_guid(entryId)) {
+ return http_get(api_base("v1/entries/" + entryId));
+ }
+ throw new Error("entryId is not a valid guid.");
+}
+
+export async function get_time_entries(entryQuery: TimeEntryQuery): Promise<IInternalFetchResponse> {
+ return http_post(api_base("v1/entries/query"), entryQuery);
+}
+
+export async function delete_time_entry(id: string): Promise<IInternalFetchResponse> {
+ if (!is_guid(id)) throw new Error("id is not a valid guid");
+ return http_delete(api_base("v1/entries/" + id + "/delete"));
+}
+
+export async function update_time_entry(entryDto: TimeEntryDto): Promise<IInternalFetchResponse> {
+ if (!is_guid(entryDto.id ?? "")) throw new Error("id is not a valid guid");
+ if (!entryDto.category) throw new Error("category is empty");
+ if (!entryDto.stop) throw new Error("stop is empty");
+ if (!entryDto.start) throw new Error("start is empty");
+ return http_post(api_base("v1/entries/update"), entryDto);
+}
+
+// LABELS
+export async function create_time_label(labelDto: TimeLabelDto): Promise<IInternalFetchResponse> {
+ return http_post(api_base("v1/labels/create"), labelDto);
+}
+
+export async function get_time_labels(): Promise<IInternalFetchResponse> {
+ return http_get(api_base("v1/labels"));
+}
+
+export async function delete_time_label(id: string): Promise<IInternalFetchResponse> {
+ if (!is_guid(id)) throw new Error("id is not a valid guid");
+ return http_delete(api_base("v1/labels/" + id + "/delete"));
+}
+
+export async function update_time_label(labelDto: TimeLabelDto): Promise<IInternalFetchResponse> {
+ if (!is_guid(labelDto.id ?? "")) throw new Error("id is not a valid guid");
+ if (!labelDto.name) throw new Error("name is empty");
+ if (!labelDto.color) throw new Error("color is empty");
+ return http_post(api_base("v1/labels/update"), labelDto);
+}
+
+// CATEGORIES
+export async function create_time_category(category: TimeCategoryDto): Promise<IInternalFetchResponse> {
+ if (!category.name) throw new Error("name is empty");
+ if (!category.color) throw new Error("color is empty");
+ return http_post(api_base("v1/categories/create"), category);
+}
+
+export async function get_time_categories(): Promise<IInternalFetchResponse> {
+ return http_get(api_base("v1/categories"));
+}
+
+export async function delete_time_category(id: string): Promise<IInternalFetchResponse> {
+ if (!is_guid(id)) throw new Error("id is not a valid guid");
+ return http_delete(api_base("v1/categories/" + id + "/delete"));
+}
+
+export async function update_time_category(category: TimeCategoryDto): Promise<IInternalFetchResponse> {
+ if (!is_guid(category.id ?? "")) throw new Error("id is not a valid guid");
+ if (!category.name) throw new Error("name is empty");
+ if (!category.color) throw new Error("color is empty");
+ return http_post(api_base("v1/categories/update"), category);
+}
diff --git a/apps/kit/src/lib/api/user.ts b/apps/kit/src/lib/api/user.ts
new file mode 100644
index 0000000..f0dc932
--- /dev/null
+++ b/apps/kit/src/lib/api/user.ts
@@ -0,0 +1,47 @@
+import {api_base} from "$lib/configuration";
+import {http_delete, http_get, http_post} from "./internal-fetch";
+import type {LoginPayload} from "$lib/models/LoginPayload";
+import type {UpdateProfilePayload} from "$lib/models/UpdateProfilePayload";
+import type {CreateAccountPayload} from "$lib/models/CreateAccountPayload";
+import type {IInternalFetchResponse} from "$lib/models/IInternalFetchResponse";
+
+export async function login(payload: LoginPayload): Promise<IInternalFetchResponse> {
+ return http_post(api_base("_/account/login"), payload);
+}
+
+export async function logout(): Promise<IInternalFetchResponse> {
+ return http_get(api_base("_/account/logout"));
+}
+
+export async function create_forgot_password_request(username: string): Promise<IInternalFetchResponse> {
+ if (!username) throw new Error("Username is empty");
+ return http_get(api_base("_/forgot-password-requests/create?username=" + username));
+}
+
+export async function check_forgot_password_request(public_id: string): Promise<IInternalFetchResponse> {
+ if (!public_id) throw new Error("Id is empty");
+ return http_get(api_base("_/forgot-password-requests/is-valid?id=" + public_id));
+}
+
+export async function fulfill_forgot_password_request(public_id: string, newPassword: string): Promise<IInternalFetchResponse> {
+ if (!public_id) throw new Error("Id is empty");
+ return http_post(api_base("_/forgot-password-requests/fulfill"), {id: public_id, newPassword});
+}
+
+export async function delete_account(): Promise<IInternalFetchResponse> {
+ return http_delete(api_base("_/account/delete"));
+}
+
+export async function update_profile(payload: UpdateProfilePayload): Promise<IInternalFetchResponse> {
+ if (!payload.password && !payload.username) throw new Error("Password and Username is empty");
+ return http_post(api_base("_/account/update"), payload);
+}
+
+export async function create_account(payload: CreateAccountPayload): Promise<IInternalFetchResponse> {
+ if (!payload.password && !payload.username) throw new Error("Password and Username is empty");
+ return http_post(api_base("_/account/create"), payload);
+}
+
+export async function get_profile_for_active_check(): Promise<IInternalFetchResponse> {
+ return http_get(api_base("_/account"), 0, true);
+}
diff --git a/apps/kit/src/lib/colors.ts b/apps/kit/src/lib/colors.ts
new file mode 100644
index 0000000..34c7992
--- /dev/null
+++ b/apps/kit/src/lib/colors.ts
@@ -0,0 +1,47 @@
+export function generate_random_hex_color(skip_contrast_check = false) {
+ let hex = __generate_random_hex_color();
+ if (skip_contrast_check) return hex;
+ while ((__calculate_contrast_ratio("#ffffff", hex) < 4.5) || (__calculate_contrast_ratio("#000000", hex) < 4.5)) {
+ hex = __generate_random_hex_color();
+ }
+
+ return hex;
+}
+
+// Largely copied from chroma js api
+function __generate_random_hex_color(): string {
+ let code = "#";
+ for (let i = 0; i < 6; i++) {
+ code += "0123456789abcdef".charAt(Math.floor(Math.random() * 16));
+ }
+ return code;
+}
+
+function __calculate_contrast_ratio(hex1: string, hex2: string): number {
+ const rgb1 = __hex_to_rgb(hex1);
+ const rgb2 = __hex_to_rgb(hex2);
+ const l1 = __get_luminance(rgb1[0], rgb1[1], rgb1[2]);
+ const l2 = __get_luminance(rgb2[0], rgb2[1], rgb2[2]);
+ const result = l1 > l2 ? (l1 + 0.05) / (l2 + 0.05) : (l2 + 0.05) / (l1 + 0.05);
+ return result;
+}
+
+function __hex_to_rgb(hex: string): number[] {
+ if (!hex.match(/^#([A-Fa-f0-9]{6})$/)) return [];
+ if (hex[0] === "#") hex = hex.substring(1, hex.length);
+ return [parseInt(hex.substring(0, 2), 16), parseInt(hex.substring(2, 4), 16), parseInt(hex.substring(4, 6), 16)];
+}
+
+function __get_luminance(r: any, g: any, b: any) {
+ // relative luminance
+ // see http://www.w3.org/TR/2008/REC-WCAG20-20081211/#relativeluminancedef
+ r = __luminance_x(r);
+ g = __luminance_x(g);
+ b = __luminance_x(b);
+ return 0.2126 * r + 0.7152 * g + 0.0722 * b;
+}
+
+function __luminance_x(x: any) {
+ x /= 255;
+ return x <= 0.03928 ? x / 12.92 : Math.pow((x + 0.055) / 1.055, 2.4);
+}
diff --git a/apps/kit/src/lib/components/alert.svelte b/apps/kit/src/lib/components/alert.svelte
new file mode 100644
index 0000000..4a5c7ea
--- /dev/null
+++ b/apps/kit/src/lib/components/alert.svelte
@@ -0,0 +1,149 @@
+<script lang="ts">
+ import { random_string } from "$shared/lib/helpers";
+ import { afterUpdate, onMount } from "svelte";
+ import { Temporal } from "temporal-polyfill";
+
+ const noCooldownSetting = "no-cooldown";
+ // if no unique id is supplied, cooldown will not work between page loads.
+ // Therefore we are disabling it with noCooldownSetting in the fallback id.
+ export let id = "alert--" + noCooldownSetting + "--" + random_string(4);
+ export let title = "";
+ export let message = "";
+ export let type = "info";
+ export let closeable = false;
+ export let closeableCooldown = "-1";
+ export let visible = true;
+
+ const cooldownStorageKey = "lastseen--" + id;
+ $: cooldownEnabled =
+ id.indexOf(noCooldownSetting) === -1 &&
+ closeable &&
+ (closeableCooldown === "~" || parseInt(closeableCooldown) > 0);
+
+ function close() {
+ visible = false;
+ if (cooldownEnabled) {
+ console.log(
+ "Cooldown enabled for " + id + ", " + closeableCooldown === "~"
+ ? "with an endless cooldown"
+ : ""
+ );
+ localStorage.setItem(
+ cooldownStorageKey,
+ String(Temporal.Now.instant().epochSeconds)
+ );
+ }
+ }
+
+ // Manages the state of the alert if cooldown is enabled
+ function run_cooldown() {
+ if (!cooldownEnabled) {
+ console.log("Alert cooldown is not enabled for " + id);
+ return;
+ }
+ if (!localStorage.getItem(cooldownStorageKey)) {
+ console.log("Alert " + id + " has not been seen yet, displaying");
+ visible = true;
+ return;
+ }
+ if (!visible) {
+ console.log(
+ "Alert " + id + " is not visible, stopping cooldown change"
+ );
+ return;
+ }
+ if (closeableCooldown === "~") {
+ console.log("Alert " + id + " has an infinite cooldown, hiding");
+ visible = false;
+ return;
+ }
+
+ const lastSeen = Temporal.Instant.fromEpochSeconds(
+ localStorage.getItem(cooldownStorageKey) as number
+ );
+ if (
+ Temporal.Instant.compare(
+ Temporal.Now.instant(),
+ lastSeen.add({ seconds: parseInt(closeableCooldown) })
+ ) === 1
+ ) {
+ console.log(
+ "Alert " +
+ id +
+ " has a cooldown of " +
+ closeableCooldown +
+ " and was last seen " +
+ lastSeen.toLocaleString() +
+ " making it due for a showing"
+ );
+ visible = true;
+ } else {
+ visible = false;
+ }
+ }
+
+ onMount(() => {
+ if (cooldownEnabled) {
+ run_cooldown();
+ }
+ });
+
+ afterUpdate(() => {
+ if (type === "default") {
+ type = "primary";
+ }
+ });
+</script>
+
+<div
+ class="alert alert--{type} padding-sm radius-md"
+ {id}
+ class:alert--is-visible={visible}
+ role="alert"
+>
+ <div class="flex justify-between">
+ <div class="flex flex-row items-center">
+ <svg
+ class="icon icon--sm alert__icon margin-right-xxs"
+ viewBox="0 0 24 24"
+ aria-hidden="true"
+ >
+ <path
+ d="M12,0C5.383,0,0,5.383,0,12s5.383,12,12,12s12-5.383,12-12S18.617,0,12,0z M14.658,18.284 c-0.661,0.26-2.952,1.354-4.272,0.191c-0.394-0.346-0.59-0.785-0.59-1.318c0-0.998,0.328-1.868,0.919-3.957 c0.104-0.395,0.231-0.907,0.231-1.313c0-0.701-0.266-0.887-0.987-0.887c-0.352,0-0.742,0.125-1.095,0.257l0.195-0.799 c0.787-0.32,1.775-0.71,2.621-0.71c1.269,0,2.203,0.633,2.203,1.837c0,0.347-0.06,0.955-0.186,1.375l-0.73,2.582 c-0.151,0.522-0.424,1.673-0.001,2.014c0.416,0.337,1.401,0.158,1.887-0.071L14.658,18.284z M13.452,8c-0.828,0-1.5-0.672-1.5-1.5 s0.672-1.5,1.5-1.5s1.5,0.672,1.5,1.5S14.28,8,13.452,8z"
+ />
+ </svg>
+ {#if title}
+ <p class="text-sm">
+ <strong class="error-title">{title}</strong>
+ </p>
+ {:else if message}
+ <div class="text-component text-sm break-word">
+ {@html message}
+ </div>
+ {/if}
+ </div>
+ {#if closeable}
+ <button class="reset alert__close-btn" on:click={close}>
+ <svg
+ class="icon"
+ viewBox="0 0 20 20"
+ fill="none"
+ stroke="currentColor"
+ stroke-linecap="round"
+ stroke-linejoin="round"
+ stroke-width="2"
+ >
+ <title>Close alert</title>
+ <line x1="3" y1="3" x2="17" y2="17" />
+ <line x1="17" y1="3" x2="3" y2="17" />
+ </svg>
+ </button>
+ {/if}
+ </div>
+
+ {#if message && title}
+ <div class="text-component text-sm break-word padding-top-xs">
+ {@html message}
+ </div>
+ {/if}
+</div>
diff --git a/apps/kit/src/lib/components/button.svelte b/apps/kit/src/lib/components/button.svelte
new file mode 100644
index 0000000..5550e5e
--- /dev/null
+++ b/apps/kit/src/lib/components/button.svelte
@@ -0,0 +1,72 @@
+<script lang="ts">
+ export type ButtonKind = "primary" | "secondary" | "white"
+ export type ButtonSize = "xs" | "sm" | "md" | "lg" | "xl";
+ export let kind = "primary" as ButtonKind;
+ export let size = "sm" as ButtonSize;
+ export let type: "button" | "submit" | "reset" = "button";
+ export let id = undefined;
+ export let tabindex = undefined;
+ export let style = undefined;
+ export let title = undefined;
+ export let disabled = false;
+ export let href = undefined;
+ export let text;
+
+ let sizeClasses = "px-3 py-2 text-xs";
+ let kindClasses = "border-transparent text-white bg-indigo-600 hover:bg-indigo-700 focus:ring-indigo-500";
+
+ $: shared_props = {
+ type: type,
+ id: id || null,
+ title: title || null,
+ disabled: disabled || null,
+ tabindex: tabindex || null,
+ style: style || null,
+ };
+
+ $: switch (size) {
+ case "xs":
+ sizeClasses = "px-2.5 py-1.5 text-xs";
+ break;
+ case "sm":
+ sizeClasses = "px-3 py-2 text-sm";
+ break;
+ case "md":
+ sizeClasses = "px-4 py-2 text-sm";
+ break;
+ case "lg":
+ sizeClasses = "px-4 py-2 text-base";
+ break;
+ case "xl":
+ sizeClasses = "px-6 py-3 text-base";
+ break;
+ }
+
+ $: switch (kind) {
+ case "secondary":
+ kindClasses = "border-transparent text-indigo-700 bg-indigo-100 hover:bg-indigo-200";
+ break;
+ case "primary":
+ kindClasses = "border-transparent text-white bg-indigo-600 hover:bg-indigo-700";
+ break;
+ case "white":
+ kindClasses = "border-gray-300 text-gray-700 bg-white hover:bg-gray-50";
+ break;
+ }
+</script>
+{#if href && !disabled}
+ <a {...shared_props}
+ {href}
+ on:click
+ {type}
+ class="{sizeClasses} {kindClasses} inline-flex items-center border font-medium rounded shadow-sm focus:outline-none focus:ring-2 focus:ring-indigo-500">
+ {text}
+ </a>
+{:else}
+ <button {...shared_props}
+ on:click
+ {type}
+ class="{sizeClasses} {kindClasses} inline-flex items-center border font-medium rounded shadow-sm focus:outline-none focus:ring-2 focus:ring-indigo-500">
+ {text}
+ </button>
+{/if} \ No newline at end of file
diff --git a/apps/kit/src/lib/components/icons/adjustments.svelte b/apps/kit/src/lib/components/icons/adjustments.svelte
new file mode 100644
index 0000000..b6d3f4d
--- /dev/null
+++ b/apps/kit/src/lib/components/icons/adjustments.svelte
@@ -0,0 +1,5 @@
+<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 {$$restProps.class??''}" fill="none" viewBox="0 0 24 24"
+ stroke="currentColor" stroke-width="2">
+ <path stroke-linecap="round" stroke-linejoin="round"
+ d="M12 6V4m0 2a2 2 0 100 4m0-4a2 2 0 110 4m-6 8a2 2 0 100-4m0 4a2 2 0 110-4m0 4v2m0-6V4m6 6v10m6-2a2 2 0 100-4m0 4a2 2 0 110-4m0 4v2m0-6V4"/>
+</svg> \ No newline at end of file
diff --git a/apps/kit/src/lib/components/icons/database.svelte b/apps/kit/src/lib/components/icons/database.svelte
new file mode 100644
index 0000000..05c70ed
--- /dev/null
+++ b/apps/kit/src/lib/components/icons/database.svelte
@@ -0,0 +1,3 @@
+<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 {$$restProps.class ?? ''}" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
+ <path stroke-linecap="round" stroke-linejoin="round" d="M4 7v10c0 2.21 3.582 4 8 4s8-1.79 8-4V7M4 7c0 2.21 3.582 4 8 4s8-1.79 8-4M4 7c0-2.21 3.582-4 8-4s8 1.79 8 4m0 5c0 2.21-3.582 4-8 4s-8-1.79-8-4" />
+</svg> \ No newline at end of file
diff --git a/apps/kit/src/lib/components/icons/home.svelte b/apps/kit/src/lib/components/icons/home.svelte
new file mode 100644
index 0000000..cc49c4d
--- /dev/null
+++ b/apps/kit/src/lib/components/icons/home.svelte
@@ -0,0 +1,5 @@
+<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 {$$restProps.class ?? ''}" fill="none" viewBox="0 0 24 24"
+ stroke="currentColor" stroke-width="2">
+ <path stroke-linecap="round" stroke-linejoin="round"
+ d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"/>
+</svg> \ No newline at end of file
diff --git a/apps/kit/src/lib/components/icons/index.ts b/apps/kit/src/lib/components/icons/index.ts
new file mode 100644
index 0000000..d3abf24
--- /dev/null
+++ b/apps/kit/src/lib/components/icons/index.ts
@@ -0,0 +1,13 @@
+import XIcon from "./x.svelte";
+import MenuIcon from "./menu.svelte";
+import AdjustmentsIcon from "./adjustments.svelte";
+import DatabaseIcon from "./database.svelte";
+import HomeIcon from "./home.svelte";
+
+export {
+ XIcon,
+ MenuIcon,
+ HomeIcon,
+ DatabaseIcon,
+ AdjustmentsIcon
+} \ No newline at end of file
diff --git a/apps/kit/src/lib/components/icons/menu.svelte b/apps/kit/src/lib/components/icons/menu.svelte
new file mode 100644
index 0000000..12a68a5
--- /dev/null
+++ b/apps/kit/src/lib/components/icons/menu.svelte
@@ -0,0 +1,4 @@
+<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 {$$restProps.class ?? ''}" fill="none" viewBox="0 0 24 24" stroke="currentColor"
+ stroke-width="2">
+ <path stroke-linecap="round" stroke-linejoin="round" d="M4 6h16M4 12h16M4 18h16"/>
+</svg>
diff --git a/apps/kit/src/lib/components/icons/x.svelte b/apps/kit/src/lib/components/icons/x.svelte
new file mode 100644
index 0000000..c7e05a8
--- /dev/null
+++ b/apps/kit/src/lib/components/icons/x.svelte
@@ -0,0 +1,4 @@
+<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 {$$restProps.class ?? ''}" fill="none" viewBox="0 0 24 24"
+ stroke="currentColor" stroke-width="2">
+ <path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12"/>
+</svg> \ No newline at end of file
diff --git a/apps/kit/src/lib/components/locale-switcher.svelte b/apps/kit/src/lib/components/locale-switcher.svelte
new file mode 100644
index 0000000..39d6168
--- /dev/null
+++ b/apps/kit/src/lib/components/locale-switcher.svelte
@@ -0,0 +1,52 @@
+<script lang="ts">
+ import {browser} from "$app/environment";
+ import {page} from "$app/stores";
+ import {setLocale, locale} from "$lib/i18n/i18n-svelte";
+ import type {Locales} from "$lib/i18n/i18n-types";
+ import {locales} from "$lib/i18n/i18n-util";
+ import {loadLocaleAsync} from "$lib/i18n/i18n-util.async";
+
+ const switchLocale = async (
+ newLocale: Locales,
+ updateHistoryState = true,
+ ) => {
+ if (!newLocale || $locale === newLocale) return;
+
+ // load new dictionary from server
+ await loadLocaleAsync(newLocale);
+
+ // select locale
+ setLocale(newLocale);
+
+ // update `lang` attribute
+ document.querySelector("html")?.setAttribute("lang", newLocale);
+
+ //TODO set cookie that persists the locale
+ };
+
+ // update locale when navigating via browser back/forward buttons
+ const handlePopStateEvent = async ({state}: PopStateEvent) =>
+ switchLocale(state.locale, false);
+
+ // update locale when page store changes
+ $: if (browser) {
+ const lang = $page.params.lang as Locales;
+ switchLocale(lang, false);
+ }
+</script>
+
+<svelte:window on:popstate={handlePopStateEvent}/>
+
+<ul>
+ {#each locales as l}
+ <li>
+ <button
+ type="button"
+ class:active={l === $locale}
+ on:click={() => switchLocale(l)}
+ >
+ {l}
+ </button>
+ </li>
+ {/each}
+</ul>
diff --git a/apps/kit/src/lib/configuration.ts b/apps/kit/src/lib/configuration.ts
new file mode 100644
index 0000000..d6f6b4f
--- /dev/null
+++ b/apps/kit/src/lib/configuration.ts
@@ -0,0 +1,45 @@
+export const TOP_BASE_DOMAIN = "greatoffice.app";
+export const BASE_DOMAIN = "dev.greatoffice.app";
+export const DEV_BASE_DOMAIN = "http://127.0.0.1";
+export const API_ADDRESS = "https://api." + BASE_DOMAIN;
+export const DEV_API_ADDRESS = "http://127.0.0.1:5000";
+export const SECONDS_BETWEEN_SESSION_CHECK = 600;
+
+export function base_domain(path: string = ""): string {
+ return (is_development() ? DEV_BASE_DOMAIN : TOP_BASE_DOMAIN) + (path !== "" ? "/" + path : "");
+}
+
+export function api_base(path: string = ""): string {
+ return (is_development() ? DEV_API_ADDRESS : API_ADDRESS) + (path !== "" ? "/" + path : "");
+}
+
+export function is_development(): boolean {
+ // @ts-ignore
+ return import.meta.env.DEV;
+}
+
+export function is_debug(): boolean {
+ return localStorage.getItem(StorageKeys.debug) !== "true";
+}
+
+export const CookieNames = {
+ theme: "go_theme",
+ locale: "go_locale"
+};
+
+export const QueryKeys = {
+ labels: "labels",
+ categories: "categories",
+ entries: "entries",
+};
+
+export const StorageKeys = {
+ session: "sessionData",
+ theme: "theme",
+ debug: "debug",
+ categories: "categories",
+ labels: "labels",
+ entries: "entries",
+ stopwatch: "stopwatchState",
+ logLevel: "logLevel"
+}; \ No newline at end of file
diff --git a/apps/kit/src/lib/helpers.ts b/apps/kit/src/lib/helpers.ts
new file mode 100644
index 0000000..f0f60cd
--- /dev/null
+++ b/apps/kit/src/lib/helpers.ts
@@ -0,0 +1,493 @@
+import {browser} from "$app/environment";
+import type {TimeEntryDto} from "$lib/models/TimeEntryDto";
+import type {UnwrappedEntryDateTime} from "$lib/models/UnwrappedEntryDateTime";
+import {logInfo} from "$lib/logger";
+import {Temporal} from "temporal-polyfill";
+
+export const EMAIL_REGEX = new RegExp(/^([a-z0-9]+(?:([._\-])[a-z0-9]+)*@(?:[a-z0-9]+(?:(-)[a-z0-9]+)?\.)+[a-z0-9](?:[a-z0-9]*[a-z0-9])?)$/i);
+export const URL_REGEX = new RegExp(/^(http:\/\/www\.|https:\/\/www\.|http:\/\/|https:\/\/)?[a-z0-9]+([\-.][a-z0-9]+)*\.[a-z]{2,5}(:[0-9]{1,5})?(\/.*)?$/gm);
+export const GUID_REGEX = new RegExp(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-5][0-9a-f]{3}-[089ab][0-9a-f]{3}-[0-9a-f]{12}$/i);
+export const NORWEGIAN_PHONE_NUMBER_REGEX = new RegExp(/(0047|\+47|47)?\d{8,12}/);
+
+export function get_default_sorted(unsorted: Array<TimeEntryDto>): Array<TimeEntryDto> {
+ if (unsorted.length < 1) return unsorted;
+ const byStart = unsorted.sort((a, b) => {
+ return Temporal.Instant.compare(Temporal.Instant.from(b.start), Temporal.Instant.from(a.start));
+ });
+
+ return byStart.sort((a, b) => {
+ return Temporal.Instant.compare(Temporal.Instant.from(b.stop), Temporal.Instant.from(a.stop));
+ });
+}
+
+export function is_email(value: string): boolean {
+ return EMAIL_REGEX.test(String(value).toLowerCase());
+}
+
+export function is_url(value: string): boolean {
+ return URL_REGEX.test(String(value).toLowerCase());
+}
+
+export function is_norwegian_phone_number(value: string): boolean {
+ if (value.length < 8 || value.length > 12) {
+ return false;
+ }
+ return NORWEGIAN_PHONE_NUMBER_REGEX.test(String(value));
+}
+
+// export function switch_theme() {
+// const html = document.querySelector("html");
+// if (html.dataset.theme === "dark") {
+// html.dataset.theme = "light";
+// } else {
+// html.dataset.theme = "dark";
+// }
+// set_cookie(CookieNames.theme, html.dataset.theme, base_domain());
+// }
+
+export function get_cookie(name: string) {
+ const value = `; ${document.cookie}`;
+ const parts = value.split(`; ${name}=`);
+ if (parts.length === 2) return parts.pop()?.split(";").shift();
+}
+
+export function set_cookie(name: string, value: string, baseDomain = window.location.host) {
+ document.cookie = name + "=" + encodeURIComponent(value) + (baseDomain ? ";domain=" + baseDomain : "");
+}
+
+export function unwrap_date_time_from_entry(entry: TimeEntryDto): UnwrappedEntryDateTime {
+ if (!entry) throw new Error("entry was undefined");
+ const currentTimeZone = Temporal.Now.timeZone().id;
+ const startInstant = Temporal.Instant.from(entry.start).toZonedDateTimeISO(currentTimeZone);
+ const stopInstant = Temporal.Instant.from(entry.stop).toZonedDateTimeISO(currentTimeZone);
+
+ return {
+ start_date: startInstant.toPlainDate(),
+ stop_date: stopInstant.toPlainDate(),
+ start_time: startInstant.toPlainTime(),
+ stop_time: stopInstant.toPlainTime(),
+ duration: Temporal.Duration.from({
+ hours: stopInstant.hour,
+ minutes: stopInstant.minute,
+ }).subtract(Temporal.Duration.from({
+ hours: startInstant.hour,
+ minutes: startInstant.minute,
+ })),
+ };
+}
+
+
+export function is_guid(value: string): boolean {
+ if (!value) {
+ return false;
+ }
+ if (value[0] === "{") {
+ value = value.substring(1, value.length - 1);
+ }
+ return GUID_REGEX.test(value);
+}
+
+export function is_empty_object(obj: object): boolean {
+ return obj !== void 0 && Object.keys(obj).length > 0;
+}
+
+export function merge_obj_arr<T>(a: Array<T>, b: Array<T>, props: Array<string>): Array<T> {
+ let start = 0;
+ let merge = [];
+
+ while (start < a.length) {
+
+ if (a[start] === b[start]) {
+ //pushing the merged objects into array
+ merge.push({...a[start], ...b[start]});
+ }
+ //incrementing start value
+ start = start + 1;
+ }
+ return merge;
+}
+
+export function set_favicon(url: string) {
+ // Find the current favicon element
+ const favicon = document.querySelector("link[rel=\"icon\"]") as HTMLLinkElement;
+ if (favicon) {
+ // Update the new link
+ favicon.href = url;
+ } else {
+ // Create new `link`
+ const link = document.createElement("link");
+ link.rel = "icon";
+ link.href = url;
+
+ // Append to the `head` element
+ document.head.appendChild(link);
+ }
+}
+
+export function set_emoji_favicon(emoji: string) {
+ // Create a canvas element
+ const canvas = document.createElement("canvas");
+ canvas.height = 64;
+ canvas.width = 64;
+
+ // Get the canvas context
+ const context = canvas.getContext("2d") as CanvasRenderingContext2D;
+ context.font = "64px serif";
+ context.fillText(emoji, 0, 64);
+
+ // Get the custom URL
+ const url = canvas.toDataURL();
+
+ // Update the favicon
+ set_favicon(url);
+}
+
+
+// https://stackoverflow.com/a/48400665/11961742
+export function seconds_to_hour_minute_string(seconds: number, hourChar = "h", minuteChar = "m") {
+ const hours = Math.floor(seconds / (60 * 60));
+ seconds -= hours * (60 * 60);
+ const minutes = Math.floor(seconds / 60);
+ return hours + "h" + minutes + "m";
+}
+
+export function seconds_to_hour_minute(seconds: number) {
+ const hours = Math.floor(seconds / (60 * 60));
+ seconds -= hours * (60 * 60);
+ const minutes = Math.floor(seconds / 60);
+ return {hours, minutes};
+}
+
+export function get_query_string(params: any = {}): string {
+ const map = Object.keys(params).reduce((arr: Array<string>, key: string) => {
+ if (params[key] !== undefined) {
+ return arr.concat(`${key}=${encodeURIComponent(params[key])}`);
+ }
+ return arr;
+ }, [] as any);
+
+ if (map.length) {
+ return `?${map.join("&")}`;
+ }
+
+ return "";
+}
+
+export function make_url(url: string, params: object): string {
+ return `${url}${get_query_string(params)}`;
+}
+
+export function noop() {
+}
+
+export async function run_async(functionToRun: Function): Promise<any> {
+ return new Promise((greatSuccess, graveFailure) => {
+ try {
+ greatSuccess(functionToRun());
+ } catch (exception) {
+ graveFailure(exception);
+ }
+ });
+}
+
+// https://stackoverflow.com/a/45215694/11961742
+export function get_selected_options(domElement: HTMLSelectElement): Array<string> {
+ const ret = [];
+
+ // fast but not universally supported
+ if (domElement.selectedOptions !== undefined) {
+ for (let i = 0; i < domElement.selectedOptions.length; i++) {
+ ret.push(domElement.selectedOptions[i].value);
+ }
+
+ // compatible, but can be painfully slow
+ } else {
+ for (let i = 0; i < domElement.options.length; i++) {
+ if (domElement.options[i].selected) {
+ ret.push(domElement.options[i].value);
+ }
+ }
+ }
+ return ret;
+}
+
+export function random_string(length: number): string {
+ if (!length) {
+ throw new Error("length is undefined");
+ }
+ let result = "";
+ const characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
+ const charactersLength = characters.length;
+ for (let i = 0; i < length; i++) {
+ result += characters.charAt(Math.floor(Math.random() * charactersLength));
+ }
+ return result;
+}
+
+interface CreateElementOptions {
+ name: string,
+ properties?: object,
+ children?: Array<HTMLElement | Function | Node>
+}
+
+export function create_element_from_object(elementOptions: CreateElementOptions): HTMLElement {
+ return create_element(elementOptions.name, elementOptions.properties, elementOptions.children);
+}
+
+export function create_element(name: string, properties?: object, children?: Array<HTMLElement | any>): HTMLElement {
+ if (!name || name.length < 1) {
+ throw new Error("name is required");
+ }
+ const node = document.createElement(name);
+ if (properties) {
+ for (const [key, value] of Object.entries(properties)) {
+ // @ts-ignore
+ node[key] = value;
+ }
+ }
+
+ if (children && children.length > 0) {
+ let actualChildren = children;
+ if (typeof children === "function") {
+ // @ts-ignore
+ actualChildren = children();
+ }
+ for (const child of actualChildren) {
+ node.appendChild(child as Node);
+ }
+ }
+ return node;
+}
+
+export function get_element_position(element: HTMLElement | any) {
+ if (!element) return {x: 0, y: 0};
+ let x = 0;
+ let y = 0;
+ while (true) {
+ x += element.offsetLeft;
+ y += element.offsetTop;
+ if (element.offsetParent === null) {
+ break;
+ }
+ element = element.offsetParent;
+ }
+ return {x, y};
+}
+
+export function restrict_input_to_numbers(element: HTMLElement, specials: Array<string> = [], mergeSpecialsWithDefaults: boolean = false): void {
+ if (element) {
+ element.addEventListener("keydown", (e) => {
+ const defaultSpecials = ["Backspace", "ArrowLeft", "ArrowRight", "Tab"];
+ let keys = specials.length > 0 ? specials : defaultSpecials;
+ if (mergeSpecialsWithDefaults && specials) {
+ keys = [...specials, ...defaultSpecials];
+ }
+ if (keys.indexOf(e.key) !== -1) {
+ return;
+ }
+ if (isNaN(parseInt(e.key))) {
+ e.preventDefault();
+ }
+ });
+ }
+}
+
+export function element_has_focus(element: HTMLElement): boolean {
+ return element === document.activeElement;
+}
+
+export function move_focus(element: HTMLElement): void {
+ if (!element) {
+ element = document.getElementsByTagName("body")[0];
+ }
+ element.focus();
+ // @ts-ignore
+ if (!element_has_focus(element)) {
+ element.setAttribute("tabindex", "-1");
+ element.focus();
+ }
+}
+
+export function get_url_parameter(name: string): string {
+ // @ts-ignore
+ return new RegExp("[?&]" + name + "=([^&#]*)")?.exec(window.location.href)[1];
+}
+
+export function update_url_parameter(param: string, newVal: string): void {
+ let newAdditionalURL = "";
+ let tempArray = location.href.split("?");
+ const baseURL = tempArray[0];
+ const additionalURL = tempArray[1];
+ let temp = "";
+ if (additionalURL) {
+ tempArray = additionalURL.split("&");
+ for (let i = 0; i < tempArray.length; i++) {
+ if (tempArray[i].split("=")[0] !== param) {
+ newAdditionalURL += temp + tempArray[i];
+ temp = "&";
+ }
+ }
+ }
+ const rows_txt = temp + "" + param + "=" + newVal;
+ const newUrl = baseURL + "?" + newAdditionalURL + rows_txt;
+ window.history.replaceState("", "", newUrl);
+}
+
+
+export function get_style_string(rules: CSSRuleList) {
+ let styleString = "";
+ for (const [key, value] of Object.entries(rules)) {
+ styleString += key + ":" + value + ";";
+ }
+ return styleString;
+}
+
+export function parse_iso_local(s: string) {
+ const b = s.split(/\D/);
+ //@ts-ignore
+ return new Date(b[0], b[1] - 1, b[2], b[3], b[4], b[5]);
+}
+
+export function resolve_references(json: any) {
+ if (!json) return;
+ if (typeof json === "string") {
+ json = JSON.parse(json ?? "{}");
+ }
+ const byid = {}, refs = [];
+ json = function recurse(obj, prop, parent) {
+ if (typeof obj !== "object" || !obj) {
+ return obj;
+ }
+ if (Object.prototype.toString.call(obj) === "[object Array]") {
+ for (let i = 0; i < obj.length; i++) {
+ if (typeof obj[i] !== "object" || !obj[i]) {
+ continue;
+ } else if ("$ref" in obj[i]) {
+ // @ts-ignore
+ obj[i] = recurse(obj[i], i, obj);
+ } else {
+ obj[i] = recurse(obj[i], prop, obj);
+ }
+ }
+ return obj;
+ }
+ if ("$ref" in obj) {
+ let ref = obj.$ref;
+ if (ref in byid) {
+ // @ts-ignore
+ return byid[ref];
+ }
+ refs.push([parent, prop, ref]);
+ return;
+ } else if ("$id" in obj) {
+ let id = obj.$id;
+ delete obj.$id;
+ if ("$values" in obj) {
+ obj = obj.$values.map(recurse);
+ } else {
+ for (let prop2 in obj) {
+ // @ts-ignore
+ obj[prop2] = recurse(obj[prop2], prop2, obj);
+ }
+ }
+ // @ts-ignore
+ byid[id] = obj;
+ }
+ return obj;
+ }(json);
+ for (let i = 0; i < refs.length; i++) {
+ let ref = refs[i];
+ // @ts-ignore
+ ref[0][ref[1]] = byid[ref[2]];
+ }
+ return json;
+}
+
+export function get_random_int(min: number, max: number): number {
+ min = Math.ceil(min);
+ max = Math.floor(max);
+ return Math.floor(Math.random() * (max - min + 1)) + min;
+}
+
+export function to_readable_bytes(bytes: number): string {
+ const s = ["bytes", "kB", "MB", "GB", "TB", "PB"];
+ const e = Math.floor(Math.log(bytes) / Math.log(1024));
+ return (bytes / Math.pow(1024, e)).toFixed(2) + " " + s[e];
+}
+
+export function can_use_dom(): boolean {
+ return !!(typeof window !== "undefined" && window.document && window.document.createElement);
+}
+
+export function session_storage_remove_regex(regex: RegExp): void {
+ if (!browser) {
+ logInfo("sessionStorage is not available in non-browser contexts");
+ return;
+ }
+ let n = sessionStorage.length;
+ while (n--) {
+ const key = sessionStorage.key(n);
+ if (key && regex.test(key)) {
+ sessionStorage.removeItem(key);
+ }
+ }
+}
+
+export function local_storage_remove_regex(regex: RegExp): void {
+ if (!browser) {
+ logInfo("sessionStorage is not available in non-browser contexts");
+ return;
+ }
+ let n = localStorage.length;
+ while (n--) {
+ const key = localStorage.key(n);
+ if (key && regex.test(key)) {
+ localStorage.removeItem(key);
+ }
+ }
+}
+
+export function session_storage_set_json(key: string, value: object): void {
+ if (!browser) {
+ console.warn("sessionStorage is not available in non-browser contexts");
+ return;
+ }
+ sessionStorage.setItem(key, JSON.stringify(value));
+}
+
+export function session_storage_get_json(key: string): object {
+ if (!browser) {
+ console.warn("sessionStorage is not available in non-browser contexts");
+ return {};
+ }
+ return JSON.parse(sessionStorage.getItem(key) ?? "{}");
+}
+
+export function local_storage_set_json(key: string, value: object): void {
+ if (!browser) {
+ console.warn("sessionStorage is not available in non-browser contexts");
+ return;
+ }
+ localStorage.setItem(key, JSON.stringify(value));
+}
+
+export function local_storage_get_json(key: string): object {
+ if (!browser) {
+ console.warn("sessionStorage is not available in non-browser contexts");
+ return {};
+ }
+ return JSON.parse(localStorage.getItem(key) ?? "{}");
+}
+
+export function get_hash_code(value: string): number | undefined {
+ let hash = 0;
+ if (value.length === 0) {
+ return;
+ }
+ for (let i = 0; i < value.length; i++) {
+ const char = value.charCodeAt(i);
+ hash = (hash << 5) - hash + char;
+ hash |= 0;
+ }
+ return hash;
+}
diff --git a/apps/kit/src/lib/i18n/en/index.ts b/apps/kit/src/lib/i18n/en/index.ts
new file mode 100644
index 0000000..f3def1b
--- /dev/null
+++ b/apps/kit/src/lib/i18n/en/index.ts
@@ -0,0 +1,136 @@
+import type {BaseTranslation} from "../i18n-types";
+
+const en: BaseTranslation = {
+ nav: {
+ home: "Home",
+ data: "Data",
+ settings: "Settings",
+ usermenu: {
+ logout: "Log out",
+ logoutTitle: "Log out of your profile",
+ profile: "Profile",
+ profileTitle: "Administrate your profile",
+ toggleTitle: "Toggle user menu",
+ }
+ },
+ views: {
+ dataTablePaginator: {
+ goToPrevPage: "Go to previous page",
+ goToNextPage: "Go to next page",
+ of: "of",
+ },
+ categoryForm: {
+ name: "Name",
+ color: "Color",
+ defaultLabels: "Default labels",
+ labelsPlaceholder: "Search or create"
+ },
+ settingsCategoriesTile: {
+ deleteAllConfirm: "Are you sure you want to delete this category?\nThis will delete all relating entries!",
+ active: "Active",
+ archived: "Archived",
+ name: "Name",
+ color: "Color",
+ editEntry: "Edit entry",
+ deleteEntry: "Delete entry",
+ noCategories: "No categories",
+ categories: "Categories"
+ },
+ settingsLabelsTile: {
+ deleteAllConfirm: "Are you sure you want to delete this label?\nIt will be removed from all related entries!",
+ active: "Active",
+ archived: "Archived",
+ name: "Name",
+ color: "Color",
+ editEntry: "Edit label",
+ deleteEntry: "Delete label",
+ noLabels: "No labels",
+ labels: "Labels"
+ },
+ entryForm: {
+ entryUpdateError: "An error occured while updating the entry, try again soon.",
+ entryCreateError: "An error occured while creating the entry, try again soon.",
+ errDescriptionReq: "Description is required",
+ reset: "Reset",
+ description: "Description",
+ save: "Save",
+ create: "Create",
+ category: {
+ category: "Category",
+ placeholder: "Search or create",
+ noResults: "No categories available (Create a new one by searching for it)",
+ errisRequired: "Category is required",
+ _logReset: "Reset category section"
+ },
+ labels: {
+ placeholder: "Search or create",
+ noResults: "No labels available (Create a new one by searching for it)",
+ labels: "Labels",
+ _logReset: "Reset labels section"
+ },
+ dateTime: {
+ errDateIsRequired: "Date is required",
+ errFromIsRequired: "From is required",
+ errFromAfterTo: "From can not be after To",
+ errFromEqTo: "From and To can not be equal",
+ errToIsRequired: "To is required",
+ errToBeforeFrom: "To can not be before From",
+ from: "From",
+ to: "To",
+ date: "Date",
+ _logReset: "Reset date time section"
+ }
+ }
+ },
+ data: {
+ durationSummary: "Showing {entryCountString:string}, totalling in {totalHourMin:string}",
+ hourSingleChar: "h",
+ minSingleChar: "m",
+ entry: "entry",
+ entries: "entries",
+ confirmDeleteEntry: "Are you sure you want to delete this entry?",
+ editEntry: "Edit entry",
+ date: "Date",
+ from: "From",
+ duration: "Duration",
+ category: "Category",
+ description: "Description",
+ loading: "Loading",
+ noEntries: "No entries",
+ to: "to",
+ use: "Use",
+ },
+ home: {
+ confirmDeleteEntry: "Are you sure you want to delete this entry?",
+ newEntry: "New entry",
+ editEntry: "Edit entry",
+ deleteEntry: "Delete entry",
+ loggedTimeToday: "Logged time today",
+ loggedTimeTodayString: "{hours}h{minutes}m",
+ currentTime: "Current time",
+ loading: "Loading",
+ stopwatch: "Stopwatch",
+ todayEntries: "Today's entries",
+ noEntriesToday: "No entries today",
+ refreshTodayEntries: "Refresh today's entries",
+ category: "Category",
+ timespan: "Timespan",
+ },
+ messages: {
+ pageNotFound: "Page not found",
+ goToFrontpage: "Go to frontpage",
+ noInternet: "It seems like your device does not have a internet connection, please check your connection."
+ },
+ login: {
+ loginToYourAccount: "Log in to your account",
+ or: "Or",
+ createANewAccount: "create a new account",
+ emailAddress: "Email address",
+ password: "Password",
+ notMyComputer: "This is not my computer",
+ forgotPassword: "Forgot your password?",
+ logIn: "Log in"
+ },
+};
+
+export default en;
diff --git a/apps/kit/src/lib/i18n/formatters.ts b/apps/kit/src/lib/i18n/formatters.ts
new file mode 100644
index 0000000..78734f9
--- /dev/null
+++ b/apps/kit/src/lib/i18n/formatters.ts
@@ -0,0 +1,11 @@
+import type { FormattersInitializer } from 'typesafe-i18n'
+import type { Locales, Formatters } from './i18n-types'
+
+export const initFormatters: FormattersInitializer<Locales, Formatters> = (locale: Locales) => {
+
+ const formatters: Formatters = {
+ // add your formatter functions here
+ }
+
+ return formatters
+}
diff --git a/apps/kit/src/lib/i18n/i18n-svelte.ts b/apps/kit/src/lib/i18n/i18n-svelte.ts
new file mode 100644
index 0000000..6cdffb3
--- /dev/null
+++ b/apps/kit/src/lib/i18n/i18n-svelte.ts
@@ -0,0 +1,12 @@
+// This file was auto-generated by 'typesafe-i18n'. Any manual changes will be overwritten.
+/* eslint-disable */
+
+import { initI18nSvelte } from 'typesafe-i18n/svelte'
+import type { Formatters, Locales, TranslationFunctions, Translations } from './i18n-types'
+import { loadedFormatters, loadedLocales } from './i18n-util'
+
+const { locale, LL, setLocale } = initI18nSvelte<Locales, Translations, TranslationFunctions, Formatters>(loadedLocales, loadedFormatters)
+
+export { locale, LL, setLocale }
+
+export default LL
diff --git a/apps/kit/src/lib/i18n/i18n-types.ts b/apps/kit/src/lib/i18n/i18n-types.ts
new file mode 100644
index 0000000..f3e0f80
--- /dev/null
+++ b/apps/kit/src/lib/i18n/i18n-types.ts
@@ -0,0 +1,890 @@
+// This file was auto-generated by 'typesafe-i18n'. Any manual changes will be overwritten.
+/* eslint-disable */
+import type { BaseTranslation as BaseTranslationType, LocalizedString, RequiredParams } from 'typesafe-i18n'
+
+export type BaseTranslation = BaseTranslationType
+export type BaseLocale = 'en'
+
+export type Locales =
+ | 'en'
+ | 'nb'
+
+export type Translation = RootTranslation
+
+export type Translations = RootTranslation
+
+type RootTranslation = {
+ nav: {
+ /**
+ * Home
+ */
+ home: string
+ /**
+ * Data
+ */
+ data: string
+ /**
+ * Settings
+ */
+ settings: string
+ usermenu: {
+ /**
+ * Log out
+ */
+ logout: string
+ /**
+ * Log out of your profile
+ */
+ logoutTitle: string
+ /**
+ * Profile
+ */
+ profile: string
+ /**
+ * Administrate your profile
+ */
+ profileTitle: string
+ /**
+ * Toggle user menu
+ */
+ toggleTitle: string
+ }
+ }
+ views: {
+ dataTablePaginator: {
+ /**
+ * Go to previous page
+ */
+ goToPrevPage: string
+ /**
+ * Go to next page
+ */
+ goToNextPage: string
+ /**
+ * of
+ */
+ of: string
+ }
+ categoryForm: {
+ /**
+ * Name
+ */
+ name: string
+ /**
+ * Color
+ */
+ color: string
+ /**
+ * Default labels
+ */
+ defaultLabels: string
+ /**
+ * Search or create
+ */
+ labelsPlaceholder: string
+ }
+ settingsCategoriesTile: {
+ /**
+ * Are you sure you want to delete this category?
+ This will delete all relating entries!
+ */
+ deleteAllConfirm: string
+ /**
+ * Active
+ */
+ active: string
+ /**
+ * Archived
+ */
+ archived: string
+ /**
+ * Name
+ */
+ name: string
+ /**
+ * Color
+ */
+ color: string
+ /**
+ * Edit entry
+ */
+ editEntry: string
+ /**
+ * Delete entry
+ */
+ deleteEntry: string
+ /**
+ * No categories
+ */
+ noCategories: string
+ /**
+ * Categories
+ */
+ categories: string
+ }
+ settingsLabelsTile: {
+ /**
+ * Are you sure you want to delete this label?
+ It will be removed from all related entries!
+ */
+ deleteAllConfirm: string
+ /**
+ * Active
+ */
+ active: string
+ /**
+ * Archived
+ */
+ archived: string
+ /**
+ * Name
+ */
+ name: string
+ /**
+ * Color
+ */
+ color: string
+ /**
+ * Edit label
+ */
+ editEntry: string
+ /**
+ * Delete label
+ */
+ deleteEntry: string
+ /**
+ * No labels
+ */
+ noLabels: string
+ /**
+ * Labels
+ */
+ labels: string
+ }
+ entryForm: {
+ /**
+ * An error occured while updating the entry, try again soon.
+ */
+ entryUpdateError: string
+ /**
+ * An error occured while creating the entry, try again soon.
+ */
+ entryCreateError: string
+ /**
+ * Description is required
+ */
+ errDescriptionReq: string
+ /**
+ * Reset
+ */
+ reset: string
+ /**
+ * Description
+ */
+ description: string
+ /**
+ * Save
+ */
+ save: string
+ /**
+ * Create
+ */
+ create: string
+ category: {
+ /**
+ * Category
+ */
+ category: string
+ /**
+ * Search or create
+ */
+ placeholder: string
+ /**
+ * No categories available (Create a new one by searching for it)
+ */
+ noResults: string
+ /**
+ * Category is required
+ */
+ errisRequired: string
+ /**
+ * Reset category section
+ */
+ _logReset: string
+ }
+ labels: {
+ /**
+ * Search or create
+ */
+ placeholder: string
+ /**
+ * No labels available (Create a new one by searching for it)
+ */
+ noResults: string
+ /**
+ * Labels
+ */
+ labels: string
+ /**
+ * Reset labels section
+ */
+ _logReset: string
+ }
+ dateTime: {
+ /**
+ * Date is required
+ */
+ errDateIsRequired: string
+ /**
+ * From is required
+ */
+ errFromIsRequired: string
+ /**
+ * From can not be after To
+ */
+ errFromAfterTo: string
+ /**
+ * From and To can not be equal
+ */
+ errFromEqTo: string
+ /**
+ * To is required
+ */
+ errToIsRequired: string
+ /**
+ * To can not be before From
+ */
+ errToBeforeFrom: string
+ /**
+ * From
+ */
+ from: string
+ /**
+ * To
+ */
+ to: string
+ /**
+ * Date
+ */
+ date: string
+ /**
+ * Reset date time section
+ */
+ _logReset: string
+ }
+ }
+ }
+ data: {
+ /**
+ * Showing {entryCountString}, totalling in {totalHourMin}
+ * @param {string} entryCountString
+ * @param {string} totalHourMin
+ */
+ durationSummary: RequiredParams<'entryCountString' | 'totalHourMin'>
+ /**
+ * h
+ */
+ hourSingleChar: string
+ /**
+ * m
+ */
+ minSingleChar: string
+ /**
+ * entry
+ */
+ entry: string
+ /**
+ * entries
+ */
+ entries: string
+ /**
+ * Are you sure you want to delete this entry?
+ */
+ confirmDeleteEntry: string
+ /**
+ * Edit entry
+ */
+ editEntry: string
+ /**
+ * Date
+ */
+ date: string
+ /**
+ * From
+ */
+ from: string
+ /**
+ * Duration
+ */
+ duration: string
+ /**
+ * Category
+ */
+ category: string
+ /**
+ * Description
+ */
+ description: string
+ /**
+ * Loading
+ */
+ loading: string
+ /**
+ * No entries
+ */
+ noEntries: string
+ /**
+ * to
+ */
+ to: string
+ /**
+ * Use
+ */
+ use: string
+ }
+ home: {
+ /**
+ * Are you sure you want to delete this entry?
+ */
+ confirmDeleteEntry: string
+ /**
+ * New entry
+ */
+ newEntry: string
+ /**
+ * Edit entry
+ */
+ editEntry: string
+ /**
+ * Delete entry
+ */
+ deleteEntry: string
+ /**
+ * Logged time today
+ */
+ loggedTimeToday: string
+ /**
+ * {hours}h{minutes}m
+ * @param {unknown} hours
+ * @param {unknown} minutes
+ */
+ loggedTimeTodayString: RequiredParams<'hours' | 'minutes'>
+ /**
+ * Current time
+ */
+ currentTime: string
+ /**
+ * Loading
+ */
+ loading: string
+ /**
+ * Stopwatch
+ */
+ stopwatch: string
+ /**
+ * Today's entries
+ */
+ todayEntries: string
+ /**
+ * No entries today
+ */
+ noEntriesToday: string
+ /**
+ * Refresh today's entries
+ */
+ refreshTodayEntries: string
+ /**
+ * Category
+ */
+ category: string
+ /**
+ * Timespan
+ */
+ timespan: string
+ }
+ messages: {
+ /**
+ * Page not found
+ */
+ pageNotFound: string
+ /**
+ * Go to frontpage
+ */
+ goToFrontpage: string
+ /**
+ * It seems like your device does not have a internet connection, please check your connection.
+ */
+ noInternet: string
+ }
+ login: {
+ /**
+ * Log in to your account
+ */
+ loginToYourAccount: string
+ /**
+ * Or
+ */
+ or: string
+ /**
+ * create a new account
+ */
+ createANewAccount: string
+ /**
+ * Email address
+ */
+ emailAddress: string
+ /**
+ * Password
+ */
+ password: string
+ /**
+ * This is not my computer
+ */
+ notMyComputer: string
+ /**
+ * Forgot your password?
+ */
+ forgotPassword: string
+ /**
+ * Log in
+ */
+ logIn: string
+ }
+}
+
+export type TranslationFunctions = {
+ nav: {
+ /**
+ * Home
+ */
+ home: () => LocalizedString
+ /**
+ * Data
+ */
+ data: () => LocalizedString
+ /**
+ * Settings
+ */
+ settings: () => LocalizedString
+ usermenu: {
+ /**
+ * Log out
+ */
+ logout: () => LocalizedString
+ /**
+ * Log out of your profile
+ */
+ logoutTitle: () => LocalizedString
+ /**
+ * Profile
+ */
+ profile: () => LocalizedString
+ /**
+ * Administrate your profile
+ */
+ profileTitle: () => LocalizedString
+ /**
+ * Toggle user menu
+ */
+ toggleTitle: () => LocalizedString
+ }
+ }
+ views: {
+ dataTablePaginator: {
+ /**
+ * Go to previous page
+ */
+ goToPrevPage: () => LocalizedString
+ /**
+ * Go to next page
+ */
+ goToNextPage: () => LocalizedString
+ /**
+ * of
+ */
+ of: () => LocalizedString
+ }
+ categoryForm: {
+ /**
+ * Name
+ */
+ name: () => LocalizedString
+ /**
+ * Color
+ */
+ color: () => LocalizedString
+ /**
+ * Default labels
+ */
+ defaultLabels: () => LocalizedString
+ /**
+ * Search or create
+ */
+ labelsPlaceholder: () => LocalizedString
+ }
+ settingsCategoriesTile: {
+ /**
+ * Are you sure you want to delete this category?
+ This will delete all relating entries!
+ */
+ deleteAllConfirm: () => LocalizedString
+ /**
+ * Active
+ */
+ active: () => LocalizedString
+ /**
+ * Archived
+ */
+ archived: () => LocalizedString
+ /**
+ * Name
+ */
+ name: () => LocalizedString
+ /**
+ * Color
+ */
+ color: () => LocalizedString
+ /**
+ * Edit entry
+ */
+ editEntry: () => LocalizedString
+ /**
+ * Delete entry
+ */
+ deleteEntry: () => LocalizedString
+ /**
+ * No categories
+ */
+ noCategories: () => LocalizedString
+ /**
+ * Categories
+ */
+ categories: () => LocalizedString
+ }
+ settingsLabelsTile: {
+ /**
+ * Are you sure you want to delete this label?
+ It will be removed from all related entries!
+ */
+ deleteAllConfirm: () => LocalizedString
+ /**
+ * Active
+ */
+ active: () => LocalizedString
+ /**
+ * Archived
+ */
+ archived: () => LocalizedString
+ /**
+ * Name
+ */
+ name: () => LocalizedString
+ /**
+ * Color
+ */
+ color: () => LocalizedString
+ /**
+ * Edit label
+ */
+ editEntry: () => LocalizedString
+ /**
+ * Delete label
+ */
+ deleteEntry: () => LocalizedString
+ /**
+ * No labels
+ */
+ noLabels: () => LocalizedString
+ /**
+ * Labels
+ */
+ labels: () => LocalizedString
+ }
+ entryForm: {
+ /**
+ * An error occured while updating the entry, try again soon.
+ */
+ entryUpdateError: () => LocalizedString
+ /**
+ * An error occured while creating the entry, try again soon.
+ */
+ entryCreateError: () => LocalizedString
+ /**
+ * Description is required
+ */
+ errDescriptionReq: () => LocalizedString
+ /**
+ * Reset
+ */
+ reset: () => LocalizedString
+ /**
+ * Description
+ */
+ description: () => LocalizedString
+ /**
+ * Save
+ */
+ save: () => LocalizedString
+ /**
+ * Create
+ */
+ create: () => LocalizedString
+ category: {
+ /**
+ * Category
+ */
+ category: () => LocalizedString
+ /**
+ * Search or create
+ */
+ placeholder: () => LocalizedString
+ /**
+ * No categories available (Create a new one by searching for it)
+ */
+ noResults: () => LocalizedString
+ /**
+ * Category is required
+ */
+ errisRequired: () => LocalizedString
+ /**
+ * Reset category section
+ */
+ _logReset: () => LocalizedString
+ }
+ labels: {
+ /**
+ * Search or create
+ */
+ placeholder: () => LocalizedString
+ /**
+ * No labels available (Create a new one by searching for it)
+ */
+ noResults: () => LocalizedString
+ /**
+ * Labels
+ */
+ labels: () => LocalizedString
+ /**
+ * Reset labels section
+ */
+ _logReset: () => LocalizedString
+ }
+ dateTime: {
+ /**
+ * Date is required
+ */
+ errDateIsRequired: () => LocalizedString
+ /**
+ * From is required
+ */
+ errFromIsRequired: () => LocalizedString
+ /**
+ * From can not be after To
+ */
+ errFromAfterTo: () => LocalizedString
+ /**
+ * From and To can not be equal
+ */
+ errFromEqTo: () => LocalizedString
+ /**
+ * To is required
+ */
+ errToIsRequired: () => LocalizedString
+ /**
+ * To can not be before From
+ */
+ errToBeforeFrom: () => LocalizedString
+ /**
+ * From
+ */
+ from: () => LocalizedString
+ /**
+ * To
+ */
+ to: () => LocalizedString
+ /**
+ * Date
+ */
+ date: () => LocalizedString
+ /**
+ * Reset date time section
+ */
+ _logReset: () => LocalizedString
+ }
+ }
+ }
+ data: {
+ /**
+ * Showing {entryCountString}, totalling in {totalHourMin}
+ */
+ durationSummary: (arg: { entryCountString: string, totalHourMin: string }) => LocalizedString
+ /**
+ * h
+ */
+ hourSingleChar: () => LocalizedString
+ /**
+ * m
+ */
+ minSingleChar: () => LocalizedString
+ /**
+ * entry
+ */
+ entry: () => LocalizedString
+ /**
+ * entries
+ */
+ entries: () => LocalizedString
+ /**
+ * Are you sure you want to delete this entry?
+ */
+ confirmDeleteEntry: () => LocalizedString
+ /**
+ * Edit entry
+ */
+ editEntry: () => LocalizedString
+ /**
+ * Date
+ */
+ date: () => LocalizedString
+ /**
+ * From
+ */
+ from: () => LocalizedString
+ /**
+ * Duration
+ */
+ duration: () => LocalizedString
+ /**
+ * Category
+ */
+ category: () => LocalizedString
+ /**
+ * Description
+ */
+ description: () => LocalizedString
+ /**
+ * Loading
+ */
+ loading: () => LocalizedString
+ /**
+ * No entries
+ */
+ noEntries: () => LocalizedString
+ /**
+ * to
+ */
+ to: () => LocalizedString
+ /**
+ * Use
+ */
+ use: () => LocalizedString
+ }
+ home: {
+ /**
+ * Are you sure you want to delete this entry?
+ */
+ confirmDeleteEntry: () => LocalizedString
+ /**
+ * New entry
+ */
+ newEntry: () => LocalizedString
+ /**
+ * Edit entry
+ */
+ editEntry: () => LocalizedString
+ /**
+ * Delete entry
+ */
+ deleteEntry: () => LocalizedString
+ /**
+ * Logged time today
+ */
+ loggedTimeToday: () => LocalizedString
+ /**
+ * {hours}h{minutes}m
+ */
+ loggedTimeTodayString: (arg: { hours: unknown, minutes: unknown }) => LocalizedString
+ /**
+ * Current time
+ */
+ currentTime: () => LocalizedString
+ /**
+ * Loading
+ */
+ loading: () => LocalizedString
+ /**
+ * Stopwatch
+ */
+ stopwatch: () => LocalizedString
+ /**
+ * Today's entries
+ */
+ todayEntries: () => LocalizedString
+ /**
+ * No entries today
+ */
+ noEntriesToday: () => LocalizedString
+ /**
+ * Refresh today's entries
+ */
+ refreshTodayEntries: () => LocalizedString
+ /**
+ * Category
+ */
+ category: () => LocalizedString
+ /**
+ * Timespan
+ */
+ timespan: () => LocalizedString
+ }
+ messages: {
+ /**
+ * Page not found
+ */
+ pageNotFound: () => LocalizedString
+ /**
+ * Go to frontpage
+ */
+ goToFrontpage: () => LocalizedString
+ /**
+ * It seems like your device does not have a internet connection, please check your connection.
+ */
+ noInternet: () => LocalizedString
+ }
+ login: {
+ /**
+ * Log in to your account
+ */
+ loginToYourAccount: () => LocalizedString
+ /**
+ * Or
+ */
+ or: () => LocalizedString
+ /**
+ * create a new account
+ */
+ createANewAccount: () => LocalizedString
+ /**
+ * Email address
+ */
+ emailAddress: () => LocalizedString
+ /**
+ * Password
+ */
+ password: () => LocalizedString
+ /**
+ * This is not my computer
+ */
+ notMyComputer: () => LocalizedString
+ /**
+ * Forgot your password?
+ */
+ forgotPassword: () => LocalizedString
+ /**
+ * Log in
+ */
+ logIn: () => LocalizedString
+ }
+}
+
+export type Formatters = {}
diff --git a/apps/kit/src/lib/i18n/i18n-util.async.ts b/apps/kit/src/lib/i18n/i18n-util.async.ts
new file mode 100644
index 0000000..3ccef5f
--- /dev/null
+++ b/apps/kit/src/lib/i18n/i18n-util.async.ts
@@ -0,0 +1,27 @@
+// This file was auto-generated by 'typesafe-i18n'. Any manual changes will be overwritten.
+/* eslint-disable */
+
+import { initFormatters } from './formatters'
+import type { Locales, Translations } from './i18n-types'
+import { loadedFormatters, loadedLocales, locales } from './i18n-util'
+
+const localeTranslationLoaders = {
+ en: () => import('./en'),
+ nb: () => import('./nb'),
+}
+
+const updateDictionary = (locale: Locales, dictionary: Partial<Translations>) =>
+ loadedLocales[locale] = { ...loadedLocales[locale], ...dictionary }
+
+export const importLocaleAsync = async (locale: Locales) =>
+ (await localeTranslationLoaders[locale]()).default as unknown as Translations
+
+export const loadLocaleAsync = async (locale: Locales): Promise<void> => {
+ updateDictionary(locale, await importLocaleAsync(locale))
+ loadFormatters(locale)
+}
+
+export const loadAllLocalesAsync = (): Promise<void[]> => Promise.all(locales.map(loadLocaleAsync))
+
+export const loadFormatters = (locale: Locales): void =>
+ void (loadedFormatters[locale] = initFormatters(locale))
diff --git a/apps/kit/src/lib/i18n/i18n-util.sync.ts b/apps/kit/src/lib/i18n/i18n-util.sync.ts
new file mode 100644
index 0000000..f1a8e9e
--- /dev/null
+++ b/apps/kit/src/lib/i18n/i18n-util.sync.ts
@@ -0,0 +1,26 @@
+// This file was auto-generated by 'typesafe-i18n'. Any manual changes will be overwritten.
+/* eslint-disable */
+
+import { initFormatters } from './formatters'
+import type { Locales, Translations } from './i18n-types'
+import { loadedFormatters, loadedLocales, locales } from './i18n-util'
+
+import en from './en'
+import nb from './nb'
+
+const localeTranslations = {
+ en,
+ nb,
+}
+
+export const loadLocale = (locale: Locales): void => {
+ if (loadedLocales[locale]) return
+
+ loadedLocales[locale] = localeTranslations[locale] as unknown as Translations
+ loadFormatters(locale)
+}
+
+export const loadAllLocales = (): void => locales.forEach(loadLocale)
+
+export const loadFormatters = (locale: Locales): void =>
+ void (loadedFormatters[locale] = initFormatters(locale))
diff --git a/apps/kit/src/lib/i18n/i18n-util.ts b/apps/kit/src/lib/i18n/i18n-util.ts
new file mode 100644
index 0000000..11d4b23
--- /dev/null
+++ b/apps/kit/src/lib/i18n/i18n-util.ts
@@ -0,0 +1,33 @@
+// This file was auto-generated by 'typesafe-i18n'. Any manual changes will be overwritten.
+/* eslint-disable */
+
+import { i18n as initI18n, i18nObject as initI18nObject, i18nString as initI18nString } from 'typesafe-i18n'
+import type { LocaleDetector } from 'typesafe-i18n/detectors'
+import { detectLocale as detectLocaleFn } from 'typesafe-i18n/detectors'
+import type { Formatters, Locales, Translations, TranslationFunctions } from './i18n-types'
+
+export const baseLocale: Locales = 'en'
+
+export const locales: Locales[] = [
+ 'en',
+ 'nb'
+]
+
+export const isLocale = (locale: string) => locales.includes(locale as Locales)
+
+export const loadedLocales = {} as Record<Locales, Translations>
+
+export const loadedFormatters = {} as Record<Locales, Formatters>
+
+export const i18nString = (locale: Locales) => initI18nString<Locales, Formatters>(locale, loadedFormatters[locale])
+
+export const i18nObject = (locale: Locales) =>
+ initI18nObject<Locales, Translations, TranslationFunctions, Formatters>(
+ locale,
+ loadedLocales[locale],
+ loadedFormatters[locale]
+ )
+
+export const i18n = () => initI18n<Locales, Translations, TranslationFunctions, Formatters>(loadedLocales, loadedFormatters)
+
+export const detectLocale = (...detectors: LocaleDetector[]) => detectLocaleFn<Locales>(baseLocale, locales, ...detectors)
diff --git a/apps/kit/src/lib/i18n/nb/index.ts b/apps/kit/src/lib/i18n/nb/index.ts
new file mode 100644
index 0000000..b350994
--- /dev/null
+++ b/apps/kit/src/lib/i18n/nb/index.ts
@@ -0,0 +1,136 @@
+import type {Translation} from "../i18n-types";
+
+const nb: Translation = {
+ nav: {
+ home: "Hjem",
+ data: "Data",
+ settings: "Innstillinger",
+ usermenu: {
+ logout: "Logg ut",
+ logoutTitle: "Logg ut av din profil",
+ profile: "Profil",
+ profileTitle: "Administrer din profil",
+ toggleTitle: "Vis brukermeny"
+ }
+ },
+ views: {
+ categoryForm: {
+ name: "Navn",
+ color: "Farge",
+ defaultLabels: "Standard merknader",
+ labelsPlaceholder: "Søk eller opprett"
+ },
+ dataTablePaginator: {
+ goToPrevPage: "GÃ¥ til forrige side",
+ goToNextPage: "GÃ¥ til neste side",
+ of: "av",
+ },
+ settingsCategoriesTile: {
+ deleteAllConfirm: "Er du sikker på at du vil slette denne kategorien?\nDette vil slette alle tilhørende rader!",
+ active: "Aktive",
+ archived: "Arkiverte",
+ name: "Navn",
+ color: "Farge",
+ editEntry: "Rediger kategori",
+ deleteEntry: "Slett kategori",
+ noCategories: "Ingen kategorier",
+ categories: "Kategorier"
+ },
+ settingsLabelsTile: {
+ deleteAllConfirm: "Er du sikker på at du vil slette denne merknaden?\nDen vil bli slette fra alle relaterte rader!",
+ active: "Aktive",
+ archived: "Arkiverte",
+ name: "Navn",
+ color: "Farge",
+ editEntry: "Rediger merknad",
+ deleteEntry: "Slett merknad",
+ noLabels: "Ingen merknader",
+ labels: "Merknader"
+ },
+ entryForm: {
+ entryUpdateError: "En feil oppstod med lagringen av din rad, prøv igjen snart.",
+ entryCreateError: "En feil oppstod med opprettelsen av din rad, prøv igjen snart.",
+ errDescriptionReq: "Beskrivelse er påkrevd",
+ reset: "Tilbakestill",
+ description: "Beskrivelse",
+ save: "Lagre",
+ create: "Opprett",
+ category: {
+ category: "Kategori",
+ placeholder: "Søk eller opprett",
+ noResults: "Ingen kategorier tilgjengelig (Opprett en ny ved å skrive navnet i søkefeltet).",
+ errisRequired: "Kategori er påkrevd",
+ _logReset: "Tilbakestilte kategori-seksjonen"
+ },
+ labels: {
+ placeholder: "Søk eller opprett",
+ noResults: "Ingen merkander tilgjengelig (Opprett en ny ved å skrive navnet i søkefeltet).",
+ labels: "Merknader",
+ _logReset: "Tilbakestilte merknader-seksjonen"
+ },
+ dateTime: {
+ errDateIsRequired: "Dato er påkrevd",
+ errFromIsRequired: "Fra er påkrevd",
+ errFromAfterTo: "Fra kan ikke være etter Til",
+ errFromEqTo: "Fra og Til kan ikke ha lik verdi",
+ errToIsRequired: "Til er påkrevd",
+ errToBeforeFrom: "Til kan ikke være før Fra",
+ from: "Fra",
+ to: "Til",
+ date: "Dato",
+ _logReset: "Tilbakestilte dato-seksjonen"
+ }
+ }
+ },
+ data: {
+ durationSummary: "Viser {entryCountString:string}, Tilsammen {totalHourMin:string}",
+ hourSingleChar: "t",
+ minSingleChar: "m",
+ entry: "rad",
+ entries: "rader",
+ confirmDeleteEntry: "Er du sikker på at du vil slette denne raden?",
+ editEntry: "Rediger rad",
+ date: "Dato",
+ from: "Fra",
+ duration: "Tidsrom",
+ category: "Kategori",
+ description: "Beskrivelse",
+ loading: "Laster",
+ noEntries: "Ingen rader",
+ to: "til",
+ use: "Bruk",
+ },
+ home: {
+ loggedTimeTodayString: "{hours}t{minutes}m",
+ confirmDeleteEntry: "Er du sikker på at du vil slette denne raden?",
+ newEntry: "Ny tidsoppføring",
+ editEntry: "Rediger rad",
+ deleteEntry: "Slett rad",
+ loggedTimeToday: "Registrert tid hittil idag",
+ currentTime: "Klokken",
+ loading: "Laster",
+ stopwatch: "Stoppeklokke",
+ todayEntries: "Dagens tidsoppføringer",
+ noEntriesToday: "Ingen oppføringer i dag",
+ refreshTodayEntries: "Last inn dagens tidsoppføringer på nytt",
+ category: "Kategori",
+ timespan: "Tidsrom",
+ },
+ messages: {
+ pageNotFound: "Fant ikke siden",
+ goToFrontpage: "GÃ¥ til forsiden",
+ noInternet: "Det ser ut som at du er uten internettilgang, vennligst sjekk tilkoblingen din."
+ },
+ login: {
+ loginToYourAccount: "Logg inn i din konto",
+ or: "Eller",
+ createANewAccount: "lag en ny konto",
+ emailAddress: "E-postadresse",
+ password: "Passord",
+ notMyComputer: "Dette er ikke min datamaskin",
+ forgotPassword: "Glem passord?",
+ logIn: "Logg inn"
+ },
+};
+
+export default nb;
diff --git a/apps/kit/src/lib/locale.ts b/apps/kit/src/lib/locale.ts
new file mode 100644
index 0000000..002f874
--- /dev/null
+++ b/apps/kit/src/lib/locale.ts
@@ -0,0 +1,20 @@
+import {writable} from "svelte/store";
+import {base_domain, CookieNames} from "./configuration";
+import {get_cookie, set_cookie} from "./helpers";
+
+export function preffered_or_default() {
+ if (/^en\b/i.test(navigator.language)) {
+ return "en";
+ }
+ if (/^nb\b/i.test(navigator.language) || /^nn\b/i.test(navigator.language)) {
+ return "nb";
+ }
+ return "en";
+}
+
+type Locales = "en"|"nb";
+export const currentLocale = writable<Locales>((get_cookie(CookieNames.locale) === "preffered" ? preffered_or_default() : get_cookie(CookieNames.locale) ?? preffered_or_default()) as Locales);
+currentLocale.subscribe(locale => {
+ // @ts-ignore
+ set_cookie(CookieNames.locale, locale, base_domain());
+});
diff --git a/apps/kit/src/lib/logger.ts b/apps/kit/src/lib/logger.ts
new file mode 100644
index 0000000..e017ba0
--- /dev/null
+++ b/apps/kit/src/lib/logger.ts
@@ -0,0 +1,87 @@
+import {browser, dev} from "$app/environment";
+import {StorageKeys} from "$lib/configuration";
+import pino from "pino";
+
+const pinoConfig = dev ? {
+ transport: {
+ target: "pino-pretty",
+ },
+} : {};
+
+const pinoLogger = pino(pinoConfig);
+
+function browserLogLevel(): number {
+ if (browser) return LogLevel.toNumber(sessionStorage.getItem(StorageKeys.logLevel), LogLevel.INFO);
+ throw new Error("Called browser api in server");
+}
+
+function serverLogLevel(): number {
+ if (!browser) return LogLevel.toNumber(process.env.LOG_LEVEL, LogLevel.ERROR);
+ throw new Error("Called server api in browser");
+}
+
+export const LogLevel = {
+ DEBUG: 0,
+ INFO: 1,
+ ERROR: 2,
+ SILENT: 3,
+ toString(levelInt: number): string {
+ switch (levelInt) {
+ case 0:
+ return "DEBUG";
+ case 1:
+ return "INFO";
+ case 2:
+ return "ERROR";
+ case 3:
+ return "SILENT";
+ default:
+ throw new Error("Log level int is unknown");
+ }
+ },
+ toNumber(levelString?: string | null, fallback?: number): number {
+ if (!levelString && fallback) return fallback;
+ else if (!levelString && !fallback) throw new Error("levelString was empty, and no fallback was specified");
+ switch (levelString?.toUpperCase()) {
+ case "DEBUG":
+ return 0;
+ case "INFO":
+ return 1;
+ case "ERROR":
+ return 2;
+ case "SILENT":
+ return 3;
+ default:
+ if (!fallback) throw new Error("Log level string is unknown");
+ else return fallback;
+ }
+ },
+};
+
+export function logDebug(message: string, ...additional: any[]): void {
+ if (browser && browserLogLevel() <= LogLevel.DEBUG) {
+ pinoLogger.debug(message, additional);
+ }
+
+ if (!browser && serverLogLevel() <= LogLevel.DEBUG) {
+ pinoLogger.debug(message, additional);
+ }
+}
+
+export function logInfo(message: string, ...additional: any[]): void {
+ if (browser && browserLogLevel() <= LogLevel.INFO) {
+ pinoLogger.info(message, additional);
+ }
+ if (!browser && serverLogLevel() <= LogLevel.INFO) {
+ pinoLogger.info(message, additional);
+ }
+}
+
+export function logError(message: any, ...additional: any[]): void {
+ if (browser && browserLogLevel() <= LogLevel.ERROR) {
+ pinoLogger.error(message, additional);
+ }
+ if (!browser && serverLogLevel() <= LogLevel.ERROR) {
+ pinoLogger.error(message, additional);
+ }
+} \ No newline at end of file
diff --git a/apps/kit/src/lib/models/CreateAccountPayload.ts b/apps/kit/src/lib/models/CreateAccountPayload.ts
new file mode 100644
index 0000000..d116308
--- /dev/null
+++ b/apps/kit/src/lib/models/CreateAccountPayload.ts
@@ -0,0 +1,4 @@
+export interface CreateAccountPayload {
+ username: string,
+ password: string
+}
diff --git a/apps/kit/src/lib/models/ErrorResult.ts b/apps/kit/src/lib/models/ErrorResult.ts
new file mode 100644
index 0000000..7c70017
--- /dev/null
+++ b/apps/kit/src/lib/models/ErrorResult.ts
@@ -0,0 +1,4 @@
+export interface ErrorResult {
+ title: string,
+ text: string
+}
diff --git a/apps/kit/src/lib/models/IInternalFetchRequest.ts b/apps/kit/src/lib/models/IInternalFetchRequest.ts
new file mode 100644
index 0000000..68505e2
--- /dev/null
+++ b/apps/kit/src/lib/models/IInternalFetchRequest.ts
@@ -0,0 +1,6 @@
+export interface IInternalFetchRequest {
+ url: string,
+ init?: RequestInit,
+ timeout?: number
+ retry_count?: number
+}
diff --git a/apps/kit/src/lib/models/IInternalFetchResponse.ts b/apps/kit/src/lib/models/IInternalFetchResponse.ts
new file mode 100644
index 0000000..6c91b35
--- /dev/null
+++ b/apps/kit/src/lib/models/IInternalFetchResponse.ts
@@ -0,0 +1,6 @@
+export interface IInternalFetchResponse {
+ ok: boolean,
+ status: number,
+ data: any,
+ http_response: Response
+}
diff --git a/apps/kit/src/lib/models/ISession.ts b/apps/kit/src/lib/models/ISession.ts
new file mode 100644
index 0000000..f7ed46b
--- /dev/null
+++ b/apps/kit/src/lib/models/ISession.ts
@@ -0,0 +1,7 @@
+export interface ISession {
+ profile: {
+ username: string,
+ id: string,
+ },
+ lastChecked: number,
+} \ No newline at end of file
diff --git a/apps/kit/src/lib/models/IValidationResult.ts b/apps/kit/src/lib/models/IValidationResult.ts
new file mode 100644
index 0000000..9a21b13
--- /dev/null
+++ b/apps/kit/src/lib/models/IValidationResult.ts
@@ -0,0 +1,31 @@
+export interface IValidationResult {
+ errors: Array<IValidationError>,
+ has_errors: Function,
+ add_error: Function,
+ remove_error: Function,
+}
+
+export interface IValidationError {
+ _id?: string,
+ title: string,
+ text?: string
+}
+
+export default class ValidationResult implements IValidationResult {
+ errors: IValidationError[]
+ has_errors(): boolean {
+ return this.errors?.length > 0;
+ }
+ add_error(prop: string, error: IValidationError): void {
+ if (!this.errors) this.errors = [];
+ error._id = prop;
+ this.errors.push(error);
+ }
+ remove_error(property: string): void {
+ const new_errors = [];
+ for (const error of this.errors) {
+ if (error._id != property) new_errors.push(error)
+ }
+ this.errors = new_errors;
+ }
+}
diff --git a/apps/kit/src/lib/models/LoginPayload.ts b/apps/kit/src/lib/models/LoginPayload.ts
new file mode 100644
index 0000000..ccd9bed
--- /dev/null
+++ b/apps/kit/src/lib/models/LoginPayload.ts
@@ -0,0 +1,4 @@
+export interface LoginPayload {
+ username: string,
+ password: string
+}
diff --git a/apps/kit/src/lib/models/TimeCategoryDto.ts b/apps/kit/src/lib/models/TimeCategoryDto.ts
new file mode 100644
index 0000000..fcdb7ea
--- /dev/null
+++ b/apps/kit/src/lib/models/TimeCategoryDto.ts
@@ -0,0 +1,9 @@
+import { Temporal } from "temporal-polyfill";
+
+export interface TimeCategoryDto {
+ selected?: boolean;
+ id?: string,
+ modified_at?: Temporal.PlainDate,
+ name?: string,
+ color?: string
+}
diff --git a/apps/kit/src/lib/models/TimeEntryDto.ts b/apps/kit/src/lib/models/TimeEntryDto.ts
new file mode 100644
index 0000000..571c52e
--- /dev/null
+++ b/apps/kit/src/lib/models/TimeEntryDto.ts
@@ -0,0 +1,13 @@
+import type { TimeLabelDto } from "./TimeLabelDto";
+import type { TimeCategoryDto } from "./TimeCategoryDto";
+import { Temporal } from "temporal-polyfill";
+
+export interface TimeEntryDto {
+ id: string,
+ modified_at?: Temporal.PlainDate,
+ start: string,
+ stop: string,
+ description: string,
+ labels?: Array<TimeLabelDto>,
+ category: TimeCategoryDto,
+}
diff --git a/apps/kit/src/lib/models/TimeEntryQuery.ts b/apps/kit/src/lib/models/TimeEntryQuery.ts
new file mode 100644
index 0000000..d983d1a
--- /dev/null
+++ b/apps/kit/src/lib/models/TimeEntryQuery.ts
@@ -0,0 +1,27 @@
+import type { TimeCategoryDto } from "./TimeCategoryDto";
+import type { TimeLabelDto } from "./TimeLabelDto";
+import type { Temporal } from "temporal-polyfill";
+
+export interface TimeEntryQuery {
+ duration: TimeEntryQueryDuration,
+ categories?: Array<TimeCategoryDto>,
+ labels?: Array<TimeLabelDto>,
+ dateRange?: TimeEntryQueryDateRange,
+ specificDate?: Temporal.PlainDateTime
+ page: number,
+ pageSize: number
+}
+
+export interface TimeEntryQueryDateRange {
+ from: Temporal.PlainDateTime,
+ to: Temporal.PlainDateTime
+}
+
+export enum TimeEntryQueryDuration {
+ TODAY = 0,
+ THIS_WEEK = 1,
+ THIS_MONTH = 2,
+ THIS_YEAR = 3,
+ SPECIFIC_DATE = 4,
+ DATE_RANGE = 5,
+}
diff --git a/apps/kit/src/lib/models/TimeLabelDto.ts b/apps/kit/src/lib/models/TimeLabelDto.ts
new file mode 100644
index 0000000..7183bcf
--- /dev/null
+++ b/apps/kit/src/lib/models/TimeLabelDto.ts
@@ -0,0 +1,8 @@
+import { Temporal } from "temporal-polyfill";
+
+export interface TimeLabelDto {
+ id?: string,
+ modified_at?: Temporal.PlainDate,
+ name?: string,
+ color?: string
+}
diff --git a/apps/kit/src/lib/models/TimeQueryDto.ts b/apps/kit/src/lib/models/TimeQueryDto.ts
new file mode 100644
index 0000000..607c51e
--- /dev/null
+++ b/apps/kit/src/lib/models/TimeQueryDto.ts
@@ -0,0 +1,29 @@
+import type { TimeEntryDto } from "./TimeEntryDto";
+import ValidationResult, { IValidationResult } from "./IValidationResult";
+
+export interface ITimeQueryDto {
+ results: Array<TimeEntryDto>,
+ page: number,
+ pageSize: number,
+ totalRecords: number,
+ totalPageCount: number,
+ is_valid: Function
+}
+
+export class TimeQueryDto implements ITimeQueryDto {
+ results: TimeEntryDto[];
+ page: number;
+ pageSize: number;
+ totalRecords: number;
+ totalPageCount: number;
+
+ is_valid(): IValidationResult {
+ const result = new ValidationResult();
+ if (this.page < 0) {
+ result.add_error("page", {
+ title: "Page cannot be less than zero",
+ })
+ }
+ return result;
+ }
+}
diff --git a/apps/kit/src/lib/models/UnwrappedEntryDateTime.ts b/apps/kit/src/lib/models/UnwrappedEntryDateTime.ts
new file mode 100644
index 0000000..d614f91
--- /dev/null
+++ b/apps/kit/src/lib/models/UnwrappedEntryDateTime.ts
@@ -0,0 +1,9 @@
+import { Temporal } from "temporal-polyfill";
+
+export interface UnwrappedEntryDateTime {
+ start_date: Temporal.PlainDate,
+ stop_date: Temporal.PlainDate,
+ start_time: Temporal.PlainTime,
+ stop_time: Temporal.PlainTime,
+ duration: Temporal.Duration,
+}
diff --git a/apps/kit/src/lib/models/UpdateProfilePayload.ts b/apps/kit/src/lib/models/UpdateProfilePayload.ts
new file mode 100644
index 0000000..d2983ff
--- /dev/null
+++ b/apps/kit/src/lib/models/UpdateProfilePayload.ts
@@ -0,0 +1,4 @@
+export interface UpdateProfilePayload {
+ username?: string,
+ password?: string,
+}
diff --git a/apps/kit/src/lib/persistent-store.ts b/apps/kit/src/lib/persistent-store.ts
new file mode 100644
index 0000000..922f3ab
--- /dev/null
+++ b/apps/kit/src/lib/persistent-store.ts
@@ -0,0 +1,102 @@
+import { writable as _writable, readable as _readable, } from "svelte/store";
+import type { Writable, Readable, StartStopNotifier } from "svelte/store";
+
+enum StoreType {
+ SESSION = 0,
+ LOCAL = 1
+}
+
+interface StoreOptions {
+ store?: StoreType;
+}
+
+const default_store_options = {
+ store: StoreType.SESSION
+} as StoreOptions;
+
+interface WritableStore<T> {
+ name: string,
+ initialState: T,
+ options?: StoreOptions
+}
+
+interface ReadableStore<T> {
+ name: string,
+ initialState: T,
+ callback: StartStopNotifier<any>,
+ options?: StoreOptions
+}
+
+function get_store(type: StoreType): Storage {
+ switch (type) {
+ case StoreType.SESSION:
+ return window.sessionStorage;
+ case StoreType.LOCAL:
+ return window.localStorage;
+ }
+}
+
+function prepared_store_value(value: any): string {
+ try {
+ return JSON.stringify(value);
+ } catch (e) {
+ console.error(e);
+ return "__INVALID__";
+ }
+}
+
+function get_store_value<T>(options: WritableStore<T> | ReadableStore<T>): any {
+ try {
+ const storage = get_store(options.options.store);
+ const value = storage.getItem(options.name);
+ if (!value) return false;
+ return JSON.parse(value);
+ } catch (e) {
+ console.error(e);
+ return { __INVALID__: true };
+ }
+}
+
+function hydrate<T>(store: Writable<T>, options: WritableStore<T> | ReadableStore<T>): void {
+ const value = get_store_value<T>(options);
+ if (value && store.set) store.set(value);
+}
+
+function subscribe<T>(store: Writable<T> | Readable<T>, options: WritableStore<T> | ReadableStore<T>): void {
+ const storage = get_store(options.options.store);
+ if (!store.subscribe) return;
+ store.subscribe((state: any) => {
+ storage.setItem(options.name, prepared_store_value(state));
+ });
+}
+
+function writable_persistent<T>(options: WritableStore<T>): Writable<T> {
+ if (options.options === undefined) options.options = default_store_options;
+ console.log("Creating writable store with options: ", options);
+ const store = _writable<T>(options.initialState);
+ hydrate(store, options);
+ subscribe(store, options);
+ return store;
+}
+
+function readable_persistent<T>(options: ReadableStore<T>): Readable<T> {
+ if (options.options === undefined) options.options = default_store_options;
+ console.log("Creating readable store with options: ", options);
+ const store = _readable<T>(options.initialState, options.callback);
+ // hydrate(store, options);
+ subscribe(store, options);
+ return store;
+}
+
+export {
+ writable_persistent,
+ readable_persistent,
+ StoreType
+};
+
+export type {
+ WritableStore,
+ ReadableStore,
+ StoreOptions
+};
+
diff --git a/apps/kit/src/lib/session.ts b/apps/kit/src/lib/session.ts
new file mode 100644
index 0000000..ee79933
--- /dev/null
+++ b/apps/kit/src/lib/session.ts
@@ -0,0 +1,69 @@
+import {logError, logInfo} from "$lib/logger";
+import { Temporal } from "temporal-polyfill";
+import { get_profile_for_active_check, logout } from "./api/user";
+import { is_guid, session_storage_get_json, session_storage_set_json } from "./helpers";
+import { SECONDS_BETWEEN_SESSION_CHECK, StorageKeys } from "./configuration";
+import type { ISession } from "$lib/models/ISession";
+
+export async function is_active(forceRefresh: boolean = false): Promise<boolean> {
+ const nowEpoch = Temporal.Now.instant().epochSeconds;
+ const data = session_storage_get_json(StorageKeys.session) as ISession;
+ const expiryEpoch = data?.lastChecked + SECONDS_BETWEEN_SESSION_CHECK;
+ const lastCheckIsStaleOrNone = !is_guid(data?.profile?.id) || (expiryEpoch < nowEpoch);
+ if (forceRefresh || lastCheckIsStaleOrNone) {
+ return await call_api();
+ } else {
+ const sessionIsValid = data.profile && is_guid(data.profile.id);
+ if (!sessionIsValid) {
+ clear_session_data();
+ logInfo("Session data is not valid");
+ }
+ return sessionIsValid;
+ }
+}
+
+export async function end_session(cb: Function): Promise<void> {
+ await logout();
+ clear_session_data();
+ cb();
+}
+
+async function call_api(): Promise<boolean> {
+ logInfo("Getting profile data while checking session state");
+ try {
+ const response = await get_profile_for_active_check();
+ if (response.ok) {
+ const userData = await response.data;
+ if (is_guid(userData.id) && userData.username) {
+ const session = {
+ profile: userData,
+ lastChecked: Temporal.Now.instant().epochSeconds
+ } as ISession;
+ session_storage_set_json(StorageKeys.session, session);
+ logInfo("Successfully got profile data while checking session state");
+ return true;
+ } else {
+ logError("Api returned invalid data while getting profile data");
+ clear_session_data();
+ return false;
+ }
+ } else {
+ logError("Api returned unsuccessfully while getting profile data");
+ clear_session_data();
+ return false;
+ }
+ } catch (e) {
+ logError(e);
+ clear_session_data();
+ return false;
+ }
+}
+
+export function clear_session_data() {
+ session_storage_set_json(StorageKeys.session, {});
+ logInfo("Cleared session data.");
+}
+
+export function get_session_data(): ISession {
+ return session_storage_get_json(StorageKeys.session) as ISession;
+}
diff --git a/apps/kit/src/params/guid.ts b/apps/kit/src/params/guid.ts
new file mode 100644
index 0000000..d8f7231
--- /dev/null
+++ b/apps/kit/src/params/guid.ts
@@ -0,0 +1,5 @@
+import {is_guid} from "$lib/helpers";
+
+export function match(param: string): boolean {
+ return is_guid(param);
+} \ No newline at end of file
diff --git a/apps/kit/src/params/integer.ts b/apps/kit/src/params/integer.ts
new file mode 100644
index 0000000..6e36cd8
--- /dev/null
+++ b/apps/kit/src/params/integer.ts
@@ -0,0 +1,3 @@
+export function match(param: string): boolean {
+ return /^\d+$/.test(param);
+} \ No newline at end of file
diff --git a/apps/kit/src/routes/(app)/+layout.svelte b/apps/kit/src/routes/(app)/+layout.svelte
new file mode 100644
index 0000000..3f60af3
--- /dev/null
+++ b/apps/kit/src/routes/(app)/+layout.svelte
@@ -0,0 +1,215 @@
+<svelte:options immutable={true}/>
+<svelte:window bind:online={online}/>
+<script lang="ts">
+ import LL, {setLocale} from "$lib/i18n/i18n-svelte";
+ import {Dialog, TransitionChild, TransitionRoot} from '@rgossiaux/svelte-headlessui';
+ import {XIcon, MenuIcon, HomeIcon, DatabaseIcon, AdjustmentsIcon} from "$lib/components/icons";
+
+ let online = true;
+ let sidebarIsOpen = false;
+ const username = "dumb";
+
+ setLocale("nb");
+
+ const navigations = [
+ {
+ name: "Home",
+ icon: HomeIcon
+ },
+ {
+ name: "Data",
+ icon: DatabaseIcon
+ },
+ {
+ name: "Settings",
+ icon: AdjustmentsIcon
+ }
+ ]
+</script>
+{#if !online}
+ <div class="bg-yellow-50 border-l-4 border-yellow-400 p-4">
+ <div class="flex">
+ <div class="flex-shrink-0">
+ <svg class="h-5 w-5 text-yellow-400" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"
+ fill="currentColor" aria-hidden="true">
+ <path fill-rule="evenodd"
+ d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z"
+ clip-rule="evenodd"/>
+ </svg>
+ </div>
+ <div class="ml-3">
+ <p class="text-sm text-yellow-700">
+ You seem to be offline, please check your internet connection.
+ </p>
+ </div>
+ </div>
+ </div>
+{/if}
+<div class="h-full flex">
+ <TransitionRoot show={sidebarIsOpen}>
+ <Dialog class="relative z-40 lg:hidden" on:close={() => sidebarIsOpen = !sidebarIsOpen}>
+ <TransitionChild
+ enter="transition-opacity ease-linear duration-300"
+ enterFrom="opacity-0"
+ enterTo="opacity-100"
+ leave="transition-opacity ease-linear duration-300"
+ leaveFrom="opacity-100"
+ leaveTo="opacity-0">
+ <div class="fixed inset-0 bg-gray-600 bg-opacity-75"></div>
+ </TransitionChild>
+
+ <div class="fixed inset-0 flex z-40">
+ <TransitionChild
+ enter="transition ease-in-out duration-300 transform"
+ enterFrom="-translate-x-full"
+ enterTo="translate-x-0"
+ leave="transition ease-in-out duration-300 transform"
+ leaveFrom="translate-x-0"
+ leaveTo="-translate-x-full">
+ <DialogPanel class="relative flex-1 flex flex-col max-w-xs w-full bg-white focus:outline-none">
+ <TransitionChild
+ enter="ease-in-out duration-300"
+ enterFrom="opacity-0"
+ enterTo="opacity-100"
+ leave="ease-in-out duration-300"
+ leaveFrom="opacity-100"
+ leaveTo="opacity-0">
+ <div class="absolute top-0 right-0 -mr-12 pt-2">
+ <button type="button"
+ class="ml-1 flex items-center justify-center h-10 w-10 rounded-full focus:outline-none focus:ring-2 focus:ring-inset focus:ring-white"
+ on:click={() => sidebarIsOpen = false}>
+ <span class="sr-only">Close sidebar</span>
+ <XIcon class="text-white" aria-hidden="true"/>
+ </button>
+ </div>
+ </TransitionChild>
+ <div class="flex-1 h-0 pt-5 pb-4 overflow-y-auto">
+ <div class="flex-shrink-0 flex items-center px-4">
+ <img class="h-8 w-auto"
+ src="https://tailwindui.com/img/logos/workflow-mark.svg?color=indigo&shade=600"
+ alt="Workflow"
+ />
+ </div>
+ <nav aria-label="Sidebar" class="mt-5">
+ <div class="px-2 space-y-1">
+ {#each navigations as item (item.name)}
+ <a href={item.href}
+ class="{item.current? 'bg-gray-100 text-gray-900': 'text-gray-600 hover:bg-gray-50 hover:text-gray-900'} group flex items-center px-2 py-2 text-base font-medium rounded-md">
+ <svelte:component this="{item.icon}"
+ class="{item.current ? 'text-gray-500' : 'text-gray-400 group-hover:text-gray-500'} mr-4 h-6 w-6"
+ aria-hidden="true"></svelte:component>
+ {item.name}
+ </a>
+ {/each}
+ </div>
+ </nav>
+ </div>
+ <div class="flex-shrink-0 flex border-t border-gray-200 p-4">
+ <a href="#" class="flex-shrink-0 group block">
+ <div class="flex items-center">
+ <div>
+ <img class="inline-block h-10 w-10 rounded-full"
+ src="https://images.unsplash.com/photo-1517365830460-955ce3ccd263?ixlib=rb-=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=8&w=256&h=256&q=80"
+ alt=""
+ />
+ </div>
+ <div class="ml-3">
+ <p class="text-base font-medium text-gray-700 group-hover:text-gray-900">
+ {username}
+ </p>
+ <p class="text-sm font-medium text-gray-500 group-hover:text-gray-700">
+ {$LL.nav.usermenu.profileTitle()}
+ </p>
+ </div>
+ </div>
+ </a>
+ </div>
+ </DialogPanel>
+ </TransitionChild>
+ <div class="flex-shrink-0 w-14" aria-hidden="true">
+ <!--{/* Force sidebar to shrink to fit close icon */}-->
+ </div>
+ </div>
+ </Dialog>
+ </TransitionRoot>
+
+ <!--{/* Static sidebar for desktop */}-->
+ <div class="hidden lg:flex lg:flex-shrink-0">
+ <div class="flex flex-col w-64">
+ <!--{/* Sidebar component, swap this element with another sidebar if you like */}-->
+ <div class="flex-1 flex flex-col min-h-0 border-r border-gray-200 bg-gray-100">
+ <div class="flex-1 flex flex-col pt-5 pb-4 overflow-y-auto">
+ <div class="flex items-center flex-shrink-0 px-4">
+ <img class="h-8 w-auto"
+ src="https://tailwindui.com/img/logos/workflow-mark.svg?color=indigo&shade=600"
+ alt="Workflow"
+ />
+ </div>
+ <nav class="mt-5 flex-1" aria-label="Sidebar">
+ <div class="px-2 space-y-1">
+ {#each navigations as item (item.name)}
+ <a key={item.name}
+ href={item.href}
+ class="{item.current ? 'bg-gray-200 text-gray-900' : 'text-gray-600 hover:bg-gray-50 hover:text-gray-900'} group flex items-center px-2 py-2 text-sm font-medium rounded-md">
+ <svelte:component this="{item.icon}"
+ class="{item.current ? 'text-gray-500' : 'text-gray-400 group-hover:text-gray-500'} mr-3 h-6 w-6"
+ aria-hidden="true"></svelte:component>
+ {item.name}
+ </a>
+ {/each}
+ </div>
+ </nav>
+ </div>
+ <div class="flex-shrink-0 flex border-t border-gray-200 p-4">
+ <a href="#" class="flex-shrink-0 w-full group block">
+ <div class="flex items-center">
+ <div>
+ <img class="inline-block h-9 w-9 rounded-full"
+ src="https://images.unsplash.com/photo-1517365830460-955ce3ccd263?ixlib=rb-=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=8&w=256&h=256&q=80"
+ alt=""
+ />
+ </div>
+ <div class="ml-3">
+ <p class="text-base font-medium text-gray-700 group-hover:text-gray-900">
+ {username}
+ </p>
+ <p class="text-sm font-medium text-gray-500 group-hover:text-gray-700">
+ {$LL.nav.usermenu.profileTitle()}
+ </p>
+ </div>
+ </div>
+ </a>
+ </div>
+ </div>
+ </div>
+ </div>
+ <div class="flex flex-col min-w-0 flex-1 overflow-hidden">
+ <div class="lg:hidden">
+ <div class="flex items-center justify-between bg-gray-50 border-b border-gray-200 px-4 py-1.5">
+ <div>
+ <button type="button"
+ class="-mr-3 h-12 w-12 inline-flex items-center justify-center rounded-md text-gray-500 hover:text-gray-900"
+ on:click={() => sidebarIsOpen = true}>
+ <span class="sr-only">Open sidebar</span>
+ <MenuIcon aria-hidden="true"/>
+ </button>
+ </div>
+ </div>
+ </div>
+ <div class="flex-1 relative z-0 flex overflow-hidden">
+ <main class="flex-1 relative z-0 overflow-y-auto focus:outline-none">
+ <!--
+ MAIN CONTENT
+ -->
+ <slot/>
+ </main>
+ <aside class="hidden relative xl:flex xl:flex-col flex-shrink-0 w-96 border-l border-gray-200 overflow-y-auto">
+ <!--{/* Start secondary column (hidden on smaller screens) */}
+ <div class="absolute inset-0 py-6 px-4 sm:px-6 lg:px-8">
+ <div class="h-full border-2 border-gray-200 border-dashed rounded-lg" />
+ </div>
+ {/* End secondary column */}-->
+ </aside>
+ </div>
+ </div>
+</div> \ No newline at end of file
diff --git a/apps/kit/src/routes/(app)/home/+page.svelte b/apps/kit/src/routes/(app)/home/+page.svelte
new file mode 100644
index 0000000..247ee47
--- /dev/null
+++ b/apps/kit/src/routes/(app)/home/+page.svelte
@@ -0,0 +1 @@
+<h1>Welcome Home</h1> \ No newline at end of file
diff --git a/apps/kit/src/routes/(public)/+layout.svelte b/apps/kit/src/routes/(public)/+layout.svelte
new file mode 100644
index 0000000..1301e50
--- /dev/null
+++ b/apps/kit/src/routes/(public)/+layout.svelte
@@ -0,0 +1,6 @@
+<script>
+ import {setLocale} from "$lib/i18n/i18n-svelte";
+
+ setLocale("nb");
+</script>
+<slot></slot> \ No newline at end of file
diff --git a/apps/kit/src/routes/(public)/login/+page.svelte b/apps/kit/src/routes/(public)/login/+page.svelte
new file mode 100644
index 0000000..800575e
--- /dev/null
+++ b/apps/kit/src/routes/(public)/login/+page.svelte
@@ -0,0 +1,95 @@
+<script lang="ts">
+ import {goto} from "$app/navigation";
+ import {login} from "$lib/api/user";
+ import LL from "$lib/i18n/i18n-svelte";
+ import type {ErrorResult} from "$lib/models/ErrorResult";
+ import type {LoginPayload} from "$lib/models/LoginPayload";
+
+ const data = {
+ username: "",
+ password: ""
+ } as LoginPayload;
+ let error = {
+ text: "",
+ title: ""
+ } as ErrorResult;
+
+ async function submitFormAsync() {
+ error = {text: "", title: ""};
+ const loginResponse = await login(data);
+ if (loginResponse.ok) {
+ await goto("/home")
+ } else {
+ error.title = loginResponse.data.title;
+ error.text = loginResponse.data.text;
+ }
+ }
+</script>
+<div class="min-h-full flex flex-col justify-center py-12 sm:px-6 lg:px-8">
+ <div class="sm:mx-auto sm:w-full sm:max-w-md">
+ <h2 class="mt-6 text-center text-3xl tracking-tight font-bold text-gray-900">{$LL.login.loginToYourAccount()}</h2>
+ <p class="mt-2 text-center text-sm text-gray-600">
+ {$LL.login.or()}
+ <a href="/signup"
+ class="font-medium text-indigo-600 hover:text-indigo-500">{$LL.login.createANewAccount()}</a>
+ </p>
+ </div>
+
+ <div class="mt-8 sm:mx-auto sm:w-full sm:max-w-md">
+ <div class="bg-white py-8 px-4 shadow sm:rounded-lg sm:px-10">
+ {#if error.text || error.title}
+ <div class="rounded-md bg-red-50 p-3 mb-3">
+ {#if error.title}
+ <h3 class="text-sm font-medium text-red-800">{error.title}</h3>
+ {/if}
+ {#if error.text}
+ <div class="mt-2 text-sm text-red-700">
+ {error.text}
+ </div>
+ {/if}
+ </div>
+ {/if}
+ <form class="space-y-6" on:submit|preventDefault={submitFormAsync}>
+ <div>
+ <label for="email"
+ class="block text-sm font-medium text-gray-700">{$LL.login.emailAddress()}</label>
+ <div class="mt-1">
+ <input id="email" name="email" type="email" autocomplete="email" required
+ value={data.username}
+ class="appearance-none block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm placeholder-gray-400 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm">
+ </div>
+ </div>
+
+ <div>
+ <label for="password" class="block text-sm font-medium text-gray-700">{$LL.login.password()}</label>
+ <div class="mt-1">
+ <input id="password" name="password" type="password" autocomplete="current-password" required
+ value={data.password}
+ class="appearance-none block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm placeholder-gray-400 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm">
+ </div>
+ </div>
+
+ <div class="flex items-center justify-between">
+ <div class="flex items-center">
+ <input id="remember-me" name="remember-me" type="checkbox"
+ class="h-4 w-4 text-indigo-600 focus:ring-indigo-500 border-gray-300 rounded">
+ <label for="remember-me"
+ class="ml-2 block text-sm text-gray-900">{$LL.login.notMyComputer()}</label>
+ </div>
+
+ <div class="text-sm">
+ <a href="/reset"
+ class="font-medium text-indigo-600 hover:text-indigo-500">{$LL.login.forgotPassword()}</a>
+ </div>
+ </div>
+
+ <div>
+ <button type="submit"
+ class="w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
+ {$LL.login.logIn()}
+ </button>
+ </div>
+ </form>
+ </div>
+ </div>
+</div>
diff --git a/apps/kit/src/routes/(public)/reset/+page.svelte b/apps/kit/src/routes/(public)/reset/+page.svelte
new file mode 100644
index 0000000..41c4728
--- /dev/null
+++ b/apps/kit/src/routes/(public)/reset/+page.svelte
@@ -0,0 +1,29 @@
+<div class="min-h-full flex flex-col justify-center py-12 sm:px-6 lg:px-8">
+ <div class="sm:mx-auto sm:w-full sm:max-w-md">
+ <h2 class="mt-6 text-center text-3xl tracking-tight font-bold text-gray-900">Request a password reset</h2>
+ <p class="mt-2 text-center text-sm text-gray-600">
+ Or
+ <a href="/login" class="font-medium text-indigo-600 hover:text-indigo-500">go to login page</a>
+ </p>
+ </div>
+
+ <div class="mt-8 sm:mx-auto sm:w-full sm:max-w-md">
+ <div class="bg-white py-8 px-4 shadow sm:rounded-lg sm:px-10">
+ <form class="space-y-6" action="#" method="POST">
+ <div>
+ <label for="email" class="block text-sm font-medium text-gray-700"> Email address </label>
+ <div class="mt-1">
+ <input id="email" name="email" type="email" autocomplete="email" required
+ class="appearance-none block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm placeholder-gray-400 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm">
+ </div>
+ </div>
+ <div>
+ <button type="submit"
+ class="w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
+ Send request
+ </button>
+ </div>
+ </form>
+ </div>
+ </div>
+</div>
diff --git a/apps/kit/src/routes/(public)/signup/+page.svelte b/apps/kit/src/routes/(public)/signup/+page.svelte
new file mode 100644
index 0000000..d4a1bda
--- /dev/null
+++ b/apps/kit/src/routes/(public)/signup/+page.svelte
@@ -0,0 +1,38 @@
+<div class="min-h-full flex flex-col justify-center py-12 sm:px-6 lg:px-8">
+ <div class="sm:mx-auto sm:w-full sm:max-w-md">
+ <h2 class="mt-6 text-center text-3xl tracking-tight font-bold text-gray-900">Create your new account</h2>
+ <p class="mt-2 text-center text-sm text-gray-600">
+ Or
+ <a href="/login" class="font-medium text-indigo-600 hover:text-indigo-500">go to login page</a>
+ </p>
+ </div>
+
+ <div class="mt-8 sm:mx-auto sm:w-full sm:max-w-md">
+ <div class="bg-white py-8 px-4 shadow sm:rounded-lg sm:px-10">
+ <form class="space-y-6" action="#" method="POST">
+ <div>
+ <label for="email" class="block text-sm font-medium text-gray-700"> Email address </label>
+ <div class="mt-1">
+ <input id="email" name="email" type="email" autocomplete="email" required
+ class="appearance-none block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm placeholder-gray-400 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm">
+ </div>
+ </div>
+
+ <div>
+ <label for="password" class="block text-sm font-medium text-gray-700"> Password </label>
+ <div class="mt-1">
+ <input id="password" name="password" type="password" autocomplete="current-password" required
+ class="appearance-none block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm placeholder-gray-400 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm">
+ </div>
+ </div>
+
+ <div>
+ <button type="submit"
+ class="w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
+ Create account
+ </button>
+ </div>
+ </form>
+ </div>
+ </div>
+</div>
diff --git a/apps/kit/src/routes/+layout.server.ts b/apps/kit/src/routes/+layout.server.ts
new file mode 100644
index 0000000..01aae89
--- /dev/null
+++ b/apps/kit/src/routes/+layout.server.ts
@@ -0,0 +1,13 @@
+// import {is_active} from "$lib/session";
+// import {redirect} from "@sveltejs/kit";
+// import type {LayoutServerLoad} from "./$types";
+//
+// export const load: LayoutServerLoad = async ({routeId}) => {
+// const sessionIsValid = await is_active();
+// const isPublicRoute = routeId?.startsWith("(public)");
+// if (sessionIsValid && isPublicRoute) {
+// throw redirect(302, "/home");
+// } else if (!sessionIsValid && !isPublicRoute) {
+// throw redirect(302, "/login");
+// }
+// }; \ No newline at end of file
diff --git a/apps/kit/src/routes/+layout.svelte b/apps/kit/src/routes/+layout.svelte
new file mode 100644
index 0000000..ee76da9
--- /dev/null
+++ b/apps/kit/src/routes/+layout.svelte
@@ -0,0 +1,23 @@
+<script lang="ts">
+ import "../app.pcss";
+ import {afterNavigate, beforeNavigate, goto} from "$app/navigation";
+ import {is_active} from "$lib/session";
+ import type {Navigation} from "@sveltejs/kit";
+
+ async function redirect_if_necessary(ticket: Navigation) {
+ const sessionIsValid = await is_active();
+ const isPublicRoute = ticket.to?.routeId?.startsWith("(public)");
+ if (sessionIsValid && isPublicRoute) {
+ await goto("/home");
+ } else if (!sessionIsValid && !isPublicRoute) {
+ await goto("/login");
+ }
+ }
+
+ // This should probably be removed in favor of the logic in layout.server.ts.
+ // That requires a more sophisticated server side implementation of session handling,
+ // and i don't want that tbh, i want to stay as much in the browser as possible.
+ afterNavigate(redirect_if_necessary);
+ beforeNavigate(redirect_if_necessary);
+</script>
+<slot></slot> \ No newline at end of file
diff --git a/apps/kit/static/preload.js b/apps/kit/static/preload.js
new file mode 100644
index 0000000..379902f
--- /dev/null
+++ b/apps/kit/static/preload.js
@@ -0,0 +1,13 @@
+const value = `; ${document.cookie}`;
+const parts = value.split(`; go_theme=`);
+let currentTheme = "system";
+if (parts.length === 2) {
+ currentTheme = parts.pop().split(";").shift();
+}
+if (currentTheme === "light") {
+ document.querySelector("html").dataset.theme = "light";
+} else if (currentTheme === "dark") {
+ document.querySelector("html").dataset.theme = "dark";
+} else {
+ document.querySelector("html").dataset.theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
+} \ No newline at end of file
diff --git a/apps/kit/svelte.config.js b/apps/kit/svelte.config.js
new file mode 100644
index 0000000..2c7fdde
--- /dev/null
+++ b/apps/kit/svelte.config.js
@@ -0,0 +1,16 @@
+import adapter from "svelte-adapter-bun";
+import preprocess from "svelte-preprocess";
+
+/** @type {import('@sveltejs/kit').Config} */
+const config = {
+ preprocess: [
+ preprocess({
+ postcss: true,
+ }),
+ ],
+ kit: {
+ adapter: adapter(),
+ },
+};
+
+export default config;
diff --git a/apps/kit/tailwind.config.cjs b/apps/kit/tailwind.config.cjs
new file mode 100644
index 0000000..a81035a
--- /dev/null
+++ b/apps/kit/tailwind.config.cjs
@@ -0,0 +1,13 @@
+const config = {
+ content: ["./src/**/*.{html,js,svelte,ts}"],
+
+ theme: {
+ extend: {},
+ },
+
+ plugins: [
+ require("@tailwindcss/forms")
+ ],
+};
+
+module.exports = config;
diff --git a/apps/kit/tests/test.ts b/apps/kit/tests/test.ts
new file mode 100644
index 0000000..4e57937
--- /dev/null
+++ b/apps/kit/tests/test.ts
@@ -0,0 +1,6 @@
+import { expect, test } from '@playwright/test';
+
+test('index page has expected h1', async ({ page }) => {
+ await page.goto('/');
+ expect(await page.textContent('h1')).toBe('Welcome to SvelteKit');
+});
diff --git a/apps/kit/tsconfig.json b/apps/kit/tsconfig.json
new file mode 100644
index 0000000..ed68025
--- /dev/null
+++ b/apps/kit/tsconfig.json
@@ -0,0 +1,35 @@
+{
+ "extends": "./.svelte-kit/tsconfig.json",
+ "compilerOptions": {
+ "allowJs": true,
+ "checkJs": true,
+ "esModuleInterop": true,
+ "forceConsistentCasingInFileNames": true,
+ "resolveJsonModule": true,
+ "skipLibCheck": true,
+ "sourceMap": true,
+ "strict": true,
+ "lib": [
+ "es2021",
+ "dom",
+ "es6"
+ ],
+ "paths": {
+ "$lib/*": [
+ "./src/lib/*"
+ ],
+ },
+ "types": [
+ "bun-types"
+ ]
+ },
+ "include": [
+ "./**/*.d.ts",
+ "./**/*.ts",
+ "./**/*.js",
+ "./**/*.svelte"
+ ],
+ "exclude": [
+ "./node_modules"
+ ]
+} \ No newline at end of file
diff --git a/apps/kit/vite.config.js b/apps/kit/vite.config.js
new file mode 100644
index 0000000..f777f75
--- /dev/null
+++ b/apps/kit/vite.config.js
@@ -0,0 +1,14 @@
+import { sveltekit } from '@sveltejs/kit/vite';
+
+/** @type {import('vite').UserConfig} */
+const config = {
+ plugins: [sveltekit()],
+ build: { target: "es2020" },
+ optimizeDeps: {
+ esbuildOptions: {
+ target: "es2020"
+ }
+ }
+};
+
+export default config;