feat: discord threads (#513)

This commit is contained in:
Bradley Schofield
2024-01-17 10:09:40 +00:00
committed by GitHub
parent 9dc4ef5659
commit bc888e3b18
61 changed files with 1852 additions and 343 deletions

6
.env.example Normal file
View File

@@ -0,0 +1,6 @@
PUBLIC_APPWRITE_PROJECT_INIT_ID=
PUBLIC_APPWRITE_PROJECT_ID=
PUBLIC_APPWRITE_DB_MAIN_ID=
PUBLIC_APPWRITE_COL_THREADS_ID=
PUBLIC_APPWRITE_COL_MESSAGES_ID=
PUBLIC_APPWRITE_FN_TLDR_ID=

View File

@@ -18,7 +18,7 @@ jobs:
uses: actions/checkout@v2
- name: Login to DockerHub
uses: docker/login-action@v1
uses: docker/login-action@v1
with:
registry: ghcr.io
username: ${{ github.actor }}
@@ -54,6 +54,12 @@ jobs:
git checkout ${{ env.TAG }}
rm -f .env
echo "PUBLIC_APPWRITE_PROJECT_INIT_ID=${{ env.PUBLIC_APPWRITE_PROJECT_INIT_ID }}" >> .env
echo "PUBLIC_APPWRITE_PROJECT_ID=${{ env.PUBLIC_APPWRITE_PROJECT_ID }}" >> .env
echo "PUBLIC_APPWRITE_DB_MAIN_ID=${{ env.PUBLIC_APPWRITE_DB_MAIN_ID }}" >> .env
echo "PUBLIC_APPWRITE_COL_THREADS_ID=${{ env.PUBLIC_APPWRITE_COL_THREADS_ID }}" >> .env
echo "PUBLIC_APPWRITE_COL_MESSAGES_ID=${{ env.PUBLIC_APPWRITE_COL_MESSAGES_ID }}" >> .env
echo "PUBLIC_APPWRITE_FN_TLDR_ID=${{ env.PUBLIC_APPWRITE_FN_TLDR_ID }}" >> .env
echo "_APP_VERSION=${{ env.TAG }}" >> .env
echo "_APP_DOMAIN=${{ secrets.PRD_APP_DOMAIN }}" >> .env
echo "_APP_SYSTEM_SECURITY_EMAIL_ADDRESS=${{ secrets.APP_SYSTEM_SECURITY_EMAIL_ADDRESS }}" >> .env

View File

@@ -19,7 +19,7 @@ jobs:
uses: actions/checkout@v2
- name: Login to DockerHub
uses: docker/login-action@v1
uses: docker/login-action@v1
with:
registry: ghcr.io
username: ${{ github.actor }}
@@ -55,6 +55,12 @@ jobs:
git checkout ${{ env.TAG }}
rm -f .env
echo "PUBLIC_APPWRITE_PROJECT_INIT_ID=${{ env.PUBLIC_APPWRITE_PROJECT_INIT_ID }}" >> .env
echo "PUBLIC_APPWRITE_PROJECT_ID=${{ env.PUBLIC_APPWRITE_PROJECT_ID }}" >> .env
echo "PUBLIC_APPWRITE_DB_MAIN_ID=${{ env.PUBLIC_APPWRITE_DB_MAIN_ID }}" >> .env
echo "PUBLIC_APPWRITE_COL_THREADS_ID=${{ env.PUBLIC_APPWRITE_COL_THREADS_ID }}" >> .env
echo "PUBLIC_APPWRITE_COL_MESSAGES_ID=${{ env.PUBLIC_APPWRITE_COL_MESSAGES_ID }}" >> .env
echo "PUBLIC_APPWRITE_FN_TLDR_ID=${{ env.PUBLIC_APPWRITE_FN_TLDR_ID }}" >> .env
echo "_APP_VERSION=${{ env.TAG }}" >> .env
echo "_APP_DOMAIN=${{ secrets.STG_APP_DOMAIN }}" >> .env
echo "_APP_SYSTEM_SECURITY_EMAIL_ADDRESS=${{ secrets.APP_SYSTEM_SECURITY_EMAIL_ADDRESS }}" >> .env

View File

@@ -37,4 +37,10 @@ jobs:
- name: Build Website
env:
NODE_OPTIONS: '--max_old_space_size=4096'
PUBLIC_APPWRITE_PROJECT_INIT_ID: ${{ secrets.PUBLIC_APPWRITE_PROJECT_INIT_ID }}
PUBLIC_APPWRITE_PROJECT_ID: ${{ secrets.PUBLIC_APPWRITE_PROJECT_ID }}
PUBLIC_APPWRITE_DB_MAIN_ID: ${{ secrets.PUBLIC_APPWRITE_DB_MAIN_ID }}
PUBLIC_APPWRITE_COL_THREADS_ID: ${{ secrets.PUBLIC_APPWRITE_COL_THREADS_ID }}
PUBLIC_APPWRITE_COL_MESSAGES_ID: ${{ secrets.PUBLIC_APPWRITE_COL_MESSAGES_ID }}
PUBLIC_APPWRITE_FN_TLDR_ID: ${{ secrets.PUBLIC_APPWRITE_FN_TLDR_ID }}
run: pnpm run build

4
.gitignore vendored
View File

@@ -6,7 +6,9 @@ node_modules
/package
.env
.env.*
.envrc
!.env.example
vite.config.js.timestamp-*
vite.config.ts.timestamp-*
package-lock.json
package-lock.json
.tool-versions

View File

@@ -60,6 +60,13 @@ services:
<<: *x-logging
networks:
- cloud
environment:
- PUBLIC_APPWRITE_PROJECT_INIT_ID
- PUBLIC_APPWRITE_PROJECT_ID
- PUBLIC_APPWRITE_DB_MAIN_ID
- PUBLIC_APPWRITE_COL_THREADS_ID
- PUBLIC_APPWRITE_COL_MESSAGES_ID
- PUBLIC_APPWRITE_FN_TLDR_ID
deploy:
<<: *x-update-config
mode: replicated
@@ -68,7 +75,7 @@ services:
max_replicas_per_node: 2
constraints:
- node.role == worker
preferences:
preferences:
- spread: node.role == worker
labels:
- traefik.enable=true
@@ -94,7 +101,7 @@ services:
environment:
- TIME_BETWEEN_RUNS=3600
- UNUSED_TIME=6h
sematext-agent:
image: sematext/agent:latest
environment:

View File

@@ -61,6 +61,13 @@ services:
<<: *x-logging
networks:
- cloud
environment:
- PUBLIC_APPWRITE_PROJECT_INIT_ID
- PUBLIC_APPWRITE_PROJECT_ID
- PUBLIC_APPWRITE_DB_MAIN_ID
- PUBLIC_APPWRITE_COL_THREADS_ID
- PUBLIC_APPWRITE_COL_MESSAGES_ID
- PUBLIC_APPWRITE_FN_TLDR_ID
deploy:
<<: *x-update-config
mode: replicated
@@ -69,7 +76,7 @@ services:
max_replicas_per_node: 2
constraints:
- node.role == worker
preferences:
preferences:
- spread: node.role == worker
labels:
- traefik.enable=true
@@ -96,7 +103,7 @@ services:
- RUN_ON_STARTUP=true
volumes:
- /var/run/docker.sock:/var/run/docker.sock
sematext-agent:
image: sematext/agent:latest
environment:

View File

@@ -20,18 +20,19 @@
},
"devDependencies": {
"@melt-ui/pp": "^0.1.4",
"@melt-ui/svelte": "^0.60.2",
"@melt-ui/svelte": "^0.65.0",
"@playwright/test": "^1.40.0",
"@sveltejs/adapter-node": "^1.3.1",
"@sveltejs/adapter-static": "^2.0.3",
"@sveltejs/enhanced-img": "^0.1.2",
"@sveltejs/kit": "^1.27.6",
"@sveltejs/kit": "^1.27.7",
"@types/compression": "^1.7.5",
"@types/glob": "^8.1.0",
"@types/markdown-it": "^13.0.7",
"@types/morgan": "^1.9.9",
"@typescript-eslint/eslint-plugin": "^5.62.0",
"@typescript-eslint/parser": "^5.62.0",
"appwrite": "^13.0.1",
"eslint": "^8.54.0",
"eslint-config-prettier": "^8.10.0",
"eslint-plugin-svelte": "^2.35.1",
@@ -40,11 +41,12 @@
"oslllo-svg-fixer": "^3.0.0",
"prettier": "^2.8.8",
"prettier-plugin-svelte": "^2.10.1",
"sass": "^1.69.5",
"sass": "^1.69.7",
"svelte": "^4.2.7",
"svelte-check": "^3.6.0",
"svelte-markdoc-preprocess": "^1.1.3",
"svelte-sequential-preprocessor": "^2.0.1",
"sveltekit-search-params": "^1.0.16",
"svgo": "^3.0.4",
"svgtofont": "^4.0.0",
"tslib": "^2.6.2",
@@ -66,6 +68,7 @@
"markdown-it": "^13.0.2",
"meilisearch": "^0.35.1",
"motion": "^10.16.4",
"sharp": "^0.32.6"
"sharp": "^0.32.6",
"svelte-markdown": "^0.4.0"
}
}

216
pnpm-lock.yaml generated
View File

