From 2f016703c470a018987b79ad0355cb730a02eaba Mon Sep 17 00:00:00 2001 From: moonmd Date: Sat, 7 Dec 2024 13:30:28 -0800 Subject: [PATCH] feat: latex rendering with katex --- package.json | 5 + pnpm-lock.yaml | 302 +++++++++++++++++++++------------------ src/api.ts | 3 +- src/exporter/html.ts | 41 +++++- src/exporter/json.ts | 2 +- src/exporter/markdown.ts | 4 +- src/template.html | 52 ++++++- 7 files changed, 261 insertions(+), 148 deletions(-) diff --git a/package.json b/package.json index b6a2d7b..94ff883 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,8 @@ "mdast-util-to-markdown": "^1.5.0", "micromark-extension-gfm": "^2.0.3", "preact": "^10.17.1", + "react": "^18.3.1", + "react-dom": "^18.3.1", "react-i18next": "^12.3.1", "sanitize-filename": "^1.6.3", "sentinel-js": "^0.0.7", @@ -56,12 +58,15 @@ "@commitlint/config-conventional": "^18.6.2", "@pionxzh/eslint-config": "^1.0.1", "@preact/preset-vite": "^2.8.3", + "@types/katex": "^0.16.7", "@types/mdast": "^3.0.15", "@types/node": "^20.11.20", "@types/unist": "^2.0.10", "eslint": "^8.57.0", "husky": "^9.0.11", + "katex": "^0.16.11", "lint-staged": "^15.2.7", + "react": "^18.3.1", "typescript": "^5.5.2", "vite": "^5.3.2" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3e3c0c2..24bad83 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -7,13 +7,13 @@ settings: dependencies: '@headlessui/react': specifier: ^1.7.14 - version: 1.7.14(react-dom@18.2.0)(react@18.2.0) + version: 1.7.14(react-dom@18.3.1)(react@18.3.1) '@radix-ui/react-dialog': specifier: ^1.0.3 - version: 1.0.3(react-dom@18.2.0)(react@18.2.0) + version: 1.0.3(react-dom@18.3.1)(react@18.3.1) '@radix-ui/react-hover-card': specifier: ^1.0.5 - version: 1.0.5(react-dom@18.2.0)(react@18.2.0) + version: 1.0.5(react-dom@18.3.1)(react@18.3.1) eventemitter3: specifier: ^5.0.1 version: 5.0.1 @@ -53,9 +53,15 @@ dependencies: preact: specifier: ^10.17.1 version: 10.17.1 + react: + specifier: ^18.3.1 + version: 18.3.1 + react-dom: + specifier: ^18.3.1 + version: 18.3.1(react@18.3.1) react-i18next: specifier: ^12.3.1 - version: 12.3.1(i18next@22.5.1)(react-dom@18.2.0)(react@18.2.0) + version: 12.3.1(i18next@22.5.1)(react-dom@18.3.1)(react@18.3.1) sanitize-filename: specifier: ^1.6.3 version: 1.6.3 @@ -82,6 +88,9 @@ devDependencies: '@preact/preset-vite': specifier: ^2.8.3 version: 2.8.3(@babel/core@7.23.2)(preact@10.17.1)(vite@5.3.2) + '@types/katex': + specifier: ^0.16.7 + version: 0.16.7 '@types/mdast': specifier: ^3.0.15 version: 3.0.15 @@ -97,9 +106,15 @@ devDependencies: husky: specifier: ^9.0.11 version: 9.0.11 + katex: + specifier: ^0.16.11 + version: 0.16.11 lint-staged: specifier: ^15.2.7 version: 15.2.7 + react: + specifier: ^18.3.1 + version: 18.3.1 typescript: specifier: ^5.5.2 version: 5.5.2 @@ -792,21 +807,21 @@ packages: '@floating-ui/core': 0.7.3 dev: false - /@floating-ui/react-dom@0.7.2(react-dom@18.2.0)(react@18.2.0): + /@floating-ui/react-dom@0.7.2(react-dom@18.3.1)(react@18.3.1): resolution: {integrity: sha512-1T0sJcpHgX/u4I1OzIEhlcrvkUN8ln39nz7fMoE/2HDHrPiMFoOGR7++GYyfUmIQHkkrTinaeQsO3XWubjSvGg==} peerDependencies: react: '>=16.8.0' react-dom: '>=16.8.0' dependencies: '@floating-ui/dom': 0.5.4 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - use-isomorphic-layout-effect: 1.1.2(react@18.2.0) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + use-isomorphic-layout-effect: 1.1.2(react@18.3.1) transitivePeerDependencies: - '@types/react' dev: false - /@headlessui/react@1.7.14(react-dom@18.2.0)(react@18.2.0): + /@headlessui/react@1.7.14(react-dom@18.3.1)(react@18.3.1): resolution: {integrity: sha512-znzdq9PG8rkwcu9oQ2FwIy0ZFtP9Z7ycS+BAqJ3R5EIqC/0bJGvhT7193rFf+45i9nnPsYvCQVW4V/bB9Xc+gA==} engines: {node: '>=10'} peerDependencies: @@ -814,8 +829,8 @@ packages: react-dom: ^16 || ^17 || ^18 dependencies: client-only: 0.0.1 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) dev: false /@humanwhocodes/config-array@0.11.14: @@ -996,37 +1011,37 @@ packages: '@babel/runtime': 7.21.0 dev: false - /@radix-ui/react-arrow@1.0.2(react-dom@18.2.0)(react@18.2.0): + /@radix-ui/react-arrow@1.0.2(react-dom@18.3.1)(react@18.3.1): resolution: {integrity: sha512-fqYwhhI9IarZ0ll2cUSfKuXHlJK0qE4AfnRrPBbRwEH/4mGQn04/QFGomLi8TXWIdv9WJk//KgGm+aDxVIr1wA==} peerDependencies: react: ^16.8 || ^17.0 || ^18.0 react-dom: ^16.8 || ^17.0 || ^18.0 dependencies: '@babel/runtime': 7.21.0 - '@radix-ui/react-primitive': 1.0.2(react-dom@18.2.0)(react@18.2.0) - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) + '@radix-ui/react-primitive': 1.0.2(react-dom@18.3.1)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) dev: false - /@radix-ui/react-compose-refs@1.0.0(react@18.2.0): + /@radix-ui/react-compose-refs@1.0.0(react@18.3.1): resolution: {integrity: sha512-0KaSv6sx787/hK3eF53iOkiSLwAGlFMx5lotrqD2pTjB18KbybKoEIgkNZTKC60YECDQTKGTRcDBILwZVqVKvA==} peerDependencies: react: ^16.8 || ^17.0 || ^18.0 dependencies: '@babel/runtime': 7.21.0 - react: 18.2.0 + react: 18.3.1 dev: false - /@radix-ui/react-context@1.0.0(react@18.2.0): + /@radix-ui/react-context@1.0.0(react@18.3.1): resolution: {integrity: sha512-1pVM9RfOQ+n/N5PJK33kRSKsr1glNxomxONs5c49MliinBY6Yw2Q995qfBUUo0/Mbg05B/sGA0gkgPI7kmSHBg==} peerDependencies: react: ^16.8 || ^17.0 || ^18.0 dependencies: '@babel/runtime': 7.21.0 - react: 18.2.0 + react: 18.3.1 dev: false - /@radix-ui/react-dialog@1.0.3(react-dom@18.2.0)(react@18.2.0): + /@radix-ui/react-dialog@1.0.3(react-dom@18.3.1)(react@18.3.1): resolution: {integrity: sha512-owNhq36kNPqC2/a+zJRioPg6HHnTn5B/sh/NjTY8r4W9g1L5VJlrzZIVcBr7R9Mg8iLjVmh6MGgMlfoVf/WO/A==} peerDependencies: react: ^16.8 || ^17.0 || ^18.0 @@ -1034,26 +1049,26 @@ packages: dependencies: '@babel/runtime': 7.21.0 '@radix-ui/primitive': 1.0.0 - '@radix-ui/react-compose-refs': 1.0.0(react@18.2.0) - '@radix-ui/react-context': 1.0.0(react@18.2.0) - '@radix-ui/react-dismissable-layer': 1.0.3(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-focus-guards': 1.0.0(react@18.2.0) - '@radix-ui/react-focus-scope': 1.0.2(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-id': 1.0.0(react@18.2.0) - '@radix-ui/react-portal': 1.0.2(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-presence': 1.0.0(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-primitive': 1.0.2(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-slot': 1.0.1(react@18.2.0) - '@radix-ui/react-use-controllable-state': 1.0.0(react@18.2.0) + '@radix-ui/react-compose-refs': 1.0.0(react@18.3.1) + '@radix-ui/react-context': 1.0.0(react@18.3.1) + '@radix-ui/react-dismissable-layer': 1.0.3(react-dom@18.3.1)(react@18.3.1) + '@radix-ui/react-focus-guards': 1.0.0(react@18.3.1) + '@radix-ui/react-focus-scope': 1.0.2(react-dom@18.3.1)(react@18.3.1) + '@radix-ui/react-id': 1.0.0(react@18.3.1) + '@radix-ui/react-portal': 1.0.2(react-dom@18.3.1)(react@18.3.1) + '@radix-ui/react-presence': 1.0.0(react-dom@18.3.1)(react@18.3.1) + '@radix-ui/react-primitive': 1.0.2(react-dom@18.3.1)(react@18.3.1) + '@radix-ui/react-slot': 1.0.1(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.0.0(react@18.3.1) aria-hidden: 1.2.3 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - react-remove-scroll: 2.5.5(react@18.2.0) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-remove-scroll: 2.5.5(react@18.3.1) transitivePeerDependencies: - '@types/react' dev: false - /@radix-ui/react-dismissable-layer@1.0.3(react-dom@18.2.0)(react@18.2.0): + /@radix-ui/react-dismissable-layer@1.0.3(react-dom@18.3.1)(react@18.3.1): resolution: {integrity: sha512-nXZOvFjOuHS1ovumntGV7NNoLaEp9JEvTht3MBjP44NSW5hUKj/8OnfN3+8WmB+CEhN44XaGhpHoSsUIEl5P7Q==} peerDependencies: react: ^16.8 || ^17.0 || ^18.0 @@ -1061,38 +1076,38 @@ packages: dependencies: '@babel/runtime': 7.21.0 '@radix-ui/primitive': 1.0.0 - '@radix-ui/react-compose-refs': 1.0.0(react@18.2.0) - '@radix-ui/react-primitive': 1.0.2(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-use-callback-ref': 1.0.0(react@18.2.0) - '@radix-ui/react-use-escape-keydown': 1.0.2(react@18.2.0) - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) + '@radix-ui/react-compose-refs': 1.0.0(react@18.3.1) + '@radix-ui/react-primitive': 1.0.2(react-dom@18.3.1)(react@18.3.1) + '@radix-ui/react-use-callback-ref': 1.0.0(react@18.3.1) + '@radix-ui/react-use-escape-keydown': 1.0.2(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) dev: false - /@radix-ui/react-focus-guards@1.0.0(react@18.2.0): + /@radix-ui/react-focus-guards@1.0.0(react@18.3.1): resolution: {integrity: sha512-UagjDk4ijOAnGu4WMUPj9ahi7/zJJqNZ9ZAiGPp7waUWJO0O1aWXi/udPphI0IUjvrhBsZJGSN66dR2dsueLWQ==} peerDependencies: react: ^16.8 || ^17.0 || ^18.0 dependencies: '@babel/runtime': 7.21.0 - react: 18.2.0 + react: 18.3.1 dev: false - /@radix-ui/react-focus-scope@1.0.2(react-dom@18.2.0)(react@18.2.0): + /@radix-ui/react-focus-scope@1.0.2(react-dom@18.3.1)(react@18.3.1): resolution: {integrity: sha512-spwXlNTfeIprt+kaEWE/qYuYT3ZAqJiAGjN/JgdvgVDTu8yc+HuX+WOWXrKliKnLnwck0F6JDkqIERncnih+4A==} peerDependencies: react: ^16.8 || ^17.0 || ^18.0 react-dom: ^16.8 || ^17.0 || ^18.0 dependencies: '@babel/runtime': 7.21.0 - '@radix-ui/react-compose-refs': 1.0.0(react@18.2.0) - '@radix-ui/react-primitive': 1.0.2(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-use-callback-ref': 1.0.0(react@18.2.0) - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) + '@radix-ui/react-compose-refs': 1.0.0(react@18.3.1) + '@radix-ui/react-primitive': 1.0.2(react-dom@18.3.1)(react@18.3.1) + '@radix-ui/react-use-callback-ref': 1.0.0(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) dev: false - /@radix-ui/react-hover-card@1.0.5(react-dom@18.2.0)(react@18.2.0): + /@radix-ui/react-hover-card@1.0.5(react-dom@18.3.1)(react@18.3.1): resolution: {integrity: sha512-jXRuZEkxSWdHZbVyL0J46cm7pQjmOMpwJEFKY+VqAJnV+FxS+zIZExI1OCeIiDwCBzUy6If1FfouOsfqBxr86g==} peerDependencies: react: ^16.8 || ^17.0 || ^18.0 @@ -1100,156 +1115,156 @@ packages: dependencies: '@babel/runtime': 7.21.0 '@radix-ui/primitive': 1.0.0 - '@radix-ui/react-compose-refs': 1.0.0(react@18.2.0) - '@radix-ui/react-context': 1.0.0(react@18.2.0) - '@radix-ui/react-dismissable-layer': 1.0.3(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-popper': 1.1.1(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-portal': 1.0.2(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-presence': 1.0.0(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-primitive': 1.0.2(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-use-controllable-state': 1.0.0(react@18.2.0) - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) + '@radix-ui/react-compose-refs': 1.0.0(react@18.3.1) + '@radix-ui/react-context': 1.0.0(react@18.3.1) + '@radix-ui/react-dismissable-layer': 1.0.3(react-dom@18.3.1)(react@18.3.1) + '@radix-ui/react-popper': 1.1.1(react-dom@18.3.1)(react@18.3.1) + '@radix-ui/react-portal': 1.0.2(react-dom@18.3.1)(react@18.3.1) + '@radix-ui/react-presence': 1.0.0(react-dom@18.3.1)(react@18.3.1) + '@radix-ui/react-primitive': 1.0.2(react-dom@18.3.1)(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.0.0(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) transitivePeerDependencies: - '@types/react' dev: false - /@radix-ui/react-id@1.0.0(react@18.2.0): + /@radix-ui/react-id@1.0.0(react@18.3.1): resolution: {integrity: sha512-Q6iAB/U7Tq3NTolBBQbHTgclPmGWE3OlktGGqrClPozSw4vkQ1DfQAOtzgRPecKsMdJINE05iaoDUG8tRzCBjw==} peerDependencies: react: ^16.8 || ^17.0 || ^18.0 dependencies: '@babel/runtime': 7.21.0 - '@radix-ui/react-use-layout-effect': 1.0.0(react@18.2.0) - react: 18.2.0 + '@radix-ui/react-use-layout-effect': 1.0.0(react@18.3.1) + react: 18.3.1 dev: false - /@radix-ui/react-popper@1.1.1(react-dom@18.2.0)(react@18.2.0): + /@radix-ui/react-popper@1.1.1(react-dom@18.3.1)(react@18.3.1): resolution: {integrity: sha512-keYDcdMPNMjSC8zTsZ8wezUMiWM9Yj14wtF3s0PTIs9srnEPC9Kt2Gny1T3T81mmSeyDjZxsD9N5WCwNNb712w==} peerDependencies: react: ^16.8 || ^17.0 || ^18.0 react-dom: ^16.8 || ^17.0 || ^18.0 dependencies: '@babel/runtime': 7.21.0 - '@floating-ui/react-dom': 0.7.2(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-arrow': 1.0.2(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-compose-refs': 1.0.0(react@18.2.0) - '@radix-ui/react-context': 1.0.0(react@18.2.0) - '@radix-ui/react-primitive': 1.0.2(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-use-callback-ref': 1.0.0(react@18.2.0) - '@radix-ui/react-use-layout-effect': 1.0.0(react@18.2.0) - '@radix-ui/react-use-rect': 1.0.0(react@18.2.0) - '@radix-ui/react-use-size': 1.0.0(react@18.2.0) + '@floating-ui/react-dom': 0.7.2(react-dom@18.3.1)(react@18.3.1) + '@radix-ui/react-arrow': 1.0.2(react-dom@18.3.1)(react@18.3.1) + '@radix-ui/react-compose-refs': 1.0.0(react@18.3.1) + '@radix-ui/react-context': 1.0.0(react@18.3.1) + '@radix-ui/react-primitive': 1.0.2(react-dom@18.3.1)(react@18.3.1) + '@radix-ui/react-use-callback-ref': 1.0.0(react@18.3.1) + '@radix-ui/react-use-layout-effect': 1.0.0(react@18.3.1) + '@radix-ui/react-use-rect': 1.0.0(react@18.3.1) + '@radix-ui/react-use-size': 1.0.0(react@18.3.1) '@radix-ui/rect': 1.0.0 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) transitivePeerDependencies: - '@types/react' dev: false - /@radix-ui/react-portal@1.0.2(react-dom@18.2.0)(react@18.2.0): + /@radix-ui/react-portal@1.0.2(react-dom@18.3.1)(react@18.3.1): resolution: {integrity: sha512-swu32idoCW7KA2VEiUZGBSu9nB6qwGdV6k6HYhUoOo3M1FFpD+VgLzUqtt3mwL1ssz7r2x8MggpLSQach2Xy/Q==} peerDependencies: react: ^16.8 || ^17.0 || ^18.0 react-dom: ^16.8 || ^17.0 || ^18.0 dependencies: '@babel/runtime': 7.21.0 - '@radix-ui/react-primitive': 1.0.2(react-dom@18.2.0)(react@18.2.0) - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) + '@radix-ui/react-primitive': 1.0.2(react-dom@18.3.1)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) dev: false - /@radix-ui/react-presence@1.0.0(react-dom@18.2.0)(react@18.2.0): + /@radix-ui/react-presence@1.0.0(react-dom@18.3.1)(react@18.3.1): resolution: {integrity: sha512-A+6XEvN01NfVWiKu38ybawfHsBjWum42MRPnEuqPsBZ4eV7e/7K321B5VgYMPv3Xx5An6o1/l9ZuDBgmcmWK3w==} peerDependencies: react: ^16.8 || ^17.0 || ^18.0 react-dom: ^16.8 || ^17.0 || ^18.0 dependencies: '@babel/runtime': 7.21.0 - '@radix-ui/react-compose-refs': 1.0.0(react@18.2.0) - '@radix-ui/react-use-layout-effect': 1.0.0(react@18.2.0) - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) + '@radix-ui/react-compose-refs': 1.0.0(react@18.3.1) + '@radix-ui/react-use-layout-effect': 1.0.0(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) dev: false - /@radix-ui/react-primitive@1.0.2(react-dom@18.2.0)(react@18.2.0): + /@radix-ui/react-primitive@1.0.2(react-dom@18.3.1)(react@18.3.1): resolution: {integrity: sha512-zY6G5Qq4R8diFPNwtyoLRZBxzu1Z+SXMlfYpChN7Dv8gvmx9X3qhDqiLWvKseKVJMuedFeU/Sa0Sy/Ia+t06Dw==} peerDependencies: react: ^16.8 || ^17.0 || ^18.0 react-dom: ^16.8 || ^17.0 || ^18.0 dependencies: '@babel/runtime': 7.21.0 - '@radix-ui/react-slot': 1.0.1(react@18.2.0) - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) + '@radix-ui/react-slot': 1.0.1(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) dev: false - /@radix-ui/react-slot@1.0.1(react@18.2.0): + /@radix-ui/react-slot@1.0.1(react@18.3.1): resolution: {integrity: sha512-avutXAFL1ehGvAXtPquu0YK5oz6ctS474iM3vNGQIkswrVhdrS52e3uoMQBzZhNRAIE0jBnUyXWNmSjGHhCFcw==} peerDependencies: react: ^16.8 || ^17.0 || ^18.0 dependencies: '@babel/runtime': 7.21.0 - '@radix-ui/react-compose-refs': 1.0.0(react@18.2.0) - react: 18.2.0 + '@radix-ui/react-compose-refs': 1.0.0(react@18.3.1) + react: 18.3.1 dev: false - /@radix-ui/react-use-callback-ref@1.0.0(react@18.2.0): + /@radix-ui/react-use-callback-ref@1.0.0(react@18.3.1): resolution: {integrity: sha512-GZtyzoHz95Rhs6S63D2t/eqvdFCm7I+yHMLVQheKM7nBD8mbZIt+ct1jz4536MDnaOGKIxynJ8eHTkVGVVkoTg==} peerDependencies: react: ^16.8 || ^17.0 || ^18.0 dependencies: '@babel/runtime': 7.21.0 - react: 18.2.0 + react: 18.3.1 dev: false - /@radix-ui/react-use-controllable-state@1.0.0(react@18.2.0): + /@radix-ui/react-use-controllable-state@1.0.0(react@18.3.1): resolution: {integrity: sha512-FohDoZvk3mEXh9AWAVyRTYR4Sq7/gavuofglmiXB2g1aKyboUD4YtgWxKj8O5n+Uak52gXQ4wKz5IFST4vtJHg==} peerDependencies: react: ^16.8 || ^17.0 || ^18.0 dependencies: '@babel/runtime': 7.21.0 - '@radix-ui/react-use-callback-ref': 1.0.0(react@18.2.0) - react: 18.2.0 + '@radix-ui/react-use-callback-ref': 1.0.0(react@18.3.1) + react: 18.3.1 dev: false - /@radix-ui/react-use-escape-keydown@1.0.2(react@18.2.0): + /@radix-ui/react-use-escape-keydown@1.0.2(react@18.3.1): resolution: {integrity: sha512-DXGim3x74WgUv+iMNCF+cAo8xUHHeqvjx8zs7trKf+FkQKPQXLk2sX7Gx1ysH7Q76xCpZuxIJE7HLPxRE+Q+GA==} peerDependencies: react: ^16.8 || ^17.0 || ^18.0 dependencies: '@babel/runtime': 7.21.0 - '@radix-ui/react-use-callback-ref': 1.0.0(react@18.2.0) - react: 18.2.0 + '@radix-ui/react-use-callback-ref': 1.0.0(react@18.3.1) + react: 18.3.1 dev: false - /@radix-ui/react-use-layout-effect@1.0.0(react@18.2.0): + /@radix-ui/react-use-layout-effect@1.0.0(react@18.3.1): resolution: {integrity: sha512-6Tpkq+R6LOlmQb1R5NNETLG0B4YP0wc+klfXafpUCj6JGyaUc8il7/kUZ7m59rGbXGczE9Bs+iz2qloqsZBduQ==} peerDependencies: react: ^16.8 || ^17.0 || ^18.0 dependencies: '@babel/runtime': 7.21.0 - react: 18.2.0 + react: 18.3.1 dev: false - /@radix-ui/react-use-rect@1.0.0(react@18.2.0): + /@radix-ui/react-use-rect@1.0.0(react@18.3.1): resolution: {integrity: sha512-TB7pID8NRMEHxb/qQJpvSt3hQU4sqNPM1VCTjTRjEOa7cEop/QMuq8S6fb/5Tsz64kqSvB9WnwsDHtjnrM9qew==} peerDependencies: react: ^16.8 || ^17.0 || ^18.0 dependencies: '@babel/runtime': 7.21.0 '@radix-ui/rect': 1.0.0 - react: 18.2.0 + react: 18.3.1 dev: false - /@radix-ui/react-use-size@1.0.0(react@18.2.0): + /@radix-ui/react-use-size@1.0.0(react@18.3.1): resolution: {integrity: sha512-imZ3aYcoYCKhhgNpkNDh/aTiU05qw9hX+HHI1QDBTyIlcFjgeFlKKySNGMwTp7nYFLQg/j0VA2FmCY4WPDDHMg==} peerDependencies: react: ^16.8 || ^17.0 || ^18.0 dependencies: '@babel/runtime': 7.21.0 - '@radix-ui/react-use-layout-effect': 1.0.0(react@18.2.0) - react: 18.2.0 + '@radix-ui/react-use-layout-effect': 1.0.0(react@18.3.1) + react: 18.3.1 dev: false /@radix-ui/rect@1.0.0: @@ -1440,6 +1455,10 @@ packages: resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} dev: true + /@types/katex@0.16.7: + resolution: {integrity: sha512-HMwFiRujE5PjrgwHQ25+bsLJgowjGjm5Z8FVSf0N6PwgJrwxH0QxzHYDcKsTfV3wva0vzrpqMTJS2jXPr5BMEQ==} + dev: true + /@types/mdast@3.0.15: resolution: {integrity: sha512-LnwD+mUEfxWMa1QpDraczIn6k0Ee3SMicuYSSzS6ZYl2gKS09EClnJYGd8Du6rfc5r/GZEk5o1mRb8TaTj03sQ==} dependencies: @@ -2012,6 +2031,11 @@ packages: engines: {node: '>=18'} dev: true + /commander@8.3.0: + resolution: {integrity: sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==} + engines: {node: '>= 12'} + dev: true + /comment-parser@1.4.0: resolution: {integrity: sha512-QLyTNiZ2KDOibvFPlZ6ZngVsZ/0gYnE6uTXi5aoDg8ed3AkJAz4sEje3Y8a29hQ1s6A99MZXe47fLAXQ1rTqaw==} engines: {node: '>= 12.0.0'} @@ -3786,6 +3810,13 @@ packages: set-immediate-shim: 1.0.1 dev: false + /katex@0.16.11: + resolution: {integrity: sha512-RQrI8rlHY92OLf3rho/Ts8i/XvjgguEjOkO1BEXcU3N8BqPpSzBNwV/G0Ukr+P/l3ivvJUE/Fa/CwbS6HesGNQ==} + hasBin: true + dependencies: + commander: 8.3.0 + dev: true + /kind-of@6.0.3: resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} engines: {node: '>=0.10.0'} @@ -4845,17 +4876,17 @@ packages: engines: {node: '>=8'} dev: true - /react-dom@18.2.0(react@18.2.0): - resolution: {integrity: sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==} + /react-dom@18.3.1(react@18.3.1): + resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==} peerDependencies: - react: ^18.2.0 + react: ^18.3.1 dependencies: loose-envify: 1.4.0 - react: 18.2.0 - scheduler: 0.23.0 + react: 18.3.1 + scheduler: 0.23.2 dev: false - /react-i18next@12.3.1(i18next@22.5.1)(react-dom@18.2.0)(react@18.2.0): + /react-i18next@12.3.1(i18next@22.5.1)(react-dom@18.3.1)(react@18.3.1): resolution: {integrity: sha512-5v8E2XjZDFzK7K87eSwC7AJcAkcLt5xYZ4+yTPDAW1i7C93oOY1dnr4BaQM7un4Hm+GmghuiPvevWwlca5PwDA==} peerDependencies: i18next: '>= 19.0.0' @@ -4871,15 +4902,15 @@ packages: '@babel/runtime': 7.21.0 html-parse-stringify: 3.0.1 i18next: 22.5.1 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) dev: false /react-is@16.13.1: resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} dev: true - /react-remove-scroll-bar@2.3.4(react@18.2.0): + /react-remove-scroll-bar@2.3.4(react@18.3.1): resolution: {integrity: sha512-63C4YQBUt0m6ALadE9XV56hV8BgJWDmmTPY758iIJjfQKt2nYwoUrPk0LXRXcB/yIj82T1/Ixfdpdk68LwIB0A==} engines: {node: '>=10'} peerDependencies: @@ -4889,12 +4920,12 @@ packages: '@types/react': optional: true dependencies: - react: 18.2.0 - react-style-singleton: 2.2.1(react@18.2.0) + react: 18.3.1 + react-style-singleton: 2.2.1(react@18.3.1) tslib: 2.5.0 dev: false - /react-remove-scroll@2.5.5(react@18.2.0): + /react-remove-scroll@2.5.5(react@18.3.1): resolution: {integrity: sha512-ImKhrzJJsyXJfBZ4bzu8Bwpka14c/fQt0k+cyFp/PBhTfyDnU5hjOtM4AG/0AMyy8oKzOTR0lDgJIM7pYXI0kw==} engines: {node: '>=10'} peerDependencies: @@ -4904,15 +4935,15 @@ packages: '@types/react': optional: true dependencies: - react: 18.2.0 - react-remove-scroll-bar: 2.3.4(react@18.2.0) - react-style-singleton: 2.2.1(react@18.2.0) + react: 18.3.1 + react-remove-scroll-bar: 2.3.4(react@18.3.1) + react-style-singleton: 2.2.1(react@18.3.1) tslib: 2.5.0 - use-callback-ref: 1.3.0(react@18.2.0) - use-sidecar: 1.1.2(react@18.2.0) + use-callback-ref: 1.3.0(react@18.3.1) + use-sidecar: 1.1.2(react@18.3.1) dev: false - /react-style-singleton@2.2.1(react@18.2.0): + /react-style-singleton@2.2.1(react@18.3.1): resolution: {integrity: sha512-ZWj0fHEMyWkHzKYUr2Bs/4zU6XLmq9HsgBURm7g5pAVfyn49DgUiNgY2d4lXRlYSiCif9YBGpQleewkcqddc7g==} engines: {node: '>=10'} peerDependencies: @@ -4924,16 +4955,15 @@ packages: dependencies: get-nonce: 1.0.1 invariant: 2.2.4 - react: 18.2.0 + react: 18.3.1 tslib: 2.5.0 dev: false - /react@18.2.0: - resolution: {integrity: sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==} + /react@18.3.1: + resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==} engines: {node: '>=0.10.0'} dependencies: loose-envify: 1.4.0 - dev: false /read-pkg-up@7.0.1: resolution: {integrity: sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==} @@ -5170,8 +5200,8 @@ packages: truncate-utf8-bytes: 1.0.2 dev: false - /scheduler@0.23.0: - resolution: {integrity: sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==} + /scheduler@0.23.2: + resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==} dependencies: loose-envify: 1.4.0 dev: false @@ -5696,7 +5726,7 @@ packages: resolution: {integrity: sha512-12c4Vi40DHVdZ/8mOLjZjp0asCzM6hi8Gj116fpImRP1FN4gBMCtMi9XhLNOmre/FEQYNqHbZmX8iyYAtIcy8Q==} dev: false - /use-callback-ref@1.3.0(react@18.2.0): + /use-callback-ref@1.3.0(react@18.3.1): resolution: {integrity: sha512-3FT9PRuRdbB9HfXhEq35u4oZkvpJ5kuYbpqhCfmiZyReuRgpnhDlbr2ZEnnuS0RrJAPn6l23xjFg9kpDM+Ms7w==} engines: {node: '>=10'} peerDependencies: @@ -5706,11 +5736,11 @@ packages: '@types/react': optional: true dependencies: - react: 18.2.0 + react: 18.3.1 tslib: 2.5.0 dev: false - /use-isomorphic-layout-effect@1.1.2(react@18.2.0): + /use-isomorphic-layout-effect@1.1.2(react@18.3.1): resolution: {integrity: sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA==} peerDependencies: '@types/react': '*' @@ -5719,10 +5749,10 @@ packages: '@types/react': optional: true dependencies: - react: 18.2.0 + react: 18.3.1 dev: false - /use-sidecar@1.1.2(react@18.2.0): + /use-sidecar@1.1.2(react@18.3.1): resolution: {integrity: sha512-epTbsLuzZ7lPClpz2TyryBfztm7m+28DlEv2ZCQ3MDr5ssiwyOwGH/e5F9CkfWjJ1t4clvI58yF822/GUkjjhw==} engines: {node: '>=10'} peerDependencies: @@ -5733,7 +5763,7 @@ packages: optional: true dependencies: detect-node-es: 1.1.0 - react: 18.2.0 + react: 18.3.1 tslib: 2.5.0 dev: false diff --git a/src/api.ts b/src/api.ts index 7eb6404..f2e0602 100644 --- a/src/api.ts +++ b/src/api.ts @@ -397,12 +397,13 @@ async function fetchConversations(offset = 0, limit = 20): Promise { const conversations: ApiConversationItem[] = [] - const limit = 20 + const limit = 100 let offset = 0 while (true) { const result = await fetchConversations(offset, limit) conversations.push(...result.items) if (offset + limit >= result.total) break + if (offset + limit >= 1000) break offset += limit } return conversations diff --git a/src/exporter/html.ts b/src/exporter/html.ts index 9d738a8..d427de9 100644 --- a/src/exporter/html.ts +++ b/src/exporter/html.ts @@ -63,7 +63,7 @@ export async function exportAllToHtml(fileNameFormat: string, apiConversations: level: 9, }, }) - downloadFile('chatgpt-export.zip', 'application/zip', blob) + downloadFile('chatgpt-export-html.zip', 'application/zip', blob) return true } @@ -75,6 +75,8 @@ function conversationToHtml(conversation: ConversationResult, avatar: string, me const timeStampHtml = ScriptStorage.get(KEY_TIMESTAMP_HTML) ?? false const timeStamp24H = ScriptStorage.get(KEY_TIMESTAMP_24H) ?? false + const LatexRegex = /(\s\$\$.+?\$\$\s|\s\$.+?\$\s|\\\[.+?\\\]|\\\(.+?\\\))|(^\$$[\S\s]+?^\$$)|(^\$\$[\S\s]+?^\$\$\$)/gm + const conversationHtml = conversationNodes.map(({ message }) => { if (!message || !message.content) return null @@ -107,12 +109,39 @@ function conversationToHtml(conversation: ConversationResult, avatar: string, me let postSteps: Array<(input: string) => string> = [] if (message.author.role === 'assistant') { postSteps = [...postSteps, input => transformFootNotes(input, message.metadata)] + postSteps.push((input) => { + const matches = input.match(LatexRegex) + + // Skip code block as the following steps can potentially break the code + const isCodeBlock = /```/.test(input) + if (!isCodeBlock && matches) { + let index = 0 + input = input.replace(LatexRegex, () => { + // Replace it with `╬${index}╬` to avoid processing from ruining the formula + return `╬${index++}╬` + }) + input = input + .replace(/^\\\[(.+)\\\]$/gm, '$$$$$1$$$$') + .replace(/\\\[/g, '$$') + .replace(/\\\]/g, '$$') + .replace(/\\\(/g, '$') + .replace(/\\\)/g, '$') + } + + let transformed = toHtml(fromMarkdown(input)) + + if (!isCodeBlock && matches) { + // Replace `╬${index}╬` back to the original latex + transformed = transformed.replace(/╬(\d+)╬/g, (_, index) => { + return matches[+index] + }) + } + + return transformed + }) } if (message.author.role === 'user') { - postSteps = [...postSteps, input => `

${escapeHtml(input)}

`] - } - else { - postSteps = [...postSteps, input => toHtml(fromMarkdown(input))] + postSteps = [...postSteps, input => `

${escapeHtml(input)}

`] } const postProcess = (input: string) => postSteps.reduce((acc, fn) => fn(acc), input) const content = transformContent(message.content, message.metadata, postProcess) @@ -260,7 +289,7 @@ function transformContent( }).join('\n') || '' } default: - return postProcess('[Unsupported Content]') + return postProcess(`[Unsupported Content: ${content.content_type} ]`) } } diff --git a/src/exporter/json.ts b/src/exporter/json.ts index 7256af8..92cb374 100644 --- a/src/exporter/json.ts +++ b/src/exporter/json.ts @@ -100,7 +100,7 @@ export async function exportAllToJson(fileNameFormat: string, apiConversations: level: 9, }, }) - downloadFile('chatgpt-export.zip', 'application/zip', blob) + downloadFile('chatgpt-export-json.zip', 'application/zip', blob) return true } diff --git a/src/exporter/markdown.ts b/src/exporter/markdown.ts index c3b111b..a0b3ba8 100644 --- a/src/exporter/markdown.ts +++ b/src/exporter/markdown.ts @@ -58,7 +58,7 @@ export async function exportAllToMarkdown(fileNameFormat: string, apiConversatio level: 9, }, }) - downloadFile('chatgpt-export.zip', 'application/zip', blob) + downloadFile('chatgpt-export-markdown.zip', 'application/zip', blob) return true } @@ -141,9 +141,7 @@ function conversationToMarkdown(conversation: ConversationResult, metaList?: Exp .replace(/\\\]/g, '$') .replace(/\\\(/g, '$') .replace(/\\\)/g, '$') - const matches = input.match(LatexRegex) - // Skip code block as the following steps can potentially break the code const isCodeBlock = /```/.test(input) if (!isCodeBlock && matches) { diff --git a/src/template.html b/src/template.html index 6d7d122..e316c52 100644 --- a/src/template.html +++ b/src/template.html @@ -10,6 +10,55 @@ + + + + @@ -525,7 +575,7 @@

Exported by - ChatGPT Exporter + ChatGPT Exporter at {{time}}

{{details}}