start user page

This commit is contained in:
Sam 2023-03-11 01:36:30 +01:00
parent 90205a1243
commit 27cec4e77e
Signed by: sam
GPG key ID: B4EF20DDE721CAA1
17 changed files with 401 additions and 82 deletions

View file

@ -16,6 +16,8 @@
"@sveltejs/kit": "^1.5.0", "@sveltejs/kit": "^1.5.0",
"@tailwindcss/forms": "^0.5.3", "@tailwindcss/forms": "^0.5.3",
"@tailwindcss/typography": "^0.5.9", "@tailwindcss/typography": "^0.5.9",
"@types/marked": "^4.0.8",
"@types/sanitize-html": "^2.8.1",
"@typescript-eslint/eslint-plugin": "^5.45.0", "@typescript-eslint/eslint-plugin": "^5.45.0",
"@typescript-eslint/parser": "^5.45.0", "@typescript-eslint/parser": "^5.45.0",
"autoprefixer": "^10.4.13", "autoprefixer": "^10.4.13",
@ -34,7 +36,11 @@
}, },
"type": "module", "type": "module",
"dependencies": { "dependencies": {
"@steeze-ui/heroicons": "^2.2.2", "@popperjs/core": "^2.11.6",
"@steeze-ui/svelte-icon": "^1.3.2" "bootstrap": "5.3.0-alpha1",
"bootstrap-icons": "^1.10.3",
"marked": "^4.2.12",
"sanitize-html": "^2.10.0",
"sveltestrap": "^5.10.0"
} }
} }

View file