@@ -38,29 +38,32 @@ dependencies:
sharp:
specifier: ^0.32.6
version: 0.32.6
svelte-markdown:
specifier: ^0.4.0
version: 0.4.1(svelte@4.2.7)
devDependencies:
'@melt-ui/pp':
specifier: ^0.1.4
version: 0.1.4(@melt-ui/svelte@0.60.2)(svelte@4.2.7)
version: 0.1.4(@melt-ui/svelte@0.65.2)(svelte@4.2.7)
'@melt-ui/svelte':
specifier: ^0.60.2
version: 0.60.2(svelte@4.2.7)
specifier: ^0.65.0
version: 0.65.2(svelte@4.2.7)
'@playwright/test':
specifier: ^1.40.0
version: 1.40.0
'@sveltejs/adapter-node':
specifier: ^1.3.1
version: 1.3.1(@sveltejs/kit@1.27.6)
version: 1.3.1(@sveltejs/kit@1.30.3)
'@sveltejs/adapter-static':
specifier: ^2.0.3
version: 2.0.3(@sveltejs/kit@1.27.6)
version: 2.0.3(@sveltejs/kit@1.30.3)
'@sveltejs/enhanced-img':
specifier: ^0.1.2
version: 0.1.5(svelte@4.2.7)
'@sveltejs/kit':
specifier: ^1.27.6
version: 1.27.6(svelte@4.2.7)(vite@4.5.1)
specifier: ^1.27.7
version: 1.30.3(svelte@4.2.7)(vite@4.5.1)
'@types/compression':
specifier: ^1.7.5
version: 1.7.5
@@ -79,6 +82,9 @@ devDependencies:
'@typescript-eslint/parser':
specifier: ^5.62.0
version: 5.62.0(eslint@8.54.0)(typescript@5.3.2)
appwrite:
specifier: ^13.0.1
version: 13.0.1
eslint:
specifier: ^8.54.0
version: 8.54.0
@@ -104,20 +110,23 @@ devDependencies:
specifier: ^2.10.1
version: 2.10.1(prettier@2.8.8)(svelte@4.2.7)
sass:
specifier: ^1.69.5
version: 1.69.5
specifier: ^1.69.7
version: 1.69.7
svelte:
specifier: ^4.2.7
version: 4.2.7
svelte-check:
specifier: ^3.6.0
version: 3.6.0(postcss@8.4.31)(sass@1.69.5)(svelte@4.2.7)
version: 3.6.0(postcss@8.4.31)(sass@1.69.7)(svelte@4.2.7)
svelte-markdoc-preprocess:
specifier: ^1.1.3
version: 1.1.3
svelte-sequential-preprocessor:
specifier: ^2.0.1
version: 2.0.1
sveltekit-search-params:
specifier: ^1.0.16
version: 1.1.1(@sveltejs/kit@1.30.3)(svelte@4.2.7)
svgo:
specifier: ^3.0.4
version: 3.0.4
@@ -132,7 +141,7 @@ devDependencies:
version: 5.3.2
vite:
specifier: ^4.5.1
version: 4.5.1(@types/node@20.9.3)(sass@1.69.5)
version: 4.5.1(@types/node@20.9.3)(sass@1.69.7)
vite-plugin-dynamic-import:
specifier: ^1.5.0
version: 1.5.0
@@ -141,7 +150,7 @@ devDependencies:
version: 1.1.7(vite@4.5.1)
vitest:
specifier: ^0.32.4
version: 0.32.4(sass@1.69.5)
version: 0.32.4(sass@1.69.7)
packages:
@@ -156,7 +165,6 @@ packages:
dependencies:
'@jridgewell/gen-mapping': 0.3.3
'@jridgewell/trace-mapping': 0.3.20
dev: true
/@appwrite.io/pink-icons@0.1.0-next.9:
resolution: {integrity: sha512-6t4Pqt/xugjpJQyaMx1u/7Gt9CkW5iItDAgUKcIMm84E4NbDJq8ZdAhhvctGQQppKUgHDPi+6x1XveUUd7tdbg==}
@@ -462,6 +470,12 @@ packages:
resolution: {integrity: sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==}
dev: true
/@internationalized/date@3.5.1:
resolution: {integrity: sha512-LUQIfwU9e+Fmutc/DpRTGXSdgYZLBegi4wygCWDSVmUdLTaMHsQyASDiJtREwanwKuQLq0hY76fCJ9J/9I2xOQ==}
dependencies:
'@swc/helpers': 0.5.3
dev: true
/@isaacs/cliui@8.0.2:
resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==}
engines: {node: '>=12'}
@@ -863,28 +877,23 @@ packages:
'@jridgewell/set-array': 1.1.2
'@jridgewell/sourcemap-codec': 1.4.15
'@jridgewell/trace-mapping': 0.3.20
dev: true
/@jridgewell/resolve-uri@3.1.1:
resolution: {integrity: sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==}
engines: {node: '>=6.0.0'}
dev: true
/@jridgewell/set-array@1.1.2:
resolution: {integrity: sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==}
engines: {node: '>=6.0.0'}
dev: true
/@jridgewell/sourcemap-codec@1.4.15:
resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==}
dev: true
/@jridgewell/trace-mapping@0.3.20:
resolution: {integrity: sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==}
dependencies:
'@jridgewell/resolve-uri': 3.1.1
'@jridgewell/sourcemap-codec': 1.4.15
dev: true
/@lit-labs/ssr-dom-shim@1.1.2:
resolution: {integrity: sha512-jnOD+/+dSrfTWYfSXBXlo5l5f0q1UuJo3tkbMDCYA2lKUYq79jaxqtGEvnRoh049nt1vdo1+45RinipU6FGY2g==}
@@ -911,25 +920,26 @@ packages:
'@types/markdown-it': 12.2.3
dev: true
/@melt-ui/pp@0.1.4(@melt-ui/svelte@0.60.2)(svelte@4.2.7):
/@melt-ui/pp@0.1.4(@melt-ui/svelte@0.65.2)(svelte@4.2.7):
resolution: {integrity: sha512-zR+Kl3CZJPJBHW8V7YcdQCMI/dVcnW9Ct3yGbVaIywYVStVRS7F9uEDOea3xLLT2WTGodQePzPlUn53yKFu87g==}
engines: {pnpm: '>=8.6.3'}
peerDependencies:
'@melt-ui/svelte': '>= 0.29.0'
svelte: ^3.55.0 || ^4.0.0 || ^5.0.0-next.1
dependencies:
'@melt-ui/svelte': 0.60.2(svelte@4.2.7)
'@melt-ui/svelte': 0.65.2(svelte@4.2.7)
estree-walker: 3.0.3
svelte: 4.2.7
dev: true
/@melt-ui/svelte@0.60.2(svelte@4.2.7):
resolution: {integrity: sha512-dBGW7dIDBMYchCH0Fx9byqrCYtpkXX1QUGXdv1e2aQE20bDCrQqwVSV0/1iWuJVYyFeo2xUdr0/LVZJ6lOw4mg==}
/@melt-ui/svelte@0.65.2(svelte@4.2.7):
resolution: {integrity: sha512-BpsSl9Bjp1++8U3+LaDOFUoX/PFQ9N7QWFhlFdUEZduhrbVyU70v9A459SKrQ+esFSjvh1AvqJYkMAUJXJlAmQ==}
peerDependencies:
svelte: '>=3 <5'
dependencies:
'@floating-ui/core': 1.5.0
'@floating-ui/dom': 1.5.3
'@internationalized/date': 3.5.1
dequal: 2.0.3
focus-trap: 7.5.4
nanoid: 4.0.2
@@ -1254,7 +1264,7 @@ packages:
lit: 2.8.0
dev: false
/@sveltejs/adapter-node@1.3.1(@sveltejs/kit@1.27.6):
/@sveltejs/adapter-node@1.3.1(@sveltejs/kit@1.30.3):
resolution: {integrity: sha512-A0VgRQDCDPzdLNoiAbcOxGw4zT1Mc+n1LwT1OmO350R7WxrEqdMUChPPOd1iMfIDWlP4ie6E2d/WQf5es2d4Zw==}
peerDependencies:
'@sveltejs/kit': ^1.0.0
@@ -1262,16 +1272,16 @@ packages:
'@rollup/plugin-commonjs': 25.0.7(rollup@3.29.4)
'@rollup/plugin-json': 6.0.1(rollup@3.29.4)
'@rollup/plugin-node-resolve': 15.2.3(rollup@3.29.4)
'@sveltejs/kit': 1.27.6(svelte@4.2.7)(vite@4.5.1)
'@sveltejs/kit': 1.30.3(svelte@4.2.7)(vite@4.5.1)
rollup: 3.29.4
dev: true
/@sveltejs/adapter-static@2.0.3(@sveltejs/kit@1.27.6):
/@sveltejs/adapter-static@2.0.3(@sveltejs/kit@1.30.3):
resolution: {integrity: sha512-VUqTfXsxYGugCpMqQv1U0LIdbR3S5nBkMMDmpjGVJyM6Q2jHVMFtdWJCkeHMySc6mZxJ+0eZK3T7IgmUCDrcUQ==}
peerDependencies:
'@sveltejs/kit': ^1.5.0
dependencies:
'@sveltejs/kit': 1.27.6(svelte@4.2.7)(vite@4.5.1)
'@sveltejs/kit': 1.30.3(svelte@4.2.7)(vite@4.5.1)
dev: true
/@sveltejs/enhanced-img@0.1.5(svelte@4.2.7):
@@ -1285,8 +1295,8 @@ packages:
- svelte
dev: true
/@sveltejs/kit@1.27.6(svelte@4.2.7)(vite@4.5.1):
resolution: {integrity: sha512-GsjTkMbKzXdbeRg0tk8S7HNShQ4879ftRr0ZHaZfjbig1xQwG57Bvcm9U9/mpLJtCapLbLWUnygKrgcLISLC8A==}
/@sveltejs/kit@1.30.3(svelte@4.2.7)(vite@4.5.1):
resolution: {integrity: sha512-0DzVXfU4h+tChFvoc8C61IqErCyskD4ydSIDjpKS2lYlEzIYrtYrY7juSqACFxqcvZAnOEXvSY+zZ8br0+ZMMg==}
engines: {node: ^16.14 || >=18}
hasBin: true
requiresBuild: true
@@ -1308,7 +1318,7 @@ packages:
svelte: 4.2.7
tiny-glob: 0.2.9
undici: 5.26.5
vite: 4.5.1(@types/node@20.9.3)(sass@1.69.5)
vite: 4.5.1(@types/node@20.9.3)(sass@1.69.7)
transitivePeerDependencies:
- supports-color
dev: true
@@ -1324,7 +1334,7 @@ packages:
'@sveltejs/vite-plugin-svelte': 2.5.2(svelte@4.2.7)(vite@4.5.1)
debug: 4.3.4
svelte: 4.2.7
vite: 4.5.1(@types/node@20.9.3)(sass@1.69.5)
vite: 4.5.1(@types/node@20.9.3)(sass@1.69.7)
transitivePeerDependencies:
- supports-color
dev: true
@@ -1343,12 +1353,18 @@ packages:
magic-string: 0.30.5
svelte: 4.2.7
svelte-hmr: 0.15.3(svelte@4.2.7)
vite: 4.5.1(@types/node@20.9.3)(sass@1.69.5)
vite: 4.5.1(@types/node@20.9.3)(sass@1.69.7)
vitefu: 0.2.5(vite@4.5.1)
transitivePeerDependencies:
- supports-color
dev: true
/@swc/helpers@0.5.3:
resolution: {integrity: sha512-FaruWX6KdudYloq1AHD/4nU+UsMTdNE8CKyrseXWEcgjDAbvkwJg2QGPAnfIJLIWsjZOSPLOAykK6fuYp4vp4A==}
dependencies:
tslib: 2.6.2
dev: true
/@tokenizer/token@0.3.0:
resolution: {integrity: sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==}
dev: true
@@ -1408,7 +1424,6 @@ packages:
/@types/estree@1.0.5:
resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==}
dev: true
/@types/express-serve-static-core@4.17.41:
resolution: {integrity: sha512-OaJ7XLaelTgrvlZD8/aa0vvvxZdUmlCn6MtWeB7TkiKW70BQLc9XEPpDLPdbo52ZhXUCrznlWdCHWxJWtdyajA==}
@@ -1476,6 +1491,10 @@ packages:
'@types/mdurl': 1.0.5
dev: true
/@types/marked@5.0.2:
resolution: {integrity: sha512-OucS4KMHhFzhz27KxmWg7J+kIYqyqoW5kdIEI319hqARQQUTqhao3M/F+uFnDXD0Rg72iDDZxZNxq5gvctmLlg==}
dev: false
/@types/mdurl@1.0.5:
resolution: {integrity: sha512-6L6VymKTzYSrEf4Nev4Xa1LCHKrlTlYCBMTlQKFuddo1CvQcE52I0mwfOJayueUC7MJuXOeHTcIU683lzd0cUA==}
dev: true
@@ -1781,7 +1800,6 @@ packages:
resolution: {integrity: sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==}
engines: {node: '>=0.4.0'}
hasBin: true
dev: true
/agent-base@6.0.2:
resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==}
@@ -1864,6 +1882,15 @@ packages:
picomatch: 2.3.1
dev: true
/appwrite@13.0.1:
resolution: {integrity: sha512-kdOLB5Qbr2beQW72diA/dx8L16LywHcQV1H6oqgGtf64Mo6LsvyIM1hEVxWmFLwAXMaOtsqb7Mcs4+oQHo+WmQ==}
dependencies:
cross-fetch: 3.1.5
isomorphic-form-data: 2.0.0
transitivePeerDependencies:
- encoding
dev: true
/aproba@2.0.0:
resolution: {integrity: sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==}
dev: true
@@ -1883,7 +1910,6 @@ packages:
resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==}
dependencies:
dequal: 2.0.3
dev: true
/arr-diff@4.0.0:
resolution: {integrity: sha512-YVIQ82gZPGBebQV/a8dar4AitzCQs0jjXwMPZllpXMaGjXPYVUawSxQrRsjhjupyVxEvbHgUmIhKVlND+j02kA==}
@@ -1927,6 +1953,10 @@ packages:
resolution: {integrity: sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==}
dev: true
/asynckit@0.4.0:
resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
dev: true
/atob@2.1.2:
resolution: {integrity: sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==}
engines: {node: '>= 4.5.0'}
@@ -1950,7 +1980,6 @@ packages:
resolution: {integrity: sha512-jsyHu61e6N4Vbz/v18DHwWYKK0bSWLqn47eeDSKPB7m8tqMHF9YJ+mhIk2lVteyZrY8tnSj/jHOv4YiTCuCJgg==}
dependencies:
dequal: 2.0.3
dev: true
/b4a@1.6.4:
resolution: {integrity: sha512-fpWrvyVHEKyeEvbKZTVOeZF3VSKKWtJxFIxX/jaVPf+cLbGUSitjb49pHLqPV2BUNNZ0LcoeEGfE/YCpyDYHIw==}
@@ -2276,7 +2305,6 @@ packages:
acorn: 8.11.2
estree-walker: 3.0.3
periscopic: 3.1.0
dev: true
/collection-visit@1.0.0:
resolution: {integrity: sha512-lNkKvzEeMBBjUGHZ+q6z9pSJla0KWAQPvtzhEV9+iGyQYG+pBpl7xKDhxoNSOZH2hhv0v5k0y2yAM4o4SjoSkw==}
@@ -2318,6 +2346,13 @@ packages:
hasBin: true
dev: true
/combined-stream@1.0.8:
resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}
engines: {node: '>= 0.8'}
dependencies:
delayed-stream: 1.0.0
dev: true
/commander@4.1.1:
resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==}
engines: {node: '>= 6'}
@@ -2416,6 +2451,14 @@ packages:
resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==}
dev: true
/cross-fetch@3.1.5:
resolution: {integrity: sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==}
dependencies:
node-fetch: 2.6.7
transitivePeerDependencies:
- encoding
dev: true
/cross-fetch@3.1.8:
resolution: {integrity: sha512-cvA+JwZoU0Xq+h6WkMvAUqPEYy92Obet6UdKLfW60qn99ftItKjB5T+BkyWOFWe2pUyfQ+IJHmpOTznqk1M6Kg==}
dependencies:
@@ -2457,7 +2500,6 @@ packages:
dependencies:
mdn-data: 2.0.30
source-map-js: 1.0.2
dev: true
/css-what@6.1.0:
resolution: {integrity: sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==}
@@ -2579,6 +2621,11 @@ packages:
slash: 3.0.0
dev: true
/delayed-stream@1.0.0:
resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
engines: {node: '>=0.4.0'}
dev: true
/delegates@1.0.0:
resolution: {integrity: sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==}
dev: true
@@ -2591,7 +2638,6 @@ packages:
/dequal@2.0.3:
resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==}
engines: {node: '>=6'}
dev: true
/destroy@1.2.0:
resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==}
@@ -2938,7 +2984,6 @@ packages:
resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==}
dependencies:
'@types/estree': 1.0.5
dev: true
/esutils@2.0.3:
resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==}
@@ -3177,6 +3222,15 @@ packages:
signal-exit: 4.1.0
dev: true
/form-data@2.5.1:
resolution: {integrity: sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==}
engines: {node: '>= 0.12'}
dependencies:
asynckit: 0.4.0
combined-stream: 1.0.8
mime-types: 2.1.35
dev: true
/forwarded@0.2.0:
resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==}
engines: {node: '>= 0.6'}
@@ -3759,7 +3813,6 @@ packages:
resolution: {integrity: sha512-v3rht/LgVcsdZa3O2Nqs+NMowLOxeOm7Ay9+/ARQ2F+qEoANRcqrjAZKGN0v8ymUetZGgkp26LTnGT7H0Qo9Pg==}
dependencies:
'@types/estree': 1.0.5
dev: true
/is-windows@1.0.2:
resolution: {integrity: sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==}
@@ -3786,6 +3839,12 @@ packages:
engines: {node: '>=0.10.0'}
dev: true
/isomorphic-form-data@2.0.0:
resolution: {integrity: sha512-TYgVnXWeESVmQSg4GLVbalmQ+B4NPi/H4eWxqALKj63KsUrcu301YDjBqaOw3h+cbak7Na4Xyps3BiptHtxTfg==}
dependencies:
form-data: 2.5.1
dev: true
/jackspeak@2.3.6:
resolution: {integrity: sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==}
engines: {node: '>=14'}
@@ -3965,7 +4024,6 @@ packages:
/locate-character@3.0.0:
resolution: {integrity: sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==}
dev: true
/locate-path@6.0.0:
resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==}
@@ -4020,7 +4078,6 @@ packages:
engines: {node: '>=12'}
dependencies:
'@jridgewell/sourcemap-codec': 1.4.15
dev: true
/make-fetch-happen@10.2.1:
resolution: {integrity: sha512-NgOPbRiaQM10DYXvN3/hhGVI2M5MtITFryzBGxHM5p4wnFxsVCbxkrBrDsk+EZ5OB4jEOT7AjDxtdF+KVEFT7w==}
@@ -4070,6 +4127,12 @@ packages:
uc.micro: 1.0.6
dev: false
/marked@5.1.2:
resolution: {integrity: sha512-ahRPGXJpjMjwSOlBoTMZAK7ATXkli5qCPxZ21TG44rx1KEo44bii4ekgTDQPNRQ4Kh7JMb9Ub1PVk1NxRSsorg==}
engines: {node: '>= 16'}
hasBin: true
dev: false
/maxstache-stream@1.0.4:
resolution: {integrity: sha512-v8qlfPN0pSp7bdSoLo1NTjG43GXGqk5W2NWFnOCq2GlmFFqebGzPCjLKSbShuqIOVorOtZSAy7O/S1OCCRONUw==}
dependencies:
@@ -4089,7 +4152,6 @@ packages:
/mdn-data@2.0.30:
resolution: {integrity: sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==}
dev: true
/mdurl@1.0.1:
resolution: {integrity: sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==}
@@ -4158,14 +4220,12 @@ packages:
/mime-db@1.52.0:
resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==}
engines: {node: '>= 0.6'}
dev: false
/mime-types@2.1.35:
resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
engines: {node: '>= 0.6'}
dependencies:
mime-db: 1.52.0
dev: false
/mime@1.6.0:
resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==}
@@ -4428,6 +4488,18 @@ packages:
/node-addon-api@6.1.0:
resolution: {integrity: sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==}
/node-fetch@2.6.7:
resolution: {integrity: sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==}
engines: {node: 4.x || >=6.0.0}
peerDependencies:
encoding: ^0.1.0
peerDependenciesMeta:
encoding:
optional: true
dependencies:
whatwg-url: 5.0.0
dev: true
/node-fetch@2.7.0:
resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==}
engines: {node: 4.x || >=6.0.0}
@@ -4748,7 +4820,6 @@ packages:
'@types/estree': 1.0.5
estree-walker: 3.0.3
is-reference: 3.0.2
dev: true
/phin@2.9.3:
resolution: {integrity: sha512-CzFr90qM24ju5f88quFC/6qohjC144rehe5n6DH900lgXmUe86+xCKc10ev56gRKC4/BkHUoG4uSiQgBiIXwDA==}
@@ -5182,8 +5253,8 @@ packages:
rimraf: 2.7.1
dev: true
/sass@1.69.5:
resolution: {integrity: sha512-qg2+UCJibLr2LCVOt3OlPhr/dqVHWOa9XtZf2OjbLs/T4VPSJ00udtgJxH3neXZm+QqX8B+3cU7RaLqp1iVfcQ==}
/sass@1.69.7:
resolution: {integrity: sha512-rzj2soDeZ8wtE2egyLXgOOHQvaC2iosZrkF6v3EUG+tBwEvhqUCzm0VP3k9gHF9LXbSrRhT5SksoI56Iw8NPnQ==}
engines: {node: '>=14.0.0'}
hasBin: true
dependencies:
@@ -5413,7 +5484,6 @@ packages:
/source-map-js@1.0.2:
resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==}
engines: {node: '>=0.10.0'}
dev: true
/source-map-resolve@0.5.3:
resolution: {integrity: sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==}
@@ -5582,7 +5652,7 @@ packages:
engines: {node: '>= 0.4'}
dev: true
/svelte-check@3.6.0(postcss@8.4.31)(sass@1.69.5)(svelte@4.2.7):
/svelte-check@3.6.0(postcss@8.4.31)(sass@1.69.7)(svelte@4.2.7):
resolution: {integrity: sha512-8VfqhfuRJ1sKW+o8isH2kPi0RhjXH1nNsIbCFGyoUHG+ZxVxHYRKcb+S8eaL/1tyj3VGvWYx3Y5+oCUsJgnzcw==}
hasBin: true
peerDependencies:
@@ -5595,7 +5665,7 @@ packages:
picocolors: 1.0.0
sade: 1.8.1
svelte: 4.2.7
svelte-preprocess: 5.1.0(postcss@8.4.31)(sass@1.69.5)(svelte@4.2.7)(typescript@5.3.2)
svelte-preprocess: 5.1.0(postcss@8.4.31)(sass@1.69.7)(svelte@4.2.7)(typescript@5.3.2)
typescript: 5.3.2
transitivePeerDependencies:
- '@babel/core'
@@ -5649,6 +5719,16 @@ packages:
- react
dev: true
/svelte-markdown@0.4.1(svelte@4.2.7):
resolution: {integrity: sha512-pOlLY6EruKJaWI9my/2bKX8PdTeP5CM0s4VMmwmC2prlOkjAf+AOmTM4wW/l19Y6WZ87YmP8+ZCJCCwBChWjYw==}
peerDependencies:
svelte: ^4.0.0
dependencies:
'@types/marked': 5.0.2
marked: 5.1.2
svelte: 4.2.7
dev: false
/svelte-parse-markup@0.1.2(svelte@4.2.7):
resolution: {integrity: sha512-DycY7DJr7VqofiJ63ut1/NEG92HrWWL56VWITn/cJCu+LlZhMoBkBXT4opUitPEEwbq1nMQbv4vTKUfbOqIW1g==}
peerDependencies:
@@ -5657,7 +5737,7 @@ packages:
svelte: 4.2.7
dev: true
/svelte-preprocess@5.1.0(postcss@8.4.31)(sass@1.69.5)(svelte@4.2.7)(typescript@5.3.2):
/svelte-preprocess@5.1.0(postcss@8.4.31)(sass@1.69.7)(svelte@4.2.7)(typescript@5.3.2):
resolution: {integrity: sha512-EkErPiDzHAc0k2MF5m6vBNmRUh338h2myhinUw/xaqsLs7/ZvsgREiLGj03VrSzbY/TB5ZXgBOsKraFee5yceA==}
engines: {node: '>= 14.10.0'}
requiresBuild: true
@@ -5699,7 +5779,7 @@ packages:
detect-indent: 6.1.0
magic-string: 0.27.0
postcss: 8.4.31
sass: 1.69.5
sass: 1.69.7
sorcery: 0.11.0
strip-indent: 3.0.0
svelte: 4.2.7
@@ -5731,6 +5811,15 @@ packages:
locate-character: 3.0.0
magic-string: 0.30.5
periscopic: 3.1.0
/sveltekit-search-params@1.1.1(@sveltejs/kit@1.30.3)(svelte@4.2.7):
resolution: {integrity: sha512-TVmCa50Cnyryt8UPeFZAE8gMsO80h2kXr531Um0VJaMKK24ZvdR4qbHWyLew9U4d5Flw1w3SbyHWTbLiUGPC7w==}
peerDependencies:
'@sveltejs/kit': ^1.0.0 || ^2.0.0
svelte: ^3.55.0 || ^4.0.0 || ^5.0.0
dependencies:
'@sveltejs/kit': 1.30.3(svelte@4.2.7)(vite@4.5.1)
svelte: 4.2.7
dev: true
/svg-pathdata@6.0.3:
@@ -5977,7 +6066,6 @@ packages:
/tr46@0.0.3:
resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==}
dev: false
/ts-interface-checker@0.1.13:
resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==}
@@ -6177,7 +6265,7 @@ packages:
- rollup
dev: true
/vite-node@0.32.4(@types/node@20.9.3)(sass@1.69.5):
/vite-node@0.32.4(@types/node@20.9.3)(sass@1.69.7):
resolution: {integrity: sha512-L2gIw+dCxO0LK14QnUMoqSYpa9XRGnTTTDjW2h19Mr+GR0EFj4vx52W41gFXfMLqpA00eK4ZjOVYo1Xk//LFEw==}
engines: {node: '>=v14.18.0'}
hasBin: true
@@ -6187,7 +6275,7 @@ packages:
mlly: 1.4.2
pathe: 1.1.1
picocolors: 1.0.0
vite: 4.5.1(@types/node@20.9.3)(sass@1.69.5)
vite: 4.5.1(@types/node@20.9.3)(sass@1.69.7)
transitivePeerDependencies:
- '@types/node'
- less
@@ -6216,10 +6304,10 @@ packages:
dependencies:
ansi-colors: 4.1.3
pathe: 1.1.1
vite: 4.5.1(@types/node@20.9.3)(sass@1.69.5)
vite: 4.5.1(@types/node@20.9.3)(sass@1.69.7)
dev: true
/vite@4.5.1(@types/node@20.9.3)(sass@1.69.5):
/vite@4.5.1(@types/node@20.9.3)(sass@1.69.7):
resolution: {integrity: sha512-AXXFaAJ8yebyqzoNB9fu2pHoo/nWX+xZlaRwoeYUxEqBO+Zj4msE5G+BhGBll9lYEKv9Hfks52PAF2X7qDYXQA==}
engines: {node: ^14.18.0 || >=16.0.0}
hasBin: true
@@ -6251,7 +6339,7 @@ packages:
esbuild: 0.18.20
postcss: 8.4.31
rollup: 3.29.4
sass: 1.69.5
sass: 1.69.7
optionalDependencies:
fsevents: 2.3.3
dev: true
@@ -6264,10 +6352,10 @@ packages:
vite:
optional: true
dependencies:
vite: 4.5.1(@types/node@20.9.3)(sass@1.69.5)
vite: 4.5.1(@types/node@20.9.3)(sass@1.69.7)
dev: true
/vitest@0.32.4(sass@1.69.5):
/vitest@0.32.4(sass@1.69.7):
resolution: {integrity: sha512-3czFm8RnrsWwIzVDu/Ca48Y/M+qh3vOnF16czJm98Q/AN1y3B6PBsyV8Re91Ty5s7txKNjEhpgtGPcfdbh2MZg==}
engines: {node: '>=v14.18.0'}
hasBin: true
@@ -6319,8 +6407,8 @@ packages:
strip-literal: 1.3.0
tinybench: 2.5.1
tinypool: 0.5.0
vite: 4.5.1(@types/node@20.9.3)(sass@1.69.5)
vite-node: 0.32.4(@types/node@20.9.3)(sass@1.69.5)
vite: 4.5.1(@types/node@20.9.3)(sass@1.69.7)
vite-node: 0.32.4(@types/node@20.9.3)(sass@1.69.7)
why-is-node-running: 2.2.2
transitivePeerDependencies:
- less
@@ -6334,14 +6422,12 @@ packages:
/webidl-conversions@3.0.1:
resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==}
dev: false
/whatwg-url@5.0.0:
resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==}
dependencies:
tr46: 0.0.3
webidl-conversions: 3.0.1
dev: false
/which@2.0.2:
resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}

