diff --git a/CHANGELOGS.md b/CHANGELOGS.md index d5cee41..4c81497 100644 --- a/CHANGELOGS.md +++ b/CHANGELOGS.md @@ -1,16 +1,3 @@ -# Campfire v0.4.0 -- Huge refactor, along with some improved documentation -- Custom emotes now show in the sidebar profile display -- Infinite scrolling notifications -- Notifications now show content warnings where applicable -- Notifications now show custom emoji -- Notifications now show post media -- Boosts now reflect the visibility of the original post -- Added compose box, and the ability to create posts -- Added button to delete own posts -- Rewrote Campfire URLs so they can be viewed anonymously -- Improved UI tweaks - # Campfire v0.3.0 - Added notifications view - Many more background tweaks, fixes, and optimisations diff --git a/package-lock.json b/package-lock.json index ef4c2d5..68c3b16 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,18 +1,18 @@ { "name": "campfire-client", - "version": "0.4.0", + "version": "0.3.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "campfire-client", - "version": "0.4.0", + "version": "0.3.0", "license": "GPL-3.0", "devDependencies": { "@poppanator/sveltekit-svg": "^4.2.1", "@sveltejs/adapter-auto": "^3.2.2", "@sveltejs/adapter-static": "^3.0.2", - "@sveltejs/kit": "^2.16.0", + "@sveltejs/kit": "^2.5.17", "@sveltejs/vite-plugin-svelte": "^3.1.1", "svelte": "^4.2.18", "vite": "^5.3.1" @@ -477,9 +477,9 @@ } }, "node_modules/@polka/url": { - "version": "1.0.0-next.28", - "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.28.tgz", - "integrity": "sha512-8LduaNlMZGwdZ6qWrKlfa+2M4gahzFkprZiAt2TF8uS0qQgBizKXpXURqvTJ4WtmupWxaLqjRb2UCTe72mu+Aw==", + "version": "1.0.0-next.25", + "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.25.tgz", + "integrity": "sha512-j7P6Rgr3mmtdkeDGTe0E/aYyWEWVtc5yFXtHCRHs28/jptDEWfaVOc5T7cblqy1XKPPfCxJc/8DwQ5YgLOZOVQ==", "dev": true, "license": "MIT" }, @@ -526,9 +526,9 @@ "dev": true }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.31.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.31.0.tgz", - "integrity": "sha512-9NrR4033uCbUBRgvLcBrJofa2KY9DzxL2UKZ1/4xA/mnTNyhZCWBuD8X3tPm1n4KxcgaraOYgrFKSgwjASfmlA==", + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.18.0.tgz", + "integrity": "sha512-Tya6xypR10giZV1XzxmH5wr25VcZSncG0pZIjfePT0OVBvqNEurzValetGNarVrGiq66EBVAFn15iYX4w6FKgQ==", "cpu": [ "arm" ], @@ -540,9 +540,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.31.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.31.0.tgz", - "integrity": "sha512-iBbODqT86YBFHajxxF8ebj2hwKm1k8PTBQSojSt3d1FFt1gN+xf4CowE47iN0vOSdnd+5ierMHBbu/rHc7nq5g==", + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.18.0.tgz", + "integrity": "sha512-avCea0RAP03lTsDhEyfy+hpfr85KfyTctMADqHVhLAF3MlIkq83CP8UfAHUssgXTYd+6er6PaAhx/QGv4L1EiA==", "cpu": [ "arm64" ], @@ -554,9 +554,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.31.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.31.0.tgz", - "integrity": "sha512-WHIZfXgVBX30SWuTMhlHPXTyN20AXrLH4TEeH/D0Bolvx9PjgZnn4H677PlSGvU6MKNsjCQJYczkpvBbrBnG6g==", + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.18.0.tgz", + "integrity": "sha512-IWfdwU7KDSm07Ty0PuA/W2JYoZ4iTj3TUQjkVsO/6U+4I1jN5lcR71ZEvRh52sDOERdnNhhHU57UITXz5jC1/w==", "cpu": [ "arm64" ], @@ -568,9 +568,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.31.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.31.0.tgz", - "integrity": "sha512-hrWL7uQacTEF8gdrQAqcDy9xllQ0w0zuL1wk1HV8wKGSGbKPVjVUv/DEwT2+Asabf8Dh/As+IvfdU+H8hhzrQQ==", + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.18.0.tgz", + "integrity": "sha512-n2LMsUz7Ynu7DoQrSQkBf8iNrjOGyPLrdSg802vk6XT3FtsgX6JbE8IHRvposskFm9SNxzkLYGSq9QdpLYpRNA==", "cpu": [ "x64" ], @@ -581,38 +581,10 @@ "darwin" ] }, - "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.31.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.31.0.tgz", - "integrity": "sha512-S2oCsZ4hJviG1QjPY1h6sVJLBI6ekBeAEssYKad1soRFv3SocsQCzX6cwnk6fID6UQQACTjeIMB+hyYrFacRew==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.31.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.31.0.tgz", - "integrity": "sha512-pCANqpynRS4Jirn4IKZH4tnm2+2CqCNLKD7gAdEjzdLGbH1iO0zouHz4mxqg0uEMpO030ejJ0aA6e1PJo2xrPA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.31.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.31.0.tgz", - "integrity": "sha512-0O8ViX+QcBd3ZmGlcFTnYXZKGbFu09EhgD27tgTdGnkcYXLat4KIsBBQeKLR2xZDCXdIBAlWLkiXE1+rJpCxFw==", + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.18.0.tgz", + "integrity": "sha512-C/zbRYRXFjWvz9Z4haRxcTdnkPt1BtCkz+7RtBSuNmKzMzp3ZxdM28Mpccn6pt28/UWUCTXa+b0Mx1k3g6NOMA==", "cpu": [ "arm" ], @@ -624,9 +596,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.31.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.31.0.tgz", - "integrity": "sha512-w5IzG0wTVv7B0/SwDnMYmbr2uERQp999q8FMkKG1I+j8hpPX2BYFjWe69xbhbP6J9h2gId/7ogesl9hwblFwwg==", + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.18.0.tgz", + "integrity": "sha512-l3m9ewPgjQSXrUMHg93vt0hYCGnrMOcUpTz6FLtbwljo2HluS4zTXFy2571YQbisTnfTKPZ01u/ukJdQTLGh9A==", "cpu": [ "arm" ], @@ -638,9 +610,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.31.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.31.0.tgz", - "integrity": "sha512-JyFFshbN5xwy6fulZ8B/8qOqENRmDdEkcIMF0Zz+RsfamEW+Zabl5jAb0IozP/8UKnJ7g2FtZZPEUIAlUSX8cA==", + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.18.0.tgz", + "integrity": "sha512-rJ5D47d8WD7J+7STKdCUAgmQk49xuFrRi9pZkWoRD1UeSMakbcepWXPF8ycChBoAqs1pb2wzvbY6Q33WmN2ftw==", "cpu": [ "arm64" ], @@ -652,9 +624,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.31.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.31.0.tgz", - "integrity": "sha512-kpQXQ0UPFeMPmPYksiBL9WS/BDiQEjRGMfklVIsA0Sng347H8W2iexch+IEwaR7OVSKtr2ZFxggt11zVIlZ25g==", + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.18.0.tgz", + "integrity": "sha512-be6Yx37b24ZwxQ+wOQXXLZqpq4jTckJhtGlWGZs68TgdKXJgw54lUUoFYrg6Zs/kjzAQwEwYbp8JxZVzZLRepQ==", "cpu": [ "arm64" ], @@ -665,24 +637,10 @@ "linux" ] }, - "node_modules/@rollup/rollup-linux-loongarch64-gnu": { - "version": "4.31.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.31.0.tgz", - "integrity": "sha512-pMlxLjt60iQTzt9iBb3jZphFIl55a70wexvo8p+vVFK+7ifTRookdoXX3bOsRdmfD+OKnMozKO6XM4zR0sHRrQ==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.31.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.31.0.tgz", - "integrity": "sha512-D7TXT7I/uKEuWiRkEFbed1UUYZwcJDU4vZQdPTcepK7ecPhzKOYk4Er2YR4uHKme4qDeIh6N3XrLfpuM7vzRWQ==", + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.18.0.tgz", + "integrity": "sha512-hNVMQK+qrA9Todu9+wqrXOHxFiD5YmdEi3paj6vP02Kx1hjd2LLYR2eaN7DsEshg09+9uzWi2W18MJDlG0cxJA==", "cpu": [ "ppc64" ], @@ -694,9 +652,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.31.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.31.0.tgz", - "integrity": "sha512-wal2Tc8O5lMBtoePLBYRKj2CImUCJ4UNGJlLwspx7QApYny7K1cUYlzQ/4IGQBLmm+y0RS7dwc3TDO/pmcneTw==", + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.18.0.tgz", + "integrity": "sha512-ROCM7i+m1NfdrsmvwSzoxp9HFtmKGHEqu5NNDiZWQtXLA8S5HBCkVvKAxJ8U+CVctHwV2Gb5VUaK7UAkzhDjlg==", "cpu": [ "riscv64" ], @@ -708,9 +666,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.31.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.31.0.tgz", - "integrity": "sha512-O1o5EUI0+RRMkK9wiTVpk2tyzXdXefHtRTIjBbmFREmNMy7pFeYXCFGbhKFwISA3UOExlo5GGUuuj3oMKdK6JQ==", + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.18.0.tgz", + "integrity": "sha512-0UyyRHyDN42QL+NbqevXIIUnKA47A+45WyasO+y2bGJ1mhQrfrtXUpTxCOrfxCR4esV3/RLYyucGVPiUsO8xjg==", "cpu": [ "s390x" ], @@ -722,9 +680,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.31.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.31.0.tgz", - "integrity": "sha512-zSoHl356vKnNxwOWnLd60ixHNPRBglxpv2g7q0Cd3Pmr561gf0HiAcUBRL3S1vPqRC17Zo2CX/9cPkqTIiai1g==", + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.18.0.tgz", + "integrity": "sha512-xuglR2rBVHA5UsI8h8UbX4VJ470PtGCf5Vpswh7p2ukaqBGFTnsfzxUBetoWBWymHMxbIG0Cmx7Y9qDZzr648w==", "cpu": [ "x64" ], @@ -736,9 +694,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.31.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.31.0.tgz", - "integrity": "sha512-ypB/HMtcSGhKUQNiFwqgdclWNRrAYDH8iMYH4etw/ZlGwiTVxBz2tDrGRrPlfZu6QjXwtd+C3Zib5pFqID97ZA==", + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.18.0.tgz", + "integrity": "sha512-LKaqQL9osY/ir2geuLVvRRs+utWUNilzdE90TpyoX0eNqPzWjRm14oMEE+YLve4k/NAqCdPkGYDaDF5Sw+xBfg==", "cpu": [ "x64" ], @@ -750,9 +708,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.31.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.31.0.tgz", - "integrity": "sha512-JuhN2xdI/m8Hr+aVO3vspO7OQfUFO6bKLIRTAy0U15vmWjnZDLrEgCZ2s6+scAYaQVpYSh9tZtRijApw9IXyMw==", + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.18.0.tgz", + "integrity": "sha512-7J6TkZQFGo9qBKH0pk2cEVSRhJbL6MtfWxth7Y5YmZs57Pi+4x6c2dStAUvaQkHQLnEQv1jzBUW43GvZW8OFqA==", "cpu": [ "arm64" ], @@ -764,9 +722,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.31.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.31.0.tgz", - "integrity": "sha512-U1xZZXYkvdf5MIWmftU8wrM5PPXzyaY1nGCI4KI4BFfoZxHamsIe+BtnPLIvvPykvQWlVbqUXdLa4aJUuilwLQ==", + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.18.0.tgz", + "integrity": "sha512-Txjh+IxBPbkUB9+SXZMpv+b/vnTEtFyfWZgJ6iyCmt2tdx0OF5WhFowLmnh8ENGNpfUlUZkdI//4IEmhwPieNg==", "cpu": [ "ia32" ], @@ -778,9 +736,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.31.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.31.0.tgz", - "integrity": "sha512-ul8rnCsUumNln5YWwz0ted2ZHFhzhRRnkpBZ+YRuHoRAlUji9KChpOUOndY7uykrPEPXVbHLlsdo6v5yXo/TXw==", + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.18.0.tgz", + "integrity": "sha512-UOo5FdvOL0+eIVTgS4tIdbW+TtnBLWg1YBCcU2KWM7nuNwRz9bksDX1bekJJCpu25N1DVWaCwnT39dVQxzqS8g==", "cpu": [ "x64" ], @@ -815,23 +773,25 @@ } }, "node_modules/@sveltejs/kit": { - "version": "2.16.0", - "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.16.0.tgz", - "integrity": "sha512-S9i1ZWKqluzoaJ6riYnEdbe+xJluMTMkhABouBa66GaWcAyCjW/jAc0NdJQJ/DXyK1CnP5quBW25e99MNyvLxA==", + "version": "2.5.17", + "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.5.17.tgz", + "integrity": "sha512-wiADwq7VreR3ctOyxilAZOfPz3Jiy2IIp2C8gfafhTdQaVuGIHllfqQm8dXZKADymKr3uShxzgLZFT+a+CM4kA==", "dev": true, + "hasInstallScript": true, "license": "MIT", "dependencies": { "@types/cookie": "^0.6.0", "cookie": "^0.6.0", - "devalue": "^5.1.0", - "esm-env": "^1.2.2", + "devalue": "^5.0.0", + "esm-env": "^1.0.0", "import-meta-resolve": "^4.1.0", "kleur": "^4.1.5", "magic-string": "^0.30.5", "mrmime": "^2.0.0", "sade": "^1.8.1", "set-cookie-parser": "^2.6.0", - "sirv": "^3.0.0" + "sirv": "^2.0.4", + "tiny-glob": "^0.2.9" }, "bin": { "svelte-kit": "svelte-kit.js" @@ -840,9 +800,9 @@ "node": ">=18.13" }, "peerDependencies": { - "@sveltejs/vite-plugin-svelte": "^3.0.0 || ^4.0.0-next.1 || ^5.0.0", + "@sveltejs/vite-plugin-svelte": "^3.0.0", "svelte": "^4.0.0 || ^5.0.0-next.0", - "vite": "^5.0.3 || ^6.0.0" + "vite": "^5.0.3" } }, "node_modules/@sveltejs/vite-plugin-svelte": { @@ -904,9 +864,9 @@ "license": "MIT" }, "node_modules/@types/estree": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", - "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", "dev": true, "license": "MIT" }, @@ -1103,9 +1063,9 @@ } }, "node_modules/devalue": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.1.1.tgz", - "integrity": "sha512-maua5KUiapvEwiEAe+XnlZ3Rh0GD+qI1J/nb9vrJc3muPXvcF/8gXYTWF76+5DAqHyDUtOIImEuo0YKE9mshVw==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.0.0.tgz", + "integrity": "sha512-gO+/OMXF7488D+u3ue+G7Y4AA3ZmUnB3eHJXmBTgNHvr4ZNzl36A0ZtG+XCRNYCkYx/bFmw4qtkoFLa+wSrwAA==", "dev": true, "license": "MIT" }, @@ -1221,9 +1181,9 @@ } }, "node_modules/esm-env": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.2.tgz", - "integrity": "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.0.0.tgz", + "integrity": "sha512-Cf6VksWPsTuW01vU9Mk/3vRue91Zevka5SjyNf3nEpokFRuqt/KjUQoGAwq9qMmhpLTHmXzSIrFRw8zxWzmFBA==", "dev": true, "license": "MIT" }, @@ -1252,6 +1212,20 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, + "node_modules/globalyzer": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/globalyzer/-/globalyzer-0.1.0.tgz", + "integrity": "sha512-40oNTM9UfG6aBmuKxk/giHn5nQ8RVz/SS4Ir6zgzOv9/qC3kKZ9v4etGTcJbEl/NyVQH7FGU7d+X1egr57Md2Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/globrex": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/globrex/-/globrex-0.1.2.tgz", + "integrity": "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==", + "dev": true, + "license": "MIT" + }, "node_modules/import-meta-resolve": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.1.0.tgz", @@ -1335,9 +1309,9 @@ "license": "MIT" }, "node_modules/nanoid": { - "version": "3.3.8", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", - "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", "dev": true, "funding": [ { @@ -1379,9 +1353,9 @@ } }, "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", + "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==", "dev": true, "license": "ISC" }, @@ -1398,9 +1372,9 @@ } }, "node_modules/postcss": { - "version": "8.5.1", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.1.tgz", - "integrity": "sha512-6oz2beyjc5VMn/KV1pPw8fliQkhBXrVn1Z3TVyqZxU8kZpzEKhBdmCFqI6ZbmGtamQvQGuU1sgPTk8ZrXDD7jQ==", + "version": "8.4.38", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", + "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", "dev": true, "funding": [ { @@ -1418,22 +1392,22 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.8", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" + "nanoid": "^3.3.7", + "picocolors": "^1.0.0", + "source-map-js": "^1.2.0" }, "engines": { "node": "^10 || ^12 || >=14" } }, "node_modules/rollup": { - "version": "4.31.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.31.0.tgz", - "integrity": "sha512-9cCE8P4rZLx9+PjoyqHLs31V9a9Vpvfo4qNcs6JCiGWYhw2gijSetFbH6SSy1whnkgcefnUwr8sad7tgqsGvnw==", + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.18.0.tgz", + "integrity": "sha512-QmJz14PX3rzbJCN1SG4Xe/bAAX2a6NpCP8ab2vfu2GiUr8AQcr2nCV/oEO3yneFarB67zk8ShlIyWb2LGTb3Sg==", "dev": true, "license": "MIT", "dependencies": { - "@types/estree": "1.0.6" + "@types/estree": "1.0.5" }, "bin": { "rollup": "dist/bin/rollup" @@ -1443,25 +1417,22 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.31.0", - "@rollup/rollup-android-arm64": "4.31.0", - "@rollup/rollup-darwin-arm64": "4.31.0", - "@rollup/rollup-darwin-x64": "4.31.0", - "@rollup/rollup-freebsd-arm64": "4.31.0", - "@rollup/rollup-freebsd-x64": "4.31.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.31.0", - "@rollup/rollup-linux-arm-musleabihf": "4.31.0", - "@rollup/rollup-linux-arm64-gnu": "4.31.0", - "@rollup/rollup-linux-arm64-musl": "4.31.0", - "@rollup/rollup-linux-loongarch64-gnu": "4.31.0", - "@rollup/rollup-linux-powerpc64le-gnu": "4.31.0", - "@rollup/rollup-linux-riscv64-gnu": "4.31.0", - "@rollup/rollup-linux-s390x-gnu": "4.31.0", - "@rollup/rollup-linux-x64-gnu": "4.31.0", - "@rollup/rollup-linux-x64-musl": "4.31.0", - "@rollup/rollup-win32-arm64-msvc": "4.31.0", - "@rollup/rollup-win32-ia32-msvc": "4.31.0", - "@rollup/rollup-win32-x64-msvc": "4.31.0", + "@rollup/rollup-android-arm-eabi": "4.18.0", + "@rollup/rollup-android-arm64": "4.18.0", + "@rollup/rollup-darwin-arm64": "4.18.0", + "@rollup/rollup-darwin-x64": "4.18.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.18.0", + "@rollup/rollup-linux-arm-musleabihf": "4.18.0", + "@rollup/rollup-linux-arm64-gnu": "4.18.0", + "@rollup/rollup-linux-arm64-musl": "4.18.0", + "@rollup/rollup-linux-powerpc64le-gnu": "4.18.0", + "@rollup/rollup-linux-riscv64-gnu": "4.18.0", + "@rollup/rollup-linux-s390x-gnu": "4.18.0", + "@rollup/rollup-linux-x64-gnu": "4.18.0", + "@rollup/rollup-linux-x64-musl": "4.18.0", + "@rollup/rollup-win32-arm64-msvc": "4.18.0", + "@rollup/rollup-win32-ia32-msvc": "4.18.0", + "@rollup/rollup-win32-x64-msvc": "4.18.0", "fsevents": "~2.3.2" } }, @@ -1486,9 +1457,9 @@ "license": "MIT" }, "node_modules/sirv": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.0.tgz", - "integrity": "sha512-BPwJGUeDaDCHihkORDchNyyTvWFhcusy1XMmhEVTQTwGeybFbp8YEmB+njbPnth1FibULBSBVwCQni25XlCUDg==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/sirv/-/sirv-2.0.4.tgz", + "integrity": "sha512-94Bdh3cC2PKrbgSOUqTiGPWVZeSiXfKOVZNJniWoqrWrRkB1CJzBU3NEbiTsPcYy1lDsANA/THzS+9WBiy5nfQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1497,13 +1468,13 @@ "totalist": "^3.0.0" }, "engines": { - "node": ">=18" + "node": ">= 10" } }, "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", + "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", "dev": true, "license": "BSD-3-Clause", "engines": { @@ -1511,9 +1482,9 @@ } }, "node_modules/svelte": { - "version": "4.2.19", - "resolved": "https://registry.npmjs.org/svelte/-/svelte-4.2.19.tgz", - "integrity": "sha512-IY1rnGr6izd10B0A8LqsBfmlT5OILVuZ7XsI0vdGPEvuonFV7NYEUK4dAkm9Zg2q0Um92kYjTpS1CAP3Nh/KWw==", + "version": "4.2.18", + "resolved": "https://registry.npmjs.org/svelte/-/svelte-4.2.18.tgz", + "integrity": "sha512-d0FdzYIiAePqRJEb90WlJDkjUEx42xhivxN8muUBmfZnP+tzUgz12DJ2hRJi8sIHCME7jeK1PTMgKPSfTd8JrA==", "dev": true, "license": "MIT", "dependencies": { @@ -1575,6 +1546,17 @@ "url": "https://opencollective.com/svgo" } }, + "node_modules/tiny-glob": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/tiny-glob/-/tiny-glob-0.2.9.tgz", + "integrity": "sha512-g/55ssRPUjShh+xkfx9UPDXqhckHEsHr4Vd9zX55oSdGZc/MD0m3sferOkwWtp98bv+kcVfEHtRJgBVJzelrzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "globalyzer": "0.1.0", + "globrex": "^0.1.2" + } + }, "node_modules/totalist": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", @@ -1586,15 +1568,15 @@ } }, "node_modules/vite": { - "version": "5.4.11", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.11.tgz", - "integrity": "sha512-c7jFQRklXua0mTzneGW9QVyxFjUgwcihC4bXEtujIo2ouWCe1Ajt/amn2PCxYnhYfd5k09JX3SB7OYWFKYqj8Q==", + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.3.1.tgz", + "integrity": "sha512-XBmSKRLXLxiaPYamLv3/hnP/KXDai1NDexN0FpkTaZXTfycHvkRHoenpgl/fvuK/kPbB6xAgoyiryAhQNxYmAQ==", "dev": true, "license": "MIT", "dependencies": { "esbuild": "^0.21.3", - "postcss": "^8.4.43", - "rollup": "^4.20.0" + "postcss": "^8.4.38", + "rollup": "^4.13.0" }, "bin": { "vite": "bin/vite.js" @@ -1613,7 +1595,6 @@ "less": "*", "lightningcss": "^1.21.0", "sass": "*", - "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.4.0" @@ -1631,9 +1612,6 @@ "sass": { "optional": true }, - "sass-embedded": { - "optional": true - }, "stylus": { "optional": true }, diff --git a/package.json b/package.json index e039bc0..301641e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "campfire-client", - "version": "0.4.0", + "version": "0.3.0", "description": "social media for the galaxy-wide-web! 🌌", "private": true, "type": "module", @@ -18,7 +18,7 @@ "@poppanator/sveltekit-svg": "^4.2.1", "@sveltejs/adapter-auto": "^3.2.2", "@sveltejs/adapter-static": "^3.0.2", - "@sveltejs/kit": "^2.16.0", + "@sveltejs/kit": "^2.5.17", "@sveltejs/vite-plugin-svelte": "^3.1.1", "svelte": "^4.2.18", "vite": "^5.3.1" diff --git a/src/img/icons/bin.svg b/src/img/icons/bin.svg deleted file mode 100644 index 5476ee2..0000000 --- a/src/img/icons/bin.svg +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/src/img/icons/bookmark.svg b/src/img/icons/bookmark.svg index 70f328d..b9e9b8e 100644 --- a/src/img/icons/bookmark.svg +++ b/src/img/icons/bookmark.svg @@ -1,3 +1,3 @@ - - + + diff --git a/src/img/icons/dm.svg b/src/img/icons/dm.svg deleted file mode 100644 index 41b64da..0000000 --- a/src/img/icons/dm.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/img/icons/error.svg b/src/img/icons/error.svg deleted file mode 100644 index cdcc032..0000000 --- a/src/img/icons/error.svg +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/src/img/icons/explore.svg b/src/img/icons/explore.svg index e2a289c..9699f07 100644 --- a/src/img/icons/explore.svg +++ b/src/img/icons/explore.svg @@ -1,10 +1,10 @@ - + - + - + diff --git a/src/img/icons/followers.svg b/src/img/icons/followers.svg deleted file mode 100644 index 7293c31..0000000 --- a/src/img/icons/followers.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/src/img/icons/hashtag.svg b/src/img/icons/hashtag.svg index 52f7097..5a574d7 100644 --- a/src/img/icons/hashtag.svg +++ b/src/img/icons/hashtag.svg @@ -1,10 +1,10 @@ - - - - - - - - - + + + + + + + + + diff --git a/src/img/icons/info.svg b/src/img/icons/info.svg index e9342e4..23077aa 100644 --- a/src/img/icons/info.svg +++ b/src/img/icons/info.svg @@ -1,3 +1,5 @@ - - + + + + diff --git a/src/img/icons/like.svg b/src/img/icons/like.svg index 45682c9..a2ffd55 100644 --- a/src/img/icons/like.svg +++ b/src/img/icons/like.svg @@ -1,3 +1,3 @@ - - + + diff --git a/src/img/icons/like_fill.svg b/src/img/icons/like_fill.svg index 09aa5b8..6d2eb3b 100644 --- a/src/img/icons/like_fill.svg +++ b/src/img/icons/like_fill.svg @@ -1,10 +1,10 @@ - + - + - + diff --git a/src/img/icons/lists.svg b/src/img/icons/lists.svg index 47a99bb..a3e5c30 100644 --- a/src/img/icons/lists.svg +++ b/src/img/icons/lists.svg @@ -1,10 +1,3 @@ - - - - - - - - - + + diff --git a/src/img/icons/logout.svg b/src/img/icons/logout.svg index d97e04d..1a3cf80 100644 --- a/src/img/icons/logout.svg +++ b/src/img/icons/logout.svg @@ -1,11 +1,11 @@ - + - - + + - + diff --git a/src/img/icons/media.svg b/src/img/icons/media.svg deleted file mode 100644 index f88b45e..0000000 --- a/src/img/icons/media.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - diff --git a/src/img/icons/mention.svg b/src/img/icons/mention.svg deleted file mode 100644 index 3895acb..0000000 --- a/src/img/icons/mention.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/img/icons/more.svg b/src/img/icons/more.svg index ab0f996..bc531b2 100644 --- a/src/img/icons/more.svg +++ b/src/img/icons/more.svg @@ -1,3 +1,5 @@ - - + + + + diff --git a/src/img/icons/notifications.svg b/src/img/icons/notifications.svg index 7e3dfa6..e946ca5 100644 --- a/src/img/icons/notifications.svg +++ b/src/img/icons/notifications.svg @@ -1,11 +1,11 @@ - + - - + + - + diff --git a/src/img/icons/plus.svg b/src/img/icons/plus.svg deleted file mode 100644 index 0bcf501..0000000 --- a/src/img/icons/plus.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/src/img/icons/plus_fill.svg b/src/img/icons/plus_fill.svg deleted file mode 100644 index cd16e2a..0000000 --- a/src/img/icons/plus_fill.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/src/img/icons/poll.svg b/src/img/icons/poll.svg deleted file mode 100644 index f258b9d..0000000 --- a/src/img/icons/poll.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/src/img/icons/post.svg b/src/img/icons/post.svg index 3d4ac43..79d5536 100644 --- a/src/img/icons/post.svg +++ b/src/img/icons/post.svg @@ -1,3 +1,3 @@ - - + + diff --git a/src/img/icons/public.svg b/src/img/icons/public.svg deleted file mode 100644 index c321adf..0000000 --- a/src/img/icons/public.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - diff --git a/src/img/icons/quote.svg b/src/img/icons/quote.svg index 5a74811..98ce541 100644 --- a/src/img/icons/quote.svg +++ b/src/img/icons/quote.svg @@ -1,3 +1,4 @@ - - + + + diff --git a/src/img/icons/react.svg b/src/img/icons/react.svg index e7396e9..532213d 100644 --- a/src/img/icons/react.svg +++ b/src/img/icons/react.svg @@ -1,14 +1,15 @@ - - - - - - - - - - - - - - + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/img/icons/reload.svg b/src/img/icons/reload.svg deleted file mode 100644 index 4e58201..0000000 --- a/src/img/icons/reload.svg +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/src/img/icons/reply.svg b/src/img/icons/reply.svg index dc18cb0..5462c7b 100644 --- a/src/img/icons/reply.svg +++ b/src/img/icons/reply.svg @@ -1,3 +1,3 @@ - - + + diff --git a/src/img/icons/repost.svg b/src/img/icons/repost.svg index b679f16..c0f950f 100644 --- a/src/img/icons/repost.svg +++ b/src/img/icons/repost.svg @@ -1,12 +1,12 @@ - + - - - + + + - + diff --git a/src/img/icons/search.svg b/src/img/icons/search.svg index eecb00c..0864af4 100644 --- a/src/img/icons/search.svg +++ b/src/img/icons/search.svg @@ -1,10 +1,11 @@ - + - + + - + diff --git a/src/img/icons/settings.svg b/src/img/icons/settings.svg index bfc8697..be2926e 100644 --- a/src/img/icons/settings.svg +++ b/src/img/icons/settings.svg @@ -1,11 +1,11 @@ - + - + - + - + diff --git a/src/img/icons/timeline.svg b/src/img/icons/timeline.svg index 712e88f..a2be615 100644 --- a/src/img/icons/timeline.svg +++ b/src/img/icons/timeline.svg @@ -1,10 +1,12 @@ - + - + + + - + diff --git a/src/img/icons/unlisted.svg b/src/img/icons/unlisted.svg deleted file mode 100644 index 6b88c40..0000000 --- a/src/img/icons/unlisted.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/img/icons/warning.svg b/src/img/icons/warning.svg deleted file mode 100644 index 0fffc6f..0000000 --- a/src/img/icons/warning.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/lib/account.js b/src/lib/account.js deleted file mode 100644 index 7bfd696..0000000 --- a/src/lib/account.js +++ /dev/null @@ -1,52 +0,0 @@ -import { server } from '$lib/client/server.js'; -import { parseEmoji, renderEmoji } from '$lib/emoji.js'; -import { get, writable } from 'svelte/store'; - -const cache = writable({}); - -/** - * Parses an account using API data, and returns a writable store object. - * @param {Object} data - * @param {number} ancestor_count - */ -export function parseAccount(data) { - if (!data) { - console.error("Attempted to parse account data but no data was provided"); - return null; - } - let account = get(cache)[data.id]; - if (account) return account; - // cache miss! - - account = {}; - account.id = data.id; - account.nickname = data.display_name.trim(); - account.username = data.username; - account.name = account.nickname || account.username; - account.avatar_url = data.avatar; - account.url = data.url; - - if (data.acct.includes('@')) - account.host = data.acct.split('@')[1]; - else - account.host = get(server).host; - - account.mention = "@" + account.username; - if (account.host != get(server).host) - account.mention += "@" + account.host; - - account.emojis = {}; - data.emojis.forEach(emoji => { - account.emojis[emoji.shortcode] = parseEmoji(emoji.shortcode, emoji.url); - }); - - account.rich_name = account.nickname ? renderEmoji(account.nickname, account.emojis) : account.username; - - cache.update(cache => { - cache[account.id] = account; - return cache; - }); - - return account; -} - diff --git a/src/lib/api.js b/src/lib/api.js deleted file mode 100644 index 61f6bd9..0000000 --- a/src/lib/api.js +++ /dev/null @@ -1,423 +0,0 @@ -/** - * GET /api/v1/instance - * @param {string} host - The domain of the target server. - */ -export async function getInstance(host) { - const data = await fetch(`https://${host}/api/v1/instance`) - .then(res => res.json()) - .catch(error => console.error(error)); - return data ? data : false; -} - -/** - * POST /api/v1/apps - * Attempts to create an application for a given server host. - * @param {string} host - The domain of the target server. - */ -export async function createApp(host) { - let form = new FormData(); - form.append("client_name", "Campfire"); - form.append("redirect_uris", `${location.origin}/callback`); - form.append("scopes", "read write push"); - form.append("website", "https://campfire.bliss.town"); - - const res = await fetch(`https://${host}/api/v1/apps`, { - method: "POST", - body: form, - }) - .then(res => res.json()) - .catch(error => { - console.error(error); - return false; - }); - - if (!res || !res.client_id) return false; - - return { - id: res.client_id, - secret: res.client_secret, - }; -} - -/** - * Returns the OAuth authorization url for the target server. - * @param {string} host - The domain of the target server. - * @param {string} app_id - The application id for the target server. - */ -export function getOAuthUrl(host, app_id) { - return `https://${host}/oauth/authorize` + - `?client_id=${app_id}` + - "&scope=read+write+push" + - `&redirect_uri=${location.origin}/callback` + - "&response_type=code"; -} - -/** - * POST /oauth/token - * Attempts to generate an OAuth token. - * Returns false on failure. - * @param {string} host - The domain of the target server. - * @param {string} client_id - The application id. - * @param {string} secret - The application secret. - * @param {string} code - The authorization code provided by OAuth. - */ -export async function getToken(host, client_id, secret, code) { - let form = new FormData(); - form.append("client_id", client_id); - form.append("client_secret", secret); - form.append("redirect_uri", `${location.origin}/callback`); - form.append("grant_type", "authorization_code"); - form.append("code", code); - form.append("scope", "read write push"); - - const res = await fetch(`https://${host}/oauth/token`, { - method: "POST", - body: form, - }) - .then(res => res.json()) - .catch(error => { - console.error(error); - return false; - }); - - if (!res || !res.access_token) return false; - - return res.access_token; -} - -/** - * POST /oauth/revoke - * Attempts to revoke an OAuth token. - * Returns false on failure. - * @param {string} host - The domain of the target server. - * @param {string} client_id - The application id. - * @param {string} secret - The application secret. - * @param {string} token - The application token. - */ -export async function revokeToken(host, client_id, secret, token) { - let form = new FormData(); - form.append("client_id", client_id); - form.append("client_secret", secret); - form.append("token", token); - - const res = await fetch(`https://${host}/oauth/revoke`, { - method: "POST", - body: form, - }) - .catch(error => { - console.error(error); - return false; - }); - - if (!res.ok) return false; - return true; -} - -/** - * GET /api/v1/accounts/verify_credentials - * This endpoint returns information about the client account, - * and other useful data. - * Returns false on failure. - * @param {string} host - The domain of the target server. - * @param {string} token - The application token. - */ -export async function verifyCredentials(host, token) { - let url = `https://${host}/api/v1/accounts/verify_credentials`; - const data = await fetch(url, { - method: 'GET', - headers: { "Authorization": "Bearer " + token } - }).then(res => res.json()); - - return data; -} - -/** - * GET /api/v1/streaming/health - * Checks if the server's streaming service is alive - */ -export async function getStreamingHealth(host) { - let url = `https://${host}/api/v1/streaming/health`; - const res = await fetch(url, { - method: 'GET' - }); - - return res.ok; -} - -/** - * GET /api/v1/notifications - * @param {string} host - The domain of the target server. - * @param {string} token - The application token. - * @param {string} min_id - If provided, only shows notifications after this ID. - * @param {string} max_id - If provided, only shows notifications before this ID. - * @param {string} limit - The maximum number of notifications to retrieve (default 40). - * @param {string} types - A list of notification types to filter to. - */ -export async function getNotifications(host, token, min_id, max_id, limit, types) { - let url = `https://${host}/api/v1/notifications`; - - let params = new URLSearchParams(); - if (min_id) params.append("min_id", min_id); - if (max_id) params.append("max_id", max_id); - if (limit) params.append("limit", limit); - if (types) params.append("types", types.join(',')); - const params_string = params.toString(); - if (params_string) url += '?' + params_string; - - const data = await fetch(url, { - method: 'GET', - headers: { "Authorization": "Bearer " + token } - }).then(res => res.json()); - - return data; -} - -/** - * GET /api/v1/timelines/{timeline} - * @param {string} host - The domain of the target server. - * @param {string} token - The application token. - * @param {string} timeline - The name of the timeline to pull (default "home"). - * @param {string} max_id - If provided, only shows posts after this ID. - */ -export async function getTimeline(host, token, timeline, max_id) { - let url = `https://${host}/api/v1/timelines/${timeline || "home"}`; - - let params = new URLSearchParams(); - if (max_id) params.append("max_id", max_id); - const params_string = params.toString(); - if (params_string) url += '?' + params_string; - - const data = await fetch(url, { - method: 'GET', - headers: { "Authorization": token ? `Bearer ${token}` : null } - }).then(res => res.json()); - - return data; -} - -/** - * GET /api/v1/statuses/{post_id}. - * @param {string} host - The domain of the target server. - * @param {string} token - The application token. - * @param {string} post_id - The ID of the post to fetch. - */ -export async function getPost(host, token, post_id) { - let url = `https://${host}/api/v1/statuses/${post_id}`; - - const data = await fetch(url, { - method: 'GET', - headers: { "Authorization": token ? `Bearer ${token}` : null } - }).then(res => res.json()) - - if (!data || data.error) return false; - - return data; -} - -/** - * POST /api/v1/statuses - * @param {string} host - The domain of the target server. - * @param {string} token - The application token - * @param {any} post_data - The post content - */ -export async function createPost(host, token, post_data) { - let formdata = new FormData(); - for (const key in post_data) { - formdata.append(key, post_data[key]); - } - - let url = `https://${host}/api/v1/statuses`; - const data = await fetch(url, { - method: 'POST', - headers: { "Authorization": `Bearer ${token}` }, - body: formdata - }) - - return await data.json(); -} - -/** - * PUT /api/v1/statuses/{post_id} - * @param {string} host - The domain of the target server. - * @param {string} token - The application token - * @param {any} post_id - The ID of the post to edit. - * @param {any} post_data - The post content - */ -export async function editPost(host, token, post_id, post_data) { - let formdata = new FormData(); - for (const key in post_data) { - formdata.append(key, post_data[key]); - } - - let url = `https://${host}/api/v1/statuses/${post_id}`; - const data = await fetch(url, { - method: 'PUT', - headers: { "Authorization": `Bearer ${token}` }, - body: formdata - }) - - return await data.json(); -} - -/** - * DELETE /api/v1/statuses/{post_id} - * Returns the deleted post's data, in the case of republishing. - * @param {string} host - The domain of the target server. - * @param {string} token - The application token - * @param {any} post_id - The ID of the post to delete. - */ -export async function deletePost(host, token, post_id) { - let url = `https://${host}/api/v1/statuses/${post_id}`; - const data = await fetch(url, { - method: 'DELETE', - headers: { "Authorization": `Bearer ${token}` }, - }) - - return await data.json(); -} - -/** - * GET /api/v1/statuses/{post_id}/context. - * @param {string} host - The domain of the target server. - * @param {string} token - The application token. - * @param {string} post_id - The ID of the post to fetch. - */ -export async function getPostContext(host, token, post_id) { - let url = `https://${host}/api/v1/statuses/${post_id}/context`; - - const data = await fetch(url, { - method: 'GET', - headers: { "Authorization": token ? `Bearer ${token}` : null } - }).then(res => res.json()); - - return data; -} - -/** - * POST /api/v1/statuses/{post_id}/reblog. - * @param {string} host - The domain of the target server. - * @param {string} token - The application token. - * @param {string} post_id - The ID of the post to boost. - * @param {string} visibility - The visibility with which to boost the post. - */ -export async function boostPost(host, token, post_id, visibility) { - let url = `https://${host}/api/v1/statuses/${post_id}/reblog`; - - let form = new FormData(); - if (visibility) form.append("visibility", visibility); - - const data = await fetch(url, { - method: 'POST', - headers: { "Authorization": `Bearer ${token}` }, - body: form, - }).then(res => res.json()); - - return data; -} - -/** - * POST /api/v1/statuses/{post_id}/unreblog. - * @param {string} host - The domain of the target server. - * @param {string} token - The application token. - * @param {string} post_id - The ID of the post to unboost. - */ -export async function unboostPost(host, token, post_id) { - let url = `https://${host}/api/v1/statuses/${post_id}/unreblog`; - - const data = await fetch(url, { - method: 'POST', - headers: { "Authorization": `Bearer ${token}` } - }).then(res => res.json()); - - return data; -} - -/** - * POST /api/v1/statuses/{post_id}/favourite. - * @param {string} host - The domain of the target server. - * @param {string} token - The application token. - * @param {string} post_id - The ID of the post to favourite. - */ -export async function favouritePost(host, token, post_id) { - let url = `https://${host}/api/v1/statuses/${post_id}/favourite`; - - const data = await fetch(url, { - method: 'POST', - headers: { "Authorization": `Bearer ${token}` } - }).then(res => res.json()); - - return data; -} - -/** - * POST /api/v1/statuses/{post_id}/unfavourite. - * @param {string} host - The domain of the target server. - * @param {string} token - The application token. - * @param {string} post_id - The ID of the post to unfavourite. - */ -export async function unfavouritePost(host, token, post_id) { - let url = `https://${host}/api/v1/statuses/${post_id}/unfavourite`; - - const data = await fetch(url, { - method: 'POST', - headers: { "Authorization": `Bearer ${token}` } - }).then(res => res.json()); - - return data; -} - -/** - * POST /api/v1/statuses/{post_id}/react/{shortcode} - * @param {string} host - The domain of the target server. - * @param {string} token - The application token. - * @param {string} post_id - The ID of the post to favourite. - * @param {string} shortcode - The shortcode of the emote to react with. - */ -export async function reactPost(host, token, post_id, shortcode) { - // note: reacting with foreign emotes is unsupported on most servers - // chuckya appears to allow this, but other servers tested have - // not demonstrated this. - let url = `https://${host}/api/v1/statuses/${post_id}/react/${encodeURIComponent(shortcode)}`; - - const data = await fetch(url, { - method: 'POST', - headers: { "Authorization": `Bearer ${token}` } - }).then(res => res.json()); - - return data; -} - -/** - * POST /api/v1/statuses/{post_id}/unreact/{shortcode} - * @param {string} host - The domain of the target server. - * @param {string} token - The application token. - * @param {string} post_id - The ID of the post to favourite. - * @param {string} shortcode - The shortcode of the reaction emote to remove. - */ -export async function unreactPost(host, token, post_id, shortcode) { - let url = `https://${host}/api/v1/statuses/${post_id}/unreact/${encodeURIComponent(shortcode)}`; - - const data = await fetch(url, { - method: 'POST', - headers: { "Authorization": `Bearer ${token}` } - }).then(res => res.json()); - - return data; -} - -/** - * GET /api/v1/accounts/{user_id} - * @param {string} host - The domain of the target server. - * @param {string} token - The application token. - * @param {string} user_id - The ID of the user to fetch. - */ -export async function getUser(host, token, user_id) { - let url = `https://${host}/api/v1/accounts/${user_id}`; - - const data = await fetch(url, { - method: 'GET', - headers: { "Authorization": token ? `Bearer ${token}` : null } - }).then(res => res.json()); - - return data; -} diff --git a/src/lib/app.css b/src/lib/app.css index d6656b4..d010639 100644 --- a/src/lib/app.css +++ b/src/lib/app.css @@ -74,11 +74,6 @@ main { width: 732px; } -img.emoji { - height: 1.2em; - margin: -.2em 0; -} - .throb { animation: .25s throb alternate infinite ease-in; } diff --git a/src/lib/client/api.js b/src/lib/client/api.js new file mode 100644 index 0000000..09e6514 --- /dev/null +++ b/src/lib/client/api.js @@ -0,0 +1,339 @@ +import { client } from '$lib/client/client.js'; +import { user } from '$lib/stores/user.js'; +import { capabilities } from '../client/instance.js'; +import Post from '$lib/post.js'; +import User from '$lib/user/user.js'; +import Emoji from '$lib/emoji.js'; +import { get } from 'svelte/store'; + +export async function createApp(host) { + let form = new FormData(); + form.append("client_name", "Campfire"); + form.append("redirect_uris", `${location.origin}/callback`); + form.append("scopes", "read write push"); + form.append("website", "https://campfire.bliss.town"); + + const res = await fetch(`https://${host}/api/v1/apps`, { + method: "POST", + body: form, + }) + .then(res => res.json()) + .catch(error => { + console.error(error); + return false; + }); + + if (!res || !res.client_id) return false; + + return { + id: res.client_id, + secret: res.client_secret, + }; +} + +export function getOAuthUrl() { + return `https://${get(client).instance.host}/oauth/authorize` + + `?client_id=${get(client).app.id}` + + "&scope=read+write+push" + + `&redirect_uri=${location.origin}/callback` + + "&response_type=code"; +} + +export async function getToken(code) { + let form = new FormData(); + form.append("client_id", get(client).app.id); + form.append("client_secret", get(client).app.secret); + form.append("redirect_uri", `${location.origin}/callback`); + form.append("grant_type", "authorization_code"); + form.append("code", code); + form.append("scope", "read write push"); + + const res = await fetch(`https://${get(client).instance.host}/oauth/token`, { + method: "POST", + body: form, + }) + .then(res => res.json()) + .catch(error => { + console.error(error); + return false; + }); + + if (!res || !res.access_token) return false; + + return res.access_token; +} + +export async function revokeToken() { + let form = new FormData(); + form.append("client_id", get(client).app.id); + form.append("client_secret", get(client).app.secret); + form.append("token", get(client).app.token); + + const res = await fetch(`https://${get(client).instance.host}/oauth/revoke`, { + method: "POST", + body: form, + }) + .catch(error => { + console.error(error); + return false; + }); + + if (!res.ok) return false; + return true; +} + +export async function verifyCredentials() { + let url = `https://${get(client).instance.host}/api/v1/accounts/verify_credentials`; + const data = await fetch(url, { + method: 'GET', + headers: { "Authorization": "Bearer " + get(client).app.token } + }).then(res => res.json()); + + return data; +} + +export async function getNotifications(since_id, limit, types) { + if (!get(user)) return false; + + let url = `https://${get(client).instance.host}/api/v1/notifications`; + + let params = new URLSearchParams(); + if (since_id) params.append("since_id", since_id); + if (limit) params.append("limit", limit); + if (types) params.append("types", types.join(',')); + const params_string = params.toString(); + if (params_string) url += '?' + params_string; + + const data = await fetch(url, { + method: 'GET', + headers: { "Authorization": "Bearer " + get(client).app.token } + }).then(res => res.json()); + + return data; +} + +export async function getTimeline(last_post_id) { + if (!get(user)) return false; + let url = `https://${get(client).instance.host}/api/v1/timelines/home`; + if (last_post_id) url += "?max_id=" + last_post_id; + const data = await fetch(url, { + method: 'GET', + headers: { "Authorization": "Bearer " + get(client).app.token } + }).then(res => res.json()); + + return data; +} + +export async function getPost(post_id, ancestor_count) { + let url = `https://${get(client).instance.host}/api/v1/statuses/${post_id}`; + const data = await fetch(url, { + method: 'GET', + headers: { "Authorization": "Bearer " + get(client).app.token } + }).then(res => { return res.ok ? res.json() : false }); + + if (data === false) return false; + return data; +} + +export async function getPostContext(post_id) { + let url = `https://${get(client).instance.host}/api/v1/statuses/${post_id}/context`; + const data = await fetch(url, { + method: 'GET', + headers: { "Authorization": "Bearer " + get(client).app.token } + }).then(res => { return res.ok ? res.json() : false }); + + if (data === false) return false; + return data; +} + +export async function boostPost(post_id) { + let url = `https://${get(client).instance.host}/api/v1/statuses/${post_id}/reblog`; + const data = await fetch(url, { + method: 'POST', + headers: { "Authorization": "Bearer " + get(client).app.token } + }).then(res => { return res.ok ? res.json() : false }); + + if (data === false) return false; + return data; +} + +export async function unboostPost(post_id) { + let url = `https://${get(client).instance.host}/api/v1/statuses/${post_id}/unreblog`; + const data = await fetch(url, { + method: 'POST', + headers: { "Authorization": "Bearer " + get(client).app.token } + }).then(res => { return res.ok ? res.json() : false }); + + if (data === false) return false; + return data; +} + +export async function favouritePost(post_id) { + let url = `https://${get(client).instance.host}/api/v1/statuses/${post_id}/favourite`; + const data = await fetch(url, { + method: 'POST', + headers: { "Authorization": "Bearer " + get(client).app.token } + }).then(res => { return res.ok ? res.json() : false }); + + if (data === false) return false; + return data; +} + +export async function unfavouritePost(post_id) { + let url = `https://${get(client).instance.host}/api/v1/statuses/${post_id}/unfavourite`; + const data = await fetch(url, { + method: 'POST', + headers: { "Authorization": "Bearer " + get(client).app.token } + }).then(res => { return res.ok ? res.json() : false }); + + if (data === false) return false; + return data; +} + +export async function reactPost(post_id, shortcode) { + // for whatever reason (at least in my testing on iceshrimp) + // using shortcodes for external emoji results in a fallback + // to the default like emote. + // identical api calls on chuckya instances do not display + // this behaviour. + let url = `https://${get(client).instance.host}/api/v1/statuses/${post_id}/react/${encodeURIComponent(shortcode)}`; + const data = await fetch(url, { + method: 'POST', + headers: { "Authorization": "Bearer " + get(client).app.token } + }).then(res => { return res.ok ? res.json() : false }); + + if (data === false) return false; + return data; +} + +export async function unreactPost(post_id, shortcode) { + let url = `https://${get(client).instance.host}/api/v1/statuses/${post_id}/unreact/${encodeURIComponent(shortcode)}`; + const data = await fetch(url, { + method: 'POST', + headers: { "Authorization": "Bearer " + get(client).app.token } + }).then(res => { return res.ok ? res.json() : false }); + + if (data === false) return false; + return data; +} + +export async function parsePost(data, ancestor_count) { + let post = new Post(); + + post.text = data.content; + post.html = data.content; + + post.reply = null; + if ((data.in_reply_to_id || data.reply) && + ancestor_count !== 0 + ) { + const reply_data = data.reply || await getPost(data.in_reply_to_id, ancestor_count - 1); + // if the post returns false, we probably don't have permission to read it. + // we'll respect the thread's privacy, and leave it alone :) + if (!reply_data) return false; + post.reply = await parsePost(reply_data, ancestor_count - 1, false); + } + + post.boost = data.reblog ? await parsePost(data.reblog, 1, false) : null; + + post.id = data.id; + post.created_at = new Date(data.created_at); + post.user = await parseUser(data.account); + post.warning = data.spoiler_text; + post.boost_count = data.reblogs_count; + post.reply_count = data.replies_count; + post.favourite_count = data.favourites_count; + post.favourited = data.favourited; + post.boosted = data.reblogged; + post.mentions = data.mentions; + post.files = data.media_attachments; + post.url = data.url; + post.visibility = data.visibility; + + post.emojis = []; + if (data.emojis) { + data.emojis.forEach(emoji_data => { + let name = emoji_data.shortcode.split('@')[0]; + post.emojis.push(parseEmoji({ + id: name + '@' + post.user.host, + name: name, + host: post.user.host, + url: emoji_data.url, + })); + }); + } + + if (data.reactions && get(client).instance.capabilities.includes(capabilities.REACTIONS)) { + post.reactions = parseReactions(data.reactions); + } + return post; +} + +export async function parseUser(data) { + if (!data) { + console.error("Attempted to parse user data but no data was provided"); + return null; + } + let user = await get(client).getCacheUser(data.id); + + if (user) return user; + // cache miss! + + user = new User(); + user.id = data.id; + user.nickname = data.display_name.trim(); + user.username = data.username; + user.avatar_url = data.avatar; + user.url = data.url; + + if (data.acct.includes('@')) + user.host = data.acct.split('@')[1]; + else + user.host = get(client).instance.host; + + user.emojis = []; + data.emojis.forEach(emoji_data => { + emoji_data.id = emoji_data.shortcode + '@' + user.host; + emoji_data.name = emoji_data.shortcode; + emoji_data.host = user.host; + user.emojis.push(parseEmoji(emoji_data)); + }); + + get(client).putCacheUser(user); + return user; +} + +export function parseReactions(data) { + let reactions = []; + data.forEach(reaction_data => { + let reaction = { + count: reaction_data.count, + name: reaction_data.name, + me: reaction_data.me, + }; + if (reaction_data.url) reaction.url = reaction_data.url; + reactions.push(reaction); + }); + return reactions; +} + +export function parseEmoji(data) { + let emoji = new Emoji( + data.id, + data.name, + data.host, + data.url, + ); + get(client).putCacheEmoji(emoji); + return emoji; +} + +export async function getUser(user_id) { + let url = `https://${get(client).instance.host}/api/v1/accounts/${user_id}`; + const data = await fetch(url, { + method: 'GET', + headers: { "Authorization": "Bearer " + get(client).app.token } + }).then(res => res.json()); + + return data; +} diff --git a/src/lib/client/app.js b/src/lib/client/app.js deleted file mode 100644 index e17ead4..0000000 --- a/src/lib/client/app.js +++ /dev/null @@ -1,37 +0,0 @@ -import { writable } from 'svelte/store'; -import { app_name } from '$lib/config.js'; -import { browser } from "$app/environment"; - -// if app is falsy, assume user has not begun the login process. -// if app.token is falsy, assume user has not logged in. -export const app = writable(loadApp()); - -// write to localStorage on each update -app.subscribe(app => { - saveApp(app); -}); - -/** - * Saves the provided app to localStorage. - * If `app` is falsy, data is removed from localStorage. - * @param {Object} app - */ -function saveApp(app) { - if (!browser) return; - if (!app) { - localStorage.removeItem(app_name + "_app"); - return; - } - localStorage.setItem(app_name + "_app", JSON.stringify(app)); -} - -/** - * Returns application data loaded from localStorage, if it exists. - * Otherwise, returns false. - */ -function loadApp() { - if (!browser) return; - let data = localStorage.getItem(app_name + "_app"); - if (!data) return false; - return JSON.parse(data); -} diff --git a/src/lib/client/client.js b/src/lib/client/client.js new file mode 100644 index 0000000..b1beb6e --- /dev/null +++ b/src/lib/client/client.js @@ -0,0 +1,192 @@ +import { Instance, server_types } from './instance.js'; +import * as api from './api.js'; +import { get, writable } from 'svelte/store'; +import { last_read_notif_id } from '$lib/notifications.js'; +import { user, logged_in } from '$lib/stores/user.js'; + +export const client = writable(false); + +const save_name = "campfire"; + +export class Client { + instance; + app; + #cache; + + constructor() { + this.instance = null; + this.app = null; + this.cache = { + users: {}, + emojis: {}, + }; + } + + async init(host) { + if (host.startsWith("https://")) host = host.substring(8); + const url = `https://${host}/api/v1/instance`; + const data = await fetch(url).then(res => res.json()).catch(error => { console.error(error) }); + if (!data) { + console.error(`Failed to connect to ${host}`); + return `Failed to connect to ${host}!`; + } + + this.instance = new Instance(host, data.version); + if (this.instance.type == server_types.UNSUPPORTED) { + console.warn(`Server ${host} is unsupported - ${data.version}`); + if (!confirm( + `This app does not officially support ${host}. ` + + `Things may break, or otherwise not work as epxected! ` + + `Are you sure you wish to continue?` + )) return false; + } else { + console.log(`Server is "${this.instance.type}" (or compatible) with capabilities: [${this.instance.capabilities}].`); + } + + this.app = await api.createApp(host); + + if (!this.app || !this.instance) { + console.error("Failed to create app. Check the network logs for details."); + return false; + } + + this.save(); + + client.set(this); + + return true; + } + + getOAuthUrl() { + return api.getOAuthUrl(this.app.secret); + } + + async getToken(code) { + const token = await api.getToken(code); + if (!token) { + console.error("Failed to obtain access token"); + return false; + } + return token; + } + + async revokeToken() { + return await api.revokeToken(); + } + + async getNotifications(since_id, limit, types) { + return await api.getNotifications(since_id, limit, types); + } + + async getTimeline(last_post_id) { + return await api.getTimeline(last_post_id); + } + + async getPost(post_id, parent_replies, child_replies) { + return await api.getPost(post_id, parent_replies, child_replies); + } + + async getPostContext(post_id) { + return await api.getPostContext(post_id); + } + + async boostPost(post_id) { + return await api.boostPost(post_id); + } + + async unboostPost(post_id) { + return await api.unboostPost(post_id); + } + + async favouritePost(post_id) { + return await api.favouritePost(post_id); + } + + async unfavouritePost(post_id) { + return await api.unfavouritePost(post_id); + } + + async reactPost(post_id, shortcode) { + return await api.reactPost(post_id, shortcode); + } + + async unreactPost(post_id, shortcode) { + return await api.unreactPost(post_id, shortcode); + } + + putCacheUser(user) { + this.cache.users[user.id] = user; + client.set(this); + } + + async getCacheUser(user_id) { + let user = this.cache.users[user_id]; + if (user) return user; + + return false; + } + + async getUserByMention(mention) { + let users = Object.values(this.cache.users); + for (let i in users) { + const user = users[i]; + if (user.mention == mention) return user; + } + return false; + } + + putCacheEmoji(emoji) { + this.cache.emojis[emoji.id] = emoji; + client.set(this); + } + + getEmoji(emoji_id) { + let emoji = this.cache.emojis[emoji_id]; + if (!emoji) return false; + return emoji; + } + + async getUser(user_id) { + return await api.getUser(user_id); + } + + save() { + if (typeof localStorage === typeof undefined) return; + localStorage.setItem(save_name, JSON.stringify({ + version: APP_VERSION, + instance: { + host: this.instance.host, + version: this.instance.version, + }, + last_read_notif_id: get(last_read_notif_id), + app: this.app, + })); + } + + load() { + if (typeof localStorage === typeof undefined) return; + let json = localStorage.getItem(save_name); + if (!json) return false; + let saved = JSON.parse(json); + if (!saved.version || saved.version !== APP_VERSION) { + localStorage.removeItem(save_name); + return false; + } + this.instance = new Instance(saved.instance.host, saved.instance.version); + last_read_notif_id.set(saved.last_read_notif_id || 0); + this.app = saved.app; + client.set(this); + return true; + } + + async logout() { + if (!this.instance || !this.app) return; + if (!await this.revokeToken()) { + console.warn("Failed to log out correctly; ditching the old tokens anyways."); + } + localStorage.removeItem(save_name); + logged_in.set(false); + client.set(new Client()); + console.log("Logged out successfully."); + } +} diff --git a/src/lib/client/instance.js b/src/lib/client/instance.js new file mode 100644 index 0000000..92003e8 --- /dev/null +++ b/src/lib/client/instance.js @@ -0,0 +1,70 @@ +export const server_types = { + UNSUPPORTED: "unsupported", + MASTODON: "mastodon", + GLITCHSOC: "glitchsoc", + CHUCKYA: "chuckya", + FIREFISH: "firefish", + ICESHRIMP: "iceshrimp", + SHARKEY: "sharkey", +}; + +export const capabilities = { + MARKDOWN_CONTENT: "mdcontent", + REACTIONS: "reactions", +}; + +export class Instance { + host; + version; + capabilities; + type = server_types.UNSUPPORTED; + + constructor(host, version) { + this.host = host; + this.version = version; + this.#setType(version); + this.capabilities = this.#getCapabilities(this.type); + } + + #setType(version) { + this.type = server_types.UNSUPPORTED; + if (version.constructor !== String) return; + let version_lower = version.toLowerCase(); + for (let i = 1; i < Object.keys(server_types).length; i++) { + const check_type = Object.values(server_types)[i]; + if (version_lower.includes(check_type)) { + this.type = check_type; + return; + } + } + } + + #getCapabilities(type) { + let c = []; + switch (type) { + case server_types.MASTODON: + break; + case server_types.GLITCHSOC: + c.push(capabilities.REACTIONS); + break; + case server_types.CHUCKYA: + c.push(capabilities.REACTIONS); + break; + case server_types.FIREFISH: + c.push(capabilities.REACTIONS); + break; + case server_types.ICESHRIMP: + // more trouble than it's worth atm + // the server already hands this to us ;p + //c.push(capabilities.MARKDOWN_CONTENT); + c.push(capabilities.REACTIONS); + break; + case server_types.SHARKEY: + c.push(capabilities.REACTIONS); + break; + default: + break; + } + return c; + } +} diff --git a/src/lib/client/server.js b/src/lib/client/server.js deleted file mode 100644 index 6b42ffe..0000000 --- a/src/lib/client/server.js +++ /dev/null @@ -1,143 +0,0 @@ -import * as api from '$lib/api.js'; -import { writable } from 'svelte/store'; -import { app_name } from '$lib/config.js'; -import { browser } from "$app/environment"; - -const server_types = { - UNSUPPORTED: "unsupported", - MASTODON: "mastodon", - GLITCHSOC: "glitchsoc", - CHUCKYA: "chuckya", - FIREFISH: "firefish", - ICESHRIMP: "iceshrimp", - SHARKEY: "sharkey", - AKKOMA: "akkoma", // TODO: verify - PLEROMA: "pleroma", // TODO: verify -}; - -export const capabilities = { - MARKDOWN_CONTENT: "markdown_content", - REACTIONS: "reactions", - FOREIGN_REACTIONS: "foreign_reactions", -}; - -// if server is falsy, assume user has not begun the login process. -export let server = writable(loadServer()); - -// write to localStorage on each update -server.subscribe(server => { - saveServer(server); -}); - -/** - * Attempts to create an server object using a given hostname. - * @param {string} host - The domain of the target server. - */ -export async function createServer(host) { - if (!host) { - console.error("Attempted to create server without providing a hostname"); - return false; - } - if (host.startsWith("http://")) { - console.error("Cowardly refusing to connect to an insecure server"); - return false; - } - - let server = {}; - server.host = host; - - if (host.startsWith("https://")) host = host.substring(8); - const data = await api.getInstance(host); - if (!data) { - console.error(`Failed to connect to ${host}`); - return false; - } - - server.version = data.version; - server.type = getType(server.version); - server.capabilities = getCapabilities(server.type); - - if (server.type === server_types.UNSUPPORTED) { - console.warn(`Server ${host} is unsupported (${server.version}). Things may break, or not work as expected`); - } else { - console.log(`Server detected as "${server.type}" (${server.version}) with capabilities: {${server.capabilities.join(', ')}}`); - } - - return server; -} - -/** - * Saves the provided server to localStorage. - * If `server` is falsy, data is removed from localStorage. - * @param {Object} server - */ -function saveServer(server) { - if (!browser) return; - if (!server) { - localStorage.removeItem(app_name + "_server"); - return; - } - localStorage.setItem(app_name + "_server", JSON.stringify(server)); -} - -/** - * Returns server data loaded from localStorage, if it exists. - * Otherwise, returns false. - */ -function loadServer() { - if (!browser) return; - let data = localStorage.getItem(app_name + "_server"); - if (!data) return false; - return JSON.parse(data); -} - -/** - * Returns the type of an server, inferred from its version string. - * @param {string} version - * @returns the inferred server_type - */ -function getType(version) { - if (version.constructor !== String) return; - let version_lower = version.toLowerCase(); - for (let i = 1; i < Object.keys(server_types).length; i++) { - const type = Object.values(server_types)[i]; - if (version_lower.includes(type)) { - return type; - } - } - return server_types.UNSUPPORTED; -} - -/** - * Returns a list of capabilities for a given server_type. - * @param {string} type - */ -function getCapabilities(type) { - let c = []; - switch (type) { - case server_types.MASTODON: - break; - case server_types.GLITCHSOC: - c.push(capabilities.REACTIONS); - break; - case server_types.CHUCKYA: - c.push(capabilities.REACTIONS); - c.push(capabilities.FOREIGN_REACTIONS); - break; - case server_types.FIREFISH: - c.push(capabilities.REACTIONS); - break; - case server_types.ICESHRIMP: - // more trouble than it's worth atm - // mastodon API already hands html to us - //c.push(capabilities.MARKDOWN_CONTENT); - c.push(capabilities.REACTIONS); - break; - case server_types.SHARKEY: - c.push(capabilities.REACTIONS); - break; - default: - break; - } - return c; -} diff --git a/src/lib/config.js b/src/lib/config.js deleted file mode 100644 index ccb3cdc..0000000 --- a/src/lib/config.js +++ /dev/null @@ -1 +0,0 @@ -export const app_name = "campfire"; diff --git a/src/lib/emoji.js b/src/lib/emoji.js index 29385c3..89df2d1 100644 --- a/src/lib/emoji.js +++ b/src/lib/emoji.js @@ -1,27 +1,52 @@ +import { client } from './client/client.js'; import { get } from 'svelte/store'; -export const EMOJI_REGEX = /:[\w\-.]{0,32}:/g; -export function parseEmoji(shortcode, url) { - let emoji = { shortcode, url }; - if (emoji.shortcode == '❤') emoji.shortcode = '❤️'; // stupid heart unicode - emoji.html = `${emoji.shortcode}`; - return emoji; +export const EMOJI_REGEX = /:[\w\-.]{0,32}@[\w\-.]{0,32}:/g; +export const EMOJI_NAME_REGEX = /:[\w\-.]{0,32}:/g; + +export default class Emoji { + name; + url; + + constructor(id, name, host, url) { + this.id = id; + this.name = name; + this.host = host; + this.url = url; + } + + get html() { + if (this.url) + return `${this.name}`; + else + return `${this.name}`; + } } -export function renderEmoji(text, emoji_list) { +export function parseText(text, host) { if (!text) return text; - let index = text.search(EMOJI_REGEX); + let index = text.search(EMOJI_NAME_REGEX); if (index === -1) return text; - // find the closing comma + // find the emoji name let length = text.substring(index + 1).search(':'); if (length <= 0) return text; + let emoji_name = text.substring(index + 1, index + length + 1); + let emoji = get(client).getEmoji(emoji_name + '@' + host); - // see if emoji is valid - let shortcode = text.substring(index + 1, index + length + 1); - let emoji = emoji_list[shortcode]; - let replace = emoji ? emoji.html : shortcode; - - return text.substring(0, index) + replace + renderEmoji(text.substring(index + length + 2), emoji_list); + if (emoji) { + return text.substring(0, index) + emoji.html + + parseText(text.substring(index + length + 2), host); + } + return text.substring(0, index + length + 1) + + parseText(text.substring(index + length + 1), host); +} + +export function parseOne(emoji_id) { + if (emoji_id == '❤') return '❤️'; // stupid heart unicode + if (EMOJI_REGEX.exec(':' + emoji_id + ':')) return emoji_id; + let cached_emoji = get(client).getEmoji(emoji_id); + if (!cached_emoji) return emoji_id; + return cached_emoji.html; } diff --git a/src/lib/notifications.js b/src/lib/notifications.js index 7798d29..bbdc69f 100644 --- a/src/lib/notifications.js +++ b/src/lib/notifications.js @@ -1,82 +1,40 @@ -import * as api from '$lib/api.js'; -import { server } from '$lib/client/server.js'; -import { app } from '$lib/client/app.js'; -import { app_name } from '$lib/config.js'; +import { client } from '$lib/client/client.js'; +import * as api from '$lib/client/api.js'; import { get, writable } from 'svelte/store'; -import { browser } from '$app/environment'; -import { parsePost } from '$lib/post.js'; -import { parseAccount } from '$lib/account.js'; -const prefix = app_name + '_notif_'; -const notification_limit = 40; +export let notifications = writable([]); +export let unread_notif_count = writable(0); +export let last_read_notif_id = writable(0); -export const notifications = writable([]); -export const unread_notif_count = writable(load("unread_count")); -export const last_read_notif_id = writable(load("last_read")); +let loading; +export async function getNotifications() { + if (loading) return; // no spamming!! + loading = true; -unread_notif_count.subscribe(count => save("unread_count", count)); -last_read_notif_id.subscribe(id => save("last_read", id)); - -/** - * Saves the provided data to localStorage. - * If `data` is falsy, the record is removed from localStorage. - * @param {Object} name - * @param {any} data - */ -function save(name, data) { - if (!browser) return; - if (data) { - localStorage.setItem(prefix + name, data); - } else { - localStorage.removeItem(prefix + name); - } -} - -/** - * Returns named data loaded from localStorage, if it exists. - * Otherwise, returns false. - */ -function load(name) { - if (!browser) return; - let data = localStorage.getItem(prefix + name); - return data ? data : false; -} - -export async function getNotifications(min_id, max_id) { - const new_notifications = await api.getNotifications( - get(server).host, - get(app).token, - min_id, - max_id, - notification_limit, - ); - - if (!new_notifications) { - console.error(`Failed to retrieve notifications.`); - loading = false; - return; - } - - for (let i in new_notifications) { - let notif = new_notifications[i]; - notif.accounts = [ await parseAccount(notif.account) ]; - - const _notifications = get(notifications); - if (_notifications.length > 0) { - let prev = _notifications[_notifications.length - 1]; - - if (notif.type === prev.type) { - if (prev.status && notif.status && prev.status.id === notif.status.id) { - notifications.update(notifications => { - notifications[notifications.length - 1].accounts.push(notif.accounts[0]); - return notifications; - }); - continue; + api.getNotifications().then(async data => { + if (!data || data.length <= 0) return; + notifications.set([]); + for (let i in data) { + let notif = data[i]; + notif.accounts = [ await api.parseUser(notif.account) ]; + if (get(notifications).length > 0) { + let prev = get(notifications)[get(notifications).length - 1]; + if (notif.type === prev.type) { + if (prev.status && notif.status && prev.status.id === notif.status.id) { + notifications.update(notifications => { + notifications[notifications.length - 1].accounts.push(notif.accounts[0]); + return notifications; + }); + continue; + } } } + notif.status = notif.status ? await api.parsePost(notif.status, 0, false) : null; + notifications.update(notifications => [...notifications, notif]); } - - notif.status = notif.status ? await parsePost(notif.status, 0, false) : null; - notifications.update(notifications => [...notifications, notif]); - } + last_read_notif_id.set(data[0].id); + unread_notif_count.set(0); + get(client).save(); + loading = false; + }); } diff --git a/src/lib/post.js b/src/lib/post.js index 66ccf34..9b6d10f 100644 --- a/src/lib/post.js +++ b/src/lib/post.js @@ -1,84 +1,177 @@ -import * as api from '$lib/api.js'; -import { server } from '$lib/client/server.js'; -import { app } from '$lib/client/app.js'; -import { parseAccount } from '$lib/account.js'; -import { parseEmoji, renderEmoji } from '$lib/emoji.js'; -import { get, writable } from 'svelte/store'; +import { parseText as parseEmoji } from './emoji.js'; -const cache = writable({}); +export default class Post { + id; + created_at; + user; + text; + warning; + boost_count; + reply_count; + favourite_count; + favourited; + boosted; + mentions; + reactions; + emojis; + files; + url; + reply; + reply_id; + replies; + boost; + visibility; -/** - * Parses a post using API data, and returns a writable store object. - * @param {Object} data - * @param {number} ancestor_count - */ -export async function parsePost(data, ancestor_count) { - let post = {}; - if (!ancestor_count) ancestor_count = 0; - - post.html = data.content; - - post.reply = null; - if ((data.in_reply_to_id || data.reply) && ancestor_count !== 0) { - const reply_data = data.reply || await api.getPost(get(server).host, get(app).token, data.in_reply_to_id); - // if the post returns false, we probably don't have permission to read it. - // we'll respect the thread's privacy, and leave it alone :) - if (!reply_data) return false; - post.reply = await parsePost(reply_data, ancestor_count - 1, false); + async rich_text() { + return parseEmoji(this.text, this.user.host); } - post.boost = data.reblog ? await parsePost(data.reblog, 1, false) : null; + /* + async rich_text() { + let text = this.text; + if (!text) return text; + let client = Client.get(); - post.id = data.id; - post.created_at = new Date(data.created_at); - post.account = await parseAccount(data.account); - post.warning = data.spoiler_text; - post.reply_count = data.replies_count; - post.boost_count = data.reblogs_count; - post.boosted = data.reblogged; - post.favourite_count = data.favourites_count; - post.favourited = data.favourited; - post.mentions = data.mentions; - post.media = data.media_attachments; - post.url = data.url; - post.visibility = data.visibility; + const markdown_tokens = [ + { tag: "pre", token: "```" }, + { tag: "code", token: "`" }, + { tag: "strong", token: "**" }, + { tag: "strong", token: "__" }, + { tag: "em", token: "*" }, + { tag: "em", token: "_" }, + ]; - post.emojis = []; - if (post.emojis) { - data.emojis.forEach(emoji => { - post.emojis[emoji.shortcode] = parseEmoji(emoji.shortcode, emoji.url); - }); + let response = ""; + let md_layer; + let index = 0; + while (index < text.length) { + let sample = text.substring(index); + let md_nostack = !(md_layer && md_layer.nostack); + + // handle newlines + if (md_nostack && sample.startsWith('\n')) { + response += "
"; + index++; + continue; + } + + // handle mentions + if (client.instance.capabilities.includes(capabilities.MARKDOWN_CONTENT) + && md_nostack + && sample.match(/^@[\w\-.]+@[\w\-.]+/g) + ) { + // find end of the mention + let length = 1; + while (index + length < text.length && /[a-z0-9-_.]/.test(text[index + length])) length++; + length++; // skim the middle @ + while (index + length < text.length && /[a-z0-9-_.]/.test(text[index + length])) length++; + + let mention = text.substring(index, index + length); + + // attempt to resolve mention to a user + let user = await client.getUserByMention(mention); + if (user) { + const out = `` + + `` + + '@' + user.username + '@' + user.host + ""; + if (md_layer) md_layer.text += out; + else response += out; + } else { + response += mention; + } + index += mention.length; + continue; + } + + // handle links + if (client.instance.capabilities.includes(capabilities.MARKDOWN_CONTENT) + && md_nostack + && sample.match(/^[a-z]{3,6}:\/\/[^\s]+/g) + ) { + // get length of link + let length = text.substring(index).search(/\s|$/g); + let url = text.substring(index, index + length); + let out = `${url}`; + if (md_layer) md_layer.text += out; + else response += out; + index += length; + continue; + } + + // handle emojis + if (md_nostack && sample.match(/^:[\w\-.]{0,32}:/g)) { + // find the emoji name + let length = text.substring(index + 1).search(':'); + if (length <= 0) return text; + let emoji_name = text.substring(index + 1, index + length + 1); + let emoji = client.getEmoji(emoji_name + '@' + this.user.host); + + index += length + 2; + + if (!emoji) { + let out = ':' + emoji_name + ':'; + if (md_layer) md_layer.text += out; + else response += out; + continue; + } + + let out = emoji.html; + if (md_layer) md_layer.text += out; + else response += out; + continue; + } + + // handle markdown + // TODO: handle misskey-flavoured markdown(?) + if (md_layer) { + // try to pop layer + if (sample.startsWith(md_layer.token)) { + index += md_layer.token.length; + let out = `<${md_layer.tag}>${md_layer.text}`; + if (md_layer.token === '```') + out = `
${md_layer.text}
`; + if (md_layer.parent) md_layer.parent.text += out; + else response += out; + md_layer = md_layer.parent; + } else { + md_layer.text += sample[0]; + index++; + } + } else if (md_nostack) { + // should we add a layer? + let pushed = false; + for (let i = 0; i < markdown_tokens.length; i++) { + let item = markdown_tokens[i]; + if (sample.startsWith(item.token)) { + let new_md_layer = { + token: item.token, + tag: item.tag, + text: "", + parent: md_layer, + }; + if (item.token === '```' || item.token === '`') new_md_layer.nostack = true; + md_layer = new_md_layer; + pushed = true; + index += md_layer.token.length; + break; + } + } + if (!pushed) { + response += sample[0]; + index++; + } + } + } + + // destroy the remaining stack + while (md_layer) { + let out = md_layer.token + md_layer.text; + if (md_layer.parent) md_layer.parent.text += out; + else response += out; + md_layer = md_layer.parent; + } + + return response; } - - if (data.reactions) post.reactions = parseReactions(data.reactions); - - post.rich_text = renderEmoji(post.html, post.emojis); - - return post; - - // let cache_post = get(cache)[post.id]; - // if (cache_post) { - // cache_post.set(post); - // } else { - // cache.update(cache => { - // cache[post.id] = writable(post); - // return cache; - // }); - // } - - // return get(cache)[post.id]; -} - -export function parseReactions(data) { - let reactions = []; - data.forEach(reaction_data => { - let reaction = { - count: reaction_data.count, - name: reaction_data.name, - me: reaction_data.me, - }; - if (reaction_data.url) reaction.url = reaction_data.url; - reactions.push(reaction); - }); - return reactions; + */ } diff --git a/src/lib/sound.js b/src/lib/sound.js index 377b833..3aa0e58 100644 --- a/src/lib/sound.js +++ b/src/lib/sound.js @@ -6,12 +6,12 @@ let sounds; if (typeof Audio !== typeof undefined) { sounds = { "default": new Audio(sound_log), - "post": new Audio(sound_success), - "boost": new Audio(sound_hello), + "post": new Audio(sound_hello), + "boost": new Audio(sound_success), }; } -export function playSound(name) { +export function play_sound(name) { if (name === false) return; if (!name) name = "default"; const sound = sounds[name]; diff --git a/src/lib/stores/account.js b/src/lib/stores/account.js deleted file mode 100644 index 5c6fecc..0000000 --- a/src/lib/stores/account.js +++ /dev/null @@ -1,3 +0,0 @@ -import { writable } from 'svelte/store'; - -export let account = writable(false); diff --git a/src/lib/stores/compose.js b/src/lib/stores/compose.js deleted file mode 100644 index 2fe3b96..0000000 --- a/src/lib/stores/compose.js +++ /dev/null @@ -1,4 +0,0 @@ -import { writable } from 'svelte/store'; - -export const show = writable(false); -export const reply_post = writable(null); diff --git a/src/lib/stores/user.js b/src/lib/stores/user.js new file mode 100644 index 0000000..fb9c2c4 --- /dev/null +++ b/src/lib/stores/user.js @@ -0,0 +1,22 @@ +import { client } from '$lib/client/client.js'; +import * as api from '$lib/client/api.js'; +import { get, writable } from 'svelte/store'; + +export let user = writable(0); +export let logged_in = writable(false); + +export async function getUser() { + // already known + if (get(user)) return get(user); + + // cannot provide- not logged in + if (!get(client).app || !get(client).app.token) return false; + + // logged in- attempt to retrieve using token + const data = await api.verifyCredentials(); + if (!data) return false; + + user.set(await api.parseUser(data)); + console.log(`Logged in as @${get(user).username}@${get(user).host}`); + return get(user); +} diff --git a/src/lib/timeline.js b/src/lib/timeline.js index ae8a5e3..0ef7b8f 100644 --- a/src/lib/timeline.js +++ b/src/lib/timeline.js @@ -1,10 +1,8 @@ -import * as api from '$lib/api.js'; -import { server } from '$lib/client/server.js'; -import { app } from '$lib/client/app.js'; +import { client } from '$lib/client/client.js'; import { get, writable } from 'svelte/store'; -import { parsePost } from '$lib/post.js'; +import { parsePost } from '$lib/client/api.js'; -export const timeline = writable([]); +export let timeline = writable([]); let loading = false; @@ -12,16 +10,9 @@ export async function getTimeline(clean) { if (loading) return; // no spamming!! loading = true; - let last_post = false; - if (!clean && get(timeline).length > 0) - last_post = get(timeline)[get(timeline).length - 1].id; - - const timeline_data = await api.getTimeline( - get(server).host, - get(app).token, - "home", - last_post - ); + let timeline_data; + if (clean || get(timeline).length === 0) timeline_data = await get(client).getTimeline() + else timeline_data = await get(client).getTimeline(get(timeline)[get(timeline).length - 1].id); if (!timeline_data) { console.error(`Failed to retrieve timeline.`); @@ -33,7 +24,7 @@ export async function getTimeline(clean) { for (let i in timeline_data) { const post_data = timeline_data[i]; - const post = await parsePost(post_data, 1); + const post = await parsePost(post_data, 1, false); if (!post) { if (post === null || post === undefined) { if (post_data.id) { diff --git a/src/lib/ui/Button.svelte b/src/lib/ui/Button.svelte index 22e4d87..ea68fc4 100644 --- a/src/lib/ui/Button.svelte +++ b/src/lib/ui/Button.svelte @@ -1,5 +1,5 @@ - -
-
- - - -
- -
-
-
-
- -
-
- {#if show_cw} - - {/if} - - -
- - diff --git a/src/lib/ui/Feed.svelte b/src/lib/ui/Feed.svelte new file mode 100644 index 0000000..a5c5d84 --- /dev/null +++ b/src/lib/ui/Feed.svelte @@ -0,0 +1,62 @@ + + +
+

Home

+ +
+ +
+ {#if posts.length <= 0} +
+ getting the feed... +
+ {/if} + {#each posts as post} + + {/each} +
+ + diff --git a/src/lib/ui/LoginForm.svelte b/src/lib/ui/LoginForm.svelte index 1a64f88..8cf9b07 100644 --- a/src/lib/ui/LoginForm.svelte +++ b/src/lib/ui/LoginForm.svelte @@ -1,42 +1,37 @@ @@ -45,11 +40,11 @@

Welcome, fediverse user!

-

Please enter your server domain to log in.

+

Please enter your instance domain to log in.

- - {#if display_error} -

{display_error}

+ + {#if instance_url_error} +

{instance_url_error}

{/if}

@@ -75,10 +70,6 @@ text-align: center; } - .app-logo :global(svg) { - width: 100%; - } - .input-wrapper { width: 360px; margin: 0 auto; diff --git a/src/lib/ui/Modal.svelte b/src/lib/ui/Modal.svelte deleted file mode 100644 index 3ac3727..0000000 --- a/src/lib/ui/Modal.svelte +++ /dev/null @@ -1,97 +0,0 @@ - - -{#if visible} -
visible = !visible}>
-
- -
-{/if} - - diff --git a/src/lib/ui/Navigation.svelte b/src/lib/ui/Navigation.svelte index a5bedfd..c4ce6e5 100644 --- a/src/lib/ui/Navigation.svelte +++ b/src/lib/ui/Navigation.svelte @@ -1,18 +1,17 @@
@@ -169,17 +151,16 @@
- playSound()}> + play_sound()}>
{/if} - campfire v{VERSION}
@@ -202,7 +183,7 @@ background-color: var(--bg-800); } - .server-header { + .instance-header { width: 100%; height: 172px; display: flex; @@ -215,7 +196,7 @@ background-image: linear-gradient(to top, var(--bg-800), var(--bg-600)); } - .server-icon { + .instance-icon { height: 50%; border-radius: 8px; } @@ -364,11 +345,6 @@ font-size: .65em; } - .nickname :global(.emoji) { - height: 1.2em; - margin: -.1em 0; - } - .flex-row { display: flex; flex-direction: row; diff --git a/src/lib/ui/Notification.svelte b/src/lib/ui/Notification.svelte index b44b63e..f16abec 100644 --- a/src/lib/ui/Notification.svelte +++ b/src/lib/ui/Notification.svelte @@ -1,12 +1,12 @@
{#if data.status}
- {#if data.status.warning} -
- {data.status.warning} -
- {:else} - {@html data.status.rich_text} - {/if} - - {#if data.status.media && data.status.media.length > 0} -
- {#each data.status.media as media} -
- {#if ["image", "gifv", "gif"].includes(media.type)} - - {media.description} - - {:else if media.type === "video"} - - {/if} -
- {/each} -
- {/if} + {@html data.status.html}
{#if data.type === "mention"} {#if data.status.reactions} @@ -148,7 +122,6 @@ text-decoration: inherit; color: inherit; transition: background-color .1s; - cursor: pointer; } .notification:hover { @@ -181,7 +154,6 @@ } header .notif-avatars img:not(:first-child) { box-shadow: 4px 0 8px -2px rgba(0,0,0,.33); - z-index: 0; } header .notif-avatars img:not(:last-child) { margin-left: -8px; @@ -275,52 +247,4 @@ margin-right: 4px; border-radius: 4px; } - - .notif-content .warning { - width: calc(100% - 16px); - margin-bottom: 10px; - padding: 4px 8px; - --warn-bg: color-mix(in srgb, var(--bg-700), var(--accent) 1%); - background: repeating-linear-gradient(-45deg, transparent, transparent 10px, var(--warn-bg) 10px, var(--warn-bg) 20px); - font-family: inherit; - font-size: inherit; - color: inherit; - text-align: left; - border: none; - border-radius: 8px; - cursor: pointer; - outline-color: var(--warn-bg); - transition: outline .05s, box-shadow .05s; - } - - .notif-media-container { - margin: 16px 0 4px 0; - display: flex; - flex-direction: row; - gap: 8px; - font-size: 14px; - line-height: 1.45em; - } - - .notif-media { - display: inline-block; - border-radius: 12px; - background-color: #000; - overflow: hidden; - } - - .notif-media a { - width: 5em; - height: 5em; - display: block; - cursor: zoom-in; - } - - .notif-media img, - .notif-media video { - width: 100%; - height: 100%; - display: block; - object-fit: cover; - } diff --git a/src/lib/ui/post/ActionBar.svelte b/src/lib/ui/post/ActionBar.svelte index 8d86ba8..2b4c579 100644 --- a/src/lib/ui/post/ActionBar.svelte +++ b/src/lib/ui/post/ActionBar.svelte @@ -1,11 +1,7 @@ - diff --git a/src/routes/[server]/[account]/+page.js b/src/routes/[server]/[account]/+page.js deleted file mode 100644 index edbfd18..0000000 --- a/src/routes/[server]/[account]/+page.js +++ /dev/null @@ -1,8 +0,0 @@ -import { error } from '@sveltejs/kit'; - -export async function load({ params }) { - return error(404, 'Not Found'); - // return { - // account_name: params.account - // }; -} diff --git a/src/routes/[server]/[account]/[post]/+page.js b/src/routes/[server]/[account]/[post]/+page.js deleted file mode 100644 index 0cd52c3..0000000 --- a/src/routes/[server]/[account]/[post]/+page.js +++ /dev/null @@ -1,7 +0,0 @@ -export async function load({ params }) { - return { - server_host: params.server, - account_handle: params.account, - post_id: params.post - }; -} diff --git a/src/routes/[server]/[account]/[post]/+page.svelte b/src/routes/[server]/[account]/[post]/+page.svelte deleted file mode 100644 index 75daa65..0000000 --- a/src/routes/[server]/[account]/[post]/+page.svelte +++ /dev/null @@ -1,146 +0,0 @@ - - -{#await post} -
- loading post... -
-{:then post} - {#if error} -

{@html error}

- {:else} -
- {#if previous_page} - - {/if} - -

- Post by {@html post.account.rich_name} -

-
- -
- -
- {#each post.replies as reply} - {#await reply then reply} - - {/await} - {/each} -
- {/if} -{/await} - - diff --git a/src/routes/callback/+page.svelte b/src/routes/callback/+page.svelte index 8b6985e..9540cb8 100644 --- a/src/routes/callback/+page.svelte +++ b/src/routes/callback/+page.svelte @@ -1,46 +1,42 @@
diff --git a/src/routes/post/+page.js b/src/routes/post/+page.js new file mode 100644 index 0000000..c0ac9bd --- /dev/null +++ b/src/routes/post/+page.js @@ -0,0 +1,5 @@ +import { error } from '@sveltejs/kit'; + +export function load(event) { + error(404, 'Not Found'); +} diff --git a/src/routes/[server]/+page.js b/src/routes/post/[id]/+page.js similarity index 63% rename from src/routes/[server]/+page.js rename to src/routes/post/[id]/+page.js index bc936af..9291873 100644 --- a/src/routes/[server]/+page.js +++ b/src/routes/post/[id]/+page.js @@ -1,5 +1,5 @@ export async function load({ params }) { return { - server_domain: params.server + post_id: params.id }; } diff --git a/src/routes/post/[id]/+page.svelte b/src/routes/post/[id]/+page.svelte new file mode 100644 index 0000000..e2e27aa --- /dev/null +++ b/src/routes/post/[id]/+page.svelte @@ -0,0 +1,132 @@ + + +{#if !error} +
+ {#await post then post} + + +

+ Post by {@html post.user.rich_name} +

+ {/await} +
+ +
+ {#await post} +
+ loading post... +
+ {:then post} + +
+ {#each post.replies as reply} + {#await reply then reply} + + {/await} + {/each} + {/await} +
+{:else} +

{@html error}

+{/if} + + diff --git a/svelte.config.js b/svelte.config.js index 9c24c44..b371a21 100644 --- a/svelte.config.js +++ b/svelte.config.js @@ -17,11 +17,6 @@ const config = { }), version: { name: child_process.execSync('git rev-parse HEAD').toString().trim() - }, - alias: { - '@cf/ui/*': "./src/lib/ui", - '@cf/icons/*': "./src/img/icons", - '@cf/store/*': "./src/lib/stores" } }, };