@ -1,37 +1,49 @@
lockfileVersion: 5.4 lockfileVersion: 5.4
specifiers: specifiers:
'@steeze-ui/heroicons': ^2.2.2 '@popperjs/core': ^2.11.6
'@steeze-ui/svelte-icon': ^1.3.2
'@sveltejs/adapter-auto': ^2.0.0 '@sveltejs/adapter-auto': ^2.0.0
'@sveltejs/kit': ^1.5.0 '@sveltejs/kit': ^1.5.0
'@tailwindcss/forms': ^0.5.3 '@tailwindcss/forms': ^0.5.3
'@tailwindcss/typography': ^0.5.9 '@tailwindcss/typography': ^0.5.9
'@types/marked': ^4.0.8
'@types/sanitize-html': ^2.8.1
'@typescript-eslint/eslint-plugin': ^5.45.0 '@typescript-eslint/eslint-plugin': ^5.45.0
'@typescript-eslint/parser': ^5.45.0 '@typescript-eslint/parser': ^5.45.0
autoprefixer: ^10.4.13 autoprefixer: ^10.4.13
bootstrap: 5.3.0-alpha1
bootstrap-icons: ^1.10.3
eslint: ^8.28.0 eslint: ^8.28.0
eslint-config-prettier: ^8.5.0 eslint-config-prettier: ^8.5.0
eslint-plugin-svelte3: ^4.0.0 eslint-plugin-svelte3: ^4.0.0
marked: ^4.2.12
postcss: ^8.4.21 postcss: ^8.4.21
prettier: ^2.8.0 prettier: ^2.8.0
prettier-plugin-svelte: ^2.8.1 prettier-plugin-svelte: ^2.8.1
sanitize-html: ^2.10.0
svelte: ^3.54.0 svelte: ^3.54.0
svelte-check: ^3.0.1 svelte-check: ^3.0.1
sveltestrap: ^5.10.0
tailwindcss: ^3.2.7 tailwindcss: ^3.2.7
tslib: ^2.4.1 tslib: ^2.4.1
typescript: ^4.9.3 typescript: ^4.9.3
vite: ^4.0.0 vite: ^4.0.0
dependencies: dependencies:
'@steeze-ui/heroicons': 2.2.2 '@popperjs/core': 2.11.6
'@steeze-ui/svelte-icon': 1.3.2 bootstrap: 5.3.0-alpha1_@popperjs+core@2.11.6
bootstrap-icons: 1.10.3
marked: 4.2.12
sanitize-html: 2.10.0
sveltestrap: 5.10.0_svelte@3.55.1
devDependencies: devDependencies:
'@sveltejs/adapter-auto': 2.0.0_@sveltejs+kit@1.11.0 '@sveltejs/adapter-auto': 2.0.0_@sveltejs+kit@1.11.0
'@sveltejs/kit': 1.11.0_svelte@3.55.1+vite@4.1.4 '@sveltejs/kit': 1.11.0_svelte@3.55.1+vite@4.1.4
'@tailwindcss/forms': 0.5.3_tailwindcss@3.2.7 '@tailwindcss/forms': 0.5.3_tailwindcss@3.2.7
'@tailwindcss/typography': 0.5.9_tailwindcss@3.2.7 '@tailwindcss/typography': 0.5.9_tailwindcss@3.2.7
'@types/marked': 4.0.8
'@types/sanitize-html': 2.8.1
'@typescript-eslint/eslint-plugin': 5.54.1_mlk7dnz565t663n4razh6a6v6i '@typescript-eslint/eslint-plugin': 5.54.1_mlk7dnz565t663n4razh6a6v6i
'@typescript-eslint/parser': 5.54.1_ycpbpc6yetojsgtrx3mwntkhsu '@typescript-eslint/parser': 5.54.1_ycpbpc6yetojsgtrx3mwntkhsu
autoprefixer: 10.4.13_postcss@8.4.21 autoprefixer: 10.4.13_postcss@8.4.21
@ -331,12 +343,8 @@ packages:
resolution: {integrity: sha512-a5Sab1C4/icpTZVzZc5Ghpz88yQtGOyNqYXcZgOssB2uuAr+wF/MvN6bgtW32q7HHrvBki+BsZ0OuNv6EV3K9g==} resolution: {integrity: sha512-a5Sab1C4/icpTZVzZc5Ghpz88yQtGOyNqYXcZgOssB2uuAr+wF/MvN6bgtW32q7HHrvBki+BsZ0OuNv6EV3K9g==}
dev: true dev: true
/@steeze-ui/heroicons/2.2.2: /@popperjs/core/2.11.6:
resolution: {integrity: sha512-VUkgSAbjLzsEuF4kDQDESANb/NNxqhkOMnrzD1VhB3tSGr7wVZ5FKS7aM2QZcKXjziwR6dbt7Nhs3fpZhqOi1A==} resolution: {integrity: sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw==}
dev: false
/@steeze-ui/svelte-icon/1.3.2:
resolution: {integrity: sha512-Y7tH9UKeDYLfBicWLw4n4mhgfuSwgEdtq9wog7H+DjvfpTvea5SEqjHI8onoKZonJqAaUBGVjH02LAiqRWu+cw==}
dev: false dev: false
/@sveltejs/adapter-auto/2.0.0_@sveltejs+kit@1.11.0: /@sveltejs/adapter-auto/2.0.0_@sveltejs+kit@1.11.0:
@ -424,10 +432,20 @@ packages:
resolution: {integrity: sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==} resolution: {integrity: sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==}
dev: true dev: true
/@types/marked/4.0.8:
resolution: {integrity: sha512-HVNzMT5QlWCOdeuBsgXP8EZzKUf0+AXzN+sLmjvaB3ZlLqO+e4u0uXrdw9ub69wBKFs+c6/pA4r9sy6cCDvImw==}
dev: true
/@types/pug/2.0.6: /@types/pug/2.0.6:
resolution: {integrity: sha512-SnHmG9wN1UVmagJOnyo/qkk0Z7gejYxOYYmaAwr5u2yFYfsupN3sg10kyzN8Hep/2zbHxCnsumxOoRIRMBwKCg==} resolution: {integrity: sha512-SnHmG9wN1UVmagJOnyo/qkk0Z7gejYxOYYmaAwr5u2yFYfsupN3sg10kyzN8Hep/2zbHxCnsumxOoRIRMBwKCg==}
dev: true dev: true
/@types/sanitize-html/2.8.1:
resolution: {integrity: sha512-Q6kMAbBBaXA5IagoipeSr4Y/zuGyh4BZ5lewgb3cYe3OYqy0k/d67iMsC4O895eks676bVAe9G+0y1i0k2ZlnA==}
dependencies:
htmlparser2: 8.0.1
dev: true
/@types/sass/1.45.0: /@types/sass/1.45.0:
resolution: {integrity: sha512-jn7qwGFmJHwUSphV8zZneO3GmtlgLsmhs/LQyVvQbIIa+fzGMUiHI4HXJZL3FT8MJmgXWbLGiVVY7ElvHq6vDA==} resolution: {integrity: sha512-jn7qwGFmJHwUSphV8zZneO3GmtlgLsmhs/LQyVvQbIIa+fzGMUiHI4HXJZL3FT8MJmgXWbLGiVVY7ElvHq6vDA==}
deprecated: This is a stub types definition. sass provides its own type definitions, so you do not need this installed. deprecated: This is a stub types definition. sass provides its own type definitions, so you do not need this installed.
@ -669,6 +687,18 @@ packages:
engines: {node: '>=8'} engines: {node: '>=8'}
dev: true dev: true
/bootstrap-icons/1.10.3:
resolution: {integrity: sha512-7Qvj0j0idEm/DdX9Q0CpxAnJYqBCFCiUI6qzSPYfERMcokVuV9Mdm/AJiVZI8+Gawe4h/l6zFcOzvV7oXCZArw==}
dev: false
/bootstrap/5.3.0-alpha1_@popperjs+core@2.11.6:
resolution: {integrity: sha512-ABZpKK4ObS3kKlIqH+ZVDqoy5t/bhFG0oHTAzByUdon7YIom0lpCeTqRniDzJmbtcWkNe800VVPBiJgxSYTYew==}
peerDependencies:
'@popperjs/core': ^2.11.6
dependencies:
'@popperjs/core': 2.11.6
dev: false
/brace-expansion/1.1.11: /brace-expansion/1.1.11:
resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==}
dependencies: dependencies:
@ -796,7 +826,6 @@ packages:
/deepmerge/4.3.0: /deepmerge/4.3.0:
resolution: {integrity: sha512-z2wJZXrmeHdvYJp/Ux55wIjqo81G5Bp4c+oELTW+7ar6SogWHajt5a9gO3s3IDaGSAXjDk0vlQKN3rms8ab3og==} resolution: {integrity: sha512-z2wJZXrmeHdvYJp/Ux55wIjqo81G5Bp4c+oELTW+7ar6SogWHajt5a9gO3s3IDaGSAXjDk0vlQKN3rms8ab3og==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
dev: true
/defined/1.0.1: /defined/1.0.1:
resolution: {integrity: sha512-hsBd2qSVCRE+5PmNdHt1uzyrFu5d3RwmFDKzyNZMFq/EwDNJF7Ee5+D5oEKF0hU6LhtoUF1macFvOe4AskQC1Q==} resolution: {integrity: sha512-hsBd2qSVCRE+5PmNdHt1uzyrFu5d3RwmFDKzyNZMFq/EwDNJF7Ee5+D5oEKF0hU6LhtoUF1macFvOe4AskQC1Q==}
@ -843,10 +872,37 @@ packages:
esutils: 2.0.3 esutils: 2.0.3
dev: true dev: true
/dom-serializer/2.0.0:
resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==}
dependencies:
domelementtype: 2.3.0
domhandler: 5.0.3
entities: 4.4.0
/domelementtype/2.3.0:
resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==}
/domhandler/5.0.3:
resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==}
engines: {node: '>= 4'}
dependencies:
domelementtype: 2.3.0
/domutils/3.0.1:
resolution: {integrity: sha512-z08c1l761iKhDFtfXO04C7kTdPBLi41zwOZl00WS8b5eiaebNpY00HKbztwBq+e3vyqWNwWF3mP9YLUeqIrF+Q==}
dependencies:
dom-serializer: 2.0.0
domelementtype: 2.3.0
domhandler: 5.0.3
/electron-to-chromium/1.4.325: /electron-to-chromium/1.4.325:
resolution: {integrity: sha512-K1C03NT4I7BuzsRdCU5RWkgZxtswnKDYM6/eMhkEXqKu4e5T+ck610x3FPzu1y7HVFSiQKZqP16gnJzPpji1TQ==} resolution: {integrity: sha512-K1C03NT4I7BuzsRdCU5RWkgZxtswnKDYM6/eMhkEXqKu4e5T+ck610x3FPzu1y7HVFSiQKZqP16gnJzPpji1TQ==}
dev: true dev: true
/entities/4.4.0:
resolution: {integrity: sha512-oYp7156SP8LkeGD0GF85ad1X9Ai79WtRsZ2gxJqtBuzH+98YUV6jkHEKlZkMbcrjJjIVJNIDP/3WL9wQkoPbWA==}
engines: {node: '>=0.12'}
/es6-promise/3.3.1: /es6-promise/3.3.1:
resolution: {integrity: sha512-SOp9Phqvqn7jtEUxPWdWfWoLmyt2VaJ6MpvP9Comy1MceMXqE6bxvaTu4iaxpYYPzhny28Lc+M87/c2cPK6lDg==} resolution: {integrity: sha512-SOp9Phqvqn7jtEUxPWdWfWoLmyt2VaJ6MpvP9Comy1MceMXqE6bxvaTu4iaxpYYPzhny28Lc+M87/c2cPK6lDg==}
dev: true dev: true
@ -889,7 +945,6 @@ packages:
/escape-string-regexp/4.0.0: /escape-string-regexp/4.0.0:
resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==}
engines: {node: '>=10'} engines: {node: '>=10'}
dev: true
/eslint-config-prettier/8.7.0_eslint@8.35.0: /eslint-config-prettier/8.7.0_eslint@8.35.0:
resolution: {integrity: sha512-HHVXLSlVUhMSmyW4ZzEuvjpwqamgmlfkutD53cYXLikh4pt/modINRcCIApJ84czDxM4GZInwUrromsDdTImTA==} resolution: {integrity: sha512-HHVXLSlVUhMSmyW4ZzEuvjpwqamgmlfkutD53cYXLikh4pt/modINRcCIApJ84czDxM4GZInwUrromsDdTImTA==}
@ -1192,6 +1247,14 @@ packages:
function-bind: 1.1.1 function-bind: 1.1.1
dev: true dev: true
/htmlparser2/8.0.1:
resolution: {integrity: sha512-4lVbmc1diZC7GUJQtRQ5yBAeUCL1exyMwmForWkRLnwyzWBFxN633SALPMGYaWZvKe9j1pRZJpauvmxENSp/EA==}
dependencies:
domelementtype: 2.3.0
domhandler: 5.0.3
domutils: 3.0.1
entities: 4.4.0
/ignore/5.2.4: /ignore/5.2.4:
resolution: {integrity: sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==} resolution: {integrity: sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==}
engines: {node: '>= 4'} engines: {node: '>= 4'}
@ -1264,6 +1327,11 @@ packages:
engines: {node: '>=8'} engines: {node: '>=8'}
dev: true dev: true
/is-plain-object/5.0.0:
resolution: {integrity: sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==}
engines: {node: '>=0.10.0'}
dev: false
/isexe/2.0.0: /isexe/2.0.0:
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
dev: true dev: true
@ -1352,6 +1420,12 @@ packages:
'@jridgewell/sourcemap-codec': 1.4.14 '@jridgewell/sourcemap-codec': 1.4.14
dev: true dev: true
/marked/4.2.12:
resolution: {integrity: sha512-yr8hSKa3Fv4D3jdZmtMMPghgVt6TWbk86WQaWhDloQjRSQhMMYCAro7jP7VDJrjjdV8pxVxMssXS8B8Y5DZ5aw==}
engines: {node: '>= 12'}
hasBin: true
dev: false
/merge2/1.4.1: /merge2/1.4.1:
resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
engines: {node: '>= 8'} engines: {node: '>= 8'}
@ -1416,7 +1490,6 @@ packages:
resolution: {integrity: sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==} resolution: {integrity: sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==}
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
hasBin: true hasBin: true
dev: true
/natural-compare-lite/1.4.0: /natural-compare-lite/1.4.0:
resolution: {integrity: sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==} resolution: {integrity: sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==}
@ -1484,6 +1557,10 @@ packages:
callsites: 3.1.0 callsites: 3.1.0
dev: true dev: true
/parse-srcset/1.0.2:
resolution: {integrity: sha512-/2qh0lav6CmI15FzA3i/2Bzk2zCgQhGMkvhOhKNcBVQ1ldgpbfiNTVslmooUmWJcADi1f1kIeynbDRVzNlfR6Q==}
dev: false
/path-exists/4.0.0: /path-exists/4.0.0:
resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}
engines: {node: '>=8'} engines: {node: '>=8'}
@ -1510,7 +1587,6 @@ packages:
/picocolors/1.0.0: /picocolors/1.0.0:
resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==}
dev: true
/picomatch/2.3.1: /picomatch/2.3.1:
resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
@ -1598,7 +1674,6 @@ packages:
nanoid: 3.3.4 nanoid: 3.3.4
picocolors: 1.0.0 picocolors: 1.0.0
source-map-js: 1.0.2 source-map-js: 1.0.2
dev: true
/prelude-ls/1.2.1: /prelude-ls/1.2.1:
resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
@ -1716,6 +1791,17 @@ packages:
rimraf: 2.7.1 rimraf: 2.7.1
dev: true dev: true
/sanitize-html/2.10.0:
resolution: {integrity: sha512-JqdovUd81dG4k87vZt6uA6YhDfWkUGruUu/aPmXLxXi45gZExnt9Bnw/qeQU8oGf82vPyaE0vO4aH0PbobB9JQ==}
dependencies:
deepmerge: 4.3.0
escape-string-regexp: 4.0.0
htmlparser2: 8.0.1
is-plain-object: 5.0.0
parse-srcset: 1.0.2
postcss: 8.4.21
dev: false
/sass/1.58.3: /sass/1.58.3:
resolution: {integrity: sha512-Q7RaEtYf6BflYrQ+buPudKR26/lH+10EmO9bBqbmPh/KeLqv8bjpTNqxe71ocONqXq+jYiCbpPUmQMS+JJPk4A==} resolution: {integrity: sha512-Q7RaEtYf6BflYrQ+buPudKR26/lH+10EmO9bBqbmPh/KeLqv8bjpTNqxe71ocONqXq+jYiCbpPUmQMS+JJPk4A==}
engines: {node: '>=12.0.0'} engines: {node: '>=12.0.0'}
@ -1777,7 +1863,6 @@ packages:
/source-map-js/1.0.2: /source-map-js/1.0.2:
resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==} resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
dev: true
/streamsearch/1.1.0: /streamsearch/1.1.0:
resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==} resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==}
@ -1903,7 +1988,15 @@ packages:
/svelte/3.55.1: /svelte/3.55.1:
resolution: {integrity: sha512-S+87/P0Ve67HxKkEV23iCdAh/SX1xiSfjF1HOglno/YTbSTW7RniICMCofWGdJJbdjw3S+0PfFb1JtGfTXE0oQ==} resolution: {integrity: sha512-S+87/P0Ve67HxKkEV23iCdAh/SX1xiSfjF1HOglno/YTbSTW7RniICMCofWGdJJbdjw3S+0PfFb1JtGfTXE0oQ==}
engines: {node: '>= 8'} engines: {node: '>= 8'}
dev: true
/sveltestrap/5.10.0_svelte@3.55.1:
resolution: {integrity: sha512-k6Ob+6G2AMYvBidXHBKM9W28fJqFHbmosqCe/NC8pv6TV7K+v47Yw+zmnLWkjqCzzmjkSLkL48SrHZrlWc9mYQ==}
peerDependencies:
svelte: ^3.29.0
dependencies:
'@popperjs/core': 2.11.6
svelte: 3.55.1
dev: false
/tailwindcss/3.2.7_postcss@8.4.21: /tailwindcss/3.2.7_postcss@8.4.21:
resolution: {integrity: sha512-B6DLqJzc21x7wntlH/GsZwEXTBttVSl1FtCzC8WP4oBc/NKef7kaax5jeihkkCEWc831/5NDJ9gRNDK6NEioQQ==} resolution: {integrity: sha512-B6DLqJzc21x7wntlH/GsZwEXTBttVSl1FtCzC8WP4oBc/NKef7kaax5jeihkkCEWc831/5NDJ9gRNDK6NEioQQ==}