View File

@@ -642,7 +642,7 @@
{
"link": "/docs/models/runtime",
"redirect": "/docs/references/cloud/models/runtime"
},
},
{
"link": "/docs/installation",
"redirect": "/docs/advanced/self-hosting"
@@ -659,5 +659,4 @@
"link": "/policy/privacy",
"redirect": "/privacy"
}
]

View File

@@ -0,0 +1 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M9.814 6.600 L 6.578 6.617 6.460 6.706 C 6.133 6.956,6.164 7.490,6.517 7.690 L 6.650 7.766 9.175 7.766 C 10.564 7.766,11.700 7.778,11.700 7.791 C 11.700 7.805,10.486 9.031,9.002 10.516 C 7.492 12.027,6.288 13.258,6.268 13.311 C 6.213 13.459,6.227 13.690,6.300 13.833 C 6.436 14.100,6.816 14.213,7.093 14.069 C 7.162 14.033,8.403 12.817,9.900 11.319 L 12.583 8.633 12.600 11.208 L 12.617 13.783 12.694 13.892 C 12.934 14.229,13.477 14.205,13.685 13.849 L 13.764 13.715 13.768 10.360 C 13.772 6.725,13.781 6.918,13.594 6.743 C 13.485 6.641,13.289 6.565,13.159 6.575 C 13.099 6.579,11.594 6.590,9.814 6.600 " stroke="none" fill-rule="evenodd" fill="black"></path></svg>

