diff --git a/.github/workflows/node.js.yml b/.github/workflows/node.js.yml
index f512836..cbcb7b9 100644
--- a/.github/workflows/node.js.yml
+++ b/.github/workflows/node.js.yml
@@ -20,7 +20,7 @@ jobs:
strategy:
matrix:
- node-version: [18.x]
+ node-version: [22.x]
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
steps:
diff --git a/package-lock.json b/package-lock.json
index 39bf425..9364094 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -26,7 +26,7 @@
"jszip": "^3.10.1",
"ngx-highlightjs": "^12.0.0",
"pair-pagination": "^0.0.1",
- "pdfjs-dist": "^4.6.82",
+ "pdfjs-dist": "4.6.82",
"readiverse": "^0.1.0",
"rxjs": "~7.8.0",
"tslib": "^2.3.0",
@@ -3293,6 +3293,103 @@
"win32"
]
},
+ "node_modules/@mapbox/node-pre-gyp": {
+ "version": "1.0.11",
+ "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz",
+ "integrity": "sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==",
+ "license": "BSD-3-Clause",
+ "optional": true,
+ "dependencies": {
+ "detect-libc": "^2.0.0",
+ "https-proxy-agent": "^5.0.0",
+ "make-dir": "^3.1.0",
+ "node-fetch": "^2.6.7",
+ "nopt": "^5.0.0",
+ "npmlog": "^5.0.1",
+ "rimraf": "^3.0.2",
+ "semver": "^7.3.5",
+ "tar": "^6.1.11"
+ },
+ "bin": {
+ "node-pre-gyp": "bin/node-pre-gyp"
+ }
+ },
+ "node_modules/@mapbox/node-pre-gyp/node_modules/abbrev": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
+ "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==",
+ "license": "ISC",
+ "optional": true
+ },
+ "node_modules/@mapbox/node-pre-gyp/node_modules/agent-base": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
+ "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "debug": "4"
+ },
+ "engines": {
+ "node": ">= 6.0.0"
+ }
+ },
+ "node_modules/@mapbox/node-pre-gyp/node_modules/https-proxy-agent": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
+ "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "agent-base": "6",
+ "debug": "4"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/@mapbox/node-pre-gyp/node_modules/make-dir": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
+ "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "semver": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/@mapbox/node-pre-gyp/node_modules/make-dir/node_modules/semver": {
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+ "license": "ISC",
+ "optional": true,
+ "bin": {
+ "semver": "bin/semver.js"
+ }
+ },
+ "node_modules/@mapbox/node-pre-gyp/node_modules/nopt": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz",
+ "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==",
+ "license": "ISC",
+ "optional": true,
+ "dependencies": {
+ "abbrev": "1"
+ },
+ "bin": {
+ "nopt": "bin/nopt.js"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/@msgpackr-extract/msgpackr-extract-darwin-arm64": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-arm64/-/msgpackr-extract-darwin-arm64-3.0.3.tgz",
@@ -5569,7 +5666,7 @@
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
- "dev": true,
+ "devOptional": true,
"license": "MIT",
"engines": {
"node": ">=8"
@@ -5618,6 +5715,43 @@
"url": "https://github.com/sponsors/jonschlinkert"
}
},
+ "node_modules/aproba": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz",
+ "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==",
+ "license": "ISC",
+ "optional": true
+ },
+ "node_modules/are-we-there-yet": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz",
+ "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==",
+ "deprecated": "This package is no longer supported.",
+ "license": "ISC",
+ "optional": true,
+ "dependencies": {
+ "delegates": "^1.0.0",
+ "readable-stream": "^3.6.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/are-we-there-yet/node_modules/readable-stream": {
+ "version": "3.6.2",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
+ "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "inherits": "^2.0.3",
+ "string_decoder": "^1.1.1",
+ "util-deprecate": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
"node_modules/argparse": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
@@ -5743,14 +5877,14 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
- "dev": true,
+ "devOptional": true,
"license": "MIT"
},
"node_modules/base64-js": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
- "devOptional": true,
+ "dev": true,
"funding": [
{
"type": "github",
@@ -5828,7 +5962,7 @@
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz",
"integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==",
- "devOptional": true,
+ "dev": true,
"license": "MIT",
"dependencies": {
"buffer": "^5.5.0",
@@ -5840,7 +5974,7 @@
"version": "3.6.2",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
"integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
- "devOptional": true,
+ "dev": true,
"license": "MIT",
"dependencies": {
"inherits": "^2.0.3",
@@ -5912,7 +6046,7 @@
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
- "dev": true,
+ "devOptional": true,
"license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0",
@@ -5969,7 +6103,7 @@
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
"integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
- "devOptional": true,
+ "dev": true,
"funding": [
{
"type": "github",
@@ -6235,28 +6369,21 @@
"license": "CC-BY-4.0"
},
"node_modules/canvas": {
- "version": "3.0.0-rc2",
- "resolved": "https://registry.npmjs.org/canvas/-/canvas-3.0.0-rc2.tgz",
- "integrity": "sha512-esx4bYDznnqgRX4G8kaEaf0W3q8xIc51WpmrIitDzmcoEgwnv9wSKdzT6UxWZ4wkVu5+ileofppX0TpyviJRdQ==",
+ "version": "2.11.2",
+ "resolved": "https://registry.npmjs.org/canvas/-/canvas-2.11.2.tgz",
+ "integrity": "sha512-ItanGBMrmRV7Py2Z+Xhs7cT+FNt5K0vPL4p9EZ/UX/Mu7hFbkxSjKF2KVtPwX7UYWp7dRKnrTvReflgrItJbdw==",
"hasInstallScript": true,
"license": "MIT",
"optional": true,
"dependencies": {
- "node-addon-api": "^7.0.0",
- "prebuild-install": "^7.1.1",
+ "@mapbox/node-pre-gyp": "^1.0.0",
+ "nan": "^2.17.0",
"simple-get": "^3.0.3"
},
"engines": {
- "node": "^18.12.0 || >= 20.9.0"
+ "node": ">=6"
}
},
- "node_modules/canvas/node_modules/node-addon-api": {
- "version": "7.1.1",
- "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz",
- "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==",
- "license": "MIT",
- "optional": true
- },
"node_modules/chalk": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
@@ -6301,7 +6428,7 @@
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz",
"integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==",
- "dev": true,
+ "devOptional": true,
"license": "ISC",
"engines": {
"node": ">=10"
@@ -6506,6 +6633,16 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/color-support": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz",
+ "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==",
+ "license": "ISC",
+ "optional": true,
+ "bin": {
+ "color-support": "bin.js"
+ }
+ },
"node_modules/colorette": {
"version": "2.0.20",
"resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz",
@@ -6590,7 +6727,7 @@
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
- "dev": true,
+ "devOptional": true,
"license": "MIT"
},
"node_modules/connect": {
@@ -6688,6 +6825,13 @@
"node": ">= 0.6"
}
},
+ "node_modules/console-control-strings": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
+ "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==",
+ "license": "ISC",
+ "optional": true
+ },
"node_modules/content-disposition": {
"version": "0.5.4",
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
@@ -6967,7 +7111,7 @@
"version": "4.3.7",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
"integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
- "dev": true,
+ "devOptional": true,
"license": "MIT",
"dependencies": {
"ms": "^2.1.3"
@@ -6994,16 +7138,6 @@
"node": ">=8"
}
},
- "node_modules/deep-extend": {
- "version": "0.6.0",
- "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
- "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==",
- "license": "MIT",
- "optional": true,
- "engines": {
- "node": ">=4.0.0"
- }
- },
"node_modules/default-browser": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.2.1.tgz",
@@ -7077,6 +7211,13 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/delegates": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
+ "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==",
+ "license": "MIT",
+ "optional": true
+ },
"node_modules/depd": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
@@ -7261,7 +7402,6 @@
"version": "0.1.13",
"resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz",
"integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==",
- "dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
@@ -7272,7 +7412,6 @@
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
- "dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
@@ -7282,16 +7421,6 @@
"node": ">=0.10.0"
}
},
- "node_modules/end-of-stream": {
- "version": "1.4.4",
- "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
- "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==",
- "license": "MIT",
- "optional": true,
- "dependencies": {
- "once": "^1.4.0"
- }
- },
"node_modules/engine.io": {
"version": "6.6.2",
"resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.2.tgz",
@@ -7608,16 +7737,6 @@
"node": ">=0.8.x"
}
},
- "node_modules/expand-template": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz",
- "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==",
- "license": "(MIT OR WTFPL)",
- "optional": true,
- "engines": {
- "node": ">=6"
- }
- },
"node_modules/exponential-backoff": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.1.tgz",
@@ -7945,13 +8064,6 @@
"node": ">= 0.6"
}
},
- "node_modules/fs-constants": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
- "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==",
- "license": "MIT",
- "optional": true
- },
"node_modules/fs-extra": {
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz",
@@ -7984,7 +8096,7 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
- "dev": true,
+ "devOptional": true,
"license": "ISC"
},
"node_modules/fsevents": {
@@ -8011,6 +8123,67 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/gauge": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz",
+ "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==",
+ "deprecated": "This package is no longer supported.",
+ "license": "ISC",
+ "optional": true,
+ "dependencies": {
+ "aproba": "^1.0.3 || ^2.0.0",
+ "color-support": "^1.1.2",
+ "console-control-strings": "^1.0.0",
+ "has-unicode": "^2.0.1",
+ "object-assign": "^4.1.1",
+ "signal-exit": "^3.0.0",
+ "string-width": "^4.2.3",
+ "strip-ansi": "^6.0.1",
+ "wide-align": "^1.1.2"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/gauge/node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "license": "MIT",
+ "optional": true
+ },
+ "node_modules/gauge/node_modules/is-fullwidth-code-point": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+ "license": "MIT",
+ "optional": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/gauge/node_modules/signal-exit": {
+ "version": "3.0.7",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
+ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
+ "license": "ISC",
+ "optional": true
+ },
+ "node_modules/gauge/node_modules/string-width": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/gensync": {
"version": "1.0.0-beta.2",
"resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
@@ -8063,19 +8236,12 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/github-from-package": {
- "version": "0.0.0",
- "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz",
- "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==",
- "license": "MIT",
- "optional": true
- },
"node_modules/glob": {
"version": "7.2.3",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
"deprecated": "Glob versions prior to v9 are no longer supported",
- "dev": true,
+ "devOptional": true,
"license": "ISC",
"dependencies": {
"fs.realpath": "^1.0.0",
@@ -8215,6 +8381,13 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/has-unicode": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz",
+ "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==",
+ "license": "ISC",
+ "optional": true
+ },
"node_modules/hasown": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
@@ -8450,7 +8623,7 @@
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
- "devOptional": true,
+ "dev": true,
"funding": [
{
"type": "github",
@@ -8585,7 +8758,7 @@
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
"integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
"deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.",
- "dev": true,
+ "devOptional": true,
"license": "ISC",
"dependencies": {
"once": "^1.3.0",
@@ -10335,7 +10508,7 @@
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
- "dev": true,
+ "devOptional": true,
"license": "ISC",
"dependencies": {
"brace-expansion": "^1.1.7"
@@ -10348,7 +10521,7 @@
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
"integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
- "devOptional": true,
+ "dev": true,
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/ljharb"
@@ -10498,7 +10671,7 @@
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz",
"integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==",
- "dev": true,
+ "devOptional": true,
"license": "MIT",
"dependencies": {
"minipass": "^3.0.0",
@@ -10512,7 +10685,7 @@
"version": "3.3.6",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz",
"integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==",
- "dev": true,
+ "devOptional": true,
"license": "ISC",
"dependencies": {
"yallist": "^4.0.0"
@@ -10525,7 +10698,7 @@
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
- "dev": true,
+ "devOptional": true,
"license": "ISC"
},
"node_modules/mkdirp": {
@@ -10541,13 +10714,6 @@
"mkdirp": "bin/cmd.js"
}
},
- "node_modules/mkdirp-classic": {
- "version": "0.5.3",
- "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz",
- "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==",
- "license": "MIT",
- "optional": true
- },
"node_modules/mrmime": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.0.tgz",
@@ -10622,6 +10788,13 @@
"node": "^18.17.0 || >=20.5.0"
}
},
+ "node_modules/nan": {
+ "version": "2.22.0",
+ "resolved": "https://registry.npmjs.org/nan/-/nan-2.22.0.tgz",
+ "integrity": "sha512-nbajikzWTMwsW+eSsNm3QwlOs7het9gGJU5dDZzRTQGk03vyBOauxgI4VakDzE0PtsGTmXPsXTbbjVhRwR5mpw==",
+ "license": "MIT",
+ "optional": true
+ },
"node_modules/nanoid": {
"version": "3.3.7",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
@@ -10641,13 +10814,6 @@
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
}
},
- "node_modules/napi-build-utils": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz",
- "integrity": "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==",
- "license": "MIT",
- "optional": true
- },
"node_modules/needle": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/needle/-/needle-3.3.1.tgz",
@@ -10710,19 +10876,6 @@
"@angular/core": ">=17.0.0"
}
},
- "node_modules/node-abi": {
- "version": "3.71.0",
- "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.71.0.tgz",
- "integrity": "sha512-SZ40vRiy/+wRTf21hxkkEjPJZpARzUMVcJoQse2EF8qkUWbbO2z7vd5oA/H6bVH6SZQ5STGcu0KRDS7biNRfxw==",
- "license": "MIT",
- "optional": true,
- "dependencies": {
- "semver": "^7.3.5"
- },
- "engines": {
- "node": ">=10"
- }
- },
"node_modules/node-addon-api": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-6.1.0.tgz",
@@ -10731,6 +10884,27 @@
"license": "MIT",
"optional": true
},
+ "node_modules/node-fetch": {
+ "version": "2.7.0",
+ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
+ "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "whatwg-url": "^5.0.0"
+ },
+ "engines": {
+ "node": "4.x || >=6.0.0"
+ },
+ "peerDependencies": {
+ "encoding": "^0.1.0"
+ },
+ "peerDependenciesMeta": {
+ "encoding": {
+ "optional": true
+ }
+ }
+ },
"node_modules/node-forge": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz",
@@ -11176,6 +11350,20 @@
"url": "https://github.com/sponsors/isaacs"
}
},
+ "node_modules/npmlog": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz",
+ "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==",
+ "deprecated": "This package is no longer supported.",
+ "license": "ISC",
+ "optional": true,
+ "dependencies": {
+ "are-we-there-yet": "^2.0.0",
+ "console-control-strings": "^1.1.0",
+ "gauge": "^3.0.0",
+ "set-blocking": "^2.0.0"
+ }
+ },
"node_modules/nth-check": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz",
@@ -11193,7 +11381,7 @@
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
- "dev": true,
+ "devOptional": true,
"license": "MIT",
"engines": {
"node": ">=0.10.0"
@@ -11614,7 +11802,7 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
"integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
- "dev": true,
+ "devOptional": true,
"license": "MIT",
"engines": {
"node": ">=0.10.0"
@@ -11691,15 +11879,15 @@
}
},
"node_modules/pdfjs-dist": {
- "version": "4.8.69",
- "resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-4.8.69.tgz",
- "integrity": "sha512-IHZsA4T7YElCKNNXtiLgqScw4zPd3pG9do8UrznC757gMd7UPeHSL2qwNNMJo4r79fl8oj1Xx+1nh2YkzdMpLQ==",
+ "version": "4.6.82",
+ "resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-4.6.82.tgz",
+ "integrity": "sha512-BUOryeRFwvbLe0lOU6NhkJNuVQUp06WxlJVVCsxdmJ4y5cU3O3s3/0DunVdK1PMm7v2MUw52qKYaidhDH1Z9+w==",
"license": "Apache-2.0",
"engines": {
"node": ">=18"
},
"optionalDependencies": {
- "canvas": "^3.0.0-rc2",
+ "canvas": "^2.11.2",
"path2d": "^0.2.1"
}
},
@@ -11912,88 +12100,6 @@
"dev": true,
"license": "MIT"
},
- "node_modules/prebuild-install": {
- "version": "7.1.2",
- "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.2.tgz",
- "integrity": "sha512-UnNke3IQb6sgarcZIDU3gbMeTp/9SSU1DAIkil7PrqG1vZlBtY5msYccSKSHDqa3hNg436IXK+SNImReuA1wEQ==",
- "license": "MIT",
- "optional": true,
- "dependencies": {
- "detect-libc": "^2.0.0",
- "expand-template": "^2.0.3",
- "github-from-package": "0.0.0",
- "minimist": "^1.2.3",
- "mkdirp-classic": "^0.5.3",
- "napi-build-utils": "^1.0.1",
- "node-abi": "^3.3.0",
- "pump": "^3.0.0",
- "rc": "^1.2.7",
- "simple-get": "^4.0.0",
- "tar-fs": "^2.0.0",
- "tunnel-agent": "^0.6.0"
- },
- "bin": {
- "prebuild-install": "bin.js"
- },
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/prebuild-install/node_modules/decompress-response": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz",
- "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==",
- "license": "MIT",
- "optional": true,
- "dependencies": {
- "mimic-response": "^3.1.0"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/prebuild-install/node_modules/mimic-response": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz",
- "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==",
- "license": "MIT",
- "optional": true,
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/prebuild-install/node_modules/simple-get": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz",
- "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/feross"
- },
- {
- "type": "patreon",
- "url": "https://www.patreon.com/feross"
- },
- {
- "type": "consulting",
- "url": "https://feross.org/support"
- }
- ],
- "license": "MIT",
- "optional": true,
- "dependencies": {
- "decompress-response": "^6.0.0",
- "once": "^1.3.1",
- "simple-concat": "^1.0.0"
- }
- },
"node_modules/proc-log": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/proc-log/-/proc-log-5.0.0.tgz",
@@ -12052,17 +12158,6 @@
"license": "MIT",
"optional": true
},
- "node_modules/pump": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz",
- "integrity": "sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==",
- "license": "MIT",
- "optional": true,
- "dependencies": {
- "end-of-stream": "^1.1.0",
- "once": "^1.3.1"
- }
- },
"node_modules/punycode": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz",
@@ -12150,29 +12245,6 @@
"node": ">= 0.8"
}
},
- "node_modules/rc": {
- "version": "1.2.8",
- "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
- "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==",
- "license": "(BSD-2-Clause OR MIT OR Apache-2.0)",
- "optional": true,
- "dependencies": {
- "deep-extend": "^0.6.0",
- "ini": "~1.3.0",
- "minimist": "^1.2.0",
- "strip-json-comments": "~2.0.1"
- },
- "bin": {
- "rc": "cli.js"
- }
- },
- "node_modules/rc/node_modules/ini": {
- "version": "1.3.8",
- "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
- "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==",
- "license": "ISC",
- "optional": true
- },
"node_modules/readable-stream": {
"version": "2.3.8",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
@@ -12468,7 +12540,7 @@
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
"integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
"deprecated": "Rimraf versions prior to v4 are no longer supported",
- "dev": true,
+ "devOptional": true,
"license": "ISC",
"dependencies": {
"glob": "^7.1.3"
@@ -12903,6 +12975,13 @@
"node": ">= 0.8.0"
}
},
+ "node_modules/set-blocking": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
+ "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==",
+ "license": "ISC",
+ "optional": true
+ },
"node_modules/set-function-length": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
@@ -13501,7 +13580,7 @@
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
- "dev": true,
+ "devOptional": true,
"license": "MIT",
"dependencies": {
"ansi-regex": "^5.0.1"
@@ -13524,16 +13603,6 @@
"node": ">=8"
}
},
- "node_modules/strip-json-comments": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
- "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==",
- "license": "MIT",
- "optional": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
@@ -13584,7 +13653,7 @@
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz",
"integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==",
- "dev": true,
+ "devOptional": true,
"license": "ISC",
"dependencies": {
"chownr": "^2.0.0",
@@ -13598,63 +13667,11 @@
"node": ">=10"
}
},
- "node_modules/tar-fs": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz",
- "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==",
- "license": "MIT",
- "optional": true,
- "dependencies": {
- "chownr": "^1.1.1",
- "mkdirp-classic": "^0.5.2",
- "pump": "^3.0.0",
- "tar-stream": "^2.1.4"
- }
- },
- "node_modules/tar-fs/node_modules/chownr": {
- "version": "1.1.4",
- "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
- "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==",
- "license": "ISC",
- "optional": true
- },
- "node_modules/tar-stream": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz",
- "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==",
- "license": "MIT",
- "optional": true,
- "dependencies": {
- "bl": "^4.0.3",
- "end-of-stream": "^1.4.1",
- "fs-constants": "^1.0.0",
- "inherits": "^2.0.3",
- "readable-stream": "^3.1.1"
- },
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/tar-stream/node_modules/readable-stream": {
- "version": "3.6.2",
- "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
- "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
- "license": "MIT",
- "optional": true,
- "dependencies": {
- "inherits": "^2.0.3",
- "string_decoder": "^1.1.1",
- "util-deprecate": "^1.0.1"
- },
- "engines": {
- "node": ">= 6"
- }
- },
"node_modules/tar/node_modules/fs-minipass": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz",
"integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==",
- "dev": true,
+ "devOptional": true,
"license": "ISC",
"dependencies": {
"minipass": "^3.0.0"
@@ -13667,7 +13684,7 @@
"version": "3.3.6",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz",
"integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==",
- "dev": true,
+ "devOptional": true,
"license": "ISC",
"dependencies": {
"yallist": "^4.0.0"
@@ -13680,7 +13697,7 @@
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz",
"integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==",
- "dev": true,
+ "devOptional": true,
"license": "ISC",
"engines": {
"node": ">=8"
@@ -13690,7 +13707,7 @@
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
"integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==",
- "dev": true,
+ "devOptional": true,
"license": "MIT",
"bin": {
"mkdirp": "bin/cmd.js"
@@ -13703,7 +13720,7 @@
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
- "dev": true,
+ "devOptional": true,
"license": "ISC"
},
"node_modules/terser": {
@@ -13868,6 +13885,13 @@
"node": ">=0.6"
}
},
+ "node_modules/tr46": {
+ "version": "0.0.3",
+ "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
+ "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
+ "license": "MIT",
+ "optional": true
+ },
"node_modules/tree-dump": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/tree-dump/-/tree-dump-1.0.2.tgz",
@@ -14068,19 +14092,6 @@
"url": "https://github.com/sponsors/isaacs"
}
},
- "node_modules/tunnel-agent": {
- "version": "0.6.0",
- "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
- "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==",
- "license": "Apache-2.0",
- "optional": true,
- "dependencies": {
- "safe-buffer": "^5.0.1"
- },
- "engines": {
- "node": "*"
- }
- },
"node_modules/type-fest": {
"version": "0.21.3",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz",
@@ -14912,6 +14923,13 @@
"license": "MIT",
"optional": true
},
+ "node_modules/webidl-conversions": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
+ "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
+ "license": "BSD-2-Clause",
+ "optional": true
+ },
"node_modules/webpack": {
"version": "5.96.1",
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.96.1.tgz",
@@ -15300,6 +15318,17 @@
"node": ">=0.8.0"
}
},
+ "node_modules/whatwg-url": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
+ "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "tr46": "~0.0.3",
+ "webidl-conversions": "^3.0.0"
+ }
+ },
"node_modules/which": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
@@ -15313,6 +15342,48 @@
"which": "bin/which"
}
},
+ "node_modules/wide-align": {
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz",
+ "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==",
+ "license": "ISC",
+ "optional": true,
+ "dependencies": {
+ "string-width": "^1.0.2 || 2 || 3 || 4"
+ }
+ },
+ "node_modules/wide-align/node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "license": "MIT",
+ "optional": true
+ },
+ "node_modules/wide-align/node_modules/is-fullwidth-code-point": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+ "license": "MIT",
+ "optional": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/wide-align/node_modules/string-width": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/wildcard": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz",
diff --git a/package.json b/package.json
index 1dc5cec..b25438c 100644
--- a/package.json
+++ b/package.json
@@ -1,13 +1,14 @@
{
"name": "chytanka",
- "version": "0.14.28",
+ "version": "0.14.31",
"scripts": {
"ng": "ng",
"start": "ng serve",
"build": "ng build",
"watch": "ng build --watch --configuration development",
"test": "ng test",
- "serve:ssr:chytanka": "node dist/chytanka/server/server.mjs"
+ "serve:ssr:chytanka": "node dist/chytanka/server/server.mjs",
+ "upv": "node scripts/update-version.js"
},
"private": true,
"dependencies": {
@@ -29,7 +30,7 @@
"jszip": "^3.10.1",
"ngx-highlightjs": "^12.0.0",
"pair-pagination": "^0.0.1",
- "pdfjs-dist": "^4.6.82",
+ "pdfjs-dist": "4.6.82",
"readiverse": "^0.1.0",
"rxjs": "~7.8.0",
"tslib": "^2.3.0",
diff --git a/scripts/update-version.js b/scripts/update-version.js
new file mode 100644
index 0000000..06b861f
--- /dev/null
+++ b/scripts/update-version.js
@@ -0,0 +1,26 @@
+const fs = require('fs');
+const packageJson = require('../package.json');
+
+const version = packageJson.version;
+
+const environmentFilePaths = [
+ 'src/environments/environment.ts',
+ 'src/environments/environment.development.ts'
+]
+
+environmentFilePaths.forEach(path => update(path))
+
+function update(environmentFilePath) {
+ const filename = environmentFilePath.split('/').pop()
+ let content = fs.readFileSync(environmentFilePath, 'utf8');
+ const date = new Date(Date.now())
+ const versionDate = `${date.getFullYear()}.${date.getMonth()+1}.${date.getDate()}`
+ const V = `${version}-${versionDate}`;
+ const updatedContent = content.replace(
+ /version:\s*['"].*?['"]/,
+ `version: "${V}"`
+ );
+ fs.writeFileSync(environmentFilePath, updatedContent, 'utf8');
+
+ console.log('\x1b[36m%s\x1b[0m',`Updated ${filename} version to: ${V}`);
+}
\ No newline at end of file
diff --git a/src/app/app.component.ts b/src/app/app.component.ts
index 24b806d..6229458 100644
--- a/src/app/app.component.ts
+++ b/src/app/app.component.ts
@@ -2,11 +2,12 @@ import { Component, HostListener, PLATFORM_ID, WritableSignal, inject, signal }
import { LangService } from './shared/data-access/lang.service';
import { ActivatedRoute } from '@angular/router';
import { DOCUMENT, isPlatformBrowser } from '@angular/common';
+import { environment } from '../environments/environment';
const SCALE_GAP = 128;
@Component({
- selector: 'app-root',
+ selector: 'chtnk-root',
template: `
`,
styles: [`
// :host {
@@ -16,12 +17,14 @@ const SCALE_GAP = 128;
standalone: false
})
export class AppComponent {
+ private readonly document = inject(DOCUMENT);
platformId = inject(PLATFORM_ID)
+
constructor(public lang: LangService, private route: ActivatedRoute) {
this.lang.updateManifest()
this.lang.updateTranslate()
- if (isPlatformBrowser(this.platformId) && window.console) {
+ if (isPlatformBrowser(this.platformId) && window.console && environment.prod) {
const msg = `What are you looking for here? The plot twist is in the next volume!`
console.log(`%c${msg}`, "background-color: #166496; color: #ffd60a; font-size: 4rem; font-family: monospace; padding: 8px 16px");
}
@@ -39,8 +42,6 @@ export class AppComponent {
})
}
- private readonly document = inject(DOCUMENT);
-
@HostListener('window:resize')
initScaleDifference() {
const w = this.document.documentElement.clientWidth;
diff --git a/src/app/app.module.ts b/src/app/app.module.ts
index 0fada98..9beb1f7 100644
--- a/src/app/app.module.ts
+++ b/src/app/app.module.ts
@@ -1,5 +1,5 @@
import { LOCALE_ID, NgModule, isDevMode, provideZoneChangeDetection } from '@angular/core';
-import { BrowserModule, provideClientHydration } from '@angular/platform-browser';
+import { BrowserModule, provideClientHydration, withEventReplay } from '@angular/platform-browser';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
@@ -30,7 +30,7 @@ registerLocaleData(localeUk)
SharedModule],
providers: [
provideZoneChangeDetection({ eventCoalescing: true }),
- provideClientHydration(),
+ provideClientHydration(withEventReplay()),
provideHttpClient(withFetch())
]
})
diff --git a/src/app/file/data-access/file-hash.service.ts b/src/app/file/data-access/file-hash.service.ts
index 876c38a..0c6f875 100644
--- a/src/app/file/data-access/file-hash.service.ts
+++ b/src/app/file/data-access/file-hash.service.ts
@@ -5,18 +5,41 @@ import * as CryptoJS from 'crypto-js';
providedIn: 'root'
})
export class FileHashService {
- public getMd5Hash(arrayBuffer: ArrayBuffer) {
- const wordArray = this.arrayBufferToWordArray(arrayBuffer);
- const md5Hash = CryptoJS.MD5(wordArray).toString();
- return md5Hash;
- }
+ // getSHA256(arrayBuffer: ArrayBuffer): string {
+ // const wordArray = CryptoJS.lib.WordArray.create(new Uint8Array(arrayBuffer) as any);
+ // return CryptoJS.SHA256(wordArray).toString(CryptoJS.enc.Hex);
+ // }
- private arrayBufferToWordArray(arrayBuffer: ArrayBuffer): CryptoJS.lib.WordArray {
- const uint8Array = new Uint8Array(arrayBuffer);
- const words: any[] = [];
- for (let i = 0; i < uint8Array.length; i++) {
- words[i >>> 2] |= uint8Array[i] << (24 - (i % 4) * 8);
+ async sha256(file: File): Promise {
+ const sha256 = CryptoJS.algo.SHA256.create();
+ const sliceSize = 50_000_000; // 50 MiB
+ let start = 0;
+
+ while (start < file.size) {
+ const slice: Uint8Array = await this.readSlice(file, start, sliceSize);
+ const wordArray = CryptoJS.lib.WordArray.create(slice);
+ sha256.update(wordArray);
+ start += sliceSize;
}
- return CryptoJS.lib.WordArray.create(words, uint8Array.length);
+
+ return sha256.finalize().toString();
+ }
+
+ private async readSlice(file: File, start: number, size: number): Promise {
+ return new Promise((resolve, reject) => {
+ const fileReader = new FileReader();
+ const slice = file.slice(start, start + size);
+
+ fileReader.onload = () => {
+ if (fileReader.result) {
+ resolve(new Uint8Array(fileReader.result as ArrayBuffer));
+ } else {
+ reject(new Error("FileReader returned no result."));
+ }
+ };
+ fileReader.onerror = () => reject(fileReader.error || new Error("FileReader failed."));
+ fileReader.readAsArrayBuffer(slice);
+ });
}
+
}
diff --git a/src/app/file/data-access/file-history.service.ts b/src/app/file/data-access/file-history.service.ts
new file mode 100644
index 0000000..35bce3b
--- /dev/null
+++ b/src/app/file/data-access/file-history.service.ts
@@ -0,0 +1,87 @@
+import { isPlatformBrowser } from '@angular/common';
+import { inject, Injectable, PLATFORM_ID } from '@angular/core';
+import Dexie from 'dexie';
+
+const HISTORY_DB_NAME: string = `ChytankaHistoryDB`;
+const HISTORY_TABLE_NAME: string = `filehistory`;
+
+@Injectable({
+ providedIn: 'root'
+})
+export class FileHistoryService {
+ private db!: Dexie;
+
+ platformId = inject(PLATFORM_ID)
+
+ constructor() {
+ this.createDatabase();
+ }
+
+ private createDatabase() {
+ this.db = new Dexie(HISTORY_DB_NAME);
+ this.db.version(1).stores({
+ filehistory: '++id,sha256,pages,size,title,format,page,cover,arrayBuffer,created,updated'
+ });
+ }
+
+ async addHistory(fileHistory: any) {
+ if (!isPlatformBrowser(this.platformId)) return;
+
+ const { sha256, arrayBuffer, pages, page, cover, size, title, format } = fileHistory
+
+ // await this.db.table(HISTORY_TABLE_NAME).add({ site, post_id, title, cover });
+ const existingEntry = await this.db.table(HISTORY_TABLE_NAME).where({ sha256 }).first();
+
+ if (existingEntry) {
+ // Entry already exists, update the 'updated' field
+ const now = new Date().toISOString();
+ await this.db.table(HISTORY_TABLE_NAME).update(existingEntry.id, { arrayBuffer, updated: now });
+ } else {
+ // Entry doesn't exist, add a new one
+ const now = new Date().toISOString();
+ await this.db.table(HISTORY_TABLE_NAME).add({
+ created: now,
+ updated: now,
+ sha256,
+ arrayBuffer,
+ pages, page, cover, size, title, format
+ });
+ }
+ }
+
+ async getAllHistory() {
+ return await this.db.table(HISTORY_TABLE_NAME).orderBy('updated').reverse().toArray();
+ }
+
+ async getItemBySha256(sha256: string) {
+ return await this.db.table(HISTORY_TABLE_NAME).where('sha256').equals(sha256).first();
+}
+
+
+ async getAllHistoryWithoutBufferArray() {
+ const records = await this.db.table(HISTORY_TABLE_NAME).orderBy('updated').reverse().toArray();
+
+ return records.map(({ arrayBuffer, ...rest }) => rest);
+ }
+
+ async getTotalSizeAndCount(): Promise<{ size: number; count: number }> {
+ let size = 0;
+ let count = 0;
+
+ await this.db.table(HISTORY_TABLE_NAME).each(item => {
+ size += item.size;
+ count++;
+ });
+
+ return { size, count };
+ }
+
+ async clearHistory() {
+ await this.db.table(HISTORY_TABLE_NAME).clear();
+ }
+
+ async deleteHistoryItem(itemId: number) {
+ await this.db.table(HISTORY_TABLE_NAME).delete(itemId);
+ }
+
+}
diff --git a/src/app/file/data-access/file-settings.service.ts b/src/app/file/data-access/file-settings.service.ts
new file mode 100644
index 0000000..7e1ca96
--- /dev/null
+++ b/src/app/file/data-access/file-settings.service.ts
@@ -0,0 +1,70 @@
+import { isPlatformServer } from '@angular/common';
+import { inject, Injectable, PLATFORM_ID, signal, WritableSignal } from '@angular/core';
+
+const SAVE_FILE_HISTORY_NAME = "saveFileToHistory"
+const COPY_FILE_HISTORY_NAME = "copyFileToHistory"
+const RETENTION_TIME_NAME = "retentionTime"
+const STORAGE_LIMIT_NAME = "storageLimit"
+
+const DEFAULT_RETENTION_TIME = 30; // days
+const DEFAULT_STORAGE_LIMIT = 1024; //Mb
+
+@Injectable({
+ providedIn: 'root'
+})
+export class FileSettingsService {
+ platformId = inject(PLATFORM_ID)
+
+ readonly saveFileToHistory: WritableSignal = signal(false);
+ readonly copyFileToHistory: WritableSignal = signal(false);
+ readonly retentionTime: WritableSignal = signal(DEFAULT_RETENTION_TIME)
+ readonly storageLimit: WritableSignal = signal(DEFAULT_STORAGE_LIMIT)
+
+ constructor() {
+ this.init()
+ }
+
+ init() {
+ if (isPlatformServer(this.platformId)) return;
+
+ const n = Boolean(localStorage.getItem(SAVE_FILE_HISTORY_NAME) == 'true');
+ this.setSaveFileToHistory(n);
+
+ const sf = Boolean(localStorage.getItem(COPY_FILE_HISTORY_NAME) == 'true');
+ this.setCopyFileToHistory(sf);
+
+ const rt = Number(localStorage.getItem(RETENTION_TIME_NAME) ?? DEFAULT_RETENTION_TIME);
+ this.setRetentionTime(rt)
+
+ const sl = Number(localStorage.getItem(STORAGE_LIMIT_NAME) ?? DEFAULT_STORAGE_LIMIT);
+ this.setStorageLimit(sl)
+ }
+
+ setSaveFileToHistory(n: boolean) {
+ if (isPlatformServer(this.platformId)) return;
+
+ this.saveFileToHistory.set(n);
+ localStorage.setItem(SAVE_FILE_HISTORY_NAME, n.toString())
+ }
+
+ setCopyFileToHistory(n: boolean) {
+ if (isPlatformServer(this.platformId)) return;
+
+ this.copyFileToHistory.set(n);
+ localStorage.setItem(COPY_FILE_HISTORY_NAME, n.toString())
+ }
+
+ setRetentionTime(days: number) {
+ if (isPlatformServer(this.platformId)) return;
+
+ this.retentionTime.set(days);
+ localStorage.setItem(RETENTION_TIME_NAME, days.toString())
+ }
+
+ setStorageLimit(megabytes: number) {
+ if (isPlatformServer(this.platformId)) return;
+
+ this.storageLimit.set(megabytes);
+ localStorage.setItem(STORAGE_LIMIT_NAME, megabytes.toString())
+ }
+}
diff --git a/src/app/file/data-access/zip.worker.ts b/src/app/file/data-access/zip.worker.ts
index 7fe606f..70f7732 100644
--- a/src/app/file/data-access/zip.worker.ts
+++ b/src/app/file/data-access/zip.worker.ts
@@ -11,7 +11,7 @@ addEventListener('message', ({ data }) => {
.then(async zip => {
const filesName: string[] = Object.keys(zip.files);
- console.dir(zip.files)
+ // console.dir(zip.files)
const comicInfoFile = getComicInfoFile(filesName)
diff --git a/src/app/file/file-routing.module.ts b/src/app/file/file-routing.module.ts
index e36f683..b57caa5 100644
--- a/src/app/file/file-routing.module.ts
+++ b/src/app/file/file-routing.module.ts
@@ -1,19 +1,19 @@
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
+const zip = () => import('./zip/zip.component').then(mod => mod.ZipComponent);
+const pdf = () => import('./pdf/pdf.component').then(mod => mod.PdfComponent);
+const mobi = () => import('./mobi/mobi.component').then(mod => mod.MobiComponent);
+
const routes: Routes = [
- {
- path: 'zip',
- loadComponent: () => import('./zip/zip.component').then(mod => mod.ZipComponent)
- },
- {
- path: 'pdf',
- loadComponent: () => import('./pdf/pdf.component').then(mod => mod.PdfComponent)
- },
- {
- path: 'mobi',
- loadComponent: () => import('./mobi/mobi.component').then(mod => mod.MobiComponent)
- }
+ { path: 'zip/:sha256', loadComponent: zip },
+ { path: 'zip', loadComponent: zip },
+
+ { path: 'pdf/:sha256', loadComponent: pdf },
+ { path: 'pdf', loadComponent: pdf },
+
+ { path: 'mobi/:sha256', loadComponent: mobi },
+ { path: 'mobi', loadComponent: mobi }
];
@NgModule({
diff --git a/src/app/file/zip/zip.component.html b/src/app/file/zip/zip.component.html
index 0ec8be5..2e84688 100644
--- a/src/app/file/zip/zip.component.html
+++ b/src/app/file/zip/zip.component.html
@@ -1,5 +1,6 @@
@if(episode && episode.images && episode.images.length > 0){
-
+
+
} @else {
}
\ No newline at end of file
diff --git a/src/app/file/zip/zip.component.ts b/src/app/file/zip/zip.component.ts
index 2e73e61..6a4c1f5 100644
--- a/src/app/file/zip/zip.component.ts
+++ b/src/app/file/zip/zip.component.ts
@@ -1,34 +1,61 @@
-import { Component, effect, inject, OnDestroy, OnInit } from '@angular/core';
+import { Component, effect, inject, OnDestroy, OnInit, signal } from '@angular/core';
import { FileService } from '../data-access/file.service';
import { SharedModule } from '../../shared/shared.module';
-import { Router } from '@angular/router';
+import { ActivatedRoute, Router } from '@angular/router';
import { CompositionEpisode, CompositionImage } from '../../@site-modules/@common-read';
import { DomManipulationService } from '../../shared/data-access';
import { ComicInfo } from '../../shared/utils/comic-info';
import { Acbf } from '../../shared/utils/acbf';
+import { FileHashService } from '../data-access/file-hash.service';
+import { FileHistoryService } from '../data-access/file-history.service';
+import { FileSettingsService } from '../data-access/file-settings.service';
+import { map } from 'rxjs';
+import { ViewerComponent } from "../../viewer/viewer.component";
@Component({
- selector: 'app-zip',
- imports: [SharedModule],
- templateUrl: './zip.component.html',
- styleUrl: './zip.component.scss'
+ selector: 'app-zip',
+ imports: [SharedModule, /*ViewerComponent*/],
+ templateUrl: './zip.component.html',
+ styleUrl: './zip.component.scss'
})
export class ZipComponent implements OnInit, OnDestroy {
private worker!: Worker;
-
+ fileHash = inject(FileHashService)
+ fileHistory = inject(FileHistoryService)
+ fileSetts = inject(FileSettingsService)
+
+ sha256: string | undefined;
+ arrayBuffer: ArrayBuffer | undefined
+
episode: CompositionEpisode | undefined;
router = inject(Router)
+ private activatedRoute = inject(ActivatedRoute);
dm = inject(DomManipulationService)
fs = inject(FileService)
+ status = signal('')
constructor() {
this.initZipWorker()
effect(() => { this.fileChange(); });
}
+ sha256Params: string = '';
+ ngOnInit() {
+ this.sha256Params = this.activatedRoute.snapshot.params['sha256']
- ngOnInit() { }
+ if (this.sha256Params && this.sha256Params != '')
+ this.loadFromHistory(this.sha256Params)
+ }
+
+ async loadFromHistory(sha256: string) {
+ const { arrayBuffer, title } = await this.fileHistory.getItemBySha256(sha256)
+ if (!arrayBuffer) return;
+
+ this.sha256 = sha256
+ this.arrayBuffer = arrayBuffer
+ this.openArrayBuffer(arrayBuffer, title, sha256)
+ }
ngOnDestroy() {
this.terminateWorker()
@@ -46,10 +73,10 @@ export class ZipComponent implements OnInit, OnDestroy {
.set('acbf', this.acbfHandler.bind(this))
- private acbfHandler(msg: any) {
- const acbf = new Acbf(msg.data)
-
- }
+ private acbfHandler(msg: any) {
+ const acbf = new Acbf(msg.data)
+
+ }
private comicinfoHandler(msg: any) {
const comicInfo = new ComicInfo(msg.data)
@@ -66,6 +93,20 @@ export class ZipComponent implements OnInit, OnDestroy {
if (this.episode) {
this.episode.images = imgs
+
+ const obj = {
+ arrayBuffer: (this.fileSetts.copyFileToHistory()) ? this.arrayBuffer : null,
+ sha256: this.sha256,
+ pages: this.episode.images.length,
+ size: this.fs.file()?.size,
+ page: 1,
+ cover: '',
+ title: this.fs.file()?.name,
+ format: 'zip'
+ }
+
+ if (this.fileSetts.saveFileToHistory()) this.fileHistory.addHistory(obj)
+
}
}
private fileHandler(msg: any) {
@@ -90,20 +131,30 @@ export class ZipComponent implements OnInit, OnDestroy {
}
fileChange() {
+ this.status.set(`Opening file: ${this.fs.file()?.name}`)
const file = this.fs.file();
if (file && this.worker) {
const reader = new FileReader();
reader.onload = (e) => {
- const arrayBuffer = reader.result as ArrayBuffer;
- this.episode = {
- title: file.name,
- images: []
- }
- this.worker.postMessage({ arrayBuffer: arrayBuffer });
+ this.arrayBuffer = reader.result as ArrayBuffer;
+
+ this.openArrayBuffer(this.arrayBuffer, file.name)
};
reader.readAsArrayBuffer(file);
} else {
- this.router.navigateByUrl('/')
+ // this.router.navigateByUrl('/')
}
}
+
+ async openArrayBuffer(ab: ArrayBuffer, filename: string, sha256: string = '') {
+ if (sha256 == '') this.sha256 = await this.fileHash.sha256(this.fs.file() as File)
+
+ this.episode = { title: filename, images: [] }
+ this.worker.postMessage({ arrayBuffer: ab });
+ }
+
+ onPageChange(e: { total: number, current: number[] }) {
+ const { current, total } = e
+ console.log(`${current}/${total}`);
+ }
}
diff --git a/src/app/history/ui/history-list/history-list.component.html b/src/app/history/ui/history-list/history-list.component.html
index d1bfb92..a8b7375 100644
--- a/src/app/history/ui/history-list/history-list.component.html
+++ b/src/app/history/ui/history-list/history-list.component.html
@@ -1,11 +1,59 @@
-
- @for (item of historyItems() | async; track $index) {
-
- }
-
-
-@if (historyItems() | async; as items) { @if (items.length > 0) {
-
-} @else {}}
\ No newline at end of file
+@let files = historyFiles() | async;
+@let sites = historyItems() | async;
+
+@if (sites && sites.length > 0) {
+
+
+ SITES HISTORY
+ @if ( sites.length > 0) {
+
+ }
+
+
+
+ @for (item of sites; track $index) {
+
+ }
+
+
+}
+
+@if (files && files.length > 0) {
+
+
+ FILES HISTORY |
+ {{fileSize() | filesize}} |
+ {{fileCount()}}
+ @if ( files.length > 0) {
+
+ }
+
+
+
+ @for (item of files; track item.sha256;) {
+ @let ab = item.arrayBuffer;
+
+
0)?item.cover : '/assets/no-image.svg',
+ title: item.title,
+ updated: item.updated,
+ id: item.id,
+ size: item.size,
+ page: item.page,
+ pages: item.pages
+ }" (delete)="delFileById($event)" />
+
+ }
+
+
+}
+
+@if (files?.length ==0 && sites?.length ==0) {
+none
+}
\ No newline at end of file
diff --git a/src/app/history/ui/history-list/history-list.component.ts b/src/app/history/ui/history-list/history-list.component.ts
index f54334b..f38b586 100644
--- a/src/app/history/ui/history-list/history-list.component.ts
+++ b/src/app/history/ui/history-list/history-list.component.ts
@@ -1,18 +1,21 @@
import { Component, WritableSignal, inject, signal } from '@angular/core';
import { HistoryService } from '../../data-access/history.service';
import { LangService } from '../../../shared/data-access/lang.service';
+import { FileHistoryService } from '../../../file/data-access/file-history.service';
@Component({
- selector: 'app-history-list',
- templateUrl: './history-list.component.html',
- styleUrl: './history-list.component.scss',
- standalone: false
+ selector: 'app-history-list',
+ templateUrl: './history-list.component.html',
+ styleUrl: './history-list.component.scss',
+ standalone: false
})
export class HistoryListComponent {
public history: HistoryService = inject(HistoryService);
+ public fileHistory: FileHistoryService = inject(FileHistoryService);
lang: LangService = inject(LangService);
historyItems: WritableSignal> = signal(this.displayHistory() ?? []);
+ historyFiles: WritableSignal> = signal(this.displayFilesHistory() ?? []);
async displayHistory() {
const history = await this.history.getAllHistory();
@@ -29,4 +32,33 @@ export class HistoryListComponent {
await this.history.clearHistory();
this.historyItems.update(value => this.history.getAllHistory())
}
+
+ async displayFilesHistory() {
+ const history = await this.fileHistory.getAllHistory();
+ this.getTotalSizeAndCount()
+
+ return history;
+ }
+
+ async clearFileHistory() {
+ await this.fileHistory.clearHistory();
+ this.historyFiles.update(value => this.fileHistory.getAllHistory())
+ this.getTotalSizeAndCount()
+ }
+
+ async delFileById(id: number) {
+ await this.fileHistory.deleteHistoryItem(id);
+ this.historyFiles.update(value => this.fileHistory.getAllHistory())
+ this.getTotalSizeAndCount()
+ }
+
+ fileSize = signal(0);
+ fileCount= signal(0);
+
+ async getTotalSizeAndCount() {
+ const {count, size} = await this.fileHistory.getTotalSizeAndCount()
+
+ this.fileSize.set(size)
+ this.fileCount.set(count)
+ }
}
diff --git a/src/app/link-parser/data-access/link-parser.service.ts b/src/app/link-parser/data-access/link-parser.service.ts
index e0dd19d..d5147f4 100644
--- a/src/app/link-parser/data-access/link-parser.service.ts
+++ b/src/app/link-parser/data-access/link-parser.service.ts
@@ -1,4 +1,4 @@
-import { Injectable } from '@angular/core';
+import { Injectable, signal } from '@angular/core';
import { LinkParseResult, LinkParser } from '../utils';
@Injectable({
@@ -6,6 +6,21 @@ import { LinkParseResult, LinkParser } from '../utils';
})
export class LinkParserService {
+ supportFiles = signal([".zip", ".cbz", ".pdf", ".mobi"])
+
+ supportSites = signal([
+ "Imgur",
+ "Telegra.ph",
+ "Reddit",
+ "MangaDex",
+ "Zenko",
+ "Comick",
+ "NHentai",
+ "Yandere Pools",
+ "Blankary",
+ "Pixiv"
+ ].sort())
+
parsers: LinkParser[] = [];
constructor() { }
diff --git a/src/app/link-parser/link-parser/link-parser.component.html b/src/app/link-parser/link-parser/link-parser.component.html
index 8047e61..21977e4 100644
--- a/src/app/link-parser/link-parser/link-parser.component.html
+++ b/src/app/link-parser/link-parser/link-parser.component.html
@@ -3,8 +3,10 @@
-@defer{ }
-@defer{ }
\ No newline at end of file
+@defer (on immediate) {
+
+
+}
\ No newline at end of file
diff --git a/src/app/link-parser/link-parser/link-parser.component.scss b/src/app/link-parser/link-parser/link-parser.component.scss
index 366fecd..9dae161 100644
--- a/src/app/link-parser/link-parser/link-parser.component.scss
+++ b/src/app/link-parser/link-parser/link-parser.component.scss
@@ -6,21 +6,29 @@
height: 100dvh;
background: rgb(0, 39, 65);
+ app-chytanka-logo-with-tags {
+ height: 80dvh;
+ }
+
@media (max-aspect-ratio: 1) or (max-width: 640px) {
grid-template-columns: auto;
- grid-template-rows: 61.8fr 38.2fr;
+ // grid-template-rows: 61.8fr 38.2fr;
+ grid-template-rows: 1fr 1fr;
+
+ app-chytanka-logo-with-tags {
+ height: unset;
+ }
}
@media (prefers-color-scheme: light) {
background: #eceff2;
- background: radial-gradient(circle, #fffcf2 0%, #d4e4f2 100%);
}
}
main {
display: grid;
row-gap: 2ch;
- overflow: auto;
+ // overflow: auto;
max-height: 100%;
padding: 4ch 0;
}
@@ -56,6 +64,7 @@ lp-header {
}
:host ::ng-deep app-overlay {
+
&::after,
&::before {
content: unset;
@@ -68,7 +77,7 @@ lp-header {
:host:has(dialog[open]) {
transform: scale(calc(1 - var(--scale-diff-x, .1)), calc(1 - var(--scale-diff-y, .1)));
- filter: blur(3px);
+ // filter: blur(3px);
}
:host:has(input[type=url]:focus) {
diff --git a/src/app/link-parser/link-parser/link-parser.component.ts b/src/app/link-parser/link-parser/link-parser.component.ts
index bb55e4d..d0e4a78 100644
--- a/src/app/link-parser/link-parser/link-parser.component.ts
+++ b/src/app/link-parser/link-parser/link-parser.component.ts
@@ -1,26 +1,27 @@
-import { Component, inject } from '@angular/core';
+import { ChangeDetectionStrategy, Component, inject } from '@angular/core';
import { LangService } from '../../shared/data-access/lang.service';
import { MetaTagsService } from '../../shared/data-access/meta-tags.service';
+import { LinkParserService } from '../data-access/link-parser.service';
@Component({
- selector: 'app-link-parser',
- templateUrl: './link-parser.component.html',
- styleUrls: [
- './link-parser.component.scss',
- './link-parser.dual-screen.component.scss'
- ],
- standalone: false
+ selector: 'app-link-parser',
+ templateUrl: './link-parser.component.html',
+ styleUrls: [
+ './link-parser.component.scss',
+ './link-parser.dual-screen.component.scss'
+ ],
+ standalone: false,
+ changeDetection: ChangeDetectionStrategy.OnPush
})
export class LinkParserComponent {
private meta: MetaTagsService = inject(MetaTagsService);
private lang: LangService = inject(LangService)
+ public parser: LinkParserService = inject(LinkParserService)
- /**
- *
- */
constructor() {
this.initMeta()
}
+
initMeta() {
this.meta.setOg()
this.meta.setTwiter()
diff --git a/src/app/link-parser/ui/footer/footer.component.html b/src/app/link-parser/ui/footer/footer.component.html
index a15426b..14d44fd 100644
--- a/src/app/link-parser/ui/footer/footer.component.html
+++ b/src/app/link-parser/ui/footer/footer.component.html
@@ -1,6 +1,7 @@
- {{lang.ph().shortTitle}} {{version}}
-
+
@for (item of social; track $index) {
diff --git a/src/app/link-parser/ui/footer/footer.component.ts b/src/app/link-parser/ui/footer/footer.component.ts
index dc086ff..2128023 100644
--- a/src/app/link-parser/ui/footer/footer.component.ts
+++ b/src/app/link-parser/ui/footer/footer.component.ts
@@ -1,5 +1,6 @@
import { Component, inject } from '@angular/core';
import { LangService } from '../../../shared/data-access/lang.service';
+import { environment } from '../../../../environments/environment';
const SOCIAL_LINKS: any[] = [
{
@@ -36,7 +37,7 @@ const SOCIAL_LINKS: any[] = [
standalone: false
})
export class FooterComponent {
- public readonly version: string = 'v2024.11.14'
+ public readonly version: string = environment.version
public lang: LangService = inject(LangService);
public social: any[] = SOCIAL_LINKS;
diff --git a/src/app/link-parser/ui/parser-form/parser-form.component.html b/src/app/link-parser/ui/parser-form/parser-form.component.html
index 7063ecd..8ca8a13 100644
--- a/src/app/link-parser/ui/parser-form/parser-form.component.html
+++ b/src/app/link-parser/ui/parser-form/parser-form.component.html
@@ -1,18 +1,21 @@
@if (linkParams()) {
-
{{lang.ph().letsgo}}
diff --git a/src/app/link-parser/ui/parser-form/parser-form.component.scss b/src/app/link-parser/ui/parser-form/parser-form.component.scss
index 2abc349..a7fd6ab 100644
--- a/src/app/link-parser/ui/parser-form/parser-form.component.scss
+++ b/src/app/link-parser/ui/parser-form/parser-form.component.scss
@@ -9,16 +9,28 @@
}
app-text-embracer {
- --border-color: #166496;
+ --mono-color-1: #4c93c8;
+ --mono-color-2: #002741;
+ --border-color: var(--mono-color-1);
--border-width: 2px;
color: #ffd60a;
+ --shc: #166496;
font-family: 'Troubleside', sans-serif;
+ font-weight: 900;
font-size: clamp(1rem, 8vw, 5rem);
justify-content: center;
+ -webkit-text-stroke: var(--mono-color-2) var(--border-width);
+ --dot-color: var(--mono-color-1);
+ paint-order: stroke fill;
@media (prefers-color-scheme: light) {
- --back-color: #fff62a;
- color: #166496;
+ --mono-color-1: #166496;
+ --mono-color-2: #eceff2;
+ --shc: var(--mono-color-1);
+ color: var(--mono-color-1);
+ -webkit-text-stroke: var(--mono-color-1) var(--border-width);
+
+ color: var(--mono-color-2);
}
@media (max-width: 1080px) {
@@ -67,7 +79,7 @@ textarea {
color: #166496;
}
-
+
&:focus {
border-color: #166496;
@@ -99,8 +111,9 @@ input[type=url]::placeholder {
.slogan-wrapper {
display: grid;
- grid-template-columns: 1fr auto;
- align-items: center;
+ // grid-template-columns: 1fr auto;
+ place-items: center;
+ gap: 2ch;
grid-column: 2;
}
@@ -110,4 +123,38 @@ input[type=url]::placeholder {
font-style: italic;
opacity: .72;
text-wrap: balance;
+}
+
+.go-btn {
+ display: flex;
+ gap: 1ch;
+ align-items: center;
+ --dot-color: #166496;
+ border: 1px solid var(--dot-color);
+ --stroke: #002741;
+ --bg-1: var(--gl) 0px 0px / 4px 4px;
+ --bg-2: var(--gl) 0px 0px / 3px 3px, var(--gl) 1.5px 1.5px / 3px 3px;
+ --gl: radial-gradient(circle 1px at 0px 0px, var(--dot-color) 1px, transparent 0);
+ background: var(--bg-1);
+
+ text-transform: uppercase;
+ font-weight: bold;
+ // -webkit-text-stroke: 0.5ch var(--stroke);
+ // paint-order: stroke fill;
+
+ box-shadow: var(--flat-shadow-high);
+
+
+ @media (prefers-color-scheme: light) {
+ --dot-color: #166496;
+ color: #166496;
+ --stroke: #eceff2;
+ }
+
+ &:hover,
+ &:focus {
+ --dot-color: #4c93c8;
+ background: var(--bg-2);
+
+ }
}
\ No newline at end of file
diff --git a/src/app/link-parser/ui/parser-form/parser-form.component.ts b/src/app/link-parser/ui/parser-form/parser-form.component.ts
index e8f046b..0a3d7bb 100644
--- a/src/app/link-parser/ui/parser-form/parser-form.component.ts
+++ b/src/app/link-parser/ui/parser-form/parser-form.component.ts
@@ -1,4 +1,4 @@
-import { Component, computed, inject, PLATFORM_ID, signal, Signal, WritableSignal } from '@angular/core';
+import { ChangeDetectionStrategy, Component, computed, inject, PLATFORM_ID, signal, Signal, WritableSignal } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { LangService } from '../../../shared/data-access/lang.service';
import { Base64 } from '../../../shared/utils';
@@ -11,16 +11,14 @@ import { ComickLinkParser } from '../../utils/comick-link-parser';
selector: 'app-parser-form',
templateUrl: './parser-form.component.html',
styleUrl: './parser-form.component.scss',
- standalone: false
+ standalone: false,
+ changeDetection: ChangeDetectionStrategy.OnPush
})
export class ParserFormComponent {
-
private router: Router = inject(Router);
private route: ActivatedRoute = inject(ActivatedRoute);
setts = inject(LinkParserSettingsService)
platformId = inject(PLATFORM_ID)
-
-
link: WritableSignal = signal('');
linkParams: Signal = computed(() => this.parser.parse(this.link()));
linkParams64: Signal = computed(() => {
@@ -31,21 +29,6 @@ export class ParserFormComponent {
};
});
- supportFiles = signal([".zip", ".cbz", ".pdf", ".mobi"])
-
- supportSites = signal([
- "Imgur",
- "Telegra.ph",
- "Reddit",
- "MangaDex",
- "Zenko",
- "Comick",
- "NHentai",
- "Yandere Pools",
- "Blankary",
- "Pixiv"
- ].sort())
-
constructor(public parser: LinkParserService, public lang: LangService) {
this.initParser();
}
@@ -84,8 +67,6 @@ export class ParserFormComponent {
if (!this.linkParams()) { this.link.set('') }
}
-
-
initUrl() {
const routeParamUrl: string | null = this.route.snapshot.paramMap.get('url');
diff --git a/src/app/link-parser/ui/settings/settings.component.html b/src/app/link-parser/ui/settings/settings.component.html
index e52651a..c7d4c9f 100644
--- a/src/app/link-parser/ui/settings/settings.component.html
+++ b/src/app/link-parser/ui/settings/settings.component.html
@@ -1,28 +1,117 @@
-
-
-
-
- 🈚 {{lang.ph().language}}
- {{lang.ph().getByKey(getLangValue(lang.lang()).hintPhraceKey)}}
-
-
{{lang.ph().settingLangDesc}}
-
-
-
-
-
-
-
-
- @if(setts.autoPasteLink()) {
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/app/link-parser/ui/settings/settings.component.scss b/src/app/link-parser/ui/settings/settings.component.scss
index 18c2981..49535c1 100644
--- a/src/app/link-parser/ui/settings/settings.component.scss
+++ b/src/app/link-parser/ui/settings/settings.component.scss
@@ -1,4 +1,4 @@
-:host {
+:host, fieldset {
display: grid;
gap: 2ch;
}
@@ -75,4 +75,15 @@ overflow: hidden;
}
}
}
+}
+
+fieldset {
+ padding: 2ch;
+ border: 1px dashed #16649680;
+ border-radius: var(--r);
+}
+
+input[type=number] {
+ width: 8ch;
+ text-align: center;
}
\ No newline at end of file
diff --git a/src/app/link-parser/ui/settings/settings.component.ts b/src/app/link-parser/ui/settings/settings.component.ts
index 5532b79..5bbbb52 100644
--- a/src/app/link-parser/ui/settings/settings.component.ts
+++ b/src/app/link-parser/ui/settings/settings.component.ts
@@ -1,16 +1,20 @@
-import { Component, WritableSignal, effect, inject, signal } from '@angular/core';
+import { Component, WritableSignal, effect, inject, input, signal } from '@angular/core';
import { LinkParserSettingsService } from '../../data-access/link-parser-settings.service';
import { LangService } from '../../../shared/data-access/lang.service';
+import { FileSettingsService } from '../../../file/data-access/file-settings.service';
+import { VibrationService } from '../../../shared/data-access/vibration.service';
@Component({
- selector: 'app-settings',
- templateUrl: './settings.component.html',
- styleUrl: './settings.component.scss',
- standalone: false
+ selector: 'app-settings',
+ templateUrl: './settings.component.html',
+ styleUrl: './settings.component.scss',
+ standalone: false
})
export class SettingsComponent {
setts = inject(LinkParserSettingsService)
lang = inject(LangService)
+ fileSetts = inject(FileSettingsService)
+ vibro = inject(VibrationService)
getLangValue(lang: string) {
@@ -19,6 +23,44 @@ export class SettingsComponent {
setAutoPasteLink(e: Event) {
this.setts.setAutoPasteLink((e.target as HTMLInputElement).checked)
+
+ this.vibro.vibrateForSettings(this.setts.autoPasteLink())
+
+ }
+
+ setSaveFileToHistory(e: Event) {
+ this.fileSetts.setSaveFileToHistory((e.target as HTMLInputElement).checked)
+
+ this.vibro.vibrateForSettings(this.fileSetts.saveFileToHistory())
+ }
+
+ setCopyFileToHistory(e: Event) {
+ this.fileSetts.setCopyFileToHistory((e.target as HTMLInputElement).checked)
+
+ this.vibro.vibrateForSettings(this.fileSetts.copyFileToHistory())
+ }
+
+ setRetentionTime(e: Event) {
+ this.fileSetts.setRetentionTime(Number((e.target as HTMLInputElement).value))
+
+ this.vibro.vibrate()
+ }
+
+ setStorageLimit(e: Event) {
+ this.fileSetts.setStorageLimit(Number((e.target as HTMLInputElement).value))
+
+ this.vibro.vibrate()
}
+ setVibration(e: Event) {
+ this.vibro.setVibrationOn((e.target as HTMLInputElement).checked)
+
+ this.vibro.vibrateForSettings(this.vibro.vibrationOn())
+ }
+
+ vibrateLangToggle(e: string) {
+ setTimeout(() => {
+ this.vibro.vibrateLangToggle(e)
+ }, 0);
+ }
}
diff --git a/src/app/page-not-found.component.ts b/src/app/page-not-found.component.ts
index 81883dc..c831d04 100644
--- a/src/app/page-not-found.component.ts
+++ b/src/app/page-not-found.component.ts
@@ -3,12 +3,12 @@ import { LangService } from './shared/data-access/lang.service';
import { isPlatformBrowser } from '@angular/common';
@Component({
- selector: 'app-page-not-found',
- template: `
+ selector: 'app-page-not-found',
+ template: `
{{selectedMessage()}}
🏠
`,
- styles: `
+ styles: `
:host {
display: grid;
min-height: 100dvh;
@@ -16,18 +16,40 @@ import { isPlatformBrowser } from '@angular/common';
text-align: center;
}
app-text-embracer {
- --border-color: #166496;
- --border-width: 2px;
- color: #ffd60a;
- margin: auto;
- font-family: 'Troubleside', sans-serif;
- font-size: clamp(1rem, 8vw, 5rem);
- @media (max-aspect-ratio: 1) or (max-width: 640px) {
- font-size: clamp(1rem, 10vw, 8rem);
- }
+ --mono-color-1: #4c93c8;
+ --mono-color-2: #002741;
+ --border-color: var(--mono-color-1);
+ --border-width: 2px;
+ color: #ffd60a;
+ --shc: #166496;
+ font-family: 'Troubleside', sans-serif;
+ font-weight: 900;
+ font-size: clamp(1rem, 16vw, 10rem);
+ justify-content: center;
+ -webkit-text-stroke: var(--mono-color-2) var(--border-width);
+ --dot-color: var(--mono-color-1);
+ paint-order: stroke fill;
+
+ @media (prefers-color-scheme: light) {
+ --mono-color-1: #166496;
+ --mono-color-2: #eceff2;
+ --shc: var(--mono-color-1);
+ color: var(--mono-color-1);
+ -webkit-text-stroke: var(--mono-color-1) var(--border-width);
+
+ color: var(--mono-color-2);
+ }
+
+ @media (max-width: 1080px) {
+ font-size: clamp(1rem, 14vw, 16rem);
}
+
+ @media ((orientation: portrait) and (max-aspect-ratio: 1)) {
+ font-size: clamp(1rem, 20vw, 16rem);
+ }
+}
`,
- standalone: false
+ standalone: false
})
export class PageNotFoundComponent {
platformId = inject(PLATFORM_ID)
diff --git a/src/app/shared/data-access/lang.service.ts b/src/app/shared/data-access/lang.service.ts
index cfc025f..e69a78a 100644
--- a/src/app/shared/data-access/lang.service.ts
+++ b/src/app/shared/data-access/lang.service.ts
@@ -4,6 +4,7 @@ import { Observable, map, of } from 'rxjs';
import { HttpClient } from '@angular/common/http';
import { ViewModeOption } from './viewer.service';
import { DOCUMENT, isPlatformBrowser } from '@angular/common';
+import { VibrationService } from './vibration.service';
const LANG_OPTIONS: ViewModeOption[] = [
{ dir: "rtl", mode: "pages", hintPhraceKey: "english", code: "en", emoji: "🇬🇧" },
@@ -26,6 +27,8 @@ export class LangService {
langOpt = LANG_OPTIONS
platformId = inject(PLATFORM_ID)
private readonly document = inject(DOCUMENT);
+ vibro = inject(VibrationService)
+
lang: WritableSignal = signal(
(!isPlatformBrowser(this.platformId)) ? DEFAULT_LANG :
@@ -48,6 +51,8 @@ export class LangService {
localStorage.setItem(LANG_STORAGE_NAME, lang)
this.updateTranslate();
+
+ this.vibro.vibrateLangToggle(this.lang())
}
updateManifest() {
diff --git a/src/app/shared/data-access/vibration.service.ts b/src/app/shared/data-access/vibration.service.ts
new file mode 100644
index 0000000..88c77c9
--- /dev/null
+++ b/src/app/shared/data-access/vibration.service.ts
@@ -0,0 +1,120 @@
+import { isPlatformServer } from '@angular/common';
+import { computed, inject, Injectable, PLATFORM_ID, Signal, signal, WritableSignal } from '@angular/core';
+
+const DOT = 40; // Коротка вібрація для точки
+const DASH = 80; // Довга вібрація для тире
+const INTRA_LETTER_PAUSE = 64; // Пауза між елементами в одній букві
+const LETTER_PAUSE = 96; // Пауза між літерами
+const WORD_PAUSE = 128; // Пауза між словами
+
+const morseCode = new Map([
+ ['A', ['.', '-']], // .-
+ ['B', ['-', '.', '.', '.']], // -...
+ ['C', ['-', '.', '-', '.']], // -.-.
+ ['D', ['-', '.', '.']], // -..
+ ['E', ['.']], // .
+ ['F', ['.', '.', '-', '.']], // ..-.
+ ['G', ['-', '-', '.']], // --.
+ ['H', ['.', '.', '.', '.']], // ....
+ ['I', ['.', '.']], // ..
+ ['J', ['.', '-', '-', '-']], // .---
+ ['K', ['-', '.', '-']], // -.-
+ ['L', ['.', '-', '.', '.']], // .-..
+ ['M', ['-', '-']], // --
+ ['N', ['-', '.']], // -.
+ ['O', ['-', '-', '-']], // ---
+ ['P', ['.', '-', '-', '.']], // .--.
+ ['Q', ['-', '-', '.', '-']], // --.-
+ ['R', ['.', '-', '.']], // .-.
+ ['S', ['.', '.', '.']], // ...
+ ['T', ['-']], // -
+ ['U', ['.', '.', '-']], // ..-
+ ['V', ['.', '.', '.', '-']], // ...-
+ ['W', ['.', '-', '-']], // .--
+ ['X', ['-', '.', '.', '-']], // -..-
+ ['Y', ['-', '.', '-', '-']], // -.--
+ ['Z', ['-', '-', '.', '.']], // --..
+ ['1', ['.', '-', '-', '-', '-']], // .----
+ ['2', ['.', '.', '-', '-', '-']], // ..---
+ ['3', ['.', '.', '.', '-', '-']], // ...--
+ ['4', ['.', '.', '.', '.', '-']], // ....-
+ ['5', ['.', '.', '.', '.', '.']], // .....
+ ['6', ['-', '.', '.', '.', '.']], // -....
+ ['7', ['-', '-', '.', '.', '.']], // --...
+ ['8', ['-', '-', '-', '.', '.']], // ---..
+ ['9', ['-', '-', '-', '-', '.']], // ----.
+ ['0', ['-', '-', '-', '-', '-']], // -----
+ [' ', []]
+]);
+
+@Injectable({
+ providedIn: 'root'
+})
+export class VibrationService {
+ vibrationOn: WritableSignal = signal(false);
+
+ enabled = computed(() => {
+ return (!isPlatformServer(this._platformId) && ("vibrate" in navigator))
+ })
+
+ private _platformId = inject(PLATFORM_ID)
+
+ constructor() {
+ this.initVibrationOn()
+ }
+
+ initVibrationOn() {
+ if (isPlatformServer(this._platformId)) return;
+
+ const n = Boolean(localStorage.getItem('vibrationOn') == 'true');
+ this.vibrationOn.set(n);
+ }
+
+ setVibrationOn(n: boolean) {
+ if (isPlatformServer(this._platformId)) return;
+
+ this.vibrationOn.set(n);
+ localStorage.setItem('vibrationOn', n.toString())
+ }
+
+ vibrate(pattern: VibratePattern = DOT) {
+
+ if (isPlatformServer(this._platformId) || !this.vibrationOn()) return
+
+ navigator.vibrate(0);
+ navigator.vibrate(pattern)
+ }
+
+ vibrateForSettings = (isEnabled: boolean) => this.vibrate(this.getVibrationPattern(isEnabled ? "ON" : "OFF"));
+
+ vibrateLangToggle = (lang: string) => this.vibrate(this.getVibrationPattern(lang.toUpperCase()))
+
+ getVibrationPattern(text: string) {
+
+ let pattern: VibratePattern = [];
+
+ text.toUpperCase().split('').forEach((char, index, array) => {
+ const morse = morseCode.get(char);
+ if (morse) {
+ morse.forEach((signal, signalIndex) => {
+ pattern.push(signal === '.' ? DOT : DASH); // Вібрація для точки або тире
+ if (signalIndex < morse.length - 1) {
+ pattern.push(INTRA_LETTER_PAUSE); // Пауза між елементами букви
+ }
+ });
+
+ // Пауза між літерами
+ if (index < array.length - 1) {
+ pattern.push(LETTER_PAUSE);
+ }
+ }
+
+ // Пауза між словами
+ if (index < array.length - 1 && array[index + 1] === ' ') {
+ pattern.push(WORD_PAUSE);
+ }
+ });
+
+ return pattern;
+ }
+}
diff --git a/src/app/shared/data-access/viewer.service.ts b/src/app/shared/data-access/viewer.service.ts
index 1323890..a1f26f7 100644
--- a/src/app/shared/data-access/viewer.service.ts
+++ b/src/app/shared/data-access/viewer.service.ts
@@ -26,7 +26,7 @@ export class ViewerService {
public viewModeOption: WritableSignal = signal(VIEV_MODE_OPTIONS[0]);
- nightlight: WritableSignal = signal(0);
+ nightlight: WritableSignal = signal(6500);
keyboard: boolean = (isPlatformBrowser(this.platformId)) && (navigator as any).keyboard;
diff --git a/src/app/shared/pipes/filesize.pipe.ts b/src/app/shared/pipes/filesize.pipe.ts
new file mode 100644
index 0000000..3a42a17
--- /dev/null
+++ b/src/app/shared/pipes/filesize.pipe.ts
@@ -0,0 +1,11 @@
+import { Pipe, PipeTransform } from '@angular/core';
+
+@Pipe({
+ name: 'filesize',
+ standalone: false
+})
+export class FileSizePipe implements PipeTransform {
+ transform(size: number, extension: string = 'MB') {
+ return (size / (1024 * 1024)).toFixed(2) + extension;
+ }
+}
\ No newline at end of file
diff --git a/src/app/shared/shared.module.ts b/src/app/shared/shared.module.ts
index 9149997..3d54611 100644
--- a/src/app/shared/shared.module.ts
+++ b/src/app/shared/shared.module.ts
@@ -23,6 +23,10 @@ import { ViewerFooterComponent } from './ui/viewer/components/viewer-footer/view
import { ViewerHeaderComponent } from './ui/viewer/components/viewer-header/viewer-header.component';
import { MangaPageEvenComponent } from './ui/manga-page/manga-page-even.component';
import { FileChangeComponent } from './ui/file-change/file-change.component';
+import { ChytankaLogoWithTagsComponent } from './ui/chytanka-logo-with-tags/chytanka-logo-with-tags.component';
+import { FileSizePipe } from './pipes/filesize.pipe';
+import { RoughPaperComponent } from './ui/filters/rough-paper/rough-paper.component';
+import { SharpenComponent } from './ui/filters/sharpen/sharpen.component';
@@ -48,13 +52,17 @@ import { FileChangeComponent } from './ui/file-change/file-change.component';
ViewerFooterComponent,
ViewerHeaderComponent,
MangaPageEvenComponent,
- FileChangeComponent
+ FileChangeComponent,
+ ChytankaLogoWithTagsComponent,
+ FileSizePipe
],
imports: [
CommonModule,
FormsModule,
- RouterModule
+ RouterModule,
+ RoughPaperComponent,
+ SharpenComponent
],
- exports: [TruncatePipe, TextEmbracerComponent, ViewerComponent, OverlayComponent, ViewModeBarComponent, MadeInUkraineComponent, DialogComponent, LangToggleComponent, TitleCardComponent, LoadingComponent, SeparatorComponent, FileChangeComponent]
+ exports: [TruncatePipe, TextEmbracerComponent, ViewerComponent, OverlayComponent, ViewModeBarComponent, MadeInUkraineComponent, DialogComponent, LangToggleComponent, TitleCardComponent, LoadingComponent, SeparatorComponent, FileChangeComponent, ChytankaLogoWithTagsComponent, FileSizePipe]
})
export class SharedModule { }
diff --git a/src/app/shared/ui/chytanka-logo-with-tags/chytanka-logo-with-tags.component.html b/src/app/shared/ui/chytanka-logo-with-tags/chytanka-logo-with-tags.component.html
new file mode 100644
index 0000000..f047f7f
--- /dev/null
+++ b/src/app/shared/ui/chytanka-logo-with-tags/chytanka-logo-with-tags.component.html
@@ -0,0 +1,38 @@
+@for (tag of siteTags(); track $index) {
+@let c = getRandomCoordinates();
+@let s = getRandom(0.8, 1.28);
+{{formatTagname(tag)}}
+}
+@for (tag of fileTags(); track $index) {
+@let c = getRandomCoordinates();
+@let s = getRandom(0.8, 1.28);
+{{formatTagname(tag)}}
+}
+
+
+
+
+v{{version}}
\ No newline at end of file
diff --git a/src/app/shared/ui/chytanka-logo-with-tags/chytanka-logo-with-tags.component.scss b/src/app/shared/ui/chytanka-logo-with-tags/chytanka-logo-with-tags.component.scss
new file mode 100644
index 0000000..0aced22
--- /dev/null
+++ b/src/app/shared/ui/chytanka-logo-with-tags/chytanka-logo-with-tags.component.scss
@@ -0,0 +1,132 @@
+:host {
+ position: relative;
+ // overflow: hidden;
+ min-height: 0;
+ min-width: 0;
+ display: grid;
+ place-items: stretch;
+}
+
+#logoPath {
+
+ fill: #000118;
+
+ @media (prefers-color-scheme: light) {
+ fill: #166496;
+ }
+}
+
+.one-bit {
+ filter: url('#oneBit');
+}
+
+.filters {
+ position: fixed;
+ user-select: none;
+ pointer-events: none;
+}
+
+svg,
+img {
+ display: block;
+ min-width: 0;
+ min-height: 0;
+ user-select: none;
+ pointer-events: none;
+}
+
+.version {
+ position: absolute;
+ top: 50%; left: 50%;
+ transform: translate(-50%,-50%);
+ font-weight: bold;
+ text-align: center;
+ opacity: .5;
+ color: #fff;
+
+ @media (prefers-color-scheme: light) {
+ color: #000;
+ }
+
+ a,
+ a:hover {
+ color: inherit;
+ }
+
+ a:hover {
+ text-decoration: underline;
+ }
+}
+
+.tag {
+ z-index: 1;
+ transition: all var(--t) linear;
+ position: absolute;
+ padding: .25ch;
+ text-align: center;
+ border-radius: .25ch;
+ font-size: small;
+ font-family: 'Courier New', Courier, monospace;
+ font-weight: bold;
+ --b: #ffd60a;
+ --f: black;
+ background-color: var(--b);
+ color: var(--f);
+ box-shadow: var(--flat-shadow-medium);
+ list-style: none;
+
+ &:hover {
+ cursor: none;
+ scale: 1.5 !important;
+ z-index: 2;
+ }
+
+ &.reddit {
+ --b: #FF4500;
+ --f: white;
+ }
+
+ &.pixiv {
+ --b: #0091d4;
+ --f: white;
+ }
+
+ &.blankary {
+ --b: #000;
+ --f: white;
+ }
+
+ // &.comick {}
+
+ &.imgur {
+ --b: #1bb76e;
+ }
+
+ &.mangadex {
+ --b: #ff6740;
+ }
+
+ // &.nhentai {}
+
+ &.zenko {
+ --b: #078DEE;
+ --f: white;
+ }
+
+ &.zip {
+ --b: #0074D9;
+ }
+
+ &.cbz {
+ --b: rgb(216, 68, 68);
+ }
+
+ &.pdf {
+ --b: #0074D9;
+ }
+
+ &.mobi {
+ --b: #4CAF50;
+ --f: #FFC107;
+ }
+}
\ No newline at end of file
diff --git a/src/app/shared/ui/chytanka-logo-with-tags/chytanka-logo-with-tags.component.ts b/src/app/shared/ui/chytanka-logo-with-tags/chytanka-logo-with-tags.component.ts
new file mode 100644
index 0000000..5adf6e3
--- /dev/null
+++ b/src/app/shared/ui/chytanka-logo-with-tags/chytanka-logo-with-tags.component.ts
@@ -0,0 +1,33 @@
+import { ChangeDetectionStrategy, Component, inject, input } from '@angular/core';
+import { environment } from '../../../../environments/environment';
+
+type Coordinate = { x: number; y: number };
+
+@Component({
+ selector: 'app-chytanka-logo-with-tags',
+ standalone: false,
+
+ templateUrl: './chytanka-logo-with-tags.component.html',
+ styleUrl: './chytanka-logo-with-tags.component.scss',
+ changeDetection: ChangeDetectionStrategy.OnPush
+})
+export class ChytankaLogoWithTagsComponent {
+ version = environment.version;
+ fileTags = input([])
+ siteTags = input([])
+
+ formatTagname(tagname: string) {
+ return tagname.replaceAll('.', '').toLowerCase()
+ }
+
+ getRandom = (min: number = 10, max: number = 90): number => {
+ return Math.random() * (max - min) + min;
+ };
+
+ getRandomCoordinates(): Coordinate {
+ const x = this.getRandom();
+ const y = this.getRandom();
+
+ return { x: parseFloat(x.toFixed(2)), y: parseFloat(y.toFixed(2)) };
+ }
+}
diff --git a/src/app/shared/ui/dialog/dialog.component.scss b/src/app/shared/ui/dialog/dialog.component.scss
index e32cb60..b4fc055 100644
--- a/src/app/shared/ui/dialog/dialog.component.scss
+++ b/src/app/shared/ui/dialog/dialog.component.scss
@@ -33,6 +33,7 @@ dialog {
.dialog-wrapper {
display: grid;
max-height: 100%;
+ width: 100%;
grid-template: auto 1fr auto / 1fr;
width: min(80vw, 60ch);
}
@@ -90,7 +91,7 @@ dialog {
}
&[open] {
- display: flex;
+ // display: flex;
box-shadow: var(--flat-shadow-high);
}
}
\ No newline at end of file
diff --git a/src/app/shared/ui/file-change/file-change.component.ts b/src/app/shared/ui/file-change/file-change.component.ts
index b75285d..ac3721d 100644
--- a/src/app/shared/ui/file-change/file-change.component.ts
+++ b/src/app/shared/ui/file-change/file-change.component.ts
@@ -1,37 +1,43 @@
-import { Component, HostListener, inject, input, OnInit } from '@angular/core';
+import { Component, HostListener, inject, input, OnInit, PLATFORM_ID } from '@angular/core';
import { FileService } from '../../../file/data-access/file.service';
import { Router } from '@angular/router';
import { FILE_PATH } from '../../../app-routing.module';
import { LangService } from '../../data-access/lang.service';
+import { isPlatformServer } from '@angular/common';
@Component({
- selector: 'app-file-change',
- templateUrl: './file-change.component.html',
- styleUrl: './file-change.component.scss',
- standalone: false
+ selector: 'app-file-change',
+ templateUrl: './file-change.component.html',
+ styleUrl: './file-change.component.scss',
+ standalone: false
})
export class FileChangeComponent implements OnInit {
+ platformId = inject(PLATFORM_ID)
+ fs = inject(FileService)
+ router = inject(Router)
+ lang = inject(LangService)
+
+ accept = input([])
+
+ input: HTMLInputElement | undefined;
+ showDragAndDropZone: boolean = false;
+
ngOnInit(): void {
+ if (isPlatformServer(this.platformId)) return;
+
this.initFileInput();
if ("launchQueue" in window) {
(window as any).launchQueue.setConsumer(async (launchParams: FileSystemFileHandle) => {
- console.log(launchParams);
-
- const file: File = await launchParams.getFile();
+ console.log((launchParams as any).files[0]);
+
+ // const file: File = await launchParams.getFile();
+ const file: File = (launchParams as any).files[0] as File;
this.fileHandler(file)
});
}
}
- // should be input
- // accept = [".zip", ".cbz", 'application/vnd.comicbook+zip', 'application/zip', '.pdf']
- accept = input([])
-
- fs = inject(FileService)
- router = inject(Router)
- lang = inject(LangService)
-
onFileSelected(event: any) {
const file: File = event.target.files[0];
@@ -54,18 +60,16 @@ export class FileChangeComponent implements OnInit {
getRouteType(file: File): string | undefined {
const fileType = file.type || file.name.split('.').pop()?.toLowerCase();
-
+
if (!fileType) return undefined;
-
+
if (fileType.includes('pdf')) return 'pdf';
if (fileType.includes('mobi')) return 'mobi';
if (/zip|cbz/.test(fileType)) return 'zip';
-
+
return undefined;
}
- input = document.createElement('input')
-
initFileInput() {
this.input = document.createElement('input')
@@ -75,12 +79,10 @@ export class FileChangeComponent implements OnInit {
this.input.oninput = (e: Event) => {
this.onFileSelected(e)
}
-
}
openFileDialog() {
-
- this.input.click();
+ if (this.input) this.input.click();
}
@HostListener('window:keydown', ['$event'])
@@ -109,6 +111,4 @@ export class FileChangeComponent implements OnInit {
this.fileHandler(file)
}
-
- showDragAndDropZone: boolean = false;
}
diff --git a/src/app/shared/ui/filters/rough-paper/rough-paper.component.ts b/src/app/shared/ui/filters/rough-paper/rough-paper.component.ts
new file mode 100644
index 0000000..15fe0a4
--- /dev/null
+++ b/src/app/shared/ui/filters/rough-paper/rough-paper.component.ts
@@ -0,0 +1,37 @@
+import { Component, input } from '@angular/core';
+
+@Component({
+ selector: 'fx-rough-paper',
+ imports: [],
+ template: ``,
+ styles: `
+ svg {
+ position: fixed;
+ user-select: none;
+ pointer-events: none;
+ width: 0;
+ height: 0;
+ z-index: -1;
+ }
+`
+})
+export class RoughPaperComponent {
+ baseFrequency = input(0.04)
+ numOctaves = input(5)
+ azimuth = input(45)
+ elevation = input(100)
+ lightingColor = input("#ffffff")
+}
diff --git a/src/app/shared/ui/filters/sharpen/sharpen.component.ts b/src/app/shared/ui/filters/sharpen/sharpen.component.ts
new file mode 100644
index 0000000..0f65185
--- /dev/null
+++ b/src/app/shared/ui/filters/sharpen/sharpen.component.ts
@@ -0,0 +1,25 @@
+import { Component } from '@angular/core';
+
+@Component({
+ selector: 'fx-sharpen',
+ imports: [],
+ template: `
+ `,
+ styles: `
+ svg {
+ position: fixed;
+ user-select: none;
+ pointer-events: none;
+ width: 0;
+ height: 0;
+ z-index: -1;
+ }
+ `
+})
+export class SharpenComponent {
+
+}
diff --git a/src/app/shared/ui/manga-page/manga-page-even.component.scss b/src/app/shared/ui/manga-page/manga-page-even.component.scss
index 03e7dd8..e49a26c 100644
--- a/src/app/shared/ui/manga-page/manga-page-even.component.scss
+++ b/src/app/shared/ui/manga-page/manga-page-even.component.scss
@@ -25,7 +25,7 @@ section {
display: grid;
place-items: center;
overflow: hidden;
-
+ background: var(--bg-1);
counter-increment: read-order;
&:nth-child(1),
diff --git a/src/app/shared/ui/manga-page/manga-page.component.scss b/src/app/shared/ui/manga-page/manga-page.component.scss
index 1c603bf..5b9588a 100644
--- a/src/app/shared/ui/manga-page/manga-page.component.scss
+++ b/src/app/shared/ui/manga-page/manga-page.component.scss
@@ -21,6 +21,7 @@ section {
place-items: center;
overflow: hidden;
counter-increment: read-order;
+ background: var(--bg-1);
&::after {
content: counter(read-order);
diff --git a/src/app/shared/ui/pages-indicator/pages-indicator.component.ts b/src/app/shared/ui/pages-indicator/pages-indicator.component.ts
index 30cd313..f3e275b 100644
--- a/src/app/shared/ui/pages-indicator/pages-indicator.component.ts
+++ b/src/app/shared/ui/pages-indicator/pages-indicator.component.ts
@@ -30,7 +30,7 @@ export class PagesIndicatorComponent {
if(this.activeIndexs().length ==0) return []
const totalPages = this.images().length
const currentPage = Math.max(...this.activeIndexs().map(v => v + 1))
- console.log(currentPage);
+ // console.log(currentPage);
return pairPagination({ totalPages, currentPage }) as number[]
})
diff --git a/src/app/shared/ui/text-embracer/text-embracer.component.html b/src/app/shared/ui/text-embracer/text-embracer.component.html
index 0bd8c04..2875690 100644
--- a/src/app/shared/ui/text-embracer/text-embracer.component.html
+++ b/src/app/shared/ui/text-embracer/text-embracer.component.html
@@ -1,3 +1,14 @@
@for(item of letters(); track item) {
-{{item}}
-}
\ No newline at end of file
+{{item}}
+}
+
+
\ No newline at end of file
diff --git a/src/app/shared/ui/text-embracer/text-embracer.component.scss b/src/app/shared/ui/text-embracer/text-embracer.component.scss
index 9b18765..f7ad1b4 100644
--- a/src/app/shared/ui/text-embracer/text-embracer.component.scss
+++ b/src/app/shared/ui/text-embracer/text-embracer.component.scss
@@ -3,6 +3,7 @@
--border-width: 1px;
--border-style: solid;
--back-color: transparent;
+ --shc: var(--border-color);
margin: 0;
text-transform: uppercase;
text-align: center;
@@ -11,7 +12,8 @@
display: flex;
gap: .1ch;
- & > span {
+ &>span {
+ text-box: ex alphabetic;
aspect-ratio: 2/3;
display: grid;
border: var(--border-width) var(--border-style) var(--border-color);
@@ -19,7 +21,26 @@
height: 1.5lh;
place-content: center;
background-color: var(--back-color);
- --shc: var(--border-color);
- text-shadow: 0.025ch .025ch var(--shc), 0.05ch .05ch var(--shc), 0.075ch .075ch var(--shc), 0.1ch .1ch var(--shc);
+ --bg-1: var(--gl) 0px 0px / 4px 4px;
+ --bg-2: var(--gl) 0px 0px / 3px 3px, var(--gl) 1.5px 1.5px / 3px 3px;
+ --gl: radial-gradient(circle 1px at 0px 0px, var(--dot-color) 1px, transparent 0);
+ background: var(--bg-1);
+
+ text-shadow: 0.025ch .025ch var(--shc),
+ 0.05ch .05ch var(--shc),
+ 0.075ch .075ch var(--shc),
+ 0.1ch .1ch var(--shc);
+ transition: all var(--t) ease-in-out;
+
+ &:hover {
+ background: var(--bg-2);
+ }
+
+ span {
+ left: -0.05ch;
+ top: -0.05ch;
+ position: relative;
+ // filter: url("#oneBitShadow");
+ }
}
}
\ No newline at end of file
diff --git a/src/app/shared/ui/title-card/title-card.component.html b/src/app/shared/ui/title-card/title-card.component.html
index e0a8355..90a5ffd 100644
--- a/src/app/shared/ui/title-card/title-card.component.html
+++ b/src/app/shared/ui/title-card/title-card.component.html
@@ -1,12 +1,27 @@
+@let isLink = (value?.site && value?.post_id);
+
+@if (isLink) {
-
![]()
+
-
- {{value?.site}}
+
+ {{getSiteTag()}}
+ @if(value.size){ {{value.size | filesize}} }
+ @if(value.page && value.pages){ {{value.page}}/{{value.pages}} }
+
{{value.title ?? lang.ph().untitled}}
+
+} @else {
+
+
![]()
+
+
+ {{value.title ?? lang.ph().untitled}} @if(value.page && value.pages){ {{value.page}}/{{value.pages}} }
+
+}
{{value.updated | date : 'short'}}
\ No newline at end of file
diff --git a/src/app/shared/ui/title-card/title-card.component.scss b/src/app/shared/ui/title-card/title-card.component.scss
index e7cc06d..254046a 100644
--- a/src/app/shared/ui/title-card/title-card.component.scss
+++ b/src/app/shared/ui/title-card/title-card.component.scss
@@ -52,4 +52,5 @@
background-color: #166496;
color: white;
filter: grayscale(1);
+ text-transform: uppercase;
}
\ No newline at end of file
diff --git a/src/app/shared/ui/title-card/title-card.component.ts b/src/app/shared/ui/title-card/title-card.component.ts
index 676c83e..436b47f 100644
--- a/src/app/shared/ui/title-card/title-card.component.ts
+++ b/src/app/shared/ui/title-card/title-card.component.ts
@@ -2,11 +2,11 @@ import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output, inject
import { LangService } from '../../data-access/lang.service';
@Component({
- selector: 'app-title-card',
- templateUrl: './title-card.component.html',
- styleUrl: './title-card.component.scss',
- changeDetection: ChangeDetectionStrategy.OnPush,
- standalone: false
+ selector: 'app-title-card',
+ templateUrl: './title-card.component.html',
+ styleUrl: './title-card.component.scss',
+ changeDetection: ChangeDetectionStrategy.OnPush,
+ standalone: false
})
export class TitleCardComponent {
@Input() value: any = {};
@@ -18,4 +18,13 @@ export class TitleCardComponent {
}
lang = inject(LangService);
+
+ getRouteLink() {
+ const foo = (this.value?.site instanceof Array) ? this.value?.site : [this.value?.site]
+ return ['', ...foo, this.value?.post_id]
+ }
+
+ getSiteTag() {
+ return (this.value?.site instanceof Array) ? this.value?.site.pop() : this.value?.site
+ }
}
diff --git a/src/app/shared/ui/viewer/components/hint-page/hint-page.component.html b/src/app/shared/ui/viewer/components/hint-page/hint-page.component.html
index 4d6a996..6f69ae1 100644
--- a/src/app/shared/ui/viewer/components/hint-page/hint-page.component.html
+++ b/src/app/shared/ui/viewer/components/hint-page/hint-page.component.html
@@ -1,7 +1,6 @@
-
-
-
\ No newline at end of file
diff --git a/src/app/shared/ui/viewer/components/hint-page/hint-page.component.scss b/src/app/shared/ui/viewer/components/hint-page/hint-page.component.scss
index 0b8405c..1e3f532 100644
--- a/src/app/shared/ui/viewer/components/hint-page/hint-page.component.scss
+++ b/src/app/shared/ui/viewer/components/hint-page/hint-page.component.scss
@@ -14,8 +14,9 @@ svg {
text {
text-anchor: middle;
- font-family: 'Troubleside', monospace;
+ font-family: monospace;
direction: ltr;
+ text-transform: uppercase;
}
&.abs {
@@ -35,5 +36,6 @@ svg {
font-style: italic;
opacity: .8;
font-family: 'EB Garamond';
+ text-transform: unset;
}
}
\ No newline at end of file
diff --git a/src/app/shared/ui/viewer/viewer.component.html b/src/app/shared/ui/viewer/viewer.component.html
index 3c36e46..edc75f9 100644
--- a/src/app/shared/ui/viewer/viewer.component.html
+++ b/src/app/shared/ui/viewer/viewer.component.html
@@ -126,5 +126,7 @@
}
@defer{
-
+
+
+
}
\ No newline at end of file
diff --git a/src/app/shared/ui/viewer/viewer.component.ts b/src/app/shared/ui/viewer/viewer.component.ts
index 61455c6..dd0119f 100644
--- a/src/app/shared/ui/viewer/viewer.component.ts
+++ b/src/app/shared/ui/viewer/viewer.component.ts
@@ -1,4 +1,4 @@
-import { AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, HostListener, Input, PLATFORM_ID, Signal, ViewChild, WritableSignal, computed, inject, signal } from '@angular/core';
+import { AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, HostListener, Input, PLATFORM_ID, Signal, ViewChild, WritableSignal, computed, inject, output, signal } from '@angular/core';
import { CompositionEpisode } from '../../../@site-modules/@common-read';
import { ViewerService, DomManipulationService } from '../../data-access';
import { ActivatedRoute, Router } from '@angular/router';
@@ -16,22 +16,24 @@ const CHTNK_LIST_RESPONCE_EVENT_NAME = 'listresponse'
const CHTNK_LIST_REQUEST_EVENT_NAME = 'listrequest'
@Component({
- selector: 'app-viewer',
- templateUrl: './viewer.component.html',
- styleUrls: [
- './viewer.component.scss',
- './viewer.pages.component.scss',
- './viewer.long.component.scss',
- '../../../shared/ui/@styles/details.scss',
- '../../../shared/ui/@styles/input-group.scss'
- ],
- changeDetection: ChangeDetectionStrategy.OnPush,
- standalone: false
+ selector: 'app-viewer',
+ templateUrl: './viewer.component.html',
+ styleUrls: [
+ './viewer.component.scss',
+ './viewer.pages.component.scss',
+ './viewer.long.component.scss',
+ '../../../shared/ui/@styles/details.scss',
+ '../../../shared/ui/@styles/input-group.scss'
+ ],
+ changeDetection: ChangeDetectionStrategy.OnPush,
+ standalone: false
})
export class ViewerComponent implements AfterViewInit {
readonly separator: string = '│'
showNsfw: WritableSignal = signal(false);
+ pagechange = output<{ total: number, current: number[] }>()
+
@Input() episode: CompositionEpisode | undefined = undefined;
@Input() playlist: Playlist = [];
@Input() playlistLink: string = "";
@@ -129,18 +131,18 @@ export class ViewerComponent implements AfterViewInit {
}
// this.showOverlay = false;
- console.log();
-
+ // console.log();
+
// if (JSON.stringify(this.activeIndexs()) !== JSON.stringify(activeIndxs))
- this.activeIndexs.set(activeIndxs);
+ this.activeIndexs.set(activeIndxs);
+ const total = this.episode?.images.length
+ const current = activeIndxs.map(i => i + 1)
+ this.embedHelper.postMessage({ total, current }, CHTNK_CHANGE_PAGE_EVENT_NAME);
- this.embedHelper.postMessage({
- total: this.episode?.images.length,
- current: activeIndxs.map(i => i + 1)
- }, CHTNK_CHANGE_PAGE_EVENT_NAME);
+ this.pagechange.emit({ total: Number(total), current })
}
diff --git a/src/app/shared/ui/warm-control/warm-control.component.html b/src/app/shared/ui/warm-control/warm-control.component.html
index ef022ab..f724312 100644
--- a/src/app/shared/ui/warm-control/warm-control.component.html
+++ b/src/app/shared/ui/warm-control/warm-control.component.html
@@ -1,4 +1,7 @@
- -->
+
+
\ No newline at end of file
diff --git a/src/app/shared/ui/warm-filter/warm-filter.component.html b/src/app/shared/ui/warm-filter/warm-filter.component.html
index 7ebb324..d687dfa 100644
--- a/src/app/shared/ui/warm-filter/warm-filter.component.html
+++ b/src/app/shared/ui/warm-filter/warm-filter.component.html
@@ -1,5 +1,5 @@
\ No newline at end of file
diff --git a/src/app/shared/ui/warm-filter/warm-filter.component.ts b/src/app/shared/ui/warm-filter/warm-filter.component.ts
index 612315d..3ad3e57 100644
--- a/src/app/shared/ui/warm-filter/warm-filter.component.ts
+++ b/src/app/shared/ui/warm-filter/warm-filter.component.ts
@@ -1,15 +1,54 @@
-import { ChangeDetectionStrategy, Component, HostBinding, Input } from '@angular/core';
+import { ChangeDetectionStrategy, Component, computed, HostBinding, input, Input, signal } from '@angular/core';
@Component({
- selector: 'app-warm-filter',
- templateUrl: './warm-filter.component.html',
- styleUrl: './warm-filter.component.scss',
- changeDetection: ChangeDetectionStrategy.OnPush,
- standalone: false
+ selector: 'app-warm-filter',
+ templateUrl: './warm-filter.component.html',
+ styleUrl: './warm-filter.component.scss',
+ changeDetection: ChangeDetectionStrategy.OnPush,
+ standalone: false
})
export class WarmFilterComponent {
- @Input()
- @HostBinding('style.opacity')
- value: number = 0;
+ kelvin = input(5000)
+
+ matrixKelvin = computed(() => {
+ const { r, g, b } = this.kelvinToRgbMatrix(this.kelvin())
+
+ return `${r} 0 0 0 0 0 ${g} 0 0 0 0 0 ${b} 0 0 0 0 0 1 0`
+ })
+
+
+
+
+ kelvinToRgbMatrix(kelvin: number): { r: number, g: number, b: number } {
+ if (kelvin < 1000 || kelvin > 6500) {
+ throw new Error("Kelvin value must be between 1000 and 6500.");
+ }
+
+ // Нормалізація значення K
+ const temperature = kelvin / 100;
+
+ // Розрахунок червоного компонента
+ const red = temperature <= 66
+ ? 255
+ : Math.min(255, 329.698727446 * Math.pow(temperature - 60, -0.1332047592));
+
+ // Розрахунок зеленого компонента
+ const green = temperature <= 66
+ ? Math.min(255, 99.4708025861 * Math.log(temperature) - 161.1195681661)
+ : Math.min(255, 288.1221695283 * Math.pow(temperature - 60, -0.0755148492));
+
+ // Розрахунок синього компонента
+ const blue = temperature >= 66
+ ? 255
+ : temperature <= 19
+ ? 0
+ : Math.min(255, 138.5177312231 * Math.log(temperature - 10) - 305.0447927307);
+
+ // Перетворення значень у діапазон [0, 1] для фільтра feColorMatrix
+ const normalize = (value: number) => value / 255;
+
+ return { r: normalize(red), g: normalize(green), b: normalize(blue) };
+ }
+
}
diff --git a/src/app/shared/utils/phrases.ts b/src/app/shared/utils/phrases.ts
index 76a4f5a..dd9e0ab 100644
--- a/src/app/shared/utils/phrases.ts
+++ b/src/app/shared/utils/phrases.ts
@@ -1,7 +1,8 @@
export class Phrases {
title: string = "Chytanka — read it easily and comfortably!";
shortTitle: string = "Chytanka";
- enterLink: string = "Enter link to episode on ";
+ enterLink: string = "Enter link to episode on the supported sites";
+ orOpenFile = "or open file"
slogan: string = "and read it easily and comfortably!";
letsgo: string = "Let's go"
dataLoadErr: string = "Data loading error. Please try again later."
diff --git a/src/app/viewer/components/page/page.component.html b/src/app/viewer/components/page/page.component.html
new file mode 100644
index 0000000..95a0b70
--- /dev/null
+++ b/src/app/viewer/components/page/page.component.html
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/app/viewer/components/page/page.component.scss b/src/app/viewer/components/page/page.component.scss
new file mode 100644
index 0000000..e69de29
diff --git a/src/app/viewer/components/page/page.component.ts b/src/app/viewer/components/page/page.component.ts
new file mode 100644
index 0000000..8ddb695
--- /dev/null
+++ b/src/app/viewer/components/page/page.component.ts
@@ -0,0 +1,9 @@
+import { Component, input } from '@angular/core';
+
+@Component({
+ selector: 'chtnk-page',
+ imports: [],
+ templateUrl: './page.component.html',
+ styleUrl: './page.component.scss'
+})
+export class PageComponent {}
diff --git a/src/app/viewer/components/pages/pages.component.html b/src/app/viewer/components/pages/pages.component.html
new file mode 100644
index 0000000..95a0b70
--- /dev/null
+++ b/src/app/viewer/components/pages/pages.component.html
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/app/viewer/components/pages/pages.component.scss b/src/app/viewer/components/pages/pages.component.scss
new file mode 100644
index 0000000..4f27991
--- /dev/null
+++ b/src/app/viewer/components/pages/pages.component.scss
@@ -0,0 +1,20 @@
+:host.right-to-left,
+:host.left-to-right {
+ display: flex;
+ overflow-x: auto;
+ overflow-y: hidden;
+ scroll-behavior: smooth;
+ scroll-snap-type: x mandatory;
+ scrollbar-width: none;
+ user-select: none;
+ touch-action: pan-x;
+}
+
+:host.right-to-left,
+:host.vertical {
+ direction: rtl;
+}
+
+:host.left-to-right {
+ direction: ltr;
+}
\ No newline at end of file
diff --git a/src/app/viewer/components/pages/pages.component.ts b/src/app/viewer/components/pages/pages.component.ts
new file mode 100644
index 0000000..10ab5a0
--- /dev/null
+++ b/src/app/viewer/components/pages/pages.component.ts
@@ -0,0 +1,11 @@
+import { Component } from '@angular/core';
+
+@Component({
+ selector: 'chtnk-pages',
+ imports: [],
+ templateUrl: './pages.component.html',
+ styleUrl: './pages.component.scss'
+})
+export class PagesComponent {
+
+}
diff --git a/src/app/viewer/viewer.component.html b/src/app/viewer/viewer.component.html
new file mode 100644
index 0000000..110eb08
--- /dev/null
+++ b/src/app/viewer/viewer.component.html
@@ -0,0 +1,20 @@
+@let pages = episode()?.images ?? [];
+
+
+ 🌻📖
+ @for (page of pages; track $index) {
+
+ @defer (on viewport) {
+
+
+
+ } @placeholder {
+
+ }
+
+ }
+
+ 🌻📚📖📕📘📗📙📓📒
+ @if (pages.length % 2 != 0) { }
+
+
diff --git a/src/app/viewer/viewer.component.scss b/src/app/viewer/viewer.component.scss
new file mode 100644
index 0000000..e2c8a1a
--- /dev/null
+++ b/src/app/viewer/viewer.component.scss
@@ -0,0 +1,32 @@
+:host {
+ display: grid;
+ height: 100vh;
+ height: 100dvh;
+ position: relative;
+ align-content: center;
+}
+
+chtnk-page {
+ display: block;
+ width: 50%;
+ flex: 1 0 auto;
+ position: relative;
+}
+
+chtnk-page:nth-child(odd) {
+ scroll-snap-align: start;
+}
+
+img {
+ max-height: 100%;
+ max-width: 100%;
+ display: block;
+}
+
+.right-to-left chtnk-page:nth-child(odd) {
+ direction: ltr;
+}
+
+.left-to-right chtnk-page:nth-child(odd) {
+ direction: rtl;
+}
diff --git a/src/app/viewer/viewer.component.ts b/src/app/viewer/viewer.component.ts
new file mode 100644
index 0000000..7fc1f8e
--- /dev/null
+++ b/src/app/viewer/viewer.component.ts
@@ -0,0 +1,24 @@
+import { Component, input, signal } from '@angular/core';
+import { CompositionEpisode } from '../@site-modules/@common-read';
+import { PageComponent } from "./components/page/page.component";
+import { PagesComponent } from "./components/pages/pages.component";
+
+@Component({
+ standalone: true,
+ selector: 'chtnk-viewer',
+ templateUrl: './viewer.component.html',
+ styleUrl: './viewer.component.scss',
+ imports: [PageComponent, PagesComponent],
+ host: {
+ '[class.panels]': 'panels()',
+ '(click)': 'tooglePanels()',
+ }
+})
+export class ViewerComponent {
+ panels = signal(true);
+ episode = input();
+
+ tooglePanels() {
+ this.panels.update(v => !v);
+ }
+}
diff --git a/src/assets/icons/icon-512x512.svg b/src/assets/icons/icon-512x512.svg
index bea1afa..dff6029 100755
--- a/src/assets/icons/icon-512x512.svg
+++ b/src/assets/icons/icon-512x512.svg
@@ -1,34 +1,13 @@
-
+
\ No newline at end of file
diff --git a/src/assets/langs/uk.json b/src/assets/langs/uk.json
index bcf2766..80ce579 100644
--- a/src/assets/langs/uk.json
+++ b/src/assets/langs/uk.json
@@ -1,7 +1,8 @@
{
"title": "Читанка — читай легко і комфортно!",
"shortTitle": "Читанка",
- "enterLink": "Введи посилання на епізод з ",
+ "enterLink": "Введи посилання на епізод з сайтів, що підтримуютсья",
+ "orOpenFile":" або відкрий файл",
"slogan": "і читай легко та зручно!",
"letsgo": "Вйо до",
"dataLoadErr": "Помилка завантаження даних. Будь ласка, спробуйте пізніше.",
diff --git a/src/assets/new-logo.svg b/src/assets/new-logo.svg
new file mode 100644
index 0000000..f19a087
--- /dev/null
+++ b/src/assets/new-logo.svg
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/src/assets/no-image.svg b/src/assets/no-image.svg
new file mode 100644
index 0000000..b2c86ee
--- /dev/null
+++ b/src/assets/no-image.svg
@@ -0,0 +1,8 @@
+
+
+
+
+
+ Waiting for the pixels to align
+
+
diff --git a/src/environments/environment.development.ts b/src/environments/environment.development.ts
index c1bf151..deb7bc4 100644
--- a/src/environments/environment.development.ts
+++ b/src/environments/environment.development.ts
@@ -1,6 +1,8 @@
const PROXY = `https://proxy.chytanka.ink/api?url=`
export const environment = {
+ version: "0.14.31-2025.5.3",
+ prod: false,
proxy: PROXY,
blankaryoHost: `https://blankary.com/page/`,
imgurHost: 'https://api.imgur.com/3/album/',
diff --git a/src/environments/environment.ts b/src/environments/environment.ts
index c1bf151..33ab2fb 100644
--- a/src/environments/environment.ts
+++ b/src/environments/environment.ts
@@ -1,6 +1,8 @@
const PROXY = `https://proxy.chytanka.ink/api?url=`
export const environment = {
+ version: "0.14.31-2025.5.3",
+ prod: true,
proxy: PROXY,
blankaryoHost: `https://blankary.com/page/`,
imgurHost: 'https://api.imgur.com/3/album/',
diff --git a/src/favicon.ico b/src/favicon.ico
index 4d06197..bf63ddf 100644
Binary files a/src/favicon.ico and b/src/favicon.ico differ
diff --git a/src/index.html b/src/index.html
index 33dc1a6..7f5e648 100644
--- a/src/index.html
+++ b/src/index.html
@@ -45,11 +45,11 @@
-
+
⏳ loading...
-
+
diff --git a/src/server.ts b/src/server.ts
index 0f3612e..e2091ae 100644
--- a/src/server.ts
+++ b/src/server.ts
@@ -53,7 +53,7 @@ app.use('/**', (req, res, next) => {
* Start the server if this module is the main entry point.
* The server listens on the port defined by the `PORT` environment variable, or defaults to 4000.
*/
-if (isMainModule(import.meta.url)) {
+if (isMainModule(import.meta.url) || process.env['FORCE_START']) {
const port = process.env['PORT'] || 4000;
app.listen(port, () => {
console.log(`Node Express server listening on http://localhost:${port}`);
diff --git a/src/styles.scss b/src/styles.scss
index b79a499..171d819 100644
--- a/src/styles.scss
+++ b/src/styles.scss
@@ -5,10 +5,14 @@
}
:root {
+ // filter: grayscale(1);
--blur: 1ch;
--t: 133.333334ms; //266.666667ms; //calc(1s / 60 * 16);
--surface: hsl(203.44 8% 16%);
--text: hsl(200 5% 80%);
+ --dot-color: #000;
+ --bg-1: var(--gl) 0px 0px / 4px 4px;
+ --gl: radial-gradient(circle 1px at 0px 0px, var(--dot-color) 1px, transparent 0);
color-scheme: light dark;
background-color: var(--surface);
color: var(--text);
@@ -167,20 +171,29 @@ a[target="_blank"]:after {
}
:root {
- --shc: #a8923e; //rgb(0, 39, 65);
+ --shc: #ffd60a; //rgb(0, 39, 65);
+ --avarage-l: 0.48;
+ --avarage-l-2: 0.36;
@media (prefers-color-scheme: light) {
- --shc: #74b5e8;
+ --shc: #166496;
}
& {
- --flat-shadow-high: 1px 1px var(--shc),
- 2px 2px var(--shc),
- 3px 3px 0 -1px var(--shc),
- 4px 4px 0 -1px var(--shc);
+ --shadow-color: oklch(from var(--shc) var(--avarage-l-2) c h);
+ --shadow-distance: .25rem;
+ --shadow-distance-2: .5rem;
- --flat-shadow-medium: 1px 1px var(--shc),
- 2px 2px var(--shc);
+ // --flat-shadow-high: 1px 1px var(--shc),
+ // 2px 2px var(--shc),
+ // 3px 3px 0 -1px var(--shc),
+ // 4px 4px 0 -1px var(--shc);
+
+ // --flat-shadow-medium: 1px 1px var(--shc),
+ // 2px 2px var(--shc);
+
+ --flat-shadow-medium: 1px 1px var(--surface), var(--shadow-distance) var(--shadow-distance) var(--shadow-color);
+ --flat-shadow-high: 1px 1px var(--surface), var(--shadow-distance-2) var(--shadow-distance-2) var(--shadow-color);
}
}