├── .env.example ├── .gitignore ├── .npmrc ├── .prettierignore ├── .prettierrc ├── README.md ├── artwork ├── SVG │ ├── Comment.svg │ ├── Heart.svg │ ├── HeartOutline.svg │ ├── Logo.svg │ ├── New.svg │ ├── Trash.svg │ ├── Unused.svg │ └── Upload.svg └── icons.ai ├── package.json ├── pnpm-lock.yaml ├── postcss.config.js ├── postgres ├── migrate.js └── migrations │ ├── 000.sql │ └── 001.sql ├── scripts └── update-vercel-config.js ├── src ├── ambient.d.ts ├── app.css ├── app.d.ts ├── app.html ├── hooks.server.ts ├── lib │ ├── actions.ts │ ├── components │ │ ├── Avatar.svelte │ │ ├── AvatarImage.svelte │ │ ├── Image.svelte │ │ ├── Login.svelte │ │ ├── Metadata.svelte │ │ ├── Modal.svelte │ │ ├── PhotoList.svelte │ │ ├── Publisher.svelte │ │ ├── Scroller.svelte │ │ └── Uploader.svelte │ ├── icons │ │ ├── Comment.svelte │ │ ├── Heart.svelte │ │ ├── HeartOutline.svelte │ │ ├── Logo.svelte │ │ ├── Trash.svelte │ │ └── account-circle.svg │ ├── image-size │ │ ├── index.ts │ │ ├── jpg.ts │ │ ├── png.ts │ │ └── types.d.ts │ ├── image.ts │ ├── server │ │ └── database.ts │ ├── state.ts │ ├── types.d.ts │ └── utils.ts └── routes │ ├── +layout.server.ts │ ├── +layout.svelte │ ├── +page.server.ts │ ├── +page.svelte │ ├── [account] │ ├── +page.server.ts │ ├── +page.svelte │ └── [photo] │ │ ├── +page.server.ts │ │ └── +page.svelte │ ├── api │ └── photos │ │ ├── [account_id].json │ │ └── +server.ts │ │ └── feed.json │ │ └── +server.ts │ ├── auth │ ├── +page.server.ts │ ├── +page.svelte │ └── callback │ │ └── +server.ts │ └── publish │ ├── +page.server.ts │ └── +page.svelte ├── static └── favicon.png ├── svelte.config.js ├── tailwind.config.js ├── tsconfig.json ├── vercel.json └── vite.config.ts /.env.example: -------------------------------------------------------------------------------- 1 | # get this from https://discord.com/developers/applications 2 | DISCORD_CLIENT_ID= 3 | DISCORD_CLIENT_SECRET= 4 | 5 | # get these from Vercel after you create a Postgres database and a Blob store 6 | POSTGRES_URL= 7 | BLOB_READ_WRITE_TOKEN= -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /build 4 | /.svelte-kit 5 | /package 6 | .env 7 | .env.* 8 | !.env.example 9 | vite.config.js.timestamp-* 10 | vite.config.ts.timestamp-* 11 | .vercel -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | engine-strict=true 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /build 4 | /.svelte-kit 5 | /package 6 | .env 7 | .env.* 8 | !.env.example 9 | 10 | # Ignore files for PNPM, NPM and YARN 11 | pnpm-lock.yaml 12 | package-lock.json 13 | yarn.lock 14 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "useTabs": true, 3 | "singleQuote": true, 4 | "trailingComma": "none", 5 | "printWidth": 100, 6 | "plugins": ["prettier-plugin-svelte"], 7 | "pluginSearchDirs": ["."], 8 | "overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }] 9 | } 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # create-svelte 2 | 3 | Everything you need to build a Svelte project, powered by [`create-svelte`](https://github.com/sveltejs/kit/tree/master/packages/create-svelte). 4 | 5 | ## Creating a project 6 | 7 | If you're seeing this, you've probably already done this step. Congrats! 8 | 9 | ```bash 10 | # create a new project in the current directory 11 | npm create svelte@latest 12 | 13 | # create a new project in my-app 14 | npm create svelte@latest my-app 15 | ``` 16 | 17 | ## Developing 18 | 19 | Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server: 20 | 21 | ```bash 22 | npm run dev 23 | 24 | # or start the server and open the app in a new browser tab 25 | npm run dev -- --open 26 | ``` 27 | 28 | ## Building 29 | 30 | To create a production version of your app: 31 | 32 | ```bash 33 | npm run build 34 | ``` 35 | 36 | You can preview the production build with `npm run preview`. 37 | 38 | > To deploy your app, you may need to install an [adapter](https://kit.svelte.dev/docs/adapters) for your target environment. 39 | -------------------------------------------------------------------------------- /artwork/SVG/Comment.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /artwork/SVG/Heart.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /artwork/SVG/HeartOutline.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /artwork/SVG/Logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /artwork/SVG/New.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /artwork/SVG/Trash.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /artwork/SVG/Unused.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /artwork/SVG/Upload.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /artwork/icons.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rich-Harris/sveltesnaps/fa50e79c3c9d8f10e532f3a751d48ed55336b48e/artwork/icons.ai -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sveltesnaps", 3 | "version": "0.0.1", 4 | "private": true, 5 | "scripts": { 6 | "dev": "vite dev", 7 | "build": "vite build && node scripts/update-vercel-config", 8 | "preview": "vite preview", 9 | "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", 10 | "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", 11 | "lint": "prettier --plugin-search-dir . --check .", 12 | "format": "prettier --plugin-search-dir . --write ." 13 | }, 14 | "devDependencies": { 15 | "@sveltejs/adapter-vercel": "^2.4.3", 16 | "@sveltejs/kit": "^1.16.2", 17 | "autoprefixer": "^10.4.14", 18 | "postcss": "^8.4.23", 19 | "prettier": "^2.8.8", 20 | "prettier-plugin-svelte": "^2.10.0", 21 | "svelte": "^3.58.0", 22 | "svelte-check": "^3.2.0", 23 | "tailwindcss": "^3.3.2", 24 | "tslib": "^2.5.0", 25 | "typescript": "^5.0.4", 26 | "vite": "^4.3.4" 27 | }, 28 | "type": "module", 29 | "dependencies": { 30 | "@fontsource/pangolin": "^4.5.9", 31 | "@vercel/blob": "^0.3.2", 32 | "@vercel/postgres": "0.1.0-canary.5", 33 | "postgres": "^3.3.4", 34 | "svelte-autosize": "^1.1.0" 35 | } 36 | } -------------------------------------------------------------------------------- /pnpm-lock.yaml: -------------------------------------------------------------------------------- 1 | lockfileVersion: '6.0' 2 | 3 | dependencies: 4 | '@fontsource/pangolin': 5 | specifier: ^4.5.9 6 | version: 4.5.9 7 | '@vercel/blob': 8 | specifier: ^0.3.2 9 | version: 0.3.2 10 | '@vercel/postgres': 11 | specifier: 0.1.0-canary.5 12 | version: 0.1.0-canary.5 13 | postgres: 14 | specifier: ^3.3.4 15 | version: 3.3.4 16 | svelte-autosize: 17 | specifier: ^1.1.0 18 | version: 1.1.0 19 | 20 | devDependencies: 21 | '@sveltejs/adapter-vercel': 22 | specifier: ^2.4.3 23 | version: 2.4.3(@sveltejs/kit@1.16.2) 24 | '@sveltejs/kit': 25 | specifier: ^1.16.2 26 | version: 1.16.2(svelte@3.58.0)(vite@4.3.4) 27 | autoprefixer: 28 | specifier: ^10.4.14 29 | version: 10.4.14(postcss@8.4.23) 30 | postcss: 31 | specifier: ^8.4.23 32 | version: 8.4.23 33 | prettier: 34 | specifier: ^2.8.8 35 | version: 2.8.8 36 | prettier-plugin-svelte: 37 | specifier: ^2.10.0 38 | version: 2.10.0(prettier@2.8.8)(svelte@3.58.0) 39 | svelte: 40 | specifier: ^3.58.0 41 | version: 3.58.0 42 | svelte-check: 43 | specifier: ^3.2.0 44 | version: 3.2.0(postcss@8.4.23)(svelte@3.58.0) 45 | tailwindcss: 46 | specifier: ^3.3.2 47 | version: 3.3.2 48 | tslib: 49 | specifier: ^2.5.0 50 | version: 2.5.0 51 | typescript: 52 | specifier: ^5.0.4 53 | version: 5.0.4 54 | vite: 55 | specifier: ^4.3.4 56 | version: 4.3.4 57 | 58 | packages: 59 | 60 | /@alloc/quick-lru@5.2.0: 61 | resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} 62 | engines: {node: '>=10'} 63 | dev: true 64 | 65 | /@esbuild/android-arm64@0.17.18: 66 | resolution: {integrity: sha512-/iq0aK0eeHgSC3z55ucMAHO05OIqmQehiGay8eP5l/5l+iEr4EIbh4/MI8xD9qRFjqzgkc0JkX0LculNC9mXBw==} 67 | engines: {node: '>=12'} 68 | cpu: [arm64] 69 | os: [android] 70 | requiresBuild: true 71 | dev: true 72 | optional: true 73 | 74 | /@esbuild/android-arm@0.17.18: 75 | resolution: {integrity: sha512-EmwL+vUBZJ7mhFCs5lA4ZimpUH3WMAoqvOIYhVQwdIgSpHC8ImHdsRyhHAVxpDYUSm0lWvd63z0XH1IlImS2Qw==} 76 | engines: {node: '>=12'} 77 | cpu: [arm] 78 | os: [android] 79 | requiresBuild: true 80 | dev: true 81 | optional: true 82 | 83 | /@esbuild/android-x64@0.17.18: 84 | resolution: {integrity: sha512-x+0efYNBF3NPW2Xc5bFOSFW7tTXdAcpfEg2nXmxegm4mJuVeS+i109m/7HMiOQ6M12aVGGFlqJX3RhNdYM2lWg==} 85 | engines: {node: '>=12'} 86 | cpu: [x64] 87 | os: [android] 88 | requiresBuild: true 89 | dev: true 90 | optional: true 91 | 92 | /@esbuild/darwin-arm64@0.17.18: 93 | resolution: {integrity: sha512-6tY+djEAdF48M1ONWnQb1C+6LiXrKjmqjzPNPWXhu/GzOHTHX2nh8Mo2ZAmBFg0kIodHhciEgUBtcYCAIjGbjQ==} 94 | engines: {node: '>=12'} 95 | cpu: [arm64] 96 | os: [darwin] 97 | requiresBuild: true 98 | dev: true 99 | optional: true 100 | 101 | /@esbuild/darwin-x64@0.17.18: 102 | resolution: {integrity: sha512-Qq84ykvLvya3dO49wVC9FFCNUfSrQJLbxhoQk/TE1r6MjHo3sFF2tlJCwMjhkBVq3/ahUisj7+EpRSz0/+8+9A==} 103 | engines: {node: '>=12'} 104 | cpu: [x64] 105 | os: [darwin] 106 | requiresBuild: true 107 | dev: true 108 | optional: true 109 | 110 | /@esbuild/freebsd-arm64@0.17.18: 111 | resolution: {integrity: sha512-fw/ZfxfAzuHfaQeMDhbzxp9mc+mHn1Y94VDHFHjGvt2Uxl10mT4CDavHm+/L9KG441t1QdABqkVYwakMUeyLRA==} 112 | engines: {node: '>=12'} 113 | cpu: [arm64] 114 | os: [freebsd] 115 | requiresBuild: true 116 | dev: true 117 | optional: true 118 | 119 | /@esbuild/freebsd-x64@0.17.18: 120 | resolution: {integrity: sha512-FQFbRtTaEi8ZBi/A6kxOC0V0E9B/97vPdYjY9NdawyLd4Qk5VD5g2pbWN2VR1c0xhzcJm74HWpObPszWC+qTew==} 121 | engines: {node: '>=12'} 122 | cpu: [x64] 123 | os: [freebsd] 124 | requiresBuild: true 125 | dev: true 126 | optional: true 127 | 128 | /@esbuild/linux-arm64@0.17.18: 129 | resolution: {integrity: sha512-R7pZvQZFOY2sxUG8P6A21eq6q+eBv7JPQYIybHVf1XkQYC+lT7nDBdC7wWKTrbvMXKRaGudp/dzZCwL/863mZQ==} 130 | engines: {node: '>=12'} 131 | cpu: [arm64] 132 | os: [linux] 133 | requiresBuild: true 134 | dev: true 135 | optional: true 136 | 137 | /@esbuild/linux-arm@0.17.18: 138 | resolution: {integrity: sha512-jW+UCM40LzHcouIaqv3e/oRs0JM76JfhHjCavPxMUti7VAPh8CaGSlS7cmyrdpzSk7A+8f0hiedHqr/LMnfijg==} 139 | engines: {node: '>=12'} 140 | cpu: [arm] 141 | os: [linux] 142 | requiresBuild: true 143 | dev: true 144 | optional: true 145 | 146 | /@esbuild/linux-ia32@0.17.18: 147 | resolution: {integrity: sha512-ygIMc3I7wxgXIxk6j3V00VlABIjq260i967Cp9BNAk5pOOpIXmd1RFQJQX9Io7KRsthDrQYrtcx7QCof4o3ZoQ==} 148 | engines: {node: '>=12'} 149 | cpu: [ia32] 150 | os: [linux] 151 | requiresBuild: true 152 | dev: true 153 | optional: true 154 | 155 | /@esbuild/linux-loong64@0.17.18: 156 | resolution: {integrity: sha512-bvPG+MyFs5ZlwYclCG1D744oHk1Pv7j8psF5TfYx7otCVmcJsEXgFEhQkbhNW8otDHL1a2KDINW20cfCgnzgMQ==} 157 | engines: {node: '>=12'} 158 | cpu: [loong64] 159 | os: [linux] 160 | requiresBuild: true 161 | dev: true 162 | optional: true 163 | 164 | /@esbuild/linux-mips64el@0.17.18: 165 | resolution: {integrity: sha512-oVqckATOAGuiUOa6wr8TXaVPSa+6IwVJrGidmNZS1cZVx0HqkTMkqFGD2HIx9H1RvOwFeWYdaYbdY6B89KUMxA==} 166 | engines: {node: '>=12'} 167 | cpu: [mips64el] 168 | os: [linux] 169 | requiresBuild: true 170 | dev: true 171 | optional: true 172 | 173 | /@esbuild/linux-ppc64@0.17.18: 174 | resolution: {integrity: sha512-3dLlQO+b/LnQNxgH4l9rqa2/IwRJVN9u/bK63FhOPB4xqiRqlQAU0qDU3JJuf0BmaH0yytTBdoSBHrb2jqc5qQ==} 175 | engines: {node: '>=12'} 176 | cpu: [ppc64] 177 | os: [linux] 178 | requiresBuild: true 179 | dev: true 180 | optional: true 181 | 182 | /@esbuild/linux-riscv64@0.17.18: 183 | resolution: {integrity: sha512-/x7leOyDPjZV3TcsdfrSI107zItVnsX1q2nho7hbbQoKnmoeUWjs+08rKKt4AUXju7+3aRZSsKrJtaRmsdL1xA==} 184 | engines: {node: '>=12'} 185 | cpu: [riscv64] 186 | os: [linux] 187 | requiresBuild: true 188 | dev: true 189 | optional: true 190 | 191 | /@esbuild/linux-s390x@0.17.18: 192 | resolution: {integrity: sha512-cX0I8Q9xQkL/6F5zWdYmVf5JSQt+ZfZD2bJudZrWD+4mnUvoZ3TDDXtDX2mUaq6upMFv9FlfIh4Gfun0tbGzuw==} 193 | engines: {node: '>=12'} 194 | cpu: [s390x] 195 | os: [linux] 196 | requiresBuild: true 197 | dev: true 198 | optional: true 199 | 200 | /@esbuild/linux-x64@0.17.18: 201 | resolution: {integrity: sha512-66RmRsPlYy4jFl0vG80GcNRdirx4nVWAzJmXkevgphP1qf4dsLQCpSKGM3DUQCojwU1hnepI63gNZdrr02wHUA==} 202 | engines: {node: '>=12'} 203 | cpu: [x64] 204 | os: [linux] 205 | requiresBuild: true 206 | dev: true 207 | optional: true 208 | 209 | /@esbuild/netbsd-x64@0.17.18: 210 | resolution: {integrity: sha512-95IRY7mI2yrkLlTLb1gpDxdC5WLC5mZDi+kA9dmM5XAGxCME0F8i4bYH4jZreaJ6lIZ0B8hTrweqG1fUyW7jbg==} 211 | engines: {node: '>=12'} 212 | cpu: [x64] 213 | os: [netbsd] 214 | requiresBuild: true 215 | dev: true 216 | optional: true 217 | 218 | /@esbuild/openbsd-x64@0.17.18: 219 | resolution: {integrity: sha512-WevVOgcng+8hSZ4Q3BKL3n1xTv5H6Nb53cBrtzzEjDbbnOmucEVcZeGCsCOi9bAOcDYEeBZbD2SJNBxlfP3qiA==} 220 | engines: {node: '>=12'} 221 | cpu: [x64] 222 | os: [openbsd] 223 | requiresBuild: true 224 | dev: true 225 | optional: true 226 | 227 | /@esbuild/sunos-x64@0.17.18: 228 | resolution: {integrity: sha512-Rzf4QfQagnwhQXVBS3BYUlxmEbcV7MY+BH5vfDZekU5eYpcffHSyjU8T0xucKVuOcdCsMo+Ur5wmgQJH2GfNrg==} 229 | engines: {node: '>=12'} 230 | cpu: [x64] 231 | os: [sunos] 232 | requiresBuild: true 233 | dev: true 234 | optional: true 235 | 236 | /@esbuild/win32-arm64@0.17.18: 237 | resolution: {integrity: sha512-Kb3Ko/KKaWhjeAm2YoT/cNZaHaD1Yk/pa3FTsmqo9uFh1D1Rfco7BBLIPdDOozrObj2sahslFuAQGvWbgWldAg==} 238 | engines: {node: '>=12'} 239 | cpu: [arm64] 240 | os: [win32] 241 | requiresBuild: true 242 | dev: true 243 | optional: true 244 | 245 | /@esbuild/win32-ia32@0.17.18: 246 | resolution: {integrity: sha512-0/xUMIdkVHwkvxfbd5+lfG7mHOf2FRrxNbPiKWg9C4fFrB8H0guClmaM3BFiRUYrznVoyxTIyC/Ou2B7QQSwmw==} 247 | engines: {node: '>=12'} 248 | cpu: [ia32] 249 | os: [win32] 250 | requiresBuild: true 251 | dev: true 252 | optional: true 253 | 254 | /@esbuild/win32-x64@0.17.18: 255 | resolution: {integrity: sha512-qU25Ma1I3NqTSHJUOKi9sAH1/Mzuvlke0ioMJRthLXKm7JiSKVwFghlGbDLOO2sARECGhja4xYfRAZNPAkooYg==} 256 | engines: {node: '>=12'} 257 | cpu: [x64] 258 | os: [win32] 259 | requiresBuild: true 260 | dev: true 261 | optional: true 262 | 263 | /@fontsource/pangolin@4.5.9: 264 | resolution: {integrity: sha512-Ofdh94TJMk+YawvwKDwisIp68l+vJ9Q4sms34xZi61HdqTxoUgdHpvtpf71b+24SO9UkD7ULHnbLPGx4IxXgDA==} 265 | dev: false 266 | 267 | /@jridgewell/gen-mapping@0.3.3: 268 | resolution: {integrity: sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==} 269 | engines: {node: '>=6.0.0'} 270 | dependencies: 271 | '@jridgewell/set-array': 1.1.2 272 | '@jridgewell/sourcemap-codec': 1.4.15 273 | '@jridgewell/trace-mapping': 0.3.18 274 | dev: true 275 | 276 | /@jridgewell/resolve-uri@3.1.0: 277 | resolution: {integrity: sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==} 278 | engines: {node: '>=6.0.0'} 279 | dev: true 280 | 281 | /@jridgewell/set-array@1.1.2: 282 | resolution: {integrity: sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==} 283 | engines: {node: '>=6.0.0'} 284 | dev: true 285 | 286 | /@jridgewell/sourcemap-codec@1.4.14: 287 | resolution: {integrity: sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==} 288 | dev: true 289 | 290 | /@jridgewell/sourcemap-codec@1.4.15: 291 | resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==} 292 | dev: true 293 | 294 | /@jridgewell/trace-mapping@0.3.18: 295 | resolution: {integrity: sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==} 296 | dependencies: 297 | '@jridgewell/resolve-uri': 3.1.0 298 | '@jridgewell/sourcemap-codec': 1.4.14 299 | dev: true 300 | 301 | /@mapbox/node-pre-gyp@1.0.10: 302 | resolution: {integrity: sha512-4ySo4CjzStuprMwk35H5pPbkymjv1SF3jGLj6rAHp/xT/RF7TL7bd9CTm1xDY49K2qF7jmR/g7k+SkLETP6opA==} 303 | hasBin: true 304 | dependencies: 305 | detect-libc: 2.0.1 306 | https-proxy-agent: 5.0.1 307 | make-dir: 3.1.0 308 | node-fetch: 2.6.9 309 | nopt: 5.0.0 310 | npmlog: 5.0.1 311 | rimraf: 3.0.2 312 | semver: 7.5.0 313 | tar: 6.1.14 314 | transitivePeerDependencies: 315 | - encoding 316 | - supports-color 317 | dev: true 318 | 319 | /@neondatabase/serverless@0.3.0: 320 | resolution: {integrity: sha512-hcVz1pL4WzQBIRMfrQyJ65pidPL1JVZnAsDJbLXr/DqLZTAYc/9rtV9oZGSKFYqczJ7E1MVMwW7aVrebCfWgeg==} 321 | dev: false 322 | 323 | /@nodelib/fs.scandir@2.1.5: 324 | resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} 325 | engines: {node: '>= 8'} 326 | dependencies: 327 | '@nodelib/fs.stat': 2.0.5 328 | run-parallel: 1.2.0 329 | dev: true 330 | 331 | /@nodelib/fs.stat@2.0.5: 332 | resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} 333 | engines: {node: '>= 8'} 334 | dev: true 335 | 336 | /@nodelib/fs.walk@1.2.8: 337 | resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} 338 | engines: {node: '>= 8'} 339 | dependencies: 340 | '@nodelib/fs.scandir': 2.1.5 341 | fastq: 1.15.0 342 | dev: true 343 | 344 | /@polka/url@1.0.0-next.21: 345 | resolution: {integrity: sha512-a5Sab1C4/icpTZVzZc5Ghpz88yQtGOyNqYXcZgOssB2uuAr+wF/MvN6bgtW32q7HHrvBki+BsZ0OuNv6EV3K9g==} 346 | dev: true 347 | 348 | /@rollup/pluginutils@4.2.1: 349 | resolution: {integrity: sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==} 350 | engines: {node: '>= 8.0.0'} 351 | dependencies: 352 | estree-walker: 2.0.2 353 | picomatch: 2.3.1 354 | dev: true 355 | 356 | /@sveltejs/adapter-vercel@2.4.3(@sveltejs/kit@1.16.2): 357 | resolution: {integrity: sha512-3k/3udwaioFYdKDAgQcWSByB+KCbtjX+ARonYGCtYE0iuxWLStrESxy3SaU+17XD5Frh8w7tfY8ft4TV3ej3Dg==} 358 | peerDependencies: 359 | '@sveltejs/kit': ^1.5.0 360 | dependencies: 361 | '@sveltejs/kit': 1.16.2(svelte@3.58.0)(vite@4.3.4) 362 | '@vercel/nft': 0.22.6 363 | esbuild: 0.17.18 364 | transitivePeerDependencies: 365 | - encoding 366 | - supports-color 367 | dev: true 368 | 369 | /@sveltejs/kit@1.16.2(svelte@3.58.0)(vite@4.3.4): 370 | resolution: {integrity: sha512-yxcpA4nvlVlJ+VyYnj0zD3QN05kfmoh4OyitlPrVG34nnZSHzFpE4eZ33X1A/tc9prslSFRhpM6rWngCs0nM8w==} 371 | engines: {node: ^16.14 || >=18} 372 | hasBin: true 373 | requiresBuild: true 374 | peerDependencies: 375 | svelte: ^3.54.0 376 | vite: ^4.0.0 377 | dependencies: 378 | '@sveltejs/vite-plugin-svelte': 2.1.1(svelte@3.58.0)(vite@4.3.4) 379 | '@types/cookie': 0.5.1 380 | cookie: 0.5.0 381 | devalue: 4.3.0 382 | esm-env: 1.0.0 383 | kleur: 4.1.5 384 | magic-string: 0.30.0 385 | mime: 3.0.0 386 | sade: 1.8.1 387 | set-cookie-parser: 2.6.0 388 | sirv: 2.0.3 389 | svelte: 3.58.0 390 | tiny-glob: 0.2.9 391 | undici: 5.22.0 392 | vite: 4.3.4 393 | transitivePeerDependencies: 394 | - supports-color 395 | dev: true 396 | 397 | /@sveltejs/vite-plugin-svelte@2.1.1(svelte@3.58.0)(vite@4.3.4): 398 | resolution: {integrity: sha512-7YeBDt4us0FiIMNsVXxyaP4Hwyn2/v9x3oqStkHU3ZdIc5O22pGwUwH33wUqYo+7Itdmo8zxJ45Qvfm3H7UUjQ==} 399 | engines: {node: ^14.18.0 || >= 16} 400 | peerDependencies: 401 | svelte: ^3.54.0 402 | vite: ^4.0.0 403 | dependencies: 404 | debug: 4.3.4 405 | deepmerge: 4.3.1 406 | kleur: 4.1.5 407 | magic-string: 0.30.0 408 | svelte: 3.58.0 409 | svelte-hmr: 0.15.1(svelte@3.58.0) 410 | vite: 4.3.4 411 | vitefu: 0.2.4(vite@4.3.4) 412 | transitivePeerDependencies: 413 | - supports-color 414 | dev: true 415 | 416 | /@types/cookie@0.5.1: 417 | resolution: {integrity: sha512-COUnqfB2+ckwXXSFInsFdOAWQzCCx+a5hq2ruyj+Vjund94RJQd4LG2u9hnvJrTgunKAaax7ancBYlDrNYxA0g==} 418 | dev: true 419 | 420 | /@types/pug@2.0.6: 421 | resolution: {integrity: sha512-SnHmG9wN1UVmagJOnyo/qkk0Z7gejYxOYYmaAwr5u2yFYfsupN3sg10kyzN8Hep/2zbHxCnsumxOoRIRMBwKCg==} 422 | dev: true 423 | 424 | /@vercel/blob@0.3.2: 425 | resolution: {integrity: sha512-p0/kpUJvyfa40GpCLvIfbJp3wyEUfoaU94ybSpy5Ha8kNsIIvLiLFQeLCdvRqf0/dl4d24ZoeUY4w5ZIg+OYrQ==} 426 | engines: {node: '>=16.14'} 427 | dependencies: 428 | undici: 5.22.0 429 | dev: false 430 | 431 | /@vercel/nft@0.22.6: 432 | resolution: {integrity: sha512-gTsFnnT4mGxodr4AUlW3/urY+8JKKB452LwF3m477RFUJTAaDmcz2JqFuInzvdybYIeyIv1sSONEJxsxnbQ5JQ==} 433 | engines: {node: '>=14'} 434 | hasBin: true 435 | dependencies: 436 | '@mapbox/node-pre-gyp': 1.0.10 437 | '@rollup/pluginutils': 4.2.1 438 | acorn: 8.8.2 439 | async-sema: 3.1.1 440 | bindings: 1.5.0 441 | estree-walker: 2.0.2 442 | glob: 7.2.3 443 | graceful-fs: 4.2.11 444 | micromatch: 4.0.5 445 | node-gyp-build: 4.6.0 446 | resolve-from: 5.0.0 447 | transitivePeerDependencies: 448 | - encoding 449 | - supports-color 450 | dev: true 451 | 452 | /@vercel/postgres@0.1.0-canary.5: 453 | resolution: {integrity: sha512-kyYERT38q3RLlziv00ynp6+2h6PewuBPwINHRCwlcgA2wLUimUUOyT3PcgNvu0Ofhta3/iAcTx/Rx6GabEw7Lw==} 454 | engines: {node: '>=14.6'} 455 | dependencies: 456 | '@neondatabase/serverless': 0.3.0 457 | dev: false 458 | 459 | /abbrev@1.1.1: 460 | resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==} 461 | dev: true 462 | 463 | /acorn@8.8.2: 464 | resolution: {integrity: sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==} 465 | engines: {node: '>=0.4.0'} 466 | hasBin: true 467 | dev: true 468 | 469 | /agent-base@6.0.2: 470 | resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} 471 | engines: {node: '>= 6.0.0'} 472 | dependencies: 473 | debug: 4.3.4 474 | transitivePeerDependencies: 475 | - supports-color 476 | dev: true 477 | 478 | /ansi-regex@5.0.1: 479 | resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} 480 | engines: {node: '>=8'} 481 | dev: true 482 | 483 | /any-promise@1.3.0: 484 | resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} 485 | dev: true 486 | 487 | /anymatch@3.1.3: 488 | resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} 489 | engines: {node: '>= 8'} 490 | dependencies: 491 | normalize-path: 3.0.0 492 | picomatch: 2.3.1 493 | dev: true 494 | 495 | /aproba@2.0.0: 496 | resolution: {integrity: sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==} 497 | dev: true 498 | 499 | /are-we-there-yet@2.0.0: 500 | resolution: {integrity: sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==} 501 | engines: {node: '>=10'} 502 | dependencies: 503 | delegates: 1.0.0 504 | readable-stream: 3.6.2 505 | dev: true 506 | 507 | /arg@5.0.2: 508 | resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} 509 | dev: true 510 | 511 | /async-sema@3.1.1: 512 | resolution: {integrity: sha512-tLRNUXati5MFePdAk8dw7Qt7DpxPB60ofAgn8WRhW6a2rcimZnYBP9oxHiv0OHy+Wz7kPMG+t4LGdt31+4EmGg==} 513 | dev: true 514 | 515 | /autoprefixer@10.4.14(postcss@8.4.23): 516 | resolution: {integrity: sha512-FQzyfOsTlwVzjHxKEqRIAdJx9niO6VCBCoEwax/VLSoQF29ggECcPuBqUMZ+u8jCZOPSy8b8/8KnuFbp0SaFZQ==} 517 | engines: {node: ^10 || ^12 || >=14} 518 | hasBin: true 519 | peerDependencies: 520 | postcss: ^8.1.0 521 | dependencies: 522 | browserslist: 4.21.5 523 | caniuse-lite: 1.0.30001482 524 | fraction.js: 4.2.0 525 | normalize-range: 0.1.2 526 | picocolors: 1.0.0 527 | postcss: 8.4.23 528 | postcss-value-parser: 4.2.0 529 | dev: true 530 | 531 | /autosize@6.0.1: 532 | resolution: {integrity: sha512-f86EjiUKE6Xvczc4ioP1JBlWG7FKrE13qe/DxBCpe8GCipCq2nFw73aO8QEBKHfSbYGDN5eB9jXWKen7tspDqQ==} 533 | dev: false 534 | 535 | /balanced-match@1.0.2: 536 | resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} 537 | dev: true 538 | 539 | /binary-extensions@2.2.0: 540 | resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==} 541 | engines: {node: '>=8'} 542 | dev: true 543 | 544 | /bindings@1.5.0: 545 | resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==} 546 | dependencies: 547 | file-uri-to-path: 1.0.0 548 | dev: true 549 | 550 | /brace-expansion@1.1.11: 551 | resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} 552 | dependencies: 553 | balanced-match: 1.0.2 554 | concat-map: 0.0.1 555 | dev: true 556 | 557 | /braces@3.0.2: 558 | resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} 559 | engines: {node: '>=8'} 560 | dependencies: 561 | fill-range: 7.0.1 562 | dev: true 563 | 564 | /browserslist@4.21.5: 565 | resolution: {integrity: sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w==} 566 | engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} 567 | hasBin: true 568 | dependencies: 569 | caniuse-lite: 1.0.30001482 570 | electron-to-chromium: 1.4.380 571 | node-releases: 2.0.10 572 | update-browserslist-db: 1.0.11(browserslist@4.21.5) 573 | dev: true 574 | 575 | /buffer-crc32@0.2.13: 576 | resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==} 577 | dev: true 578 | 579 | /busboy@1.6.0: 580 | resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==} 581 | engines: {node: '>=10.16.0'} 582 | dependencies: 583 | streamsearch: 1.1.0 584 | 585 | /callsites@3.1.0: 586 | resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} 587 | engines: {node: '>=6'} 588 | dev: true 589 | 590 | /camelcase-css@2.0.1: 591 | resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==} 592 | engines: {node: '>= 6'} 593 | dev: true 594 | 595 | /caniuse-lite@1.0.30001482: 596 | resolution: {integrity: sha512-F1ZInsg53cegyjroxLNW9DmrEQ1SuGRTO1QlpA0o2/6OpQ0gFeDRoq1yFmnr8Sakn9qwwt9DmbxHB6w167OSuQ==} 597 | dev: true 598 | 599 | /chokidar@3.5.3: 600 | resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==} 601 | engines: {node: '>= 8.10.0'} 602 | dependencies: 603 | anymatch: 3.1.3 604 | braces: 3.0.2 605 | glob-parent: 5.1.2 606 | is-binary-path: 2.1.0 607 | is-glob: 4.0.3 608 | normalize-path: 3.0.0 609 | readdirp: 3.6.0 610 | optionalDependencies: 611 | fsevents: 2.3.2 612 | dev: true 613 | 614 | /chownr@2.0.0: 615 | resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==} 616 | engines: {node: '>=10'} 617 | dev: true 618 | 619 | /color-support@1.1.3: 620 | resolution: {integrity: sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==} 621 | hasBin: true 622 | dev: true 623 | 624 | /commander@4.1.1: 625 | resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} 626 | engines: {node: '>= 6'} 627 | dev: true 628 | 629 | /concat-map@0.0.1: 630 | resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} 631 | dev: true 632 | 633 | /console-control-strings@1.1.0: 634 | resolution: {integrity: sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==} 635 | dev: true 636 | 637 | /cookie@0.5.0: 638 | resolution: {integrity: sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==} 639 | engines: {node: '>= 0.6'} 640 | dev: true 641 | 642 | /cssesc@3.0.0: 643 | resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} 644 | engines: {node: '>=4'} 645 | hasBin: true 646 | dev: true 647 | 648 | /debug@4.3.4: 649 | resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} 650 | engines: {node: '>=6.0'} 651 | peerDependencies: 652 | supports-color: '*' 653 | peerDependenciesMeta: 654 | supports-color: 655 | optional: true 656 | dependencies: 657 | ms: 2.1.2 658 | dev: true 659 | 660 | /deepmerge@4.3.1: 661 | resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} 662 | engines: {node: '>=0.10.0'} 663 | dev: true 664 | 665 | /delegates@1.0.0: 666 | resolution: {integrity: sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==} 667 | dev: true 668 | 669 | /detect-indent@6.1.0: 670 | resolution: {integrity: sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==} 671 | engines: {node: '>=8'} 672 | dev: true 673 | 674 | /detect-libc@2.0.1: 675 | resolution: {integrity: sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w==} 676 | engines: {node: '>=8'} 677 | dev: true 678 | 679 | /devalue@4.3.0: 680 | resolution: {integrity: sha512-n94yQo4LI3w7erwf84mhRUkUJfhLoCZiLyoOZ/QFsDbcWNZePrLwbQpvZBUG2TNxwV3VjCKPxkiiQA6pe3TrTA==} 681 | dev: true 682 | 683 | /didyoumean@1.2.2: 684 | resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} 685 | dev: true 686 | 687 | /dlv@1.1.3: 688 | resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==} 689 | dev: true 690 | 691 | /electron-to-chromium@1.4.380: 692 | resolution: {integrity: sha512-XKGdI4pWM78eLH2cbXJHiBnWUwFSzZM7XujsB6stDiGu9AeSqziedP6amNLpJzE3i0rLTcfAwdCTs5ecP5yeSg==} 693 | dev: true 694 | 695 | /emoji-regex@8.0.0: 696 | resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} 697 | dev: true 698 | 699 | /es6-promise@3.3.1: 700 | resolution: {integrity: sha512-SOp9Phqvqn7jtEUxPWdWfWoLmyt2VaJ6MpvP9Comy1MceMXqE6bxvaTu4iaxpYYPzhny28Lc+M87/c2cPK6lDg==} 701 | dev: true 702 | 703 | /esbuild@0.17.18: 704 | resolution: {integrity: sha512-z1lix43jBs6UKjcZVKOw2xx69ffE2aG0PygLL5qJ9OS/gy0Ewd1gW/PUQIOIQGXBHWNywSc0floSKoMFF8aK2w==} 705 | engines: {node: '>=12'} 706 | hasBin: true 707 | requiresBuild: true 708 | optionalDependencies: 709 | '@esbuild/android-arm': 0.17.18 710 | '@esbuild/android-arm64': 0.17.18 711 | '@esbuild/android-x64': 0.17.18 712 | '@esbuild/darwin-arm64': 0.17.18 713 | '@esbuild/darwin-x64': 0.17.18 714 | '@esbuild/freebsd-arm64': 0.17.18 715 | '@esbuild/freebsd-x64': 0.17.18 716 | '@esbuild/linux-arm': 0.17.18 717 | '@esbuild/linux-arm64': 0.17.18 718 | '@esbuild/linux-ia32': 0.17.18 719 | '@esbuild/linux-loong64': 0.17.18 720 | '@esbuild/linux-mips64el': 0.17.18 721 | '@esbuild/linux-ppc64': 0.17.18 722 | '@esbuild/linux-riscv64': 0.17.18 723 | '@esbuild/linux-s390x': 0.17.18 724 | '@esbuild/linux-x64': 0.17.18 725 | '@esbuild/netbsd-x64': 0.17.18 726 | '@esbuild/openbsd-x64': 0.17.18 727 | '@esbuild/sunos-x64': 0.17.18 728 | '@esbuild/win32-arm64': 0.17.18 729 | '@esbuild/win32-ia32': 0.17.18 730 | '@esbuild/win32-x64': 0.17.18 731 | dev: true 732 | 733 | /escalade@3.1.1: 734 | resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==} 735 | engines: {node: '>=6'} 736 | dev: true 737 | 738 | /esm-env@1.0.0: 739 | resolution: {integrity: sha512-Cf6VksWPsTuW01vU9Mk/3vRue91Zevka5SjyNf3nEpokFRuqt/KjUQoGAwq9qMmhpLTHmXzSIrFRw8zxWzmFBA==} 740 | dev: true 741 | 742 | /estree-walker@2.0.2: 743 | resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} 744 | dev: true 745 | 746 | /fast-glob@3.2.12: 747 | resolution: {integrity: sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==} 748 | engines: {node: '>=8.6.0'} 749 | dependencies: 750 | '@nodelib/fs.stat': 2.0.5 751 | '@nodelib/fs.walk': 1.2.8 752 | glob-parent: 5.1.2 753 | merge2: 1.4.1 754 | micromatch: 4.0.5 755 | dev: true 756 | 757 | /fastq@1.15.0: 758 | resolution: {integrity: sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==} 759 | dependencies: 760 | reusify: 1.0.4 761 | dev: true 762 | 763 | /file-uri-to-path@1.0.0: 764 | resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==} 765 | dev: true 766 | 767 | /fill-range@7.0.1: 768 | resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==} 769 | engines: {node: '>=8'} 770 | dependencies: 771 | to-regex-range: 5.0.1 772 | dev: true 773 | 774 | /fraction.js@4.2.0: 775 | resolution: {integrity: sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==} 776 | dev: true 777 | 778 | /fs-minipass@2.1.0: 779 | resolution: {integrity: sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==} 780 | engines: {node: '>= 8'} 781 | dependencies: 782 | minipass: 3.3.6 783 | dev: true 784 | 785 | /fs.realpath@1.0.0: 786 | resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} 787 | dev: true 788 | 789 | /fsevents@2.3.2: 790 | resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} 791 | engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} 792 | os: [darwin] 793 | requiresBuild: true 794 | dev: true 795 | optional: true 796 | 797 | /function-bind@1.1.1: 798 | resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==} 799 | dev: true 800 | 801 | /gauge@3.0.2: 802 | resolution: {integrity: sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==} 803 | engines: {node: '>=10'} 804 | dependencies: 805 | aproba: 2.0.0 806 | color-support: 1.1.3 807 | console-control-strings: 1.1.0 808 | has-unicode: 2.0.1 809 | object-assign: 4.1.1 810 | signal-exit: 3.0.7 811 | string-width: 4.2.3 812 | strip-ansi: 6.0.1 813 | wide-align: 1.1.5 814 | dev: true 815 | 816 | /glob-parent@5.1.2: 817 | resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} 818 | engines: {node: '>= 6'} 819 | dependencies: 820 | is-glob: 4.0.3 821 | dev: true 822 | 823 | /glob-parent@6.0.2: 824 | resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} 825 | engines: {node: '>=10.13.0'} 826 | dependencies: 827 | is-glob: 4.0.3 828 | dev: true 829 | 830 | /glob@7.1.6: 831 | resolution: {integrity: sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==} 832 | dependencies: 833 | fs.realpath: 1.0.0 834 | inflight: 1.0.6 835 | inherits: 2.0.4 836 | minimatch: 3.1.2 837 | once: 1.4.0 838 | path-is-absolute: 1.0.1 839 | dev: true 840 | 841 | /glob@7.2.3: 842 | resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} 843 | dependencies: 844 | fs.realpath: 1.0.0 845 | inflight: 1.0.6 846 | inherits: 2.0.4 847 | minimatch: 3.1.2 848 | once: 1.4.0 849 | path-is-absolute: 1.0.1 850 | dev: true 851 | 852 | /globalyzer@0.1.0: 853 | resolution: {integrity: sha512-40oNTM9UfG6aBmuKxk/giHn5nQ8RVz/SS4Ir6zgzOv9/qC3kKZ9v4etGTcJbEl/NyVQH7FGU7d+X1egr57Md2Q==} 854 | dev: true 855 | 856 | /globrex@0.1.2: 857 | resolution: {integrity: sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==} 858 | dev: true 859 | 860 | /graceful-fs@4.2.11: 861 | resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} 862 | dev: true 863 | 864 | /has-unicode@2.0.1: 865 | resolution: {integrity: sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==} 866 | dev: true 867 | 868 | /has@1.0.3: 869 | resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==} 870 | engines: {node: '>= 0.4.0'} 871 | dependencies: 872 | function-bind: 1.1.1 873 | dev: true 874 | 875 | /https-proxy-agent@5.0.1: 876 | resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} 877 | engines: {node: '>= 6'} 878 | dependencies: 879 | agent-base: 6.0.2 880 | debug: 4.3.4 881 | transitivePeerDependencies: 882 | - supports-color 883 | dev: true 884 | 885 | /import-fresh@3.3.0: 886 | resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} 887 | engines: {node: '>=6'} 888 | dependencies: 889 | parent-module: 1.0.1 890 | resolve-from: 4.0.0 891 | dev: true 892 | 893 | /inflight@1.0.6: 894 | resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} 895 | dependencies: 896 | once: 1.4.0 897 | wrappy: 1.0.2 898 | dev: true 899 | 900 | /inherits@2.0.4: 901 | resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} 902 | dev: true 903 | 904 | /is-binary-path@2.1.0: 905 | resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} 906 | engines: {node: '>=8'} 907 | dependencies: 908 | binary-extensions: 2.2.0 909 | dev: true 910 | 911 | /is-core-module@2.12.0: 912 | resolution: {integrity: sha512-RECHCBCd/viahWmwj6enj19sKbHfJrddi/6cBDsNTKbNq0f7VeaUkBo60BqzvPqo/W54ChS62Z5qyun7cfOMqQ==} 913 | dependencies: 914 | has: 1.0.3 915 | dev: true 916 | 917 | /is-extglob@2.1.1: 918 | resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} 919 | engines: {node: '>=0.10.0'} 920 | dev: true 921 | 922 | /is-fullwidth-code-point@3.0.0: 923 | resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} 924 | engines: {node: '>=8'} 925 | dev: true 926 | 927 | /is-glob@4.0.3: 928 | resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} 929 | engines: {node: '>=0.10.0'} 930 | dependencies: 931 | is-extglob: 2.1.1 932 | dev: true 933 | 934 | /is-number@7.0.0: 935 | resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} 936 | engines: {node: '>=0.12.0'} 937 | dev: true 938 | 939 | /jiti@1.18.2: 940 | resolution: {integrity: sha512-QAdOptna2NYiSSpv0O/BwoHBSmz4YhpzJHyi+fnMRTXFjp7B8i/YG5Z8IfusxB1ufjcD2Sre1F3R+nX3fvy7gg==} 941 | hasBin: true 942 | dev: true 943 | 944 | /kleur@4.1.5: 945 | resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} 946 | engines: {node: '>=6'} 947 | dev: true 948 | 949 | /lilconfig@2.1.0: 950 | resolution: {integrity: sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==} 951 | engines: {node: '>=10'} 952 | dev: true 953 | 954 | /lines-and-columns@1.2.4: 955 | resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} 956 | dev: true 957 | 958 | /lru-cache@6.0.0: 959 | resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} 960 | engines: {node: '>=10'} 961 | dependencies: 962 | yallist: 4.0.0 963 | dev: true 964 | 965 | /magic-string@0.27.0: 966 | resolution: {integrity: sha512-8UnnX2PeRAPZuN12svgR9j7M1uWMovg/CEnIwIG0LFkXSJJe4PdfUGiTGl8V9bsBHFUtfVINcSyYxd7q+kx9fA==} 967 | engines: {node: '>=12'} 968 | dependencies: 969 | '@jridgewell/sourcemap-codec': 1.4.15 970 | dev: true 971 | 972 | /magic-string@0.30.0: 973 | resolution: {integrity: sha512-LA+31JYDJLs82r2ScLrlz1GjSgu66ZV518eyWT+S8VhyQn/JL0u9MeBOvQMGYiPk1DBiSN9DDMOcXvigJZaViQ==} 974 | engines: {node: '>=12'} 975 | dependencies: 976 | '@jridgewell/sourcemap-codec': 1.4.15 977 | dev: true 978 | 979 | /make-dir@3.1.0: 980 | resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==} 981 | engines: {node: '>=8'} 982 | dependencies: 983 | semver: 6.3.0 984 | dev: true 985 | 986 | /merge2@1.4.1: 987 | resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} 988 | engines: {node: '>= 8'} 989 | dev: true 990 | 991 | /micromatch@4.0.5: 992 | resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==} 993 | engines: {node: '>=8.6'} 994 | dependencies: 995 | braces: 3.0.2 996 | picomatch: 2.3.1 997 | dev: true 998 | 999 | /mime@3.0.0: 1000 | resolution: {integrity: sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==} 1001 | engines: {node: '>=10.0.0'} 1002 | hasBin: true 1003 | dev: true 1004 | 1005 | /min-indent@1.0.1: 1006 | resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} 1007 | engines: {node: '>=4'} 1008 | dev: true 1009 | 1010 | /minimatch@3.1.2: 1011 | resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} 1012 | dependencies: 1013 | brace-expansion: 1.1.11 1014 | dev: true 1015 | 1016 | /minimist@1.2.8: 1017 | resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} 1018 | dev: true 1019 | 1020 | /minipass@3.3.6: 1021 | resolution: {integrity: sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==} 1022 | engines: {node: '>=8'} 1023 | dependencies: 1024 | yallist: 4.0.0 1025 | dev: true 1026 | 1027 | /minipass@5.0.0: 1028 | resolution: {integrity: sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==} 1029 | engines: {node: '>=8'} 1030 | dev: true 1031 | 1032 | /minizlib@2.1.2: 1033 | resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==} 1034 | engines: {node: '>= 8'} 1035 | dependencies: 1036 | minipass: 3.3.6 1037 | yallist: 4.0.0 1038 | dev: true 1039 | 1040 | /mkdirp@0.5.6: 1041 | resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==} 1042 | hasBin: true 1043 | dependencies: 1044 | minimist: 1.2.8 1045 | dev: true 1046 | 1047 | /mkdirp@1.0.4: 1048 | resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} 1049 | engines: {node: '>=10'} 1050 | hasBin: true 1051 | dev: true 1052 | 1053 | /mri@1.2.0: 1054 | resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} 1055 | engines: {node: '>=4'} 1056 | dev: true 1057 | 1058 | /mrmime@1.0.1: 1059 | resolution: {integrity: sha512-hzzEagAgDyoU1Q6yg5uI+AorQgdvMCur3FcKf7NhMKWsaYg+RnbTyHRa/9IlLF9rf455MOCtcqqrQQ83pPP7Uw==} 1060 | engines: {node: '>=10'} 1061 | dev: true 1062 | 1063 | /ms@2.1.2: 1064 | resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} 1065 | dev: true 1066 | 1067 | /mz@2.7.0: 1068 | resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} 1069 | dependencies: 1070 | any-promise: 1.3.0 1071 | object-assign: 4.1.1 1072 | thenify-all: 1.6.0 1073 | dev: true 1074 | 1075 | /nanoid@3.3.6: 1076 | resolution: {integrity: sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==} 1077 | engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} 1078 | hasBin: true 1079 | dev: true 1080 | 1081 | /node-fetch@2.6.9: 1082 | resolution: {integrity: sha512-DJm/CJkZkRjKKj4Zi4BsKVZh3ValV5IR5s7LVZnW+6YMh0W1BfNA8XSs6DLMGYlId5F3KnA70uu2qepcR08Qqg==} 1083 | engines: {node: 4.x || >=6.0.0} 1084 | peerDependencies: 1085 | encoding: ^0.1.0 1086 | peerDependenciesMeta: 1087 | encoding: 1088 | optional: true 1089 | dependencies: 1090 | whatwg-url: 5.0.0 1091 | dev: true 1092 | 1093 | /node-gyp-build@4.6.0: 1094 | resolution: {integrity: sha512-NTZVKn9IylLwUzaKjkas1e4u2DLNcV4rdYagA4PWdPwW87Bi7z+BznyKSRwS/761tV/lzCGXplWsiaMjLqP2zQ==} 1095 | hasBin: true 1096 | dev: true 1097 | 1098 | /node-releases@2.0.10: 1099 | resolution: {integrity: sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w==} 1100 | dev: true 1101 | 1102 | /nopt@5.0.0: 1103 | resolution: {integrity: sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==} 1104 | engines: {node: '>=6'} 1105 | hasBin: true 1106 | dependencies: 1107 | abbrev: 1.1.1 1108 | dev: true 1109 | 1110 | /normalize-path@3.0.0: 1111 | resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} 1112 | engines: {node: '>=0.10.0'} 1113 | dev: true 1114 | 1115 | /normalize-range@0.1.2: 1116 | resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==} 1117 | engines: {node: '>=0.10.0'} 1118 | dev: true 1119 | 1120 | /npmlog@5.0.1: 1121 | resolution: {integrity: sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==} 1122 | dependencies: 1123 | are-we-there-yet: 2.0.0 1124 | console-control-strings: 1.1.0 1125 | gauge: 3.0.2 1126 | set-blocking: 2.0.0 1127 | dev: true 1128 | 1129 | /object-assign@4.1.1: 1130 | resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} 1131 | engines: {node: '>=0.10.0'} 1132 | dev: true 1133 | 1134 | /object-hash@3.0.0: 1135 | resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==} 1136 | engines: {node: '>= 6'} 1137 | dev: true 1138 | 1139 | /once@1.4.0: 1140 | resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} 1141 | dependencies: 1142 | wrappy: 1.0.2 1143 | dev: true 1144 | 1145 | /parent-module@1.0.1: 1146 | resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} 1147 | engines: {node: '>=6'} 1148 | dependencies: 1149 | callsites: 3.1.0 1150 | dev: true 1151 | 1152 | /path-is-absolute@1.0.1: 1153 | resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} 1154 | engines: {node: '>=0.10.0'} 1155 | dev: true 1156 | 1157 | /path-parse@1.0.7: 1158 | resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} 1159 | dev: true 1160 | 1161 | /picocolors@1.0.0: 1162 | resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} 1163 | dev: true 1164 | 1165 | /picomatch@2.3.1: 1166 | resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} 1167 | engines: {node: '>=8.6'} 1168 | dev: true 1169 | 1170 | /pify@2.3.0: 1171 | resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} 1172 | engines: {node: '>=0.10.0'} 1173 | dev: true 1174 | 1175 | /pirates@4.0.5: 1176 | resolution: {integrity: sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==} 1177 | engines: {node: '>= 6'} 1178 | dev: true 1179 | 1180 | /postcss-import@15.1.0(postcss@8.4.23): 1181 | resolution: {integrity: sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==} 1182 | engines: {node: '>=14.0.0'} 1183 | peerDependencies: 1184 | postcss: ^8.0.0 1185 | dependencies: 1186 | postcss: 8.4.23 1187 | postcss-value-parser: 4.2.0 1188 | read-cache: 1.0.0 1189 | resolve: 1.22.2 1190 | dev: true 1191 | 1192 | /postcss-js@4.0.1(postcss@8.4.23): 1193 | resolution: {integrity: sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==} 1194 | engines: {node: ^12 || ^14 || >= 16} 1195 | peerDependencies: 1196 | postcss: ^8.4.21 1197 | dependencies: 1198 | camelcase-css: 2.0.1 1199 | postcss: 8.4.23 1200 | dev: true 1201 | 1202 | /postcss-load-config@4.0.1(postcss@8.4.23): 1203 | resolution: {integrity: sha512-vEJIc8RdiBRu3oRAI0ymerOn+7rPuMvRXslTvZUKZonDHFIczxztIyJ1urxM1x9JXEikvpWWTUUqal5j/8QgvA==} 1204 | engines: {node: '>= 14'} 1205 | peerDependencies: 1206 | postcss: '>=8.0.9' 1207 | ts-node: '>=9.0.0' 1208 | peerDependenciesMeta: 1209 | postcss: 1210 | optional: true 1211 | ts-node: 1212 | optional: true 1213 | dependencies: 1214 | lilconfig: 2.1.0 1215 | postcss: 8.4.23 1216 | yaml: 2.2.2 1217 | dev: true 1218 | 1219 | /postcss-nested@6.0.1(postcss@8.4.23): 1220 | resolution: {integrity: sha512-mEp4xPMi5bSWiMbsgoPfcP74lsWLHkQbZc3sY+jWYd65CUwXrUaTp0fmNpa01ZcETKlIgUdFN/MpS2xZtqL9dQ==} 1221 | engines: {node: '>=12.0'} 1222 | peerDependencies: 1223 | postcss: ^8.2.14 1224 | dependencies: 1225 | postcss: 8.4.23 1226 | postcss-selector-parser: 6.0.12 1227 | dev: true 1228 | 1229 | /postcss-selector-parser@6.0.12: 1230 | resolution: {integrity: sha512-NdxGCAZdRrwVI1sy59+Wzrh+pMMHxapGnpfenDVlMEXoOcvt4pGE0JLK9YY2F5dLxcFYA/YbVQKhcGU+FtSYQg==} 1231 | engines: {node: '>=4'} 1232 | dependencies: 1233 | cssesc: 3.0.0 1234 | util-deprecate: 1.0.2 1235 | dev: true 1236 | 1237 | /postcss-value-parser@4.2.0: 1238 | resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} 1239 | dev: true 1240 | 1241 | /postcss@8.4.23: 1242 | resolution: {integrity: sha512-bQ3qMcpF6A/YjR55xtoTr0jGOlnPOKAIMdOWiv0EIT6HVPEaJiJB4NLljSbiHoC2RX7DN5Uvjtpbg1NPdwv1oA==} 1243 | engines: {node: ^10 || ^12 || >=14} 1244 | dependencies: 1245 | nanoid: 3.3.6 1246 | picocolors: 1.0.0 1247 | source-map-js: 1.0.2 1248 | dev: true 1249 | 1250 | /postgres@3.3.4: 1251 | resolution: {integrity: sha512-XVu0+d/Y56pl2lSaf0c7V19AhAEfpVrhID1IENWN8nf0xch6hFq6dAov5dtUX6ZD46wfr1TxvLhxLtV8WnNsOg==} 1252 | dev: false 1253 | 1254 | /prettier-plugin-svelte@2.10.0(prettier@2.8.8)(svelte@3.58.0): 1255 | resolution: {integrity: sha512-GXMY6t86thctyCvQq+jqElO+MKdB09BkL3hexyGP3Oi8XLKRFaJP1ud/xlWCZ9ZIa2BxHka32zhHfcuU+XsRQg==} 1256 | peerDependencies: 1257 | prettier: ^1.16.4 || ^2.0.0 1258 | svelte: ^3.2.0 1259 | dependencies: 1260 | prettier: 2.8.8 1261 | svelte: 3.58.0 1262 | dev: true 1263 | 1264 | /prettier@2.8.8: 1265 | resolution: {integrity: sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==} 1266 | engines: {node: '>=10.13.0'} 1267 | hasBin: true 1268 | dev: true 1269 | 1270 | /queue-microtask@1.2.3: 1271 | resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} 1272 | dev: true 1273 | 1274 | /read-cache@1.0.0: 1275 | resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==} 1276 | dependencies: 1277 | pify: 2.3.0 1278 | dev: true 1279 | 1280 | /readable-stream@3.6.2: 1281 | resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} 1282 | engines: {node: '>= 6'} 1283 | dependencies: 1284 | inherits: 2.0.4 1285 | string_decoder: 1.3.0 1286 | util-deprecate: 1.0.2 1287 | dev: true 1288 | 1289 | /readdirp@3.6.0: 1290 | resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} 1291 | engines: {node: '>=8.10.0'} 1292 | dependencies: 1293 | picomatch: 2.3.1 1294 | dev: true 1295 | 1296 | /resolve-from@4.0.0: 1297 | resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} 1298 | engines: {node: '>=4'} 1299 | dev: true 1300 | 1301 | /resolve-from@5.0.0: 1302 | resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} 1303 | engines: {node: '>=8'} 1304 | dev: true 1305 | 1306 | /resolve@1.22.2: 1307 | resolution: {integrity: sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==} 1308 | hasBin: true 1309 | dependencies: 1310 | is-core-module: 2.12.0 1311 | path-parse: 1.0.7 1312 | supports-preserve-symlinks-flag: 1.0.0 1313 | dev: true 1314 | 1315 | /reusify@1.0.4: 1316 | resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} 1317 | engines: {iojs: '>=1.0.0', node: '>=0.10.0'} 1318 | dev: true 1319 | 1320 | /rimraf@2.7.1: 1321 | resolution: {integrity: sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==} 1322 | hasBin: true 1323 | dependencies: 1324 | glob: 7.2.3 1325 | dev: true 1326 | 1327 | /rimraf@3.0.2: 1328 | resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} 1329 | hasBin: true 1330 | dependencies: 1331 | glob: 7.2.3 1332 | dev: true 1333 | 1334 | /rollup@3.21.3: 1335 | resolution: {integrity: sha512-VnPfEG51nIv2xPLnZaekkuN06q9ZbnyDcLkaBdJa/W7UddyhOfMP2yOPziYQfeY7k++fZM8FdQIummFN5y14kA==} 1336 | engines: {node: '>=14.18.0', npm: '>=8.0.0'} 1337 | hasBin: true 1338 | optionalDependencies: 1339 | fsevents: 2.3.2 1340 | dev: true 1341 | 1342 | /run-parallel@1.2.0: 1343 | resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} 1344 | dependencies: 1345 | queue-microtask: 1.2.3 1346 | dev: true 1347 | 1348 | /sade@1.8.1: 1349 | resolution: {integrity: sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==} 1350 | engines: {node: '>=6'} 1351 | dependencies: 1352 | mri: 1.2.0 1353 | dev: true 1354 | 1355 | /safe-buffer@5.2.1: 1356 | resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} 1357 | dev: true 1358 | 1359 | /sander@0.5.1: 1360 | resolution: {integrity: sha512-3lVqBir7WuKDHGrKRDn/1Ye3kwpXaDOMsiRP1wd6wpZW56gJhsbp5RqQpA6JG/P+pkXizygnr1dKR8vzWaVsfA==} 1361 | dependencies: 1362 | es6-promise: 3.3.1 1363 | graceful-fs: 4.2.11 1364 | mkdirp: 0.5.6 1365 | rimraf: 2.7.1 1366 | dev: true 1367 | 1368 | /semver@6.3.0: 1369 | resolution: {integrity: sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==} 1370 | hasBin: true 1371 | dev: true 1372 | 1373 | /semver@7.5.0: 1374 | resolution: {integrity: sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA==} 1375 | engines: {node: '>=10'} 1376 | hasBin: true 1377 | dependencies: 1378 | lru-cache: 6.0.0 1379 | dev: true 1380 | 1381 | /set-blocking@2.0.0: 1382 | resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} 1383 | dev: true 1384 | 1385 | /set-cookie-parser@2.6.0: 1386 | resolution: {integrity: sha512-RVnVQxTXuerk653XfuliOxBP81Sf0+qfQE73LIYKcyMYHG94AuH0kgrQpRDuTZnSmjpysHmzxJXKNfa6PjFhyQ==} 1387 | dev: true 1388 | 1389 | /signal-exit@3.0.7: 1390 | resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} 1391 | dev: true 1392 | 1393 | /sirv@2.0.3: 1394 | resolution: {integrity: sha512-O9jm9BsID1P+0HOi81VpXPoDxYP374pkOLzACAoyUQ/3OUVndNpsz6wMnY2z+yOxzbllCKZrM+9QrWsv4THnyA==} 1395 | engines: {node: '>= 10'} 1396 | dependencies: 1397 | '@polka/url': 1.0.0-next.21 1398 | mrmime: 1.0.1 1399 | totalist: 3.0.1 1400 | dev: true 1401 | 1402 | /sorcery@0.11.0: 1403 | resolution: {integrity: sha512-J69LQ22xrQB1cIFJhPfgtLuI6BpWRiWu1Y3vSsIwK/eAScqJxd/+CJlUuHQRdX2C9NGFamq+KqNywGgaThwfHw==} 1404 | hasBin: true 1405 | dependencies: 1406 | '@jridgewell/sourcemap-codec': 1.4.15 1407 | buffer-crc32: 0.2.13 1408 | minimist: 1.2.8 1409 | sander: 0.5.1 1410 | dev: true 1411 | 1412 | /source-map-js@1.0.2: 1413 | resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==} 1414 | engines: {node: '>=0.10.0'} 1415 | dev: true 1416 | 1417 | /streamsearch@1.1.0: 1418 | resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==} 1419 | engines: {node: '>=10.0.0'} 1420 | 1421 | /string-width@4.2.3: 1422 | resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} 1423 | engines: {node: '>=8'} 1424 | dependencies: 1425 | emoji-regex: 8.0.0 1426 | is-fullwidth-code-point: 3.0.0 1427 | strip-ansi: 6.0.1 1428 | dev: true 1429 | 1430 | /string_decoder@1.3.0: 1431 | resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} 1432 | dependencies: 1433 | safe-buffer: 5.2.1 1434 | dev: true 1435 | 1436 | /strip-ansi@6.0.1: 1437 | resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} 1438 | engines: {node: '>=8'} 1439 | dependencies: 1440 | ansi-regex: 5.0.1 1441 | dev: true 1442 | 1443 | /strip-indent@3.0.0: 1444 | resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} 1445 | engines: {node: '>=8'} 1446 | dependencies: 1447 | min-indent: 1.0.1 1448 | dev: true 1449 | 1450 | /sucrase@3.32.0: 1451 | resolution: {integrity: sha512-ydQOU34rpSyj2TGyz4D2p8rbktIOZ8QY9s+DGLvFU1i5pWJE8vkpruCjGCMHsdXwnD7JDcS+noSwM/a7zyNFDQ==} 1452 | engines: {node: '>=8'} 1453 | hasBin: true 1454 | dependencies: 1455 | '@jridgewell/gen-mapping': 0.3.3 1456 | commander: 4.1.1 1457 | glob: 7.1.6 1458 | lines-and-columns: 1.2.4 1459 | mz: 2.7.0 1460 | pirates: 4.0.5 1461 | ts-interface-checker: 0.1.13 1462 | dev: true 1463 | 1464 | /supports-preserve-symlinks-flag@1.0.0: 1465 | resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} 1466 | engines: {node: '>= 0.4'} 1467 | dev: true 1468 | 1469 | /svelte-autosize@1.1.0: 1470 | resolution: {integrity: sha512-CPHq/K0ssrwuHBxFCCOtpWtXZwk4edjEIZ2mJT4KKzjSBq+CO9nOmstjpOh5TpoOf/4MtDcwAwnWmktqANjU6A==} 1471 | dependencies: 1472 | autosize: 6.0.1 1473 | dev: false 1474 | 1475 | /svelte-check@3.2.0(postcss@8.4.23)(svelte@3.58.0): 1476 | resolution: {integrity: sha512-6ZnscN8dHEN5Eq5LgIzjj07W9nc9myyBH+diXsUAuiY/3rt0l65/LCIQYlIuoFEjp2F1NhXqZiJwV9omPj9tMw==} 1477 | hasBin: true 1478 | peerDependencies: 1479 | svelte: ^3.55.0 1480 | dependencies: 1481 | '@jridgewell/trace-mapping': 0.3.18 1482 | chokidar: 3.5.3 1483 | fast-glob: 3.2.12 1484 | import-fresh: 3.3.0 1485 | picocolors: 1.0.0 1486 | sade: 1.8.1 1487 | svelte: 3.58.0 1488 | svelte-preprocess: 5.0.3(postcss@8.4.23)(svelte@3.58.0)(typescript@5.0.4) 1489 | typescript: 5.0.4 1490 | transitivePeerDependencies: 1491 | - '@babel/core' 1492 | - coffeescript 1493 | - less 1494 | - postcss 1495 | - postcss-load-config 1496 | - pug 1497 | - sass 1498 | - stylus 1499 | - sugarss 1500 | dev: true 1501 | 1502 | /svelte-hmr@0.15.1(svelte@3.58.0): 1503 | resolution: {integrity: sha512-BiKB4RZ8YSwRKCNVdNxK/GfY+r4Kjgp9jCLEy0DuqAKfmQtpL38cQK3afdpjw4sqSs4PLi3jIPJIFp259NkZtA==} 1504 | engines: {node: ^12.20 || ^14.13.1 || >= 16} 1505 | peerDependencies: 1506 | svelte: '>=3.19.0' 1507 | dependencies: 1508 | svelte: 3.58.0 1509 | dev: true 1510 | 1511 | /svelte-preprocess@5.0.3(postcss@8.4.23)(svelte@3.58.0)(typescript@5.0.4): 1512 | resolution: {integrity: sha512-GrHF1rusdJVbOZOwgPWtpqmaexkydznKzy5qIC2FabgpFyKN57bjMUUUqPRfbBXK5igiEWn1uO/DXsa2vJ5VHA==} 1513 | engines: {node: '>= 14.10.0'} 1514 | requiresBuild: true 1515 | peerDependencies: 1516 | '@babel/core': ^7.10.2 1517 | coffeescript: ^2.5.1 1518 | less: ^3.11.3 || ^4.0.0 1519 | postcss: ^7 || ^8 1520 | postcss-load-config: ^2.1.0 || ^3.0.0 || ^4.0.0 1521 | pug: ^3.0.0 1522 | sass: ^1.26.8 1523 | stylus: ^0.55.0 1524 | sugarss: ^2.0.0 || ^3.0.0 || ^4.0.0 1525 | svelte: ^3.23.0 1526 | typescript: '>=3.9.5 || ^4.0.0 || ^5.0.0' 1527 | peerDependenciesMeta: 1528 | '@babel/core': 1529 | optional: true 1530 | coffeescript: 1531 | optional: true 1532 | less: 1533 | optional: true 1534 | postcss: 1535 | optional: true 1536 | postcss-load-config: 1537 | optional: true 1538 | pug: 1539 | optional: true 1540 | sass: 1541 | optional: true 1542 | stylus: 1543 | optional: true 1544 | sugarss: 1545 | optional: true 1546 | typescript: 1547 | optional: true 1548 | dependencies: 1549 | '@types/pug': 2.0.6 1550 | detect-indent: 6.1.0 1551 | magic-string: 0.27.0 1552 | postcss: 8.4.23 1553 | sorcery: 0.11.0 1554 | strip-indent: 3.0.0 1555 | svelte: 3.58.0 1556 | typescript: 5.0.4 1557 | dev: true 1558 | 1559 | /svelte@3.58.0: 1560 | resolution: {integrity: sha512-brIBNNB76mXFmU/Kerm4wFnkskBbluBDCjx/8TcpYRb298Yh2dztS2kQ6bhtjMcvUhd5ynClfwpz5h2gnzdQ1A==} 1561 | engines: {node: '>= 8'} 1562 | dev: true 1563 | 1564 | /tailwindcss@3.3.2: 1565 | resolution: {integrity: sha512-9jPkMiIBXvPc2KywkraqsUfbfj+dHDb+JPWtSJa9MLFdrPyazI7q6WX2sUrm7R9eVR7qqv3Pas7EvQFzxKnI6w==} 1566 | engines: {node: '>=14.0.0'} 1567 | hasBin: true 1568 | dependencies: 1569 | '@alloc/quick-lru': 5.2.0 1570 | arg: 5.0.2 1571 | chokidar: 3.5.3 1572 | didyoumean: 1.2.2 1573 | dlv: 1.1.3 1574 | fast-glob: 3.2.12 1575 | glob-parent: 6.0.2 1576 | is-glob: 4.0.3 1577 | jiti: 1.18.2 1578 | lilconfig: 2.1.0 1579 | micromatch: 4.0.5 1580 | normalize-path: 3.0.0 1581 | object-hash: 3.0.0 1582 | picocolors: 1.0.0 1583 | postcss: 8.4.23 1584 | postcss-import: 15.1.0(postcss@8.4.23) 1585 | postcss-js: 4.0.1(postcss@8.4.23) 1586 | postcss-load-config: 4.0.1(postcss@8.4.23) 1587 | postcss-nested: 6.0.1(postcss@8.4.23) 1588 | postcss-selector-parser: 6.0.12 1589 | postcss-value-parser: 4.2.0 1590 | resolve: 1.22.2 1591 | sucrase: 3.32.0 1592 | transitivePeerDependencies: 1593 | - ts-node 1594 | dev: true 1595 | 1596 | /tar@6.1.14: 1597 | resolution: {integrity: sha512-piERznXu0U7/pW7cdSn7hjqySIVTYT6F76icmFk7ptU7dDYlXTm5r9A6K04R2vU3olYgoKeo1Cg3eeu5nhftAw==} 1598 | engines: {node: '>=10'} 1599 | dependencies: 1600 | chownr: 2.0.0 1601 | fs-minipass: 2.1.0 1602 | minipass: 5.0.0 1603 | minizlib: 2.1.2 1604 | mkdirp: 1.0.4 1605 | yallist: 4.0.0 1606 | dev: true 1607 | 1608 | /thenify-all@1.6.0: 1609 | resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} 1610 | engines: {node: '>=0.8'} 1611 | dependencies: 1612 | thenify: 3.3.1 1613 | dev: true 1614 | 1615 | /thenify@3.3.1: 1616 | resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} 1617 | dependencies: 1618 | any-promise: 1.3.0 1619 | dev: true 1620 | 1621 | /tiny-glob@0.2.9: 1622 | resolution: {integrity: sha512-g/55ssRPUjShh+xkfx9UPDXqhckHEsHr4Vd9zX55oSdGZc/MD0m3sferOkwWtp98bv+kcVfEHtRJgBVJzelrzg==} 1623 | dependencies: 1624 | globalyzer: 0.1.0 1625 | globrex: 0.1.2 1626 | dev: true 1627 | 1628 | /to-regex-range@5.0.1: 1629 | resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} 1630 | engines: {node: '>=8.0'} 1631 | dependencies: 1632 | is-number: 7.0.0 1633 | dev: true 1634 | 1635 | /totalist@3.0.1: 1636 | resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==} 1637 | engines: {node: '>=6'} 1638 | dev: true 1639 | 1640 | /tr46@0.0.3: 1641 | resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} 1642 | dev: true 1643 | 1644 | /ts-interface-checker@0.1.13: 1645 | resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} 1646 | dev: true 1647 | 1648 | /tslib@2.5.0: 1649 | resolution: {integrity: sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==} 1650 | dev: true 1651 | 1652 | /typescript@5.0.4: 1653 | resolution: {integrity: sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw==} 1654 | engines: {node: '>=12.20'} 1655 | hasBin: true 1656 | dev: true 1657 | 1658 | /undici@5.22.0: 1659 | resolution: {integrity: sha512-fR9RXCc+6Dxav4P9VV/sp5w3eFiSdOjJYsbtWfd4s5L5C4ogyuVpdKIVHeW0vV1MloM65/f7W45nR9ZxwVdyiA==} 1660 | engines: {node: '>=14.0'} 1661 | dependencies: 1662 | busboy: 1.6.0 1663 | 1664 | /update-browserslist-db@1.0.11(browserslist@4.21.5): 1665 | resolution: {integrity: sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA==} 1666 | hasBin: true 1667 | peerDependencies: 1668 | browserslist: '>= 4.21.0' 1669 | dependencies: 1670 | browserslist: 4.21.5 1671 | escalade: 3.1.1 1672 | picocolors: 1.0.0 1673 | dev: true 1674 | 1675 | /util-deprecate@1.0.2: 1676 | resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} 1677 | dev: true 1678 | 1679 | /vite@4.3.4: 1680 | resolution: {integrity: sha512-f90aqGBoxSFxWph2b39ae2uHAxm5jFBBdnfueNxZAT1FTpM13ccFQExCaKbR2xFW5atowjleRniQ7onjJ22QEg==} 1681 | engines: {node: ^14.18.0 || >=16.0.0} 1682 | hasBin: true 1683 | peerDependencies: 1684 | '@types/node': '>= 14' 1685 | less: '*' 1686 | sass: '*' 1687 | stylus: '*' 1688 | sugarss: '*' 1689 | terser: ^5.4.0 1690 | peerDependenciesMeta: 1691 | '@types/node': 1692 | optional: true 1693 | less: 1694 | optional: true 1695 | sass: 1696 | optional: true 1697 | stylus: 1698 | optional: true 1699 | sugarss: 1700 | optional: true 1701 | terser: 1702 | optional: true 1703 | dependencies: 1704 | esbuild: 0.17.18 1705 | postcss: 8.4.23 1706 | rollup: 3.21.3 1707 | optionalDependencies: 1708 | fsevents: 2.3.2 1709 | dev: true 1710 | 1711 | /vitefu@0.2.4(vite@4.3.4): 1712 | resolution: {integrity: sha512-fanAXjSaf9xXtOOeno8wZXIhgia+CZury481LsDaV++lSvcU2R9Ch2bPh3PYFyoHW+w9LqAeYRISVQjUIew14g==} 1713 | peerDependencies: 1714 | vite: ^3.0.0 || ^4.0.0 1715 | peerDependenciesMeta: 1716 | vite: 1717 | optional: true 1718 | dependencies: 1719 | vite: 4.3.4 1720 | dev: true 1721 | 1722 | /webidl-conversions@3.0.1: 1723 | resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} 1724 | dev: true 1725 | 1726 | /whatwg-url@5.0.0: 1727 | resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} 1728 | dependencies: 1729 | tr46: 0.0.3 1730 | webidl-conversions: 3.0.1 1731 | dev: true 1732 | 1733 | /wide-align@1.1.5: 1734 | resolution: {integrity: sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==} 1735 | dependencies: 1736 | string-width: 4.2.3 1737 | dev: true 1738 | 1739 | /wrappy@1.0.2: 1740 | resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} 1741 | dev: true 1742 | 1743 | /yallist@4.0.0: 1744 | resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} 1745 | dev: true 1746 | 1747 | /yaml@2.2.2: 1748 | resolution: {integrity: sha512-CBKFWExMn46Foo4cldiChEzn7S7SRV+wqiluAb6xmueD/fGyRHIhX8m14vVGgeFWjN540nKCNVj6P21eQjgTuA==} 1749 | engines: {node: '>= 14'} 1750 | dev: true 1751 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {} 5 | } 6 | }; 7 | -------------------------------------------------------------------------------- /postgres/migrate.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | 3 | import { loadEnv } from 'vite'; 4 | import postgres from 'postgres'; 5 | import * as fs from 'node:fs'; 6 | 7 | const env = loadEnv('production', '.', ''); 8 | 9 | const sql = postgres(env.POSTGRES_URL, { 10 | ssl: 'require' 11 | }); 12 | 13 | await sql` 14 | CREATE TABLE IF NOT EXISTS _migration ( 15 | id TEXT PRIMARY KEY NOT NULL, 16 | timestamp TIMESTAMP NOT NULL 17 | ) 18 | `; 19 | 20 | await sql` 21 | CREATE TABLE IF NOT EXISTS _migration_lock ( 22 | id TEXT PRIMARY KEY NOT NULL, 23 | is_locked BOOLEAN NOT NULL 24 | ); 25 | `; 26 | 27 | await sql` 28 | INSERT INTO _migration_lock ( 29 | id, 30 | is_locked 31 | ) VALUES ( 32 | 'migration_lock', 33 | false 34 | ) ON CONFLICT (id) DO NOTHING; 35 | `; 36 | 37 | await sql.begin(async (sql) => { 38 | const rows = await sql` 39 | WITH updated AS ( 40 | UPDATE _migration_lock 41 | SET is_locked = true 42 | WHERE id = 'migration_lock' AND is_locked = false 43 | RETURNING id 44 | ) 45 | SELECT id FROM updated; 46 | `; 47 | 48 | if (rows.length === 0) { 49 | throw new Error('migration is in progress'); 50 | } 51 | 52 | const [latest] = await sql` 53 | SELECT id, timestamp FROM _migration 54 | ORDER BY timestamp DESC 55 | LIMIT 1 56 | `; 57 | 58 | if (latest) { 59 | console.log(`latest migration: ${latest.id} at ${latest.timestamp.toISOString()}`); 60 | } else { 61 | console.log(`no previous migrations found`); 62 | } 63 | 64 | const dir = new URL('migrations', import.meta.url); 65 | 66 | for (const file of fs.readdirSync(dir)) { 67 | if (file <= latest?.id) continue; 68 | if (!file.endsWith('.sql')) continue; 69 | 70 | console.log(`running migration ${file}`); 71 | 72 | await sql.file(new URL(`migrations/${file}`, import.meta.url)); 73 | 74 | await sql` 75 | INSERT INTO _migration (id, timestamp) VALUES (${file}, ${new Date()}) 76 | `; 77 | 78 | await new Promise((f) => setTimeout(f, 100)); 79 | } 80 | 81 | await sql` 82 | UPDATE _migration_lock 83 | SET is_locked = false 84 | WHERE id = 'migration_lock' 85 | `; 86 | }); 87 | 88 | await sql.end(); 89 | -------------------------------------------------------------------------------- /postgres/migrations/000.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE IF EXISTS account CASCADE; 2 | DROP TABLE IF EXISTS follows CASCADE; 3 | DROP TABLE IF EXISTS likes CASCADE; 4 | DROP TABLE IF EXISTS comment CASCADE; 5 | DROP TABLE IF EXISTS photo CASCADE; 6 | DROP TABLE IF EXISTS session CASCADE; 7 | -------------------------------------------------------------------------------- /postgres/migrations/001.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE account ( 2 | id UUID PRIMARY KEY DEFAULT GEN_RANDOM_UUID(), 3 | created_at TIMESTAMPTZ NOT NULL DEFAULT (NOW() AT TIME ZONE 'UTC'), 4 | discord_id TEXT NOT NULL, 5 | name TEXT NOT NULL, 6 | avatar TEXT, 7 | UNIQUE(discord_id) 8 | ); 9 | 10 | CREATE TABLE follows ( 11 | created_at TIMESTAMPTZ NOT NULL DEFAULT (NOW() AT TIME ZONE 'UTC'), 12 | account_id UUID NOT NULL, 13 | following_id UUID NOT NULL, 14 | UNIQUE(account_id, following_id) 15 | ); 16 | 17 | CREATE TABLE likes ( 18 | created_at TIMESTAMPTZ NOT NULL DEFAULT (NOW() AT TIME ZONE 'UTC'), 19 | account_id UUID NOT NULL, 20 | photo_id UUID NOT NULL, 21 | UNIQUE(account_id, photo_id) 22 | ); 23 | 24 | CREATE TABLE photo ( 25 | id UUID PRIMARY KEY DEFAULT GEN_RANDOM_UUID(), 26 | created_at TIMESTAMPTZ NOT NULL DEFAULT (NOW() AT TIME ZONE 'UTC'), 27 | account_id UUID NOT NULL, 28 | FOREIGN KEY (account_id) REFERENCES account (id), 29 | url TEXT NOT NULL, 30 | width INTEGER NOT NULL, 31 | height INTEGER NOT NULL, 32 | description TEXT NOT NULL, 33 | published boolean NOT NULL DEFAULT false 34 | ); 35 | 36 | CREATE TABLE comment ( 37 | id UUID PRIMARY KEY DEFAULT GEN_RANDOM_UUID(), 38 | created_at TIMESTAMPTZ NOT NULL DEFAULT (NOW() AT TIME ZONE 'UTC'), 39 | account_id UUID NOT NULL, 40 | FOREIGN KEY (account_id) REFERENCES account (id), 41 | photo_id UUID NOT NULL, 42 | FOREIGN KEY (photo_id) REFERENCES photo (id), 43 | text TEXT NOT NULL 44 | ); 45 | 46 | CREATE TABLE session ( 47 | id UUID PRIMARY KEY DEFAULT GEN_RANDOM_UUID(), 48 | created_at TIMESTAMPTZ NOT NULL DEFAULT (NOW() AT TIME ZONE 'UTC'), 49 | account_id UUID NOT NULL, 50 | FOREIGN KEY (account_id) REFERENCES account (id) 51 | ); -------------------------------------------------------------------------------- /scripts/update-vercel-config.js: -------------------------------------------------------------------------------- 1 | import fs from 'node:fs'; 2 | import path from 'node:path'; 3 | 4 | const config_file = '.vercel/output/config.json'; 5 | const config = JSON.parse(fs.readFileSync(config_file, 'utf8')); 6 | 7 | config.images = { 8 | sizes: [640, 960, 1280], 9 | domains: ['public.blob.vercel-storage.com'], 10 | formats: ['image/avif', 'image/webp'], 11 | minimumCacheTTL: 300 12 | }; 13 | 14 | fs.writeFileSync(config_file, JSON.stringify(config, null, '\t')); 15 | -------------------------------------------------------------------------------- /src/ambient.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'svelte-autosize'; 2 | -------------------------------------------------------------------------------- /src/app.css: -------------------------------------------------------------------------------- 1 | @import '@fontsource/pangolin'; 2 | 3 | @tailwind base; 4 | @tailwind components; 5 | @tailwind utilities; 6 | 7 | @layer base { 8 | html { 9 | font-family: 'Pangolin'; 10 | } 11 | } -------------------------------------------------------------------------------- /src/app.d.ts: -------------------------------------------------------------------------------- 1 | // See https://kit.svelte.dev/docs/types#app 2 | 3 | import type { Account } from '$lib/types'; 4 | 5 | // for information about these interfaces 6 | declare global { 7 | namespace App { 8 | // interface Error {} 9 | interface Locals { 10 | user?: Account; 11 | } 12 | // interface PageData {} 13 | // interface Platform {} 14 | } 15 | } 16 | 17 | export {}; 18 | -------------------------------------------------------------------------------- /src/app.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | %sveltekit.head% 8 | 9 | 16 | 17 | 18 |
%sveltekit.body%
19 | 20 | 21 | -------------------------------------------------------------------------------- /src/hooks.server.ts: -------------------------------------------------------------------------------- 1 | import { sql } from '$lib/server/database'; 2 | import type { Account } from '$lib/types'; 3 | 4 | export async function handle({ event, resolve }) { 5 | const session_id = event.cookies.get('session'); 6 | 7 | if (session_id) { 8 | const [account] = await sql` 9 | SELECT account.* 10 | FROM account 11 | INNER JOIN session ON session.account_id = account.id 12 | WHERE session.id = ${session_id} 13 | `; 14 | 15 | event.locals.user = account as Account; 16 | } 17 | 18 | return resolve(event); 19 | } 20 | -------------------------------------------------------------------------------- /src/lib/actions.ts: -------------------------------------------------------------------------------- 1 | export function smoothload(node: HTMLImageElement) { 2 | function load() { 3 | if (node.naturalWidth) return; // already loaded 4 | 5 | node.style.opacity = '0'; 6 | node.style.transition = 'opacity 0.4s'; 7 | 8 | node.addEventListener( 9 | 'load', 10 | () => { 11 | node.style.opacity = '1'; 12 | }, 13 | { 14 | once: true 15 | } 16 | ); 17 | } 18 | 19 | const observer = new MutationObserver((mutations) => { 20 | for (const mutation of mutations) { 21 | if (mutation.attributeName === 'src') { 22 | load(); 23 | } 24 | } 25 | }); 26 | 27 | observer.observe(node, { 28 | attributes: true 29 | }); 30 | 31 | load(); 32 | 33 | return { 34 | destroy() { 35 | observer.disconnect(); 36 | } 37 | }; 38 | } 39 | 40 | export function trapfocus(node: HTMLElement) { 41 | const previous = document.activeElement as HTMLElement; 42 | 43 | function focusable(): HTMLElement[] { 44 | return Array.from( 45 | node.querySelectorAll( 46 | 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])' 47 | ) 48 | ); 49 | } 50 | 51 | function handleKeydown(event: KeyboardEvent) { 52 | if (event.key !== 'Tab') return; 53 | 54 | const current = document.activeElement; 55 | 56 | const elements = focusable(); 57 | const first = elements.at(0); 58 | const last = elements.at(-1); 59 | 60 | if (event.shiftKey && current === first) { 61 | last?.focus(); 62 | event.preventDefault(); 63 | } 64 | 65 | if (!event.shiftKey && current === last) { 66 | first?.focus(); 67 | event.preventDefault(); 68 | } 69 | } 70 | 71 | focusable()[0]?.focus(); 72 | 73 | node.addEventListener('keydown', handleKeydown); 74 | 75 | return { 76 | destroy() { 77 | node.removeEventListener('keydown', handleKeydown); 78 | previous?.focus({ preventScroll: true }); 79 | } 80 | }; 81 | } 82 | -------------------------------------------------------------------------------- /src/lib/components/Avatar.svelte: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 | 11 | {#if full} 12 | {name} 13 | {/if} 14 | 15 | -------------------------------------------------------------------------------- /src/lib/components/AvatarImage.svelte: -------------------------------------------------------------------------------- 1 | 8 | 9 | {name} 16 | -------------------------------------------------------------------------------- /src/lib/components/Image.svelte: -------------------------------------------------------------------------------- 1 | 8 | 9 |
13 | {#key photo.id} 14 | {photo.description} 20 | {/key} 21 |
22 | -------------------------------------------------------------------------------- /src/lib/components/Login.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 |
6 | 7 |
8 | -------------------------------------------------------------------------------- /src/lib/components/Metadata.svelte: -------------------------------------------------------------------------------- 1 | 10 | 11 | 12 | 13 | {$state[photo.id].num_comments} 14 |
15 | 16 |
17 | 18 | {$state[photo.id].num_likes} 19 |
20 | {#if $state[photo.id].liked_by_user}{:else}{/if} 21 |
22 |
23 |
24 | -------------------------------------------------------------------------------- /src/lib/components/Modal.svelte: -------------------------------------------------------------------------------- 1 | 8 | 9 | { 11 | if (e.key === 'Escape') { 12 | dispatch('close'); 13 | } 14 | }} 15 | /> 16 | 17 | 18 |
23 |
{ 27 | if (e.target === e.currentTarget) { 28 | dispatch('close'); 29 | } 30 | }} 31 | > 32 | 33 |
34 |
35 | -------------------------------------------------------------------------------- /src/lib/components/PhotoList.svelte: -------------------------------------------------------------------------------- 1 | 34 | 35 | { 39 | if (loading || !next) return; 40 | loading = true; 41 | 42 | const response = await fetch(`${endpoint}?start=${next}`); 43 | const result = await response.json(); 44 | 45 | dispatch('loaded', result); 46 | 47 | loading = false; 48 | }} 49 | > 50 |
51 | 52 |
53 | 54 | 95 | 96 |
97 | 98 |
99 | 100 |
101 | {#if next} 102 | next page 103 | {/if} 104 |
105 |
106 | 107 | {#if $page.state.selected} 108 | history.back()}> 109 |
110 |
111 | 112 |
113 |
114 |
115 | {/if} 116 | -------------------------------------------------------------------------------- /src/lib/components/Publisher.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 |
6 | 14 | 15 | 21 |
22 | 23 | 28 | -------------------------------------------------------------------------------- /src/lib/components/Scroller.svelte: -------------------------------------------------------------------------------- 1 | 101 | 102 | 103 | 104 |
105 |
111 | 112 | 113 |
114 | {#each items.slice(a, b) as item, i (item)} 115 |
116 | 117 |
118 | {:else} 119 | 120 | {/each} 121 |
122 | 123 | 124 |
125 |
126 | -------------------------------------------------------------------------------- /src/lib/components/Uploader.svelte: -------------------------------------------------------------------------------- 1 | 14 | 15 |
{ 21 | pending = true; 22 | 23 | return async ({ result }) => { 24 | if (result.type === 'redirect') { 25 | await goto(result.location, { 26 | replaceState: true 27 | }); 28 | } else { 29 | // TODO handle network errors 30 | } 31 | 32 | pending = false; 33 | file = undefined; 34 | }; 35 | }} 36 | > 37 | {#if $page.state.show_uploader} 38 | history.back()}> 39 |
40 |
41 | Preview 42 | 43 | 44 |
45 |
46 |
47 | {/if} 48 | 49 | 100 |
101 | 102 | 108 | -------------------------------------------------------------------------------- /src/lib/icons/Comment.svelte: -------------------------------------------------------------------------------- 1 | 2 | Speech bubble 3 | 4 | 8 | 9 | -------------------------------------------------------------------------------- /src/lib/icons/Heart.svelte: -------------------------------------------------------------------------------- 1 | 2 | Heart outline 3 | 4 | 5 | 9 | 10 | 11 | 15 | 16 | -------------------------------------------------------------------------------- /src/lib/icons/HeartOutline.svelte: -------------------------------------------------------------------------------- 1 | 2 | Heart 3 | 7 | 8 | -------------------------------------------------------------------------------- /src/lib/icons/Logo.svelte: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/lib/icons/Trash.svelte: -------------------------------------------------------------------------------- 1 | 2 | Trash can 3 | 4 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/lib/icons/account-circle.svg: -------------------------------------------------------------------------------- 1 | account-circle -------------------------------------------------------------------------------- /src/lib/image-size/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Code adapted from https://github.com/image-size/image-size 3 | Reproduced here under the MIT License 4 | 5 | --- 6 | 7 | The MIT License (MIT) 8 | 9 | Copyright © 2017 Aditya Yadav, http://netroy.in 10 | 11 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 16 | */ 17 | 18 | import { png } from './png'; 19 | import { jpg } from './jpg'; 20 | import type { Size } from './types'; 21 | 22 | const types = { 23 | png, 24 | jpg, 25 | jpeg: jpg 26 | }; 27 | 28 | export function get_dimensions(buffer: ArrayBuffer, type: keyof typeof types): Size { 29 | const fn = types[type]; 30 | 31 | if (!fn) { 32 | throw new TypeError(`Unsupported image format ${type}`); 33 | } 34 | 35 | return fn(buffer); 36 | } 37 | -------------------------------------------------------------------------------- /src/lib/image-size/jpg.ts: -------------------------------------------------------------------------------- 1 | // NOTE: we only support baseline and progressive JPGs here 2 | // due to the structure of the loader class, we only get a buffer 3 | // with a maximum size of 4096 bytes. so if the SOF marker is outside 4 | // if this range we can't detect the file size correctly. 5 | 6 | import type { Size } from './types'; 7 | 8 | const APP1_DATA_SIZE_BYTES = 2; 9 | const EXIF_HEADER_BYTES = 6; 10 | 11 | // Each entry is exactly 12 bytes 12 | const IDF_ENTRY_BYTES = 12; 13 | const NUM_DIRECTORY_ENTRIES_BYTES = 2; 14 | 15 | function is_exif(view: DataView, p: number): boolean { 16 | return view.getUint16(p + 2) === 0x4578 && view.getUint16(p + 4) === 0x6966; 17 | } 18 | 19 | function extract_orientation( 20 | view: DataView, 21 | exif_start: number, 22 | exif_end: number, 23 | is_little_endian: boolean 24 | ) { 25 | // TODO: assert that this contains 0x002A 26 | // let STATIC_MOTOROLA_TIFF_HEADER_BYTES = 2 27 | // let TIFF_IMAGE_FILE_DIRECTORY_BYTES = 4 28 | 29 | // TODO: derive from TIFF_IMAGE_FILE_DIRECTORY_BYTES 30 | const idf_offset = 8; 31 | 32 | // IDF osset works from right after the header bytes 33 | // (so the offset includes the tiff byte align) 34 | const offset = exif_start + EXIF_HEADER_BYTES + idf_offset; 35 | 36 | const idf_directory_entries = view.getUint16(offset, is_little_endian); 37 | 38 | for ( 39 | let directory_entry_number = 0; 40 | directory_entry_number < idf_directory_entries; 41 | directory_entry_number++ 42 | ) { 43 | const start = offset + NUM_DIRECTORY_ENTRIES_BYTES + directory_entry_number * IDF_ENTRY_BYTES; 44 | 45 | // Skip on corrupt EXIF blocks 46 | if (start > exif_end) { 47 | return; 48 | } 49 | 50 | const tag_number = view.getUint16(start + 0, is_little_endian); 51 | 52 | // 0x0112 (decimal: 274) is the `orientation` tag ID 53 | if (tag_number === 274) { 54 | const data_format = view.getUint16(start + 2, is_little_endian); 55 | 56 | if (data_format !== 3) { 57 | return; 58 | } 59 | 60 | // unsigned int has 2 bytes per component 61 | // if there would more than 4 bytes in total it's a pointer 62 | const number_of_components = view.getUint32(start + 4, is_little_endian); 63 | if (number_of_components !== 1) { 64 | return; 65 | } 66 | 67 | return view.getUint16(start + 8, is_little_endian); 68 | } 69 | } 70 | } 71 | 72 | function validate_exif_block(view: DataView, p: number, index: number) { 73 | // Skip APP1 Data Size 74 | const exif_start = p + APP1_DATA_SIZE_BYTES; 75 | const exif_end = p + index; 76 | 77 | // Ignore Empty EXIF. Validate byte alignment 78 | const byte1 = view.getUint8(exif_start + 6); 79 | const byte2 = view.getUint8(exif_start + 7); 80 | const is_big_endian = byte1 === 0x4d && byte2 === 0x4d; 81 | const is_little_endian = byte1 === 0x49 && byte2 === 0x49; 82 | 83 | if (is_big_endian || is_little_endian) { 84 | return extract_orientation(view, exif_start, exif_end, is_little_endian); 85 | } 86 | } 87 | 88 | export function jpg(data: ArrayBuffer): Size { 89 | const bytes = new Uint8Array(data); 90 | 91 | if (bytes[0] === 0xff && bytes[1] === 0xd8) { 92 | const view = new DataView(data); 93 | 94 | // Skip 4 bytes, they are for signature 95 | let p = 4; 96 | 97 | let orientation: number | undefined; 98 | 99 | while (p < bytes.length) { 100 | // read length of the next block 101 | const i = view.getUint16(p, false); 102 | 103 | if (is_exif(view, p)) { 104 | orientation = validate_exif_block(view, p, i); 105 | } 106 | 107 | // index should be within buffer limits 108 | if (p + i > bytes.length) { 109 | throw new TypeError('Corrupt JPG, exceeded buffer limits'); 110 | } 111 | 112 | // Every JPEG block must begin with a 0xFF 113 | if (bytes[p + i] !== 0xff) { 114 | throw new TypeError('Invalid JPG, marker table corrupted'); 115 | } 116 | 117 | // 0xFFC0 is baseline standard(SOF) 118 | // 0xFFC1 is baseline optimized(SOF) 119 | // 0xFFC2 is progressive(SOF2) 120 | const next = bytes[p + i + 1]; 121 | 122 | if (next === 0xc0 || next === 0xc1 || next === 0xc2) { 123 | const size = { 124 | width: view.getUint16(p + i + 7, false), 125 | height: view.getUint16(p + i + 5, false) 126 | }; 127 | 128 | if (orientation && orientation >= 5 && orientation <= 8) { 129 | [size.width, size.height] = [size.height, size.width]; 130 | } 131 | 132 | // TODO do we need to swap width and height if orientation is 1? 133 | return size; 134 | } 135 | 136 | // move to the next block 137 | p += i + 2; 138 | } 139 | 140 | throw new TypeError('Invalid JPG, no size found'); 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /src/lib/image-size/png.ts: -------------------------------------------------------------------------------- 1 | import type { Size } from './types'; 2 | 3 | const decoder = new TextDecoder(); 4 | 5 | export function png(data: ArrayBuffer): Size { 6 | const bytes = new Uint8Array(data); 7 | 8 | if (decoder.decode(bytes.slice(1, 8)) === 'PNG\r\n\x1a\n') { 9 | const view = new DataView(data); 10 | let str = decoder.decode(bytes.slice(12, 16)); 11 | 12 | // handle 'fried' PNGs https://github.com/esjeon/pngdefry 13 | if (str === 'CgBI') { 14 | if (decoder.decode(bytes.slice(28, 32)) === 'IHDR') { 15 | return { 16 | height: view.getUint32(36, false), 17 | width: view.getUint32(32, false) 18 | }; 19 | } 20 | } 21 | 22 | if (str === 'IHDR') { 23 | return { 24 | height: view.getUint32(20, false), 25 | width: view.getUint32(16, false) 26 | }; 27 | } 28 | } 29 | 30 | throw new TypeError('Invalid PNG'); 31 | } 32 | -------------------------------------------------------------------------------- /src/lib/image-size/types.d.ts: -------------------------------------------------------------------------------- 1 | export interface Size { 2 | width: number; 3 | height: number; 4 | } 5 | -------------------------------------------------------------------------------- /src/lib/image.ts: -------------------------------------------------------------------------------- 1 | import { dev } from '$app/environment'; 2 | 3 | export function optimize(src: string, widths = [640, 960, 1280], quality = 90) { 4 | if (dev) return src; 5 | 6 | return widths 7 | .slice() 8 | .sort((a, b) => a - b) 9 | .map((width, i) => { 10 | const url = `/_vercel/image?url=${encodeURIComponent(src)}&w=${width}&q=${quality}`; 11 | const descriptor = i < widths.length - 1 ? ` ${width}w` : ''; 12 | return url + descriptor; 13 | }) 14 | .join(', '); 15 | } 16 | -------------------------------------------------------------------------------- /src/lib/server/database.ts: -------------------------------------------------------------------------------- 1 | import { POSTGRES_URL } from '$env/static/private'; 2 | import postgres from 'postgres'; 3 | 4 | export const sql = postgres(POSTGRES_URL, { 5 | ssl: 'require' 6 | }); 7 | -------------------------------------------------------------------------------- /src/lib/state.ts: -------------------------------------------------------------------------------- 1 | import { writable } from 'svelte/store'; 2 | import type { Account, Comment, Photo, PhotoListItem } from './types'; 3 | 4 | // note: this store will leak memory on the server! luckily, we're using 5 | // serverless functions, which means we don't need to care — the 6 | // function won't be kept alive long enough for it to matter. 7 | 8 | const { subscribe, update } = writable( 9 | {} as { 10 | [id: string]: { 11 | num_comments: number; 12 | num_likes: number; 13 | liked_by_user: boolean; 14 | }; 15 | } 16 | ); 17 | 18 | export const state = { subscribe }; 19 | 20 | export function init_photos(photos: PhotoListItem[]) { 21 | update((lookup) => { 22 | for (const photo of photos) { 23 | if (!lookup[photo.id]) { 24 | lookup[photo.id] = { 25 | num_comments: photo.num_comments, 26 | num_likes: photo.num_likes, 27 | liked_by_user: photo.liked_by_user 28 | }; 29 | } 30 | } 31 | 32 | return lookup; 33 | }); 34 | } 35 | 36 | export function update_photo(photo: Photo, comments: Comment[], likes: Account[], user?: Account) { 37 | update((lookup) => { 38 | lookup[photo.id] = { 39 | num_comments: comments.length, 40 | num_likes: likes.length, 41 | liked_by_user: !!user && likes.some((like) => like.name === user.name) 42 | }; 43 | return lookup; 44 | }); 45 | } 46 | -------------------------------------------------------------------------------- /src/lib/types.d.ts: -------------------------------------------------------------------------------- 1 | export interface Account { 2 | id: string; 3 | created_at: Date; 4 | name: string; 5 | avatar: string; 6 | } 7 | 8 | export interface AccountDetails extends Account { 9 | followed_by_user: boolean; 10 | } 11 | 12 | export interface Photo { 13 | id: string; 14 | created_at: Date; 15 | url: string; 16 | width: number; 17 | height: number; 18 | description: string; 19 | likes: number; 20 | published: boolean; 21 | } 22 | 23 | export interface PhotoListItem extends Photo { 24 | name: string; 25 | avatar: string; 26 | num_likes: number; 27 | num_comments: number; 28 | liked_by_user: boolean; 29 | } 30 | 31 | export interface Comment { 32 | id: string; 33 | created_at: Date; 34 | photo_id: string; 35 | name: string; 36 | avatar: string; 37 | text: string; 38 | } 39 | -------------------------------------------------------------------------------- /src/lib/utils.ts: -------------------------------------------------------------------------------- 1 | import { readable } from 'svelte/store'; 2 | 3 | export const PAGE_SIZE = 5; // TODO make this larger, when there's more photos in the database 4 | 5 | export function startViewTransition(fn) { 6 | if (document.startViewTransition) { 7 | document.startViewTransition(fn); 8 | } 9 | 10 | fn(); 11 | } 12 | 13 | export function ago(a: Date, b: Date) { 14 | const ms = b.getTime() - a.getTime(); 15 | 16 | if (ms < 10 * 1000) return 'a few seconds ago'; 17 | if (ms < 60 * 1000) return 'less than a minute ago'; 18 | 19 | const minutes = Math.floor(ms / (60 * 1000)); 20 | 21 | if (minutes === 1) return 'a minute ago'; 22 | if (minutes < 60) return `${minutes} minutes ago`; 23 | 24 | const hours = Math.floor(ms / (60 * 60 * 1000)); 25 | 26 | if (hours === 1) return 'an hour ago'; 27 | if (hours < 24) return `${hours} hours ago`; 28 | 29 | const a_day = new Date(a.getFullYear(), a.getMonth(), a.getDate()); 30 | const b_day = new Date(b.getFullYear(), b.getMonth(), b.getDate()); 31 | 32 | const days = Math.round((b_day.getTime() - a_day.getTime()) / (24 * 60 * 60 * 1000)); 33 | 34 | if (days === 1) return 'yesterday'; 35 | if (days < 7) return `${days} days ago`; 36 | if (days === 7) return 'a week ago'; 37 | 38 | if (days < 28) return `${Math.ceil(days / 7)} weeks ago`; 39 | if (days < 335) return `${Math.ceil(days / (365 / 12))} months ago`; 40 | 41 | const years = Math.round(days / 365); 42 | return `${years} ${years === 1 ? 'year' : 'years'} ago`; 43 | } 44 | 45 | export const now = readable(new Date(), (set) => { 46 | const interval = setInterval(() => { 47 | set(new Date()); 48 | }, 1000); 49 | 50 | return () => { 51 | clearInterval(interval); 52 | }; 53 | }); 54 | -------------------------------------------------------------------------------- /src/routes/+layout.server.ts: -------------------------------------------------------------------------------- 1 | export function load({ locals }) { 2 | return { 3 | user: locals.user 4 | }; 5 | } 6 | -------------------------------------------------------------------------------- /src/routes/+layout.svelte: -------------------------------------------------------------------------------- 1 | 12 | 13 | 33 | 34 |
35 | 36 |
37 | 38 | {#if data.user && $page.url.pathname !== '/publish'} 39 | 40 | {/if} 41 | -------------------------------------------------------------------------------- /src/routes/+page.server.ts: -------------------------------------------------------------------------------- 1 | import { BLOB_READ_WRITE_TOKEN } from '$env/static/private'; 2 | import { get_dimensions } from '$lib/image-size/index.js'; 3 | import { sql } from '$lib/server/database.js'; 4 | import type { PhotoListItem } from '$lib/types.js'; 5 | import { error, redirect } from '@sveltejs/kit'; 6 | import * as blob from '@vercel/blob'; 7 | 8 | export async function load({ locals, fetch, url }) { 9 | if (locals.user) { 10 | const response = await fetch( 11 | `/api/photos/feed.json?start=${url.searchParams.get('start') || ''}` 12 | ); 13 | const { photos, next } = await response.json(); 14 | 15 | return { 16 | photos: photos as PhotoListItem[], 17 | next: next as string 18 | }; 19 | } 20 | 21 | return { 22 | photos: [] as PhotoListItem[], 23 | next: null 24 | }; 25 | } 26 | 27 | export const actions = { 28 | post: async ({ locals, request }) => { 29 | if (!locals.user) { 30 | throw error(401); 31 | } 32 | 33 | const form = await request.formData(); 34 | 35 | const file = form.get('file') as File; 36 | const description = (form.get('description') as string) ?? ''; 37 | 38 | if (!file) { 39 | throw error(422); 40 | } 41 | 42 | const ext = file.name.split('.').at(-1) as 'jpg' | 'png'; 43 | const name = `${locals.user.id}/${Date.now()}.${ext}`; 44 | 45 | const data = await file.arrayBuffer(); 46 | const { width, height } = get_dimensions(data, ext); 47 | 48 | const { url } = await blob.put(name, file, { 49 | access: 'public', 50 | token: BLOB_READ_WRITE_TOKEN 51 | }); 52 | 53 | // if no description was provided, it means we came here via a no-JS 54 | // form submission, and we need to go to the `/publish` page to finish up 55 | const published = !!description; 56 | 57 | const [{ id }] = await sql` 58 | INSERT INTO photo (account_id, url, width, height, description, published) 59 | VALUES (${locals.user.id}, ${url}, ${width}, ${height}, ${description}, ${published}) 60 | RETURNING id 61 | `; 62 | 63 | throw redirect(303, published ? `/${locals.user.name}/${id}` : `/publish?id=${id}`); 64 | } 65 | }; 66 | -------------------------------------------------------------------------------- /src/routes/+page.svelte: -------------------------------------------------------------------------------- 1 | 32 | 33 | {#if data.user} 34 |
35 | { 41 | data.photos = [...data.photos, ...e.detail.photos]; 42 | data.next = e.detail.next; 43 | }} 44 | > 45 |

your feed

46 |

no photos yet. post some, and follow your friends!

47 |
48 |
49 | {:else} 50 |
51 | Log in to upload photos and see your friends' posts! 52 |
53 | {/if} 54 | -------------------------------------------------------------------------------- /src/routes/[account]/+page.server.ts: -------------------------------------------------------------------------------- 1 | import { sql } from '$lib/server/database.js'; 2 | import type { AccountDetails, PhotoListItem } from '$lib/types.js'; 3 | import { error } from '@sveltejs/kit'; 4 | 5 | export async function load({ locals, params, fetch, url }) { 6 | const [account] = locals.user 7 | ? await sql` 8 | SELECT a.*, 9 | CASE WHEN f.account_id IS NOT NULL THEN TRUE ELSE FALSE END AS followed_by_user 10 | FROM account a 11 | LEFT JOIN follows f ON a.id = f.following_id AND f.account_id = ${locals.user.id} 12 | WHERE a.name = ${params.account}; 13 | ` 14 | : await sql` 15 | SELECT *, FALSE AS followed_by_user FROM account WHERE name = ${params.account} 16 | `; 17 | 18 | if (!account) { 19 | throw error(404); 20 | } 21 | 22 | // we _could_ load the data directly here, but we also want to be able to fetch 23 | // the data directly from the browser for the sake of infinite scroll, 24 | // so we use an API route instead 25 | const response = await fetch( 26 | `/api/photos/${account.id}.json?start=${url.searchParams.get('start') ?? ''}` 27 | ); 28 | const { photos, next } = await response.json(); 29 | 30 | return { 31 | account: account as AccountDetails, 32 | photos: photos as PhotoListItem[], 33 | next 34 | }; 35 | } 36 | 37 | export const actions = { 38 | toggle_follow: async ({ locals, params, request }) => { 39 | if (!locals.user) throw error(401); 40 | 41 | const data = await request.formData(); 42 | const id = data.get('id') as string; 43 | const followed = data.get('followed') === 'true'; 44 | 45 | if (!id) throw error(422); 46 | 47 | if (followed) { 48 | await sql` 49 | INSERT INTO follows (account_id, following_id) 50 | VALUES (${locals.user.id}, ${id}) 51 | ON CONFLICT DO NOTHING 52 | `; 53 | } else { 54 | await sql` 55 | DELETE FROM follows 56 | WHERE account_id = ${locals.user.id} 57 | AND following_id = ${id} 58 | `; 59 | } 60 | } 61 | }; 62 | -------------------------------------------------------------------------------- /src/routes/[account]/+page.svelte: -------------------------------------------------------------------------------- 1 | 33 | 34 |
35 | { 41 | data.photos = [...data.photos, ...e.detail.photos]; 42 | data.next = e.detail.next; 43 | }} 44 | > 45 |
46 |

47 | 48 | posts by {data.account.name} 49 |

50 | 51 | {#if data.user && data.account.id !== data.user.id} 52 |
{ 57 | data.account.followed_by_user = !data.account.followed_by_user; 58 | }} 59 | > 60 | 61 | 62 | 73 |
74 | {/if} 75 |
76 | 77 |

no photos yet!

78 |
79 |
80 | -------------------------------------------------------------------------------- /src/routes/[account]/[photo]/+page.server.ts: -------------------------------------------------------------------------------- 1 | import { sql } from '$lib/server/database.js'; 2 | import type { Account, Comment, PhotoListItem } from '$lib/types.js'; 3 | import { error, redirect } from '@sveltejs/kit'; 4 | 5 | export async function load({ params }) { 6 | const [photo] = await sql` 7 | SELECT p.*, a.name, a.avatar 8 | FROM photo p 9 | INNER JOIN account a ON p.account_id = a.id 10 | WHERE p.id = ${params.photo} 11 | AND p.published = TRUE 12 | AND a.name = ${params.account} 13 | `; 14 | 15 | if (!photo) { 16 | throw error(404); 17 | } 18 | 19 | const comments = await sql` 20 | SELECT c.*, a.name, a.avatar 21 | FROM comment c 22 | INNER JOIN account a ON c.account_id = a.id 23 | WHERE c.photo_id = ${params.photo} 24 | ORDER BY c.created_at DESC 25 | `; 26 | 27 | const likes = await sql` 28 | SELECT a.name, a.avatar 29 | FROM likes l 30 | INNER JOIN account a ON l.account_id = a.id 31 | WHERE l.photo_id = ${params.photo} 32 | ORDER BY l.created_at DESC 33 | `; 34 | 35 | return { 36 | photo: photo as PhotoListItem, 37 | comments: Array.from(comments) as Comment[], 38 | likes: Array.from(likes) as Account[] 39 | }; 40 | } 41 | 42 | export const actions = { 43 | toggle_like: async ({ locals, params, request }) => { 44 | if (!locals.user) throw error(401); 45 | 46 | const data = await request.formData(); 47 | 48 | const liked = data.get('liked') === 'true'; 49 | 50 | if (liked) { 51 | await sql` 52 | INSERT INTO likes (account_id, photo_id) 53 | VALUES (${locals.user.id}, ${params.photo}) 54 | ON CONFLICT DO NOTHING 55 | `; 56 | } else { 57 | await sql` 58 | DELETE FROM likes 59 | WHERE account_id = ${locals.user.id} 60 | AND photo_id = ${params.photo} 61 | `; 62 | } 63 | }, 64 | 65 | delete_photo: async ({ locals, params }) => { 66 | if (!locals.user) throw error(401); 67 | if (locals.user.name !== params.account) throw error(403); 68 | 69 | await sql` 70 | DELETE FROM photo 71 | WHERE id = ${params.photo} 72 | `; 73 | 74 | throw redirect(303, `/${params.account}`); 75 | }, 76 | 77 | update_description: async ({ locals, params, request }) => {}, 78 | 79 | post_comment: async ({ locals, params, request }) => { 80 | if (!locals.user) throw error(401); 81 | 82 | const data = await request.formData(); 83 | 84 | const text = data.get('text') as string; 85 | if (!text) throw error(422); 86 | 87 | const [{ id }] = await sql` 88 | INSERT INTO comment (account_id, photo_id, text) 89 | VALUES (${locals.user.id}, ${params.photo}, ${text}) 90 | returning id; 91 | `; 92 | 93 | return { id }; 94 | }, 95 | 96 | delete_comment: async ({ locals, request }) => { 97 | if (!locals.user) throw error(401); 98 | 99 | const data = await request.formData(); 100 | 101 | const id = data.get('id') as string; 102 | if (!id) throw error(422); 103 | 104 | await sql` 105 | DELETE FROM comment 106 | WHERE id = ${id} 107 | AND account_id = ${locals.user.id} 108 | `; 109 | } 110 | }; 111 | -------------------------------------------------------------------------------- /src/routes/[account]/[photo]/+page.svelte: -------------------------------------------------------------------------------- 1 | 25 | 26 |
27 |

28 | posted by {data.photo.name} 29 | {ago(data.photo.created_at, $now)} 30 |

31 | 32 | {#if data.photo.name === data.user?.name} 33 |
34 | 35 |
36 | {/if} 37 |
38 | 39 |
40 | 41 |

{data.photo.description}

42 |
43 | 44 |
45 | {#if data.user} 46 |
{ 50 | const { likes, user } = data; 51 | 52 | if (liked_by_user) { 53 | data.likes = likes.filter((like) => like.name !== user?.name); 54 | } else { 55 | data.likes = [user, ...likes]; 56 | } 57 | 58 | sync(); 59 | 60 | return ({ result }) => { 61 | if (result.type !== 'success') { 62 | data.likes = likes; 63 | sync(); 64 | } 65 | }; 66 | }} 67 | > 68 | 76 |
77 | {/if} 78 | 79 |

80 | {#if data.likes.length > 0} 81 | liked by {data.likes.length} {data.likes.length === 1 ? 'person' : 'people'} 82 | {:else if data.user} 83 | be the first to like this photo 84 | {/if} 85 |

86 |
87 | 88 | {#if data.user} 89 |
{ 94 | pending = true; 95 | 96 | const account = data.user; 97 | if (!account) return; // for typescript 98 | 99 | const comment = { 100 | name: account?.name, 101 | avatar: account?.avatar, 102 | text: formData.get('text'), 103 | id: '', 104 | created_at: new Date(), 105 | photo_id: data.photo.id 106 | }; 107 | 108 | // @ts-ignore 109 | data.comments = [comment, ...data.comments]; 110 | sync(); 111 | 112 | form.reset(); 113 | 114 | return ({ result, update }) => { 115 | pending = false; 116 | 117 | if (result.type === 'success') { 118 | comment.id = result.data.id; 119 | data.comments = data.comments; 120 | } else { 121 | update(); 122 | sync(); 123 | } 124 | }; 125 | }} 126 | > 127 |