wip reader migration
This commit is contained in:
1
frontend/.gitignore
vendored
1
frontend/.gitignore
vendored
@@ -1 +1,2 @@
|
|||||||
node_modules
|
node_modules
|
||||||
|
dist
|
||||||
|
|||||||
@@ -9,6 +9,8 @@
|
|||||||
"ajv": "^8.18.0",
|
"ajv": "^8.18.0",
|
||||||
"axios": "^1.13.6",
|
"axios": "^1.13.6",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
|
"epubjs": "^0.3.93",
|
||||||
|
"nosleep.js": "^0.12.0",
|
||||||
"orval": "8.5.3",
|
"orval": "8.5.3",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
"react-dom": "^19.0.0",
|
"react-dom": "^19.0.0",
|
||||||
@@ -341,6 +343,8 @@
|
|||||||
|
|
||||||
"@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="],
|
"@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="],
|
||||||
|
|
||||||
|
"@types/localforage": ["@types/localforage@0.0.34", "", { "dependencies": { "localforage": "*" } }, "sha512-tJxahnjm9dEI1X+hQSC5f2BSd/coZaqbIl1m3TCl0q9SVuC52XcXfV0XmoCU1+PmjyucuVITwoTnN8OlTbEXXA=="],
|
||||||
|
|
||||||
"@types/node": ["@types/node@25.5.0", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw=="],
|
"@types/node": ["@types/node@25.5.0", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw=="],
|
||||||
|
|
||||||
"@types/react": ["@types/react@19.2.14", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w=="],
|
"@types/react": ["@types/react@19.2.14", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w=="],
|
||||||
@@ -385,6 +389,8 @@
|
|||||||
|
|
||||||
"@vitest/utils": ["@vitest/utils@4.1.0", "", { "dependencies": { "@vitest/pretty-format": "4.1.0", "convert-source-map": "^2.0.0", "tinyrainbow": "^3.0.3" } }, "sha512-XfPXT6a8TZY3dcGY8EdwsBulFCIw+BeeX0RZn2x/BtiY/75YGh8FeWGG8QISN/WhaqSrE2OrlDgtF8q5uhOTmw=="],
|
"@vitest/utils": ["@vitest/utils@4.1.0", "", { "dependencies": { "@vitest/pretty-format": "4.1.0", "convert-source-map": "^2.0.0", "tinyrainbow": "^3.0.3" } }, "sha512-XfPXT6a8TZY3dcGY8EdwsBulFCIw+BeeX0RZn2x/BtiY/75YGh8FeWGG8QISN/WhaqSrE2OrlDgtF8q5uhOTmw=="],
|
||||||
|
|
||||||
|
"@xmldom/xmldom": ["@xmldom/xmldom@0.7.13", "", {}, "sha512-lm2GW5PkosIzccsaZIz7tp8cPADSIlIHWDFTR1N0SzfinhhYgeIQjFMz4rYzanCScr3DqQLeomUDArp6MWKm+g=="],
|
||||||
|
|
||||||
"acorn": ["acorn@8.16.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw=="],
|
"acorn": ["acorn@8.16.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw=="],
|
||||||
|
|
||||||
"acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="],
|
"acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="],
|
||||||
@@ -487,6 +493,10 @@
|
|||||||
|
|
||||||
"cookie": ["cookie@1.1.1", "", {}, "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ=="],
|
"cookie": ["cookie@1.1.1", "", {}, "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ=="],
|
||||||
|
|
||||||
|
"core-js": ["core-js@3.49.0", "", {}, "sha512-es1U2+YTtzpwkxVLwAFdSpaIMyQaq0PBgm3YD1W3Qpsn1NAmO3KSgZfu+oGSWVu6NvLHoHCV/aYcsE5wiB7ALg=="],
|
||||||
|
|
||||||
|
"core-util-is": ["core-util-is@1.0.3", "", {}, "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ=="],
|
||||||
|
|
||||||
"cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="],
|
"cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="],
|
||||||
|
|
||||||
"css-tree": ["css-tree@3.2.1", "", { "dependencies": { "mdn-data": "2.27.1", "source-map-js": "^1.2.1" } }, "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA=="],
|
"css-tree": ["css-tree@3.2.1", "", { "dependencies": { "mdn-data": "2.27.1", "source-map-js": "^1.2.1" } }, "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA=="],
|
||||||
@@ -497,6 +507,8 @@
|
|||||||
|
|
||||||
"csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="],
|
"csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="],
|
||||||
|
|
||||||
|
"d": ["d@1.0.2", "", { "dependencies": { "es5-ext": "^0.10.64", "type": "^2.7.2" } }, "sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw=="],
|
||||||
|
|
||||||
"data-urls": ["data-urls@7.0.0", "", { "dependencies": { "whatwg-mimetype": "^5.0.0", "whatwg-url": "^16.0.0" } }, "sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA=="],
|
"data-urls": ["data-urls@7.0.0", "", { "dependencies": { "whatwg-mimetype": "^5.0.0", "whatwg-url": "^16.0.0" } }, "sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA=="],
|
||||||
|
|
||||||
"data-view-buffer": ["data-view-buffer@1.0.2", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "is-data-view": "^1.0.2" } }, "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ=="],
|
"data-view-buffer": ["data-view-buffer@1.0.2", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "is-data-view": "^1.0.2" } }, "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ=="],
|
||||||
@@ -535,6 +547,8 @@
|
|||||||
|
|
||||||
"entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="],
|
"entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="],
|
||||||
|
|
||||||
|
"epubjs": ["epubjs@0.3.93", "", { "dependencies": { "@types/localforage": "0.0.34", "@xmldom/xmldom": "^0.7.5", "core-js": "^3.18.3", "event-emitter": "^0.3.5", "jszip": "^3.7.1", "localforage": "^1.10.0", "lodash": "^4.17.21", "marks-pane": "^1.0.9", "path-webpack": "0.0.3" } }, "sha512-c06pNSdBxcXv3dZSbXAVLE1/pmleRhOT6mXNZo6INKmvuKpYB65MwU/lO7830czCtjIiK9i+KR+3S+p0wtljrw=="],
|
||||||
|
|
||||||
"es-abstract": ["es-abstract@1.24.1", "", { "dependencies": { "array-buffer-byte-length": "^1.0.2", "arraybuffer.prototype.slice": "^1.0.4", "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.4", "data-view-buffer": "^1.0.2", "data-view-byte-length": "^1.0.2", "data-view-byte-offset": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "es-set-tostringtag": "^2.1.0", "es-to-primitive": "^1.3.0", "function.prototype.name": "^1.1.8", "get-intrinsic": "^1.3.0", "get-proto": "^1.0.1", "get-symbol-description": "^1.1.0", "globalthis": "^1.0.4", "gopd": "^1.2.0", "has-property-descriptors": "^1.0.2", "has-proto": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "internal-slot": "^1.1.0", "is-array-buffer": "^3.0.5", "is-callable": "^1.2.7", "is-data-view": "^1.0.2", "is-negative-zero": "^2.0.3", "is-regex": "^1.2.1", "is-set": "^2.0.3", "is-shared-array-buffer": "^1.0.4", "is-string": "^1.1.1", "is-typed-array": "^1.1.15", "is-weakref": "^1.1.1", "math-intrinsics": "^1.1.0", "object-inspect": "^1.13.4", "object-keys": "^1.1.1", "object.assign": "^4.1.7", "own-keys": "^1.0.1", "regexp.prototype.flags": "^1.5.4", "safe-array-concat": "^1.1.3", "safe-push-apply": "^1.0.0", "safe-regex-test": "^1.1.0", "set-proto": "^1.0.0", "stop-iteration-iterator": "^1.1.0", "string.prototype.trim": "^1.2.10", "string.prototype.trimend": "^1.0.9", "string.prototype.trimstart": "^1.0.8", "typed-array-buffer": "^1.0.3", "typed-array-byte-length": "^1.0.3", "typed-array-byte-offset": "^1.0.4", "typed-array-length": "^1.0.7", "unbox-primitive": "^1.1.0", "which-typed-array": "^1.1.19" } }, "sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw=="],
|
"es-abstract": ["es-abstract@1.24.1", "", { "dependencies": { "array-buffer-byte-length": "^1.0.2", "arraybuffer.prototype.slice": "^1.0.4", "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.4", "data-view-buffer": "^1.0.2", "data-view-byte-length": "^1.0.2", "data-view-byte-offset": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "es-set-tostringtag": "^2.1.0", "es-to-primitive": "^1.3.0", "function.prototype.name": "^1.1.8", "get-intrinsic": "^1.3.0", "get-proto": "^1.0.1", "get-symbol-description": "^1.1.0", "globalthis": "^1.0.4", "gopd": "^1.2.0", "has-property-descriptors": "^1.0.2", "has-proto": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "internal-slot": "^1.1.0", "is-array-buffer": "^3.0.5", "is-callable": "^1.2.7", "is-data-view": "^1.0.2", "is-negative-zero": "^2.0.3", "is-regex": "^1.2.1", "is-set": "^2.0.3", "is-shared-array-buffer": "^1.0.4", "is-string": "^1.1.1", "is-typed-array": "^1.1.15", "is-weakref": "^1.1.1", "math-intrinsics": "^1.1.0", "object-inspect": "^1.13.4", "object-keys": "^1.1.1", "object.assign": "^4.1.7", "own-keys": "^1.0.1", "regexp.prototype.flags": "^1.5.4", "safe-array-concat": "^1.1.3", "safe-push-apply": "^1.0.0", "safe-regex-test": "^1.1.0", "set-proto": "^1.0.0", "stop-iteration-iterator": "^1.1.0", "string.prototype.trim": "^1.2.10", "string.prototype.trimend": "^1.0.9", "string.prototype.trimstart": "^1.0.8", "typed-array-buffer": "^1.0.3", "typed-array-byte-length": "^1.0.3", "typed-array-byte-offset": "^1.0.4", "typed-array-length": "^1.0.7", "unbox-primitive": "^1.1.0", "which-typed-array": "^1.1.19" } }, "sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw=="],
|
||||||
|
|
||||||
"es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="],
|
"es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="],
|
||||||
@@ -553,6 +567,12 @@
|
|||||||
|
|
||||||
"es-to-primitive": ["es-to-primitive@1.3.0", "", { "dependencies": { "is-callable": "^1.2.7", "is-date-object": "^1.0.5", "is-symbol": "^1.0.4" } }, "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g=="],
|
"es-to-primitive": ["es-to-primitive@1.3.0", "", { "dependencies": { "is-callable": "^1.2.7", "is-date-object": "^1.0.5", "is-symbol": "^1.0.4" } }, "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g=="],
|
||||||
|
|
||||||
|
"es5-ext": ["es5-ext@0.10.64", "", { "dependencies": { "es6-iterator": "^2.0.3", "es6-symbol": "^3.1.3", "esniff": "^2.0.1", "next-tick": "^1.1.0" } }, "sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg=="],
|
||||||
|
|
||||||
|
"es6-iterator": ["es6-iterator@2.0.3", "", { "dependencies": { "d": "1", "es5-ext": "^0.10.35", "es6-symbol": "^3.1.1" } }, "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g=="],
|
||||||
|
|
||||||
|
"es6-symbol": ["es6-symbol@3.1.4", "", { "dependencies": { "d": "^1.0.2", "ext": "^1.7.0" } }, "sha512-U9bFFjX8tFiATgtkJ1zg25+KviIXpgRvRHS8sau3GfhVzThRQrOeksPeT0BWW2MNZs1OEWJ1DPXOQMn0KKRkvg=="],
|
||||||
|
|
||||||
"esbuild": ["esbuild@0.25.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.12", "@esbuild/android-arm": "0.25.12", "@esbuild/android-arm64": "0.25.12", "@esbuild/android-x64": "0.25.12", "@esbuild/darwin-arm64": "0.25.12", "@esbuild/darwin-x64": "0.25.12", "@esbuild/freebsd-arm64": "0.25.12", "@esbuild/freebsd-x64": "0.25.12", "@esbuild/linux-arm": "0.25.12", "@esbuild/linux-arm64": "0.25.12", "@esbuild/linux-ia32": "0.25.12", "@esbuild/linux-loong64": "0.25.12", "@esbuild/linux-mips64el": "0.25.12", "@esbuild/linux-ppc64": "0.25.12", "@esbuild/linux-riscv64": "0.25.12", "@esbuild/linux-s390x": "0.25.12", "@esbuild/linux-x64": "0.25.12", "@esbuild/netbsd-arm64": "0.25.12", "@esbuild/netbsd-x64": "0.25.12", "@esbuild/openbsd-arm64": "0.25.12", "@esbuild/openbsd-x64": "0.25.12", "@esbuild/openharmony-arm64": "0.25.12", "@esbuild/sunos-x64": "0.25.12", "@esbuild/win32-arm64": "0.25.12", "@esbuild/win32-ia32": "0.25.12", "@esbuild/win32-x64": "0.25.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg=="],
|
"esbuild": ["esbuild@0.25.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.12", "@esbuild/android-arm": "0.25.12", "@esbuild/android-arm64": "0.25.12", "@esbuild/android-x64": "0.25.12", "@esbuild/darwin-arm64": "0.25.12", "@esbuild/darwin-x64": "0.25.12", "@esbuild/freebsd-arm64": "0.25.12", "@esbuild/freebsd-x64": "0.25.12", "@esbuild/linux-arm": "0.25.12", "@esbuild/linux-arm64": "0.25.12", "@esbuild/linux-ia32": "0.25.12", "@esbuild/linux-loong64": "0.25.12", "@esbuild/linux-mips64el": "0.25.12", "@esbuild/linux-ppc64": "0.25.12", "@esbuild/linux-riscv64": "0.25.12", "@esbuild/linux-s390x": "0.25.12", "@esbuild/linux-x64": "0.25.12", "@esbuild/netbsd-arm64": "0.25.12", "@esbuild/netbsd-x64": "0.25.12", "@esbuild/openbsd-arm64": "0.25.12", "@esbuild/openbsd-x64": "0.25.12", "@esbuild/openharmony-arm64": "0.25.12", "@esbuild/sunos-x64": "0.25.12", "@esbuild/win32-arm64": "0.25.12", "@esbuild/win32-ia32": "0.25.12", "@esbuild/win32-x64": "0.25.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg=="],
|
||||||
|
|
||||||
"escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="],
|
"escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="],
|
||||||
@@ -575,6 +595,8 @@
|
|||||||
|
|
||||||
"eslint-visitor-keys": ["eslint-visitor-keys@4.2.1", "", {}, "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ=="],
|
"eslint-visitor-keys": ["eslint-visitor-keys@4.2.1", "", {}, "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ=="],
|
||||||
|
|
||||||
|
"esniff": ["esniff@2.0.1", "", { "dependencies": { "d": "^1.0.1", "es5-ext": "^0.10.62", "event-emitter": "^0.3.5", "type": "^2.7.2" } }, "sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg=="],
|
||||||
|
|
||||||
"espree": ["espree@10.4.0", "", { "dependencies": { "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^4.2.1" } }, "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ=="],
|
"espree": ["espree@10.4.0", "", { "dependencies": { "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^4.2.1" } }, "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ=="],
|
||||||
|
|
||||||
"esquery": ["esquery@1.7.0", "", { "dependencies": { "estraverse": "^5.1.0" } }, "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g=="],
|
"esquery": ["esquery@1.7.0", "", { "dependencies": { "estraverse": "^5.1.0" } }, "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g=="],
|
||||||
@@ -587,10 +609,14 @@
|
|||||||
|
|
||||||
"esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="],
|
"esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="],
|
||||||
|
|
||||||
|
"event-emitter": ["event-emitter@0.3.5", "", { "dependencies": { "d": "1", "es5-ext": "~0.10.14" } }, "sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA=="],
|
||||||
|
|
||||||
"execa": ["execa@9.6.1", "", { "dependencies": { "@sindresorhus/merge-streams": "^4.0.0", "cross-spawn": "^7.0.6", "figures": "^6.1.0", "get-stream": "^9.0.0", "human-signals": "^8.0.1", "is-plain-obj": "^4.1.0", "is-stream": "^4.0.1", "npm-run-path": "^6.0.0", "pretty-ms": "^9.2.0", "signal-exit": "^4.1.0", "strip-final-newline": "^4.0.0", "yoctocolors": "^2.1.1" } }, "sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA=="],
|
"execa": ["execa@9.6.1", "", { "dependencies": { "@sindresorhus/merge-streams": "^4.0.0", "cross-spawn": "^7.0.6", "figures": "^6.1.0", "get-stream": "^9.0.0", "human-signals": "^8.0.1", "is-plain-obj": "^4.1.0", "is-stream": "^4.0.1", "npm-run-path": "^6.0.0", "pretty-ms": "^9.2.0", "signal-exit": "^4.1.0", "strip-final-newline": "^4.0.0", "yoctocolors": "^2.1.1" } }, "sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA=="],
|
||||||
|
|
||||||
"expect-type": ["expect-type@1.3.0", "", {}, "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA=="],
|
"expect-type": ["expect-type@1.3.0", "", {}, "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA=="],
|
||||||
|
|
||||||
|
"ext": ["ext@1.7.0", "", { "dependencies": { "type": "^2.7.2" } }, "sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw=="],
|
||||||
|
|
||||||
"fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="],
|
"fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="],
|
||||||
|
|
||||||
"fast-diff": ["fast-diff@1.3.0", "", {}, "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw=="],
|
"fast-diff": ["fast-diff@1.3.0", "", {}, "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw=="],
|
||||||
@@ -681,12 +707,16 @@
|
|||||||
|
|
||||||
"ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="],
|
"ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="],
|
||||||
|
|
||||||
|
"immediate": ["immediate@3.0.6", "", {}, "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ=="],
|
||||||
|
|
||||||
"import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="],
|
"import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="],
|
||||||
|
|
||||||
"imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="],
|
"imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="],
|
||||||
|
|
||||||
"indent-string": ["indent-string@4.0.0", "", {}, "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg=="],
|
"indent-string": ["indent-string@4.0.0", "", {}, "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg=="],
|
||||||
|
|
||||||
|
"inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
|
||||||
|
|
||||||
"internal-slot": ["internal-slot@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "hasown": "^2.0.2", "side-channel": "^1.1.0" } }, "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw=="],
|
"internal-slot": ["internal-slot@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "hasown": "^2.0.2", "side-channel": "^1.1.0" } }, "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw=="],
|
||||||
|
|
||||||
"is-array-buffer": ["is-array-buffer@3.0.5", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "get-intrinsic": "^1.2.6" } }, "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A=="],
|
"is-array-buffer": ["is-array-buffer@3.0.5", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "get-intrinsic": "^1.2.6" } }, "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A=="],
|
||||||
@@ -781,20 +811,28 @@
|
|||||||
|
|
||||||
"jsx-ast-utils": ["jsx-ast-utils@3.3.5", "", { "dependencies": { "array-includes": "^3.1.6", "array.prototype.flat": "^1.3.1", "object.assign": "^4.1.4", "object.values": "^1.1.6" } }, "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ=="],
|
"jsx-ast-utils": ["jsx-ast-utils@3.3.5", "", { "dependencies": { "array-includes": "^3.1.6", "array.prototype.flat": "^1.3.1", "object.assign": "^4.1.4", "object.values": "^1.1.6" } }, "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ=="],
|
||||||
|
|
||||||
|
"jszip": ["jszip@3.10.1", "", { "dependencies": { "lie": "~3.3.0", "pako": "~1.0.2", "readable-stream": "~2.3.6", "setimmediate": "^1.0.5" } }, "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g=="],
|
||||||
|
|
||||||
"keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="],
|
"keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="],
|
||||||
|
|
||||||
"leven": ["leven@4.1.0", "", {}, "sha512-KZ9W9nWDT7rF7Dazg8xyLHGLrmpgq2nVNFUckhqdW3szVP6YhCpp/RAnpmVExA9JvrMynjwSLVrEj3AepHR6ew=="],
|
"leven": ["leven@4.1.0", "", {}, "sha512-KZ9W9nWDT7rF7Dazg8xyLHGLrmpgq2nVNFUckhqdW3szVP6YhCpp/RAnpmVExA9JvrMynjwSLVrEj3AepHR6ew=="],
|
||||||
|
|
||||||
"levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="],
|
"levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="],
|
||||||
|
|
||||||
|
"lie": ["lie@3.3.0", "", { "dependencies": { "immediate": "~3.0.5" } }, "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ=="],
|
||||||
|
|
||||||
"lilconfig": ["lilconfig@3.1.3", "", {}, "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw=="],
|
"lilconfig": ["lilconfig@3.1.3", "", {}, "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw=="],
|
||||||
|
|
||||||
"lines-and-columns": ["lines-and-columns@1.2.4", "", {}, "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="],
|
"lines-and-columns": ["lines-and-columns@1.2.4", "", {}, "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="],
|
||||||
|
|
||||||
"linkify-it": ["linkify-it@5.0.0", "", { "dependencies": { "uc.micro": "^2.0.0" } }, "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ=="],
|
"linkify-it": ["linkify-it@5.0.0", "", { "dependencies": { "uc.micro": "^2.0.0" } }, "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ=="],
|
||||||
|
|
||||||
|
"localforage": ["localforage@1.10.0", "", { "dependencies": { "lie": "3.1.1" } }, "sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg=="],
|
||||||
|
|
||||||
"locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="],
|
"locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="],
|
||||||
|
|
||||||
|
"lodash": ["lodash@4.18.1", "", {}, "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q=="],
|
||||||
|
|
||||||
"lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="],
|
"lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="],
|
||||||
|
|
||||||
"loose-envify": ["loose-envify@1.4.0", "", { "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, "bin": { "loose-envify": "cli.js" } }, "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q=="],
|
"loose-envify": ["loose-envify@1.4.0", "", { "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, "bin": { "loose-envify": "cli.js" } }, "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q=="],
|
||||||
@@ -809,6 +847,8 @@
|
|||||||
|
|
||||||
"markdown-it": ["markdown-it@14.1.1", "", { "dependencies": { "argparse": "^2.0.1", "entities": "^4.4.0", "linkify-it": "^5.0.0", "mdurl": "^2.0.0", "punycode.js": "^2.3.1", "uc.micro": "^2.1.0" }, "bin": { "markdown-it": "bin/markdown-it.mjs" } }, "sha512-BuU2qnTti9YKgK5N+IeMubp14ZUKUUw7yeJbkjtosvHiP0AZ5c8IAgEMk79D0eC8F23r4Ac/q8cAIFdm2FtyoA=="],
|
"markdown-it": ["markdown-it@14.1.1", "", { "dependencies": { "argparse": "^2.0.1", "entities": "^4.4.0", "linkify-it": "^5.0.0", "mdurl": "^2.0.0", "punycode.js": "^2.3.1", "uc.micro": "^2.1.0" }, "bin": { "markdown-it": "bin/markdown-it.mjs" } }, "sha512-BuU2qnTti9YKgK5N+IeMubp14ZUKUUw7yeJbkjtosvHiP0AZ5c8IAgEMk79D0eC8F23r4Ac/q8cAIFdm2FtyoA=="],
|
||||||
|
|
||||||
|
"marks-pane": ["marks-pane@1.0.9", "", {}, "sha512-Ahs4oeG90tbdPWwAJkAAoHg2lRR8lAs9mZXETNPO9hYg3AkjUJBKi1NQ4aaIQZVGrig7c/3NUV1jANl8rFTeMg=="],
|
||||||
|
|
||||||
"math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="],
|
"math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="],
|
||||||
|
|
||||||
"mdn-data": ["mdn-data@2.27.1", "", {}, "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ=="],
|
"mdn-data": ["mdn-data@2.27.1", "", {}, "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ=="],
|
||||||
@@ -835,12 +875,16 @@
|
|||||||
|
|
||||||
"natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="],
|
"natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="],
|
||||||
|
|
||||||
|
"next-tick": ["next-tick@1.1.0", "", {}, "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ=="],
|
||||||
|
|
||||||
"node-exports-info": ["node-exports-info@1.6.0", "", { "dependencies": { "array.prototype.flatmap": "^1.3.3", "es-errors": "^1.3.0", "object.entries": "^1.1.9", "semver": "^6.3.1" } }, "sha512-pyFS63ptit/P5WqUkt+UUfe+4oevH+bFeIiPPdfb0pFeYEu/1ELnJu5l+5EcTKYL5M7zaAa7S8ddywgXypqKCw=="],
|
"node-exports-info": ["node-exports-info@1.6.0", "", { "dependencies": { "array.prototype.flatmap": "^1.3.3", "es-errors": "^1.3.0", "object.entries": "^1.1.9", "semver": "^6.3.1" } }, "sha512-pyFS63ptit/P5WqUkt+UUfe+4oevH+bFeIiPPdfb0pFeYEu/1ELnJu5l+5EcTKYL5M7zaAa7S8ddywgXypqKCw=="],
|
||||||
|
|
||||||
"node-releases": ["node-releases@2.0.36", "", {}, "sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA=="],
|
"node-releases": ["node-releases@2.0.36", "", {}, "sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA=="],
|
||||||
|
|
||||||
"normalize-path": ["normalize-path@3.0.0", "", {}, "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="],
|
"normalize-path": ["normalize-path@3.0.0", "", {}, "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="],
|
||||||
|
|
||||||
|
"nosleep.js": ["nosleep.js@0.12.0", "", {}, "sha512-9d1HbpKLh3sdWlhXMhU6MMH+wQzKkrgfRkYV0EBdvt99YJfj0ilCJrWRDYG2130Tm4GXbEoTCx5b34JSaP+HhA=="],
|
||||||
|
|
||||||
"npm-run-path": ["npm-run-path@6.0.0", "", { "dependencies": { "path-key": "^4.0.0", "unicorn-magic": "^0.3.0" } }, "sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA=="],
|
"npm-run-path": ["npm-run-path@6.0.0", "", { "dependencies": { "path-key": "^4.0.0", "unicorn-magic": "^0.3.0" } }, "sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA=="],
|
||||||
|
|
||||||
"object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="],
|
"object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="],
|
||||||
@@ -871,6 +915,8 @@
|
|||||||
|
|
||||||
"p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="],
|
"p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="],
|
||||||
|
|
||||||
|
"pako": ["pako@1.0.11", "", {}, "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw=="],
|
||||||
|
|
||||||
"parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="],
|
"parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="],
|
||||||
|
|
||||||
"parse-ms": ["parse-ms@4.0.0", "", {}, "sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw=="],
|
"parse-ms": ["parse-ms@4.0.0", "", {}, "sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw=="],
|
||||||
@@ -883,6 +929,8 @@
|
|||||||
|
|
||||||
"path-parse": ["path-parse@1.0.7", "", {}, "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="],
|
"path-parse": ["path-parse@1.0.7", "", {}, "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="],
|
||||||
|
|
||||||
|
"path-webpack": ["path-webpack@0.0.3", "", {}, "sha512-AmeDxedoo5svf7aB3FYqSAKqMxys014lVKBzy1o/5vv9CtU7U4wgGWL1dA2o6MOzcD53ScN4Jmiq6VbtLz1vIQ=="],
|
||||||
|
|
||||||
"pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
|
"pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
|
||||||
|
|
||||||
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
|
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
|
||||||
@@ -919,6 +967,8 @@
|
|||||||
|
|
||||||
"pretty-ms": ["pretty-ms@9.3.0", "", { "dependencies": { "parse-ms": "^4.0.0" } }, "sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ=="],
|
"pretty-ms": ["pretty-ms@9.3.0", "", { "dependencies": { "parse-ms": "^4.0.0" } }, "sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ=="],
|
||||||
|
|
||||||
|
"process-nextick-args": ["process-nextick-args@2.0.1", "", {}, "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="],
|
||||||
|
|
||||||
"prop-types": ["prop-types@15.8.1", "", { "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", "react-is": "^16.13.1" } }, "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg=="],
|
"prop-types": ["prop-types@15.8.1", "", { "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", "react-is": "^16.13.1" } }, "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg=="],
|
||||||
|
|
||||||
"proxy-from-env": ["proxy-from-env@1.1.0", "", {}, "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="],
|
"proxy-from-env": ["proxy-from-env@1.1.0", "", {}, "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="],
|
||||||
@@ -943,6 +993,8 @@
|
|||||||
|
|
||||||
"read-cache": ["read-cache@1.0.0", "", { "dependencies": { "pify": "^2.3.0" } }, "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA=="],
|
"read-cache": ["read-cache@1.0.0", "", { "dependencies": { "pify": "^2.3.0" } }, "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA=="],
|
||||||
|
|
||||||
|
"readable-stream": ["readable-stream@2.3.8", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="],
|
||||||
|
|
||||||
"readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="],
|
"readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="],
|
||||||
|
|
||||||
"redent": ["redent@3.0.0", "", { "dependencies": { "indent-string": "^4.0.0", "strip-indent": "^3.0.0" } }, "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg=="],
|
"redent": ["redent@3.0.0", "", { "dependencies": { "indent-string": "^4.0.0", "strip-indent": "^3.0.0" } }, "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg=="],
|
||||||
@@ -967,6 +1019,8 @@
|
|||||||
|
|
||||||
"safe-array-concat": ["safe-array-concat@1.1.3", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.2", "get-intrinsic": "^1.2.6", "has-symbols": "^1.1.0", "isarray": "^2.0.5" } }, "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q=="],
|
"safe-array-concat": ["safe-array-concat@1.1.3", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.2", "get-intrinsic": "^1.2.6", "has-symbols": "^1.1.0", "isarray": "^2.0.5" } }, "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q=="],
|
||||||
|
|
||||||
|
"safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="],
|
||||||
|
|
||||||
"safe-push-apply": ["safe-push-apply@1.0.0", "", { "dependencies": { "es-errors": "^1.3.0", "isarray": "^2.0.5" } }, "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA=="],
|
"safe-push-apply": ["safe-push-apply@1.0.0", "", { "dependencies": { "es-errors": "^1.3.0", "isarray": "^2.0.5" } }, "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA=="],
|
||||||
|
|
||||||
"safe-regex-test": ["safe-regex-test@1.1.0", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "is-regex": "^1.2.1" } }, "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw=="],
|
"safe-regex-test": ["safe-regex-test@1.1.0", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "is-regex": "^1.2.1" } }, "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw=="],
|
||||||
@@ -985,6 +1039,8 @@
|
|||||||
|
|
||||||
"set-proto": ["set-proto@1.0.0", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0" } }, "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw=="],
|
"set-proto": ["set-proto@1.0.0", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0" } }, "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw=="],
|
||||||
|
|
||||||
|
"setimmediate": ["setimmediate@1.0.5", "", {}, "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA=="],
|
||||||
|
|
||||||
"shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="],
|
"shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="],
|
||||||
|
|
||||||
"shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="],
|
"shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="],
|
||||||
@@ -1023,6 +1079,8 @@
|
|||||||
|
|
||||||
"string.prototype.trimstart": ["string.prototype.trimstart@1.0.8", "", { "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0" } }, "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg=="],
|
"string.prototype.trimstart": ["string.prototype.trimstart@1.0.8", "", { "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0" } }, "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg=="],
|
||||||
|
|
||||||
|
"string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="],
|
||||||
|
|
||||||
"strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
|
"strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
|
||||||
|
|
||||||
"strip-final-newline": ["strip-final-newline@4.0.0", "", {}, "sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw=="],
|
"strip-final-newline": ["strip-final-newline@4.0.0", "", {}, "sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw=="],
|
||||||
@@ -1073,6 +1131,8 @@
|
|||||||
|
|
||||||
"tsconfck": ["tsconfck@3.1.6", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"], "bin": { "tsconfck": "bin/tsconfck.js" } }, "sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w=="],
|
"tsconfck": ["tsconfck@3.1.6", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"], "bin": { "tsconfck": "bin/tsconfck.js" } }, "sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w=="],
|
||||||
|
|
||||||
|
"type": ["type@2.7.3", "", {}, "sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ=="],
|
||||||
|
|
||||||
"type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="],
|
"type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="],
|
||||||
|
|
||||||
"typed-array-buffer": ["typed-array-buffer@1.0.3", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "is-typed-array": "^1.1.14" } }, "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw=="],
|
"typed-array-buffer": ["typed-array-buffer@1.0.3", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "is-typed-array": "^1.1.14" } }, "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw=="],
|
||||||
@@ -1185,6 +1245,8 @@
|
|||||||
|
|
||||||
"globby/unicorn-magic": ["unicorn-magic@0.4.0", "", {}, "sha512-wH590V9VNgYH9g3lH9wWjTrUoKsjLF6sGLjhR4sH1LWpLmCOH0Zf7PukhDA8BiS7KHe4oPNkcTHqYkj7SOGUOw=="],
|
"globby/unicorn-magic": ["unicorn-magic@0.4.0", "", {}, "sha512-wH590V9VNgYH9g3lH9wWjTrUoKsjLF6sGLjhR4sH1LWpLmCOH0Zf7PukhDA8BiS7KHe4oPNkcTHqYkj7SOGUOw=="],
|
||||||
|
|
||||||
|
"localforage/lie": ["lie@3.1.1", "", { "dependencies": { "immediate": "~3.0.5" } }, "sha512-RiNhHysUjhrDQntfYSfY4MU24coXXdEOgw9WGcKHNeEwffDYbF//u87M1EWaMGzuFoSbqW0C9C6lEEhDOAswfw=="],
|
||||||
|
|
||||||
"markdown-it/entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="],
|
"markdown-it/entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="],
|
||||||
|
|
||||||
"micromatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
|
"micromatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
|
||||||
@@ -1201,6 +1263,8 @@
|
|||||||
|
|
||||||
"pretty-format/react-is": ["react-is@17.0.2", "", {}, "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="],
|
"pretty-format/react-is": ["react-is@17.0.2", "", {}, "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="],
|
||||||
|
|
||||||
|
"readable-stream/isarray": ["isarray@1.0.0", "", {}, "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ=="],
|
||||||
|
|
||||||
"readdirp/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
|
"readdirp/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
|
||||||
|
|
||||||
"sucrase/commander": ["commander@4.1.1", "", {}, "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA=="],
|
"sucrase/commander": ["commander@4.1.1", "", {}, "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA=="],
|
||||||
|
|||||||
171
frontend/dist/assets/index-C7Wct-hD.js
vendored
171
frontend/dist/assets/index-C7Wct-hD.js
vendored
File diff suppressed because one or more lines are too long
1
frontend/dist/assets/index-Co--bktJ.css
vendored
1
frontend/dist/assets/index-Co--bktJ.css
vendored
File diff suppressed because one or more lines are too long
4
frontend/dist/index.html
vendored
4
frontend/dist/index.html
vendored
@@ -23,8 +23,8 @@
|
|||||||
/>
|
/>
|
||||||
<title>AnthoLume</title>
|
<title>AnthoLume</title>
|
||||||
<link rel="manifest" href="/manifest.json" />
|
<link rel="manifest" href="/manifest.json" />
|
||||||
<script type="module" crossorigin src="/assets/index-C7Wct-hD.js"></script>
|
<script type="module" crossorigin src="/assets/index-BQhAeK6-.js"></script>
|
||||||
<link rel="stylesheet" crossorigin href="/assets/index-Co--bktJ.css">
|
<link rel="stylesheet" crossorigin href="/assets/index-CdRalUYN.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
|||||||
@@ -20,6 +20,8 @@
|
|||||||
"ajv": "^8.18.0",
|
"ajv": "^8.18.0",
|
||||||
"axios": "^1.13.6",
|
"axios": "^1.13.6",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
|
"epubjs": "^0.3.93",
|
||||||
|
"nosleep.js": "^0.12.0",
|
||||||
"orval": "8.5.3",
|
"orval": "8.5.3",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
"react-dom": "^19.0.0",
|
"react-dom": "^19.0.0",
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import AdminImportPage from './pages/AdminImportPage';
|
|||||||
import AdminImportResultsPage from './pages/AdminImportResultsPage';
|
import AdminImportResultsPage from './pages/AdminImportResultsPage';
|
||||||
import AdminUsersPage from './pages/AdminUsersPage';
|
import AdminUsersPage from './pages/AdminUsersPage';
|
||||||
import AdminLogsPage from './pages/AdminLogsPage';
|
import AdminLogsPage from './pages/AdminLogsPage';
|
||||||
|
import ReaderPage from './pages/ReaderPage';
|
||||||
import { ProtectedRoute } from './auth/ProtectedRoute';
|
import { ProtectedRoute } from './auth/ProtectedRoute';
|
||||||
|
|
||||||
export function Routes() {
|
export function Routes() {
|
||||||
@@ -118,6 +119,14 @@ export function Routes() {
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</Route>
|
</Route>
|
||||||
|
<Route
|
||||||
|
path="/reader/:id"
|
||||||
|
element={
|
||||||
|
<ProtectedRoute>
|
||||||
|
<ReaderPage />
|
||||||
|
</ProtectedRoute>
|
||||||
|
}
|
||||||
|
/>
|
||||||
<Route path="/login" element={<LoginPage />} />
|
<Route path="/login" element={<LoginPage />} />
|
||||||
<Route path="/register" element={<RegisterPage />} />
|
<Route path="/register" element={<RegisterPage />} />
|
||||||
</ReactRoutes>
|
</ReactRoutes>
|
||||||
|
|||||||
148
frontend/src/hooks/useEpubReader.ts
Normal file
148
frontend/src/hooks/useEpubReader.ts
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
|
import { EBookReader, type ReaderStats, type ReaderTocItem } from '../lib/reader/EBookReader';
|
||||||
|
import type { ReaderColorScheme, ReaderFontFamily } from '../utils/localSettings';
|
||||||
|
|
||||||
|
interface UseEpubReaderOptions {
|
||||||
|
documentId: string;
|
||||||
|
initialProgress?: string;
|
||||||
|
deviceId: string;
|
||||||
|
deviceName: string;
|
||||||
|
colorScheme: ReaderColorScheme;
|
||||||
|
fontFamily: ReaderFontFamily;
|
||||||
|
fontSize: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UseEpubReaderResult {
|
||||||
|
viewerRef: (_node: HTMLDivElement | null) => void;
|
||||||
|
isReady: boolean;
|
||||||
|
isLoading: boolean;
|
||||||
|
error: string | null;
|
||||||
|
toc: ReaderTocItem[];
|
||||||
|
stats: ReaderStats;
|
||||||
|
nextPage: () => Promise<void>;
|
||||||
|
prevPage: () => Promise<void>;
|
||||||
|
goToHref: (href: string) => Promise<void>;
|
||||||
|
setTheme: (theme: {
|
||||||
|
colorScheme?: ReaderColorScheme;
|
||||||
|
fontFamily?: ReaderFontFamily;
|
||||||
|
fontSize?: number;
|
||||||
|
}) => Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useEpubReader({
|
||||||
|
documentId,
|
||||||
|
initialProgress,
|
||||||
|
deviceId,
|
||||||
|
deviceName,
|
||||||
|
colorScheme,
|
||||||
|
fontFamily,
|
||||||
|
fontSize,
|
||||||
|
}: UseEpubReaderOptions): UseEpubReaderResult {
|
||||||
|
const [viewerNode, setViewerNode] = useState<HTMLDivElement | null>(null);
|
||||||
|
const readerRef = useRef<EBookReader | null>(null);
|
||||||
|
const [isReady, setIsReady] = useState(false);
|
||||||
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
const [toc, setToc] = useState<ReaderTocItem[]>([]);
|
||||||
|
const [stats, setStats] = useState<ReaderStats>({
|
||||||
|
chapterName: 'N/A',
|
||||||
|
sectionPage: 0,
|
||||||
|
sectionTotalPages: 0,
|
||||||
|
percentage: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const container = viewerNode;
|
||||||
|
if (!container) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsReady(false);
|
||||||
|
setIsLoading(true);
|
||||||
|
setError(null);
|
||||||
|
setToc([]);
|
||||||
|
setStats({
|
||||||
|
chapterName: 'N/A',
|
||||||
|
sectionPage: 0,
|
||||||
|
sectionTotalPages: 0,
|
||||||
|
percentage: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
const reader = new EBookReader({
|
||||||
|
container,
|
||||||
|
documentId,
|
||||||
|
initialProgress,
|
||||||
|
deviceId,
|
||||||
|
deviceName,
|
||||||
|
colorScheme,
|
||||||
|
fontFamily,
|
||||||
|
fontSize,
|
||||||
|
onReady: () => setIsReady(true),
|
||||||
|
onLoading: loading => setIsLoading(loading),
|
||||||
|
onError: message => setError(message),
|
||||||
|
onStats: nextStats => setStats(nextStats),
|
||||||
|
onToc: nextToc => setToc(nextToc),
|
||||||
|
});
|
||||||
|
|
||||||
|
readerRef.current = reader;
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
reader.destroy();
|
||||||
|
if (readerRef.current === reader) {
|
||||||
|
readerRef.current = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, [deviceId, deviceName, documentId, initialProgress, viewerNode]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const reader = readerRef.current;
|
||||||
|
if (!reader || !isReady) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
void reader.applyThemeChange({
|
||||||
|
colorScheme,
|
||||||
|
fontFamily,
|
||||||
|
fontSize,
|
||||||
|
});
|
||||||
|
}, [colorScheme, fontFamily, fontSize, isReady]);
|
||||||
|
|
||||||
|
const nextPage = useCallback(async () => {
|
||||||
|
await readerRef.current?.nextPage();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const prevPage = useCallback(async () => {
|
||||||
|
await readerRef.current?.prevPage();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const goToHref = useCallback(async (href: string) => {
|
||||||
|
await readerRef.current?.displayHref(href);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const setTheme = useCallback(
|
||||||
|
async (theme: {
|
||||||
|
colorScheme?: ReaderColorScheme;
|
||||||
|
fontFamily?: ReaderFontFamily;
|
||||||
|
fontSize?: number;
|
||||||
|
}) => {
|
||||||
|
await readerRef.current?.applyThemeChange(theme);
|
||||||
|
},
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
|
return useMemo(
|
||||||
|
() => ({
|
||||||
|
viewerRef: setViewerNode,
|
||||||
|
isReady,
|
||||||
|
isLoading,
|
||||||
|
error,
|
||||||
|
toc,
|
||||||
|
stats,
|
||||||
|
nextPage,
|
||||||
|
prevPage,
|
||||||
|
goToHref,
|
||||||
|
setTheme,
|
||||||
|
}),
|
||||||
|
[error, goToHref, isLoading, isReady, nextPage, prevPage, setTheme, stats, toc]
|
||||||
|
);
|
||||||
|
}
|
||||||
879
frontend/src/lib/reader/EBookReader.ts
Normal file
879
frontend/src/lib/reader/EBookReader.ts
Normal file
@@ -0,0 +1,879 @@
|
|||||||
|
import ePub from 'epubjs';
|
||||||
|
import NoSleep from 'nosleep.js';
|
||||||
|
import type { ReaderColorScheme, ReaderFontFamily } from '../../utils/localSettings';
|
||||||
|
|
||||||
|
const THEMES: ReaderColorScheme[] = ['light', 'tan', 'blue', 'gray', 'black'];
|
||||||
|
const THEME_FILE = '/assets/reader/themes.css';
|
||||||
|
const FONT_FILE = '/assets/reader/fonts.css';
|
||||||
|
|
||||||
|
interface TocNode {
|
||||||
|
href: string;
|
||||||
|
label?: string;
|
||||||
|
subitems?: TocNode[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface EpubContents {
|
||||||
|
document: Document;
|
||||||
|
sectionIndex?: number;
|
||||||
|
range: (cfi: string) => Range;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface EpubVisibleSection {
|
||||||
|
index: number;
|
||||||
|
layout: { width: number; divisor: number };
|
||||||
|
width: () => number;
|
||||||
|
expand: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface EpubLocation {
|
||||||
|
start: {
|
||||||
|
cfi: string;
|
||||||
|
href?: string;
|
||||||
|
};
|
||||||
|
end: {
|
||||||
|
cfi: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface EpubNavigation {
|
||||||
|
toc?: TocNode[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface EpubSpineItem {
|
||||||
|
cfiBase: string;
|
||||||
|
index: number;
|
||||||
|
document: Document;
|
||||||
|
load: (_loader: unknown) => Promise<Document>;
|
||||||
|
cfiFromElement: (element: Element) => string;
|
||||||
|
wordCount?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface EpubBook {
|
||||||
|
ready: Promise<void>;
|
||||||
|
navigation?: EpubNavigation;
|
||||||
|
loaded: { navigation: Promise<EpubNavigation> };
|
||||||
|
spine: {
|
||||||
|
spineItems: EpubSpineItem[];
|
||||||
|
get: (index: number) => EpubSpineItem;
|
||||||
|
hooks: {
|
||||||
|
content: { register: (_callback: (output: Document) => void) => void };
|
||||||
|
};
|
||||||
|
};
|
||||||
|
load: (...args: unknown[]) => unknown;
|
||||||
|
renderTo: (element: HTMLElement, options: Record<string, unknown>) => EpubRendition;
|
||||||
|
getRange: (cfiRange: string) => Promise<Range>;
|
||||||
|
destroy?: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface EpubRendition {
|
||||||
|
next: () => Promise<void>;
|
||||||
|
prev: () => Promise<void>;
|
||||||
|
display: (target?: string) => Promise<void>;
|
||||||
|
currentLocation: () => Promise<EpubLocation>;
|
||||||
|
getContents: () => EpubContents[];
|
||||||
|
themes: {
|
||||||
|
default: (styles: Record<string, unknown>) => void;
|
||||||
|
register: (name: string, styles: Record<string, unknown> | string) => void;
|
||||||
|
select: (name: string) => void;
|
||||||
|
};
|
||||||
|
hooks: {
|
||||||
|
content: { register: (_callback: () => void) => void };
|
||||||
|
render: { register: (_callback: (contents: EpubContents) => void) => void };
|
||||||
|
};
|
||||||
|
manager?: {
|
||||||
|
visible?: () => EpubVisibleSection[];
|
||||||
|
};
|
||||||
|
views: () => { container: { scrollLeft: number } };
|
||||||
|
destroy?: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ParsedCfiPath {
|
||||||
|
steps: unknown[];
|
||||||
|
terminal: unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ParsedCfi {
|
||||||
|
base: unknown;
|
||||||
|
path: ParsedCfiPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface EpubCfiHelper {
|
||||||
|
parse: (_value: string) => ParsedCfi;
|
||||||
|
equalStep: (_a: unknown, _b: unknown) => boolean;
|
||||||
|
segmentString: (_value: unknown) => string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface EpubWithCfiConstructor {
|
||||||
|
CFI: new () => EpubCfiHelper;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ReaderStats {
|
||||||
|
chapterName: string;
|
||||||
|
sectionPage: number;
|
||||||
|
sectionTotalPages: number;
|
||||||
|
percentage: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ReaderTocItem {
|
||||||
|
title: string;
|
||||||
|
href: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface BookState {
|
||||||
|
pages: number;
|
||||||
|
percentage: number;
|
||||||
|
progress: string;
|
||||||
|
progressElement: Element | null;
|
||||||
|
readActivity: unknown[];
|
||||||
|
words: number;
|
||||||
|
pageStart: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ReaderSettings {
|
||||||
|
theme?: {
|
||||||
|
colorScheme?: ReaderColorScheme;
|
||||||
|
fontFamily?: string;
|
||||||
|
fontSize?: number;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface EBookReaderOptions {
|
||||||
|
container: HTMLElement;
|
||||||
|
documentId: string;
|
||||||
|
initialProgress?: string;
|
||||||
|
deviceId: string;
|
||||||
|
deviceName: string;
|
||||||
|
colorScheme: ReaderColorScheme;
|
||||||
|
fontFamily: ReaderFontFamily;
|
||||||
|
fontSize: number;
|
||||||
|
onReady: () => void;
|
||||||
|
onLoading: (_loading: boolean) => void;
|
||||||
|
onError: (_message: string) => void;
|
||||||
|
onStats: (_stats: ReaderStats) => void;
|
||||||
|
onToc: (_toc: ReaderTocItem[]) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class EBookReader {
|
||||||
|
private container: HTMLElement;
|
||||||
|
private documentId: string;
|
||||||
|
private deviceId: string;
|
||||||
|
private deviceName: string;
|
||||||
|
private readerSettings: ReaderSettings = {};
|
||||||
|
private bookState: BookState;
|
||||||
|
private book: EpubBook;
|
||||||
|
private rendition: EpubRendition;
|
||||||
|
private noSleep: NoSleep | null = null;
|
||||||
|
private wakeTimeoutId: ReturnType<typeof setTimeout> | null = null;
|
||||||
|
private destroyed = false;
|
||||||
|
private onReady: () => void;
|
||||||
|
private onLoading: (_loading: boolean) => void;
|
||||||
|
private onError: (_message: string) => void;
|
||||||
|
private onStats: (_stats: ReaderStats) => void;
|
||||||
|
private onToc: (_toc: ReaderTocItem[]) => void;
|
||||||
|
private keyupHandler: ((event: KeyboardEvent) => void) | null = null;
|
||||||
|
|
||||||
|
constructor(options: EBookReaderOptions) {
|
||||||
|
this.container = options.container;
|
||||||
|
this.documentId = options.documentId;
|
||||||
|
this.deviceId = options.deviceId;
|
||||||
|
this.deviceName = options.deviceName;
|
||||||
|
this.onReady = options.onReady;
|
||||||
|
this.onLoading = options.onLoading;
|
||||||
|
this.onError = options.onError;
|
||||||
|
this.onStats = options.onStats;
|
||||||
|
this.onToc = options.onToc;
|
||||||
|
|
||||||
|
this.bookState = {
|
||||||
|
pages: 0,
|
||||||
|
percentage: 0,
|
||||||
|
progress: options.initialProgress ?? '',
|
||||||
|
progressElement: null,
|
||||||
|
readActivity: [],
|
||||||
|
words: 0,
|
||||||
|
pageStart: Date.now(),
|
||||||
|
};
|
||||||
|
|
||||||
|
this.loadSettings();
|
||||||
|
this.readerSettings.theme = {
|
||||||
|
colorScheme: options.colorScheme,
|
||||||
|
fontFamily: options.fontFamily,
|
||||||
|
fontSize: options.fontSize,
|
||||||
|
};
|
||||||
|
|
||||||
|
this.onLoading(true);
|
||||||
|
this.book = ePub(`/api/v1/documents/${this.documentId}/file`, { openAs: 'epub' }) as EpubBook;
|
||||||
|
this.rendition = this.book.renderTo(this.container, {
|
||||||
|
manager: 'default',
|
||||||
|
flow: 'paginated',
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
allowScriptedContent: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.initCSP();
|
||||||
|
this.initWakeLock();
|
||||||
|
this.initThemes();
|
||||||
|
this.initViewerListeners();
|
||||||
|
this.initDocumentListeners();
|
||||||
|
|
||||||
|
this.book.ready
|
||||||
|
.then(this.setupReader.bind(this))
|
||||||
|
.catch(error => {
|
||||||
|
if (this.destroyed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.onError(error instanceof Error ? error.message : 'Unable to initialize reader');
|
||||||
|
this.onLoading(false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private loadSettings() {
|
||||||
|
this.readerSettings = {
|
||||||
|
theme: this.readerSettings.theme ?? {},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private initWakeLock() {
|
||||||
|
this.noSleep = new NoSleep();
|
||||||
|
document.addEventListener('wakelock', this.handleWakeLock);
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleWakeLock = () => {
|
||||||
|
if (!this.noSleep) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.wakeTimeoutId) {
|
||||||
|
clearTimeout(this.wakeTimeoutId);
|
||||||
|
}
|
||||||
|
this.wakeTimeoutId = setTimeout(() => {
|
||||||
|
void this.noSleep?.disable();
|
||||||
|
}, 1000 * 60 * 10);
|
||||||
|
|
||||||
|
void this.noSleep.enable();
|
||||||
|
};
|
||||||
|
|
||||||
|
private initThemes() {
|
||||||
|
THEMES.forEach(theme => this.rendition.themes.register(theme, THEME_FILE));
|
||||||
|
|
||||||
|
let themeLinkEl = document.querySelector('#themes') as HTMLLinkElement | null;
|
||||||
|
if (!themeLinkEl) {
|
||||||
|
themeLinkEl = document.createElement('link');
|
||||||
|
themeLinkEl.id = 'themes';
|
||||||
|
themeLinkEl.rel = 'stylesheet';
|
||||||
|
themeLinkEl.href = THEME_FILE;
|
||||||
|
document.head.append(themeLinkEl);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.rendition.themes.default({
|
||||||
|
'*': {
|
||||||
|
'font-size': 'var(--editor-font-size) !important',
|
||||||
|
'font-family': 'var(--editor-font-family) !important',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
this.rendition.hooks.content.register(() => {
|
||||||
|
this.setTheme();
|
||||||
|
this.rendition.getContents().forEach(content => {
|
||||||
|
const existing = content.document.getElementById('reader-fonts');
|
||||||
|
if (!existing) {
|
||||||
|
const nextLink = content.document.head.appendChild(content.document.createElement('link'));
|
||||||
|
nextLink.id = 'reader-fonts';
|
||||||
|
nextLink.rel = 'stylesheet';
|
||||||
|
nextLink.href = FONT_FILE;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private initCSP() {
|
||||||
|
const protocol = document.location.protocol;
|
||||||
|
const host = document.location.host;
|
||||||
|
const cspURL = `${protocol}//${host}`;
|
||||||
|
|
||||||
|
this.book.spine.hooks.content.register(output => {
|
||||||
|
const cspWrapper = document.createElement('div');
|
||||||
|
cspWrapper.innerHTML = `
|
||||||
|
<meta
|
||||||
|
http-equiv="Content-Security-Policy"
|
||||||
|
content="require-trusted-types-for 'script';
|
||||||
|
style-src 'self' blob: 'unsafe-inline' ${cspURL};
|
||||||
|
object-src 'none';
|
||||||
|
script-src 'none';"
|
||||||
|
>`;
|
||||||
|
const cspMeta = cspWrapper.children[0];
|
||||||
|
if (cspMeta) {
|
||||||
|
output.head.append(cspMeta);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private initViewerListeners() {
|
||||||
|
const nextPage = this.nextPage.bind(this);
|
||||||
|
const prevPage = this.prevPage.bind(this);
|
||||||
|
|
||||||
|
this.rendition.hooks.render.register((contents: EpubContents) => {
|
||||||
|
const renderDoc = contents.document;
|
||||||
|
|
||||||
|
const wakeLockListener = () => {
|
||||||
|
renderDoc.dispatchEvent(new CustomEvent('wakelock'));
|
||||||
|
};
|
||||||
|
renderDoc.addEventListener('click', wakeLockListener);
|
||||||
|
renderDoc.addEventListener('gesturechange', wakeLockListener);
|
||||||
|
renderDoc.addEventListener('touchstart', wakeLockListener);
|
||||||
|
|
||||||
|
renderDoc.addEventListener('click', (event: MouseEvent) => {
|
||||||
|
const windowWidth = window.innerWidth;
|
||||||
|
const windowHeight = window.innerHeight;
|
||||||
|
const barPixels = windowHeight * 0.2;
|
||||||
|
const pagePixels = windowWidth * 0.2;
|
||||||
|
const top = barPixels;
|
||||||
|
const bottom = window.innerHeight - top;
|
||||||
|
const left = pagePixels;
|
||||||
|
const right = windowWidth - left;
|
||||||
|
const leftOffset = this.rendition.views().container.scrollLeft;
|
||||||
|
const yCoord = event.clientY;
|
||||||
|
const xCoord = event.clientX - leftOffset;
|
||||||
|
|
||||||
|
if (yCoord < top || yCoord > bottom) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (xCoord < left) {
|
||||||
|
void prevPage();
|
||||||
|
} else if (xCoord > right) {
|
||||||
|
void nextPage();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private initDocumentListeners() {
|
||||||
|
const nextPage = this.nextPage.bind(this);
|
||||||
|
const prevPage = this.prevPage.bind(this);
|
||||||
|
|
||||||
|
this.keyupHandler = (event: KeyboardEvent) => {
|
||||||
|
if ((event.keyCode || event.which) === 37) {
|
||||||
|
void prevPage();
|
||||||
|
}
|
||||||
|
if ((event.keyCode || event.which) === 39) {
|
||||||
|
void nextPage();
|
||||||
|
}
|
||||||
|
if ((event.keyCode || event.which) === 84) {
|
||||||
|
const currentTheme = this.readerSettings.theme?.colorScheme || 'tan';
|
||||||
|
const currentThemeIdx = THEMES.indexOf(currentTheme);
|
||||||
|
const colorScheme =
|
||||||
|
THEMES.length === currentThemeIdx + 1 ? THEMES[0] : THEMES[currentThemeIdx + 1];
|
||||||
|
if (colorScheme) {
|
||||||
|
this.setTheme({ colorScheme });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
document.addEventListener('keyup', this.keyupHandler, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async setupReader() {
|
||||||
|
this.bookState.words = await this.countWords();
|
||||||
|
const { cfi } = await this.getCFIFromXPath(this.bookState.progress);
|
||||||
|
await this.setPosition(cfi);
|
||||||
|
const { element } = await this.getCFIFromXPath(this.bookState.progress);
|
||||||
|
this.bookState.progressElement = element ?? null;
|
||||||
|
this.highlightPositionMarker();
|
||||||
|
const stats = await this.getBookStats();
|
||||||
|
this.onStats(stats);
|
||||||
|
this.bookState.pageStart = Date.now();
|
||||||
|
this.onToc(this.getParsedTOC());
|
||||||
|
this.onLoading(false);
|
||||||
|
this.onReady();
|
||||||
|
}
|
||||||
|
|
||||||
|
private getParsedTOC(): ReaderTocItem[] {
|
||||||
|
if (!this.book.navigation?.toc) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.book.navigation.toc.reduce((agg: ReaderTocItem[], item) => {
|
||||||
|
const sectionTitle = item.label?.trim() ?? '';
|
||||||
|
agg.push({ title: sectionTitle || 'Untitled', href: item.href });
|
||||||
|
if (!item.subitems || item.subitems.length === 0) {
|
||||||
|
return agg;
|
||||||
|
}
|
||||||
|
|
||||||
|
const allSubSections = item.subitems.map(subitem => {
|
||||||
|
let itemTitle = subitem.label?.trim() ?? 'Untitled';
|
||||||
|
if (sectionTitle !== '') {
|
||||||
|
itemTitle = `${sectionTitle} - ${itemTitle}`;
|
||||||
|
}
|
||||||
|
return { title: itemTitle, href: subitem.href };
|
||||||
|
});
|
||||||
|
agg.push(...allSubSections);
|
||||||
|
return agg;
|
||||||
|
}, []);
|
||||||
|
}
|
||||||
|
|
||||||
|
setTheme(newTheme?: {
|
||||||
|
colorScheme?: ReaderColorScheme;
|
||||||
|
fontFamily?: string;
|
||||||
|
fontSize?: number;
|
||||||
|
}) {
|
||||||
|
this.readerSettings.theme =
|
||||||
|
typeof this.readerSettings.theme === 'object' && this.readerSettings.theme !== null
|
||||||
|
? this.readerSettings.theme
|
||||||
|
: {};
|
||||||
|
|
||||||
|
Object.assign(this.readerSettings.theme, newTheme);
|
||||||
|
|
||||||
|
const colorScheme = this.readerSettings.theme.colorScheme || 'tan';
|
||||||
|
const fontFamily = this.readerSettings.theme.fontFamily || 'serif';
|
||||||
|
const fontSize = this.readerSettings.theme.fontSize || 1;
|
||||||
|
|
||||||
|
this.rendition.themes.select(colorScheme);
|
||||||
|
|
||||||
|
const themeColorEl = document.querySelector("[name='theme-color']");
|
||||||
|
const themeStyleSheet = (document.querySelector('#themes') as HTMLLinkElement | null)?.sheet;
|
||||||
|
const themeStyleRule = themeStyleSheet
|
||||||
|
? Array.from(themeStyleSheet.cssRules).find(
|
||||||
|
item => (item as CSSStyleRule).selectorText === `.${colorScheme}`
|
||||||
|
)
|
||||||
|
: null;
|
||||||
|
|
||||||
|
if (!themeStyleRule) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const backgroundColor = (themeStyleRule as CSSStyleRule).style.backgroundColor;
|
||||||
|
themeColorEl?.setAttribute('content', backgroundColor);
|
||||||
|
document.body.style.backgroundColor = backgroundColor;
|
||||||
|
|
||||||
|
this.rendition.getContents().forEach(item => {
|
||||||
|
item.document.documentElement.style.setProperty('--editor-font-family', fontFamily);
|
||||||
|
item.document.documentElement.style.setProperty('--editor-font-size', `${fontSize}em`);
|
||||||
|
item.document.querySelectorAll('.highlight').forEach(element => {
|
||||||
|
Object.assign((element as HTMLElement).style, {
|
||||||
|
background: backgroundColor,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
highlightPositionMarker() {
|
||||||
|
if (!this.bookState.progressElement) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.rendition.getContents().forEach(item => {
|
||||||
|
item.document.querySelectorAll('.highlight').forEach(element => {
|
||||||
|
element.removeAttribute('style');
|
||||||
|
element.classList.remove('highlight');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const backgroundColor = getComputedStyle(this.bookState.progressElement.ownerDocument.body).backgroundColor;
|
||||||
|
|
||||||
|
Object.assign((this.bookState.progressElement as HTMLElement).style, {
|
||||||
|
background: backgroundColor,
|
||||||
|
filter: 'invert(0.2)',
|
||||||
|
});
|
||||||
|
this.bookState.progressElement.classList.add('highlight');
|
||||||
|
}
|
||||||
|
|
||||||
|
async nextPage() {
|
||||||
|
await this.createActivity();
|
||||||
|
await this.rendition.next();
|
||||||
|
this.bookState.pageStart = Date.now();
|
||||||
|
const stats = await this.getBookStats();
|
||||||
|
this.onStats(stats);
|
||||||
|
void this.createProgress();
|
||||||
|
}
|
||||||
|
|
||||||
|
async prevPage() {
|
||||||
|
await this.rendition.prev();
|
||||||
|
this.bookState.pageStart = Date.now();
|
||||||
|
const stats = await this.getBookStats();
|
||||||
|
this.onStats(stats);
|
||||||
|
void this.createProgress();
|
||||||
|
}
|
||||||
|
|
||||||
|
async displayHref(href: string) {
|
||||||
|
await this.rendition.display(href);
|
||||||
|
}
|
||||||
|
|
||||||
|
async setPosition(cfi?: string) {
|
||||||
|
if (!cfi) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.rendition.display(cfi);
|
||||||
|
await this.rendition.display(cfi);
|
||||||
|
await this.rendition.display(cfi);
|
||||||
|
this.highlightPositionMarker();
|
||||||
|
}
|
||||||
|
|
||||||
|
async applyThemeChange(newTheme: {
|
||||||
|
colorScheme?: ReaderColorScheme;
|
||||||
|
fontFamily?: string;
|
||||||
|
fontSize?: number;
|
||||||
|
}) {
|
||||||
|
const currentProgress = this.bookState.progress;
|
||||||
|
const { cfi } = await this.getCFIFromXPath(currentProgress);
|
||||||
|
this.setTheme(newTheme);
|
||||||
|
await this.setPosition(cfi);
|
||||||
|
const { element } = await this.getCFIFromXPath(currentProgress);
|
||||||
|
this.bookState.progressElement = element ?? null;
|
||||||
|
this.highlightPositionMarker();
|
||||||
|
}
|
||||||
|
|
||||||
|
async createActivity() {
|
||||||
|
const WPM_MAX = 2000;
|
||||||
|
const WPM_MIN = 100;
|
||||||
|
|
||||||
|
const pageStart = this.bookState.pageStart;
|
||||||
|
let elapsedTime = Date.now() - pageStart;
|
||||||
|
const pageWords = await this.getVisibleWordCount();
|
||||||
|
const currentWord = await this.getBookWordPosition();
|
||||||
|
const percentRead = pageWords / this.bookState.words;
|
||||||
|
const pageWPM = pageWords / (elapsedTime / 60000);
|
||||||
|
|
||||||
|
if (pageWPM >= WPM_MAX) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (pageWPM < WPM_MIN) {
|
||||||
|
elapsedTime = (pageWords / WPM_MIN) * 60000;
|
||||||
|
}
|
||||||
|
|
||||||
|
const totalPages = Math.round(1 / percentRead);
|
||||||
|
if (totalPages === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentPage = Math.round((currentWord * totalPages) / this.bookState.words);
|
||||||
|
|
||||||
|
await fetch('/api/v1/activity', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({
|
||||||
|
device_id: this.deviceId,
|
||||||
|
device_name: this.deviceName,
|
||||||
|
activity: [
|
||||||
|
{
|
||||||
|
document_id: this.documentId,
|
||||||
|
duration: Math.round(elapsedTime / 1000),
|
||||||
|
start_time: Math.round(pageStart / 1000),
|
||||||
|
page: currentPage,
|
||||||
|
pages: totalPages,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async createProgress() {
|
||||||
|
const currentCFI = await this.rendition.currentLocation();
|
||||||
|
const { element, xpath } = await this.getXPathFromCFI(currentCFI.start.cfi);
|
||||||
|
const currentWord = await this.getBookWordPosition();
|
||||||
|
this.bookState.progress = xpath ?? '';
|
||||||
|
this.bookState.progressElement = element ?? null;
|
||||||
|
|
||||||
|
const percentage =
|
||||||
|
this.bookState.words > 0
|
||||||
|
? Math.round((currentWord / this.bookState.words) * 100000) / 100000
|
||||||
|
: 0;
|
||||||
|
this.bookState.percentage = Math.round(percentage * 10000) / 100;
|
||||||
|
|
||||||
|
await fetch('/api/v1/progress', {
|
||||||
|
method: 'PUT',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({
|
||||||
|
document_id: this.documentId,
|
||||||
|
device_id: this.deviceId,
|
||||||
|
device_name: this.deviceName,
|
||||||
|
percentage,
|
||||||
|
progress: this.bookState.progress,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
sectionProgress() {
|
||||||
|
const visibleItems = this.rendition.manager?.visible?.() ?? [];
|
||||||
|
if (visibleItems.length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const visibleSection = visibleItems[0];
|
||||||
|
if (!visibleSection) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const totalBlocks = visibleSection.width() / visibleSection.layout.width;
|
||||||
|
const leftOffset = this.rendition.views().container.scrollLeft;
|
||||||
|
const sectionCurrentPage = Math.round(leftOffset / visibleSection.layout.width) + 1;
|
||||||
|
|
||||||
|
return {
|
||||||
|
sectionPages: totalBlocks,
|
||||||
|
sectionCurrentPage,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async getBookStats(): Promise<ReaderStats> {
|
||||||
|
const currentProgress = this.sectionProgress();
|
||||||
|
if (!currentProgress) {
|
||||||
|
return {
|
||||||
|
sectionPage: 0,
|
||||||
|
sectionTotalPages: 0,
|
||||||
|
chapterName: 'N/A',
|
||||||
|
percentage: this.bookState.percentage,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentLocation = await this.rendition.currentLocation();
|
||||||
|
const currentWord = await this.getBookWordPosition();
|
||||||
|
const currentTOC = this.book.navigation?.toc?.find(item => item.href === currentLocation.start.href);
|
||||||
|
|
||||||
|
return {
|
||||||
|
sectionPage: currentProgress.sectionCurrentPage,
|
||||||
|
sectionTotalPages: currentProgress.sectionPages,
|
||||||
|
chapterName: currentTOC ? currentTOC.label?.trim() || 'N/A' : 'N/A',
|
||||||
|
percentage:
|
||||||
|
this.bookState.words > 0
|
||||||
|
? Math.round((currentWord / this.bookState.words) * 10000) / 100
|
||||||
|
: 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async getXPathFromCFI(cfi: string) {
|
||||||
|
const cfiBaseMatch = cfi.match(/\(([^!]+)/);
|
||||||
|
if (!cfiBaseMatch?.[1]) {
|
||||||
|
return {} as { xpath?: string; element?: Element | null };
|
||||||
|
}
|
||||||
|
const startCFI = cfiBaseMatch[1];
|
||||||
|
|
||||||
|
const docFragmentIndex =
|
||||||
|
(this.book.spine.spineItems.find(item => item.cfiBase === startCFI)?.index ?? -1) + 1;
|
||||||
|
if (docFragmentIndex <= 0) {
|
||||||
|
return {} as { xpath?: string; element?: Element | null };
|
||||||
|
}
|
||||||
|
|
||||||
|
const basePos = `/body/DocFragment[${docFragmentIndex}]/body`;
|
||||||
|
const contents = this.rendition.getContents()[0];
|
||||||
|
const currentNodeStart = contents?.range(cfi).startContainer;
|
||||||
|
if (!currentNodeStart) {
|
||||||
|
return {} as { xpath?: string; element?: Element | null };
|
||||||
|
}
|
||||||
|
|
||||||
|
let currentNode: Node | null = currentNodeStart;
|
||||||
|
const element =
|
||||||
|
currentNode.nodeType === Node.ELEMENT_NODE
|
||||||
|
? (currentNode as Element)
|
||||||
|
: currentNode.parentElement;
|
||||||
|
|
||||||
|
let allPos = '';
|
||||||
|
while (currentNode && currentNode.nodeName !== 'BODY') {
|
||||||
|
let parentElement: Element | null = currentNode.parentElement;
|
||||||
|
if (!parentElement) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentNode.nodeType !== Node.ELEMENT_NODE) {
|
||||||
|
currentNode = parentElement;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (parentElement.nodeName === 'A' && parentElement.parentElement) {
|
||||||
|
parentElement = parentElement.parentElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentElement = currentNode as Element;
|
||||||
|
const allDescendents = parentElement.querySelectorAll(currentElement.nodeName);
|
||||||
|
const relativeIndex = Array.from(allDescendents).indexOf(currentElement) + 1;
|
||||||
|
const nodePos = `${currentElement.nodeName.toLowerCase()}[${relativeIndex}]`;
|
||||||
|
currentNode = parentElement;
|
||||||
|
allPos = `/${nodePos}${allPos}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return { xpath: `${basePos}${allPos}`, element };
|
||||||
|
}
|
||||||
|
|
||||||
|
async getCFIFromXPath(xpath?: string) {
|
||||||
|
if (!xpath) {
|
||||||
|
return {} as { cfi?: string; element?: Element | null };
|
||||||
|
}
|
||||||
|
|
||||||
|
const fragMatch = xpath.match(/^\/body\/DocFragment\[(\d+)\]/);
|
||||||
|
if (!fragMatch?.[1]) {
|
||||||
|
return {} as { cfi?: string; element?: Element | null };
|
||||||
|
}
|
||||||
|
|
||||||
|
const spinePosition = Number.parseInt(fragMatch[1], 10) - 1;
|
||||||
|
const sectionItem = this.book.spine.get(spinePosition);
|
||||||
|
await sectionItem.load(this.book.load.bind(this.book));
|
||||||
|
|
||||||
|
const renderedContent = this.rendition
|
||||||
|
.getContents()
|
||||||
|
.find(item => item.sectionIndex == spinePosition);
|
||||||
|
const docItem = renderedContent?.document || sectionItem.document;
|
||||||
|
|
||||||
|
const namespaceURI = docItem.documentElement.namespaceURI;
|
||||||
|
let remainingXPath = xpath
|
||||||
|
.replace(fragMatch[0], '/html')
|
||||||
|
.replace(/\.(\d+)$/, '')
|
||||||
|
.replace(/\/text\(\)(\[\d+\])?$/, '');
|
||||||
|
|
||||||
|
const derivedSelectorElement = remainingXPath
|
||||||
|
.replace(/^\/html\/body/, 'body')
|
||||||
|
.split('/')
|
||||||
|
.reduce((element: ParentNode | null, item: string) => {
|
||||||
|
if (!element) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const indexMatch = item.match(/(\w+)\[(\d+)\]$/);
|
||||||
|
if (!indexMatch) {
|
||||||
|
return element.querySelector(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
const [, tag, rawIndex] = indexMatch;
|
||||||
|
if (!tag || !rawIndex) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return element.querySelectorAll(tag)[Number.parseInt(rawIndex, 10) - 1] ?? null;
|
||||||
|
}, docItem as ParentNode | null);
|
||||||
|
|
||||||
|
if (namespaceURI) {
|
||||||
|
remainingXPath = remainingXPath.split('/').join('/ns:');
|
||||||
|
}
|
||||||
|
|
||||||
|
const docSearch = docItem.evaluate(
|
||||||
|
remainingXPath,
|
||||||
|
docItem,
|
||||||
|
prefix => {
|
||||||
|
if (prefix === 'ns') {
|
||||||
|
return namespaceURI;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const xpathElement = docSearch.iterateNext();
|
||||||
|
const element = xpathElement || derivedSelectorElement;
|
||||||
|
const isElementNode = Boolean(element && (element as Node).nodeType === Node.ELEMENT_NODE);
|
||||||
|
if (!isElementNode) {
|
||||||
|
return {} as { cfi?: string; element?: Element | null };
|
||||||
|
}
|
||||||
|
|
||||||
|
const resolvedElement = element as Element;
|
||||||
|
|
||||||
|
let cfi = sectionItem.cfiFromElement(resolvedElement);
|
||||||
|
if (cfi.endsWith('!/)')) {
|
||||||
|
cfi = `${cfi.slice(0, -1)}0)`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return { cfi, element: resolvedElement };
|
||||||
|
}
|
||||||
|
|
||||||
|
async getVisibleWordCount() {
|
||||||
|
const visibleText = await this.getVisibleText();
|
||||||
|
return visibleText.trim().split(/\s+/).filter(Boolean).length;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getBookWordPosition() {
|
||||||
|
const contents = this.rendition.getContents()[0];
|
||||||
|
if (!contents) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const spineItem = this.book.spine.get(contents.sectionIndex ?? 0);
|
||||||
|
const firstElement = spineItem.document.body.children[0];
|
||||||
|
if (!firstElement) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const firstCFI = spineItem.cfiFromElement(firstElement);
|
||||||
|
const currentLocation = await this.rendition.currentLocation();
|
||||||
|
const cfiRange = this.getCFIRange(firstCFI, currentLocation.start.cfi);
|
||||||
|
const textRange = await this.book.getRange(cfiRange);
|
||||||
|
const chapterText = textRange.toString();
|
||||||
|
const chapterWordPosition = chapterText.trim().split(/\s+/).filter(Boolean).length;
|
||||||
|
const preChapterWordPosition = this.book.spine.spineItems
|
||||||
|
.slice(0, contents.sectionIndex ?? 0)
|
||||||
|
.reduce((totalCount, item) => totalCount + (item.wordCount ?? 0), 0);
|
||||||
|
|
||||||
|
return chapterWordPosition + preChapterWordPosition;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getVisibleText() {
|
||||||
|
this.rendition.manager?.visible?.()?.forEach(item => item.expand());
|
||||||
|
const currentLocation = await this.rendition.currentLocation();
|
||||||
|
const cfiRange = this.getCFIRange(currentLocation.start.cfi, currentLocation.end.cfi);
|
||||||
|
const textRange = await this.book.getRange(cfiRange);
|
||||||
|
return textRange.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
getCFIRange(a: string, b: string) {
|
||||||
|
const CFI = new (ePub as unknown as EpubWithCfiConstructor).CFI();
|
||||||
|
const start = CFI.parse(a);
|
||||||
|
const end = CFI.parse(b);
|
||||||
|
const cfi: {
|
||||||
|
range: boolean;
|
||||||
|
base: unknown;
|
||||||
|
path: ParsedCfiPath;
|
||||||
|
start: ParsedCfiPath;
|
||||||
|
end: ParsedCfiPath;
|
||||||
|
} = {
|
||||||
|
range: true,
|
||||||
|
base: start.base,
|
||||||
|
path: { steps: [], terminal: null },
|
||||||
|
start: start.path,
|
||||||
|
end: end.path,
|
||||||
|
};
|
||||||
|
|
||||||
|
const len = cfi.start.steps.length;
|
||||||
|
for (let i = 0; i < len; i += 1) {
|
||||||
|
if (CFI.equalStep(cfi.start.steps[i], cfi.end.steps[i])) {
|
||||||
|
if (i === len - 1) {
|
||||||
|
if (cfi.start.terminal === cfi.end.terminal) {
|
||||||
|
cfi.path.steps.push(cfi.start.steps[i]);
|
||||||
|
cfi.range = false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
cfi.path.steps.push(cfi.start.steps[i]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cfi.start.steps = cfi.start.steps.slice(cfi.path.steps.length);
|
||||||
|
cfi.end.steps = cfi.end.steps.slice(cfi.path.steps.length);
|
||||||
|
|
||||||
|
return `epubcfi(${CFI.segmentString(cfi.base)}!${CFI.segmentString(cfi.path)},${CFI.segmentString(cfi.start)},${CFI.segmentString(cfi.end)})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
async countWords() {
|
||||||
|
const spineWC = await Promise.all(
|
||||||
|
this.book.spine.spineItems.map(async item => {
|
||||||
|
const newDoc = await item.load(this.book.load.bind(this.book));
|
||||||
|
const spineWords = ((newDoc as unknown as HTMLElement).innerText || '')
|
||||||
|
.trim()
|
||||||
|
.split(/\s+/)
|
||||||
|
.filter(Boolean).length;
|
||||||
|
item.wordCount = spineWords;
|
||||||
|
return spineWords;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
return spineWC.reduce((totalCount, itemCount) => totalCount + itemCount, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy() {
|
||||||
|
this.destroyed = true;
|
||||||
|
if (this.keyupHandler) {
|
||||||
|
document.removeEventListener('keyup', this.keyupHandler, false);
|
||||||
|
}
|
||||||
|
document.removeEventListener('wakelock', this.handleWakeLock);
|
||||||
|
if (this.wakeTimeoutId) {
|
||||||
|
clearTimeout(this.wakeTimeoutId);
|
||||||
|
}
|
||||||
|
void this.noSleep?.disable();
|
||||||
|
this.rendition.destroy?.();
|
||||||
|
this.book.destroy?.();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -125,7 +125,7 @@ export default function DocumentPage() {
|
|||||||
|
|
||||||
{document.filepath && (
|
{document.filepath && (
|
||||||
<a
|
<a
|
||||||
href={`/reader#id=${document.id}&type=REMOTE`}
|
href={`/reader/${document.id}`}
|
||||||
className="z-10 mt-2 w-full rounded bg-secondary-700 py-1 text-center text-sm font-medium text-white hover:bg-secondary-800 focus:outline-none focus:ring-4 focus:ring-secondary-300 dark:bg-secondary-600 dark:hover:bg-secondary-700"
|
className="z-10 mt-2 w-full rounded bg-secondary-700 py-1 text-center text-sm font-medium text-white hover:bg-secondary-800 focus:outline-none focus:ring-4 focus:ring-secondary-300 dark:bg-secondary-600 dark:hover:bg-secondary-700"
|
||||||
>
|
>
|
||||||
Read
|
Read
|
||||||
|
|||||||
298
frontend/src/pages/ReaderPage.tsx
Normal file
298
frontend/src/pages/ReaderPage.tsx
Normal file
@@ -0,0 +1,298 @@
|
|||||||
|
import { useEffect, useMemo, useState } from 'react';
|
||||||
|
import { Link, useParams } from 'react-router-dom';
|
||||||
|
import { useGetDocument, useGetProgress } from '../generated/anthoLumeAPIV1';
|
||||||
|
import { LoadingState } from '../components/LoadingState';
|
||||||
|
import { CloseIcon } from '../icons';
|
||||||
|
import {
|
||||||
|
getReaderColorScheme,
|
||||||
|
getReaderDevice,
|
||||||
|
getReaderFontFamily,
|
||||||
|
getReaderFontSize,
|
||||||
|
setReaderColorScheme,
|
||||||
|
setReaderFontFamily,
|
||||||
|
setReaderFontSize,
|
||||||
|
type ReaderColorScheme,
|
||||||
|
type ReaderFontFamily,
|
||||||
|
} from '../utils/localSettings';
|
||||||
|
import { useEpubReader } from '../hooks/useEpubReader';
|
||||||
|
|
||||||
|
const colorSchemes: ReaderColorScheme[] = ['light', 'tan', 'blue', 'gray', 'black'];
|
||||||
|
const fontFamilies: ReaderFontFamily[] = ['Serif', 'Open Sans', 'Arbutus Slab', 'Lato'];
|
||||||
|
|
||||||
|
export default function ReaderPage() {
|
||||||
|
const { id } = useParams<{ id: string }>();
|
||||||
|
const [isTopBarOpen, setIsTopBarOpen] = useState(false);
|
||||||
|
const [isBottomBarOpen, setIsBottomBarOpen] = useState(true);
|
||||||
|
const [colorScheme, setColorSchemeState] = useState<ReaderColorScheme>(getReaderColorScheme());
|
||||||
|
const [fontFamily, setFontFamilyState] = useState<ReaderFontFamily>(getReaderFontFamily());
|
||||||
|
const [fontSize, setFontSizeState] = useState<number>(getReaderFontSize());
|
||||||
|
|
||||||
|
const { id: defaultDeviceId, name: defaultDeviceName } = useMemo(() => getReaderDevice(), []);
|
||||||
|
|
||||||
|
const { data: documentResponse, isLoading: isDocumentLoading } = useGetDocument(id || '');
|
||||||
|
const { data: progressResponse, isLoading: isProgressLoading } = useGetProgress(id || '', {
|
||||||
|
query: {
|
||||||
|
retry: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const document = documentResponse?.status === 200 ? documentResponse.data.document : null;
|
||||||
|
const progress = progressResponse?.status === 200 ? progressResponse.data.progress : undefined;
|
||||||
|
|
||||||
|
const deviceId = defaultDeviceId;
|
||||||
|
const deviceName = defaultDeviceName;
|
||||||
|
|
||||||
|
const reader = useEpubReader({
|
||||||
|
documentId: id || '',
|
||||||
|
initialProgress: progress?.progress,
|
||||||
|
deviceId,
|
||||||
|
deviceName,
|
||||||
|
colorScheme,
|
||||||
|
fontFamily,
|
||||||
|
fontSize,
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (document?.title) {
|
||||||
|
window.document.title = `AnthoLume - Reader - ${document.title}`;
|
||||||
|
}
|
||||||
|
}, [document?.title]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
reader.setTheme({ colorScheme, fontFamily, fontSize });
|
||||||
|
}, [colorScheme, fontFamily, fontSize, reader.setTheme]);
|
||||||
|
|
||||||
|
if (isDocumentLoading || isProgressLoading) {
|
||||||
|
return <LoadingState className="min-h-screen bg-canvas" message="Loading reader..." />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!id || !document || documentResponse?.status !== 200) {
|
||||||
|
return <div className="p-6 text-content-muted">Document not found</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="fixed inset-0 z-50 bg-canvas text-content">
|
||||||
|
<div className="relative flex h-dvh flex-col overflow-hidden">
|
||||||
|
<div
|
||||||
|
className={`absolute inset-x-0 top-0 z-20 border-b border-border bg-surface/95 backdrop-blur transition-transform duration-200 ${
|
||||||
|
isTopBarOpen ? 'translate-y-0' : '-translate-y-full'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<div className="mx-auto flex max-h-[70vh] w-full max-w-6xl flex-col gap-4 overflow-auto p-4">
|
||||||
|
<div className="flex items-start justify-between gap-4">
|
||||||
|
<div className="flex min-w-0 items-start gap-4">
|
||||||
|
<Link to={`/documents/${document.id}`} className="block shrink-0">
|
||||||
|
<img
|
||||||
|
className="h-28 w-20 rounded object-cover shadow"
|
||||||
|
src={`/api/v1/documents/${document.id}/cover`}
|
||||||
|
alt={`${document.title} cover`}
|
||||||
|
/>
|
||||||
|
</Link>
|
||||||
|
<div className="min-w-0">
|
||||||
|
<p className="text-xs uppercase tracking-wide text-content-subtle">Title</p>
|
||||||
|
<p className="truncate text-lg font-semibold text-content">{document.title}</p>
|
||||||
|
<p className="mt-3 text-xs uppercase tracking-wide text-content-subtle">Author</p>
|
||||||
|
<p className="truncate text-sm text-content-muted">{document.author}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Link
|
||||||
|
to={`/documents/${document.id}`}
|
||||||
|
className="rounded border border-border px-3 py-2 text-sm text-content-muted hover:bg-surface-muted hover:text-content"
|
||||||
|
>
|
||||||
|
Back
|
||||||
|
</Link>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => setIsTopBarOpen(false)}
|
||||||
|
className="rounded border border-border p-2 text-content-muted hover:bg-surface-muted hover:text-content"
|
||||||
|
aria-label="Close reader details"
|
||||||
|
>
|
||||||
|
<CloseIcon size={18} />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid gap-2 pb-2 sm:grid-cols-2 lg:grid-cols-3">
|
||||||
|
{reader.toc.map(item => (
|
||||||
|
<button
|
||||||
|
key={`${item.href}-${item.title}`}
|
||||||
|
type="button"
|
||||||
|
onClick={() => {
|
||||||
|
void reader.goToHref(item.href);
|
||||||
|
setIsTopBarOpen(false);
|
||||||
|
}}
|
||||||
|
className="truncate rounded border border-border bg-surface px-3 py-2 text-left text-sm text-content-muted hover:bg-surface-muted hover:text-content"
|
||||||
|
>
|
||||||
|
{item.title}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="absolute left-4 top-4 z-10 flex gap-2">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => setIsTopBarOpen(open => !open)}
|
||||||
|
className="rounded bg-surface/90 px-3 py-2 text-sm font-medium text-content shadow backdrop-blur hover:bg-surface"
|
||||||
|
>
|
||||||
|
Contents
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => setIsBottomBarOpen(open => !open)}
|
||||||
|
className="rounded bg-surface/90 px-3 py-2 text-sm font-medium text-content shadow backdrop-blur hover:bg-surface"
|
||||||
|
>
|
||||||
|
Controls
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="absolute inset-0 pt-[env(safe-area-inset-top)]">
|
||||||
|
{reader.isLoading && (
|
||||||
|
<LoadingState
|
||||||
|
className="absolute inset-0 z-10 min-h-full bg-canvas"
|
||||||
|
message="Opening book..."
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{reader.error ? (
|
||||||
|
<div className="flex h-full items-center justify-center p-6 text-content-muted">
|
||||||
|
{reader.error}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div ref={reader.viewerRef} className="size-full bg-canvas" />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
className={`absolute inset-x-0 bottom-0 z-20 border-t border-border bg-surface/95 backdrop-blur transition-transform duration-200 ${
|
||||||
|
isBottomBarOpen ? 'translate-y-0' : 'translate-y-full'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<div className="mx-auto flex max-w-6xl flex-col gap-4 p-4">
|
||||||
|
<div className="flex flex-wrap items-center justify-between gap-3 text-sm text-content-muted">
|
||||||
|
<div>
|
||||||
|
<span className="text-content-subtle">Chapter:</span> {reader.stats.chapterName}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span className="text-content-subtle">Chapter Pages:</span>{' '}
|
||||||
|
{reader.stats.sectionPage} / {reader.stats.sectionTotalPages}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span className="text-content-subtle">Progress:</span>{' '}
|
||||||
|
{reader.stats.percentage.toFixed(2)}%
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="h-2 overflow-hidden rounded-full bg-surface-strong">
|
||||||
|
<div
|
||||||
|
className="h-full bg-tertiary-500 transition-all"
|
||||||
|
style={{ width: `${reader.stats.percentage}%` }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid gap-4 lg:grid-cols-[1fr_1fr_1fr_auto]">
|
||||||
|
<div>
|
||||||
|
<p className="mb-2 text-xs uppercase tracking-wide text-content-subtle">Theme</p>
|
||||||
|
<div className="flex flex-wrap gap-2">
|
||||||
|
{colorSchemes.map(option => (
|
||||||
|
<button
|
||||||
|
key={option}
|
||||||
|
type="button"
|
||||||
|
onClick={() => {
|
||||||
|
setColorSchemeState(option);
|
||||||
|
setReaderColorScheme(option);
|
||||||
|
}}
|
||||||
|
className={`rounded border px-3 py-2 text-sm capitalize ${
|
||||||
|
colorScheme === option
|
||||||
|
? 'border-primary-500 bg-primary-500/10 text-content'
|
||||||
|
: 'border-border text-content-muted hover:bg-surface-muted hover:text-content'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{option}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<p className="mb-2 text-xs uppercase tracking-wide text-content-subtle">Font</p>
|
||||||
|
<div className="flex flex-wrap gap-2">
|
||||||
|
{fontFamilies.map(option => (
|
||||||
|
<button
|
||||||
|
key={option}
|
||||||
|
type="button"
|
||||||
|
onClick={() => {
|
||||||
|
setFontFamilyState(option);
|
||||||
|
setReaderFontFamily(option);
|
||||||
|
}}
|
||||||
|
className={`rounded border px-3 py-2 text-sm ${
|
||||||
|
fontFamily === option
|
||||||
|
? 'border-primary-500 bg-primary-500/10 text-content'
|
||||||
|
: 'border-border text-content-muted hover:bg-surface-muted hover:text-content'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{option}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<p className="mb-2 text-xs uppercase tracking-wide text-content-subtle">
|
||||||
|
Font Size
|
||||||
|
</p>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => {
|
||||||
|
const nextSize = Math.max(0.8, Number((fontSize - 0.1).toFixed(2)));
|
||||||
|
setFontSizeState(nextSize);
|
||||||
|
setReaderFontSize(nextSize);
|
||||||
|
}}
|
||||||
|
className="rounded border border-border px-3 py-2 text-content-muted hover:bg-surface-muted hover:text-content"
|
||||||
|
>
|
||||||
|
-
|
||||||
|
</button>
|
||||||
|
<div className="min-w-16 text-center text-sm text-content">
|
||||||
|
{fontSize.toFixed(1)}x
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => {
|
||||||
|
const nextSize = Math.min(2.2, Number((fontSize + 0.1).toFixed(2)));
|
||||||
|
setFontSizeState(nextSize);
|
||||||
|
setReaderFontSize(nextSize);
|
||||||
|
}}
|
||||||
|
className="rounded border border-border px-3 py-2 text-content-muted hover:bg-surface-muted hover:text-content"
|
||||||
|
>
|
||||||
|
+
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-end gap-2">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => void reader.prevPage()}
|
||||||
|
disabled={!reader.isReady}
|
||||||
|
className="rounded bg-secondary-700 px-4 py-2 text-sm font-medium text-white hover:bg-secondary-800 disabled:cursor-not-allowed disabled:opacity-50"
|
||||||
|
>
|
||||||
|
Previous
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => void reader.nextPage()}
|
||||||
|
disabled={!reader.isReady}
|
||||||
|
className="rounded bg-secondary-700 px-4 py-2 text-sm font-medium text-white hover:bg-secondary-800 disabled:cursor-not-allowed disabled:opacity-50"
|
||||||
|
>
|
||||||
|
Next
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
4
frontend/src/types/epubjs.d.ts
vendored
Normal file
4
frontend/src/types/epubjs.d.ts
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
declare module 'epubjs' {
|
||||||
|
const ePub: (...args: unknown[]) => unknown;
|
||||||
|
export default ePub;
|
||||||
|
}
|
||||||
@@ -1,11 +1,18 @@
|
|||||||
export type ThemeMode = 'light' | 'dark' | 'system';
|
export type ThemeMode = 'light' | 'dark' | 'system';
|
||||||
export type DocumentsViewMode = 'grid' | 'list';
|
export type DocumentsViewMode = 'grid' | 'list';
|
||||||
|
export type ReaderColorScheme = 'light' | 'tan' | 'blue' | 'gray' | 'black';
|
||||||
|
export type ReaderFontFamily = 'Serif' | 'Open Sans' | 'Arbutus Slab' | 'Lato';
|
||||||
|
|
||||||
const LOCAL_SETTINGS_KEY = 'antholume:settings';
|
const LOCAL_SETTINGS_KEY = 'antholume:settings';
|
||||||
|
|
||||||
interface LocalSettings {
|
interface LocalSettings {
|
||||||
themeMode?: ThemeMode;
|
themeMode?: ThemeMode;
|
||||||
documentsViewMode?: DocumentsViewMode;
|
documentsViewMode?: DocumentsViewMode;
|
||||||
|
readerColorScheme?: ReaderColorScheme;
|
||||||
|
readerFontFamily?: ReaderFontFamily;
|
||||||
|
readerFontSize?: number;
|
||||||
|
readerDeviceId?: string;
|
||||||
|
readerDeviceName?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
function canUseLocalStorage(): boolean {
|
function canUseLocalStorage(): boolean {
|
||||||
@@ -64,3 +71,77 @@ export function getDocumentsViewMode(): DocumentsViewMode {
|
|||||||
export function setDocumentsViewMode(documentsViewMode: DocumentsViewMode): void {
|
export function setDocumentsViewMode(documentsViewMode: DocumentsViewMode): void {
|
||||||
updateLocalSettings({ documentsViewMode });
|
updateLocalSettings({ documentsViewMode });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getReaderColorScheme(): ReaderColorScheme {
|
||||||
|
const settings = readLocalSettings();
|
||||||
|
switch (settings.readerColorScheme) {
|
||||||
|
case 'light':
|
||||||
|
case 'tan':
|
||||||
|
case 'blue':
|
||||||
|
case 'gray':
|
||||||
|
case 'black':
|
||||||
|
return settings.readerColorScheme;
|
||||||
|
default:
|
||||||
|
return 'tan';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setReaderColorScheme(readerColorScheme: ReaderColorScheme): void {
|
||||||
|
updateLocalSettings({ readerColorScheme });
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getReaderFontFamily(): ReaderFontFamily {
|
||||||
|
const settings = readLocalSettings();
|
||||||
|
switch (settings.readerFontFamily) {
|
||||||
|
case 'Serif':
|
||||||
|
case 'Open Sans':
|
||||||
|
case 'Arbutus Slab':
|
||||||
|
case 'Lato':
|
||||||
|
return settings.readerFontFamily;
|
||||||
|
default:
|
||||||
|
return 'Serif';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setReaderFontFamily(readerFontFamily: ReaderFontFamily): void {
|
||||||
|
updateLocalSettings({ readerFontFamily });
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getReaderFontSize(): number {
|
||||||
|
const settings = readLocalSettings();
|
||||||
|
return typeof settings.readerFontSize === 'number' && settings.readerFontSize > 0
|
||||||
|
? settings.readerFontSize
|
||||||
|
: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setReaderFontSize(readerFontSize: number): void {
|
||||||
|
updateLocalSettings({ readerFontSize });
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getReaderDevice(): { id: string; name: string } {
|
||||||
|
const settings = readLocalSettings();
|
||||||
|
const id =
|
||||||
|
typeof settings.readerDeviceId === 'string' && settings.readerDeviceId.length > 0
|
||||||
|
? settings.readerDeviceId
|
||||||
|
: crypto.randomUUID();
|
||||||
|
const name =
|
||||||
|
typeof settings.readerDeviceName === 'string' && settings.readerDeviceName.length > 0
|
||||||
|
? settings.readerDeviceName
|
||||||
|
: 'Web Reader';
|
||||||
|
|
||||||
|
if (id !== settings.readerDeviceId || name !== settings.readerDeviceName) {
|
||||||
|
updateLocalSettings({
|
||||||
|
readerDeviceId: id,
|
||||||
|
readerDeviceName: name,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return { id, name };
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setReaderDevice(name: string, id?: string): void {
|
||||||
|
updateLocalSettings({
|
||||||
|
readerDeviceId: id ?? crypto.randomUUID(),
|
||||||
|
readerDeviceName: name,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user