After

Width:  |  Height:  |  Size: 770 B

View File

@@ -0,0 +1 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M3.750 3.463 C 3.591 3.536,3.520 3.610,3.451 3.775 C 3.406 3.883,3.400 4.410,3.400 8.263 C 3.400 11.267,3.411 12.658,3.435 12.723 C 3.479 12.839,3.618 12.988,3.750 13.061 C 3.837 13.109,4.005 13.118,5.025 13.127 L 6.200 13.137 6.200 14.620 C 6.200 15.878,6.208 16.121,6.251 16.225 C 6.320 16.390,6.391 16.464,6.550 16.537 C 6.717 16.614,6.881 16.615,7.040 16.541 C 7.108 16.509,8.023 15.733,9.073 14.817 L 10.983 13.150 13.567 13.133 C 15.892 13.118,16.160 13.111,16.250 13.061 C 16.382 12.988,16.521 12.839,16.565 12.723 C 16.589 12.658,16.600 11.266,16.599 8.256 C 16.599 4.084,16.596 3.877,16.537 3.750 C 16.464 3.591,16.390 3.520,16.225 3.451 C 16.116 3.406,15.416 3.400,9.993 3.401 C 4.136 3.401,3.878 3.404,3.750 3.463 M15.400 8.267 L 15.400 11.933 13.015 11.933 C 11.032 11.933,10.615 11.941,10.540 11.980 C 10.490 12.006,9.767 12.621,8.933 13.347 L 7.417 14.667 7.400 13.492 C 7.389 12.687,7.371 12.296,7.345 12.250 C 7.295 12.165,7.138 12.019,7.044 11.971 C 6.994 11.945,6.596 11.933,5.786 11.933 L 4.600 11.933 4.600 8.267 L 4.600 4.600 10.000 4.600 L 15.400 4.600 15.400 8.267 " stroke="none" fill-rule="evenodd" fill="black"></path></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -17,26 +17,28 @@ $aw-icon-dark: "\ea10";
$aw-icon-discord: "\ea11";
$aw-icon-divider-vertical: "\ea12";
$aw-icon-download: "\ea13";
$aw-icon-github: "\ea14";
$aw-icon-google: "\ea15";
$aw-icon-hamburger-menu: "\ea16";
$aw-icon-light: "\ea17";
$aw-icon-linkedin: "\ea18";
$aw-icon-location: "\ea19";
$aw-icon-logout-left: "\ea1a";
$aw-icon-logout-right: "\ea1b";
$aw-icon-microsoft: "\ea1c";
$aw-icon-minus: "\ea1d";
$aw-icon-nuxt: "\ea1e";
$aw-icon-platform: "\ea1f";
$aw-icon-plus: "\ea20";
$aw-icon-product-hunt: "\ea21";
$aw-icon-refine: "\ea22";
$aw-icon-rest: "\ea23";
$aw-icon-search: "\ea24";
$aw-icon-star: "\ea25";
$aw-icon-system: "\ea26";
$aw-icon-twitter: "\ea27";
$aw-icon-vue: "\ea28";
$aw-icon-x: "\ea29";
$aw-icon-youtube: "\ea2a";
$aw-icon-ext-link: "\ea14";
$aw-icon-github: "\ea15";
$aw-icon-google: "\ea16";
$aw-icon-hamburger-menu: "\ea17";
$aw-icon-light: "\ea18";
$aw-icon-linkedin: "\ea19";
$aw-icon-location: "\ea1a";
$aw-icon-logout-left: "\ea1b";
$aw-icon-logout-right: "\ea1c";
$aw-icon-message: "\ea1d";
$aw-icon-microsoft: "\ea1e";
$aw-icon-minus: "\ea1f";
$aw-icon-nuxt: "\ea20";
$aw-icon-platform: "\ea21";
$aw-icon-plus: "\ea22";
$aw-icon-product-hunt: "\ea23";
$aw-icon-refine: "\ea24";
$aw-icon-rest: "\ea25";
$aw-icon-search: "\ea26";
$aw-icon-star: "\ea27";
$aw-icon-system: "\ea28";
$aw-icon-twitter: "\ea29";
$aw-icon-vue: "\ea2a";
$aw-icon-x: "\ea2b";
$aw-icon-youtube: "\ea2c";

View File

@@ -36,26 +36,28 @@
.aw-icon-discord:before { content: "\ea11"; }
.aw-icon-divider-vertical:before { content: "\ea12"; }
.aw-icon-download:before { content: "\ea13"; }
.aw-icon-github:before { content: "\ea14"; }
.aw-icon-google:before { content: "\ea15"; }
.aw-icon-hamburger-menu:before { content: "\ea16"; }
.aw-icon-light:before { content: "\ea17"; }
.aw-icon-linkedin:before { content: "\ea18"; }
.aw-icon-location:before { content: "\ea19"; }
.aw-icon-logout-left:before { content: "\ea1a"; }
.aw-icon-logout-right:before { content: "\ea1b"; }
.aw-icon-microsoft:before { content: "\ea1c"; }
.aw-icon-minus:before { content: "\ea1d"; }
.aw-icon-nuxt:before { content: "\ea1e"; }
.aw-icon-platform:before { content: "\ea1f"; }
.aw-icon-plus:before { content: "\ea20"; }
.aw-icon-product-hunt:before { content: "\ea21"; }
.aw-icon-refine:before { content: "\ea22"; }
.aw-icon-rest:before { content: "\ea23"; }
.aw-icon-search:before { content: "\ea24"; }
.aw-icon-star:before { content: "\ea25"; }
.aw-icon-system:before { content: "\ea26"; }
.aw-icon-twitter:before { content: "\ea27"; }
.aw-icon-vue:before { content: "\ea28"; }
.aw-icon-x:before { content: "\ea29"; }
.aw-icon-youtube:before { content: "\ea2a"; }
.aw-icon-ext-link:before { content: "\ea14"; }
.aw-icon-github:before { content: "\ea15"; }
.aw-icon-google:before { content: "\ea16"; }
.aw-icon-hamburger-menu:before { content: "\ea17"; }
.aw-icon-light:before { content: "\ea18"; }
.aw-icon-linkedin:before { content: "\ea19"; }
.aw-icon-location:before { content: "\ea1a"; }
.aw-icon-logout-left:before { content: "\ea1b"; }
.aw-icon-logout-right:before { content: "\ea1c"; }
.aw-icon-message:before { content: "\ea1d"; }
.aw-icon-microsoft:before { content: "\ea1e"; }
.aw-icon-minus:before { content: "\ea1f"; }
.aw-icon-nuxt:before { content: "\ea20"; }
.aw-icon-platform:before { content: "\ea21"; }
.aw-icon-plus:before { content: "\ea22"; }
.aw-icon-product-hunt:before { content: "\ea23"; }
.aw-icon-refine:before { content: "\ea24"; }
.aw-icon-rest:before { content: "\ea25"; }
.aw-icon-search:before { content: "\ea26"; }
.aw-icon-star:before { content: "\ea27"; }
.aw-icon-system:before { content: "\ea28"; }
.aw-icon-twitter:before { content: "\ea29"; }
.aw-icon-vue:before { content: "\ea2a"; }
.aw-icon-x:before { content: "\ea2b"; }
.aw-icon-youtube:before { content: "\ea2c"; }

Binary file not shown.

View File

@@ -35,29 +35,31 @@
.aw-icon-discord:before { content: "\ea11"; }
.aw-icon-divider-vertical:before { content: "\ea12"; }
.aw-icon-download:before { content: "\ea13"; }
.aw-icon-github:before { content: "\ea14"; }
.aw-icon-google:before { content: "\ea15"; }
.aw-icon-hamburger-menu:before { content: "\ea16"; }
.aw-icon-light:before { content: "\ea17"; }
.aw-icon-linkedin:before { content: "\ea18"; }
.aw-icon-location:before { content: "\ea19"; }
.aw-icon-logout-left:before { content: "\ea1a"; }
.aw-icon-logout-right:before { content: "\ea1b"; }
.aw-icon-microsoft:before { content: "\ea1c"; }
.aw-icon-minus:before { content: "\ea1d"; }
.aw-icon-nuxt:before { content: "\ea1e"; }
.aw-icon-platform:before { content: "\ea1f"; }
.aw-icon-plus:before { content: "\ea20"; }
.aw-icon-product-hunt:before { content: "\ea21"; }
.aw-icon-refine:before { content: "\ea22"; }
.aw-icon-rest:before { content: "\ea23"; }
.aw-icon-search:before { content: "\ea24"; }
.aw-icon-star:before { content: "\ea25"; }
.aw-icon-system:before { content: "\ea26"; }
.aw-icon-twitter:before { content: "\ea27"; }
.aw-icon-vue:before { content: "\ea28"; }
.aw-icon-x:before { content: "\ea29"; }
.aw-icon-youtube:before { content: "\ea2a"; }
.aw-icon-ext-link:before { content: "\ea14"; }
.aw-icon-github:before { content: "\ea15"; }
.aw-icon-google:before { content: "\ea16"; }
.aw-icon-hamburger-menu:before { content: "\ea17"; }
.aw-icon-light:before { content: "\ea18"; }
.aw-icon-linkedin:before { content: "\ea19"; }
.aw-icon-location:before { content: "\ea1a"; }
.aw-icon-logout-left:before { content: "\ea1b"; }
.aw-icon-logout-right:before { content: "\ea1c"; }
.aw-icon-message:before { content: "\ea1d"; }
.aw-icon-microsoft:before { content: "\ea1e"; }
.aw-icon-minus:before { content: "\ea1f"; }
.aw-icon-nuxt:before { content: "\ea20"; }
.aw-icon-platform:before { content: "\ea21"; }
.aw-icon-plus:before { content: "\ea22"; }
.aw-icon-product-hunt:before { content: "\ea23"; }
.aw-icon-refine:before { content: "\ea24"; }
.aw-icon-rest:before { content: "\ea25"; }
.aw-icon-search:before { content: "\ea26"; }
.aw-icon-star:before { content: "\ea27"; }
.aw-icon-system:before { content: "\ea28"; }
.aw-icon-twitter:before { content: "\ea29"; }
.aw-icon-vue:before { content: "\ea2a"; }
.aw-icon-x:before { content: "\ea2b"; }
.aw-icon-youtube:before { content: "\ea2c"; }
$aw-icon-apple: "\ea01";
$aw-icon-arrow-down: "\ea02";
@@ -78,26 +80,28 @@ $aw-icon-dark: "\ea10";
$aw-icon-discord: "\ea11";
$aw-icon-divider-vertical: "\ea12";
$aw-icon-download: "\ea13";
$aw-icon-github: "\ea14";
$aw-icon-google: "\ea15";
$aw-icon-hamburger-menu: "\ea16";
$aw-icon-light: "\ea17";
$aw-icon-linkedin: "\ea18";
$aw-icon-location: "\ea19";
$aw-icon-logout-left: "\ea1a";
$aw-icon-logout-right: "\ea1b";
$aw-icon-microsoft: "\ea1c";
$aw-icon-minus: "\ea1d";
$aw-icon-nuxt: "\ea1e";
$aw-icon-platform: "\ea1f";
$aw-icon-plus: "\ea20";
$aw-icon-product-hunt: "\ea21";
$aw-icon-refine: "\ea22";
$aw-icon-rest: "\ea23";
$aw-icon-search: "\ea24";
$aw-icon-star: "\ea25";
$aw-icon-system: "\ea26";
$aw-icon-twitter: "\ea27";
$aw-icon-vue: "\ea28";
$aw-icon-x: "\ea29";
$aw-icon-youtube: "\ea2a";
$aw-icon-ext-link: "\ea14";
$aw-icon-github: "\ea15";
$aw-icon-google: "\ea16";
$aw-icon-hamburger-menu: "\ea17";
$aw-icon-light: "\ea18";
$aw-icon-linkedin: "\ea19";
$aw-icon-location: "\ea1a";
$aw-icon-logout-left: "\ea1b";
$aw-icon-logout-right: "\ea1c";
$aw-icon-message: "\ea1d";
$aw-icon-microsoft: "\ea1e";
$aw-icon-minus: "\ea1f";
$aw-icon-nuxt: "\ea20";
$aw-icon-platform: "\ea21";
$aw-icon-plus: "\ea22";
$aw-icon-product-hunt: "\ea23";
$aw-icon-refine: "\ea24";
$aw-icon-rest: "\ea25";
$aw-icon-search: "\ea26";
$aw-icon-star: "\ea27";
$aw-icon-system: "\ea28";
$aw-icon-twitter: "\ea29";
$aw-icon-vue: "\ea2a";
$aw-icon-x: "\ea2b";
$aw-icon-youtube: "\ea2c";

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 139 KiB

