Bläddra i källkod

add frontend code & rename comment.ts to server.ts

Track3 2 år sedan
förälder
incheckning
93a508d9d5
16 ändrade filer med 582 tillägg och 3 borttagningar
  1. 27 1
      .gitignore
  2. 3 0
      .vscode/extensions.json
  3. 1 2
      .vscode/settings.json
  4. 32 0
      README.md
  5. 18 0
      deno.json
  6. 281 0
      deno.lock
  7. 12 0
      index.html
  8. 0 0
      server.ts
  9. 46 0
      src/App.svelte
  10. 3 0
      src/app.css
  11. 40 0
      src/lib/Entry.svelte
  12. 93 0
      src/lib/Form.svelte
  13. 8 0
      src/main.ts
  14. 2 0
      src/vite-env.d.ts
  15. 5 0
      svelte.config.js
  16. 11 0
      vite.config.mts

+ 27 - 1
.gitignore

@@ -1 +1,27 @@
-.env
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+
+node_modules
+dist
+dist-ssr
+*.local
+.vite
+.env
+dist/
+
+# Editor directories and files
+.vscode/*
+!.vscode/extensions.json
+.idea
+.DS_Store
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?

+ 3 - 0
.vscode/extensions.json

@@ -0,0 +1,3 @@
+{
+  "recommendations": ["svelte.svelte-vscode"]
+}

+ 1 - 2
.vscode/settings.json

@@ -1,4 +1,3 @@
 {
-    "deno.enable": true,
-    "deno.unstable": true
+    "deno.enable": true
 }

+ 32 - 0
README.md

@@ -0,0 +1,32 @@
+# Comment system
+
+This project is a WIP.
+
+## Running
+
+You need to have Deno v1.28.0 or later intalled to run this repo.
+
+Start a dev server:
+
+```
+$ deno task dev
+```
+
+## Deploy
+
+Build production assets:
+
+```
+$ deno task build
+```
+
+## Notes
+
+- You need to use `.mjs` or `.mts` extension for the `vite.config.[ext]` file.
+
+## Papercuts
+
+Currently there's a "papercut" for Deno users:
+
+- peer dependencies need to be referenced in `vite.config.js` - in this example
+  it is only `svelte` package that needs to be referenced

+ 18 - 0
deno.json

@@ -0,0 +1,18 @@
+{
+  "tasks": {
+    "dev": "deno run -A --node-modules-dir npm:vite",
+    "build": "deno run -A --node-modules-dir npm:vite build",
+    "preview": "deno run -A --node-modules-dir npm:vite preview",
+    "serve": "deno run --allow-net --allow-read https://deno.land/std@0.157.0/http/file_server.ts dist/",
+    "dev-server": "deno run --allow-net --allow-read --allow-env server.ts"
+  },
+  "compilerOptions": {
+    "lib": [
+      "dom",
+      "dom.iterable",
+      "dom.asynciterable",
+      "deno.ns",
+      "deno.unstable"
+    ]
+  }
+}

+ 281 - 0
deno.lock

@@ -0,0 +1,281 @@
+{
+  "version": "2",
+  "remote": {
+    "https://deno.land/std@0.160.0/encoding/hex.ts": "4cc5324417cbb4ac9b828453d35aed45b9cc29506fad658f1f138d981ae33795",
+    "https://deno.land/std@0.160.0/hash/md5.ts": "5df247e8d9e0abeb555ed8ff39d39c0c3240f52847bfb72e363ad582a102527c",
+    "https://deno.land/std@0.165.0/async/abortable.ts": "87aa7230be8360c24ad437212311c9e8d4328854baec27b4c7abb26e85515c06",
+    "https://deno.land/std@0.165.0/async/deadline.ts": "48ac998d7564969f3e6ec6b6f9bf0217ebd00239b1b2292feba61272d5dd58d0",
+    "https://deno.land/std@0.165.0/async/debounce.ts": "dc8b92d4a4fe7eac32c924f2b8d3e62112530db70cadce27042689d82970b350",
+    "https://deno.land/std@0.165.0/async/deferred.ts": "d8fb253ffde2a056e4889ef7e90f3928f28be9f9294b6505773d33f136aab4e6",
+    "https://deno.land/std@0.165.0/async/delay.ts": "0419dfc993752849692d1f9647edf13407c7facc3509b099381be99ffbc9d699",
+    "https://deno.land/std@0.165.0/async/mod.ts": "dd0a8ed4f3984ffabe2fcca7c9f466b7932d57b1864ffee148a5d5388316db6b",
+    "https://deno.land/std@0.165.0/async/mux_async_iterator.ts": "3447b28a2a582224a3d4d3596bccbba6e85040da3b97ed64012f7decce98d093",
+    "https://deno.land/std@0.165.0/async/pool.ts": "ef9eb97b388543acbf0ac32647121e4dbe629236899586c4d4311a8770fbb239",
+    "https://deno.land/std@0.165.0/async/tee.ts": "9af3a3e7612af75861308b52249e167f5ebc3dcfc8a1a4d45462d96606ee2b70",
+    "https://deno.land/std@0.165.0/dotenv/load.ts": "9adeb1fb395f991f958a06c8b3adafdcbac0056e7a6a35bf9d33eab6347ef63c",
+    "https://deno.land/std@0.165.0/dotenv/mod.ts": "b149416f0daa0361873097495d16adbb321b8bcb594dcc5cdb6bf9639fd173fd",
+    "https://deno.land/std@0.165.0/dotenv/util.ts": "6cc392f087577a26a27f0463f77cc0c31a390aa055917099935b36eb2454592d",
+    "https://deno.land/std@0.165.0/http/server.ts": "e99c1bee8a3f6571ee4cdeb2966efad465b8f6fe62bec1bdb59c1f007cc4d155",
+    "https://unpkg.com/pocketbase@0.8.3/dist/pocketbase.es.mjs": "c9d55d64e0a1a6844a6c9bb08cc438412ae1c8a7fe7d32beb61a76cdb8ba386e"
+  },
+  "npm": {
+    "specifiers": {
+      "@sveltejs/vite-plugin-svelte@^2.0.0": "@sveltejs/vite-plugin-svelte@2.0.0_svelte@3.54.0_vite@4.0.0",
+      "insane": "insane@2.6.2",
+      "snarkdown": "snarkdown@2.0.0",
+      "svelte@^3.54.0": "svelte@3.54.0",
+      "vite": "vite@4.0.0",
+      "vite@^4.0.0": "vite@4.0.0"
+    },
+    "packages": {
+      "@esbuild/android-arm64@0.16.4": {
+        "integrity": "sha512-VPuTzXFm/m2fcGfN6CiwZTlLzxrKsWbPkG7ArRFpuxyaHUm/XFHQPD4xNwZT6uUmpIHhnSjcaCmcla8COzmZ5Q==",
+        "dependencies": {}
+      },
+      "@esbuild/android-arm@0.16.4": {
+        "integrity": "sha512-rZzb7r22m20S1S7ufIc6DC6W659yxoOrl7sKP1nCYhuvUlnCFHVSbATG4keGUtV8rDz11sRRDbWkvQZpzPaHiw==",
+        "dependencies": {}
+      },
+      "@esbuild/android-x64@0.16.4": {
+        "integrity": "sha512-MW+B2O++BkcOfMWmuHXB15/l1i7wXhJFqbJhp82IBOais8RBEQv2vQz/jHrDEHaY2X0QY7Wfw86SBL2PbVOr0g==",
+        "dependencies": {}
+      },
+      "@esbuild/darwin-arm64@0.16.4": {
+        "integrity": "sha512-a28X1O//aOfxwJVZVs7ZfM8Tyih2Za4nKJrBwW5Wm4yKsnwBy9aiS/xwpxiiTRttw3EaTg4Srerhcm6z0bu9Wg==",
+        "dependencies": {}
+      },
+      "@esbuild/darwin-x64@0.16.4": {
+        "integrity": "sha512-e3doCr6Ecfwd7VzlaQqEPrnbvvPjE9uoTpxG5pyLzr2rI2NMjDHmvY1E5EO81O/e9TUOLLkXA5m6T8lfjK9yAA==",
+        "dependencies": {}
+      },
+      "@esbuild/freebsd-arm64@0.16.4": {
+        "integrity": "sha512-Oup3G/QxBgvvqnXWrBed7xxkFNwAwJVHZcklWyQt7YCAL5bfUkaa6FVWnR78rNQiM8MqqLiT6ZTZSdUFuVIg1w==",
+        "dependencies": {}
+      },
+      "@esbuild/freebsd-x64@0.16.4": {
+        "integrity": "sha512-vAP+eYOxlN/Bpo/TZmzEQapNS8W1njECrqkTpNgvXskkkJC2AwOXwZWai/Kc2vEFZUXQttx6UJbj9grqjD/+9Q==",
+        "dependencies": {}
+      },
+      "@esbuild/linux-arm64@0.16.4": {
+        "integrity": "sha512-2zXoBhv4r5pZiyjBKrOdFP4CXOChxXiYD50LRUU+65DkdS5niPFHbboKZd/c81l0ezpw7AQnHeoCy5hFrzzs4g==",
+        "dependencies": {}
+      },
+      "@esbuild/linux-arm@0.16.4": {
+        "integrity": "sha512-A47ZmtpIPyERxkSvIv+zLd6kNIOtJH03XA0Hy7jaceRDdQaQVGSDt4mZqpWqJYgDk9rg96aglbF6kCRvPGDSUA==",
+        "dependencies": {}
+      },
+      "@esbuild/linux-ia32@0.16.4": {
+        "integrity": "sha512-uxdSrpe9wFhz4yBwt2kl2TxS/NWEINYBUFIxQtaEVtglm1eECvsj1vEKI0KX2k2wCe17zDdQ3v+jVxfwVfvvjw==",
+        "dependencies": {}
+      },
+      "@esbuild/linux-loong64@0.16.4": {
+        "integrity": "sha512-peDrrUuxbZ9Jw+DwLCh/9xmZAk0p0K1iY5d2IcwmnN+B87xw7kujOkig6ZRcZqgrXgeRGurRHn0ENMAjjD5DEg==",
+        "dependencies": {}
+      },
+      "@esbuild/linux-mips64el@0.16.4": {
+        "integrity": "sha512-sD9EEUoGtVhFjjsauWjflZklTNr57KdQ6xfloO4yH1u7vNQlOfAlhEzbyBKfgbJlW7rwXYBdl5/NcZ+Mg2XhQA==",
+        "dependencies": {}
+      },
+      "@esbuild/linux-ppc64@0.16.4": {
+        "integrity": "sha512-X1HSqHUX9D+d0l6/nIh4ZZJ94eQky8d8z6yxAptpZE3FxCWYWvTDd9X9ST84MGZEJx04VYUD/AGgciddwO0b8g==",
+        "dependencies": {}
+      },
+      "@esbuild/linux-riscv64@0.16.4": {
+        "integrity": "sha512-97ANpzyNp0GTXCt6SRdIx1ngwncpkV/z453ZuxbnBROCJ5p/55UjhbaG23UdHj88fGWLKPFtMoU4CBacz4j9FA==",
+        "dependencies": {}
+      },
+      "@esbuild/linux-s390x@0.16.4": {
+        "integrity": "sha512-pUvPQLPmbEeJRPjP0DYTC1vjHyhrnCklQmCGYbipkep+oyfTn7GTBJXoPodR7ZS5upmEyc8lzAkn2o29wD786A==",
+        "dependencies": {}
+      },
+      "@esbuild/linux-x64@0.16.4": {
+        "integrity": "sha512-N55Q0mJs3Sl8+utPRPBrL6NLYZKBCLLx0bme/+RbjvMforTGGzFvsRl4xLTZMUBFC1poDzBEPTEu5nxizQ9Nlw==",
+        "dependencies": {}
+      },
+      "@esbuild/netbsd-x64@0.16.4": {
+        "integrity": "sha512-LHSJLit8jCObEQNYkgsDYBh2JrJT53oJO2HVdkSYLa6+zuLJh0lAr06brXIkljrlI+N7NNW1IAXGn/6IZPi3YQ==",
+        "dependencies": {}
+      },
+      "@esbuild/openbsd-x64@0.16.4": {
+        "integrity": "sha512-nLgdc6tWEhcCFg/WVFaUxHcPK3AP/bh+KEwKtl69Ay5IBqUwKDaq/6Xk0E+fh/FGjnLwqFSsarsbPHeKM8t8Sw==",
+        "dependencies": {}
+      },
+      "@esbuild/sunos-x64@0.16.4": {
+        "integrity": "sha512-08SluG24GjPO3tXKk95/85n9kpyZtXCVwURR2i4myhrOfi3jspClV0xQQ0W0PYWHioJj+LejFMt41q+PG3mlAQ==",
+        "dependencies": {}
+      },
+      "@esbuild/win32-arm64@0.16.4": {
+        "integrity": "sha512-yYiRDQcqLYQSvNQcBKN7XogbrSvBE45FEQdH8fuXPl7cngzkCvpsG2H9Uey39IjQ6gqqc+Q4VXYHsQcKW0OMjQ==",
+        "dependencies": {}
+      },
+      "@esbuild/win32-ia32@0.16.4": {
+        "integrity": "sha512-5rabnGIqexekYkh9zXG5waotq8mrdlRoBqAktjx2W3kb0zsI83mdCwrcAeKYirnUaTGztR5TxXcXmQrEzny83w==",
+        "dependencies": {}
+      },
+      "@esbuild/win32-x64@0.16.4": {
+        "integrity": "sha512-sN/I8FMPtmtT2Yw+Dly8Ur5vQ5a/RmC8hW7jO9PtPSQUPkowxWpcUZnqOggU7VwyT3Xkj6vcXWd3V/qTXwultQ==",
+        "dependencies": {}
+      },
+      "@jridgewell/sourcemap-codec@1.4.14": {
+        "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==",
+        "dependencies": {}
+      },
+      "@sveltejs/vite-plugin-svelte@2.0.0_svelte@3.54.0_vite@4.0.0": {
+        "integrity": "sha512-oUFrYQarRv4fppmxdrv00qw3wX8Ycdj0uv33MfpRZyR8K67dyxiOcHnqkB0zSy5sDJA8RC/2aNtYhXJ8NINVHQ==",
+        "dependencies": {
+          "debug": "debug@4.3.4",
+          "deepmerge": "deepmerge@4.2.2",
+          "kleur": "kleur@4.1.5",
+          "magic-string": "magic-string@0.27.0",
+          "svelte": "svelte@3.54.0",
+          "svelte-hmr": "svelte-hmr@0.15.1_svelte@3.54.0",
+          "vite": "vite@4.0.0",
+          "vitefu": "vitefu@0.2.3_vite@4.0.0"
+        }
+      },
+      "assignment@2.0.0": {
+        "integrity": "sha512-naMULXjtgCs9SVUEtyvJNt68aF18em7/W+dhbR59kbz9cXWPEvUkCun2tqlgqRPSqZaKPpqLc5ZnwL8jVmJRvw==",
+        "dependencies": {}
+      },
+      "debug@4.3.4": {
+        "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
+        "dependencies": { "ms": "ms@2.1.2" }
+      },
+      "deepmerge@4.2.2": {
+        "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==",
+        "dependencies": {}
+      },
+      "esbuild@0.16.4": {
+        "integrity": "sha512-qQrPMQpPTWf8jHugLWHoGqZjApyx3OEm76dlTXobHwh/EBbavbRdjXdYi/GWr43GyN0sfpap14GPkb05NH3ROA==",
+        "dependencies": {
+          "@esbuild/android-arm": "@esbuild/android-arm@0.16.4",
+          "@esbuild/android-arm64": "@esbuild/android-arm64@0.16.4",
+          "@esbuild/android-x64": "@esbuild/android-x64@0.16.4",
+          "@esbuild/darwin-arm64": "@esbuild/darwin-arm64@0.16.4",
+          "@esbuild/darwin-x64": "@esbuild/darwin-x64@0.16.4",
+          "@esbuild/freebsd-arm64": "@esbuild/freebsd-arm64@0.16.4",
+          "@esbuild/freebsd-x64": "@esbuild/freebsd-x64@0.16.4",
+          "@esbuild/linux-arm": "@esbuild/linux-arm@0.16.4",
+          "@esbuild/linux-arm64": "@esbuild/linux-arm64@0.16.4",
+          "@esbuild/linux-ia32": "@esbuild/linux-ia32@0.16.4",
+          "@esbuild/linux-loong64": "@esbuild/linux-loong64@0.16.4",
+          "@esbuild/linux-mips64el": "@esbuild/linux-mips64el@0.16.4",
+          "@esbuild/linux-ppc64": "@esbuild/linux-ppc64@0.16.4",
+          "@esbuild/linux-riscv64": "@esbuild/linux-riscv64@0.16.4",
+          "@esbuild/linux-s390x": "@esbuild/linux-s390x@0.16.4",
+          "@esbuild/linux-x64": "@esbuild/linux-x64@0.16.4",
+          "@esbuild/netbsd-x64": "@esbuild/netbsd-x64@0.16.4",
+          "@esbuild/openbsd-x64": "@esbuild/openbsd-x64@0.16.4",
+          "@esbuild/sunos-x64": "@esbuild/sunos-x64@0.16.4",
+          "@esbuild/win32-arm64": "@esbuild/win32-arm64@0.16.4",
+          "@esbuild/win32-ia32": "@esbuild/win32-ia32@0.16.4",
+          "@esbuild/win32-x64": "@esbuild/win32-x64@0.16.4"
+        }
+      },
+      "fsevents@2.3.2": {
+        "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
+        "dependencies": {}
+      },
+      "function-bind@1.1.1": {
+        "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
+        "dependencies": {}
+      },
+      "has@1.0.3": {
+        "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
+        "dependencies": { "function-bind": "function-bind@1.1.1" }
+      },
+      "he@0.5.0": {
+        "integrity": "sha512-DoufbNNOFzwRPy8uecq+j+VCPQ+JyDelHTmSgygrA5TsR8Cbw4Qcir5sGtWiusB4BdT89nmlaVDhSJOqC/33vw==",
+        "dependencies": {}
+      },
+      "insane@2.6.2": {
+        "integrity": "sha512-BqEL1CJsjJi+/C/zKZxv31zs3r6zkLH5Nz1WMFb7UBX2KHY2yXDpbFTSEmNHzomBbGDysIfkTX55A0mQZ2CQiw==",
+        "dependencies": { "assignment": "assignment@2.0.0", "he": "he@0.5.0" }
+      },
+      "is-core-module@2.11.0": {
+        "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==",
+        "dependencies": { "has": "has@1.0.3" }
+      },
+      "kleur@4.1.5": {
+        "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==",
+        "dependencies": {}
+      },
+      "magic-string@0.27.0": {
+        "integrity": "sha512-8UnnX2PeRAPZuN12svgR9j7M1uWMovg/CEnIwIG0LFkXSJJe4PdfUGiTGl8V9bsBHFUtfVINcSyYxd7q+kx9fA==",
+        "dependencies": {
+          "@jridgewell/sourcemap-codec": "@jridgewell/sourcemap-codec@1.4.14"
+        }
+      },
+      "ms@2.1.2": {
+        "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+        "dependencies": {}
+      },
+      "nanoid@3.3.4": {
+        "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==",
+        "dependencies": {}
+      },
+      "path-parse@1.0.7": {
+        "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
+        "dependencies": {}
+      },
+      "picocolors@1.0.0": {
+        "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==",
+        "dependencies": {}
+      },
+      "postcss@8.4.19": {
+        "integrity": "sha512-h+pbPsyhlYj6N2ozBmHhHrs9DzGmbaarbLvWipMRO7RLS+v4onj26MPFXA5OBYFxyqYhUJK456SwDcY9H2/zsA==",
+        "dependencies": {
+          "nanoid": "nanoid@3.3.4",
+          "picocolors": "picocolors@1.0.0",
+          "source-map-js": "source-map-js@1.0.2"
+        }
+      },
+      "resolve@1.22.1": {
+        "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==",
+        "dependencies": {
+          "is-core-module": "is-core-module@2.11.0",
+          "path-parse": "path-parse@1.0.7",
+          "supports-preserve-symlinks-flag": "supports-preserve-symlinks-flag@1.0.0"
+        }
+      },
+      "rollup@3.7.2": {
+        "integrity": "sha512-orqIX5zkHyHKVsIBl8J5a2tnVikOAMte0DgOLViyW6McYuj45FG+cQPrXILhaifBSmy0D0hKbHg2RbgzFJcwTg==",
+        "dependencies": { "fsevents": "fsevents@2.3.2" }
+      },
+      "snarkdown@2.0.0": {
+        "integrity": "sha512-MgL/7k/AZdXCTJiNgrO7chgDqaB9FGM/1Tvlcenenb7div6obaDATzs16JhFyHHBGodHT3B7RzRc5qk8pFhg3A==",
+        "dependencies": {}
+      },
+      "source-map-js@1.0.2": {
+        "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==",
+        "dependencies": {}
+      },
+      "supports-preserve-symlinks-flag@1.0.0": {
+        "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
+        "dependencies": {}
+      },
+      "svelte-hmr@0.15.1_svelte@3.54.0": {
+        "integrity": "sha512-BiKB4RZ8YSwRKCNVdNxK/GfY+r4Kjgp9jCLEy0DuqAKfmQtpL38cQK3afdpjw4sqSs4PLi3jIPJIFp259NkZtA==",
+        "dependencies": { "svelte": "svelte@3.54.0" }
+      },
+      "svelte@3.54.0": {
+        "integrity": "sha512-tdrgeJU0hob0ZWAMoKXkhcxXA7dpTg6lZGxUeko5YqvPdJBiyRspGsCwV27kIrbrqPP2WUoSV9ca0gnLlw8YzQ==",
+        "dependencies": {}
+      },
+      "vite@4.0.0": {
+        "integrity": "sha512-ynad+4kYs8Jcnn8J7SacS9vAbk7eMy0xWg6E7bAhS1s79TK+D7tVFGXVZ55S7RNLRROU1rxoKlvZ/qjaB41DGA==",
+        "dependencies": {
+          "esbuild": "esbuild@0.16.4",
+          "fsevents": "fsevents@2.3.2",
+          "postcss": "postcss@8.4.19",
+          "resolve": "resolve@1.22.1",
+          "rollup": "rollup@3.7.2"
+        }
+      },
+      "vitefu@0.2.3_vite@4.0.0": {
+        "integrity": "sha512-75l7TTuU8isAhz1QFtNKjDkqjxvndfMC1AfIMjJ0ZQ59ZD0Ow9QOIsJJX16Wv9PS8f+zMzp6fHy5cCbKG/yVUQ==",
+        "dependencies": { "vite": "vite@4.0.0" }
+      }
+    }
+  }
+}

+ 12 - 0
index.html

@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="UTF-8" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    <title>Svelte Comment</title>
+  </head>
+  <body>
+    <div id="comments" data-url="http://localhost:8000/"></div>
+    <script type="module" src="/src/main.ts"></script>
+  </body>
+</html>

+ 0 - 0
comment.ts → server.ts


+ 46 - 0
src/App.svelte

@@ -0,0 +1,46 @@
+<script lang="ts">
+  import { setContext, getContext } from "svelte";
+  import Form from './lib/Form.svelte';
+  import Entry from './lib/Entry.svelte';
+
+  const url = document.getElementById('comments').dataset.url;
+  setContext("pageUri", window.location.pathname);
+  setContext("reqUrl", `${url}?uri=${encodeURIComponent(getContext("pageUri"))}`);
+  let count: number;
+  let comments = []
+
+  async function getComments() {
+    const response = await fetch(getContext("reqUrl"));
+    const data = await response.json();
+    // console.log(data);
+
+    count = data.count
+    comments = data.list
+  };
+  let promise = getComments();
+</script>
+
+<h2><span>评论</span>{#if count}<sup class=comment-counter>{count}</sup>{/if}</h2>
+<p>您的电子邮件地址不会被公开。</p>
+<Form bind:comments={comments} bind:count={count}/>
+
+{#await promise}
+	<p>🍵 评论加载中……</p>
+{:catch error}
+	<p style="color: red">{error.message}</p>
+{/await}
+
+{#each comments as comment}
+<div class="comment-group">
+  <Entry data={comment} parentId={comment.id} bind:comments={comments} bind:count={count}/>
+  {#if comment.reply.length > 0}
+  <div class="replies">
+    {#each comment.reply as reply}
+    <Entry data={reply} parentId={comment.id} bind:comments={comments} bind:count={count}/>
+    {/each}
+  </div>
+  {/if}
+</div>
+{/each}
+
+

+ 3 - 0
src/app.css

@@ -0,0 +1,3 @@
+.replies {
+  margin-left: 2rem;
+}

+ 40 - 0
src/lib/Entry.svelte

@@ -0,0 +1,40 @@
+<script lang="ts">
+  import Form from './Form.svelte';
+  import snarkdown from 'snarkdown';
+  import insane from 'insane';
+  export let parentId = "";
+  export let data;
+  export let comments = [];
+  export let count: number;
+
+  let fullDate: Date;
+  let displayDate: string;
+  $: fullDate = new Date(data.created);
+  $: displayDate = `${fullDate.getFullYear()}-${fullDate.getMonth()+1}-${fullDate.getDate()}`;
+
+</script>
+
+
+<article id="{data.id}">
+  <aside class="comment-avatar">
+    <img src="https://gravatar.loli.net/avatar/{data.avatar}?d=mp" alt="{data.author}的头像" loading="lazy">
+  </aside>
+  <div class="comment-wrapper">
+    <header>
+      {#if data.website}
+      <a href={data.website} target="_blank" rel="noreferrer noopener">{data.author}</a>
+      {:else}
+      <span>{data.author}</span>
+      {/if}
+      <span> &#183; </span>
+      <span class="comment-date" title={fullDate.toString()}>{displayDate}</span>
+    </header>
+    <main>
+      {@html insane(snarkdown(data.content))}
+    </main>
+    <button class="reply-btn" type="button" on:click={()=>{data.formOpened = !data.formOpened}}>{data.formOpened? "关闭" : "回复"}</button>
+    {#if data.formOpened}
+    <Form parentId={parentId} bind:formOpened={data.formOpened} bind:comments={comments} bind:count={count}/>
+    {/if}
+  </div>
+</article>

+ 93 - 0
src/lib/Form.svelte

@@ -0,0 +1,93 @@
+<script lang="ts">
+  import { getContext } from 'svelte';
+  import snarkdown from 'snarkdown';
+  import insane from 'insane';
+  export let parentId = "";
+  export let comments = [];
+  export let count: number;
+  export let formOpened = true;
+  let showPreview = false;
+  const reqUrl:string = getContext("reqUrl")
+
+  let newComment = {
+    uri: getContext("pageUri"),
+    author: "",
+    email: "",
+    website: "",
+    content: "",
+    parent: parentId
+  };
+
+  async function sendReply() {
+    const submitBtn = this.querySelector('button[type="submit"]');
+    submitBtn.disabled = true;
+
+    const response = await fetch(reqUrl, {
+      method: "POST",
+      headers: {
+        "Content-Type": "application/json; charset=UTF-8",
+      },
+      body: JSON.stringify(newComment)
+    })
+    const data = await response.json();
+    // console.log(data);
+
+    // Put new comment to page
+    count++
+    if (parentId === "") {
+      comments = [{
+        id: data.id,
+        author: data.author,
+        avatar: data.avatar,
+        website: data.website,
+        content: data.content,
+        created: data.created,
+        reply: []
+      }, ...comments]
+    } else {
+      const index = comments.findIndex(e => e.id === parentId)
+      comments[index].reply.push({
+        id: data.id,
+        author: data.author,
+        avatar: data.avatar,
+        website: data.website,
+        content: data.content,
+        created: data.created,
+      });
+    };
+    newComment.content = "";
+    submitBtn.disabled = false;
+    formOpened=false;
+  }
+</script>
+
+<form class="comment-form" on:submit|preventDefault={sendReply}>
+  <textarea name="content" placeholder="欢迎评论……(支持 Markdown 语法)" rows="6" bind:value={newComment.content} required></textarea>
+  {#if showPreview}
+  <div class="comment-preview">
+    {@html insane(snarkdown(newComment.content))}
+  </div>
+  {/if}
+  <div class=form-wrapper>
+    <label for="name">
+      名字<span class="required" aria-hidden="true">*</span>
+      <input type="text" name="author" placeholder="John Doe" bind:value={newComment.author} required>
+    </label>
+    <label for="email">
+      邮箱<span class="required" aria-hidden="true">*</span>
+      <input type="email" name="email" placeholder="someone@example.com" bind:value={newComment.email} required>
+    </label>
+  </div>
+  <label for="website">
+    网址
+    <input type="url" name="website" placeholder="https://example.com" bind:value={newComment.website}>
+  </label>
+  <div class=form-wrapper>
+    <button type="button" on:click={()=>{showPreview = !showPreview}}>
+      <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-eye"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"></path><circle cx="12" cy="12" r="3"></circle></svg> 预览
+    </button>
+    <button type="submit">
+      <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-send"><line x1="22" y1="2" x2="11" y2="13"></line><polygon points="22 2 15 22 11 13 2 9 22 2"></polygon></svg> 发送
+    </button>
+  </div>
+</form>

+ 8 - 0
src/main.ts

@@ -0,0 +1,8 @@
+import './app.css'
+import App from './App.svelte'
+
+const app = new App({
+  target: document.getElementById('comments'),
+})
+
+export default app

+ 2 - 0
src/vite-env.d.ts

@@ -0,0 +1,2 @@
+/// <reference types="svelte" />
+/// <reference types="vite/client" />

+ 5 - 0
svelte.config.js

@@ -0,0 +1,5 @@
+import { vitePreprocess } from 'npm:@sveltejs/vite-plugin-svelte@^2.0.0'
+
+export default {
+  preprocess: vitePreprocess()
+}

+ 11 - 0
vite.config.mts

@@ -0,0 +1,11 @@
+import { defineConfig } from 'npm:vite@^4.0.0'
+import { svelte } from 'npm:@sveltejs/vite-plugin-svelte@^2.0.0'
+
+import 'npm:svelte@^3.54.0'
+import 'npm:snarkdown'
+import 'npm:insane'
+
+// https://vitejs.dev/config/
+export default defineConfig({
+  plugins: [svelte()]
+})