feat: add publish pipeline with HTML/PDF/thumbnail generation
Browse filesPublisher pipeline (POST /api/publish):
- Server-side TipTap extensions mirroring frontend (no React)
- generateHTML() produces article HTML with SEO meta tags
- Playwright-based PDF and OG thumbnail generation (optional)
- Upload published artifacts to HF dataset (or local fallback)
- HTML template with light/dark theme toggle, image lightbox
Routing updates:
- Authenticated editors get SPA, visitors get published HTML
- Image proxy for private HF datasets (/api/images/*)
- Serve local published articles from /published/
Also: add type declarations for citation-js and playwright
Made-with: Cursor
- backend/package-lock.json +972 -3
- backend/package.json +13 -0
- backend/src/hf-storage.ts +64 -0
- backend/src/publisher/extensions.ts +307 -0
- backend/src/publisher/html-renderer.ts +291 -0
- backend/src/publisher/index.ts +176 -0
- backend/src/publisher/pdf-generator.ts +74 -0
- backend/src/server.ts +115 -1
- backend/src/types.d.ts +11 -0
backend/package-lock.json
CHANGED
|
@@ -18,10 +18,23 @@
|
|
| 18 |
"@hocuspocus/server": "^3.4.4",
|
| 19 |
"@huggingface/hub": "^2.11.0",
|
| 20 |
"@openrouter/ai-sdk-provider": "^2.5.1",
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 21 |
"ai": "^6.0.158",
|
| 22 |
"dotenv": "^17.4.1",
|
| 23 |
"express": "^4.21.0",
|
| 24 |
"express-ws": "^5.0.2",
|
|
|
|
| 25 |
"multer": "^2.1.1",
|
| 26 |
"yjs": "^13.6.0",
|
| 27 |
"zod": "^4.3.6"
|
|
@@ -722,12 +735,510 @@
|
|
| 722 |
"node": ">=8.0.0"
|
| 723 |
}
|
| 724 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 725 |
"node_modules/@standard-schema/spec": {
|
| 726 |
"version": "1.1.0",
|
| 727 |
"resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz",
|
| 728 |
"integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==",
|
| 729 |
"license": "MIT"
|
| 730 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 731 |
"node_modules/@types/body-parser": {
|
| 732 |
"version": "1.19.6",
|
| 733 |
"resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz",
|
|
@@ -786,6 +1297,15 @@
|
|
| 786 |
"@types/ws": "*"
|
| 787 |
}
|
| 788 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 789 |
"node_modules/@types/http-errors": {
|
| 790 |
"version": "2.0.5",
|
| 791 |
"resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz",
|
|
@@ -793,6 +1313,28 @@
|
|
| 793 |
"dev": true,
|
| 794 |
"license": "MIT"
|
| 795 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 796 |
"node_modules/@types/multer": {
|
| 797 |
"version": "2.1.0",
|
| 798 |
"resolved": "https://registry.npmjs.org/@types/multer/-/multer-2.1.0.tgz",
|
|
@@ -807,7 +1349,6 @@
|
|
| 807 |
"version": "22.19.17",
|
| 808 |
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.17.tgz",
|
| 809 |
"integrity": "sha512-wGdMcf+vPYM6jikpS/qhg6WiqSV/OhG+jeeHT/KlVqxYfD40iYJf9/AE1uQxVWFvU7MipKRkRv8NSHiCGgPr8Q==",
|
| 810 |
-
"dev": true,
|
| 811 |
"license": "MIT",
|
| 812 |
"dependencies": {
|
| 813 |
"undici-types": "~6.21.0"
|
|
@@ -848,11 +1389,22 @@
|
|
| 848 |
"@types/node": "*"
|
| 849 |
}
|
| 850 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 851 |
"node_modules/@types/ws": {
|
| 852 |
"version": "8.18.1",
|
| 853 |
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz",
|
| 854 |
"integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==",
|
| 855 |
-
"dev": true,
|
| 856 |
"license": "MIT",
|
| 857 |
"dependencies": {
|
| 858 |
"@types/node": "*"
|
|
@@ -915,6 +1467,12 @@
|
|
| 915 |
"integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==",
|
| 916 |
"license": "MIT"
|
| 917 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 918 |
"node_modules/array-flatten": {
|
| 919 |
"version": "1.1.1",
|
| 920 |
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
|
|
@@ -1078,6 +1636,15 @@
|
|
| 1078 |
"node": ">=4"
|
| 1079 |
}
|
| 1080 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1081 |
"node_modules/concat-stream": {
|
| 1082 |
"version": "2.0.0",
|
| 1083 |
"resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz",
|
|
@@ -1129,6 +1696,12 @@
|
|
| 1129 |
"integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==",
|
| 1130 |
"license": "MIT"
|
| 1131 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1132 |
"node_modules/debug": {
|
| 1133 |
"version": "2.6.9",
|
| 1134 |
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
|
@@ -1147,6 +1720,15 @@
|
|
| 1147 |
"node": ">= 0.8"
|
| 1148 |
}
|
| 1149 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1150 |
"node_modules/destroy": {
|
| 1151 |
"version": "1.2.0",
|
| 1152 |
"resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
|
|
@@ -1157,6 +1739,19 @@
|
|
| 1157 |
"npm": "1.2.8000 || >= 1.4.16"
|
| 1158 |
}
|
| 1159 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1160 |
"node_modules/dotenv": {
|
| 1161 |
"version": "17.4.1",
|
| 1162 |
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.4.1.tgz",
|
|
@@ -1205,6 +1800,18 @@
|
|
| 1205 |
"node": ">= 0.8"
|
| 1206 |
}
|
| 1207 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1208 |
"node_modules/es-define-property": {
|
| 1209 |
"version": "1.0.1",
|
| 1210 |
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
|
|
@@ -1283,6 +1890,18 @@
|
|
| 1283 |
"integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
|
| 1284 |
"license": "MIT"
|
| 1285 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1286 |
"node_modules/etag": {
|
| 1287 |
"version": "1.8.1",
|
| 1288 |
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
|
|
@@ -1515,6 +2134,24 @@
|
|
| 1515 |
"url": "https://github.com/sponsors/ljharb"
|
| 1516 |
}
|
| 1517 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1518 |
"node_modules/has-symbols": {
|
| 1519 |
"version": "1.1.0",
|
| 1520 |
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
|
|
@@ -1539,6 +2176,16 @@
|
|
| 1539 |
"node": ">= 0.4"
|
| 1540 |
}
|
| 1541 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1542 |
"node_modules/http-errors": {
|
| 1543 |
"version": "2.0.1",
|
| 1544 |
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz",
|
|
@@ -1632,6 +2279,23 @@
|
|
| 1632 |
"integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==",
|
| 1633 |
"license": "(AFL-2.1 OR BSD-3-Clause)"
|
| 1634 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1635 |
"node_modules/kleur": {
|
| 1636 |
"version": "4.1.5",
|
| 1637 |
"resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz",
|
|
@@ -1662,6 +2326,66 @@
|
|
| 1662 |
"url": "https://github.com/sponsors/dmonad"
|
| 1663 |
}
|
| 1664 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1665 |
"node_modules/math-intrinsics": {
|
| 1666 |
"version": "1.1.0",
|
| 1667 |
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
|
@@ -1671,6 +2395,12 @@
|
|
| 1671 |
"node": ">= 0.4"
|
| 1672 |
}
|
| 1673 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1674 |
"node_modules/media-typer": {
|
| 1675 |
"version": "0.3.0",
|
| 1676 |
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
|
|
@@ -1815,6 +2545,12 @@
|
|
| 1815 |
"node": ">= 0.8"
|
| 1816 |
}
|
| 1817 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1818 |
"node_modules/parseurl": {
|
| 1819 |
"version": "1.3.3",
|
| 1820 |
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
|
|
@@ -1830,6 +2566,204 @@
|
|
| 1830 |
"integrity": "sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA==",
|
| 1831 |
"license": "MIT"
|
| 1832 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1833 |
"node_modules/proxy-addr": {
|
| 1834 |
"version": "2.0.7",
|
| 1835 |
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
|
|
@@ -1843,6 +2777,15 @@
|
|
| 1843 |
"node": ">= 0.10"
|
| 1844 |
}
|
| 1845 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1846 |
"node_modules/qs": {
|
| 1847 |
"version": "6.14.2",
|
| 1848 |
"resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz",
|
|
@@ -1906,6 +2849,12 @@
|
|
| 1906 |
"url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1"
|
| 1907 |
}
|
| 1908 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1909 |
"node_modules/safe-buffer": {
|
| 1910 |
"version": "5.2.1",
|
| 1911 |
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
|
@@ -2196,11 +3145,16 @@
|
|
| 2196 |
"node": ">=14.17"
|
| 2197 |
}
|
| 2198 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2199 |
"node_modules/undici-types": {
|
| 2200 |
"version": "6.21.0",
|
| 2201 |
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
|
| 2202 |
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
|
| 2203 |
-
"dev": true,
|
| 2204 |
"license": "MIT"
|
| 2205 |
},
|
| 2206 |
"node_modules/unpipe": {
|
|
@@ -2236,12 +3190,27 @@
|
|
| 2236 |
"node": ">= 0.8"
|
| 2237 |
}
|
| 2238 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2239 |
"node_modules/webidl-conversions": {
|
| 2240 |
"version": "3.0.1",
|
| 2241 |
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
|
| 2242 |
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
|
| 2243 |
"license": "BSD-2-Clause"
|
| 2244 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2245 |
"node_modules/whatwg-url": {
|
| 2246 |
"version": "5.0.0",
|
| 2247 |
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
|
|
|
|
| 18 |
"@hocuspocus/server": "^3.4.4",
|
| 19 |
"@huggingface/hub": "^2.11.0",
|
| 20 |
"@openrouter/ai-sdk-provider": "^2.5.1",
|
| 21 |
+
"@tiptap/core": "^3.22.3",
|
| 22 |
+
"@tiptap/extension-code-block-lowlight": "^3.22.3",
|
| 23 |
+
"@tiptap/extension-image": "^3.22.3",
|
| 24 |
+
"@tiptap/extension-link": "^3.22.3",
|
| 25 |
+
"@tiptap/extension-mathematics": "^3.22.3",
|
| 26 |
+
"@tiptap/extension-table": "^3.22.3",
|
| 27 |
+
"@tiptap/extension-table-cell": "^3.22.3",
|
| 28 |
+
"@tiptap/extension-table-header": "^3.22.3",
|
| 29 |
+
"@tiptap/extension-table-row": "^3.22.3",
|
| 30 |
+
"@tiptap/html": "^3.22.3",
|
| 31 |
+
"@tiptap/pm": "^3.22.3",
|
| 32 |
+
"@tiptap/starter-kit": "^3.22.3",
|
| 33 |
"ai": "^6.0.158",
|
| 34 |
"dotenv": "^17.4.1",
|
| 35 |
"express": "^4.21.0",
|
| 36 |
"express-ws": "^5.0.2",
|
| 37 |
+
"lowlight": "^3.3.0",
|
| 38 |
"multer": "^2.1.1",
|
| 39 |
"yjs": "^13.6.0",
|
| 40 |
"zod": "^4.3.6"
|
|
|
|
| 735 |
"node": ">=8.0.0"
|
| 736 |
}
|
| 737 |
},
|
| 738 |
+
"node_modules/@remirror/core-constants": {
|
| 739 |
+
"version": "3.0.0",
|
| 740 |
+
"resolved": "https://registry.npmjs.org/@remirror/core-constants/-/core-constants-3.0.0.tgz",
|
| 741 |
+
"integrity": "sha512-42aWfPrimMfDKDi4YegyS7x+/0tlzaqwPQCULLanv3DMIlu96KTJR0fM5isWX2UViOqlGnX6YFgqWepcX+XMNg==",
|
| 742 |
+
"license": "MIT"
|
| 743 |
+
},
|
| 744 |
"node_modules/@standard-schema/spec": {
|
| 745 |
"version": "1.1.0",
|
| 746 |
"resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz",
|
| 747 |
"integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==",
|
| 748 |
"license": "MIT"
|
| 749 |
},
|
| 750 |
+
"node_modules/@tiptap/core": {
|
| 751 |
+
"version": "3.22.3",
|
| 752 |
+
"resolved": "https://registry.npmjs.org/@tiptap/core/-/core-3.22.3.tgz",
|
| 753 |
+
"integrity": "sha512-Dv9MKK5BDWCF0N2l6/Pxv3JNCce2kwuWf2cKMBc2bEetx0Pn6o7zlFmSxMvYK4UtG1Tw9Yg/ZHi6QOFWK0Zm9Q==",
|
| 754 |
+
"license": "MIT",
|
| 755 |
+
"peer": true,
|
| 756 |
+
"funding": {
|
| 757 |
+
"type": "github",
|
| 758 |
+
"url": "https://github.com/sponsors/ueberdosis"
|
| 759 |
+
},
|
| 760 |
+
"peerDependencies": {
|
| 761 |
+
"@tiptap/pm": "^3.22.3"
|
| 762 |
+
}
|
| 763 |
+
},
|
| 764 |
+
"node_modules/@tiptap/extension-blockquote": {
|
| 765 |
+
"version": "3.22.3",
|
| 766 |
+
"resolved": "https://registry.npmjs.org/@tiptap/extension-blockquote/-/extension-blockquote-3.22.3.tgz",
|
| 767 |
+
"integrity": "sha512-IaUx3zh7yLHXzIXKL+fw/jzFhsIImdhJyw0lMhe8FfYrefFqXJFYW/sey6+L/e8B3AWvTksPA6VBwefzbH77JA==",
|
| 768 |
+
"license": "MIT",
|
| 769 |
+
"funding": {
|
| 770 |
+
"type": "github",
|
| 771 |
+
"url": "https://github.com/sponsors/ueberdosis"
|
| 772 |
+
},
|
| 773 |
+
"peerDependencies": {
|
| 774 |
+
"@tiptap/core": "^3.22.3"
|
| 775 |
+
}
|
| 776 |
+
},
|
| 777 |
+
"node_modules/@tiptap/extension-bold": {
|
| 778 |
+
"version": "3.22.3",
|
| 779 |
+
"resolved": "https://registry.npmjs.org/@tiptap/extension-bold/-/extension-bold-3.22.3.tgz",
|
| 780 |
+
"integrity": "sha512-tysipHla2zCWr8XNIWRaW9O+7i7/SoEqnRqSRUUi2ailcJjlia+RBy3RykhkgyThrQDStu5KGBS/UvrXwA+O1A==",
|
| 781 |
+
"license": "MIT",
|
| 782 |
+
"funding": {
|
| 783 |
+
"type": "github",
|
| 784 |
+
"url": "https://github.com/sponsors/ueberdosis"
|
| 785 |
+
},
|
| 786 |
+
"peerDependencies": {
|
| 787 |
+
"@tiptap/core": "^3.22.3"
|
| 788 |
+
}
|
| 789 |
+
},
|
| 790 |
+
"node_modules/@tiptap/extension-bullet-list": {
|
| 791 |
+
"version": "3.22.3",
|
| 792 |
+
"resolved": "https://registry.npmjs.org/@tiptap/extension-bullet-list/-/extension-bullet-list-3.22.3.tgz",
|
| 793 |
+
"integrity": "sha512-xOmW/b1hgECIE6r3IeZvKn4VVlG3+dfTjCWE6lnnyLaqdNkNhKS1CwUmDZdYNLUS2ryIUtgz5ID1W/8A3PhbiA==",
|
| 794 |
+
"license": "MIT",
|
| 795 |
+
"funding": {
|
| 796 |
+
"type": "github",
|
| 797 |
+
"url": "https://github.com/sponsors/ueberdosis"
|
| 798 |
+
},
|
| 799 |
+
"peerDependencies": {
|
| 800 |
+
"@tiptap/extension-list": "^3.22.3"
|
| 801 |
+
}
|
| 802 |
+
},
|
| 803 |
+
"node_modules/@tiptap/extension-code": {
|
| 804 |
+
"version": "3.22.3",
|
| 805 |
+
"resolved": "https://registry.npmjs.org/@tiptap/extension-code/-/extension-code-3.22.3.tgz",
|
| 806 |
+
"integrity": "sha512-wafWTDQOuMKtXpZEuk1PFQmzopabBciNLryL90MB9S03MNLaQQZYLnmYkDBlzAaLAbgF5QiC+2XZQEBQuTVjFQ==",
|
| 807 |
+
"license": "MIT",
|
| 808 |
+
"funding": {
|
| 809 |
+
"type": "github",
|
| 810 |
+
"url": "https://github.com/sponsors/ueberdosis"
|
| 811 |
+
},
|
| 812 |
+
"peerDependencies": {
|
| 813 |
+
"@tiptap/core": "^3.22.3"
|
| 814 |
+
}
|
| 815 |
+
},
|
| 816 |
+
"node_modules/@tiptap/extension-code-block": {
|
| 817 |
+
"version": "3.22.3",
|
| 818 |
+
"resolved": "https://registry.npmjs.org/@tiptap/extension-code-block/-/extension-code-block-3.22.3.tgz",
|
| 819 |
+
"integrity": "sha512-RiQtEjDAPrHpdo6sw6b7fOw/PijqgFIsozKKkGcSeBgWHQuFg7q9OxJTj+l0e60rVwSu/5gmKEEobzM9bX+t2Q==",
|
| 820 |
+
"license": "MIT",
|
| 821 |
+
"peer": true,
|
| 822 |
+
"funding": {
|
| 823 |
+
"type": "github",
|
| 824 |
+
"url": "https://github.com/sponsors/ueberdosis"
|
| 825 |
+
},
|
| 826 |
+
"peerDependencies": {
|
| 827 |
+
"@tiptap/core": "^3.22.3",
|
| 828 |
+
"@tiptap/pm": "^3.22.3"
|
| 829 |
+
}
|
| 830 |
+
},
|
| 831 |
+
"node_modules/@tiptap/extension-code-block-lowlight": {
|
| 832 |
+
"version": "3.22.3",
|
| 833 |
+
"resolved": "https://registry.npmjs.org/@tiptap/extension-code-block-lowlight/-/extension-code-block-lowlight-3.22.3.tgz",
|
| 834 |
+
"integrity": "sha512-NGFuD9zb1QfnCgD2zW4XaUEdQvd/ydm9FmXXh8eawx/+C8xt3p21DIKRERvxWrCvvVNzIUEpZRRYehPOJiD1eg==",
|
| 835 |
+
"license": "MIT",
|
| 836 |
+
"funding": {
|
| 837 |
+
"type": "github",
|
| 838 |
+
"url": "https://github.com/sponsors/ueberdosis"
|
| 839 |
+
},
|
| 840 |
+
"peerDependencies": {
|
| 841 |
+
"@tiptap/core": "^3.22.3",
|
| 842 |
+
"@tiptap/extension-code-block": "^3.22.3",
|
| 843 |
+
"@tiptap/pm": "^3.22.3",
|
| 844 |
+
"highlight.js": "^11",
|
| 845 |
+
"lowlight": "^2 || ^3"
|
| 846 |
+
}
|
| 847 |
+
},
|
| 848 |
+
"node_modules/@tiptap/extension-document": {
|
| 849 |
+
"version": "3.22.3",
|
| 850 |
+
"resolved": "https://registry.npmjs.org/@tiptap/extension-document/-/extension-document-3.22.3.tgz",
|
| 851 |
+
"integrity": "sha512-MCSr1PFPtTd++lA3H1RNgqAczAE59XXJ5wUFIQf2F+/0DPY5q2SU4g5QsNJVxPPft5mrNT4C6ty8xBPrALFEdA==",
|
| 852 |
+
"license": "MIT",
|
| 853 |
+
"funding": {
|
| 854 |
+
"type": "github",
|
| 855 |
+
"url": "https://github.com/sponsors/ueberdosis"
|
| 856 |
+
},
|
| 857 |
+
"peerDependencies": {
|
| 858 |
+
"@tiptap/core": "^3.22.3"
|
| 859 |
+
}
|
| 860 |
+
},
|
| 861 |
+
"node_modules/@tiptap/extension-dropcursor": {
|
| 862 |
+
"version": "3.22.3",
|
| 863 |
+
"resolved": "https://registry.npmjs.org/@tiptap/extension-dropcursor/-/extension-dropcursor-3.22.3.tgz",
|
| 864 |
+
"integrity": "sha512-taXq9Tl5aybdFbptJtFRHX9LFJzbXphAbPp4/vutFyTrBu5meXDxuS+B9pEmE+Or0XcolTlW2nDZB0Tqnr18JQ==",
|
| 865 |
+
"license": "MIT",
|
| 866 |
+
"funding": {
|
| 867 |
+
"type": "github",
|
| 868 |
+
"url": "https://github.com/sponsors/ueberdosis"
|
| 869 |
+
},
|
| 870 |
+
"peerDependencies": {
|
| 871 |
+
"@tiptap/extensions": "^3.22.3"
|
| 872 |
+
}
|
| 873 |
+
},
|
| 874 |
+
"node_modules/@tiptap/extension-gapcursor": {
|
| 875 |
+
"version": "3.22.3",
|
| 876 |
+
"resolved": "https://registry.npmjs.org/@tiptap/extension-gapcursor/-/extension-gapcursor-3.22.3.tgz",
|
| 877 |
+
"integrity": "sha512-L/Px4UeQEVG/D9WIlcAOIej+4wyIBCMUSYicSR+hW68UsObe4rxVbUas1QgidQKm6DOhoT7U7D4KQHA/Gdg/7A==",
|
| 878 |
+
"license": "MIT",
|
| 879 |
+
"funding": {
|
| 880 |
+
"type": "github",
|
| 881 |
+
"url": "https://github.com/sponsors/ueberdosis"
|
| 882 |
+
},
|
| 883 |
+
"peerDependencies": {
|
| 884 |
+
"@tiptap/extensions": "^3.22.3"
|
| 885 |
+
}
|
| 886 |
+
},
|
| 887 |
+
"node_modules/@tiptap/extension-hard-break": {
|
| 888 |
+
"version": "3.22.3",
|
| 889 |
+
"resolved": "https://registry.npmjs.org/@tiptap/extension-hard-break/-/extension-hard-break-3.22.3.tgz",
|
| 890 |
+
"integrity": "sha512-J0v8I99y9tbvVmgKYKzKP/JYNsWaZYS7avn4rzLft2OhnyTfwt3OoY8DtpHmmi6apSUaCtoWHWta/TmoEfK1nQ==",
|
| 891 |
+
"license": "MIT",
|
| 892 |
+
"funding": {
|
| 893 |
+
"type": "github",
|
| 894 |
+
"url": "https://github.com/sponsors/ueberdosis"
|
| 895 |
+
},
|
| 896 |
+
"peerDependencies": {
|
| 897 |
+
"@tiptap/core": "^3.22.3"
|
| 898 |
+
}
|
| 899 |
+
},
|
| 900 |
+
"node_modules/@tiptap/extension-heading": {
|
| 901 |
+
"version": "3.22.3",
|
| 902 |
+
"resolved": "https://registry.npmjs.org/@tiptap/extension-heading/-/extension-heading-3.22.3.tgz",
|
| 903 |
+
"integrity": "sha512-XBHuhiEV2EEhZHpOLcplLqAmBIhJciU3I6AtwmqeEqDC0P114uMEfAO7JGlbBZdCYotNer26PKnu44TBTeNtkw==",
|
| 904 |
+
"license": "MIT",
|
| 905 |
+
"funding": {
|
| 906 |
+
"type": "github",
|
| 907 |
+
"url": "https://github.com/sponsors/ueberdosis"
|
| 908 |
+
},
|
| 909 |
+
"peerDependencies": {
|
| 910 |
+
"@tiptap/core": "^3.22.3"
|
| 911 |
+
}
|
| 912 |
+
},
|
| 913 |
+
"node_modules/@tiptap/extension-horizontal-rule": {
|
| 914 |
+
"version": "3.22.3",
|
| 915 |
+
"resolved": "https://registry.npmjs.org/@tiptap/extension-horizontal-rule/-/extension-horizontal-rule-3.22.3.tgz",
|
| 916 |
+
"integrity": "sha512-wI2bFzScs+KgWeBH/BtypcVKeYelCyqV0RG8nxsZMWtPrBhqixzNd0Oi3gEKtjSjKUqMQ/kjJAIRuESr5UzlHA==",
|
| 917 |
+
"license": "MIT",
|
| 918 |
+
"funding": {
|
| 919 |
+
"type": "github",
|
| 920 |
+
"url": "https://github.com/sponsors/ueberdosis"
|
| 921 |
+
},
|
| 922 |
+
"peerDependencies": {
|
| 923 |
+
"@tiptap/core": "^3.22.3",
|
| 924 |
+
"@tiptap/pm": "^3.22.3"
|
| 925 |
+
}
|
| 926 |
+
},
|
| 927 |
+
"node_modules/@tiptap/extension-image": {
|
| 928 |
+
"version": "3.22.3",
|
| 929 |
+
"resolved": "https://registry.npmjs.org/@tiptap/extension-image/-/extension-image-3.22.3.tgz",
|
| 930 |
+
"integrity": "sha512-Qpp8c5LOQaNpHrzjqZtoxtIR+8sSqJ7k8v+8anmYw3nxjvt2kpfT28Vd7aWMX55ZS43LaxMx+MkZqbmgUmMP0w==",
|
| 931 |
+
"license": "MIT",
|
| 932 |
+
"funding": {
|
| 933 |
+
"type": "github",
|
| 934 |
+
"url": "https://github.com/sponsors/ueberdosis"
|
| 935 |
+
},
|
| 936 |
+
"peerDependencies": {
|
| 937 |
+
"@tiptap/core": "^3.22.3"
|
| 938 |
+
}
|
| 939 |
+
},
|
| 940 |
+
"node_modules/@tiptap/extension-italic": {
|
| 941 |
+
"version": "3.22.3",
|
| 942 |
+
"resolved": "https://registry.npmjs.org/@tiptap/extension-italic/-/extension-italic-3.22.3.tgz",
|
| 943 |
+
"integrity": "sha512-LteA4cb4EGCiUtrK2JHvDF/Zg0/YqV4DUyHhAAho+oGEQDupZlsS6m0ia5wQcclkiTLzsoPrwcSNu6RDGQ16wQ==",
|
| 944 |
+
"license": "MIT",
|
| 945 |
+
"funding": {
|
| 946 |
+
"type": "github",
|
| 947 |
+
"url": "https://github.com/sponsors/ueberdosis"
|
| 948 |
+
},
|
| 949 |
+
"peerDependencies": {
|
| 950 |
+
"@tiptap/core": "^3.22.3"
|
| 951 |
+
}
|
| 952 |
+
},
|
| 953 |
+
"node_modules/@tiptap/extension-link": {
|
| 954 |
+
"version": "3.22.3",
|
| 955 |
+
"resolved": "https://registry.npmjs.org/@tiptap/extension-link/-/extension-link-3.22.3.tgz",
|
| 956 |
+
"integrity": "sha512-S8/P2o9pv6B3kqLjH2TRWwSAximGbciNc6R8/QcN6HWLYxp0N0JoqN3rZHl9VWIBAGRWc4zkt80dhqrl2xmgfQ==",
|
| 957 |
+
"license": "MIT",
|
| 958 |
+
"dependencies": {
|
| 959 |
+
"linkifyjs": "^4.3.2"
|
| 960 |
+
},
|
| 961 |
+
"funding": {
|
| 962 |
+
"type": "github",
|
| 963 |
+
"url": "https://github.com/sponsors/ueberdosis"
|
| 964 |
+
},
|
| 965 |
+
"peerDependencies": {
|
| 966 |
+
"@tiptap/core": "^3.22.3",
|
| 967 |
+
"@tiptap/pm": "^3.22.3"
|
| 968 |
+
}
|
| 969 |
+
},
|
| 970 |
+
"node_modules/@tiptap/extension-list": {
|
| 971 |
+
"version": "3.22.3",
|
| 972 |
+
"resolved": "https://registry.npmjs.org/@tiptap/extension-list/-/extension-list-3.22.3.tgz",
|
| 973 |
+
"integrity": "sha512-rqvv/dtqwbX+8KnPv0eMYp6PnBcuhPMol5cv1GlS8Nq/Cxt68EWGUHBuTFesw+hdnRQLmKwzoO1DlRn7PhxYRQ==",
|
| 974 |
+
"license": "MIT",
|
| 975 |
+
"peer": true,
|
| 976 |
+
"funding": {
|
| 977 |
+
"type": "github",
|
| 978 |
+
"url": "https://github.com/sponsors/ueberdosis"
|
| 979 |
+
},
|
| 980 |
+
"peerDependencies": {
|
| 981 |
+
"@tiptap/core": "^3.22.3",
|
| 982 |
+
"@tiptap/pm": "^3.22.3"
|
| 983 |
+
}
|
| 984 |
+
},
|
| 985 |
+
"node_modules/@tiptap/extension-list-item": {
|
| 986 |
+
"version": "3.22.3",
|
| 987 |
+
"resolved": "https://registry.npmjs.org/@tiptap/extension-list-item/-/extension-list-item-3.22.3.tgz",
|
| 988 |
+
"integrity": "sha512-80CNf4oO5y8+LdckT4CyMe1t01EyhpRrQC9H45JW20P7559Nrchp5my3vvMtIAJbpTPPZtcB7LwdzWGKsG5drg==",
|
| 989 |
+
"license": "MIT",
|
| 990 |
+
"funding": {
|
| 991 |
+
"type": "github",
|
| 992 |
+
"url": "https://github.com/sponsors/ueberdosis"
|
| 993 |
+
},
|
| 994 |
+
"peerDependencies": {
|
| 995 |
+
"@tiptap/extension-list": "^3.22.3"
|
| 996 |
+
}
|
| 997 |
+
},
|
| 998 |
+
"node_modules/@tiptap/extension-list-keymap": {
|
| 999 |
+
"version": "3.22.3",
|
| 1000 |
+
"resolved": "https://registry.npmjs.org/@tiptap/extension-list-keymap/-/extension-list-keymap-3.22.3.tgz",
|
| 1001 |
+
"integrity": "sha512-pKuyj5llu35zd/s2u/H9aydKZjmPRAIK5P1q/YXULhhCNln2RnmuRfQ5NklAqTD3yGciQ2lxDwwf7J6iw3ergA==",
|
| 1002 |
+
"license": "MIT",
|
| 1003 |
+
"funding": {
|
| 1004 |
+
"type": "github",
|
| 1005 |
+
"url": "https://github.com/sponsors/ueberdosis"
|
| 1006 |
+
},
|
| 1007 |
+
"peerDependencies": {
|
| 1008 |
+
"@tiptap/extension-list": "^3.22.3"
|
| 1009 |
+
}
|
| 1010 |
+
},
|
| 1011 |
+
"node_modules/@tiptap/extension-mathematics": {
|
| 1012 |
+
"version": "3.22.3",
|
| 1013 |
+
"resolved": "https://registry.npmjs.org/@tiptap/extension-mathematics/-/extension-mathematics-3.22.3.tgz",
|
| 1014 |
+
"integrity": "sha512-GIkuTkiMLRjKN1Dp/jbOZ/Yvf0OrtmjS19Q+BVbkLJI+FJlEn5m+N2f/sjDq6sVoHccdraEyq1Ag8be3ohK0ng==",
|
| 1015 |
+
"license": "MIT",
|
| 1016 |
+
"funding": {
|
| 1017 |
+
"type": "github",
|
| 1018 |
+
"url": "https://github.com/sponsors/ueberdosis"
|
| 1019 |
+
},
|
| 1020 |
+
"peerDependencies": {
|
| 1021 |
+
"@tiptap/core": "^3.22.3",
|
| 1022 |
+
"@tiptap/pm": "^3.22.3",
|
| 1023 |
+
"katex": "^0.16.4"
|
| 1024 |
+
}
|
| 1025 |
+
},
|
| 1026 |
+
"node_modules/@tiptap/extension-ordered-list": {
|
| 1027 |
+
"version": "3.22.3",
|
| 1028 |
+
"resolved": "https://registry.npmjs.org/@tiptap/extension-ordered-list/-/extension-ordered-list-3.22.3.tgz",
|
| 1029 |
+
"integrity": "sha512-orAghtmd+K4Euu4BgI1hG+iZDXBYOyl5YTwiLBc2mQn+pqtZ9LqaH2us4ETwEwNP3/IWXGSAimUZ19nuL+eM2w==",
|
| 1030 |
+
"license": "MIT",
|
| 1031 |
+
"funding": {
|
| 1032 |
+
"type": "github",
|
| 1033 |
+
"url": "https://github.com/sponsors/ueberdosis"
|
| 1034 |
+
},
|
| 1035 |
+
"peerDependencies": {
|
| 1036 |
+
"@tiptap/extension-list": "^3.22.3"
|
| 1037 |
+
}
|
| 1038 |
+
},
|
| 1039 |
+
"node_modules/@tiptap/extension-paragraph": {
|
| 1040 |
+
"version": "3.22.3",
|
| 1041 |
+
"resolved": "https://registry.npmjs.org/@tiptap/extension-paragraph/-/extension-paragraph-3.22.3.tgz",
|
| 1042 |
+
"integrity": "sha512-oO7rhfyhEuwm+50s9K3GZPjYyEEEvFAvm1wXopvZnhbkBLydIWImBfrZoC5IQh4/sRDlTIjosV2C+ji5y0tUSg==",
|
| 1043 |
+
"license": "MIT",
|
| 1044 |
+
"funding": {
|
| 1045 |
+
"type": "github",
|
| 1046 |
+
"url": "https://github.com/sponsors/ueberdosis"
|
| 1047 |
+
},
|
| 1048 |
+
"peerDependencies": {
|
| 1049 |
+
"@tiptap/core": "^3.22.3"
|
| 1050 |
+
}
|
| 1051 |
+
},
|
| 1052 |
+
"node_modules/@tiptap/extension-strike": {
|
| 1053 |
+
"version": "3.22.3",
|
| 1054 |
+
"resolved": "https://registry.npmjs.org/@tiptap/extension-strike/-/extension-strike-3.22.3.tgz",
|
| 1055 |
+
"integrity": "sha512-jY2InoUlKkuk5KHoIDGdML1OCA2n6PRHAtxwHNkAmiYh0Khf0zaVPGFpx4dgQrN7W5Q1WE6oBZnjrvy6qb7w0g==",
|
| 1056 |
+
"license": "MIT",
|
| 1057 |
+
"funding": {
|
| 1058 |
+
"type": "github",
|
| 1059 |
+
"url": "https://github.com/sponsors/ueberdosis"
|
| 1060 |
+
},
|
| 1061 |
+
"peerDependencies": {
|
| 1062 |
+
"@tiptap/core": "^3.22.3"
|
| 1063 |
+
}
|
| 1064 |
+
},
|
| 1065 |
+
"node_modules/@tiptap/extension-table": {
|
| 1066 |
+
"version": "3.22.3",
|
| 1067 |
+
"resolved": "https://registry.npmjs.org/@tiptap/extension-table/-/extension-table-3.22.3.tgz",
|
| 1068 |
+
"integrity": "sha512-inbQSusJad7H0T++L1APg/anfL5d15cNGp2YG3vwo6TQr71nn2c9pepvmz3xuAIt8eygZDRba+4GT/COP1f9QA==",
|
| 1069 |
+
"license": "MIT",
|
| 1070 |
+
"peer": true,
|
| 1071 |
+
"funding": {
|
| 1072 |
+
"type": "github",
|
| 1073 |
+
"url": "https://github.com/sponsors/ueberdosis"
|
| 1074 |
+
},
|
| 1075 |
+
"peerDependencies": {
|
| 1076 |
+
"@tiptap/core": "^3.22.3",
|
| 1077 |
+
"@tiptap/pm": "^3.22.3"
|
| 1078 |
+
}
|
| 1079 |
+
},
|
| 1080 |
+
"node_modules/@tiptap/extension-table-cell": {
|
| 1081 |
+
"version": "3.22.3",
|
| 1082 |
+
"resolved": "https://registry.npmjs.org/@tiptap/extension-table-cell/-/extension-table-cell-3.22.3.tgz",
|
| 1083 |
+
"integrity": "sha512-LgaUgNwjHe6J7dq66N7iflC9efjgygsYWkHJtfLzaSU82tXzk8HSwBrMRCaj0PU+AR967/jGixEZIxH9xfXswQ==",
|
| 1084 |
+
"license": "MIT",
|
| 1085 |
+
"funding": {
|
| 1086 |
+
"type": "github",
|
| 1087 |
+
"url": "https://github.com/sponsors/ueberdosis"
|
| 1088 |
+
},
|
| 1089 |
+
"peerDependencies": {
|
| 1090 |
+
"@tiptap/extension-table": "^3.22.3"
|
| 1091 |
+
}
|
| 1092 |
+
},
|
| 1093 |
+
"node_modules/@tiptap/extension-table-header": {
|
| 1094 |
+
"version": "3.22.3",
|
| 1095 |
+
"resolved": "https://registry.npmjs.org/@tiptap/extension-table-header/-/extension-table-header-3.22.3.tgz",
|
| 1096 |
+
"integrity": "sha512-5wQ5rne9ccdbzeqC1AEDaUzlWjnpQNgBctgSPKEv+Da88lRRcGX21H+b/8jhL7fr4/sznr45toKGUg8NmRhboQ==",
|
| 1097 |
+
"license": "MIT",
|
| 1098 |
+
"funding": {
|
| 1099 |
+
"type": "github",
|
| 1100 |
+
"url": "https://github.com/sponsors/ueberdosis"
|
| 1101 |
+
},
|
| 1102 |
+
"peerDependencies": {
|
| 1103 |
+
"@tiptap/extension-table": "^3.22.3"
|
| 1104 |
+
}
|
| 1105 |
+
},
|
| 1106 |
+
"node_modules/@tiptap/extension-table-row": {
|
| 1107 |
+
"version": "3.22.3",
|
| 1108 |
+
"resolved": "https://registry.npmjs.org/@tiptap/extension-table-row/-/extension-table-row-3.22.3.tgz",
|
| 1109 |
+
"integrity": "sha512-dkoHnbOZgb2iQLXsp2FUu0KejAh3LOqDLMixYYU/0lnUwhqGG8kgu6+0YkCiUhedbfMFqVvemdKnRddpTQ4sqg==",
|
| 1110 |
+
"license": "MIT",
|
| 1111 |
+
"funding": {
|
| 1112 |
+
"type": "github",
|
| 1113 |
+
"url": "https://github.com/sponsors/ueberdosis"
|
| 1114 |
+
},
|
| 1115 |
+
"peerDependencies": {
|
| 1116 |
+
"@tiptap/extension-table": "^3.22.3"
|
| 1117 |
+
}
|
| 1118 |
+
},
|
| 1119 |
+
"node_modules/@tiptap/extension-text": {
|
| 1120 |
+
"version": "3.22.3",
|
| 1121 |
+
"resolved": "https://registry.npmjs.org/@tiptap/extension-text/-/extension-text-3.22.3.tgz",
|
| 1122 |
+
"integrity": "sha512-Q9R7JsTdomP5uUjtPjNKxHT1xoh/i9OJZnmgJLe7FcgZEaPOQ3bWxmKZoLZQfDfZjyB8BtH+Hc7nUvhCMOePxw==",
|
| 1123 |
+
"license": "MIT",
|
| 1124 |
+
"funding": {
|
| 1125 |
+
"type": "github",
|
| 1126 |
+
"url": "https://github.com/sponsors/ueberdosis"
|
| 1127 |
+
},
|
| 1128 |
+
"peerDependencies": {
|
| 1129 |
+
"@tiptap/core": "^3.22.3"
|
| 1130 |
+
}
|
| 1131 |
+
},
|
| 1132 |
+
"node_modules/@tiptap/extension-underline": {
|
| 1133 |
+
"version": "3.22.3",
|
| 1134 |
+
"resolved": "https://registry.npmjs.org/@tiptap/extension-underline/-/extension-underline-3.22.3.tgz",
|
| 1135 |
+
"integrity": "sha512-Ch6CBWRa5w90yYSPUW6x9Py9JdrXMqk3pZ9OIlMYD8A7BqyZGfiHerX7XDMYDS09KjyK3U9XH60/zxYOzXdDLA==",
|
| 1136 |
+
"license": "MIT",
|
| 1137 |
+
"funding": {
|
| 1138 |
+
"type": "github",
|
| 1139 |
+
"url": "https://github.com/sponsors/ueberdosis"
|
| 1140 |
+
},
|
| 1141 |
+
"peerDependencies": {
|
| 1142 |
+
"@tiptap/core": "^3.22.3"
|
| 1143 |
+
}
|
| 1144 |
+
},
|
| 1145 |
+
"node_modules/@tiptap/extensions": {
|
| 1146 |
+
"version": "3.22.3",
|
| 1147 |
+
"resolved": "https://registry.npmjs.org/@tiptap/extensions/-/extensions-3.22.3.tgz",
|
| 1148 |
+
"integrity": "sha512-s5eiMq0m5N6N+W7dU6rd60KgZyyCD7FvtPNNswISfPr12EQwJBfbjWwTqd0UKNzA4fNrhQEERXnzORkykttPeA==",
|
| 1149 |
+
"license": "MIT",
|
| 1150 |
+
"peer": true,
|
| 1151 |
+
"funding": {
|
| 1152 |
+
"type": "github",
|
| 1153 |
+
"url": "https://github.com/sponsors/ueberdosis"
|
| 1154 |
+
},
|
| 1155 |
+
"peerDependencies": {
|
| 1156 |
+
"@tiptap/core": "^3.22.3",
|
| 1157 |
+
"@tiptap/pm": "^3.22.3"
|
| 1158 |
+
}
|
| 1159 |
+
},
|
| 1160 |
+
"node_modules/@tiptap/html": {
|
| 1161 |
+
"version": "3.22.3",
|
| 1162 |
+
"resolved": "https://registry.npmjs.org/@tiptap/html/-/html-3.22.3.tgz",
|
| 1163 |
+
"integrity": "sha512-Cc1v2x5HJNDjgkT9vxhP1rJGscIPWTprCRUzRCIVwLuqdnRblntJtOpxMhFUU7yXCqlY27yUqmOBAvyhH1WO9g==",
|
| 1164 |
+
"license": "MIT",
|
| 1165 |
+
"funding": {
|
| 1166 |
+
"type": "github",
|
| 1167 |
+
"url": "https://github.com/sponsors/ueberdosis"
|
| 1168 |
+
},
|
| 1169 |
+
"peerDependencies": {
|
| 1170 |
+
"@tiptap/core": "^3.22.3",
|
| 1171 |
+
"@tiptap/pm": "^3.22.3",
|
| 1172 |
+
"happy-dom": "^20.8.9"
|
| 1173 |
+
}
|
| 1174 |
+
},
|
| 1175 |
+
"node_modules/@tiptap/pm": {
|
| 1176 |
+
"version": "3.22.3",
|
| 1177 |
+
"resolved": "https://registry.npmjs.org/@tiptap/pm/-/pm-3.22.3.tgz",
|
| 1178 |
+
"integrity": "sha512-NjfWjZuvrqmpICT+GZWNIjtOdhPyqFKDMtQy7tsQ5rErM9L2ZQdy/+T/BKSO1JdTeBhdg9OP+0yfsqoYp2aT6A==",
|
| 1179 |
+
"license": "MIT",
|
| 1180 |
+
"peer": true,
|
| 1181 |
+
"dependencies": {
|
| 1182 |
+
"prosemirror-changeset": "^2.3.0",
|
| 1183 |
+
"prosemirror-collab": "^1.3.1",
|
| 1184 |
+
"prosemirror-commands": "^1.6.2",
|
| 1185 |
+
"prosemirror-dropcursor": "^1.8.1",
|
| 1186 |
+
"prosemirror-gapcursor": "^1.3.2",
|
| 1187 |
+
"prosemirror-history": "^1.4.1",
|
| 1188 |
+
"prosemirror-inputrules": "^1.4.0",
|
| 1189 |
+
"prosemirror-keymap": "^1.2.2",
|
| 1190 |
+
"prosemirror-markdown": "^1.13.1",
|
| 1191 |
+
"prosemirror-menu": "^1.2.4",
|
| 1192 |
+
"prosemirror-model": "^1.24.1",
|
| 1193 |
+
"prosemirror-schema-basic": "^1.2.3",
|
| 1194 |
+
"prosemirror-schema-list": "^1.5.0",
|
| 1195 |
+
"prosemirror-state": "^1.4.3",
|
| 1196 |
+
"prosemirror-tables": "^1.6.4",
|
| 1197 |
+
"prosemirror-trailing-node": "^3.0.0",
|
| 1198 |
+
"prosemirror-transform": "^1.10.2",
|
| 1199 |
+
"prosemirror-view": "^1.38.1"
|
| 1200 |
+
},
|
| 1201 |
+
"funding": {
|
| 1202 |
+
"type": "github",
|
| 1203 |
+
"url": "https://github.com/sponsors/ueberdosis"
|
| 1204 |
+
}
|
| 1205 |
+
},
|
| 1206 |
+
"node_modules/@tiptap/starter-kit": {
|
| 1207 |
+
"version": "3.22.3",
|
| 1208 |
+
"resolved": "https://registry.npmjs.org/@tiptap/starter-kit/-/starter-kit-3.22.3.tgz",
|
| 1209 |
+
"integrity": "sha512-vdW/Oo1fdwTL1VOQ5YYbTov00ANeHLquBVEZyL/EkV7Xv5io9rXQsCysJfTSHhiQlyr2MtWFB4+CPGuwXjQWOQ==",
|
| 1210 |
+
"license": "MIT",
|
| 1211 |
+
"dependencies": {
|
| 1212 |
+
"@tiptap/core": "^3.22.3",
|
| 1213 |
+
"@tiptap/extension-blockquote": "^3.22.3",
|
| 1214 |
+
"@tiptap/extension-bold": "^3.22.3",
|
| 1215 |
+
"@tiptap/extension-bullet-list": "^3.22.3",
|
| 1216 |
+
"@tiptap/extension-code": "^3.22.3",
|
| 1217 |
+
"@tiptap/extension-code-block": "^3.22.3",
|
| 1218 |
+
"@tiptap/extension-document": "^3.22.3",
|
| 1219 |
+
"@tiptap/extension-dropcursor": "^3.22.3",
|
| 1220 |
+
"@tiptap/extension-gapcursor": "^3.22.3",
|
| 1221 |
+
"@tiptap/extension-hard-break": "^3.22.3",
|
| 1222 |
+
"@tiptap/extension-heading": "^3.22.3",
|
| 1223 |
+
"@tiptap/extension-horizontal-rule": "^3.22.3",
|
| 1224 |
+
"@tiptap/extension-italic": "^3.22.3",
|
| 1225 |
+
"@tiptap/extension-link": "^3.22.3",
|
| 1226 |
+
"@tiptap/extension-list": "^3.22.3",
|
| 1227 |
+
"@tiptap/extension-list-item": "^3.22.3",
|
| 1228 |
+
"@tiptap/extension-list-keymap": "^3.22.3",
|
| 1229 |
+
"@tiptap/extension-ordered-list": "^3.22.3",
|
| 1230 |
+
"@tiptap/extension-paragraph": "^3.22.3",
|
| 1231 |
+
"@tiptap/extension-strike": "^3.22.3",
|
| 1232 |
+
"@tiptap/extension-text": "^3.22.3",
|
| 1233 |
+
"@tiptap/extension-underline": "^3.22.3",
|
| 1234 |
+
"@tiptap/extensions": "^3.22.3",
|
| 1235 |
+
"@tiptap/pm": "^3.22.3"
|
| 1236 |
+
},
|
| 1237 |
+
"funding": {
|
| 1238 |
+
"type": "github",
|
| 1239 |
+
"url": "https://github.com/sponsors/ueberdosis"
|
| 1240 |
+
}
|
| 1241 |
+
},
|
| 1242 |
"node_modules/@types/body-parser": {
|
| 1243 |
"version": "1.19.6",
|
| 1244 |
"resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz",
|
|
|
|
| 1297 |
"@types/ws": "*"
|
| 1298 |
}
|
| 1299 |
},
|
| 1300 |
+
"node_modules/@types/hast": {
|
| 1301 |
+
"version": "3.0.4",
|
| 1302 |
+
"resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz",
|
| 1303 |
+
"integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==",
|
| 1304 |
+
"license": "MIT",
|
| 1305 |
+
"dependencies": {
|
| 1306 |
+
"@types/unist": "*"
|
| 1307 |
+
}
|
| 1308 |
+
},
|
| 1309 |
"node_modules/@types/http-errors": {
|
| 1310 |
"version": "2.0.5",
|
| 1311 |
"resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz",
|
|
|
|
| 1313 |
"dev": true,
|
| 1314 |
"license": "MIT"
|
| 1315 |
},
|
| 1316 |
+
"node_modules/@types/linkify-it": {
|
| 1317 |
+
"version": "5.0.0",
|
| 1318 |
+
"resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz",
|
| 1319 |
+
"integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==",
|
| 1320 |
+
"license": "MIT"
|
| 1321 |
+
},
|
| 1322 |
+
"node_modules/@types/markdown-it": {
|
| 1323 |
+
"version": "14.1.2",
|
| 1324 |
+
"resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.2.tgz",
|
| 1325 |
+
"integrity": "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==",
|
| 1326 |
+
"license": "MIT",
|
| 1327 |
+
"dependencies": {
|
| 1328 |
+
"@types/linkify-it": "^5",
|
| 1329 |
+
"@types/mdurl": "^2"
|
| 1330 |
+
}
|
| 1331 |
+
},
|
| 1332 |
+
"node_modules/@types/mdurl": {
|
| 1333 |
+
"version": "2.0.0",
|
| 1334 |
+
"resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz",
|
| 1335 |
+
"integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==",
|
| 1336 |
+
"license": "MIT"
|
| 1337 |
+
},
|
| 1338 |
"node_modules/@types/multer": {
|
| 1339 |
"version": "2.1.0",
|
| 1340 |
"resolved": "https://registry.npmjs.org/@types/multer/-/multer-2.1.0.tgz",
|
|
|
|
| 1349 |
"version": "22.19.17",
|
| 1350 |
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.17.tgz",
|
| 1351 |
"integrity": "sha512-wGdMcf+vPYM6jikpS/qhg6WiqSV/OhG+jeeHT/KlVqxYfD40iYJf9/AE1uQxVWFvU7MipKRkRv8NSHiCGgPr8Q==",
|
|
|
|
| 1352 |
"license": "MIT",
|
| 1353 |
"dependencies": {
|
| 1354 |
"undici-types": "~6.21.0"
|
|
|
|
| 1389 |
"@types/node": "*"
|
| 1390 |
}
|
| 1391 |
},
|
| 1392 |
+
"node_modules/@types/unist": {
|
| 1393 |
+
"version": "3.0.3",
|
| 1394 |
+
"resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz",
|
| 1395 |
+
"integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==",
|
| 1396 |
+
"license": "MIT"
|
| 1397 |
+
},
|
| 1398 |
+
"node_modules/@types/whatwg-mimetype": {
|
| 1399 |
+
"version": "3.0.2",
|
| 1400 |
+
"resolved": "https://registry.npmjs.org/@types/whatwg-mimetype/-/whatwg-mimetype-3.0.2.tgz",
|
| 1401 |
+
"integrity": "sha512-c2AKvDT8ToxLIOUlN51gTiHXflsfIFisS4pO7pDPoKouJCESkhZnEy623gwP9laCy5lnLDAw1vAzu2vM2YLOrA==",
|
| 1402 |
+
"license": "MIT"
|
| 1403 |
+
},
|
| 1404 |
"node_modules/@types/ws": {
|
| 1405 |
"version": "8.18.1",
|
| 1406 |
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz",
|
| 1407 |
"integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==",
|
|
|
|
| 1408 |
"license": "MIT",
|
| 1409 |
"dependencies": {
|
| 1410 |
"@types/node": "*"
|
|
|
|
| 1467 |
"integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==",
|
| 1468 |
"license": "MIT"
|
| 1469 |
},
|
| 1470 |
+
"node_modules/argparse": {
|
| 1471 |
+
"version": "2.0.1",
|
| 1472 |
+
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
|
| 1473 |
+
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
|
| 1474 |
+
"license": "Python-2.0"
|
| 1475 |
+
},
|
| 1476 |
"node_modules/array-flatten": {
|
| 1477 |
"version": "1.1.1",
|
| 1478 |
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
|
|
|
|
| 1636 |
"node": ">=4"
|
| 1637 |
}
|
| 1638 |
},
|
| 1639 |
+
"node_modules/commander": {
|
| 1640 |
+
"version": "8.3.0",
|
| 1641 |
+
"resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz",
|
| 1642 |
+
"integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==",
|
| 1643 |
+
"license": "MIT",
|
| 1644 |
+
"engines": {
|
| 1645 |
+
"node": ">= 12"
|
| 1646 |
+
}
|
| 1647 |
+
},
|
| 1648 |
"node_modules/concat-stream": {
|
| 1649 |
"version": "2.0.0",
|
| 1650 |
"resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz",
|
|
|
|
| 1696 |
"integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==",
|
| 1697 |
"license": "MIT"
|
| 1698 |
},
|
| 1699 |
+
"node_modules/crelt": {
|
| 1700 |
+
"version": "1.0.6",
|
| 1701 |
+
"resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz",
|
| 1702 |
+
"integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==",
|
| 1703 |
+
"license": "MIT"
|
| 1704 |
+
},
|
| 1705 |
"node_modules/debug": {
|
| 1706 |
"version": "2.6.9",
|
| 1707 |
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
|
|
|
| 1720 |
"node": ">= 0.8"
|
| 1721 |
}
|
| 1722 |
},
|
| 1723 |
+
"node_modules/dequal": {
|
| 1724 |
+
"version": "2.0.3",
|
| 1725 |
+
"resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
|
| 1726 |
+
"integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==",
|
| 1727 |
+
"license": "MIT",
|
| 1728 |
+
"engines": {
|
| 1729 |
+
"node": ">=6"
|
| 1730 |
+
}
|
| 1731 |
+
},
|
| 1732 |
"node_modules/destroy": {
|
| 1733 |
"version": "1.2.0",
|
| 1734 |
"resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
|
|
|
|
| 1739 |
"npm": "1.2.8000 || >= 1.4.16"
|
| 1740 |
}
|
| 1741 |
},
|
| 1742 |
+
"node_modules/devlop": {
|
| 1743 |
+
"version": "1.1.0",
|
| 1744 |
+
"resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz",
|
| 1745 |
+
"integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==",
|
| 1746 |
+
"license": "MIT",
|
| 1747 |
+
"dependencies": {
|
| 1748 |
+
"dequal": "^2.0.0"
|
| 1749 |
+
},
|
| 1750 |
+
"funding": {
|
| 1751 |
+
"type": "github",
|
| 1752 |
+
"url": "https://github.com/sponsors/wooorm"
|
| 1753 |
+
}
|
| 1754 |
+
},
|
| 1755 |
"node_modules/dotenv": {
|
| 1756 |
"version": "17.4.1",
|
| 1757 |
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.4.1.tgz",
|
|
|
|
| 1800 |
"node": ">= 0.8"
|
| 1801 |
}
|
| 1802 |
},
|
| 1803 |
+
"node_modules/entities": {
|
| 1804 |
+
"version": "7.0.1",
|
| 1805 |
+
"resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz",
|
| 1806 |
+
"integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==",
|
| 1807 |
+
"license": "BSD-2-Clause",
|
| 1808 |
+
"engines": {
|
| 1809 |
+
"node": ">=0.12"
|
| 1810 |
+
},
|
| 1811 |
+
"funding": {
|
| 1812 |
+
"url": "https://github.com/fb55/entities?sponsor=1"
|
| 1813 |
+
}
|
| 1814 |
+
},
|
| 1815 |
"node_modules/es-define-property": {
|
| 1816 |
"version": "1.0.1",
|
| 1817 |
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
|
|
|
|
| 1890 |
"integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
|
| 1891 |
"license": "MIT"
|
| 1892 |
},
|
| 1893 |
+
"node_modules/escape-string-regexp": {
|
| 1894 |
+
"version": "4.0.0",
|
| 1895 |
+
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
|
| 1896 |
+
"integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
|
| 1897 |
+
"license": "MIT",
|
| 1898 |
+
"engines": {
|
| 1899 |
+
"node": ">=10"
|
| 1900 |
+
},
|
| 1901 |
+
"funding": {
|
| 1902 |
+
"url": "https://github.com/sponsors/sindresorhus"
|
| 1903 |
+
}
|
| 1904 |
+
},
|
| 1905 |
"node_modules/etag": {
|
| 1906 |
"version": "1.8.1",
|
| 1907 |
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
|
|
|
|
| 2134 |
"url": "https://github.com/sponsors/ljharb"
|
| 2135 |
}
|
| 2136 |
},
|
| 2137 |
+
"node_modules/happy-dom": {
|
| 2138 |
+
"version": "20.8.9",
|
| 2139 |
+
"resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-20.8.9.tgz",
|
| 2140 |
+
"integrity": "sha512-Tz23LR9T9jOGVZm2x1EPdXqwA37G/owYMxRwU0E4miurAtFsPMQ1d2Jc2okUaSjZqAFz2oEn3FLXC5a0a+siyA==",
|
| 2141 |
+
"license": "MIT",
|
| 2142 |
+
"peer": true,
|
| 2143 |
+
"dependencies": {
|
| 2144 |
+
"@types/node": ">=20.0.0",
|
| 2145 |
+
"@types/whatwg-mimetype": "^3.0.2",
|
| 2146 |
+
"@types/ws": "^8.18.1",
|
| 2147 |
+
"entities": "^7.0.1",
|
| 2148 |
+
"whatwg-mimetype": "^3.0.0",
|
| 2149 |
+
"ws": "^8.18.3"
|
| 2150 |
+
},
|
| 2151 |
+
"engines": {
|
| 2152 |
+
"node": ">=20.0.0"
|
| 2153 |
+
}
|
| 2154 |
+
},
|
| 2155 |
"node_modules/has-symbols": {
|
| 2156 |
"version": "1.1.0",
|
| 2157 |
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
|
|
|
|
| 2176 |
"node": ">= 0.4"
|
| 2177 |
}
|
| 2178 |
},
|
| 2179 |
+
"node_modules/highlight.js": {
|
| 2180 |
+
"version": "11.11.1",
|
| 2181 |
+
"resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.11.1.tgz",
|
| 2182 |
+
"integrity": "sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==",
|
| 2183 |
+
"license": "BSD-3-Clause",
|
| 2184 |
+
"peer": true,
|
| 2185 |
+
"engines": {
|
| 2186 |
+
"node": ">=12.0.0"
|
| 2187 |
+
}
|
| 2188 |
+
},
|
| 2189 |
"node_modules/http-errors": {
|
| 2190 |
"version": "2.0.1",
|
| 2191 |
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz",
|
|
|
|
| 2279 |
"integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==",
|
| 2280 |
"license": "(AFL-2.1 OR BSD-3-Clause)"
|
| 2281 |
},
|
| 2282 |
+
"node_modules/katex": {
|
| 2283 |
+
"version": "0.16.45",
|
| 2284 |
+
"resolved": "https://registry.npmjs.org/katex/-/katex-0.16.45.tgz",
|
| 2285 |
+
"integrity": "sha512-pQpZbdBu7wCTmQUh7ufPmLr0pFoObnGUoL/yhtwJDgmmQpbkg/0HSVti25Fu4rmd1oCR6NGWe9vqTWuWv3GcNA==",
|
| 2286 |
+
"funding": [
|
| 2287 |
+
"https://opencollective.com/katex",
|
| 2288 |
+
"https://github.com/sponsors/katex"
|
| 2289 |
+
],
|
| 2290 |
+
"license": "MIT",
|
| 2291 |
+
"peer": true,
|
| 2292 |
+
"dependencies": {
|
| 2293 |
+
"commander": "^8.3.0"
|
| 2294 |
+
},
|
| 2295 |
+
"bin": {
|
| 2296 |
+
"katex": "cli.js"
|
| 2297 |
+
}
|
| 2298 |
+
},
|
| 2299 |
"node_modules/kleur": {
|
| 2300 |
"version": "4.1.5",
|
| 2301 |
"resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz",
|
|
|
|
| 2326 |
"url": "https://github.com/sponsors/dmonad"
|
| 2327 |
}
|
| 2328 |
},
|
| 2329 |
+
"node_modules/linkify-it": {
|
| 2330 |
+
"version": "5.0.0",
|
| 2331 |
+
"resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz",
|
| 2332 |
+
"integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==",
|
| 2333 |
+
"license": "MIT",
|
| 2334 |
+
"dependencies": {
|
| 2335 |
+
"uc.micro": "^2.0.0"
|
| 2336 |
+
}
|
| 2337 |
+
},
|
| 2338 |
+
"node_modules/linkifyjs": {
|
| 2339 |
+
"version": "4.3.2",
|
| 2340 |
+
"resolved": "https://registry.npmjs.org/linkifyjs/-/linkifyjs-4.3.2.tgz",
|
| 2341 |
+
"integrity": "sha512-NT1CJtq3hHIreOianA8aSXn6Cw0JzYOuDQbOrSPe7gqFnCpKP++MQe3ODgO3oh2GJFORkAAdqredOa60z63GbA==",
|
| 2342 |
+
"license": "MIT"
|
| 2343 |
+
},
|
| 2344 |
+
"node_modules/lowlight": {
|
| 2345 |
+
"version": "3.3.0",
|
| 2346 |
+
"resolved": "https://registry.npmjs.org/lowlight/-/lowlight-3.3.0.tgz",
|
| 2347 |
+
"integrity": "sha512-0JNhgFoPvP6U6lE/UdVsSq99tn6DhjjpAj5MxG49ewd2mOBVtwWYIT8ClyABhq198aXXODMU6Ox8DrGy/CpTZQ==",
|
| 2348 |
+
"license": "MIT",
|
| 2349 |
+
"peer": true,
|
| 2350 |
+
"dependencies": {
|
| 2351 |
+
"@types/hast": "^3.0.0",
|
| 2352 |
+
"devlop": "^1.0.0",
|
| 2353 |
+
"highlight.js": "~11.11.0"
|
| 2354 |
+
},
|
| 2355 |
+
"funding": {
|
| 2356 |
+
"type": "github",
|
| 2357 |
+
"url": "https://github.com/sponsors/wooorm"
|
| 2358 |
+
}
|
| 2359 |
+
},
|
| 2360 |
+
"node_modules/markdown-it": {
|
| 2361 |
+
"version": "14.1.1",
|
| 2362 |
+
"resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.1.tgz",
|
| 2363 |
+
"integrity": "sha512-BuU2qnTti9YKgK5N+IeMubp14ZUKUUw7yeJbkjtosvHiP0AZ5c8IAgEMk79D0eC8F23r4Ac/q8cAIFdm2FtyoA==",
|
| 2364 |
+
"license": "MIT",
|
| 2365 |
+
"dependencies": {
|
| 2366 |
+
"argparse": "^2.0.1",
|
| 2367 |
+
"entities": "^4.4.0",
|
| 2368 |
+
"linkify-it": "^5.0.0",
|
| 2369 |
+
"mdurl": "^2.0.0",
|
| 2370 |
+
"punycode.js": "^2.3.1",
|
| 2371 |
+
"uc.micro": "^2.1.0"
|
| 2372 |
+
},
|
| 2373 |
+
"bin": {
|
| 2374 |
+
"markdown-it": "bin/markdown-it.mjs"
|
| 2375 |
+
}
|
| 2376 |
+
},
|
| 2377 |
+
"node_modules/markdown-it/node_modules/entities": {
|
| 2378 |
+
"version": "4.5.0",
|
| 2379 |
+
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
|
| 2380 |
+
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
|
| 2381 |
+
"license": "BSD-2-Clause",
|
| 2382 |
+
"engines": {
|
| 2383 |
+
"node": ">=0.12"
|
| 2384 |
+
},
|
| 2385 |
+
"funding": {
|
| 2386 |
+
"url": "https://github.com/fb55/entities?sponsor=1"
|
| 2387 |
+
}
|
| 2388 |
+
},
|
| 2389 |
"node_modules/math-intrinsics": {
|
| 2390 |
"version": "1.1.0",
|
| 2391 |
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
|
|
|
| 2395 |
"node": ">= 0.4"
|
| 2396 |
}
|
| 2397 |
},
|
| 2398 |
+
"node_modules/mdurl": {
|
| 2399 |
+
"version": "2.0.0",
|
| 2400 |
+
"resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz",
|
| 2401 |
+
"integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==",
|
| 2402 |
+
"license": "MIT"
|
| 2403 |
+
},
|
| 2404 |
"node_modules/media-typer": {
|
| 2405 |
"version": "0.3.0",
|
| 2406 |
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
|
|
|
|
| 2545 |
"node": ">= 0.8"
|
| 2546 |
}
|
| 2547 |
},
|
| 2548 |
+
"node_modules/orderedmap": {
|
| 2549 |
+
"version": "2.1.1",
|
| 2550 |
+
"resolved": "https://registry.npmjs.org/orderedmap/-/orderedmap-2.1.1.tgz",
|
| 2551 |
+
"integrity": "sha512-TvAWxi0nDe1j/rtMcWcIj94+Ffe6n7zhow33h40SKxmsmozs6dz/e+EajymfoFcHd7sxNn8yHM8839uixMOV6g==",
|
| 2552 |
+
"license": "MIT"
|
| 2553 |
+
},
|
| 2554 |
"node_modules/parseurl": {
|
| 2555 |
"version": "1.3.3",
|
| 2556 |
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
|
|
|
|
| 2566 |
"integrity": "sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA==",
|
| 2567 |
"license": "MIT"
|
| 2568 |
},
|
| 2569 |
+
"node_modules/prosemirror-changeset": {
|
| 2570 |
+
"version": "2.4.0",
|
| 2571 |
+
"resolved": "https://registry.npmjs.org/prosemirror-changeset/-/prosemirror-changeset-2.4.0.tgz",
|
| 2572 |
+
"integrity": "sha512-LvqH2v7Q2SF6yxatuPP2e8vSUKS/L+xAU7dPDC4RMyHMhZoGDfBC74mYuyYF4gLqOEG758wajtyhNnsTkuhvng==",
|
| 2573 |
+
"license": "MIT",
|
| 2574 |
+
"dependencies": {
|
| 2575 |
+
"prosemirror-transform": "^1.0.0"
|
| 2576 |
+
}
|
| 2577 |
+
},
|
| 2578 |
+
"node_modules/prosemirror-collab": {
|
| 2579 |
+
"version": "1.3.1",
|
| 2580 |
+
"resolved": "https://registry.npmjs.org/prosemirror-collab/-/prosemirror-collab-1.3.1.tgz",
|
| 2581 |
+
"integrity": "sha512-4SnynYR9TTYaQVXd/ieUvsVV4PDMBzrq2xPUWutHivDuOshZXqQ5rGbZM84HEaXKbLdItse7weMGOUdDVcLKEQ==",
|
| 2582 |
+
"license": "MIT",
|
| 2583 |
+
"dependencies": {
|
| 2584 |
+
"prosemirror-state": "^1.0.0"
|
| 2585 |
+
}
|
| 2586 |
+
},
|
| 2587 |
+
"node_modules/prosemirror-commands": {
|
| 2588 |
+
"version": "1.7.1",
|
| 2589 |
+
"resolved": "https://registry.npmjs.org/prosemirror-commands/-/prosemirror-commands-1.7.1.tgz",
|
| 2590 |
+
"integrity": "sha512-rT7qZnQtx5c0/y/KlYaGvtG411S97UaL6gdp6RIZ23DLHanMYLyfGBV5DtSnZdthQql7W+lEVbpSfwtO8T+L2w==",
|
| 2591 |
+
"license": "MIT",
|
| 2592 |
+
"dependencies": {
|
| 2593 |
+
"prosemirror-model": "^1.0.0",
|
| 2594 |
+
"prosemirror-state": "^1.0.0",
|
| 2595 |
+
"prosemirror-transform": "^1.10.2"
|
| 2596 |
+
}
|
| 2597 |
+
},
|
| 2598 |
+
"node_modules/prosemirror-dropcursor": {
|
| 2599 |
+
"version": "1.8.2",
|
| 2600 |
+
"resolved": "https://registry.npmjs.org/prosemirror-dropcursor/-/prosemirror-dropcursor-1.8.2.tgz",
|
| 2601 |
+
"integrity": "sha512-CCk6Gyx9+Tt2sbYk5NK0nB1ukHi2ryaRgadV/LvyNuO3ena1payM2z6Cg0vO1ebK8cxbzo41ku2DE5Axj1Zuiw==",
|
| 2602 |
+
"license": "MIT",
|
| 2603 |
+
"dependencies": {
|
| 2604 |
+
"prosemirror-state": "^1.0.0",
|
| 2605 |
+
"prosemirror-transform": "^1.1.0",
|
| 2606 |
+
"prosemirror-view": "^1.1.0"
|
| 2607 |
+
}
|
| 2608 |
+
},
|
| 2609 |
+
"node_modules/prosemirror-gapcursor": {
|
| 2610 |
+
"version": "1.4.1",
|
| 2611 |
+
"resolved": "https://registry.npmjs.org/prosemirror-gapcursor/-/prosemirror-gapcursor-1.4.1.tgz",
|
| 2612 |
+
"integrity": "sha512-pMdYaEnjNMSwl11yjEGtgTmLkR08m/Vl+Jj443167p9eB3HVQKhYCc4gmHVDsLPODfZfjr/MmirsdyZziXbQKw==",
|
| 2613 |
+
"license": "MIT",
|
| 2614 |
+
"dependencies": {
|
| 2615 |
+
"prosemirror-keymap": "^1.0.0",
|
| 2616 |
+
"prosemirror-model": "^1.0.0",
|
| 2617 |
+
"prosemirror-state": "^1.0.0",
|
| 2618 |
+
"prosemirror-view": "^1.0.0"
|
| 2619 |
+
}
|
| 2620 |
+
},
|
| 2621 |
+
"node_modules/prosemirror-history": {
|
| 2622 |
+
"version": "1.5.0",
|
| 2623 |
+
"resolved": "https://registry.npmjs.org/prosemirror-history/-/prosemirror-history-1.5.0.tgz",
|
| 2624 |
+
"integrity": "sha512-zlzTiH01eKA55UAf1MEjtssJeHnGxO0j4K4Dpx+gnmX9n+SHNlDqI2oO1Kv1iPN5B1dm5fsljCfqKF9nFL6HRg==",
|
| 2625 |
+
"license": "MIT",
|
| 2626 |
+
"dependencies": {
|
| 2627 |
+
"prosemirror-state": "^1.2.2",
|
| 2628 |
+
"prosemirror-transform": "^1.0.0",
|
| 2629 |
+
"prosemirror-view": "^1.31.0",
|
| 2630 |
+
"rope-sequence": "^1.3.0"
|
| 2631 |
+
}
|
| 2632 |
+
},
|
| 2633 |
+
"node_modules/prosemirror-inputrules": {
|
| 2634 |
+
"version": "1.5.1",
|
| 2635 |
+
"resolved": "https://registry.npmjs.org/prosemirror-inputrules/-/prosemirror-inputrules-1.5.1.tgz",
|
| 2636 |
+
"integrity": "sha512-7wj4uMjKaXWAQ1CDgxNzNtR9AlsuwzHfdFH1ygEHA2KHF2DOEaXl1CJfNPAKCg9qNEh4rum975QLaCiQPyY6Fw==",
|
| 2637 |
+
"license": "MIT",
|
| 2638 |
+
"dependencies": {
|
| 2639 |
+
"prosemirror-state": "^1.0.0",
|
| 2640 |
+
"prosemirror-transform": "^1.0.0"
|
| 2641 |
+
}
|
| 2642 |
+
},
|
| 2643 |
+
"node_modules/prosemirror-keymap": {
|
| 2644 |
+
"version": "1.2.3",
|
| 2645 |
+
"resolved": "https://registry.npmjs.org/prosemirror-keymap/-/prosemirror-keymap-1.2.3.tgz",
|
| 2646 |
+
"integrity": "sha512-4HucRlpiLd1IPQQXNqeo81BGtkY8Ai5smHhKW9jjPKRc2wQIxksg7Hl1tTI2IfT2B/LgX6bfYvXxEpJl7aKYKw==",
|
| 2647 |
+
"license": "MIT",
|
| 2648 |
+
"dependencies": {
|
| 2649 |
+
"prosemirror-state": "^1.0.0",
|
| 2650 |
+
"w3c-keyname": "^2.2.0"
|
| 2651 |
+
}
|
| 2652 |
+
},
|
| 2653 |
+
"node_modules/prosemirror-markdown": {
|
| 2654 |
+
"version": "1.13.4",
|
| 2655 |
+
"resolved": "https://registry.npmjs.org/prosemirror-markdown/-/prosemirror-markdown-1.13.4.tgz",
|
| 2656 |
+
"integrity": "sha512-D98dm4cQ3Hs6EmjK500TdAOew4Z03EV71ajEFiWra3Upr7diytJsjF4mPV2dW+eK5uNectiRj0xFxYI9NLXDbw==",
|
| 2657 |
+
"license": "MIT",
|
| 2658 |
+
"dependencies": {
|
| 2659 |
+
"@types/markdown-it": "^14.0.0",
|
| 2660 |
+
"markdown-it": "^14.0.0",
|
| 2661 |
+
"prosemirror-model": "^1.25.0"
|
| 2662 |
+
}
|
| 2663 |
+
},
|
| 2664 |
+
"node_modules/prosemirror-menu": {
|
| 2665 |
+
"version": "1.3.0",
|
| 2666 |
+
"resolved": "https://registry.npmjs.org/prosemirror-menu/-/prosemirror-menu-1.3.0.tgz",
|
| 2667 |
+
"integrity": "sha512-TImyPXCHPcDsSka2/lwJ6WjTASr4re/qWq1yoTTuLOqfXucwF6VcRa2LWCkM/EyTD1UO3CUwiH8qURJoWJRxwg==",
|
| 2668 |
+
"license": "MIT",
|
| 2669 |
+
"dependencies": {
|
| 2670 |
+
"crelt": "^1.0.0",
|
| 2671 |
+
"prosemirror-commands": "^1.0.0",
|
| 2672 |
+
"prosemirror-history": "^1.0.0",
|
| 2673 |
+
"prosemirror-state": "^1.0.0"
|
| 2674 |
+
}
|
| 2675 |
+
},
|
| 2676 |
+
"node_modules/prosemirror-model": {
|
| 2677 |
+
"version": "1.25.4",
|
| 2678 |
+
"resolved": "https://registry.npmjs.org/prosemirror-model/-/prosemirror-model-1.25.4.tgz",
|
| 2679 |
+
"integrity": "sha512-PIM7E43PBxKce8OQeezAs9j4TP+5yDpZVbuurd1h5phUxEKIu+G2a+EUZzIC5nS1mJktDJWzbqS23n1tsAf5QA==",
|
| 2680 |
+
"license": "MIT",
|
| 2681 |
+
"peer": true,
|
| 2682 |
+
"dependencies": {
|
| 2683 |
+
"orderedmap": "^2.0.0"
|
| 2684 |
+
}
|
| 2685 |
+
},
|
| 2686 |
+
"node_modules/prosemirror-schema-basic": {
|
| 2687 |
+
"version": "1.2.4",
|
| 2688 |
+
"resolved": "https://registry.npmjs.org/prosemirror-schema-basic/-/prosemirror-schema-basic-1.2.4.tgz",
|
| 2689 |
+
"integrity": "sha512-ELxP4TlX3yr2v5rM7Sb70SqStq5NvI15c0j9j/gjsrO5vaw+fnnpovCLEGIcpeGfifkuqJwl4fon6b+KdrODYQ==",
|
| 2690 |
+
"license": "MIT",
|
| 2691 |
+
"dependencies": {
|
| 2692 |
+
"prosemirror-model": "^1.25.0"
|
| 2693 |
+
}
|
| 2694 |
+
},
|
| 2695 |
+
"node_modules/prosemirror-schema-list": {
|
| 2696 |
+
"version": "1.5.1",
|
| 2697 |
+
"resolved": "https://registry.npmjs.org/prosemirror-schema-list/-/prosemirror-schema-list-1.5.1.tgz",
|
| 2698 |
+
"integrity": "sha512-927lFx/uwyQaGwJxLWCZRkjXG0p48KpMj6ueoYiu4JX05GGuGcgzAy62dfiV8eFZftgyBUvLx76RsMe20fJl+Q==",
|
| 2699 |
+
"license": "MIT",
|
| 2700 |
+
"dependencies": {
|
| 2701 |
+
"prosemirror-model": "^1.0.0",
|
| 2702 |
+
"prosemirror-state": "^1.0.0",
|
| 2703 |
+
"prosemirror-transform": "^1.7.3"
|
| 2704 |
+
}
|
| 2705 |
+
},
|
| 2706 |
+
"node_modules/prosemirror-state": {
|
| 2707 |
+
"version": "1.4.4",
|
| 2708 |
+
"resolved": "https://registry.npmjs.org/prosemirror-state/-/prosemirror-state-1.4.4.tgz",
|
| 2709 |
+
"integrity": "sha512-6jiYHH2CIGbCfnxdHbXZ12gySFY/fz/ulZE333G6bPqIZ4F+TXo9ifiR86nAHpWnfoNjOb3o5ESi7J8Uz1jXHw==",
|
| 2710 |
+
"license": "MIT",
|
| 2711 |
+
"peer": true,
|
| 2712 |
+
"dependencies": {
|
| 2713 |
+
"prosemirror-model": "^1.0.0",
|
| 2714 |
+
"prosemirror-transform": "^1.0.0",
|
| 2715 |
+
"prosemirror-view": "^1.27.0"
|
| 2716 |
+
}
|
| 2717 |
+
},
|
| 2718 |
+
"node_modules/prosemirror-tables": {
|
| 2719 |
+
"version": "1.8.5",
|
| 2720 |
+
"resolved": "https://registry.npmjs.org/prosemirror-tables/-/prosemirror-tables-1.8.5.tgz",
|
| 2721 |
+
"integrity": "sha512-V/0cDCsHKHe/tfWkeCmthNUcEp1IVO3p6vwN8XtwE9PZQLAZJigbw3QoraAdfJPir4NKJtNvOB8oYGKRl+t0Dw==",
|
| 2722 |
+
"license": "MIT",
|
| 2723 |
+
"dependencies": {
|
| 2724 |
+
"prosemirror-keymap": "^1.2.3",
|
| 2725 |
+
"prosemirror-model": "^1.25.4",
|
| 2726 |
+
"prosemirror-state": "^1.4.4",
|
| 2727 |
+
"prosemirror-transform": "^1.10.5",
|
| 2728 |
+
"prosemirror-view": "^1.41.4"
|
| 2729 |
+
}
|
| 2730 |
+
},
|
| 2731 |
+
"node_modules/prosemirror-trailing-node": {
|
| 2732 |
+
"version": "3.0.0",
|
| 2733 |
+
"resolved": "https://registry.npmjs.org/prosemirror-trailing-node/-/prosemirror-trailing-node-3.0.0.tgz",
|
| 2734 |
+
"integrity": "sha512-xiun5/3q0w5eRnGYfNlW1uU9W6x5MoFKWwq/0TIRgt09lv7Hcser2QYV8t4muXbEr+Fwo0geYn79Xs4GKywrRQ==",
|
| 2735 |
+
"license": "MIT",
|
| 2736 |
+
"dependencies": {
|
| 2737 |
+
"@remirror/core-constants": "3.0.0",
|
| 2738 |
+
"escape-string-regexp": "^4.0.0"
|
| 2739 |
+
},
|
| 2740 |
+
"peerDependencies": {
|
| 2741 |
+
"prosemirror-model": "^1.22.1",
|
| 2742 |
+
"prosemirror-state": "^1.4.2",
|
| 2743 |
+
"prosemirror-view": "^1.33.8"
|
| 2744 |
+
}
|
| 2745 |
+
},
|
| 2746 |
+
"node_modules/prosemirror-transform": {
|
| 2747 |
+
"version": "1.12.0",
|
| 2748 |
+
"resolved": "https://registry.npmjs.org/prosemirror-transform/-/prosemirror-transform-1.12.0.tgz",
|
| 2749 |
+
"integrity": "sha512-GxboyN4AMIsoHNtz5uf2r2Ru551i5hWeCMD6E2Ib4Eogqoub0NflniaBPVQ4MrGE5yZ8JV9tUHg9qcZTTrcN4w==",
|
| 2750 |
+
"license": "MIT",
|
| 2751 |
+
"dependencies": {
|
| 2752 |
+
"prosemirror-model": "^1.21.0"
|
| 2753 |
+
}
|
| 2754 |
+
},
|
| 2755 |
+
"node_modules/prosemirror-view": {
|
| 2756 |
+
"version": "1.41.8",
|
| 2757 |
+
"resolved": "https://registry.npmjs.org/prosemirror-view/-/prosemirror-view-1.41.8.tgz",
|
| 2758 |
+
"integrity": "sha512-TnKDdohEatgyZNGCDWIdccOHXhYloJwbwU+phw/a23KBvJIR9lWQWW7WHHK3vBdOLDNuF7TaX98GObUZOWkOnA==",
|
| 2759 |
+
"license": "MIT",
|
| 2760 |
+
"peer": true,
|
| 2761 |
+
"dependencies": {
|
| 2762 |
+
"prosemirror-model": "^1.20.0",
|
| 2763 |
+
"prosemirror-state": "^1.0.0",
|
| 2764 |
+
"prosemirror-transform": "^1.1.0"
|
| 2765 |
+
}
|
| 2766 |
+
},
|
| 2767 |
"node_modules/proxy-addr": {
|
| 2768 |
"version": "2.0.7",
|
| 2769 |
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
|
|
|
|
| 2777 |
"node": ">= 0.10"
|
| 2778 |
}
|
| 2779 |
},
|
| 2780 |
+
"node_modules/punycode.js": {
|
| 2781 |
+
"version": "2.3.1",
|
| 2782 |
+
"resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz",
|
| 2783 |
+
"integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==",
|
| 2784 |
+
"license": "MIT",
|
| 2785 |
+
"engines": {
|
| 2786 |
+
"node": ">=6"
|
| 2787 |
+
}
|
| 2788 |
+
},
|
| 2789 |
"node_modules/qs": {
|
| 2790 |
"version": "6.14.2",
|
| 2791 |
"resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz",
|
|
|
|
| 2849 |
"url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1"
|
| 2850 |
}
|
| 2851 |
},
|
| 2852 |
+
"node_modules/rope-sequence": {
|
| 2853 |
+
"version": "1.3.4",
|
| 2854 |
+
"resolved": "https://registry.npmjs.org/rope-sequence/-/rope-sequence-1.3.4.tgz",
|
| 2855 |
+
"integrity": "sha512-UT5EDe2cu2E/6O4igUr5PSFs23nvvukicWHx6GnOPlHAiiYbzNuCRQCuiUdHJQcqKalLKlrYJnjY0ySGsXNQXQ==",
|
| 2856 |
+
"license": "MIT"
|
| 2857 |
+
},
|
| 2858 |
"node_modules/safe-buffer": {
|
| 2859 |
"version": "5.2.1",
|
| 2860 |
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
|
|
|
| 3145 |
"node": ">=14.17"
|
| 3146 |
}
|
| 3147 |
},
|
| 3148 |
+
"node_modules/uc.micro": {
|
| 3149 |
+
"version": "2.1.0",
|
| 3150 |
+
"resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz",
|
| 3151 |
+
"integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==",
|
| 3152 |
+
"license": "MIT"
|
| 3153 |
+
},
|
| 3154 |
"node_modules/undici-types": {
|
| 3155 |
"version": "6.21.0",
|
| 3156 |
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
|
| 3157 |
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
|
|
|
|
| 3158 |
"license": "MIT"
|
| 3159 |
},
|
| 3160 |
"node_modules/unpipe": {
|
|
|
|
| 3190 |
"node": ">= 0.8"
|
| 3191 |
}
|
| 3192 |
},
|
| 3193 |
+
"node_modules/w3c-keyname": {
|
| 3194 |
+
"version": "2.2.8",
|
| 3195 |
+
"resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz",
|
| 3196 |
+
"integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==",
|
| 3197 |
+
"license": "MIT"
|
| 3198 |
+
},
|
| 3199 |
"node_modules/webidl-conversions": {
|
| 3200 |
"version": "3.0.1",
|
| 3201 |
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
|
| 3202 |
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
|
| 3203 |
"license": "BSD-2-Clause"
|
| 3204 |
},
|
| 3205 |
+
"node_modules/whatwg-mimetype": {
|
| 3206 |
+
"version": "3.0.0",
|
| 3207 |
+
"resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz",
|
| 3208 |
+
"integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==",
|
| 3209 |
+
"license": "MIT",
|
| 3210 |
+
"engines": {
|
| 3211 |
+
"node": ">=12"
|
| 3212 |
+
}
|
| 3213 |
+
},
|
| 3214 |
"node_modules/whatwg-url": {
|
| 3215 |
"version": "5.0.0",
|
| 3216 |
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
|
backend/package.json
CHANGED
|
@@ -19,10 +19,23 @@
|
|
| 19 |
"@hocuspocus/server": "^3.4.4",
|
| 20 |
"@huggingface/hub": "^2.11.0",
|
| 21 |
"@openrouter/ai-sdk-provider": "^2.5.1",
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 22 |
"ai": "^6.0.158",
|
| 23 |
"dotenv": "^17.4.1",
|
| 24 |
"express": "^4.21.0",
|
| 25 |
"express-ws": "^5.0.2",
|
|
|
|
| 26 |
"multer": "^2.1.1",
|
| 27 |
"yjs": "^13.6.0",
|
| 28 |
"zod": "^4.3.6"
|
|
|
|
| 19 |
"@hocuspocus/server": "^3.4.4",
|
| 20 |
"@huggingface/hub": "^2.11.0",
|
| 21 |
"@openrouter/ai-sdk-provider": "^2.5.1",
|
| 22 |
+
"@tiptap/core": "^3.22.3",
|
| 23 |
+
"@tiptap/extension-code-block-lowlight": "^3.22.3",
|
| 24 |
+
"@tiptap/extension-image": "^3.22.3",
|
| 25 |
+
"@tiptap/extension-link": "^3.22.3",
|
| 26 |
+
"@tiptap/extension-mathematics": "^3.22.3",
|
| 27 |
+
"@tiptap/extension-table": "^3.22.3",
|
| 28 |
+
"@tiptap/extension-table-cell": "^3.22.3",
|
| 29 |
+
"@tiptap/extension-table-header": "^3.22.3",
|
| 30 |
+
"@tiptap/extension-table-row": "^3.22.3",
|
| 31 |
+
"@tiptap/html": "^3.22.3",
|
| 32 |
+
"@tiptap/pm": "^3.22.3",
|
| 33 |
+
"@tiptap/starter-kit": "^3.22.3",
|
| 34 |
"ai": "^6.0.158",
|
| 35 |
"dotenv": "^17.4.1",
|
| 36 |
"express": "^4.21.0",
|
| 37 |
"express-ws": "^5.0.2",
|
| 38 |
+
"lowlight": "^3.3.0",
|
| 39 |
"multer": "^2.1.1",
|
| 40 |
"yjs": "^13.6.0",
|
| 41 |
"zod": "^4.3.6"
|
backend/src/hf-storage.ts
CHANGED
|
@@ -95,3 +95,67 @@ export async function flushAll(): Promise<void> {
|
|
| 95 |
const names = [...dirtyDocs.keys()];
|
| 96 |
await Promise.allSettled(names.map((n) => pushDocument(n)));
|
| 97 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 95 |
const names = [...dirtyDocs.keys()];
|
| 96 |
await Promise.allSettled(names.map((n) => pushDocument(n)));
|
| 97 |
}
|
| 98 |
+
|
| 99 |
+
// ---------- Published Assets ----------
|
| 100 |
+
|
| 101 |
+
interface PublishedPayload {
|
| 102 |
+
html: string;
|
| 103 |
+
pdf: Buffer | null;
|
| 104 |
+
thumbnail: Buffer | null;
|
| 105 |
+
meta: { title: string; description: string; authors: string[]; date: string; [key: string]: unknown };
|
| 106 |
+
}
|
| 107 |
+
|
| 108 |
+
export async function uploadPublishedAssets(
|
| 109 |
+
docName: string,
|
| 110 |
+
payload: PublishedPayload
|
| 111 |
+
): Promise<{ htmlUrl: string; pdfUrl: string | null; thumbUrl: string | null }> {
|
| 112 |
+
const safeName = sanitizeName(docName);
|
| 113 |
+
const base = `published/${safeName}`;
|
| 114 |
+
|
| 115 |
+
// Upload HTML
|
| 116 |
+
await uploadFile({
|
| 117 |
+
repo,
|
| 118 |
+
file: { path: `${base}/index.html`, content: new Blob([payload.html]) },
|
| 119 |
+
accessToken: HF_TOKEN,
|
| 120 |
+
commitTitle: `publish ${safeName}: HTML`,
|
| 121 |
+
});
|
| 122 |
+
|
| 123 |
+
const baseUrl = `https://huggingface.co/datasets/${HF_DATASET_ID}/resolve/main/${base}`;
|
| 124 |
+
let pdfUrl: string | null = null;
|
| 125 |
+
let thumbUrl: string | null = null;
|
| 126 |
+
|
| 127 |
+
// Upload PDF
|
| 128 |
+
if (payload.pdf) {
|
| 129 |
+
await uploadFile({
|
| 130 |
+
repo,
|
| 131 |
+
file: { path: `${base}/article.pdf`, content: new Blob([new Uint8Array(payload.pdf)]) },
|
| 132 |
+
accessToken: HF_TOKEN,
|
| 133 |
+
commitTitle: `publish ${safeName}: PDF`,
|
| 134 |
+
});
|
| 135 |
+
pdfUrl = `${baseUrl}/article.pdf`;
|
| 136 |
+
}
|
| 137 |
+
|
| 138 |
+
// Upload thumbnail
|
| 139 |
+
if (payload.thumbnail) {
|
| 140 |
+
await uploadFile({
|
| 141 |
+
repo,
|
| 142 |
+
file: { path: `${base}/thumb.jpg`, content: new Blob([new Uint8Array(payload.thumbnail)]) },
|
| 143 |
+
accessToken: HF_TOKEN,
|
| 144 |
+
commitTitle: `publish ${safeName}: thumbnail`,
|
| 145 |
+
});
|
| 146 |
+
thumbUrl = `${baseUrl}/thumb.jpg`;
|
| 147 |
+
}
|
| 148 |
+
|
| 149 |
+
// Upload meta.json
|
| 150 |
+
await uploadFile({
|
| 151 |
+
repo,
|
| 152 |
+
file: {
|
| 153 |
+
path: `${base}/meta.json`,
|
| 154 |
+
content: new Blob([JSON.stringify(payload.meta, null, 2)]),
|
| 155 |
+
},
|
| 156 |
+
accessToken: HF_TOKEN,
|
| 157 |
+
commitTitle: `publish ${safeName}: metadata`,
|
| 158 |
+
});
|
| 159 |
+
|
| 160 |
+
return { htmlUrl: `${baseUrl}/index.html`, pdfUrl, thumbUrl };
|
| 161 |
+
}
|
backend/src/publisher/extensions.ts
ADDED
|
@@ -0,0 +1,307 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**
|
| 2 |
+
* Server-side TipTap extensions for generateHTML().
|
| 3 |
+
*
|
| 4 |
+
* Mirrors the frontend extension list but without:
|
| 5 |
+
* - React NodeViews (not available server-side)
|
| 6 |
+
* - Collaboration / CollaborationCursor / CollaborationUndo
|
| 7 |
+
* - Placeholder, SlashCommands, ImageUpload, Comment (editor-only)
|
| 8 |
+
*/
|
| 9 |
+
|
| 10 |
+
import { Node, mergeAttributes, type Extensions } from "@tiptap/core";
|
| 11 |
+
import StarterKit from "@tiptap/starter-kit";
|
| 12 |
+
import CodeBlockLowlight from "@tiptap/extension-code-block-lowlight";
|
| 13 |
+
import Link from "@tiptap/extension-link";
|
| 14 |
+
import Mathematics from "@tiptap/extension-mathematics";
|
| 15 |
+
import Image from "@tiptap/extension-image";
|
| 16 |
+
import { Table } from "@tiptap/extension-table";
|
| 17 |
+
import { TableRow } from "@tiptap/extension-table-row";
|
| 18 |
+
import { TableCell } from "@tiptap/extension-table-cell";
|
| 19 |
+
import { TableHeader } from "@tiptap/extension-table-header";
|
| 20 |
+
import { common, createLowlight } from "lowlight";
|
| 21 |
+
|
| 22 |
+
const lowlight = createLowlight(common);
|
| 23 |
+
|
| 24 |
+
// ---- Custom inline nodes (server versions, no NodeView) ----
|
| 25 |
+
|
| 26 |
+
const CitationServer = Node.create({
|
| 27 |
+
name: "citation",
|
| 28 |
+
group: "inline",
|
| 29 |
+
inline: true,
|
| 30 |
+
atom: true,
|
| 31 |
+
addAttributes() {
|
| 32 |
+
return { key: { default: "" } };
|
| 33 |
+
},
|
| 34 |
+
parseHTML() {
|
| 35 |
+
return [{ tag: 'span[data-type="citation"]' }];
|
| 36 |
+
},
|
| 37 |
+
renderHTML({ HTMLAttributes }) {
|
| 38 |
+
return [
|
| 39 |
+
"span",
|
| 40 |
+
mergeAttributes(HTMLAttributes, {
|
| 41 |
+
"data-type": "citation",
|
| 42 |
+
class: "citation-node",
|
| 43 |
+
}),
|
| 44 |
+
];
|
| 45 |
+
},
|
| 46 |
+
});
|
| 47 |
+
|
| 48 |
+
const BibliographyServer = Node.create({
|
| 49 |
+
name: "bibliography",
|
| 50 |
+
group: "block",
|
| 51 |
+
atom: true,
|
| 52 |
+
parseHTML() {
|
| 53 |
+
return [{ tag: 'div[data-type="bibliography"]' }];
|
| 54 |
+
},
|
| 55 |
+
renderHTML({ HTMLAttributes }) {
|
| 56 |
+
return [
|
| 57 |
+
"div",
|
| 58 |
+
mergeAttributes(HTMLAttributes, {
|
| 59 |
+
"data-type": "bibliography",
|
| 60 |
+
class: "bibliography-block",
|
| 61 |
+
}),
|
| 62 |
+
];
|
| 63 |
+
},
|
| 64 |
+
});
|
| 65 |
+
|
| 66 |
+
const GlossaryServer = Node.create({
|
| 67 |
+
name: "glossary",
|
| 68 |
+
group: "inline",
|
| 69 |
+
inline: true,
|
| 70 |
+
atom: true,
|
| 71 |
+
addAttributes() {
|
| 72 |
+
return {
|
| 73 |
+
term: { default: "" },
|
| 74 |
+
definition: { default: "" },
|
| 75 |
+
};
|
| 76 |
+
},
|
| 77 |
+
parseHTML() {
|
| 78 |
+
return [{ tag: 'span[data-type="glossary"]' }];
|
| 79 |
+
},
|
| 80 |
+
renderHTML({ HTMLAttributes, node }) {
|
| 81 |
+
return [
|
| 82 |
+
"span",
|
| 83 |
+
mergeAttributes(HTMLAttributes, {
|
| 84 |
+
"data-type": "glossary",
|
| 85 |
+
class: "glossary-node",
|
| 86 |
+
title: node.attrs.definition,
|
| 87 |
+
tabindex: "0",
|
| 88 |
+
}),
|
| 89 |
+
node.attrs.term || "",
|
| 90 |
+
];
|
| 91 |
+
},
|
| 92 |
+
});
|
| 93 |
+
|
| 94 |
+
const FootnoteServer = Node.create({
|
| 95 |
+
name: "footnote",
|
| 96 |
+
group: "inline",
|
| 97 |
+
inline: true,
|
| 98 |
+
atom: true,
|
| 99 |
+
addAttributes() {
|
| 100 |
+
return { content: { default: "" } };
|
| 101 |
+
},
|
| 102 |
+
parseHTML() {
|
| 103 |
+
return [{ tag: 'span[data-type="footnote"]' }];
|
| 104 |
+
},
|
| 105 |
+
renderHTML({ HTMLAttributes }) {
|
| 106 |
+
return [
|
| 107 |
+
"span",
|
| 108 |
+
mergeAttributes(HTMLAttributes, {
|
| 109 |
+
"data-type": "footnote",
|
| 110 |
+
class: "footnote-node",
|
| 111 |
+
}),
|
| 112 |
+
];
|
| 113 |
+
},
|
| 114 |
+
});
|
| 115 |
+
|
| 116 |
+
// ---- Stack ----
|
| 117 |
+
|
| 118 |
+
const StackColumnServer = Node.create({
|
| 119 |
+
name: "stackColumn",
|
| 120 |
+
group: "stackColumn",
|
| 121 |
+
content: "block+",
|
| 122 |
+
defining: true,
|
| 123 |
+
isolating: true,
|
| 124 |
+
parseHTML() {
|
| 125 |
+
return [{ tag: 'div[data-type="stack-column"]' }];
|
| 126 |
+
},
|
| 127 |
+
renderHTML({ HTMLAttributes }) {
|
| 128 |
+
return [
|
| 129 |
+
"div",
|
| 130 |
+
mergeAttributes(HTMLAttributes, { "data-type": "stack-column" }),
|
| 131 |
+
0,
|
| 132 |
+
];
|
| 133 |
+
},
|
| 134 |
+
});
|
| 135 |
+
|
| 136 |
+
const StackServer = Node.create({
|
| 137 |
+
name: "stack",
|
| 138 |
+
group: "block",
|
| 139 |
+
content: "stackColumn{2,4}",
|
| 140 |
+
defining: true,
|
| 141 |
+
isolating: true,
|
| 142 |
+
addAttributes() {
|
| 143 |
+
return {
|
| 144 |
+
layout: { default: "2-column" },
|
| 145 |
+
gap: { default: "medium" },
|
| 146 |
+
};
|
| 147 |
+
},
|
| 148 |
+
parseHTML() {
|
| 149 |
+
return [{ tag: 'div[data-component="stack"]' }];
|
| 150 |
+
},
|
| 151 |
+
renderHTML({ HTMLAttributes }) {
|
| 152 |
+
return [
|
| 153 |
+
"div",
|
| 154 |
+
mergeAttributes(HTMLAttributes, { "data-component": "stack" }),
|
| 155 |
+
0,
|
| 156 |
+
];
|
| 157 |
+
},
|
| 158 |
+
});
|
| 159 |
+
|
| 160 |
+
// ---- Generic wrapper/atomic components from the registry ----
|
| 161 |
+
|
| 162 |
+
interface ComponentDefLite {
|
| 163 |
+
name: string;
|
| 164 |
+
kind: "wrapper" | "atomic";
|
| 165 |
+
fields: { name: string; type: string; default?: unknown }[];
|
| 166 |
+
content?: string;
|
| 167 |
+
}
|
| 168 |
+
|
| 169 |
+
const COMPONENT_DEFS: ComponentDefLite[] = [
|
| 170 |
+
{
|
| 171 |
+
name: "accordion",
|
| 172 |
+
kind: "wrapper",
|
| 173 |
+
fields: [
|
| 174 |
+
{ name: "title", type: "string", default: "Details" },
|
| 175 |
+
{ name: "open", type: "boolean", default: false },
|
| 176 |
+
],
|
| 177 |
+
},
|
| 178 |
+
{
|
| 179 |
+
name: "note",
|
| 180 |
+
kind: "wrapper",
|
| 181 |
+
fields: [
|
| 182 |
+
{ name: "title", type: "string", default: "" },
|
| 183 |
+
{ name: "emoji", type: "string", default: "" },
|
| 184 |
+
{ name: "variant", type: "select", default: "neutral" },
|
| 185 |
+
],
|
| 186 |
+
},
|
| 187 |
+
{
|
| 188 |
+
name: "quoteBlock",
|
| 189 |
+
kind: "wrapper",
|
| 190 |
+
fields: [
|
| 191 |
+
{ name: "author", type: "string", default: "" },
|
| 192 |
+
{ name: "source", type: "string", default: "" },
|
| 193 |
+
],
|
| 194 |
+
},
|
| 195 |
+
{ name: "wide", kind: "wrapper", fields: [] },
|
| 196 |
+
{ name: "fullWidth", kind: "wrapper", fields: [] },
|
| 197 |
+
{ name: "sidenote", kind: "wrapper", fields: [] },
|
| 198 |
+
{
|
| 199 |
+
name: "reference",
|
| 200 |
+
kind: "wrapper",
|
| 201 |
+
fields: [
|
| 202 |
+
{ name: "id", type: "string", default: "" },
|
| 203 |
+
{ name: "caption", type: "string", default: "" },
|
| 204 |
+
],
|
| 205 |
+
},
|
| 206 |
+
{
|
| 207 |
+
name: "htmlEmbed",
|
| 208 |
+
kind: "atomic",
|
| 209 |
+
fields: [
|
| 210 |
+
{ name: "src", type: "string", default: "" },
|
| 211 |
+
{ name: "title", type: "string", default: "" },
|
| 212 |
+
{ name: "desc", type: "string", default: "" },
|
| 213 |
+
{ name: "wide", type: "boolean", default: false },
|
| 214 |
+
{ name: "downloadable", type: "boolean", default: false },
|
| 215 |
+
],
|
| 216 |
+
},
|
| 217 |
+
{
|
| 218 |
+
name: "hfUser",
|
| 219 |
+
kind: "atomic",
|
| 220 |
+
fields: [
|
| 221 |
+
{ name: "username", type: "string", default: "" },
|
| 222 |
+
{ name: "name", type: "string", default: "" },
|
| 223 |
+
{ name: "url", type: "string", default: "" },
|
| 224 |
+
],
|
| 225 |
+
},
|
| 226 |
+
{
|
| 227 |
+
name: "rawHtml",
|
| 228 |
+
kind: "atomic",
|
| 229 |
+
fields: [
|
| 230 |
+
{ name: "html", type: "string", default: "" },
|
| 231 |
+
],
|
| 232 |
+
},
|
| 233 |
+
];
|
| 234 |
+
|
| 235 |
+
function makeServerWrapperExt(def: ComponentDefLite) {
|
| 236 |
+
return Node.create({
|
| 237 |
+
name: def.name,
|
| 238 |
+
group: "block",
|
| 239 |
+
content: def.content || "block+",
|
| 240 |
+
defining: true,
|
| 241 |
+
isolating: true,
|
| 242 |
+
addAttributes() {
|
| 243 |
+
const attrs: Record<string, { default: unknown }> = {};
|
| 244 |
+
for (const f of def.fields) attrs[f.name] = { default: f.default ?? null };
|
| 245 |
+
return attrs;
|
| 246 |
+
},
|
| 247 |
+
parseHTML() {
|
| 248 |
+
return [{ tag: `div[data-component="${def.name}"]` }];
|
| 249 |
+
},
|
| 250 |
+
renderHTML({ HTMLAttributes }) {
|
| 251 |
+
return [
|
| 252 |
+
"div",
|
| 253 |
+
mergeAttributes(HTMLAttributes, { "data-component": def.name }),
|
| 254 |
+
0,
|
| 255 |
+
];
|
| 256 |
+
},
|
| 257 |
+
});
|
| 258 |
+
}
|
| 259 |
+
|
| 260 |
+
function makeServerAtomicExt(def: ComponentDefLite) {
|
| 261 |
+
return Node.create({
|
| 262 |
+
name: def.name,
|
| 263 |
+
group: "block",
|
| 264 |
+
atom: true,
|
| 265 |
+
addAttributes() {
|
| 266 |
+
const attrs: Record<string, { default: unknown }> = {};
|
| 267 |
+
for (const f of def.fields) attrs[f.name] = { default: f.default ?? null };
|
| 268 |
+
return attrs;
|
| 269 |
+
},
|
| 270 |
+
parseHTML() {
|
| 271 |
+
return [{ tag: `div[data-component="${def.name}"]` }];
|
| 272 |
+
},
|
| 273 |
+
renderHTML({ HTMLAttributes }) {
|
| 274 |
+
return [
|
| 275 |
+
"div",
|
| 276 |
+
mergeAttributes(HTMLAttributes, { "data-component": def.name }),
|
| 277 |
+
];
|
| 278 |
+
},
|
| 279 |
+
});
|
| 280 |
+
}
|
| 281 |
+
|
| 282 |
+
export function getServerExtensions(): Extensions {
|
| 283 |
+
const wrappers = COMPONENT_DEFS.filter((d) => d.kind === "wrapper").map(makeServerWrapperExt);
|
| 284 |
+
const atomics = COMPONENT_DEFS.filter((d) => d.kind === "atomic").map(makeServerAtomicExt);
|
| 285 |
+
|
| 286 |
+
return [
|
| 287 |
+
StarterKit.configure({
|
| 288 |
+
codeBlock: false,
|
| 289 |
+
} as any),
|
| 290 |
+
CodeBlockLowlight.configure({ lowlight }),
|
| 291 |
+
Link.configure({ openOnClick: false }),
|
| 292 |
+
Mathematics.configure({ katexOptions: { throwOnError: false } }),
|
| 293 |
+
Image.configure({ allowBase64: true }),
|
| 294 |
+
Table.configure({ resizable: false }),
|
| 295 |
+
TableRow,
|
| 296 |
+
TableCell,
|
| 297 |
+
TableHeader,
|
| 298 |
+
CitationServer,
|
| 299 |
+
BibliographyServer,
|
| 300 |
+
GlossaryServer,
|
| 301 |
+
FootnoteServer,
|
| 302 |
+
StackServer,
|
| 303 |
+
StackColumnServer,
|
| 304 |
+
...wrappers,
|
| 305 |
+
...atomics,
|
| 306 |
+
];
|
| 307 |
+
}
|
backend/src/publisher/html-renderer.ts
ADDED
|
@@ -0,0 +1,291 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**
|
| 2 |
+
* Server-side HTML renderer.
|
| 3 |
+
*
|
| 4 |
+
* Takes TipTap JSON content and generates a full static HTML page
|
| 5 |
+
* with SEO tags, dark/light theming, and minimal inline JS.
|
| 6 |
+
*/
|
| 7 |
+
|
| 8 |
+
import { generateHTML } from "@tiptap/html";
|
| 9 |
+
import { getServerExtensions } from "./extensions.js";
|
| 10 |
+
|
| 11 |
+
export interface PublishMeta {
|
| 12 |
+
title: string;
|
| 13 |
+
description: string;
|
| 14 |
+
authors: string[];
|
| 15 |
+
date: string;
|
| 16 |
+
ogImage?: string;
|
| 17 |
+
}
|
| 18 |
+
|
| 19 |
+
/**
|
| 20 |
+
* Render TipTap JSON document into a complete, self-contained HTML page.
|
| 21 |
+
*/
|
| 22 |
+
export function renderArticleHTML(
|
| 23 |
+
json: Record<string, unknown>,
|
| 24 |
+
meta: PublishMeta,
|
| 25 |
+
cssTokens: string,
|
| 26 |
+
cssArticle: string
|
| 27 |
+
): string {
|
| 28 |
+
const extensions = getServerExtensions();
|
| 29 |
+
const bodyHtml = generateHTML(json as any, extensions);
|
| 30 |
+
const enrichedBody = postProcess(bodyHtml);
|
| 31 |
+
|
| 32 |
+
const authorsStr = meta.authors.join(", ");
|
| 33 |
+
const safeTitle = escapeHtml(meta.title);
|
| 34 |
+
const safeDesc = escapeHtml(meta.description);
|
| 35 |
+
|
| 36 |
+
return `<!DOCTYPE html>
|
| 37 |
+
<html lang="en" data-theme="dark">
|
| 38 |
+
<head>
|
| 39 |
+
<meta charset="UTF-8">
|
| 40 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 41 |
+
<title>${safeTitle}</title>
|
| 42 |
+
<meta name="description" content="${safeDesc}">
|
| 43 |
+
<meta name="author" content="${escapeHtml(authorsStr)}">
|
| 44 |
+
|
| 45 |
+
<!-- Open Graph -->
|
| 46 |
+
<meta property="og:type" content="article">
|
| 47 |
+
<meta property="og:title" content="${safeTitle}">
|
| 48 |
+
<meta property="og:description" content="${safeDesc}">
|
| 49 |
+
${meta.ogImage ? `<meta property="og:image" content="${escapeHtml(meta.ogImage)}">` : ""}
|
| 50 |
+
<meta property="article:published_time" content="${escapeHtml(meta.date)}">
|
| 51 |
+
${meta.authors.map((a) => `<meta property="article:author" content="${escapeHtml(a)}">`).join("\n ")}
|
| 52 |
+
|
| 53 |
+
<!-- Twitter Card -->
|
| 54 |
+
<meta name="twitter:card" content="summary_large_image">
|
| 55 |
+
<meta name="twitter:title" content="${safeTitle}">
|
| 56 |
+
<meta name="twitter:description" content="${safeDesc}">
|
| 57 |
+
${meta.ogImage ? `<meta name="twitter:image" content="${escapeHtml(meta.ogImage)}">` : ""}
|
| 58 |
+
|
| 59 |
+
<!-- KaTeX CSS -->
|
| 60 |
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.21/dist/katex.min.css"
|
| 61 |
+
integrity="sha384-zh0CIslj3dQfMxK3pZnPJAb3bprHitCE2vBz9TyOTICAn3fso5GYa90qPNMBclov"
|
| 62 |
+
crossorigin="anonymous">
|
| 63 |
+
|
| 64 |
+
<!-- highlight.js theme -->
|
| 65 |
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.9.0/build/styles/github-dark.min.css">
|
| 66 |
+
|
| 67 |
+
<style>
|
| 68 |
+
${cssTokens}
|
| 69 |
+
${cssArticle}
|
| 70 |
+
|
| 71 |
+
/* Published article layout */
|
| 72 |
+
body {
|
| 73 |
+
margin: 0;
|
| 74 |
+
padding: 0;
|
| 75 |
+
background: var(--bg-primary);
|
| 76 |
+
color: var(--text-primary);
|
| 77 |
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
| 78 |
+
}
|
| 79 |
+
|
| 80 |
+
.article-container {
|
| 81 |
+
max-width: 780px;
|
| 82 |
+
margin: 0 auto;
|
| 83 |
+
padding: 2rem 32px 4rem;
|
| 84 |
+
}
|
| 85 |
+
|
| 86 |
+
.article-header {
|
| 87 |
+
margin-bottom: 2rem;
|
| 88 |
+
padding-bottom: 1.5rem;
|
| 89 |
+
border-bottom: 1px solid var(--border);
|
| 90 |
+
}
|
| 91 |
+
|
| 92 |
+
.article-header h1 {
|
| 93 |
+
font-size: 2.5rem;
|
| 94 |
+
font-weight: 700;
|
| 95 |
+
color: var(--text-heading);
|
| 96 |
+
line-height: 1.2;
|
| 97 |
+
margin: 0 0 0.5rem;
|
| 98 |
+
letter-spacing: -0.02em;
|
| 99 |
+
}
|
| 100 |
+
|
| 101 |
+
.article-header .authors {
|
| 102 |
+
color: var(--text-secondary);
|
| 103 |
+
font-size: 0.9375rem;
|
| 104 |
+
margin-bottom: 0.25rem;
|
| 105 |
+
}
|
| 106 |
+
|
| 107 |
+
.article-header .date {
|
| 108 |
+
color: var(--text-tertiary);
|
| 109 |
+
font-size: 0.8125rem;
|
| 110 |
+
}
|
| 111 |
+
|
| 112 |
+
/* Theme toggle */
|
| 113 |
+
.theme-toggle {
|
| 114 |
+
position: fixed;
|
| 115 |
+
top: 1rem;
|
| 116 |
+
right: 1rem;
|
| 117 |
+
background: var(--bg-secondary);
|
| 118 |
+
border: 1px solid var(--border);
|
| 119 |
+
border-radius: 8px;
|
| 120 |
+
padding: 0.5rem;
|
| 121 |
+
cursor: pointer;
|
| 122 |
+
font-size: 1.2rem;
|
| 123 |
+
line-height: 1;
|
| 124 |
+
z-index: 100;
|
| 125 |
+
}
|
| 126 |
+
|
| 127 |
+
/* Accordion as details/summary */
|
| 128 |
+
details[data-component="accordion"] {
|
| 129 |
+
border: 1px solid var(--border);
|
| 130 |
+
border-radius: 8px;
|
| 131 |
+
padding: 0;
|
| 132 |
+
margin: 1em 0;
|
| 133 |
+
}
|
| 134 |
+
|
| 135 |
+
details[data-component="accordion"] > summary {
|
| 136 |
+
padding: 0.75rem 1rem;
|
| 137 |
+
cursor: pointer;
|
| 138 |
+
font-weight: 600;
|
| 139 |
+
color: var(--text-heading-secondary);
|
| 140 |
+
list-style: none;
|
| 141 |
+
user-select: none;
|
| 142 |
+
}
|
| 143 |
+
|
| 144 |
+
details[data-component="accordion"] > summary::-webkit-details-marker {
|
| 145 |
+
display: none;
|
| 146 |
+
}
|
| 147 |
+
|
| 148 |
+
details[data-component="accordion"] > summary::before {
|
| 149 |
+
content: "▸";
|
| 150 |
+
display: inline-block;
|
| 151 |
+
margin-right: 0.5rem;
|
| 152 |
+
transition: transform 0.2s;
|
| 153 |
+
}
|
| 154 |
+
|
| 155 |
+
details[data-component="accordion"][open] > summary::before {
|
| 156 |
+
transform: rotate(90deg);
|
| 157 |
+
}
|
| 158 |
+
|
| 159 |
+
details[data-component="accordion"] > .accordion-content {
|
| 160 |
+
padding: 0 1rem 1rem;
|
| 161 |
+
}
|
| 162 |
+
|
| 163 |
+
/* Image lightbox dialog */
|
| 164 |
+
dialog.lightbox {
|
| 165 |
+
border: none;
|
| 166 |
+
background: transparent;
|
| 167 |
+
padding: 0;
|
| 168 |
+
max-width: 95vw;
|
| 169 |
+
max-height: 95vh;
|
| 170 |
+
}
|
| 171 |
+
|
| 172 |
+
dialog.lightbox::backdrop {
|
| 173 |
+
background: rgba(0, 0, 0, 0.85);
|
| 174 |
+
}
|
| 175 |
+
|
| 176 |
+
dialog.lightbox img {
|
| 177 |
+
max-width: 95vw;
|
| 178 |
+
max-height: 90vh;
|
| 179 |
+
object-fit: contain;
|
| 180 |
+
border-radius: 4px;
|
| 181 |
+
}
|
| 182 |
+
</style>
|
| 183 |
+
</head>
|
| 184 |
+
<body>
|
| 185 |
+
<button class="theme-toggle" aria-label="Toggle theme" onclick="toggleTheme()">🌓</button>
|
| 186 |
+
|
| 187 |
+
<article class="article-container">
|
| 188 |
+
<header class="article-header">
|
| 189 |
+
<h1>${safeTitle}</h1>
|
| 190 |
+
${authorsStr ? `<p class="authors">${escapeHtml(authorsStr)}</p>` : ""}
|
| 191 |
+
${meta.date ? `<p class="date">${escapeHtml(formatDate(meta.date))}</p>` : ""}
|
| 192 |
+
</header>
|
| 193 |
+
|
| 194 |
+
<div class="tiptap">
|
| 195 |
+
${enrichedBody}
|
| 196 |
+
</div>
|
| 197 |
+
</article>
|
| 198 |
+
|
| 199 |
+
<!-- Image lightbox -->
|
| 200 |
+
<dialog class="lightbox" id="lightbox">
|
| 201 |
+
<img id="lightbox-img" src="" alt="">
|
| 202 |
+
</dialog>
|
| 203 |
+
|
| 204 |
+
<script>
|
| 205 |
+
// Theme toggle
|
| 206 |
+
function toggleTheme() {
|
| 207 |
+
const html = document.documentElement;
|
| 208 |
+
const current = html.getAttribute('data-theme');
|
| 209 |
+
html.setAttribute('data-theme', current === 'dark' ? 'light' : 'dark');
|
| 210 |
+
}
|
| 211 |
+
|
| 212 |
+
// Image lightbox
|
| 213 |
+
document.querySelectorAll('.tiptap img').forEach(function(img) {
|
| 214 |
+
img.style.cursor = 'zoom-in';
|
| 215 |
+
img.addEventListener('click', function() {
|
| 216 |
+
var d = document.getElementById('lightbox');
|
| 217 |
+
document.getElementById('lightbox-img').src = img.src;
|
| 218 |
+
d.showModal();
|
| 219 |
+
});
|
| 220 |
+
});
|
| 221 |
+
document.getElementById('lightbox').addEventListener('click', function(e) {
|
| 222 |
+
if (e.target === this) this.close();
|
| 223 |
+
});
|
| 224 |
+
|
| 225 |
+
// Glossary tooltips
|
| 226 |
+
document.querySelectorAll('[data-type="glossary"]').forEach(function(el) {
|
| 227 |
+
el.setAttribute('tabindex', '0');
|
| 228 |
+
});
|
| 229 |
+
</script>
|
| 230 |
+
</body>
|
| 231 |
+
</html>`;
|
| 232 |
+
}
|
| 233 |
+
|
| 234 |
+
/**
|
| 235 |
+
* Post-process the raw generateHTML output:
|
| 236 |
+
* - Transform accordion divs into <details><summary>
|
| 237 |
+
* - Transform htmlEmbed into iframes
|
| 238 |
+
* - Add semantic structure where possible
|
| 239 |
+
*/
|
| 240 |
+
function postProcess(html: string): string {
|
| 241 |
+
let result = html;
|
| 242 |
+
|
| 243 |
+
// Accordion: div[data-component="accordion"] → <details><summary>
|
| 244 |
+
result = result.replace(
|
| 245 |
+
/<div[^>]*data-component="accordion"[^>]*>([\s\S]*?)<\/div>/g,
|
| 246 |
+
(_match, inner) => {
|
| 247 |
+
const titleMatch = inner.match(/data-title="([^"]*)"/);
|
| 248 |
+
const openMatch = inner.match(/data-open="true"/);
|
| 249 |
+
const title = titleMatch ? titleMatch[1] : "Details";
|
| 250 |
+
return `<details data-component="accordion"${openMatch ? " open" : ""}>
|
| 251 |
+
<summary>${escapeHtml(title)}</summary>
|
| 252 |
+
<div class="accordion-content">${inner}</div>
|
| 253 |
+
</details>`;
|
| 254 |
+
}
|
| 255 |
+
);
|
| 256 |
+
|
| 257 |
+
// HtmlEmbed: div[data-component="htmlEmbed"] → iframe with srcdoc
|
| 258 |
+
result = result.replace(
|
| 259 |
+
/<div[^>]*data-component="htmlEmbed"[^>]*data-src="([^"]*)"[^>]*><\/div>/g,
|
| 260 |
+
(_match, src) => {
|
| 261 |
+
return `<div class="html-embed-container">
|
| 262 |
+
<iframe srcdoc="" data-embed-src="${escapeHtml(src)}"
|
| 263 |
+
style="width:100%;border:none;min-height:400px;"
|
| 264 |
+
loading="lazy" sandbox="allow-scripts"></iframe>
|
| 265 |
+
</div>`;
|
| 266 |
+
}
|
| 267 |
+
);
|
| 268 |
+
|
| 269 |
+
return result;
|
| 270 |
+
}
|
| 271 |
+
|
| 272 |
+
function escapeHtml(str: string): string {
|
| 273 |
+
return str
|
| 274 |
+
.replace(/&/g, "&")
|
| 275 |
+
.replace(/</g, "<")
|
| 276 |
+
.replace(/>/g, ">")
|
| 277 |
+
.replace(/"/g, """)
|
| 278 |
+
.replace(/'/g, "'");
|
| 279 |
+
}
|
| 280 |
+
|
| 281 |
+
function formatDate(dateStr: string): string {
|
| 282 |
+
try {
|
| 283 |
+
return new Date(dateStr).toLocaleDateString("en-US", {
|
| 284 |
+
year: "numeric",
|
| 285 |
+
month: "long",
|
| 286 |
+
day: "numeric",
|
| 287 |
+
});
|
| 288 |
+
} catch {
|
| 289 |
+
return dateStr;
|
| 290 |
+
}
|
| 291 |
+
}
|
backend/src/publisher/index.ts
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**
|
| 2 |
+
* Publish pipeline entry point.
|
| 3 |
+
*
|
| 4 |
+
* Orchestrates: Y.Doc → JSON → HTML → PDF/Thumbnail → Upload to HF
|
| 5 |
+
*/
|
| 6 |
+
|
| 7 |
+
import { readFileSync, existsSync } from "fs";
|
| 8 |
+
import { join, dirname } from "path";
|
| 9 |
+
import { fileURLToPath } from "url";
|
| 10 |
+
import * as Y from "yjs";
|
| 11 |
+
import { renderArticleHTML, type PublishMeta } from "./html-renderer.js";
|
| 12 |
+
import { isPdfEnabled, generatePdfAndThumbnail } from "./pdf-generator.js";
|
| 13 |
+
import {
|
| 14 |
+
isHfStorageEnabled,
|
| 15 |
+
uploadPublishedAssets,
|
| 16 |
+
} from "../hf-storage.js";
|
| 17 |
+
|
| 18 |
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
| 19 |
+
const DATA_DIR = join(__dirname, "..", "..", "data");
|
| 20 |
+
|
| 21 |
+
function docPath(name: string) {
|
| 22 |
+
return join(DATA_DIR, `${name.replace(/[^a-zA-Z0-9_-]/g, "_")}.yjs`);
|
| 23 |
+
}
|
| 24 |
+
|
| 25 |
+
/**
|
| 26 |
+
* Load CSS files for injection into the published HTML.
|
| 27 |
+
*/
|
| 28 |
+
function loadCSS(): { tokens: string; article: string } {
|
| 29 |
+
const frontendStyles = join(__dirname, "..", "..", "..", "frontend", "src", "styles");
|
| 30 |
+
const distStyles = join(__dirname, "..", "frontend-dist", "assets");
|
| 31 |
+
|
| 32 |
+
let tokens = "";
|
| 33 |
+
let article = "";
|
| 34 |
+
|
| 35 |
+
// Development: read source files directly
|
| 36 |
+
const tokensPath = join(frontendStyles, "tokens.css");
|
| 37 |
+
const articlePath = join(frontendStyles, "article.css");
|
| 38 |
+
|
| 39 |
+
if (existsSync(tokensPath)) {
|
| 40 |
+
tokens = readFileSync(tokensPath, "utf-8");
|
| 41 |
+
article = readFileSync(articlePath, "utf-8");
|
| 42 |
+
}
|
| 43 |
+
|
| 44 |
+
return { tokens, article };
|
| 45 |
+
}
|
| 46 |
+
|
| 47 |
+
/**
|
| 48 |
+
* Extract TipTap JSON and frontmatter from a Yjs document.
|
| 49 |
+
*/
|
| 50 |
+
function extractFromYDoc(ydoc: Y.Doc): {
|
| 51 |
+
json: Record<string, unknown>;
|
| 52 |
+
frontmatter: Record<string, unknown>;
|
| 53 |
+
} {
|
| 54 |
+
const fragment = ydoc.getXmlFragment("default");
|
| 55 |
+
|
| 56 |
+
// The Hocuspocus/TipTap collaboration stores the ProseMirror document
|
| 57 |
+
// inside a Y.XmlFragment. We need to extract it as JSON.
|
| 58 |
+
// For now, we'll try reading the JSON from the shared map if available.
|
| 59 |
+
const metaMap = ydoc.getMap("frontmatter");
|
| 60 |
+
const frontmatter: Record<string, unknown> = {};
|
| 61 |
+
|
| 62 |
+
metaMap.forEach((value, key) => {
|
| 63 |
+
frontmatter[key] = value;
|
| 64 |
+
});
|
| 65 |
+
|
| 66 |
+
// TipTap JSON is stored as the XmlFragment's prosemirror representation
|
| 67 |
+
// We need to reconstruct it. The simplest approach: store the JSON
|
| 68 |
+
// in a shared type when the editor saves.
|
| 69 |
+
const jsonMap = ydoc.getMap("tiptap-json");
|
| 70 |
+
const stored = jsonMap.get("doc");
|
| 71 |
+
|
| 72 |
+
if (stored && typeof stored === "object") {
|
| 73 |
+
return { json: stored as Record<string, unknown>, frontmatter };
|
| 74 |
+
}
|
| 75 |
+
|
| 76 |
+
// Fallback: return empty doc
|
| 77 |
+
return {
|
| 78 |
+
json: { type: "doc", content: [{ type: "paragraph" }] },
|
| 79 |
+
frontmatter,
|
| 80 |
+
};
|
| 81 |
+
}
|
| 82 |
+
|
| 83 |
+
export interface PublishResult {
|
| 84 |
+
htmlUrl: string | null;
|
| 85 |
+
pdfUrl: string | null;
|
| 86 |
+
thumbUrl: string | null;
|
| 87 |
+
success: boolean;
|
| 88 |
+
error?: string;
|
| 89 |
+
}
|
| 90 |
+
|
| 91 |
+
/**
|
| 92 |
+
* Run the full publish pipeline for a document.
|
| 93 |
+
*/
|
| 94 |
+
export async function publishDocument(docName: string): Promise<PublishResult> {
|
| 95 |
+
const p = docPath(docName);
|
| 96 |
+
|
| 97 |
+
if (!existsSync(p)) {
|
| 98 |
+
return { htmlUrl: null, pdfUrl: null, thumbUrl: null, success: false, error: "Document not found" };
|
| 99 |
+
}
|
| 100 |
+
|
| 101 |
+
// Load Yjs document
|
| 102 |
+
const ydoc = new Y.Doc();
|
| 103 |
+
const data = readFileSync(p);
|
| 104 |
+
Y.applyUpdate(ydoc, new Uint8Array(data));
|
| 105 |
+
|
| 106 |
+
// Extract content
|
| 107 |
+
const { json, frontmatter } = extractFromYDoc(ydoc);
|
| 108 |
+
|
| 109 |
+
// Build metadata
|
| 110 |
+
const meta: PublishMeta = {
|
| 111 |
+
title: (frontmatter.title as string) || docName,
|
| 112 |
+
description: (frontmatter.description as string) || "",
|
| 113 |
+
authors: Array.isArray(frontmatter.authors)
|
| 114 |
+
? (frontmatter.authors as string[])
|
| 115 |
+
: [],
|
| 116 |
+
date: (frontmatter.date as string) || new Date().toISOString(),
|
| 117 |
+
};
|
| 118 |
+
|
| 119 |
+
// Load CSS
|
| 120 |
+
const css = loadCSS();
|
| 121 |
+
|
| 122 |
+
// Generate HTML
|
| 123 |
+
const html = renderArticleHTML(json, meta, css.tokens, css.article);
|
| 124 |
+
|
| 125 |
+
// Generate PDF + thumbnail
|
| 126 |
+
let pdf: Buffer | null = null;
|
| 127 |
+
let thumbnail: Buffer | null = null;
|
| 128 |
+
|
| 129 |
+
if (isPdfEnabled()) {
|
| 130 |
+
try {
|
| 131 |
+
const assets = await generatePdfAndThumbnail(html);
|
| 132 |
+
pdf = assets.pdf;
|
| 133 |
+
thumbnail = assets.thumbnail;
|
| 134 |
+
} catch (err) {
|
| 135 |
+
console.error("[publish] PDF generation failed:", err);
|
| 136 |
+
}
|
| 137 |
+
}
|
| 138 |
+
|
| 139 |
+
// Upload to HF dataset
|
| 140 |
+
if (isHfStorageEnabled()) {
|
| 141 |
+
try {
|
| 142 |
+
const urls = await uploadPublishedAssets(docName, {
|
| 143 |
+
html,
|
| 144 |
+
pdf,
|
| 145 |
+
thumbnail,
|
| 146 |
+
meta: meta as any,
|
| 147 |
+
});
|
| 148 |
+
return { ...urls, success: true };
|
| 149 |
+
} catch (err) {
|
| 150 |
+
console.error("[publish] Upload failed:", err);
|
| 151 |
+
return {
|
| 152 |
+
htmlUrl: null,
|
| 153 |
+
pdfUrl: null,
|
| 154 |
+
thumbUrl: null,
|
| 155 |
+
success: false,
|
| 156 |
+
error: (err as Error).message,
|
| 157 |
+
};
|
| 158 |
+
}
|
| 159 |
+
}
|
| 160 |
+
|
| 161 |
+
// Local-only: save to data/published/
|
| 162 |
+
const { writeFileSync, mkdirSync } = await import("fs");
|
| 163 |
+
const publishDir = join(DATA_DIR, "published", docName.replace(/[^a-zA-Z0-9_-]/g, "_"));
|
| 164 |
+
mkdirSync(publishDir, { recursive: true });
|
| 165 |
+
writeFileSync(join(publishDir, "index.html"), html);
|
| 166 |
+
if (pdf) writeFileSync(join(publishDir, "article.pdf"), pdf);
|
| 167 |
+
if (thumbnail) writeFileSync(join(publishDir, "thumb.jpg"), thumbnail);
|
| 168 |
+
writeFileSync(join(publishDir, "meta.json"), JSON.stringify(meta, null, 2));
|
| 169 |
+
|
| 170 |
+
return {
|
| 171 |
+
htmlUrl: `/published/${docName}/index.html`,
|
| 172 |
+
pdfUrl: pdf ? `/published/${docName}/article.pdf` : null,
|
| 173 |
+
thumbUrl: thumbnail ? `/published/${docName}/thumb.jpg` : null,
|
| 174 |
+
success: true,
|
| 175 |
+
};
|
| 176 |
+
}
|
backend/src/publisher/pdf-generator.ts
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**
|
| 2 |
+
* PDF and OG thumbnail generation via Playwright.
|
| 3 |
+
*
|
| 4 |
+
* Launches headless Chromium, loads the published HTML,
|
| 5 |
+
* waits for rendering, then captures PDF + screenshot.
|
| 6 |
+
*/
|
| 7 |
+
|
| 8 |
+
let playwrightAvailable = false;
|
| 9 |
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
| 10 |
+
let chromium: any;
|
| 11 |
+
|
| 12 |
+
try {
|
| 13 |
+
// Dynamic import: playwright is optional (only in Docker production image)
|
| 14 |
+
const pw = await import(/* @vite-ignore */ "playwright");
|
| 15 |
+
chromium = pw.chromium;
|
| 16 |
+
playwrightAvailable = true;
|
| 17 |
+
} catch {
|
| 18 |
+
console.warn("[publisher] Playwright not installed, PDF/thumbnail generation disabled");
|
| 19 |
+
}
|
| 20 |
+
|
| 21 |
+
export function isPdfEnabled(): boolean {
|
| 22 |
+
return playwrightAvailable && process.env.ENABLE_PDF !== "false";
|
| 23 |
+
}
|
| 24 |
+
|
| 25 |
+
export interface GeneratedAssets {
|
| 26 |
+
pdf: Buffer | null;
|
| 27 |
+
thumbnail: Buffer | null;
|
| 28 |
+
}
|
| 29 |
+
|
| 30 |
+
/**
|
| 31 |
+
* Generate PDF and OG thumbnail from HTML string.
|
| 32 |
+
*/
|
| 33 |
+
export async function generatePdfAndThumbnail(
|
| 34 |
+
html: string
|
| 35 |
+
): Promise<GeneratedAssets> {
|
| 36 |
+
if (!playwrightAvailable) {
|
| 37 |
+
return { pdf: null, thumbnail: null };
|
| 38 |
+
}
|
| 39 |
+
|
| 40 |
+
const browser = await chromium.launch({
|
| 41 |
+
args: ["--no-sandbox", "--disable-setuid-sandbox"],
|
| 42 |
+
});
|
| 43 |
+
|
| 44 |
+
try {
|
| 45 |
+
const page = await browser.newPage();
|
| 46 |
+
|
| 47 |
+
await page.setContent(html, { waitUntil: "networkidle" });
|
| 48 |
+
|
| 49 |
+
// Wait a bit for fonts, KaTeX, images
|
| 50 |
+
await page.waitForTimeout(2000);
|
| 51 |
+
|
| 52 |
+
// OG Thumbnail (1200x630)
|
| 53 |
+
await page.setViewportSize({ width: 1200, height: 630 });
|
| 54 |
+
const thumbnail = await page.screenshot({
|
| 55 |
+
type: "jpeg",
|
| 56 |
+
quality: 85,
|
| 57 |
+
clip: { x: 0, y: 0, width: 1200, height: 630 },
|
| 58 |
+
});
|
| 59 |
+
|
| 60 |
+
// PDF
|
| 61 |
+
const pdf = await page.pdf({
|
| 62 |
+
format: "A4",
|
| 63 |
+
printBackground: true,
|
| 64 |
+
margin: { top: "1cm", bottom: "1cm", left: "1.5cm", right: "1.5cm" },
|
| 65 |
+
});
|
| 66 |
+
|
| 67 |
+
return {
|
| 68 |
+
pdf: Buffer.from(pdf),
|
| 69 |
+
thumbnail: Buffer.from(thumbnail),
|
| 70 |
+
};
|
| 71 |
+
} finally {
|
| 72 |
+
await browser.close();
|
| 73 |
+
}
|
| 74 |
+
}
|
backend/src/server.ts
CHANGED
|
@@ -18,6 +18,7 @@ import {
|
|
| 18 |
flushAll,
|
| 19 |
} from "./hf-storage.js";
|
| 20 |
import { resolveUser, extractToken, isOAuthEnabled } from "./auth.js";
|
|
|
|
| 21 |
|
| 22 |
const __dirname = dirname(fileURLToPath(import.meta.url));
|
| 23 |
const DATA_DIR = join(__dirname, "..", "data");
|
|
@@ -137,6 +138,38 @@ app.post("/api/chat", handleChat);
|
|
| 137 |
|
| 138 |
app.use("/api/citations", citationsRouter);
|
| 139 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 140 |
// ---------- Image Upload ----------
|
| 141 |
|
| 142 |
const upload = multer({ storage: multer.memoryStorage(), limits: { fileSize: 10 * 1024 * 1024 } });
|
|
@@ -172,6 +205,48 @@ app.post("/api/upload", upload.single("file"), async (req, res) => {
|
|
| 172 |
// Serve locally uploaded images
|
| 173 |
app.use("/uploads", express.static(join(DATA_DIR, "uploads")));
|
| 174 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 175 |
// ---------- Static frontend ----------
|
| 176 |
|
| 177 |
const staticDir =
|
|
@@ -181,7 +256,46 @@ const staticDir =
|
|
| 181 |
|
| 182 |
if (existsSync(staticDir)) {
|
| 183 |
app.use(express.static(staticDir));
|
| 184 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 185 |
res.sendFile(join(staticDir, "index.html"));
|
| 186 |
});
|
| 187 |
}
|
|
|
|
| 18 |
flushAll,
|
| 19 |
} from "./hf-storage.js";
|
| 20 |
import { resolveUser, extractToken, isOAuthEnabled } from "./auth.js";
|
| 21 |
+
import { publishDocument } from "./publisher/index.js";
|
| 22 |
|
| 23 |
const __dirname = dirname(fileURLToPath(import.meta.url));
|
| 24 |
const DATA_DIR = join(__dirname, "..", "data");
|
|
|
|
| 138 |
|
| 139 |
app.use("/api/citations", citationsRouter);
|
| 140 |
|
| 141 |
+
// ---------- Publish ----------
|
| 142 |
+
|
| 143 |
+
app.post("/api/publish", async (req, res) => {
|
| 144 |
+
// Auth check: only editors can publish
|
| 145 |
+
if (oauthEnabled) {
|
| 146 |
+
const token = extractToken(req.headers.cookie);
|
| 147 |
+
const user = await resolveUser(token);
|
| 148 |
+
if (!user || !user.canEdit) {
|
| 149 |
+
res.status(403).json({ error: "Unauthorized: write access required" });
|
| 150 |
+
return;
|
| 151 |
+
}
|
| 152 |
+
}
|
| 153 |
+
|
| 154 |
+
const { docName } = req.body as { docName?: string };
|
| 155 |
+
if (!docName) {
|
| 156 |
+
res.status(400).json({ error: "docName is required" });
|
| 157 |
+
return;
|
| 158 |
+
}
|
| 159 |
+
|
| 160 |
+
try {
|
| 161 |
+
const result = await publishDocument(docName);
|
| 162 |
+
if (!result.success) {
|
| 163 |
+
res.status(500).json({ error: result.error || "Publish failed" });
|
| 164 |
+
return;
|
| 165 |
+
}
|
| 166 |
+
res.json(result);
|
| 167 |
+
} catch (err: any) {
|
| 168 |
+
console.error("[publish] error:", err);
|
| 169 |
+
res.status(500).json({ error: err.message || "Publish failed" });
|
| 170 |
+
}
|
| 171 |
+
});
|
| 172 |
+
|
| 173 |
// ---------- Image Upload ----------
|
| 174 |
|
| 175 |
const upload = multer({ storage: multer.memoryStorage(), limits: { fileSize: 10 * 1024 * 1024 } });
|
|
|
|
| 205 |
// Serve locally uploaded images
|
| 206 |
app.use("/uploads", express.static(join(DATA_DIR, "uploads")));
|
| 207 |
|
| 208 |
+
// Serve locally published articles
|
| 209 |
+
app.use("/published", express.static(join(DATA_DIR, "published")));
|
| 210 |
+
|
| 211 |
+
// ---------- Image proxy for private datasets ----------
|
| 212 |
+
|
| 213 |
+
app.get("/api/images/*", async (req, res) => {
|
| 214 |
+
const imagePath = (req.params as any)[0] as string;
|
| 215 |
+
if (!imagePath || imagePath.includes("..")) {
|
| 216 |
+
res.status(400).send("Invalid path");
|
| 217 |
+
return;
|
| 218 |
+
}
|
| 219 |
+
|
| 220 |
+
const HF_DATASET_ID = process.env.HF_DATASET_ID || "";
|
| 221 |
+
const HF_TOKEN = process.env.HF_TOKEN || "";
|
| 222 |
+
|
| 223 |
+
if (!HF_DATASET_ID || !HF_TOKEN) {
|
| 224 |
+
res.status(404).send("Not configured");
|
| 225 |
+
return;
|
| 226 |
+
}
|
| 227 |
+
|
| 228 |
+
try {
|
| 229 |
+
const url = `https://huggingface.co/datasets/${HF_DATASET_ID}/resolve/main/images/${imagePath}`;
|
| 230 |
+
const response = await fetch(url, {
|
| 231 |
+
headers: { Authorization: `Bearer ${HF_TOKEN}` },
|
| 232 |
+
});
|
| 233 |
+
|
| 234 |
+
if (!response.ok) {
|
| 235 |
+
res.status(response.status).send("Not found");
|
| 236 |
+
return;
|
| 237 |
+
}
|
| 238 |
+
|
| 239 |
+
const contentType = response.headers.get("content-type");
|
| 240 |
+
if (contentType) res.setHeader("Content-Type", contentType);
|
| 241 |
+
res.setHeader("Cache-Control", "public, max-age=86400");
|
| 242 |
+
|
| 243 |
+
const buffer = Buffer.from(await response.arrayBuffer());
|
| 244 |
+
res.send(buffer);
|
| 245 |
+
} catch {
|
| 246 |
+
res.status(500).send("Proxy error");
|
| 247 |
+
}
|
| 248 |
+
});
|
| 249 |
+
|
| 250 |
// ---------- Static frontend ----------
|
| 251 |
|
| 252 |
const staticDir =
|
|
|
|
| 256 |
|
| 257 |
if (existsSync(staticDir)) {
|
| 258 |
app.use(express.static(staticDir));
|
| 259 |
+
|
| 260 |
+
app.get("*", async (req, res) => {
|
| 261 |
+
// If OAuth is enabled, serve published HTML to unauthenticated visitors
|
| 262 |
+
if (oauthEnabled) {
|
| 263 |
+
const token = extractToken(req.headers.cookie);
|
| 264 |
+
const user = await resolveUser(token);
|
| 265 |
+
|
| 266 |
+
if (!user || !user.canEdit) {
|
| 267 |
+
// Try to serve published version
|
| 268 |
+
const docMatch = req.query.doc as string | undefined;
|
| 269 |
+
const docName = docMatch || "default";
|
| 270 |
+
const safeName = docName.replace(/[^a-zA-Z0-9_-]/g, "_");
|
| 271 |
+
const publishedPath = join(DATA_DIR, "published", safeName, "index.html");
|
| 272 |
+
|
| 273 |
+
if (existsSync(publishedPath)) {
|
| 274 |
+
res.sendFile(publishedPath);
|
| 275 |
+
return;
|
| 276 |
+
}
|
| 277 |
+
|
| 278 |
+
// No published version: show a simple message
|
| 279 |
+
if (!user) {
|
| 280 |
+
res.status(200).send(`
|
| 281 |
+
<!DOCTYPE html>
|
| 282 |
+
<html>
|
| 283 |
+
<head><title>Collab Editor</title></head>
|
| 284 |
+
<body style="display:flex;align-items:center;justify-content:center;height:100vh;margin:0;font-family:system-ui;background:#0a0a0a;color:#aaa;">
|
| 285 |
+
<div style="text-align:center;">
|
| 286 |
+
<h1 style="color:#fff;">This article is not yet published</h1>
|
| 287 |
+
<p>Log in with your Hugging Face account to edit.</p>
|
| 288 |
+
<a href="/oauth/authorize" style="color:#958DF1;">Log in</a>
|
| 289 |
+
</div>
|
| 290 |
+
</body>
|
| 291 |
+
</html>
|
| 292 |
+
`);
|
| 293 |
+
return;
|
| 294 |
+
}
|
| 295 |
+
}
|
| 296 |
+
}
|
| 297 |
+
|
| 298 |
+
// Authenticated editors get the SPA
|
| 299 |
res.sendFile(join(staticDir, "index.html"));
|
| 300 |
});
|
| 301 |
}
|
backend/src/types.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
declare module "@citation-js/core";
|
| 2 |
+
declare module "@citation-js/plugin-bibtex";
|
| 3 |
+
declare module "@citation-js/plugin-csl";
|
| 4 |
+
declare module "@citation-js/plugin-doi";
|
| 5 |
+
|
| 6 |
+
// Playwright is optional - only available in Docker production image
|
| 7 |
+
declare module "playwright" {
|
| 8 |
+
export const chromium: {
|
| 9 |
+
launch(options?: Record<string, unknown>): Promise<any>;
|
| 10 |
+
};
|
| 11 |
+
}
|