After

Width:  |  Height:  |  Size: 144 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 59 KiB

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -113,142 +113,154 @@
"className": "aw-icon-download",
"unicode": "&#59923;"
},
"github": {
"ext-link": {
"encodedCode": "\\ea14",
"prefix": "aw-icon",
"className": "aw-icon-github",
"className": "aw-icon-ext-link",
"unicode": "&#59924;"
},
"google": {
"github": {
"encodedCode": "\\ea15",
"prefix": "aw-icon",
"className": "aw-icon-google",
"className": "aw-icon-github",
"unicode": "&#59925;"
},
"hamburger-menu": {
"google": {
"encodedCode": "\\ea16",
"prefix": "aw-icon",
"className": "aw-icon-hamburger-menu",
"className": "aw-icon-google",
"unicode": "&#59926;"
},
"light": {
"hamburger-menu": {
"encodedCode": "\\ea17",
"prefix": "aw-icon",
"className": "aw-icon-light",
"className": "aw-icon-hamburger-menu",
"unicode": "&#59927;"
},
"linkedin": {
"light": {
"encodedCode": "\\ea18",
"prefix": "aw-icon",
"className": "aw-icon-linkedin",
"className": "aw-icon-light",
"unicode": "&#59928;"
},
"location": {
"linkedin": {
"encodedCode": "\\ea19",
"prefix": "aw-icon",
"className": "aw-icon-location",
"className": "aw-icon-linkedin",
"unicode": "&#59929;"
},
"logout-left": {
"location": {
"encodedCode": "\\ea1a",
"prefix": "aw-icon",
"className": "aw-icon-logout-left",
"className": "aw-icon-location",
"unicode": "&#59930;"
},
"logout-right": {
"logout-left": {
"encodedCode": "\\ea1b",
"prefix": "aw-icon",
"className": "aw-icon-logout-right",
"className": "aw-icon-logout-left",
"unicode": "&#59931;"
},
"microsoft": {
"logout-right": {
"encodedCode": "\\ea1c",
"prefix": "aw-icon",
"className": "aw-icon-microsoft",
"className": "aw-icon-logout-right",
"unicode": "&#59932;"
},
"minus": {
"message": {
"encodedCode": "\\ea1d",
"prefix": "aw-icon",
"className": "aw-icon-minus",
"className": "aw-icon-message",
"unicode": "&#59933;"
},
"nuxt": {
"microsoft": {
"encodedCode": "\\ea1e",
"prefix": "aw-icon",
"className": "aw-icon-nuxt",
"className": "aw-icon-microsoft",
"unicode": "&#59934;"
},
"platform": {
"minus": {
"encodedCode": "\\ea1f",
"prefix": "aw-icon",
"className": "aw-icon-platform",
"className": "aw-icon-minus",
"unicode": "&#59935;"
},
"plus": {
"nuxt": {
"encodedCode": "\\ea20",
"prefix": "aw-icon",
"className": "aw-icon-plus",
"className": "aw-icon-nuxt",
"unicode": "&#59936;"
},
"product-hunt": {
"platform": {
"encodedCode": "\\ea21",
"prefix": "aw-icon",
"className": "aw-icon-product-hunt",
"className": "aw-icon-platform",
"unicode": "&#59937;"
},
"refine": {
"plus": {
"encodedCode": "\\ea22",
"prefix": "aw-icon",
"className": "aw-icon-refine",
"className": "aw-icon-plus",
"unicode": "&#59938;"
},
"rest": {
"product-hunt": {
"encodedCode": "\\ea23",
"prefix": "aw-icon",
"className": "aw-icon-rest",
"className": "aw-icon-product-hunt",
"unicode": "&#59939;"
},
"search": {
"refine": {
"encodedCode": "\\ea24",
"prefix": "aw-icon",
"className": "aw-icon-search",
"className": "aw-icon-refine",
"unicode": "&#59940;"
},
"star": {
"rest": {
"encodedCode": "\\ea25",
"prefix": "aw-icon",
"className": "aw-icon-star",
"className": "aw-icon-rest",
"unicode": "&#59941;"
},
"system": {
"search": {
"encodedCode": "\\ea26",
"prefix": "aw-icon",
"className": "aw-icon-system",
"className": "aw-icon-search",
"unicode": "&#59942;"
},
"twitter": {
"star": {
"encodedCode": "\\ea27",
"prefix": "aw-icon",
"className": "aw-icon-twitter",
"className": "aw-icon-star",
"unicode": "&#59943;"
},
"vue": {
"system": {
"encodedCode": "\\ea28",
"prefix": "aw-icon",
"className": "aw-icon-vue",
"className": "aw-icon-system",
"unicode": "&#59944;"
},
"x": {
"twitter": {
"encodedCode": "\\ea29",
"prefix": "aw-icon",
"className": "aw-icon-x",
"className": "aw-icon-twitter",
"unicode": "&#59945;"
},
"youtube": {
"vue": {
"encodedCode": "\\ea2a",
"prefix": "aw-icon",
"className": "aw-icon-youtube",
"className": "aw-icon-vue",
"unicode": "&#59946;"
},
"x": {
"encodedCode": "\\ea2b",
"prefix": "aw-icon",
"className": "aw-icon-x",
"unicode": "&#59947;"
},
"youtube": {
"encodedCode": "\\ea2c",
"prefix": "aw-icon",
"className": "aw-icon-youtube",
"unicode": "&#59948;"
}
}

View File

@@ -0,0 +1,5 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6.81802 13.5459L13.182 7.18197" stroke="#19191C" stroke-width="1.2" stroke-linecap="round"/>
<path d="M6.81802 7.18198H13.182" stroke="#19191C" stroke-width="1.2" stroke-linecap="round"/>
<path d="M13.182 13.5459V7.18197" stroke="#19191C" stroke-width="1.2" stroke-linecap="round"/>
</svg>

After

Width:  |  Height:  |  Size: 396 B

View File

@@ -0,0 +1,3 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M4 12.5161V4H16V12.5161H10.8L6.8 16V12.5161H4Z" stroke="black" stroke-width="1.2" stroke-linejoin="round" />
</svg>

After

Width:  |  Height:  |  Size: 222 B

View File

@@ -0,0 +1,6 @@
export const highlight = (node: HTMLElement, text: string[]) => {
text.forEach((word) => {
const regex = new RegExp(`(${word})`, 'gi');
node.innerHTML = node.innerHTML.replace(regex, '<mark>$1</mark>');
});
};

View File

@@ -0,0 +1,9 @@
import { PUBLIC_APPWRITE_PROJECT_ID } from '$env/static/public';
import { Client, Databases, Functions } from 'appwrite';
export const client = new Client();
client.setEndpoint('https://cloud.appwrite.io/v1').setProject(PUBLIC_APPWRITE_PROJECT_ID);
export const databases = new Databases(client);
export const functions = new Functions(client);

View File

@@ -0,0 +1,24 @@
<script lang="ts">
import { DropdownCtx } from './DropdownMenu.svelte';
export let checked = false;
const {
builders: { createCheckboxItem }
} = DropdownCtx.get();
const {
elements: { checkboxItem },
states: { checked: localChecked }
} = createCheckboxItem({
defaultChecked: checked,
onCheckedChange({ next }) {
checked = !!next;
return next;
}
});
$: localChecked.set(checked);
</script>
<slot checkboxItem={$checkboxItem} />

View File

@@ -0,0 +1,33 @@
<script lang="ts" context="module">
const ctxKey = Symbol('dropdown');
export const DropdownCtx = {
get() {
return getContext<DropdownMenu>(ctxKey);
},
set(ctx: DropdownMenu) {
setContext(ctxKey, ctx);
}
};
</script>
<script lang="ts">
import { createDropdownMenu, type DropdownMenu } from '@melt-ui/svelte';
import { getContext, setContext } from 'svelte';
const dropdown = createDropdownMenu({
forceVisible: true,
positioning: {
placement: 'bottom-start'
}
});
DropdownCtx.set(dropdown);
const {
elements: { menu, trigger },
states: { open }
} = dropdown;
</script>
<slot menu={$menu} trigger={$trigger} open={$open} />

View File

@@ -31,11 +31,12 @@
{ label: 'Databases', href: '/docs/products/databases' },
{ label: 'Functions', href: '/docs/products/functions' },
{ label: 'Storage', href: '/docs/products/storage' },
{ label: 'Realtime', href: '/docs/apis/realtime' }
{ label: 'Realtime', href: '/docs/apis/realtime' },
],
Learn: [
{ label: 'Docs', href: '/docs' },
{ label: 'Community', href: '/community' },
{ label: 'Threads', href: '/threads' },
{ label: 'Blog', href: '/blog' },
{ label: 'Changelog', href: '/changelog' },
{

View File

@@ -66,3 +66,9 @@
</div>
</footer>
{/if}
<style lang="scss">
.aw-icon-button {
display: grid;
}
</style>

View File

@@ -0,0 +1,13 @@
<script lang="ts">
import { buildOpenGraphImage } from '$lib/utils/metadata';
export let title: string;
export let description: string;
const ogImage = buildOpenGraphImage(title, description);
</script>
<meta property="og:image" content={ogImage} />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />
<meta name="twitter:image" content={ogImage} />
<meta name="twitter:card" content="summary_large_image" />

14
src/lib/utils/debounce.ts Normal file
View File

@@ -0,0 +1,14 @@
export function createDebounce(delay = 500) {
let timeout: NodeJS.Timeout;
return {
debounce: (callback: () => void) => {
clearTimeout(timeout);
timeout = setTimeout(callback, delay);
},
reset: () => clearTimeout(timeout),
immediate: (callback: () => void) => {
clearTimeout(timeout);
callback();
}
};
}

9
src/lib/utils/random.ts Normal file
View File

@@ -0,0 +1,9 @@
export const deterministicRandom = <T>(options: T[], seed: string): T => {
const index = Math.floor((parseInt(seed, 36) / 36 ** 4) * options.length);
return options[index];
};
export const random = (min: number, max: number): number => {
return Math.floor(Math.random() * (max - min + 1) + min);
};

View File

@@ -15,9 +15,9 @@
<script lang="ts">
import { MainFooter } from '$lib/components';
import SeoOgImage from '$lib/components/SeoOgImage.svelte';
import { DocsArticle } from '$lib/layouts';
import type { TocItem } from '$lib/layouts/DocsArticle.svelte';
import { buildOpenGraphImage } from '$lib/utils/metadata';
import { DOCS_TITLE_SUFFIX } from '$routes/titles';
import { getContext, setContext } from 'svelte';
@@ -58,7 +58,6 @@
}, []);
const seoTitle = title + DOCS_TITLE_SUFFIX;
const ogImage = buildOpenGraphImage(title, description);
</script>
<svelte:head>
@@ -70,12 +69,7 @@
<meta name="description" content={description} />
<meta property="og:description" content={description} />
<meta name="twitter:description" content={description} />
<!-- Image -->
<meta property="og:image" content={ogImage} />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />
<meta name="twitter:image" content={ogImage} />
<meta name="twitter:card" content="summary_large_image" />
<SeoOgImage {title} {description} />
</svelte:head>
<DocsArticle {title} {back} {toc} {date}>

View File

@@ -1,89 +1,89 @@
<script lang="ts">
import { Article, FooterNav, MainFooter } from '$lib/components';
import { page } from '$app/stores';
import { Main } from '$lib/layouts';
import { getContext } from 'svelte';
import type { PostsData, AuthorData } from '$routes/blog/content';
import { BLOG_TITLE_SUFFIX } from '$routes/titles';
import { DEFAULT_HOST } from '$lib/utils/metadata';
import { page } from '$app/stores';
import { Article, FooterNav, MainFooter } from '$lib/components';
import { Main } from '$lib/layouts';
import { DEFAULT_HOST } from '$lib/utils/metadata';
import type { AuthorData, PostsData } from '$routes/blog/content';
import { BLOG_TITLE_SUFFIX } from '$routes/titles';
import { getContext } from 'svelte';
export let name: string;
export let description: string;
export let name: string;
export let description: string;
const pageSlug = $page.url.pathname.substring($page.url.pathname.lastIndexOf('/') + 1);
const authors = getContext<AuthorData[]>('authors');
const postsList = getContext<PostsData[]>('posts');
const posts = postsList.filter((post) => post.category.includes(pageSlug));
const pageSlug = $page.url.pathname.substring($page.url.pathname.lastIndexOf('/') + 1);
const authors = getContext<AuthorData[]>('authors');
const postsList = getContext<PostsData[]>('posts');
const posts = postsList.filter((post) => post.category.includes(pageSlug));
const seoTitle = name + BLOG_TITLE_SUFFIX;
const ogImage = DEFAULT_HOST + '/images/open-graph/blog.png';
const seoTitle = name + BLOG_TITLE_SUFFIX;
const ogImage = DEFAULT_HOST + '/images/open-graph/blog.png';
</script>
<svelte:head>
<!-- Titles -->
<title>{seoTitle}</title>
<meta property="og:title" content={seoTitle} />
<meta name="twitter:title" content={seoTitle} />
<!-- Desscription -->
<meta name="description" content={description} />
<meta property="og:description" content={description} />
<meta name="twitter:description" content={description} />
<!-- Image -->
<meta property="og:image" content={ogImage} />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />
<meta name="twitter:image" content={ogImage} />
<meta name="twitter:card" content="summary_large_image" />
<!-- Titles -->
<title>{seoTitle}</title>
<meta property="og:title" content={seoTitle} />
<meta name="twitter:title" content={seoTitle} />
<!-- Desscription -->
<meta name="description" content={description} />
<meta property="og:description" content={description} />
<meta name="twitter:description" content={description} />
<!-- Image -->
<meta property="og:image" content={ogImage} />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />
<meta name="twitter:image" content={ogImage} />
<meta name="twitter:card" content="summary_large_image" />
</svelte:head>
<Main>
<div class="aw-big-padding-section-level-1">
<div class="aw-big-padding-section-level-2">
<div class="aw-container">
<a class="aw-link aw-u-color-text-secondary u-cross-baseline" href="/blog">
<span class="aw-icon-chevron-left" aria-hidden="true" />
<span>Back to blog</span>
</a>
<div class="aw-category-header u-margin-block-start-24">
<div class="aw-category-header-content">
<h1 class="aw-display aw-u-color-text-primary">{name}</h1>
<p class="aw-category-header-description aw-description">
{description}
</p>
</div>
<!-- <div class="aw-input-text-search-wrapper u-inline-width-100-percent-mobile">
<div class="aw-big-padding-section-level-1">
<div class="aw-big-padding-section-level-2">
<div class="aw-container">
<a class="aw-link aw-u-color-text-secondary u-cross-baseline" href="/blog">
<span class="aw-icon-chevron-left" aria-hidden="true" />
<span>Back to blog</span>
</a>
<div class="aw-category-header u-margin-block-start-24">
<div class="aw-category-header-content">
<h1 class="aw-display aw-u-color-text-primary">{name}</h1>
<p class="aw-category-header-description aw-description">
{description}
</p>
</div>
<!-- <div class="aw-input-text-search-wrapper u-inline-width-100-percent-mobile">
<span class="icon-search" aria-hidden="true" />
<input class="aw-input-text aw-u-block-size-48" type="search" placeholder="Search" />
</div> -->
</div>
</div>
<div class="u-margin-block-start-48">
<ul class="aw-grid-articles">
{#each posts as post}
{@const author = authors.find((a) => a.slug.includes(post.author))}
{#if author}
<Article
title={post.title}
href={post.href}
cover={post.cover}
date={post.date}
timeToRead={post.timeToRead}
avatar={author.avatar}
author={author.name}
/>
{/if}
{/each}
</ul>
</div>
<div
class="aw-big-padding-section-level-2 is-margin-replace-padding u-position-relative u-overflow-hidden"
>
<div class="aw-container">
<FooterNav />
<MainFooter />
</div>
</div>
</div>
</div>
</div></Main
<div class="u-margin-block-start-48">
<ul class="aw-grid-articles">
{#each posts as post}
{@const author = authors.find((a) => a.slug.includes(post.author))}
{#if author}
<Article
title={post.title}
href={post.href}
cover={post.cover}
date={post.date}
timeToRead={post.timeToRead}
avatar={author.avatar}
author={author.name}
/>
{/if}
{/each}
</ul>
</div>
<div
class="aw-big-padding-section-level-2 is-margin-replace-padding u-position-relative u-overflow-hidden"
>
<div class="aw-container">
<FooterNav />
<MainFooter />
</div>
</div>
</div>
</div>
</div></Main
>

View File

@@ -66,7 +66,7 @@
<article class="aw-main-article">
<header class="aw-main-article-header">
<a
class="aw-link aw-u-color-text-secondary u-cross-baseline"
class="aw-link is-secondary aw-u-color-text-secondary u-cross-baseline"
href="/blog"
>
<span class="aw-icon-chevron-left" aria-hidden="true" />

View File

@@ -471,22 +471,10 @@
.footer-wrapper {
overflow: hidden;
> img {
top: -100px;
inline-size: 1700px;
max-inline-size: none;
max-block-size: none;
}
@media (max-width: 1024px) {
.aw-hero {
padding-block-start: 5rem;
}
> img {
top: -300px;
left: -400px;
}
}
.aw-hero {

View File

@@ -0,0 +1,18 @@
<svg width="1000" height="1201" viewBox="0 0 1000 1201" fill="none" xmlns="http://www.w3.org/2000/svg">
<g opacity="0.4" filter="url(#filter0_f_173_392)">
<ellipse cx="500" cy="600.5" rx="200" ry="300.5" fill="url(#paint0_radial_173_392)" />
</g>
<defs>
<filter id="filter0_f_173_392" x="0" y="0" width="1000" height="1201" filterUnits="userSpaceOnUse"
color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix" />
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape" />
<feGaussianBlur stdDeviation="150" result="effect1_foregroundBlur_173_392" />
</filter>
<radialGradient id="paint0_radial_173_392" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse"
gradientTransform="translate(488.004 604.652) rotate(86.015) scale(260.897 198.704)">
<stop stop-color="#E7F8F7" />
<stop offset="1" stop-color="#85DBD8" />
</radialGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 973 B

View File

@@ -0,0 +1,18 @@
<svg width="1325" height="1219" viewBox="0 0 1325 1219" fill="none" xmlns="http://www.w3.org/2000/svg">
<g opacity="0.4" filter="url(#filter0_f_175_393)">
<ellipse cx="662.5" cy="609.5" rx="362.5" ry="309.5" fill="url(#paint0_radial_175_393)" />
</g>
<defs>
<filter id="filter0_f_175_393" x="0" y="0" width="1325" height="1219" filterUnits="userSpaceOnUse"
color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix" />
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape" />
<feGaussianBlur stdDeviation="150" result="effect1_foregroundBlur_175_393" />
</filter>
<radialGradient id="paint0_radial_175_393" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse"
gradientTransform="translate(662.5 629.739) rotate(90) scale(289.261 362.5)">
<stop offset="0.281696" stop-color="#FE9567" />
<stop offset="0.59375" stop-color="#FD366E" />
</radialGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

View File

@@ -0,0 +1 @@
export const prerender = false;

View File

@@ -0,0 +1,245 @@
<script lang="ts">
import { Main } from '$lib/layouts';
import { createDebounce } from '$lib/utils/debounce';
import { DEFAULT_DESCRIPTION, DEFAULT_HOST } from '$lib/utils/metadata';
import { TITLE_SUFFIX } from '$routes/titles';
import FooterNav from '$lib/components/FooterNav.svelte';
import MainFooter from '$lib/components/MainFooter.svelte';
import ThreadCard from './ThreadCard.svelte';
import { queryParam } from 'sveltekit-search-params';
import PreFooter from './PreFooter.svelte';
import TagsDropdown from './TagsDropdown.svelte';
import { getThreads } from './helpers';
const title = 'Threads' + TITLE_SUFFIX;
const description = DEFAULT_DESCRIPTION;
const ogImage = DEFAULT_HOST + '/images/open-graph/website.png';
export let data;
let threads = data.threads;
let searching = false; // Do some sick animation
let query = '';
const handleSearch = async (value: string) => {
query = value;
searching = true;
threads = await getThreads({
q: value,
tags: selectedTags ?? [],
allTags: true
});
};
const { debounce, reset } = createDebounce();
const search = (node: HTMLInputElement) => {
const inputHandler = () => {
const value = node.value.toLowerCase();
debounce(() => {
handleSearch(value);
});
};
const keydownHandler = (event: KeyboardEvent) => {
if (event.key === 'Enter') {
const value = node.value.toLowerCase();
reset();
handleSearch(value);
}
};
node.addEventListener('input', inputHandler);
node.addEventListener('keydown', keydownHandler);
return {
destroy() {
node.removeEventListener('input', inputHandler);
node.removeEventListener('keydown', keydownHandler);
}
};
};
const tags = [
'Web',
'Flutter',
'GraphQL',
'Cloud',
'Self Hosted'
];
const moreTags = [
'Tools',
'Accounts',
'Users',
'Teams',
'Databases',
'Storage',
'Functions',
'Realtime',
'Locale',
'Avatars',
'Webhooks',
'General',
'REST API'
];
const _selectedTags = queryParam<string[]>('tags', {
encode(tags) {
return tags.join(',');
},
decode(tags) {
return tags?.split(',') ?? [];
},
defaultValue: []
});
let selectedTags: string[] = [];
function toggleTag(tag: string) {
if (selectedTags.includes(tag)) {
selectedTags = selectedTags.filter((t) => t !== tag);
} else {
selectedTags = [...selectedTags, tag];
}
handleSearch(query);
}
</script>
<svelte:head>
<!-- Titles -->
<title>{title}</title>
<meta property="og:title" content={title} />
<meta name="twitter:title" content={title} />
<!-- Description -->
<meta name="description" content={description} />
<meta property="og:description" content={description} />
<meta name="twitter:description" content={description} />
<!-- Image -->
<meta property="og:image" content={ogImage} />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />
<meta name="twitter:image" content={ogImage} />
<meta name="twitter:card" content="summary_large_image" />
</svelte:head>
<Main>
<div
class="aw-big-padding-section-level-1 u-position-relative u-overflow-hidden"
style="margin-block-start: -10rem; padding-block-start: 10rem; border-block-end: 1px solid hsl(var(--aw-color-smooth));"
>
<div
class="u-position-absolute"
style="pointer-events: none; inset-inline-start: -700px; inset-block-start: 0px;"
>
<enhanced:img src="./(assets)/bg-red.svg" alt="" />
</div>
<div
class="u-position-absolute"
style="pointer-events: none; inset-inline-end: -700px; inset-block-start: -400px;"
>
<enhanced:img src="./(assets)/bg-green.svg" alt="" />
</div>
<div class="aw-big-padding-section-level-2 u-position-relative aw-u-margin-block-80">
<div class="aw-container">
<h1 class="aw-display aw-u-color-text-primary">Threads</h1>
</div>
</div>
</div>
<div class="aw-container" style="padding-block-end: 5rem">
<div class="u-flex u-flex-wrap u-cross-center u-gap-32">
<ul class="u-flex u-flex-wrap u-gap-8">
{#each tags as tag}
<li style="display: flex; align-items: center;">
<button
class="aw-btn-tag"
class:is-selected={selectedTags?.includes(tag)}
on:click={() => toggleTag(tag)}
>
{tag}
</button>
</li>
{/each}
<li>
<TagsDropdown tags={moreTags} selectedTags={selectedTags ?? []} {toggleTag} />
</li>
</ul>
<div
class="aw-input-text-search-wrapper u-width-full-line u-max-width-350 aw-u-max-inline-size-none-mobile u-margin-inline-start-auto"
>
<span
class="aw-icon-search u-z-index-5"
aria-hidden="true"
style="inset-block-start:0.9rem"
/>
<input
class="aw-input-button -u-padding-block-0 u-position-relative u-z-index-1"
type="text"
id="search"
placeholder="Search for threads"
data-hit="-1"
use:search
bind:value={query}
/>
</div>
</div>
{#if threads.length}
<h2 class="u-margin-block-start-16 aw-u-color-text-primary" aria-live="polite">
Found {query.length ? threads.length : '600+'} results.
</h2>
{/if}
<div class="u-flex-vertical u-gap-16 u-margin-block-start-16">
{#each threads as thread (thread.$id)}
<ThreadCard {thread} {query} />
{:else}
<div class="aw-card is-normal has-border-gradient empty-card">
<enhanced:img class="img" src="./(assets)/empty-state.png" alt="" />
<span class="aw-main-body-500">No support threads found</span>
<button
class="aw-button"
on:click={() => {
query = '';
handleSearch('');
}}>Clear search</button
>
</div>
{/each}
</div>
</div>
<PreFooter />
<div class="aw-container" style="margin-block-start: -7.75rem;">
<FooterNav />
<MainFooter />
</div>
</Main>
<style lang="scss">
.empty-card {
padding: 1.25rem;
.img {
display: block;
width: 13.75rem;
height: auto;
margin-inline: auto;
}
span {
display: block;
color: hsl(var(--aw-color-primary));
text-align: center;
}
button {
margin-block-start: 1.5rem;
margin-inline: auto;
}
}
</style>

View File

@@ -0,0 +1,13 @@
import { getThreads } from './helpers.js';
export async function load({ url }) {
const tagsParam = url.searchParams.get('tags');
return {
threads: await getThreads({
q: url.searchParams.get('q'),
tags: tagsParam ? tagsParam.split(',') : undefined,
allTags: true
})
};
}

View File

@@ -0,0 +1,72 @@
<div class="wrapper">
<img src="/images/bgs/pre-footer.png" alt="" class="aw-pre-footer-bg" style="z-index:-1" />
<div class="aw-container">
<h2 class="aw-display aw-u-color-text-primary">Need support?</h2>
<div class="cards">
<div class="cardy">
<h3 class="aw-label aw-u-color-text-primary">Join our Discord</h3>
<p class="aw-main-body-400 u-margin-block-start-16 aw-u-color-text-primary">
Get community support by joining our Discord server
</p>
<a class="aw-button u-margin-block-start-24" href="https://appwrite.io/discord">
<span class="aw-icon-discord" />
<span class="text">Join Discord</span>
</a>
</div>
<div class="cardy">
<h3 class="aw-label aw-u-color-text-primary">Get premium support</h3>
<p class="aw-main-body-400 u-margin-block-start-16 aw-u-color-text-primary">
Become a pro user and get email support from our team
</p>
<a class="aw-button u-margin-block-start-24" href="https://appwrite.io/pricing">
<span class="text">Learn more</span>
</a>
</div>
</div>
</div>
</div>
<style lang="scss">
.wrapper {
padding-block: 7.5rem;
overflow: hidden;
position: relative;
}
h2 {
text-align: center;
}
.cards {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
justify-content: center;
gap: 2.25rem;
margin-block-start: 5rem;
}
.cardy {
@include border-gradient;
--m-border-gradient-before: linear-gradient(
180deg,
rgba(255, 255, 255, 0.16) 0%,
rgba(255, 255, 255, 0) 100%
);
--m-border-radius: 1rem;
background: rgba(255, 255, 255, 0.04);
backdrop-filter: blur(7.5px);
padding: 1.5rem;
}
.aw-pre-footer-bg {
position: absolute;
top: clamp(300px, 50vw, 50%);
left: clamp(300px, 50vw, 50%);
transform: translate(-58%, -72%);
width: clamp(1200px, 200vw, 3000px);
height: auto;
max-inline-size: unset;
max-block-size: unset;
}
</style>

View File

@@ -0,0 +1,113 @@
<script lang="ts">
import DropdownCheckboxItem from '$lib/components/DropdownMenu/DropdownCheckboxItem.svelte';
import DropdownMenu from '$lib/components/DropdownMenu/DropdownMenu.svelte';
import { melt } from '@melt-ui/svelte';
import { fly } from 'svelte/transition';
export let tags: string[];
export let selectedTags: string[];
export let toggleTag: (tag: string) => void;
</script>
<DropdownMenu let:open let:menu let:trigger>
<button class="aw-btn-tag" use:melt={trigger}>
<span class="text">More</span>
<span class="aw-icon-chevron-down" style="font-size: 1rem" />
</button>
{#if open}
<div
class="menu-wrapper aw-card is-normal menu has-border-gradient"
style:z-index="1"
use:melt={menu}
transition:fly={{ y: 8, duration: 250 }}
>
<ul
>
{#each tags as tag}
{@const checked = selectedTags?.includes(tag)}
<DropdownCheckboxItem let:checkboxItem {checked}>
<li
use:melt={checkboxItem}
on:m-click={(e) => {
e.preventDefault();
toggleTag(tag);
}}
>
<div class="checkbox">
{#if checked}
<span class="aw-icon-check" />
{/if}
</div>
{tag}
</li>
</DropdownCheckboxItem>
{/each}
</ul>
</div>
{/if}
</DropdownMenu>
<style lang="scss">
.menu-wrapper {
--p-card-border-radius: 0.5rem;
padding: 0.75rem;
backdrop-filter: blur(2px);
--webkit-backdrop-filter: blur(2px);
}
ul {
min-width: 14.375rem;
display: flex;
flex-direction: column;
gap: 0.5rem;
height: 12rem;
overflow-y: auto;
&:focus {
outline: none;
box-shadow: none;
}
}
li {
display: flex;
align-items: center;
gap: 0.5rem;
border-radius: 0.5rem;
padding: 0.5rem 0.75rem;
&:hover {
cursor: pointer;
background-color: hsl(var(--aw-color-offset));
}
}
.checkbox {
--p-checkbox-size: 1.4rem;
width: var(--p-checkbox-size);
height: var(--p-checkbox-size);
background-color: #19191c;
border: 1px solid #56565c;
border-radius: 0.25rem;
position: relative;
[class*='icon'] {
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
font-size: 1rem;
color: black;
}
:global([data-state='checked']) & {
background-color: hsl(var(--aw-color-primary));
border-color: hsl(var(--aw-color-primary));
}
}
</style>

View File

@@ -0,0 +1,68 @@
<script lang="ts">
import { highlight } from '$lib/actions/highlight';
import type { DiscordThread } from './types';
export let thread: DiscordThread;
export let query: string;
$: highlightTerms = query?.split(' ') ?? [];
</script>
{#key highlightTerms}
<a
href="/threads/{thread.discord_id}"
class="aw-card is-normal has-border-gradient thread"
>
<div class="u-flex u-gap-8">
<h3 class="aw-main-body-500 aw-u-color-text-primary" use:highlight={highlightTerms}>
{thread.title}
</h3>
<!-- <time class="aw-caption-400 u-margin-inline-start-auto">12 Jan, 2023</time> -->
</div>
<p class="aw-main-body-500 u-margin-block-start-4" use:highlight={highlightTerms}>
{thread.content.length > 200 ? thread.content.slice(0, 200) + '...' : thread.content}
</p>
<div class="u-flex u-main-space-between u-gap-16 u-margin-block-start-16">
<ul class="u-flex u-gap-8">
{#each thread.tags ?? [] as tag}
<li>
<div class="aw-tag">{tag}</div>
</li>
{/each}
</ul>
<div
class="aw-icon-button is-more-content"
aria-label="Replies"
style:pointer-events="none"
>
<span class="aw-icon-message" aria-hidden="true" style="font-size:1rem" />
<span class="aw-caption-400 aw-u-line-height-1-2">{thread.message_count}</span>
</div>
</div>
</a>
{/key}
<style lang="scss">
.aw-card {
padding: 1.25rem;
}
.thread {
position: relative;
p: {
overflow-wrap: break-word;
}
}
.thread :global(mark) {
background-color: hsl(var(--aw-color-pink-500) / 0.5);
}
h3 {
margin-block-end: 0.25rem;
}
</style>

View File

@@ -0,0 +1,22 @@
import { error } from '@sveltejs/kit';
import { getRelatedThreads, getThread, getThreadMessages } from '../helpers.js';
export const prerender = false;
export const load = async ({ params }) => {
const id = params.id;
try {
const thread = await getThread(id);
const related = await getRelatedThreads(thread);
const messages = await getThreadMessages(id);
return {
...thread,
related,
messages,
};
} catch (e) {
throw error(404, 'Thread not found');
}
};

View File

@@ -0,0 +1,254 @@
<script lang="ts">
import { Main } from '$lib/layouts';
import { DEFAULT_DESCRIPTION } from '$lib/utils/metadata';
import { TITLE_SUFFIX } from '$routes/titles';
import FooterNav from '$lib/components/FooterNav.svelte';
import MainFooter from '$lib/components/MainFooter.svelte';
import SeoOgImage from '$lib/components/SeoOgImage.svelte';
import PreFooter from '../PreFooter.svelte';
import MessageCard from './MessageCard.svelte';
export let data;
const title = data.title + ' - Threads' + TITLE_SUFFIX;
const description = DEFAULT_DESCRIPTION;
const discordLink = `https://discord.com/channels/564160730845151244/${data.discord_id}`;
function shorten(str: string, len: number) {
return str.length > len ? str.slice(0, len) + '...' : str;
}
</script>
<svelte:head>
<!-- Titles -->
<title>{title}</title>
<meta property="og:title" content={title} />
<meta name="twitter:title" content={title} />
<!-- Desscription -->
<meta name="description" content={data.seo_description ?? description} />
<meta property="og:description" content={data.seo_description ?? description} />
<meta name="twitter:description" content={data.seo_description ?? description} />
<SeoOgImage
title={shorten(data.title, 32)}
description={shorten(data.seo_description ?? DEFAULT_DESCRIPTION, 64)}
/>
</svelte:head>
<Main>
<div class="aw-container" style="padding-block-end: 0;">
<div class="header">
<div>
<a class="aw-link is-secondary u-cross-baseline" href="/threads">
<span class="aw-icon-chevron-left" aria-hidden="true" />
<span>Back</span>
</a>
<h1 class="aw-title aw-u-color-text-primary">{data.title}</h1>
<ul class="tags">
<li class="aw-tag">
<span class="aw-icon-arrow-up" />
<span class="text">{data.vote_count}</span>
</li>
{#each data.tags ?? [] as tag}
<li class="aw-tag">
<span class="text">{tag}</span>
</li>
{/each}
</ul>
</div>
<div class="buttons">
<a
class="aw-button"
href={discordLink}
>
<span class="aw-icon-discord" />
<span class="text">View on Discord</span>
</a>
</div>
</div>
<div class="thread-grid">
<div class="messages">
{#each data.messages ?? [] as message, i}
{@const isFirst = i === 0}
<MessageCard {message}>
{#if isFirst}
<div class="aw-inline-info" style:margin-block-start="1.5rem">
<span
class="aw-sub-body-500 aw-u-color-text-primary"
style:display="block"
>
TL;DR
</span>
{data.tldr}
</div>
{/if}
</MessageCard>
{/each}
<div class="aw-card is-normal has-border-gradient">
<span class="aw-sub-body-500 aw-u-color-text-primary">Reply</span>
<p class="aw-sub-body-500 u-margin-block-start-16">
Reply to this thread by joining our Discord
</p>
<a
class="aw-button u-margin-block-start-24"
href={discordLink}
>
<span class="aw-icon-discord" />
<span class="text">Reply on Discord</span>
</a>
</div>
</div>
<div class="related">
{#if data.related.length}
<h2 class="aw-eyebrow aw-u-color-text-primary">Recommended threads</h2>
{/if}
<ul>
{#each data.related as thread}
<li>
<a href="/threads/{thread.$id}" data-sveltekit-reload>
<div class="u-flex u-cross-center">
<span class="aw-sub-body-500 aw-u-color-text-primary">
{thread.title.length > 40
? thread.title.slice(0, 40) + '...'
: thread.title}
</span>
</div>
<p class="aw-sub-body-400 u-margin-block-start-8">
{thread.content.length > 160
? thread.content.slice(0, 160) + '...'
: thread.content}
</p>
</a>
</li>
{/each}
</ul>
</div>
</div>
</div>
<PreFooter />
<div class="aw-container" style="margin-block-start: -7.75rem;">
<FooterNav />
<MainFooter />
</div>
</Main>
<style lang="scss">
.header {
display: grid;
grid-template-columns: 1fr auto;
gap: 4rem;
align-items: center;
margin-block-start: 2.5rem;
}
h1 {
margin-block-start: 1rem;
}
.tags {
display: flex;
gap: 0.5rem;
margin-block-start: 1rem;
}
.aw-tag {
display: flex;
align-items: center;
gap: 0.25rem;
white-space: nowrap;
}
.thread-grid {
display: grid;
grid-template-columns: 2fr 1fr;
gap: 3rem;
margin-block-start: 2rem;
border-block-start: 1px solid hsl(var(--aw-color-border));
padding-block-end: 5rem;
}
.messages {
display: flex;
flex-direction: column;
gap: 1rem;
overflow: hidden;
padding-block-start: 2rem;
:global(.aw-card) {
padding: 1.25rem;
}
}
.related {
height: 100%;
position: relative;
padding-block-start: 2.25rem;
overflow: hidden;
&::before {
content: '';
position: absolute;
top: 0;
left: -20px;
width: 1px;
height: calc(100% + 5rem);
background-color: hsl(var(--aw-color-border));
}
ul {
display: flex;
flex-direction: column;
gap: 1rem;
margin-block-start: 1.5rem;
li {
padding-block-end: 1rem;
&:not(:last-child) {
border-block-end: 1px solid hsl(var(--aw-color-smooth));
}
&:hover {
opacity: 0.75;
}
}
}
}
@keyframes dot {
0% {
translate: 0 0;
}
37% {
translate: 0 -0.25rem;
}
75% {
translate: 0 0;
}
}
@media #{$break1} {
.header {
gap: 2rem;
grid-template-columns: 1fr;
}
.thread-grid {
grid-template-columns: 1fr;
}
.related::before {
background-color: transparent;
}
}
</style>

View File

@@ -0,0 +1,120 @@
<script lang="ts">
import '$scss/hljs.css';
import { getCodeHtml, type Language } from '$lib/utils/code';
import { getContext, hasContext } from 'svelte';
import { platformMap } from '$lib/utils/references';
import { Tooltip } from '$lib/components';
import { copy } from '$lib/utils/copy';
import { melt } from '@melt-ui/svelte';
import type { CodeContext } from '$markdoc/tags/MultiCode.svelte';
export let text: string;
export let language: Language = 'typescript';
export let process = true;
export let withLineNumbers = true;
const insideMultiCode = hasContext('multi-code');
const selected = insideMultiCode ? getContext<CodeContext>('multi-code').selected : null;
enum CopyStatus {
Copy = 'Copy',
Copied = 'Copied!'
}
let copyText = CopyStatus.Copy;
async function handleCopy() {
await copy(text);
copyText = CopyStatus.Copied;
setTimeout(() => {
copyText = CopyStatus.Copy;
}, 1000);
}
if (insideMultiCode) {
const ctx = getContext<CodeContext>('multi-code');
ctx.snippets.update((n) => {
n.add(language);
return n;
});
ctx.selected.subscribe((n) => {
if (n === language) {
ctx.content.set(text);
}
});
}
$: result = process
? getCodeHtml({ content: text, language: language ?? 'sh', withLineNumbers })
: text;
</script>
{#if insideMultiCode}
{#if $selected === language}
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
{@html result}
{/if}
{:else}
<section class="theme-dark aw-code-snippet" aria-label="code-snippet panel">
<header class="aw-code-snippet-header">
<div class="aw-code-snippet-header-start">
{#if platformMap[language]}
<div class="u-flex u-gap-16">
<div class="aw-tag"><span class="text">{platformMap[language]}</span></div>
</div>
{/if}
</div>
<div class="aw-code-snippet-header-end">
<ul class="buttons-list u-flex u-gap-8">
<li class="buttons-list-item aw-u-padding-inline-start-20">
<Tooltip>
<button
slot="asChild"
let:trigger
use:melt={trigger}
on:click={handleCopy}
class="aw-icon-button"
aria-label="copy code from code-snippet"
>
<span class="aw-icon-copy" aria-hidden="true" />
</button>
<svelte:fragment slot="tooltip">
{copyText}
</svelte:fragment>
</Tooltip>
</li>
</ul>
</div>
</header>
<div class="aw-code-snippet-content">
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
{@html result}
</div>
</section>
{/if}
<style lang="scss">
button {
position: relative;
.aw-icon-copy {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
}
.aw-code-snippet {
margin-block-end: 1rem;
margin-top: 1rem;
}
.aw-code-snippet-content {
max-height: 600px;
overflow-y: auto;
}
</style>

View File

@@ -0,0 +1,11 @@
<script lang="ts">
export let href: string;
export let title: string;
export let text: string;
const isExternal = ['http://', 'https://'].some((prefix) => href.startsWith(prefix));
const target = isExternal ? '_blank' : undefined;
const rel = isExternal ? 'noopener nofollow' : undefined;
</script>
<a class="aw-link is-inline" {href} {title} {target} {rel}>{text}</a>

View File

@@ -0,0 +1,87 @@
<script lang="ts">
import SvelteMarkdown from 'svelte-markdown';
import type { DiscordMessage } from '../types';
import CodeRenderer from './CodeRenderer.svelte';
import LinkRenderer from './LinkRenderer.svelte';
export let message: DiscordMessage;
const formatTimestamp = (date: string): string => {
const dt = new Date(date);
// format like: 12 Jan, 2023, 13:10
const day = dt.getDate();
const month = dt.toLocaleString('default', { month: 'short' });
const year = dt.getFullYear();
const hours = dt.getHours();
const minutes = dt.getMinutes();
const paddedMinutes = minutes < 10 ? `0${minutes}` : minutes;
const paddedHours = hours < 10 ? `0${hours}` : hours;
return `${day} ${month}, ${year}, ${paddedHours}:${paddedMinutes}`;
};
</script>
<div class="aw-card is-normal has-border-gradient message">
<div class="header">
<div class="author">
<div class="author-img">
<img src={message.author_avatar} alt="" class="h-full w-full rounded-[inherit]" />
</div>
<span class="aw-sub-body-500 aw-u-color-text-primary">{message.author}</span>
</div>
<span class="timestamp aw-caption-400">
{formatTimestamp(message.timestamp)}
</span>
</div>
<div class="aw-sub-body-500" style=" margin-block-start: 1rem;">
<SvelteMarkdown
source={message.message}
renderers={{
code: CodeRenderer,
link: LinkRenderer
}}
/>
</div>
<slot />
</div>
<style lang="scss">
.aw-card {
overflow: hidden;
}
.aw-sub-body-500 {
font-size: 1rem !important;
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
}
.author {
display: flex;
align-items: center;
gap: 0.5rem;
}
.author-img {
--p-size: 1.5rem; // 24px
display: flex;
justify-content: center;
align-items: center;
border-radius: 100%;
width: var(--p-size);
height: var(--p-size);
img {
border-radius: 100%;
width: 100%;
height: 100%;
}
}
</style>

View File

@@ -0,0 +1,115 @@
import {
PUBLIC_APPWRITE_COL_MESSAGES_ID,
PUBLIC_APPWRITE_COL_THREADS_ID,
PUBLIC_APPWRITE_DB_MAIN_ID,
PUBLIC_APPWRITE_FN_TLDR_ID
} from '$env/static/public';
import { databases, functions } from '$lib/appwrite';
import { Query } from 'appwrite';
import type { DiscordMessage, DiscordThread } from './types';
type Ranked<T> = {
data: T;
rank: number; // Percentage of query words found, from 0 to 1
};
type FilterThreadsArgs = {
threads: DiscordThread[];
q?: string | null;
tags?: string[];
allTags?: boolean;
};
export function filterThreads({ q, threads: threadDocs, tags, allTags }: FilterThreadsArgs) {
const threads = tags
? threadDocs.filter((thread) => {
const lowercaseTags = thread.tags?.map((tag) => tag.toLowerCase());
if (allTags) {
return tags?.every((tag) => lowercaseTags?.includes(tag.toLowerCase()));
} else {
return tags?.some((tag) => lowercaseTags?.includes(tag.toLowerCase()));
}
})
: threadDocs;
if (!q) return threads;
const queryWords = q.toLowerCase().split(/\s+/);
const rankPerWord = 1 / queryWords.length;
const res: Ranked<DiscordThread>[] = [];
threads.forEach((item) => {
const foundWords = new Set<string>();
Object.values(item).forEach((value) => {
const stringified = JSON.stringify(value).toLowerCase();
queryWords.forEach((word) => {
if (stringified.includes(word)) {
foundWords.add(word);
}
});
});
const rank = foundWords.size * rankPerWord;
if (rank > 0) {
res.push({
data: item,
rank
});
}
});
return res.sort((a, b) => b.rank - a.rank).map(({ data }) => data);
}
type GetThreadsArgs = Omit<FilterThreadsArgs, 'threads'>;
export async function getThreads({ q, tags, allTags }: GetThreadsArgs) {
let query = [
q ? Query.search('search_meta', q) : undefined
];
tags = tags?.filter(Boolean).map((tag) => tag.toLowerCase()) ?? [];
if (tags.length > 0) {
query = [...query, Query.search('tags', tags.join(','))];
}
const data = await databases.listDocuments(
PUBLIC_APPWRITE_DB_MAIN_ID,
PUBLIC_APPWRITE_COL_THREADS_ID,
query.filter(Boolean) as string[]
);
const threadDocs = data.documents as unknown as DiscordThread[];
return filterThreads({ threads: threadDocs, q, tags, allTags });
}
export async function getThread($id: string) {
return (await databases.getDocument(
PUBLIC_APPWRITE_DB_MAIN_ID,
PUBLIC_APPWRITE_COL_THREADS_ID,
$id
)) as unknown as DiscordThread;
}
export async function getRelatedThreads(thread: DiscordThread, limit: number = 3) {
const tags = thread.tags?.filter(Boolean) ?? [];
const relatedThreads = await getThreads({ q: null, tags, allTags: false });
return relatedThreads.filter(({ $id }) => $id !== thread.$id).slice(0, limit);
}
export async function getThreadMessages(threadId: string) {
const data = await databases.listDocuments(
PUBLIC_APPWRITE_DB_MAIN_ID,
PUBLIC_APPWRITE_COL_MESSAGES_ID,
[Query.equal('threadId', threadId)].filter(Boolean) as string[]
);
return (data.documents as unknown as DiscordMessage[]).sort(
(a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime()
);
}

View File

@@ -0,0 +1,38 @@
import type { Models } from 'appwrite';
export type MockThread = {
id: string;
username?: string;
title: string;
text: string;
replies: MockMessage[];
};
export interface DiscordMessage extends Pick<Models.Document, '$id'> {
threadId: string;
author: string;
author_avatar: string;
message: string;
role?: string;
/* `UTC` timestamp */
timestamp: string;
}
export interface DiscordThread extends Pick<Models.Document, '$id'> {
discord_id: string;
author: string;
tags?: string[];
author_avatar: string;
seo_description?: string;
content: string;
title: string;
search_meta?: string;
tldr: string;
vote_count: number;
message_count: number;
}
export type MockMessage = {
username?: string;
text: string;
};

View File

@@ -0,0 +1,38 @@
@use '../abstract' as *;
.#{$p}-btn-tag {
--p-tag-text-color: var(--aw-color-primary);
--p-tag-bg-color: var(--aw-color-greyscale-100);
--p-tag-border-color: var(--p-tag-bg-color);
color: hsl(var(--p-tag-text-color));
background-color: hsl(var(--p-tag-bg-color));
border: 1px solid hsl(var(--p-tag-border-color));
padding-block: pxToRem(4);
padding-inline: pxToRem(8);
border-radius: pxToRem(12);
font-size: var(--aw-font-size-micro);
line-height: var(--aw-line-height-tiny);
#{$theme-dark} & {
--p-tag-bg-color: var(--aw-color-greyscale-750);
&:where(:hover) {
--p-tag-bg-color: var(--aw-color-greyscale-700);
}
&:where(:active) {
--p-tag-bg-color: var(--aw-color-greyscale-800);
}
&:where(.is-selected) {
--p-tag-border-color: var(--aw-color-white);
}
&:where(:disabled) {
--p-tag-bg-color: var(--aw-color-greyscale-800);
}
}
}

View File

@@ -9,6 +9,7 @@
@media #{$break1} { padding-inline: pxToRem(20); }
}
.#{$p}-main-section {
> * { padding-block:pxToRem(24); }
>:first-child { padding-block-start:0; }

View File

@@ -1,7 +1,8 @@
@use '../abstract' as *;
.#{$p}-icon-button {
display: block;
display: flex;
gap: pxToRem(4);
position: relative;
block-size:pxToRem(28);
inline-size:pxToRem(28);
@@ -10,10 +11,17 @@
border-radius: pxToRem(8);
> [class*='icon'] {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
position: relative;
&::before {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
}
&.is-more-content {
inline-size:fit-content; padding:pxToRem(4); line-height:pxToRem(18);
> [class*='icon'] { inline-size:pxToRem(16); }
}

View File

@@ -12,6 +12,7 @@
@forward "badges";
@forward "numeric-badge";
@forward "tag";
@forward "btn-tag";
@forward "inline-tag";
@forward "card";
@forward "lists";

View File

@@ -31,5 +31,9 @@
&.is-inline {
text-decoration: underline;
}
&.is-secondary {
--p-link-color-text-default: var(--aw-color-secondary);
}
}

View File

@@ -47,6 +47,7 @@
.#{$p}-u-margin-inline-32-negative { margin-inline:pxToRem(-32); }
.#{$p}-u-margin-block-0 { margin-block:0; }
.#{$p}-u-margin-block-80 { margin-block:pxToRem(80); }
.#{$p}-u-margin-block-start-40 { margin-block-start:pxToRem(40); }
.#{$p}-u-margin-block-start-40-mobile { @media #{$break1} {margin-block-start:pxToRem(40);} }

View File

@@ -59,4 +59,4 @@ const config = {
}
}
};
export default config;
export default config;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 MiB