tfrere HF Staff commited on
Commit
db4f7ee
·
1 Parent(s): fca850a

fix: use TiptapTransformer to extract Y.Doc content for publish

Browse files

The old extractFromYDoc relied on a Y.Map("tiptap-json") that the
editor never populated. Now uses @hocuspocus /transformer to properly
convert the Y.XmlFragment to ProseMirror JSON using the same
extensions as the server-side HTML renderer.

Made-with: Cursor

backend/package-lock.json CHANGED
@@ -16,6 +16,7 @@
16
  "@citation-js/plugin-doi": "^0.7.21",
17
  "@hocuspocus/extension-database": "^3.4.4",
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",
@@ -689,6 +690,21 @@
689
  "yjs": "^13.6.8"
690
  }
691
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
692
  "node_modules/@huggingface/hub": {
693
  "version": "2.11.0",
694
  "resolved": "https://registry.npmjs.org/@huggingface/hub/-/hub-2.11.0.tgz",
@@ -3242,6 +3258,31 @@
3242
  }
3243
  }
3244
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3245
  "node_modules/y-protocols": {
3246
  "version": "1.0.7",
3247
  "resolved": "https://registry.npmjs.org/y-protocols/-/y-protocols-1.0.7.tgz",
 
16
  "@citation-js/plugin-doi": "^0.7.21",
17
  "@hocuspocus/extension-database": "^3.4.4",
18
  "@hocuspocus/server": "^3.4.4",
19
+ "@hocuspocus/transformer": "^3.4.4",
20
  "@huggingface/hub": "^2.11.0",
21
  "@openrouter/ai-sdk-provider": "^2.5.1",
22
  "@tiptap/core": "^3.22.3",
 
690
  "yjs": "^13.6.8"
691
  }
692
  },
693
+ "node_modules/@hocuspocus/transformer": {
694
+ "version": "3.4.4",
695
+ "resolved": "https://registry.npmjs.org/@hocuspocus/transformer/-/transformer-3.4.4.tgz",
696
+ "integrity": "sha512-X0EJ863LV97YbL5m8WTt4NDSC6uHi6ZCq/teIH5aholdjdhdTmFrzMemdhha/ZqPUZyaKhOoAmZzwR55HZLPpQ==",
697
+ "license": "MIT",
698
+ "dependencies": {
699
+ "@tiptap/starter-kit": "^3.0.1"
700
+ },
701
+ "peerDependencies": {
702
+ "@tiptap/core": "^3.0.1",
703
+ "@tiptap/pm": "^3.0.1",
704
+ "y-prosemirror": "^1.2.1",
705
+ "yjs": "^13.6.8"
706
+ }
707
+ },
708
  "node_modules/@huggingface/hub": {
709
  "version": "2.11.0",
710
  "resolved": "https://registry.npmjs.org/@huggingface/hub/-/hub-2.11.0.tgz",
 
3258
  }
3259
  }
3260
  },
3261
+ "node_modules/y-prosemirror": {
3262
+ "version": "1.3.7",
3263
+ "resolved": "https://registry.npmjs.org/y-prosemirror/-/y-prosemirror-1.3.7.tgz",
3264
+ "integrity": "sha512-NpM99WSdD4Fx4if5xOMDpPtU3oAmTSjlzh5U4353ABbRHl1HtAFUx6HlebLZfyFxXN9jzKMDkVbcRjqOZVkYQg==",
3265
+ "license": "MIT",
3266
+ "peer": true,
3267
+ "dependencies": {
3268
+ "lib0": "^0.2.109"
3269
+ },
3270
+ "engines": {
3271
+ "node": ">=16.0.0",
3272
+ "npm": ">=8.0.0"
3273
+ },
3274
+ "funding": {
3275
+ "type": "GitHub Sponsors ❤",
3276
+ "url": "https://github.com/sponsors/dmonad"
3277
+ },
3278
+ "peerDependencies": {
3279
+ "prosemirror-model": "^1.7.1",
3280
+ "prosemirror-state": "^1.2.3",
3281
+ "prosemirror-view": "^1.9.10",
3282
+ "y-protocols": "^1.0.1",
3283
+ "yjs": "^13.5.38"
3284
+ }
3285
+ },
3286
  "node_modules/y-protocols": {
3287
  "version": "1.0.7",
3288
  "resolved": "https://registry.npmjs.org/y-protocols/-/y-protocols-1.0.7.tgz",
backend/package.json CHANGED
@@ -17,6 +17,7 @@
17
  "@citation-js/plugin-doi": "^0.7.21",
18
  "@hocuspocus/extension-database": "^3.4.4",
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",
 
17
  "@citation-js/plugin-doi": "^0.7.21",
18
  "@hocuspocus/extension-database": "^3.4.4",
19
  "@hocuspocus/server": "^3.4.4",
20
+ "@hocuspocus/transformer": "^3.4.4",
21
  "@huggingface/hub": "^2.11.0",
22
  "@openrouter/ai-sdk-provider": "^2.5.1",
23
  "@tiptap/core": "^3.22.3",
backend/src/publisher/index.ts CHANGED
@@ -8,7 +8,9 @@ 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,
@@ -46,34 +48,29 @@ function loadCSS(): { tokens: string; article: string } {
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,
 
8
  import { join, dirname } from "path";
9
  import { fileURLToPath } from "url";
10
  import * as Y from "yjs";
11
+ import { TiptapTransformer } from "@hocuspocus/transformer";
12
  import { renderArticleHTML, type PublishMeta } from "./html-renderer.js";
13
+ import { getServerExtensions } from "./extensions.js";
14
  import { isPdfEnabled, generatePdfAndThumbnail } from "./pdf-generator.js";
15
  import {
16
  isHfStorageEnabled,
 
48
 
49
  /**
50
  * Extract TipTap JSON and frontmatter from a Yjs document.
51
+ * Uses @hocuspocus/transformer to convert the XmlFragment to ProseMirror JSON.
52
  */
53
  function extractFromYDoc(ydoc: Y.Doc): {
54
  json: Record<string, unknown>;
55
  frontmatter: Record<string, unknown>;
56
  } {
 
 
 
 
 
57
  const metaMap = ydoc.getMap("frontmatter");
58
  const frontmatter: Record<string, unknown> = {};
 
59
  metaMap.forEach((value, key) => {
60
  frontmatter[key] = value;
61
  });
62
 
63
+ // Convert Y.XmlFragment ProseMirror JSON using TipTap extensions
64
+ const extensions = getServerExtensions();
65
+ const json = TiptapTransformer
66
+ .extensions(extensions)
67
+ .fromYdoc(ydoc, "default");
68
 
69
+ if (json && typeof json === "object" && json.type === "doc") {
70
+ return { json, frontmatter };
71
  }
72
 
73
+ console.warn("[publish] TiptapTransformer returned unexpected format, using fallback");
74
  return {
75
  json: { type: "doc", content: [{ type: "paragraph" }] },
76
  frontmatter,