View file

@ -6,7 +6,7 @@
<meta name="viewport" content="width=device-width" /> <meta name="viewport" content="width=device-width" />
%sveltekit.head% %sveltekit.head%
</head> </head>
<body class="bg-white dark:bg-slate-800 text-black dark:text-white" data-sveltekit-preload-data="hover"> <body data-sveltekit-preload-data="hover">
<div style="display: contents">%sveltekit.body%</div> <div style="display: contents">%sveltekit.body%</div>
</body> </body>
</html> </html>

View file

@ -1,10 +1,15 @@
export interface User { export interface User {
id: string; id: string;
username: string; name: string;
display_name: string | null; display_name: string | null;
bio: string | null; bio: string | null;
avatar_urls: string[] | null; avatar_urls: string[] | null;
links: string[] | null; links: string[] | null;
names: FieldEntry[];
pronouns: Pronoun[];
members: PartialMember[];
fields: Field[];
} }
export interface MeUser extends User { export interface MeUser extends User {
@ -12,6 +17,38 @@ export interface MeUser extends User {
discord_username: string | null; discord_username: string | null;
} }
export interface Field {
name: string;
entries: FieldEntry[];
}
export interface FieldEntry {
value: string;
status: WordStatus;
}
export interface Pronoun {
pronouns: string;
display_text: string | null;
status: WordStatus;
}
export enum WordStatus {
Unknown = 0,
Favourite = 1,
Okay = 2,
Jokingly = 3,
FriendsOnly = 4,
Avoid = 5,
}
export interface PartialMember {
id: string;
name: string;
display_name: string | null;
avatar_urls: string[] | null;
}
export interface APIError { export interface APIError {
code: ErrorCode; code: ErrorCode;
message?: string; message?: string;

View file

@ -1,11 +1,13 @@
import type { APIError } from "./entities"; import type { APIError } from "./entities";
export async function fetchAPI<T>( export async function apiFetch<T>(
path: string, path: string,
{ method, body, token }: { method?: string; body?: any; token?: string }, { method, body, token }: { method?: string; body?: any; token?: string },
) { ) {
const resp = await fetch(`${process.env.ORIGIN}/api/v1${path}`, { const apiBase = typeof process !== "undefined" ? process.env.ORIGIN : "";
method,
const resp = await fetch(`${apiBase}/api/v1${path}`, {
method: method || "GET",
headers: { headers: {
...(token ? { Authorization: token } : {}), ...(token ? { Authorization: token } : {}),
"Content-Type": "application/json", "Content-Type": "application/json",
@ -17,3 +19,6 @@ export async function fetchAPI<T>(
if (resp.status < 200 || resp.status >= 300) throw data as APIError; if (resp.status < 200 || resp.status >= 300) throw data as APIError;
return data as T; return data as T;
} }
export const apiFetchClient = async <T>(path: string, method: string = "GET", body: any = null) =>
apiFetch<T>(path, { method, body, token: localStorage.getItem("pronouns-token") || undefined });

View file

@ -0,0 +1,18 @@
<script lang="ts">
import { Card, CardHeader, CardTitle, ListGroup, ListGroupItem } from "sveltestrap";
import type { Field } from "$lib/api/entities";
import StatusIcon from "./StatusIcon.svelte";
export let field: Field;
</script>
<div>
<h5>{field.name}</h5>
<ul class="list-unstyled">
{#each field.entries as entry}
<li><StatusIcon status={entry.status} /> {entry.value}</li>
{/each}
</ul>
</div>

View file

@ -0,0 +1,16 @@
<script lang="ts">
import type { Pronoun } from "$lib/api/entities";
export let pronouns: Pronoun;
let pronounText: string;
if (pronouns.display_text) {
pronounText = pronouns.display_text;
} else {
const split = pronouns.pronouns.split("/");
if (split.length < 2) pronounText = split.join("/");
else pronounText = split.slice(0, 2).join("/")
}
</script>
<a href="/pronouns/{pronouns.pronouns}">{pronounText}</a>

View file

@ -0,0 +1,52 @@
<script lang="ts">
import { Icon, Tooltip } from "sveltestrap";
import { WordStatus } from "$lib/api/entities";
export let status: WordStatus;
const iconFor = (wordStatus: WordStatus) => {
switch (wordStatus) {
case WordStatus.Favourite:
return "heart-fill";
case WordStatus.Okay:
return "hand-thumbs-up";
case WordStatus.Jokingly:
return "emoji-laughing";
case WordStatus.FriendsOnly:
return "people";
case WordStatus.Avoid:
return "hand-thumbs-down";
default:
return "hand-thumbs-up";
}
};
const textFor = (wordStatus: WordStatus) => {
switch (wordStatus) {
case WordStatus.Favourite:
return "Favourite";
case WordStatus.Okay:
return "Okay";
case WordStatus.Jokingly:
return "Jokingly";
case WordStatus.FriendsOnly:
return "Friends only";
case WordStatus.Avoid:
return "Avoid";
default:
return "Okay";
}
};
let statusIcon: string;
$: statusIcon = iconFor(status);
let statusText: string;
$: statusText = textFor(status);
let iconElement: HTMLElement;
</script>
<span bind:this={iconElement} tabindex={0}><Icon name={statusIcon} /></span>
<Tooltip target={iconElement} placement="top">{statusText}</Tooltip>

View file

@ -1,5 +1,6 @@
<script> <script>
import "../app.css"; import "bootstrap/dist/css/bootstrap.min.css";
import "bootstrap-icons/font/bootstrap-icons.css";
import Navigation from "./nav/Navigation.svelte"; import Navigation from "./nav/Navigation.svelte";
</script> </script>

View file

@ -1,2 +1,6 @@
<svelte:head>
<title>pronouns.cc</title>
</svelte:head>
<h1>Welcome to SvelteKit</h1> <h1>Welcome to SvelteKit</h1>
<p>Visit <a href="https://kit.svelte.dev">kit.svelte.dev</a> to read the documentation</p> <p>Visit <a href="https://kit.svelte.dev">kit.svelte.dev</a> to read the documentation</p>

View file

@ -0,0 +1,10 @@
import { apiFetch } from "$lib/api/fetch";
import type { User } from "$lib/api/entities";
export const load = async ({ params }) => {
const resp = await apiFetch<User>(`/users/${params.username}`, {
method: "GET",
});
return resp;
};

View file

@ -0,0 +1,84 @@
<script lang="ts">
import { marked } from "marked";
import sanitizeHtml from "sanitize-html";
import FieldCard from "$lib/components/FieldCard.svelte";
import type { PageData } from "./$types";
import StatusIcon from "$lib/components/StatusIcon.svelte";
import PronounLink from "$lib/components/PronounLink.svelte";
export let data: PageData;
let bio: string | null;
$: bio = data.bio ? sanitizeHtml(marked.parse(data.bio)) : null;
</script>
<svelte:head>
<title>@{data.name} - pronouns.cc</title>
</svelte:head>
<div class="container">
<div class="grid">
<div class="row">
{#if data.avatar_urls}
<div class="col-md" />
{/if}
<div class="col-md">
{#if data.display_name}
<h2>{data.display_name}</h2>
<h4>@{data.name}</h4>
{:else}
<h2>@{data.name}</h2>
{/if}
<hr />
{#if bio}
<p>{@html bio}</p>
{/if}
</div>
{#if data.links}
<div class="col-md">
<ul>
{#each data.links as link}
<li><a href={link}>{link}</a></li>
{/each}
</ul>
</div>
{/if}
</div>
<div class="row">
{#if data.names}
<div class="col-md">
<h4>Names</h4>
<ul class="list-unstyled">
{#each data.names as name}
<li><StatusIcon status={name.status} /> {name.value}</li>
{/each}
</ul>
</div>
{/if}
{#if data.pronouns}
<div class="col-md">
<h4>Pronouns</h4>
<ul class="list-unstyled">
{#each data.pronouns as pronouns}
<li>
<StatusIcon status={pronouns.status} />
<PronounLink pronouns={pronouns} />
</li>
{/each}
</ul>
</div>
{/if}
</div>
</div>
{#if data.fields}
<div class="row row-cols-1 row-cols-sm-2 row-cols-md-3">
{#each data.fields as field}
<div class="col">
<FieldCard {field} />
</div>
{/each}
</div>
{/if}
</div>

View file

@ -1,7 +1,7 @@
import { fetchAPI } from "$lib/api/fetch"; import { apiFetch } from "$lib/api/fetch";
export const load = async () => { export const load = async () => {
const resp = await fetchAPI<UrlsResponse>("/auth/urls", { const resp = await apiFetch<UrlsResponse>("/auth/urls", {
method: "POST", method: "POST",
body: { body: {
callback_domain: process.env.ORIGIN, callback_domain: process.env.ORIGIN,

View file

@ -7,6 +7,17 @@
<svelte:head> <svelte:head>
<title>Login - pronouns.cc</title> <title>Login - pronouns.cc</title>
</svelte:head> </svelte:head>
<div>
<a href={data.discord}>Login with Discord</a> <div class="container">
<h1>Log in or sign up</h1>
<div class="row">
<div class="col">
<p>
<a class="btn btn-primary" href={data.discord} role="button">Log in with Discord</a>
</p>
</div>
<div class="col">
</div>
</div>
</div> </div>

View file

View file

@ -2,10 +2,8 @@
import { onMount } from "svelte"; import { onMount } from "svelte";
import { browser } from "$app/environment"; import { browser } from "$app/environment";
import { Icon } from "@steeze-ui/svelte-icon"; import { Collapse,Icon, Nav, Navbar, NavbarBrand, NavbarToggler, NavItem, NavLink } from "sveltestrap";
import { Moon, Sun, EllipsisVertical } from "@steeze-ui/heroicons";
import NavItem from "./NavItem.svelte";
import Logo from "./Logo.svelte"; import Logo from "./Logo.svelte";
let darkTheme: boolean = false; let darkTheme: boolean = false;
@ -23,9 +21,9 @@
if (!browser) return; if (!browser) return;
if (isDark) { if (isDark) {
document.documentElement.classList.remove("dark"); document.documentElement.setAttribute("data-bs-theme", "dark");
} else { } else {
document.documentElement.classList.add("dark"); document.documentElement.setAttribute("data-bs-theme", "light");
} }
localStorage.setItem("theme", isDark ? "dark" : "light"); localStorage.setItem("theme", isDark ? "dark" : "light");
}; };
@ -33,53 +31,34 @@
const toggleTheme = () => { const toggleTheme = () => {
darkTheme = !darkTheme; darkTheme = !darkTheme;
}; };
const toggleMenu = () => {
showMenu = !showMenu;
};
</script> </script>
<div <Navbar
class="bg-white/75 dark:bg-slate-800/75 w-full backdrop-blur border-slate-200 dark:border-slate-700 border-b" color={darkTheme ? "dark" : "light"}
light={!darkTheme}
dark={darkTheme}
expand="lg"
class="mb-4"
> >
<div class="max-w-8xl mx-auto"> <NavbarBrand href="/"><Logo /></NavbarBrand>
<div class="py-4 mx-4"> <NavbarToggler on:click={toggleMenu} />
<div class="flex items-center"> <Collapse isOpen={showMenu} navbar expand="lg">
<a href="/"><Logo /></a> <Nav class="ms-auto" navbar>
<div class="ml-auto flex items-center"> <NavItem>
<nav class="hidden lg:flex"> <NavLink href="/login">Log in</NavLink>
<ul class="flex space-x-4 font-bold"> </NavItem>
<NavItem href="/login">Log in</NavItem> <NavItem>
</ul> <NavLink
</nav> on:click={() => toggleTheme()}
<div title={darkTheme ? "Switch to light mode" : "Switch to dark mode"}
class="flex border-l border-slate-200 ml-4 pl-4 lg:ml-6 lg:pl-6 lg:mr-2 dark:border-slate-700 space-x-2 lg:space-x-4" >
> <Icon name={darkTheme ? "sun" : "moon-stars"} height="24" />
<div {darkTheme ? "Light mode" : "Dark mode"}
class="cursor-pointer flex" </NavLink>
on:click={() => toggleTheme()} </NavItem>
on:keypress={() => toggleTheme()} </Nav>
title={darkTheme ? "Switch to light mode" : "Switch to dark mode"} </Collapse>
> </Navbar>
<Icon src={darkTheme ? Sun : Moon} class="hover:text-sky-400" size="32" />
</div>
<div
class="cursor-pointer flex lg:hidden"
on:click={() => (showMenu = !showMenu)}
on:keypress={() => (showMenu = !showMenu)}
title={showMenu ? "Hide menu" : "Show menu"}
>
<Icon src={EllipsisVertical} class="hover:text-sky-400" size="32" />
</div>
<!-- <div
onClick={() => setShowMenu(!showMenu)}
title="Show menu"
className="cursor-pointer flex lg:hidden"
>
<List
className="dark:hover:text-sky-400 hover:text-sky-500"
size={24}
/>
</div> -->
</div>
</div>
</div>
</div>
</div>
</div>

View file

@ -13,8 +13,11 @@ export default defineConfig({
"/media": { "/media": {
target: "http://localhost:9000", target: "http://localhost:9000",
changeOrigin: true, changeOrigin: true,
rewrite: (path) => path.replace(/^\/media/, "/pronouns.cc") rewrite: (path) => path.replace(/^\/media/, "/pronouns.cc"),
}, },
}, },
}, },
ssr: {
noExternal: ["@popperjs/core"],
},
}); });