noteloom 0.3.2 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. package/README.md +344 -53
  2. package/bin/noteloom.mjs +139 -0
  3. package/dist/canvas.cjs +2 -0
  4. package/dist/canvas.cjs.map +1 -0
  5. package/dist/canvas.d.ts +2 -0
  6. package/dist/canvas.js +5 -0
  7. package/dist/canvas.js.map +1 -0
  8. package/dist/collab.cjs +2 -0
  9. package/dist/collab.cjs.map +1 -0
  10. package/dist/collab.d.ts +19 -0
  11. package/dist/collab.js +379 -0
  12. package/dist/collab.js.map +1 -0
  13. package/dist/comments.cjs +2 -0
  14. package/dist/comments.cjs.map +1 -0
  15. package/dist/comments.d.ts +17 -0
  16. package/dist/comments.js +19 -0
  17. package/dist/comments.js.map +1 -0
  18. package/dist/index.cjs +2 -0
  19. package/dist/index.cjs.map +1 -0
  20. package/dist/index.d.ts +392 -32
  21. package/dist/index.js +3983 -0
  22. package/dist/index.js.map +1 -0
  23. package/dist/persistence.cjs +2 -0
  24. package/dist/persistence.cjs.map +1 -0
  25. package/dist/persistence.d.ts +11 -0
  26. package/dist/persistence.js +12 -0
  27. package/dist/persistence.js.map +1 -0
  28. package/dist/shared/CommentAvatar-TKqsM8vG.js +53 -0
  29. package/dist/shared/CommentAvatar-TKqsM8vG.js.map +1 -0
  30. package/dist/shared/CommentAvatar-V457IgoQ.cjs +2 -0
  31. package/dist/shared/CommentAvatar-V457IgoQ.cjs.map +1 -0
  32. package/dist/shared/CommentThreadCard-CAJ3oPdr.cjs +2 -0
  33. package/dist/shared/CommentThreadCard-CAJ3oPdr.cjs.map +1 -0
  34. package/dist/shared/CommentThreadCard-Cxf8GwHJ.js +213 -0
  35. package/dist/shared/CommentThreadCard-Cxf8GwHJ.js.map +1 -0
  36. package/dist/shared/CommentsPanel-BwVVyMOC.js +76 -0
  37. package/dist/shared/CommentsPanel-BwVVyMOC.js.map +1 -0
  38. package/dist/shared/CommentsPanel-CES4uzye.cjs +2 -0
  39. package/dist/shared/CommentsPanel-CES4uzye.cjs.map +1 -0
  40. package/dist/shared/VersionHistory-Bg6fufP5.js +1120 -0
  41. package/dist/shared/VersionHistory-Bg6fufP5.js.map +1 -0
  42. package/dist/shared/VersionHistory-DELa2Wbq.cjs +12 -0
  43. package/dist/shared/VersionHistory-DELa2Wbq.cjs.map +1 -0
  44. package/dist/shared/VoiceListeningIndicator-BOfVmLK0.js +338 -0
  45. package/dist/shared/VoiceListeningIndicator-BOfVmLK0.js.map +1 -0
  46. package/dist/shared/VoiceListeningIndicator-DNX8JlQM.cjs +2 -0
  47. package/dist/shared/VoiceListeningIndicator-DNX8JlQM.cjs.map +1 -0
  48. package/dist/shared/fieldRegistry-BoYyW3GH.cjs +2 -0
  49. package/dist/shared/fieldRegistry-BoYyW3GH.cjs.map +1 -0
  50. package/dist/shared/fieldRegistry-PjJWXFoB.js +223 -0
  51. package/dist/shared/fieldRegistry-PjJWXFoB.js.map +1 -0
  52. package/dist/shared/icons-C1_s0jKK.js +4269 -0
  53. package/dist/shared/icons-C1_s0jKK.js.map +1 -0
  54. package/dist/shared/icons-DtCsXuU6.cjs +3531 -0
  55. package/dist/shared/icons-DtCsXuU6.cjs.map +1 -0
  56. package/dist/shared/index-B9jyyL8u.js +2145 -0
  57. package/dist/shared/index-B9jyyL8u.js.map +1 -0
  58. package/dist/shared/index-D9WEXpYM.cjs +2 -0
  59. package/dist/shared/index-D9WEXpYM.cjs.map +1 -0
  60. package/dist/shared/indexedDbPersistence-BKlGPFTP.js +112 -0
  61. package/dist/shared/indexedDbPersistence-BKlGPFTP.js.map +1 -0
  62. package/dist/shared/indexedDbPersistence-BWJ1h5Vp.cjs +2 -0
  63. package/dist/shared/indexedDbPersistence-BWJ1h5Vp.cjs.map +1 -0
  64. package/dist/shared/leafBlockFactory-A9eUhSe-.js +335 -0
  65. package/dist/shared/leafBlockFactory-A9eUhSe-.js.map +1 -0
  66. package/dist/shared/leafBlockFactory-Dp382eP8.cjs +2 -0
  67. package/dist/shared/leafBlockFactory-Dp382eP8.cjs.map +1 -0
  68. package/dist/shared/serialize-DWEfk0vU.cjs +3 -0
  69. package/dist/shared/serialize-DWEfk0vU.cjs.map +1 -0
  70. package/dist/shared/serialize-T9Odwx24.js +86 -0
  71. package/dist/shared/serialize-T9Odwx24.js.map +1 -0
  72. package/dist/shared/starter-kit-CY5wzYDf.js +4980 -0
  73. package/dist/shared/starter-kit-CY5wzYDf.js.map +1 -0
  74. package/dist/shared/starter-kit-lPN-NpJ5.cjs +27 -0
  75. package/dist/shared/starter-kit-lPN-NpJ5.cjs.map +1 -0
  76. package/dist/shared/usePopoverEdgeClamp-CDG26jBM.cjs +2 -0
  77. package/dist/shared/usePopoverEdgeClamp-CDG26jBM.cjs.map +1 -0
  78. package/dist/shared/usePopoverEdgeClamp-gmFm0CnI.js +109 -0
  79. package/dist/shared/usePopoverEdgeClamp-gmFm0CnI.js.map +1 -0
  80. package/dist/shared/useServiceWorkerUpdate-BduabXv6.js +94 -0
  81. package/dist/shared/useServiceWorkerUpdate-BduabXv6.js.map +1 -0
  82. package/dist/shared/useServiceWorkerUpdate-DQboGesi.cjs +2 -0
  83. package/dist/shared/useServiceWorkerUpdate-DQboGesi.cjs.map +1 -0
  84. package/dist/starter-kit.cjs +2 -0
  85. package/dist/starter-kit.cjs.map +1 -0
  86. package/dist/starter-kit.d.ts +9 -0
  87. package/dist/starter-kit.js +10 -0
  88. package/dist/starter-kit.js.map +1 -0
  89. package/dist/style.css +3529 -3200
  90. package/dist/versions.cjs +2 -0
  91. package/dist/versions.cjs.map +1 -0
  92. package/dist/versions.d.ts +11 -0
  93. package/dist/versions.js +13 -0
  94. package/dist/versions.js.map +1 -0
  95. package/dist/voice.cjs +2 -0
  96. package/dist/voice.cjs.map +1 -0
  97. package/dist/voice.d.ts +7 -0
  98. package/dist/voice.js +8 -0
  99. package/dist/voice.js.map +1 -0
  100. package/package.json +159 -92
  101. package/dist/noteloom.cjs +0 -3209
  102. package/dist/noteloom.cjs.map +0 -1
  103. package/dist/noteloom.es.js +0 -14622
  104. package/dist/noteloom.es.js.map +0 -1
@@ -0,0 +1,2 @@
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("./shared/indexedDbPersistence-BWJ1h5Vp.cjs"),t=require("./shared/useServiceWorkerUpdate-DQboGesi.cjs");exports.deletePersistedDocument=e.deletePersistedDocument;exports.listPersistedDocumentIds=e.listPersistedDocumentIds;exports.loadPersistedDocument=e.loadPersistedDocument;exports.savePersistedDocument=e.savePersistedDocument;exports.createAutoPersistence=t.createAutoPersistence;exports.usePersistedDocument=t.usePersistedDocument;exports.useServiceWorkerUpdate=t.useServiceWorkerUpdate;
2
+ //# sourceMappingURL=persistence.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"persistence.cjs","sources":[],"sourcesContent":[],"names":[],"mappings":""}
@@ -0,0 +1,11 @@
1
+ // noteloom/persistence — types re-exported from the main declaration file.
2
+ export {
3
+ savePersistedDocument,
4
+ loadPersistedDocument,
5
+ deletePersistedDocument,
6
+ listPersistedDocumentIds,
7
+ createAutoPersistence,
8
+ usePersistedDocument,
9
+ useServiceWorkerUpdate,
10
+ DocumentJSON,
11
+ } from './index';
@@ -0,0 +1,12 @@
1
+ import { d as t, l as r, a, s as o } from "./shared/indexedDbPersistence-BKlGPFTP.js";
2
+ import { c, u, a as i } from "./shared/useServiceWorkerUpdate-BduabXv6.js";
3
+ export {
4
+ c as createAutoPersistence,
5
+ t as deletePersistedDocument,
6
+ r as listPersistedDocumentIds,
7
+ a as loadPersistedDocument,
8
+ o as savePersistedDocument,
9
+ u as usePersistedDocument,
10
+ i as useServiceWorkerUpdate
11
+ };
12
+ //# sourceMappingURL=persistence.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"persistence.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;"}
@@ -0,0 +1,53 @@
1
+ import { jsx as c } from "react/jsx-runtime";
2
+ const i = ["#e03131", "#e8590c", "#2f9e44", "#1971c2", "#9c36b5"];
3
+ function f(e) {
4
+ let n = 0;
5
+ const t = String(e ?? "");
6
+ for (let r = 0; r < t.length; r += 1) n = n * 31 + t.charCodeAt(r) | 0;
7
+ return i[Math.abs(n) % i.length];
8
+ }
9
+ function l(e) {
10
+ const n = String(e ?? "").trim();
11
+ if (!n) return "?";
12
+ const t = n.split(/\s+/).filter(Boolean);
13
+ return t.length >= 2 ? (t[0][0] + t[1][0]).toUpperCase() : n.slice(0, 2).toUpperCase();
14
+ }
15
+ const a = 60 * 1e3, s = 60 * a, o = 24 * s;
16
+ function u(e, n = Date.now()) {
17
+ const t = n - e;
18
+ if (t < a) return "just now";
19
+ if (t < s) {
20
+ const r = Math.floor(t / a);
21
+ return `${r} min${r === 1 ? "" : "s"} ago`;
22
+ }
23
+ if (t < o) {
24
+ const r = Math.floor(t / s);
25
+ return `${r} hour${r === 1 ? "" : "s"} ago`;
26
+ }
27
+ if (t < 7 * o) {
28
+ const r = Math.floor(t / o);
29
+ return `${r} day${r === 1 ? "" : "s"} ago`;
30
+ }
31
+ return new Date(e).toLocaleDateString();
32
+ }
33
+ function g({ authorId: e, size: n = 26 }) {
34
+ return /* @__PURE__ */ c(
35
+ "span",
36
+ {
37
+ className: "be-comment-avatar",
38
+ style: {
39
+ width: n,
40
+ height: n,
41
+ fontSize: Math.max(10, n * 0.4),
42
+ background: f(e)
43
+ },
44
+ "aria-hidden": "true",
45
+ children: l(e)
46
+ }
47
+ );
48
+ }
49
+ export {
50
+ g as C,
51
+ u as f
52
+ };
53
+ //# sourceMappingURL=CommentAvatar-TKqsM8vG.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CommentAvatar-TKqsM8vG.js","sources":["../../src/react/commentFormatting.js","../../src/react/CommentAvatar.jsx"],"sourcesContent":["// Same five colors FloatingToolbar's own text-color picker already offers\n// (see TEXT_COLORS in FloatingToolbar.jsx) — reused here rather than a new\n// palette, so an author's avatar color and any text color a user might pick\n// come from the same small, already-approved set.\nconst AVATAR_COLORS = ['#e03131', '#e8590c', '#2f9e44', '#1971c2', '#9c36b5'];\n\n/**\n * Deterministically picks one of AVATAR_COLORS for `authorId` — this\n * package has no identity system of its own (no profile pictures, no\n * stored user records), so every avatar is generated from the id/name\n * string alone, and needs to land on the SAME color every time for the\n * same author without any lookup.\n */\nexport function getAvatarColor(authorId) {\n let hash = 0;\n const str = String(authorId ?? '');\n for (let i = 0; i < str.length; i += 1) hash = (hash * 31 + str.charCodeAt(i)) | 0;\n return AVATAR_COLORS[Math.abs(hash) % AVATAR_COLORS.length];\n}\n\n/** \"Bailey Chen\" -> \"BC\", \"alice\" -> \"AL\", \"\" -> \"?\". */\nexport function getInitials(authorId) {\n const str = String(authorId ?? '').trim();\n if (!str) return '?';\n const parts = str.split(/\\s+/).filter(Boolean);\n if (parts.length >= 2) return (parts[0][0] + parts[1][0]).toUpperCase();\n return str.slice(0, 2).toUpperCase();\n}\n\nconst MINUTE_MS = 60 * 1000;\nconst HOUR_MS = 60 * MINUTE_MS;\nconst DAY_MS = 24 * HOUR_MS;\n\n/**\n * \"just now\" / \"5 min ago\" / \"3 hours ago\" / \"2 days ago\", falling back to\n * a plain locale date beyond a week — a display-only nicety computed fresh\n * on each render; `CommentMessage.createdAt` itself stays a plain epoch-ms\n * number everywhere else (storage, collab envelopes, exports).\n */\nexport function formatRelativeTime(timestamp, now = Date.now()) {\n const diff = now - timestamp;\n if (diff < MINUTE_MS) return 'just now';\n if (diff < HOUR_MS) {\n const m = Math.floor(diff / MINUTE_MS);\n return `${m} min${m === 1 ? '' : 's'} ago`;\n }\n if (diff < DAY_MS) {\n const h = Math.floor(diff / HOUR_MS);\n return `${h} hour${h === 1 ? '' : 's'} ago`;\n }\n if (diff < 7 * DAY_MS) {\n const d = Math.floor(diff / DAY_MS);\n return `${d} day${d === 1 ? '' : 's'} ago`;\n }\n return new Date(timestamp).toLocaleDateString();\n}\n","import { getAvatarColor, getInitials } from './commentFormatting.js';\n\n/**\n * A small colored circle with the author's initials — this package has no\n * profile-picture/identity concept at all (see EditorProvider's\n * `commentAuthorId`), so every avatar is generated purely from the author\n * id/name string rather than a real image, the same \"no identity, just an\n * id string\" stance `authorId` already takes everywhere else in comments.\n */\nexport function CommentAvatar({ authorId, size = 26 }) {\n return (\n <span\n className=\"be-comment-avatar\"\n style={{\n width: size,\n height: size,\n fontSize: Math.max(10, size * 0.4),\n background: getAvatarColor(authorId),\n }}\n aria-hidden=\"true\"\n >\n {getInitials(authorId)}\n </span>\n );\n}\n"],"names":["AVATAR_COLORS","getAvatarColor","authorId","hash","str","i","getInitials","parts","MINUTE_MS","HOUR_MS","DAY_MS","formatRelativeTime","timestamp","now","diff","m","h","d","CommentAvatar","size","jsx"],"mappings":";AAIA,MAAMA,IAAgB,CAAC,WAAW,WAAW,WAAW,WAAW,SAAS;AASrE,SAASC,EAAeC,GAAU;AACvC,MAAIC,IAAO;AACX,QAAMC,IAAM,OAAOF,KAAY,EAAE;AACjC,WAASG,IAAI,GAAGA,IAAID,EAAI,QAAQC,KAAK,EAAG,CAAAF,IAAQA,IAAO,KAAKC,EAAI,WAAWC,CAAC,IAAK;AACjF,SAAOL,EAAc,KAAK,IAAIG,CAAI,IAAIH,EAAc,MAAM;AAC5D;AAGO,SAASM,EAAYJ,GAAU;AACpC,QAAME,IAAM,OAAOF,KAAY,EAAE,EAAE,KAAI;AACvC,MAAI,CAACE,EAAK,QAAO;AACjB,QAAMG,IAAQH,EAAI,MAAM,KAAK,EAAE,OAAO,OAAO;AAC7C,SAAIG,EAAM,UAAU,KAAWA,EAAM,CAAC,EAAE,CAAC,IAAIA,EAAM,CAAC,EAAE,CAAC,GAAG,YAAW,IAC9DH,EAAI,MAAM,GAAG,CAAC,EAAE,YAAW;AACpC;AAEA,MAAMI,IAAY,KAAK,KACjBC,IAAU,KAAKD,GACfE,IAAS,KAAKD;AAQb,SAASE,EAAmBC,GAAWC,IAAM,KAAK,IAAG,GAAI;AAC9D,QAAMC,IAAOD,IAAMD;AACnB,MAAIE,IAAON,EAAW,QAAO;AAC7B,MAAIM,IAAOL,GAAS;AAClB,UAAMM,IAAI,KAAK,MAAMD,IAAON,CAAS;AACrC,WAAO,GAAGO,CAAC,OAAOA,MAAM,IAAI,KAAK,GAAG;AAAA,EACtC;AACA,MAAID,IAAOJ,GAAQ;AACjB,UAAMM,IAAI,KAAK,MAAMF,IAAOL,CAAO;AACnC,WAAO,GAAGO,CAAC,QAAQA,MAAM,IAAI,KAAK,GAAG;AAAA,EACvC;AACA,MAAIF,IAAO,IAAIJ,GAAQ;AACrB,UAAMO,IAAI,KAAK,MAAMH,IAAOJ,CAAM;AAClC,WAAO,GAAGO,CAAC,OAAOA,MAAM,IAAI,KAAK,GAAG;AAAA,EACtC;AACA,SAAO,IAAI,KAAKL,CAAS,EAAE,mBAAkB;AAC/C;AC9CO,SAASM,EAAc,EAAE,UAAAhB,GAAU,MAAAiB,IAAO,MAAM;AACrD,SACE,gBAAAC;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,WAAU;AAAA,MACV,OAAO;AAAA,QACL,OAAOD;AAAA,QACP,QAAQA;AAAA,QACR,UAAU,KAAK,IAAI,IAAIA,IAAO,GAAG;AAAA,QACjC,YAAYlB,EAAeC,CAAQ;AAAA,MAAA;AAAA,MAErC,eAAY;AAAA,MAEX,YAAYA,CAAQ;AAAA,IAAA;AAAA,EAAA;AAG3B;"}
@@ -0,0 +1,2 @@
1
+ "use strict";const c=require("react/jsx-runtime"),s=["#e03131","#e8590c","#2f9e44","#1971c2","#9c36b5"];function f(r){let e=0;const t=String(r??"");for(let n=0;n<t.length;n+=1)e=e*31+t.charCodeAt(n)|0;return s[Math.abs(e)%s.length]}function l(r){const e=String(r??"").trim();if(!e)return"?";const t=e.split(/\s+/).filter(Boolean);return t.length>=2?(t[0][0]+t[1][0]).toUpperCase():e.slice(0,2).toUpperCase()}const a=60*1e3,i=60*a,o=24*i;function u(r,e=Date.now()){const t=e-r;if(t<a)return"just now";if(t<i){const n=Math.floor(t/a);return`${n} min${n===1?"":"s"} ago`}if(t<o){const n=Math.floor(t/i);return`${n} hour${n===1?"":"s"} ago`}if(t<7*o){const n=Math.floor(t/o);return`${n} day${n===1?"":"s"} ago`}return new Date(r).toLocaleDateString()}function h({authorId:r,size:e=26}){return c.jsx("span",{className:"be-comment-avatar",style:{width:e,height:e,fontSize:Math.max(10,e*.4),background:f(r)},"aria-hidden":"true",children:l(r)})}exports.CommentAvatar=h;exports.formatRelativeTime=u;
2
+ //# sourceMappingURL=CommentAvatar-V457IgoQ.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CommentAvatar-V457IgoQ.cjs","sources":["../../src/react/commentFormatting.js","../../src/react/CommentAvatar.jsx"],"sourcesContent":["// Same five colors FloatingToolbar's own text-color picker already offers\n// (see TEXT_COLORS in FloatingToolbar.jsx) — reused here rather than a new\n// palette, so an author's avatar color and any text color a user might pick\n// come from the same small, already-approved set.\nconst AVATAR_COLORS = ['#e03131', '#e8590c', '#2f9e44', '#1971c2', '#9c36b5'];\n\n/**\n * Deterministically picks one of AVATAR_COLORS for `authorId` — this\n * package has no identity system of its own (no profile pictures, no\n * stored user records), so every avatar is generated from the id/name\n * string alone, and needs to land on the SAME color every time for the\n * same author without any lookup.\n */\nexport function getAvatarColor(authorId) {\n let hash = 0;\n const str = String(authorId ?? '');\n for (let i = 0; i < str.length; i += 1) hash = (hash * 31 + str.charCodeAt(i)) | 0;\n return AVATAR_COLORS[Math.abs(hash) % AVATAR_COLORS.length];\n}\n\n/** \"Bailey Chen\" -> \"BC\", \"alice\" -> \"AL\", \"\" -> \"?\". */\nexport function getInitials(authorId) {\n const str = String(authorId ?? '').trim();\n if (!str) return '?';\n const parts = str.split(/\\s+/).filter(Boolean);\n if (parts.length >= 2) return (parts[0][0] + parts[1][0]).toUpperCase();\n return str.slice(0, 2).toUpperCase();\n}\n\nconst MINUTE_MS = 60 * 1000;\nconst HOUR_MS = 60 * MINUTE_MS;\nconst DAY_MS = 24 * HOUR_MS;\n\n/**\n * \"just now\" / \"5 min ago\" / \"3 hours ago\" / \"2 days ago\", falling back to\n * a plain locale date beyond a week — a display-only nicety computed fresh\n * on each render; `CommentMessage.createdAt` itself stays a plain epoch-ms\n * number everywhere else (storage, collab envelopes, exports).\n */\nexport function formatRelativeTime(timestamp, now = Date.now()) {\n const diff = now - timestamp;\n if (diff < MINUTE_MS) return 'just now';\n if (diff < HOUR_MS) {\n const m = Math.floor(diff / MINUTE_MS);\n return `${m} min${m === 1 ? '' : 's'} ago`;\n }\n if (diff < DAY_MS) {\n const h = Math.floor(diff / HOUR_MS);\n return `${h} hour${h === 1 ? '' : 's'} ago`;\n }\n if (diff < 7 * DAY_MS) {\n const d = Math.floor(diff / DAY_MS);\n return `${d} day${d === 1 ? '' : 's'} ago`;\n }\n return new Date(timestamp).toLocaleDateString();\n}\n","import { getAvatarColor, getInitials } from './commentFormatting.js';\n\n/**\n * A small colored circle with the author's initials — this package has no\n * profile-picture/identity concept at all (see EditorProvider's\n * `commentAuthorId`), so every avatar is generated purely from the author\n * id/name string rather than a real image, the same \"no identity, just an\n * id string\" stance `authorId` already takes everywhere else in comments.\n */\nexport function CommentAvatar({ authorId, size = 26 }) {\n return (\n <span\n className=\"be-comment-avatar\"\n style={{\n width: size,\n height: size,\n fontSize: Math.max(10, size * 0.4),\n background: getAvatarColor(authorId),\n }}\n aria-hidden=\"true\"\n >\n {getInitials(authorId)}\n </span>\n );\n}\n"],"names":["AVATAR_COLORS","getAvatarColor","authorId","hash","str","i","getInitials","parts","MINUTE_MS","HOUR_MS","DAY_MS","formatRelativeTime","timestamp","now","diff","m","h","d","CommentAvatar","size","jsx"],"mappings":"kDAIMA,EAAgB,CAAC,UAAW,UAAW,UAAW,UAAW,SAAS,EASrE,SAASC,EAAeC,EAAU,CACvC,IAAIC,EAAO,EACX,MAAMC,EAAM,OAAOF,GAAY,EAAE,EACjC,QAASG,EAAI,EAAGA,EAAID,EAAI,OAAQC,GAAK,EAAGF,EAAQA,EAAO,GAAKC,EAAI,WAAWC,CAAC,EAAK,EACjF,OAAOL,EAAc,KAAK,IAAIG,CAAI,EAAIH,EAAc,MAAM,CAC5D,CAGO,SAASM,EAAYJ,EAAU,CACpC,MAAME,EAAM,OAAOF,GAAY,EAAE,EAAE,KAAI,EACvC,GAAI,CAACE,EAAK,MAAO,IACjB,MAAMG,EAAQH,EAAI,MAAM,KAAK,EAAE,OAAO,OAAO,EAC7C,OAAIG,EAAM,QAAU,GAAWA,EAAM,CAAC,EAAE,CAAC,EAAIA,EAAM,CAAC,EAAE,CAAC,GAAG,YAAW,EAC9DH,EAAI,MAAM,EAAG,CAAC,EAAE,YAAW,CACpC,CAEA,MAAMI,EAAY,GAAK,IACjBC,EAAU,GAAKD,EACfE,EAAS,GAAKD,EAQb,SAASE,EAAmBC,EAAWC,EAAM,KAAK,IAAG,EAAI,CAC9D,MAAMC,EAAOD,EAAMD,EACnB,GAAIE,EAAON,EAAW,MAAO,WAC7B,GAAIM,EAAOL,EAAS,CAClB,MAAMM,EAAI,KAAK,MAAMD,EAAON,CAAS,EACrC,MAAO,GAAGO,CAAC,OAAOA,IAAM,EAAI,GAAK,GAAG,MACtC,CACA,GAAID,EAAOJ,EAAQ,CACjB,MAAMM,EAAI,KAAK,MAAMF,EAAOL,CAAO,EACnC,MAAO,GAAGO,CAAC,QAAQA,IAAM,EAAI,GAAK,GAAG,MACvC,CACA,GAAIF,EAAO,EAAIJ,EAAQ,CACrB,MAAMO,EAAI,KAAK,MAAMH,EAAOJ,CAAM,EAClC,MAAO,GAAGO,CAAC,OAAOA,IAAM,EAAI,GAAK,GAAG,MACtC,CACA,OAAO,IAAI,KAAKL,CAAS,EAAE,mBAAkB,CAC/C,CC9CO,SAASM,EAAc,CAAE,SAAAhB,EAAU,KAAAiB,EAAO,IAAM,CACrD,OACEC,EAAAA,IAAC,OAAA,CACC,UAAU,oBACV,MAAO,CACL,MAAOD,EACP,OAAQA,EACR,SAAU,KAAK,IAAI,GAAIA,EAAO,EAAG,EACjC,WAAYlB,EAAeC,CAAQ,CAAA,EAErC,cAAY,OAEX,WAAYA,CAAQ,CAAA,CAAA,CAG3B"}
@@ -0,0 +1,2 @@
1
+ "use strict";const s=require("react/jsx-runtime"),b=require("react"),m=require("./icons-DtCsXuU6.cjs"),I=require("./CommentAvatar-V457IgoQ.cjs");function K(t,e){var o;const n=t.getBlock(e);return((o=n.props)==null?void 0:o.titleRunIds)??n.contentIds}function O(t,e){const n=(t==null?void 0:t.commentIds)??[];return n.includes(e)?t??{}:{...t,commentIds:[...n,e]}}function $(t,e){const n=(t==null?void 0:t.commentIds)??[];if(!n.includes(e))return t??{};const o={...t},c=n.filter(a=>a!==e);return c.length>0?o.commentIds=c:delete o.commentIds,o}function F(t,e,n,o,c){const a=O(e.marks,c);if(n===0&&o===e.value.length)return{type:"updateRun",id:e.id,patch:{marks:a}};const i=e.value.slice(0,n),d=e.value.slice(n,o),u=e.value.slice(o),l=[];return i&&l.push({id:m.genId(),type:"text",value:i,marks:{...e.marks}}),l.push({id:m.genId(),type:"text",value:d,marks:a}),u&&l.push({id:m.genId(),type:"text",value:u,marks:{...e.marks}}),m.replaceRunSpan(t,[e.id],l)}function w(t,{blockId:e,startRunId:n,startOffset:o,endRunId:c,endOffset:a},i){const d=K(t,e),u=d.indexOf(n),l=d.indexOf(c);if(u===-1||l===-1)return null;const[f,h]=u<=l?[u,l]:[l,u],r=u<=l?o:a,C=u<=l?a:o,R=d.slice(f,h+1),y=R.map(p=>t.getRun(p));if(f===h){const p=y[0];if(p.type!=="text")return null;const v=Math.max(0,Math.min(r,C)),g=Math.min(p.value.length,Math.max(r,C));return v===g?null:F(e,p,v,g,i)}const x=[];return y.forEach((p,v)=>{if(p.type!=="text"){x.push(p);return}const g=v===0,B=v===y.length-1,j=g?r:0,k=B?C:p.value.length,N=p.value.slice(0,j),H=p.value.slice(j,k),M=p.value.slice(k);N&&x.push({id:m.genId(),type:"text",value:N,marks:{...p.marks}}),x.push({id:m.genId(),type:"text",value:H,marks:O(p.marks,i)}),M&&x.push({id:m.genId(),type:"text",value:M,marks:{...p.marks}})}),m.replaceRunSpan(e,R,x)}function A(t,e){var o,c;const n=[];for(const a of t.getAllRunIds()){const i=t.getRun(a);(c=(o=i==null?void 0:i.marks)==null?void 0:o.commentIds)!=null&&c.includes(e)&&n.push({type:"updateRun",id:a,patch:{marks:$(i.marks,e)}})}return n}function T(t,e){if(typeof t.performBatch=="function")t.performBatch(e);else for(const n of e)t.applyOperation(n)}function L(t,e,{authorId:n,text:o}){const c=m.genId(),a=w(t,e,c),i={id:c,blockId:e.blockId,anchorRunIds:[e.startRunId,e.endRunId],resolved:!1,messages:[{id:m.genId(),authorId:n,text:o,createdAt:Date.now()}]},d=a?[a,m.addCommentThread(i)]:[m.addCommentThread(i)];return T(t,d),c}function z(t,e,{authorId:n,text:o}){const c={id:m.genId(),authorId:n,text:o,createdAt:Date.now()};return t.applyOperation(m.addCommentReply(e,c)),c.id}function E(t,e,n=!0){t.applyOperation(m.resolveComment(e,n))}function D(t,e){const n=A(t,e);T(t,[...n,m.removeCommentThread(e)])}function q({authorId:t,placeholder:e="Add a comment…",autoFocus:n=!1,onSubmit:o,onCancel:c}){const[a,i]=b.useState(""),d=b.useRef(null),u=a.trim();function l(){const r=d.current;r&&(r.style.height="auto",r.style.height=`${r.scrollHeight}px`)}function f(r){i(r.target.value),l()}function h(r){r.preventDefault(),u&&(o(u),i(""),d.current&&(d.current.style.height=""))}return s.jsxs("form",{className:"be-comment-composer",onSubmit:h,children:[s.jsxs("div",{className:"be-comment-composer-input-row",children:[s.jsx(I.CommentAvatar,{authorId:t,size:26}),s.jsx("textarea",{ref:d,className:"be-comment-composer-textarea",value:a,onChange:f,placeholder:e,rows:2,autoFocus:n,onKeyDown:r=>{r.key==="Enter"&&(r.metaKey||r.ctrlKey)?h(r):r.key==="Escape"&&c&&c()}})]}),s.jsxs("div",{className:"be-comment-composer-actions",children:[c&&s.jsx("button",{type:"button",className:"be-comment-composer-cancel",onClick:c,children:"Cancel"}),s.jsxs("button",{type:"submit",className:"be-comment-composer-submit",disabled:!u,children:[s.jsx(m.SendIcon,{size:13}),"Send"]})]})]})}function S({message:t,size:e=26}){return s.jsxs("div",{className:"be-comment-message",children:[s.jsx(I.CommentAvatar,{authorId:t.authorId,size:e}),s.jsxs("div",{className:"be-comment-message-body",children:[s.jsxs("div",{className:"be-comment-message-header",children:[s.jsx("span",{className:"be-comment-message-author",children:t.authorId}),s.jsx("span",{className:"be-comment-message-time",children:I.formatRelativeTime(t.createdAt)})]}),s.jsx("p",{className:"be-comment-message-text",children:t.text})]})]})}function G({store:t,thread:e,authorId:n,onJumpToText:o}){const[c,a]=b.useState(!1),[i,d]=m.useHoveredComment(),[u,...l]=e.messages;function f(h){z(t,e.id,{authorId:n,text:h}),a(!1)}return s.jsxs("div",{className:`be-comment-thread${e.resolved?" be-comment-thread-resolved":""}${i===e.id?" be-comment-thread-hovered":""}`,onMouseEnter:()=>d(e.id),onMouseLeave:()=>d(null),children:[s.jsx("div",{onClick:o?()=>o(e.id):void 0,style:o?{cursor:"pointer"}:void 0,children:s.jsx(S,{message:u})}),l.length>0&&s.jsx("div",{className:"be-comment-thread-replies",children:l.map(h=>s.jsx(S,{message:h,size:22},h.id))}),c?s.jsx(q,{authorId:n,placeholder:"Reply…",autoFocus:!0,onSubmit:f,onCancel:()=>a(!1)}):s.jsxs("div",{className:"be-comment-thread-actions",children:[n&&s.jsxs("button",{type:"button",onClick:()=>a(!0),children:[s.jsx(m.CommentIcon,{size:13}),"Reply"]}),s.jsxs("button",{type:"button",onClick:()=>E(t,e.id,!e.resolved),children:[s.jsx(m.CheckIcon,{size:13}),e.resolved?"Reopen":"Resolve"]}),s.jsx("button",{type:"button",className:"be-comment-thread-delete",onClick:()=>D(t,e.id),"aria-label":"Delete comment",title:"Delete comment",children:s.jsx(m.TrashIcon,{size:13})})]})]})}exports.CommentComposer=q;exports.CommentThreadCard=G;exports.addComment=L;exports.addCommentMarkOverRange=w;exports.deleteComment=D;exports.removeCommentMarkEverywhere=A;exports.replyToComment=z;exports.resolveComment=E;
2
+ //# sourceMappingURL=CommentThreadCard-CAJ3oPdr.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CommentThreadCard-CAJ3oPdr.cjs","sources":["../../src/comments/commentMarks.js","../../src/comments/comments.js","../../src/react/CommentComposer.jsx","../../src/react/CommentThreadCard.jsx"],"sourcesContent":["import { genId } from '../utils/idGen.js';\nimport { replaceRunSpan } from '../store/operations.js';\n\nfunction getBlockRunIds(store, blockId) {\n const block = store.getBlock(blockId);\n return block.props?.titleRunIds ?? block.contentIds;\n}\n\n/** Appends `commentId` to a run's `marks.commentIds`, idempotently -- never clobbers other marks or other comment ids already on the run. */\nfunction appendCommentMark(marks, commentId) {\n const existing = marks?.commentIds ?? [];\n if (existing.includes(commentId)) return marks ?? {};\n return { ...marks, commentIds: [...existing, commentId] };\n}\n\n/** Removes `commentId` from a run's `marks.commentIds`, dropping the key entirely once empty. */\nfunction removeCommentMark(marks, commentId) {\n const existing = marks?.commentIds ?? [];\n if (!existing.includes(commentId)) return marks ?? {};\n const next = { ...marks };\n const remaining = existing.filter((id) => id !== commentId);\n if (remaining.length > 0) next.commentIds = remaining;\n else delete next.commentIds;\n return next;\n}\n\n/** Same single-run split as markCommands.js's applyPatchToSingleRun, but appending a comment id instead of applying a flat marks patch -- see this module's own doc comment for why it isn't shared with markCommands.js. Returns the op to apply (never applies it itself -- see addCommentMarkOverRange). */\nfunction appendCommentToSingleRunOp(blockId, run, from, to, commentId) {\n const nextMarks = appendCommentMark(run.marks, commentId);\n\n if (from === 0 && to === run.value.length) {\n return { type: 'updateRun', id: run.id, patch: { marks: nextMarks } };\n }\n\n const before = run.value.slice(0, from);\n const middle = run.value.slice(from, to);\n const after = run.value.slice(to);\n\n const newRuns = [];\n if (before) newRuns.push({ id: genId(), type: 'text', value: before, marks: { ...run.marks } });\n newRuns.push({ id: genId(), type: 'text', value: middle, marks: nextMarks });\n if (after) newRuns.push({ id: genId(), type: 'text', value: after, marks: { ...run.marks } });\n\n return replaceRunSpan(blockId, [run.id], newRuns);\n}\n\n/**\n * Computes the single operation that marks every run (or run slice) within\n * `{ blockId, startRunId, startOffset, endRunId, endOffset }` as belonging\n * to `commentId`, by appending it to each affected run's `marks.commentIds`\n * array -- but does NOT apply it (returns the op, or `null` for a\n * collapsed/unresolvable range), so callers (see src/comments/comments.js's\n * addComment) can batch it with the comment-thread-creation op as one\n * atomic undo step via `store.performBatch`.\n *\n * Mirrors markCommands.js's applyMarksPatchOverRunSpan's run-resolution/\n * splitting logic, but is a deliberately separate, new function rather\n * than a change to that shared, well-tested mark-toggle path -- marks\n * today are a flat `next[markName] = value` replace (see markCommands.js's\n * own applyMarksPatch), and comments need *append* semantics since\n * multiple comments can overlap the same text.\n *\n * The resulting op is an ordinary `replaceRunSpan`/`updateRun` -- the same\n * ops every other formatting command already uses, with the same\n * pre-existing local-only-in-collaboration scope (see EditorStore's\n * REPLACE_RUN_SPAN case) rather than a new op type.\n */\nexport function addCommentMarkOverRange(\n store,\n { blockId, startRunId, startOffset, endRunId, endOffset },\n commentId,\n) {\n const runIds = getBlockRunIds(store, blockId);\n const startIndex = runIds.indexOf(startRunId);\n const endIndex = runIds.indexOf(endRunId);\n if (startIndex === -1 || endIndex === -1) return null;\n\n const [fromIndex, toIndex] =\n startIndex <= endIndex ? [startIndex, endIndex] : [endIndex, startIndex];\n const fromOffset = startIndex <= endIndex ? startOffset : endOffset;\n const toOffset = startIndex <= endIndex ? endOffset : startOffset;\n\n const rangeRunIds = runIds.slice(fromIndex, toIndex + 1);\n const rangeRuns = rangeRunIds.map((id) => store.getRun(id));\n\n if (fromIndex === toIndex) {\n const run = rangeRuns[0];\n if (run.type !== 'text') return null;\n const from = Math.max(0, Math.min(fromOffset, toOffset));\n const to = Math.min(run.value.length, Math.max(fromOffset, toOffset));\n if (from === to) return null;\n return appendCommentToSingleRunOp(blockId, run, from, to, commentId);\n }\n\n const newRuns = [];\n rangeRuns.forEach((run, i) => {\n if (run.type !== 'text') {\n newRuns.push(run); // atomic: pass through untouched\n return;\n }\n\n const isFirst = i === 0;\n const isLast = i === rangeRuns.length - 1;\n const sliceStart = isFirst ? fromOffset : 0;\n const sliceEnd = isLast ? toOffset : run.value.length;\n\n const before = run.value.slice(0, sliceStart);\n const middle = run.value.slice(sliceStart, sliceEnd);\n const after = run.value.slice(sliceEnd);\n\n if (before) newRuns.push({ id: genId(), type: 'text', value: before, marks: { ...run.marks } });\n newRuns.push({\n id: genId(),\n type: 'text',\n value: middle,\n marks: appendCommentMark(run.marks, commentId),\n });\n if (after) newRuns.push({ id: genId(), type: 'text', value: after, marks: { ...run.marks } });\n });\n\n return replaceRunSpan(blockId, rangeRunIds, newRuns);\n}\n\n/**\n * Computes the ops that strip `commentId` from every run in the whole\n * document that carries it (via store.getAllRunIds(), not just the range\n * it was originally added over -- a later formatting edit may have split/\n * re-minted run ids since, see the package README's documented limitation\n * on `anchorRunIds`) -- does NOT apply them (returns the op list), for the\n * same batching reason as addCommentMarkOverRange (see src/comments/\n * comments.js's deleteComment). Each op is a plain `updateRun({ marks })`\n * -- `marks` is a whole-field LWW key (only a text run's lone `value` key\n * is routed through the character CRDT), so this is the same primitive\n * every other mark removal already uses.\n */\nexport function removeCommentMarkEverywhere(store, commentId) {\n const ops = [];\n for (const runId of store.getAllRunIds()) {\n const run = store.getRun(runId);\n if (!run?.marks?.commentIds?.includes(commentId)) continue;\n ops.push({\n type: 'updateRun',\n id: runId,\n patch: { marks: removeCommentMark(run.marks, commentId) },\n });\n }\n return ops;\n}\n","import { genId } from '../utils/idGen.js';\nimport {\n addCommentThread as addCommentThreadOp,\n removeCommentThread as removeCommentThreadOp,\n addCommentReply as addCommentReplyOp,\n resolveComment as resolveCommentOp,\n} from '../store/operations.js';\nimport { addCommentMarkOverRange, removeCommentMarkEverywhere } from './commentMarks.js';\n\nfunction applyOps(store, ops) {\n if (typeof store.performBatch === 'function') store.performBatch(ops);\n else for (const op of ops) store.applyOperation(op);\n}\n\n/**\n * Creates a comment thread anchored to `range` ({ blockId, startRunId,\n * startOffset, endRunId, endOffset }, the same shape markCommands.js's\n * setMarksOverSelection takes) — highlights the range (appending this\n * comment's id to each covered run's `marks.commentIds`, see\n * commentMarks.js) and creates the thread's metadata as one atomic undo\n * step. `authorId` is always caller-supplied — this package has no\n * identity concept of its own (same convention as `History.perform(op, {\n * actorId })`).\n *\n * The highlighted range is local-only in collaboration for v1 (inherits\n * the pre-existing scope of every `replaceRunSpan`-based formatting\n * operation — see EditorStore's REPLACE_RUN_SPAN case); the thread's own\n * metadata (text/author/replies/resolved) IS collaboration-aware and\n * broadcasts live to connected peers.\n *\n * Returns the new comment's id.\n */\nexport function addComment(store, range, { authorId, text }) {\n const commentId = genId();\n const markOp = addCommentMarkOverRange(store, range, commentId);\n const thread = {\n id: commentId,\n blockId: range.blockId,\n anchorRunIds: [range.startRunId, range.endRunId],\n resolved: false,\n messages: [{ id: genId(), authorId, text, createdAt: Date.now() }],\n };\n const ops = markOp ? [markOp, addCommentThreadOp(thread)] : [addCommentThreadOp(thread)];\n applyOps(store, ops);\n return commentId;\n}\n\n/** Appends a reply to an existing thread — one undo step. */\nexport function replyToComment(store, commentId, { authorId, text }) {\n const message = { id: genId(), authorId, text, createdAt: Date.now() };\n store.applyOperation(addCommentReplyOp(commentId, message));\n return message.id;\n}\n\n/** Flips a thread's resolved flag (`resolved` defaults to `true`) — one undo step. */\nexport function resolveComment(store, commentId, resolved = true) {\n store.applyOperation(resolveCommentOp(commentId, resolved));\n}\n\n/** Removes a thread's metadata AND strips its highlight from every run that still carries it — one atomic undo step. */\nexport function deleteComment(store, commentId) {\n const markRemovalOps = removeCommentMarkEverywhere(store, commentId);\n applyOps(store, [...markRemovalOps, removeCommentThreadOp(commentId)]);\n}\n","import { useRef, useState } from 'react';\nimport { CommentAvatar } from './CommentAvatar.jsx';\nimport { SendIcon } from './icons.jsx';\n\n/**\n * Avatar + auto-growing textarea, with a Send (and optional Cancel) button\n * on their own row below — the one shared \"compose a comment message\" UI\n * used for both a brand-new comment (FloatingToolbar's Comment button, once\n * an authorId is configured) and a reply (CommentThreadCard). Deliberately\n * not a modal: it's meant to sit inline inside a small popover/panel,\n * matching the \"minimal UI\" goal rather than interrupting with a full\n * dialog the way LinkEditModal does for a fundamentally different, rarer\n * action (editing a URL).\n *\n * The textarea starts at 2 rows and grows with content up to 5 (see\n * `.be-comment-composer-textarea`'s min/max-height plus the resize-on-input\n * below), then scrolls — comfortable for more than a one-line reply without\n * the composer itself growing unbounded.\n *\n * `authorId` only renders the composer's own avatar (a preview of who's\n * about to post) — it does NOT decide who the message is attributed to;\n * the caller still passes `authorId` to `addComment`/`replyToComment`\n * itself. Ctrl/Cmd+Enter submits, Escape cancels (if `onCancel` is given)\n * — a plain Enter stays newline-friendly now that there's room for more\n * than one line.\n */\nexport function CommentComposer({\n authorId,\n placeholder = 'Add a comment…',\n autoFocus = false,\n onSubmit,\n onCancel,\n}) {\n const [text, setText] = useState('');\n const textareaRef = useRef(null);\n const trimmed = text.trim();\n\n function resizeToContent() {\n const el = textareaRef.current;\n if (!el) return;\n el.style.height = 'auto';\n el.style.height = `${el.scrollHeight}px`;\n }\n\n function handleChange(event) {\n setText(event.target.value);\n resizeToContent();\n }\n\n function handleSubmit(event) {\n event.preventDefault();\n if (!trimmed) return;\n onSubmit(trimmed);\n setText('');\n if (textareaRef.current) textareaRef.current.style.height = '';\n }\n\n return (\n <form className=\"be-comment-composer\" onSubmit={handleSubmit}>\n <div className=\"be-comment-composer-input-row\">\n <CommentAvatar authorId={authorId} size={26} />\n <textarea\n ref={textareaRef}\n className=\"be-comment-composer-textarea\"\n value={text}\n onChange={handleChange}\n placeholder={placeholder}\n rows={2}\n autoFocus={autoFocus}\n onKeyDown={(event) => {\n if (event.key === 'Enter' && (event.metaKey || event.ctrlKey)) handleSubmit(event);\n else if (event.key === 'Escape' && onCancel) onCancel();\n }}\n />\n </div>\n <div className=\"be-comment-composer-actions\">\n {onCancel && (\n <button type=\"button\" className=\"be-comment-composer-cancel\" onClick={onCancel}>\n Cancel\n </button>\n )}\n <button type=\"submit\" className=\"be-comment-composer-submit\" disabled={!trimmed}>\n <SendIcon size={13} />\n Send\n </button>\n </div>\n </form>\n );\n}\n","import { useState } from 'react';\nimport { replyToComment, resolveComment, deleteComment } from '../comments/comments.js';\nimport { CommentComposer } from './CommentComposer.jsx';\nimport { CommentAvatar } from './CommentAvatar.jsx';\nimport { formatRelativeTime } from './commentFormatting.js';\nimport { CommentIcon, CheckIcon, TrashIcon } from './icons.jsx';\nimport { useHoveredComment } from './EditorProvider.jsx';\n\nfunction CommentMessageRow({ message, size = 26 }) {\n return (\n <div className=\"be-comment-message\">\n <CommentAvatar authorId={message.authorId} size={size} />\n <div className=\"be-comment-message-body\">\n <div className=\"be-comment-message-header\">\n <span className=\"be-comment-message-author\">{message.authorId}</span>\n <span className=\"be-comment-message-time\">{formatRelativeTime(message.createdAt)}</span>\n </div>\n <p className=\"be-comment-message-text\">{message.text}</p>\n </div>\n </div>\n );\n}\n\n/**\n * One comment thread — the original message, then any replies (visually\n * indented with a connecting line, chat-thread style), then either a Reply\n * composer (once clicked) or the Reply/Resolve/Delete action row. Shared by\n * both places a thread can be viewed: CommentPopover (click/hover on the\n * highlighted text) and CommentsPanel (the opt-in right-side sidebar) —\n * one implementation, so the two surfaces can't drift apart.\n *\n * Resolve/Delete act on the whole thread (this package's data model has no\n * per-reply resolve/delete — see CommentThread in the README), so the\n * action row sits once below every message, not per message.\n *\n * `authorId`, if not given, hides the Reply action entirely (composing a\n * message needs an author; resolving/deleting don't, so those stay\n * available either way) — see NoteloomEditor's `commentAuthorId` prop.\n *\n * Hovering the card echoes the highlight onto the actual text in the\n * editor (see useHoveredComment); `onJumpToText`, if given (CommentsPanel\n * passes it, CommentPopover doesn't need to -- you're already looking at\n * the text there), is called when the messages themselves are clicked, for\n * scrolling to and briefly re-highlighting the anchored text.\n */\nexport function CommentThreadCard({ store, thread, authorId, onJumpToText }) {\n const [isReplying, setIsReplying] = useState(false);\n const [hoveredCommentId, setHoveredCommentId] = useHoveredComment();\n const [rootMessage, ...replies] = thread.messages;\n\n function handleReplySubmit(text) {\n replyToComment(store, thread.id, { authorId, text });\n setIsReplying(false);\n }\n\n return (\n <div\n className={`be-comment-thread${thread.resolved ? ' be-comment-thread-resolved' : ''}${hoveredCommentId === thread.id ? ' be-comment-thread-hovered' : ''}`}\n onMouseEnter={() => setHoveredCommentId(thread.id)}\n onMouseLeave={() => setHoveredCommentId(null)}\n >\n {/* eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions */}\n <div\n onClick={onJumpToText ? () => onJumpToText(thread.id) : undefined}\n style={onJumpToText ? { cursor: 'pointer' } : undefined}\n >\n <CommentMessageRow message={rootMessage} />\n </div>\n\n {replies.length > 0 && (\n <div className=\"be-comment-thread-replies\">\n {replies.map((m) => (\n <CommentMessageRow key={m.id} message={m} size={22} />\n ))}\n </div>\n )}\n\n {isReplying ? (\n <CommentComposer\n authorId={authorId}\n placeholder=\"Reply…\"\n autoFocus\n onSubmit={handleReplySubmit}\n onCancel={() => setIsReplying(false)}\n />\n ) : (\n <div className=\"be-comment-thread-actions\">\n {/* No literal space between the icon and label -- CSS gap (see\n .be-comment-thread-actions button) handles the visual spacing,\n so button.textContent stays exactly \"Reply\"/\"Resolve\"/\"Reopen\". */}\n {authorId && (\n <button type=\"button\" onClick={() => setIsReplying(true)}>\n <CommentIcon size={13} />\n Reply\n </button>\n )}\n <button type=\"button\" onClick={() => resolveComment(store, thread.id, !thread.resolved)}>\n <CheckIcon size={13} />\n {thread.resolved ? 'Reopen' : 'Resolve'}\n </button>\n <button\n type=\"button\"\n className=\"be-comment-thread-delete\"\n onClick={() => deleteComment(store, thread.id)}\n aria-label=\"Delete comment\"\n title=\"Delete comment\"\n >\n <TrashIcon size={13} />\n </button>\n </div>\n )}\n </div>\n );\n}\n"],"names":["getBlockRunIds","store","blockId","block","_a","appendCommentMark","marks","commentId","existing","removeCommentMark","next","remaining","id","appendCommentToSingleRunOp","run","from","to","nextMarks","before","middle","after","newRuns","genId","replaceRunSpan","addCommentMarkOverRange","startRunId","startOffset","endRunId","endOffset","runIds","startIndex","endIndex","fromIndex","toIndex","fromOffset","toOffset","rangeRunIds","rangeRuns","i","isFirst","isLast","sliceStart","sliceEnd","removeCommentMarkEverywhere","ops","runId","_b","applyOps","op","addComment","range","authorId","text","markOp","thread","addCommentThreadOp","replyToComment","message","addCommentReplyOp","resolveComment","resolved","resolveCommentOp","deleteComment","markRemovalOps","removeCommentThreadOp","CommentComposer","placeholder","autoFocus","onSubmit","onCancel","setText","useState","textareaRef","useRef","trimmed","resizeToContent","el","handleChange","event","handleSubmit","jsxs","jsx","CommentAvatar","SendIcon","CommentMessageRow","size","formatRelativeTime","CommentThreadCard","onJumpToText","isReplying","setIsReplying","hoveredCommentId","setHoveredCommentId","useHoveredComment","rootMessage","replies","handleReplySubmit","m","CommentIcon","CheckIcon","TrashIcon"],"mappings":"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"}
@@ -0,0 +1,213 @@
1
+ import { jsxs as p, jsx as m } from "react/jsx-runtime";
2
+ import { useState as w, useRef as $ } from "react";
3
+ import { g as h, r as z, c as S, d as H, e as K, f as j, S as F, b as L, C as q, h as G, T as P } from "./icons-C1_s0jKK.js";
4
+ import { C as A, f as Q } from "./CommentAvatar-TKqsM8vG.js";
5
+ function U(t, e) {
6
+ var s;
7
+ const n = t.getBlock(e);
8
+ return ((s = n.props) == null ? void 0 : s.titleRunIds) ?? n.contentIds;
9
+ }
10
+ function D(t, e) {
11
+ const n = (t == null ? void 0 : t.commentIds) ?? [];
12
+ return n.includes(e) ? t ?? {} : { ...t, commentIds: [...n, e] };
13
+ }
14
+ function V(t, e) {
15
+ const n = (t == null ? void 0 : t.commentIds) ?? [];
16
+ if (!n.includes(e)) return t ?? {};
17
+ const s = { ...t }, o = n.filter((a) => a !== e);
18
+ return o.length > 0 ? s.commentIds = o : delete s.commentIds, s;
19
+ }
20
+ function W(t, e, n, s, o) {
21
+ const a = D(e.marks, o);
22
+ if (n === 0 && s === e.value.length)
23
+ return { type: "updateRun", id: e.id, patch: { marks: a } };
24
+ const c = e.value.slice(0, n), l = e.value.slice(n, s), d = e.value.slice(s), r = [];
25
+ return c && r.push({ id: h(), type: "text", value: c, marks: { ...e.marks } }), r.push({ id: h(), type: "text", value: l, marks: a }), d && r.push({ id: h(), type: "text", value: d, marks: { ...e.marks } }), z(t, [e.id], r);
26
+ }
27
+ function X(t, { blockId: e, startRunId: n, startOffset: s, endRunId: o, endOffset: a }, c) {
28
+ const l = U(t, e), d = l.indexOf(n), r = l.indexOf(o);
29
+ if (d === -1 || r === -1) return null;
30
+ const [v, f] = d <= r ? [d, r] : [r, d], i = d <= r ? s : a, C = d <= r ? a : s, x = l.slice(v, f + 1), R = x.map((u) => t.getRun(u));
31
+ if (v === f) {
32
+ const u = R[0];
33
+ if (u.type !== "text") return null;
34
+ const y = Math.max(0, Math.min(i, C)), g = Math.min(u.value.length, Math.max(i, C));
35
+ return y === g ? null : W(e, u, y, g, c);
36
+ }
37
+ const b = [];
38
+ return R.forEach((u, y) => {
39
+ if (u.type !== "text") {
40
+ b.push(u);
41
+ return;
42
+ }
43
+ const g = y === 0, T = y === R.length - 1, I = g ? i : 0, k = T ? C : u.value.length, N = u.value.slice(0, I), B = u.value.slice(I, k), M = u.value.slice(k);
44
+ N && b.push({ id: h(), type: "text", value: N, marks: { ...u.marks } }), b.push({
45
+ id: h(),
46
+ type: "text",
47
+ value: B,
48
+ marks: D(u.marks, c)
49
+ }), M && b.push({ id: h(), type: "text", value: M, marks: { ...u.marks } });
50
+ }), z(e, x, b);
51
+ }
52
+ function Y(t, e) {
53
+ var s, o;
54
+ const n = [];
55
+ for (const a of t.getAllRunIds()) {
56
+ const c = t.getRun(a);
57
+ (o = (s = c == null ? void 0 : c.marks) == null ? void 0 : s.commentIds) != null && o.includes(e) && n.push({
58
+ type: "updateRun",
59
+ id: a,
60
+ patch: { marks: V(c.marks, e) }
61
+ });
62
+ }
63
+ return n;
64
+ }
65
+ function E(t, e) {
66
+ if (typeof t.performBatch == "function") t.performBatch(e);
67
+ else for (const n of e) t.applyOperation(n);
68
+ }
69
+ function ae(t, e, { authorId: n, text: s }) {
70
+ const o = h(), a = X(t, e, o), c = {
71
+ id: o,
72
+ blockId: e.blockId,
73
+ anchorRunIds: [e.startRunId, e.endRunId],
74
+ resolved: !1,
75
+ messages: [{ id: h(), authorId: n, text: s, createdAt: Date.now() }]
76
+ }, l = a ? [a, S(c)] : [S(c)];
77
+ return E(t, l), o;
78
+ }
79
+ function Z(t, e, { authorId: n, text: s }) {
80
+ const o = { id: h(), authorId: n, text: s, createdAt: Date.now() };
81
+ return t.applyOperation(K(e, o)), o.id;
82
+ }
83
+ function _(t, e, n = !0) {
84
+ t.applyOperation(j(e, n));
85
+ }
86
+ function J(t, e) {
87
+ const n = Y(t, e);
88
+ E(t, [...n, H(e)]);
89
+ }
90
+ function ee({
91
+ authorId: t,
92
+ placeholder: e = "Add a comment…",
93
+ autoFocus: n = !1,
94
+ onSubmit: s,
95
+ onCancel: o
96
+ }) {
97
+ const [a, c] = w(""), l = $(null), d = a.trim();
98
+ function r() {
99
+ const i = l.current;
100
+ i && (i.style.height = "auto", i.style.height = `${i.scrollHeight}px`);
101
+ }
102
+ function v(i) {
103
+ c(i.target.value), r();
104
+ }
105
+ function f(i) {
106
+ i.preventDefault(), d && (s(d), c(""), l.current && (l.current.style.height = ""));
107
+ }
108
+ return /* @__PURE__ */ p("form", { className: "be-comment-composer", onSubmit: f, children: [
109
+ /* @__PURE__ */ p("div", { className: "be-comment-composer-input-row", children: [
110
+ /* @__PURE__ */ m(A, { authorId: t, size: 26 }),
111
+ /* @__PURE__ */ m(
112
+ "textarea",
113
+ {
114
+ ref: l,
115
+ className: "be-comment-composer-textarea",
116
+ value: a,
117
+ onChange: v,
118
+ placeholder: e,
119
+ rows: 2,
120
+ autoFocus: n,
121
+ onKeyDown: (i) => {
122
+ i.key === "Enter" && (i.metaKey || i.ctrlKey) ? f(i) : i.key === "Escape" && o && o();
123
+ }
124
+ }
125
+ )
126
+ ] }),
127
+ /* @__PURE__ */ p("div", { className: "be-comment-composer-actions", children: [
128
+ o && /* @__PURE__ */ m("button", { type: "button", className: "be-comment-composer-cancel", onClick: o, children: "Cancel" }),
129
+ /* @__PURE__ */ p("button", { type: "submit", className: "be-comment-composer-submit", disabled: !d, children: [
130
+ /* @__PURE__ */ m(F, { size: 13 }),
131
+ "Send"
132
+ ] })
133
+ ] })
134
+ ] });
135
+ }
136
+ function O({ message: t, size: e = 26 }) {
137
+ return /* @__PURE__ */ p("div", { className: "be-comment-message", children: [
138
+ /* @__PURE__ */ m(A, { authorId: t.authorId, size: e }),
139
+ /* @__PURE__ */ p("div", { className: "be-comment-message-body", children: [
140
+ /* @__PURE__ */ p("div", { className: "be-comment-message-header", children: [
141
+ /* @__PURE__ */ m("span", { className: "be-comment-message-author", children: t.authorId }),
142
+ /* @__PURE__ */ m("span", { className: "be-comment-message-time", children: Q(t.createdAt) })
143
+ ] }),
144
+ /* @__PURE__ */ m("p", { className: "be-comment-message-text", children: t.text })
145
+ ] })
146
+ ] });
147
+ }
148
+ function ce({ store: t, thread: e, authorId: n, onJumpToText: s }) {
149
+ const [o, a] = w(!1), [c, l] = L(), [d, ...r] = e.messages;
150
+ function v(f) {
151
+ Z(t, e.id, { authorId: n, text: f }), a(!1);
152
+ }
153
+ return /* @__PURE__ */ p(
154
+ "div",
155
+ {
156
+ className: `be-comment-thread${e.resolved ? " be-comment-thread-resolved" : ""}${c === e.id ? " be-comment-thread-hovered" : ""}`,
157
+ onMouseEnter: () => l(e.id),
158
+ onMouseLeave: () => l(null),
159
+ children: [
160
+ /* @__PURE__ */ m(
161
+ "div",
162
+ {
163
+ onClick: s ? () => s(e.id) : void 0,
164
+ style: s ? { cursor: "pointer" } : void 0,
165
+ children: /* @__PURE__ */ m(O, { message: d })
166
+ }
167
+ ),
168
+ r.length > 0 && /* @__PURE__ */ m("div", { className: "be-comment-thread-replies", children: r.map((f) => /* @__PURE__ */ m(O, { message: f, size: 22 }, f.id)) }),
169
+ o ? /* @__PURE__ */ m(
170
+ ee,
171
+ {
172
+ authorId: n,
173
+ placeholder: "Reply…",
174
+ autoFocus: !0,
175
+ onSubmit: v,
176
+ onCancel: () => a(!1)
177
+ }
178
+ ) : /* @__PURE__ */ p("div", { className: "be-comment-thread-actions", children: [
179
+ n && /* @__PURE__ */ p("button", { type: "button", onClick: () => a(!0), children: [
180
+ /* @__PURE__ */ m(q, { size: 13 }),
181
+ "Reply"
182
+ ] }),
183
+ /* @__PURE__ */ p("button", { type: "button", onClick: () => _(t, e.id, !e.resolved), children: [
184
+ /* @__PURE__ */ m(G, { size: 13 }),
185
+ e.resolved ? "Reopen" : "Resolve"
186
+ ] }),
187
+ /* @__PURE__ */ m(
188
+ "button",
189
+ {
190
+ type: "button",
191
+ className: "be-comment-thread-delete",
192
+ onClick: () => J(t, e.id),
193
+ "aria-label": "Delete comment",
194
+ title: "Delete comment",
195
+ children: /* @__PURE__ */ m(P, { size: 13 })
196
+ }
197
+ )
198
+ ] })
199
+ ]
200
+ }
201
+ );
202
+ }
203
+ export {
204
+ ee as C,
205
+ ce as a,
206
+ ae as b,
207
+ X as c,
208
+ J as d,
209
+ Z as e,
210
+ _ as f,
211
+ Y as r
212
+ };
213
+ //# sourceMappingURL=CommentThreadCard-Cxf8GwHJ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CommentThreadCard-Cxf8GwHJ.js","sources":["../../src/comments/commentMarks.js","../../src/comments/comments.js","../../src/react/CommentComposer.jsx","../../src/react/CommentThreadCard.jsx"],"sourcesContent":["import { genId } from '../utils/idGen.js';\nimport { replaceRunSpan } from '../store/operations.js';\n\nfunction getBlockRunIds(store, blockId) {\n const block = store.getBlock(blockId);\n return block.props?.titleRunIds ?? block.contentIds;\n}\n\n/** Appends `commentId` to a run's `marks.commentIds`, idempotently -- never clobbers other marks or other comment ids already on the run. */\nfunction appendCommentMark(marks, commentId) {\n const existing = marks?.commentIds ?? [];\n if (existing.includes(commentId)) return marks ?? {};\n return { ...marks, commentIds: [...existing, commentId] };\n}\n\n/** Removes `commentId` from a run's `marks.commentIds`, dropping the key entirely once empty. */\nfunction removeCommentMark(marks, commentId) {\n const existing = marks?.commentIds ?? [];\n if (!existing.includes(commentId)) return marks ?? {};\n const next = { ...marks };\n const remaining = existing.filter((id) => id !== commentId);\n if (remaining.length > 0) next.commentIds = remaining;\n else delete next.commentIds;\n return next;\n}\n\n/** Same single-run split as markCommands.js's applyPatchToSingleRun, but appending a comment id instead of applying a flat marks patch -- see this module's own doc comment for why it isn't shared with markCommands.js. Returns the op to apply (never applies it itself -- see addCommentMarkOverRange). */\nfunction appendCommentToSingleRunOp(blockId, run, from, to, commentId) {\n const nextMarks = appendCommentMark(run.marks, commentId);\n\n if (from === 0 && to === run.value.length) {\n return { type: 'updateRun', id: run.id, patch: { marks: nextMarks } };\n }\n\n const before = run.value.slice(0, from);\n const middle = run.value.slice(from, to);\n const after = run.value.slice(to);\n\n const newRuns = [];\n if (before) newRuns.push({ id: genId(), type: 'text', value: before, marks: { ...run.marks } });\n newRuns.push({ id: genId(), type: 'text', value: middle, marks: nextMarks });\n if (after) newRuns.push({ id: genId(), type: 'text', value: after, marks: { ...run.marks } });\n\n return replaceRunSpan(blockId, [run.id], newRuns);\n}\n\n/**\n * Computes the single operation that marks every run (or run slice) within\n * `{ blockId, startRunId, startOffset, endRunId, endOffset }` as belonging\n * to `commentId`, by appending it to each affected run's `marks.commentIds`\n * array -- but does NOT apply it (returns the op, or `null` for a\n * collapsed/unresolvable range), so callers (see src/comments/comments.js's\n * addComment) can batch it with the comment-thread-creation op as one\n * atomic undo step via `store.performBatch`.\n *\n * Mirrors markCommands.js's applyMarksPatchOverRunSpan's run-resolution/\n * splitting logic, but is a deliberately separate, new function rather\n * than a change to that shared, well-tested mark-toggle path -- marks\n * today are a flat `next[markName] = value` replace (see markCommands.js's\n * own applyMarksPatch), and comments need *append* semantics since\n * multiple comments can overlap the same text.\n *\n * The resulting op is an ordinary `replaceRunSpan`/`updateRun` -- the same\n * ops every other formatting command already uses, with the same\n * pre-existing local-only-in-collaboration scope (see EditorStore's\n * REPLACE_RUN_SPAN case) rather than a new op type.\n */\nexport function addCommentMarkOverRange(\n store,\n { blockId, startRunId, startOffset, endRunId, endOffset },\n commentId,\n) {\n const runIds = getBlockRunIds(store, blockId);\n const startIndex = runIds.indexOf(startRunId);\n const endIndex = runIds.indexOf(endRunId);\n if (startIndex === -1 || endIndex === -1) return null;\n\n const [fromIndex, toIndex] =\n startIndex <= endIndex ? [startIndex, endIndex] : [endIndex, startIndex];\n const fromOffset = startIndex <= endIndex ? startOffset : endOffset;\n const toOffset = startIndex <= endIndex ? endOffset : startOffset;\n\n const rangeRunIds = runIds.slice(fromIndex, toIndex + 1);\n const rangeRuns = rangeRunIds.map((id) => store.getRun(id));\n\n if (fromIndex === toIndex) {\n const run = rangeRuns[0];\n if (run.type !== 'text') return null;\n const from = Math.max(0, Math.min(fromOffset, toOffset));\n const to = Math.min(run.value.length, Math.max(fromOffset, toOffset));\n if (from === to) return null;\n return appendCommentToSingleRunOp(blockId, run, from, to, commentId);\n }\n\n const newRuns = [];\n rangeRuns.forEach((run, i) => {\n if (run.type !== 'text') {\n newRuns.push(run); // atomic: pass through untouched\n return;\n }\n\n const isFirst = i === 0;\n const isLast = i === rangeRuns.length - 1;\n const sliceStart = isFirst ? fromOffset : 0;\n const sliceEnd = isLast ? toOffset : run.value.length;\n\n const before = run.value.slice(0, sliceStart);\n const middle = run.value.slice(sliceStart, sliceEnd);\n const after = run.value.slice(sliceEnd);\n\n if (before) newRuns.push({ id: genId(), type: 'text', value: before, marks: { ...run.marks } });\n newRuns.push({\n id: genId(),\n type: 'text',\n value: middle,\n marks: appendCommentMark(run.marks, commentId),\n });\n if (after) newRuns.push({ id: genId(), type: 'text', value: after, marks: { ...run.marks } });\n });\n\n return replaceRunSpan(blockId, rangeRunIds, newRuns);\n}\n\n/**\n * Computes the ops that strip `commentId` from every run in the whole\n * document that carries it (via store.getAllRunIds(), not just the range\n * it was originally added over -- a later formatting edit may have split/\n * re-minted run ids since, see the package README's documented limitation\n * on `anchorRunIds`) -- does NOT apply them (returns the op list), for the\n * same batching reason as addCommentMarkOverRange (see src/comments/\n * comments.js's deleteComment). Each op is a plain `updateRun({ marks })`\n * -- `marks` is a whole-field LWW key (only a text run's lone `value` key\n * is routed through the character CRDT), so this is the same primitive\n * every other mark removal already uses.\n */\nexport function removeCommentMarkEverywhere(store, commentId) {\n const ops = [];\n for (const runId of store.getAllRunIds()) {\n const run = store.getRun(runId);\n if (!run?.marks?.commentIds?.includes(commentId)) continue;\n ops.push({\n type: 'updateRun',\n id: runId,\n patch: { marks: removeCommentMark(run.marks, commentId) },\n });\n }\n return ops;\n}\n","import { genId } from '../utils/idGen.js';\nimport {\n addCommentThread as addCommentThreadOp,\n removeCommentThread as removeCommentThreadOp,\n addCommentReply as addCommentReplyOp,\n resolveComment as resolveCommentOp,\n} from '../store/operations.js';\nimport { addCommentMarkOverRange, removeCommentMarkEverywhere } from './commentMarks.js';\n\nfunction applyOps(store, ops) {\n if (typeof store.performBatch === 'function') store.performBatch(ops);\n else for (const op of ops) store.applyOperation(op);\n}\n\n/**\n * Creates a comment thread anchored to `range` ({ blockId, startRunId,\n * startOffset, endRunId, endOffset }, the same shape markCommands.js's\n * setMarksOverSelection takes) — highlights the range (appending this\n * comment's id to each covered run's `marks.commentIds`, see\n * commentMarks.js) and creates the thread's metadata as one atomic undo\n * step. `authorId` is always caller-supplied — this package has no\n * identity concept of its own (same convention as `History.perform(op, {\n * actorId })`).\n *\n * The highlighted range is local-only in collaboration for v1 (inherits\n * the pre-existing scope of every `replaceRunSpan`-based formatting\n * operation — see EditorStore's REPLACE_RUN_SPAN case); the thread's own\n * metadata (text/author/replies/resolved) IS collaboration-aware and\n * broadcasts live to connected peers.\n *\n * Returns the new comment's id.\n */\nexport function addComment(store, range, { authorId, text }) {\n const commentId = genId();\n const markOp = addCommentMarkOverRange(store, range, commentId);\n const thread = {\n id: commentId,\n blockId: range.blockId,\n anchorRunIds: [range.startRunId, range.endRunId],\n resolved: false,\n messages: [{ id: genId(), authorId, text, createdAt: Date.now() }],\n };\n const ops = markOp ? [markOp, addCommentThreadOp(thread)] : [addCommentThreadOp(thread)];\n applyOps(store, ops);\n return commentId;\n}\n\n/** Appends a reply to an existing thread — one undo step. */\nexport function replyToComment(store, commentId, { authorId, text }) {\n const message = { id: genId(), authorId, text, createdAt: Date.now() };\n store.applyOperation(addCommentReplyOp(commentId, message));\n return message.id;\n}\n\n/** Flips a thread's resolved flag (`resolved` defaults to `true`) — one undo step. */\nexport function resolveComment(store, commentId, resolved = true) {\n store.applyOperation(resolveCommentOp(commentId, resolved));\n}\n\n/** Removes a thread's metadata AND strips its highlight from every run that still carries it — one atomic undo step. */\nexport function deleteComment(store, commentId) {\n const markRemovalOps = removeCommentMarkEverywhere(store, commentId);\n applyOps(store, [...markRemovalOps, removeCommentThreadOp(commentId)]);\n}\n","import { useRef, useState } from 'react';\nimport { CommentAvatar } from './CommentAvatar.jsx';\nimport { SendIcon } from './icons.jsx';\n\n/**\n * Avatar + auto-growing textarea, with a Send (and optional Cancel) button\n * on their own row below — the one shared \"compose a comment message\" UI\n * used for both a brand-new comment (FloatingToolbar's Comment button, once\n * an authorId is configured) and a reply (CommentThreadCard). Deliberately\n * not a modal: it's meant to sit inline inside a small popover/panel,\n * matching the \"minimal UI\" goal rather than interrupting with a full\n * dialog the way LinkEditModal does for a fundamentally different, rarer\n * action (editing a URL).\n *\n * The textarea starts at 2 rows and grows with content up to 5 (see\n * `.be-comment-composer-textarea`'s min/max-height plus the resize-on-input\n * below), then scrolls — comfortable for more than a one-line reply without\n * the composer itself growing unbounded.\n *\n * `authorId` only renders the composer's own avatar (a preview of who's\n * about to post) — it does NOT decide who the message is attributed to;\n * the caller still passes `authorId` to `addComment`/`replyToComment`\n * itself. Ctrl/Cmd+Enter submits, Escape cancels (if `onCancel` is given)\n * — a plain Enter stays newline-friendly now that there's room for more\n * than one line.\n */\nexport function CommentComposer({\n authorId,\n placeholder = 'Add a comment…',\n autoFocus = false,\n onSubmit,\n onCancel,\n}) {\n const [text, setText] = useState('');\n const textareaRef = useRef(null);\n const trimmed = text.trim();\n\n function resizeToContent() {\n const el = textareaRef.current;\n if (!el) return;\n el.style.height = 'auto';\n el.style.height = `${el.scrollHeight}px`;\n }\n\n function handleChange(event) {\n setText(event.target.value);\n resizeToContent();\n }\n\n function handleSubmit(event) {\n event.preventDefault();\n if (!trimmed) return;\n onSubmit(trimmed);\n setText('');\n if (textareaRef.current) textareaRef.current.style.height = '';\n }\n\n return (\n <form className=\"be-comment-composer\" onSubmit={handleSubmit}>\n <div className=\"be-comment-composer-input-row\">\n <CommentAvatar authorId={authorId} size={26} />\n <textarea\n ref={textareaRef}\n className=\"be-comment-composer-textarea\"\n value={text}\n onChange={handleChange}\n placeholder={placeholder}\n rows={2}\n autoFocus={autoFocus}\n onKeyDown={(event) => {\n if (event.key === 'Enter' && (event.metaKey || event.ctrlKey)) handleSubmit(event);\n else if (event.key === 'Escape' && onCancel) onCancel();\n }}\n />\n </div>\n <div className=\"be-comment-composer-actions\">\n {onCancel && (\n <button type=\"button\" className=\"be-comment-composer-cancel\" onClick={onCancel}>\n Cancel\n </button>\n )}\n <button type=\"submit\" className=\"be-comment-composer-submit\" disabled={!trimmed}>\n <SendIcon size={13} />\n Send\n </button>\n </div>\n </form>\n );\n}\n","import { useState } from 'react';\nimport { replyToComment, resolveComment, deleteComment } from '../comments/comments.js';\nimport { CommentComposer } from './CommentComposer.jsx';\nimport { CommentAvatar } from './CommentAvatar.jsx';\nimport { formatRelativeTime } from './commentFormatting.js';\nimport { CommentIcon, CheckIcon, TrashIcon } from './icons.jsx';\nimport { useHoveredComment } from './EditorProvider.jsx';\n\nfunction CommentMessageRow({ message, size = 26 }) {\n return (\n <div className=\"be-comment-message\">\n <CommentAvatar authorId={message.authorId} size={size} />\n <div className=\"be-comment-message-body\">\n <div className=\"be-comment-message-header\">\n <span className=\"be-comment-message-author\">{message.authorId}</span>\n <span className=\"be-comment-message-time\">{formatRelativeTime(message.createdAt)}</span>\n </div>\n <p className=\"be-comment-message-text\">{message.text}</p>\n </div>\n </div>\n );\n}\n\n/**\n * One comment thread — the original message, then any replies (visually\n * indented with a connecting line, chat-thread style), then either a Reply\n * composer (once clicked) or the Reply/Resolve/Delete action row. Shared by\n * both places a thread can be viewed: CommentPopover (click/hover on the\n * highlighted text) and CommentsPanel (the opt-in right-side sidebar) —\n * one implementation, so the two surfaces can't drift apart.\n *\n * Resolve/Delete act on the whole thread (this package's data model has no\n * per-reply resolve/delete — see CommentThread in the README), so the\n * action row sits once below every message, not per message.\n *\n * `authorId`, if not given, hides the Reply action entirely (composing a\n * message needs an author; resolving/deleting don't, so those stay\n * available either way) — see NoteloomEditor's `commentAuthorId` prop.\n *\n * Hovering the card echoes the highlight onto the actual text in the\n * editor (see useHoveredComment); `onJumpToText`, if given (CommentsPanel\n * passes it, CommentPopover doesn't need to -- you're already looking at\n * the text there), is called when the messages themselves are clicked, for\n * scrolling to and briefly re-highlighting the anchored text.\n */\nexport function CommentThreadCard({ store, thread, authorId, onJumpToText }) {\n const [isReplying, setIsReplying] = useState(false);\n const [hoveredCommentId, setHoveredCommentId] = useHoveredComment();\n const [rootMessage, ...replies] = thread.messages;\n\n function handleReplySubmit(text) {\n replyToComment(store, thread.id, { authorId, text });\n setIsReplying(false);\n }\n\n return (\n <div\n className={`be-comment-thread${thread.resolved ? ' be-comment-thread-resolved' : ''}${hoveredCommentId === thread.id ? ' be-comment-thread-hovered' : ''}`}\n onMouseEnter={() => setHoveredCommentId(thread.id)}\n onMouseLeave={() => setHoveredCommentId(null)}\n >\n {/* eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions */}\n <div\n onClick={onJumpToText ? () => onJumpToText(thread.id) : undefined}\n style={onJumpToText ? { cursor: 'pointer' } : undefined}\n >\n <CommentMessageRow message={rootMessage} />\n </div>\n\n {replies.length > 0 && (\n <div className=\"be-comment-thread-replies\">\n {replies.map((m) => (\n <CommentMessageRow key={m.id} message={m} size={22} />\n ))}\n </div>\n )}\n\n {isReplying ? (\n <CommentComposer\n authorId={authorId}\n placeholder=\"Reply…\"\n autoFocus\n onSubmit={handleReplySubmit}\n onCancel={() => setIsReplying(false)}\n />\n ) : (\n <div className=\"be-comment-thread-actions\">\n {/* No literal space between the icon and label -- CSS gap (see\n .be-comment-thread-actions button) handles the visual spacing,\n so button.textContent stays exactly \"Reply\"/\"Resolve\"/\"Reopen\". */}\n {authorId && (\n <button type=\"button\" onClick={() => setIsReplying(true)}>\n <CommentIcon size={13} />\n Reply\n </button>\n )}\n <button type=\"button\" onClick={() => resolveComment(store, thread.id, !thread.resolved)}>\n <CheckIcon size={13} />\n {thread.resolved ? 'Reopen' : 'Resolve'}\n </button>\n <button\n type=\"button\"\n className=\"be-comment-thread-delete\"\n onClick={() => deleteComment(store, thread.id)}\n aria-label=\"Delete comment\"\n title=\"Delete comment\"\n >\n <TrashIcon size={13} />\n </button>\n </div>\n )}\n </div>\n );\n}\n"],"names":["getBlockRunIds","store","blockId","block","_a","appendCommentMark","marks","commentId","existing","removeCommentMark","next","remaining","id","appendCommentToSingleRunOp","run","from","to","nextMarks","before","middle","after","newRuns","genId","replaceRunSpan","addCommentMarkOverRange","startRunId","startOffset","endRunId","endOffset","runIds","startIndex","endIndex","fromIndex","toIndex","fromOffset","toOffset","rangeRunIds","rangeRuns","i","isFirst","isLast","sliceStart","sliceEnd","removeCommentMarkEverywhere","ops","runId","_b","applyOps","op","addComment","range","authorId","text","markOp","thread","addCommentThreadOp","replyToComment","message","addCommentReplyOp","resolveComment","resolved","resolveCommentOp","deleteComment","markRemovalOps","removeCommentThreadOp","CommentComposer","placeholder","autoFocus","onSubmit","onCancel","setText","useState","textareaRef","useRef","trimmed","resizeToContent","el","handleChange","event","handleSubmit","jsxs","jsx","CommentAvatar","SendIcon","CommentMessageRow","size","formatRelativeTime","CommentThreadCard","onJumpToText","isReplying","setIsReplying","hoveredCommentId","setHoveredCommentId","useHoveredComment","rootMessage","replies","handleReplySubmit","m","CommentIcon","CheckIcon","TrashIcon"],"mappings":"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"}
@@ -0,0 +1,76 @@
1
+ import { jsx as m } from "react/jsx-runtime";
2
+ import { useCallback as h, useSyncExternalStore as S, useRef as x, useState as y, useLayoutEffect as N } from "react";
3
+ import { a as v, b as R } from "./icons-C1_s0jKK.js";
4
+ import { a as T } from "./CommentThreadCard-Cxf8GwHJ.js";
5
+ const L = "$comments";
6
+ function I() {
7
+ const n = v(), e = h(
8
+ (r) => n.subscribe(L, r),
9
+ [n]
10
+ ), o = h(() => n.getComments(), [n]);
11
+ return S(e, o);
12
+ }
13
+ function C(n, e) {
14
+ var o, r, i;
15
+ for (const s of e.anchorRunIds ?? []) {
16
+ const l = document.querySelector(`[data-run-id="${s}"]`);
17
+ if (l) return l;
18
+ }
19
+ for (const s of n.getAllRunIds())
20
+ if ((i = (r = (o = n.getRun(s)) == null ? void 0 : o.marks) == null ? void 0 : r.commentIds) != null && i.includes(e.id)) {
21
+ const l = document.querySelector(`[data-run-id="${s}"]`);
22
+ if (l) return l;
23
+ }
24
+ return null;
25
+ }
26
+ function M(n, e, o) {
27
+ const r = n.getComment(e);
28
+ if (!r) return;
29
+ const i = C(n, r);
30
+ i && (i.scrollIntoView({ behavior: "smooth", block: "center" }), o(e), setTimeout(
31
+ () => o((s) => s === e ? null : s),
32
+ 1500
33
+ ));
34
+ }
35
+ function z({ authorId: n }) {
36
+ const e = v(), o = I(), [, r] = R(), i = x(null), [s, l] = y({}), a = [...o].sort((t, c) => Number(t.resolved) - Number(c.resolved));
37
+ N(() => {
38
+ function t() {
39
+ const c = i.current;
40
+ if (!c) return;
41
+ const E = c.getBoundingClientRect().top, d = {};
42
+ let u = -1 / 0;
43
+ for (const f of a) {
44
+ const p = C(e, f), w = p ? p.getBoundingClientRect().top - E : Number.isFinite(u) ? u + 90 : 0, b = Math.max(u, w);
45
+ d[f.id] = b, u = b + 90;
46
+ }
47
+ l(d);
48
+ }
49
+ return t(), window.addEventListener("scroll", t, !0), window.addEventListener("resize", t), () => {
50
+ window.removeEventListener("scroll", t, !0), window.removeEventListener("resize", t);
51
+ };
52
+ }, [o, e]);
53
+ const g = Object.values(s).reduce((t, c) => Math.max(t, c + 120), 0);
54
+ return a.length === 0 ? null : /* @__PURE__ */ m("div", { className: "be-comments-panel", contentEditable: !1, children: /* @__PURE__ */ m("div", { ref: i, className: "be-comments-panel-list", style: { height: g }, children: a.map((t) => /* @__PURE__ */ m(
55
+ "div",
56
+ {
57
+ className: "be-comments-panel-item",
58
+ style: { top: s[t.id] ?? 0 },
59
+ children: /* @__PURE__ */ m(
60
+ T,
61
+ {
62
+ store: e,
63
+ thread: t,
64
+ authorId: n,
65
+ onJumpToText: (c) => M(e, c, r)
66
+ }
67
+ )
68
+ },
69
+ t.id
70
+ )) }) });
71
+ }
72
+ export {
73
+ z as C,
74
+ I as u
75
+ };
76
+ //# sourceMappingURL=CommentsPanel-BwVVyMOC.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CommentsPanel-BwVVyMOC.js","sources":["../../src/react/useComments.js","../../src/react/CommentsPanel.jsx"],"sourcesContent":["import { useCallback, useSyncExternalStore } from 'react';\nimport { useEditorStore } from './EditorProvider.jsx';\n\nconst COMMENTS_KEY = '$comments';\n\n/**\n * Subscribes to the store's comment threads (see src/comments/comments.js's\n * addComment/replyToComment/resolveComment/deleteComment) — reactive so a\n * comments sidebar/panel re-renders after any of those, the same way\n * useFieldTypes re-renders a field-type list.\n *\n * store.getComments() returns a referentially-stable array (cached,\n * invalidated only by a comments op) — required by useSyncExternalStore,\n * same contract as useFieldTypes/useBlock/useRun.\n */\nexport function useComments() {\n const store = useEditorStore();\n const subscribe = useCallback(\n (onStoreChange) => store.subscribe(COMMENTS_KEY, onStoreChange),\n [store],\n );\n const getSnapshot = useCallback(() => store.getComments(), [store]);\n return useSyncExternalStore(subscribe, getSnapshot);\n}\n","import { useLayoutEffect, useRef, useState } from 'react';\nimport { useEditorStore, useHoveredComment } from './EditorProvider.jsx';\nimport { useComments } from './useComments.js';\nimport { CommentThreadCard } from './CommentThreadCard.jsx';\n\n/** Best-effort DOM lookup for where a thread's highlight actually lives right now -- anchorRunIds first (cheap, usually still right), falling back to a full scan (see removeCommentMarkEverywhere's own reasoning on why anchorRunIds can go stale). */\nfunction findAnchorEl(store, thread) {\n for (const id of thread.anchorRunIds ?? []) {\n const el = document.querySelector(`[data-run-id=\"${id}\"]`);\n if (el) return el;\n }\n for (const runId of store.getAllRunIds()) {\n if (store.getRun(runId)?.marks?.commentIds?.includes(thread.id)) {\n const el = document.querySelector(`[data-run-id=\"${runId}\"]`);\n if (el) return el;\n }\n }\n return null;\n}\n\nfunction scrollToComment(store, commentId, setHoveredCommentId) {\n const thread = store.getComment(commentId);\n if (!thread) return;\n const el = findAnchorEl(store, thread);\n if (!el) return;\n el.scrollIntoView({ behavior: 'smooth', block: 'center' });\n setHoveredCommentId(commentId);\n setTimeout(\n () => setHoveredCommentId((current) => (current === commentId ? null : current)),\n 1500,\n );\n}\n\n/**\n * The opt-in right-side comments panel (Notion/Google Docs-style) — every\n * thread in the document, unresolved first, each rendered via the same\n * CommentThreadCard the click-to-view CommentPopover uses (one reply/\n * resolve/delete implementation, two surfaces). Mount automatically via\n * `<NoteloomEditor showCommentsPanel commentAuthorId=\"...\">`, or render it\n * yourself anywhere under an `<EditorProvider>` for the granular API.\n *\n * Each card aligns vertically with its own anchored text (margin-comment\n * style), recomputed on scroll/resize and whenever the thread list changes\n * — a best-effort match against the current DOM (see findAnchorEl), not an\n * exact layout algorithm; closely-spaced comments can still overlap.\n *\n * `position: fixed` by default (see `.be-comments-panel` in style.css) so\n * it works with zero host layout changes; override that rule (or wrap it\n * in your own positioned container) for a different placement.\n */\nexport function CommentsPanel({ authorId }) {\n const store = useEditorStore();\n const comments = useComments();\n const [, setHoveredCommentId] = useHoveredComment();\n const listRef = useRef(null);\n const [positions, setPositions] = useState({});\n const sorted = [...comments].sort((a, b) => Number(a.resolved) - Number(b.resolved));\n\n useLayoutEffect(() => {\n function recompute() {\n const list = listRef.current;\n if (!list) return;\n const listTop = list.getBoundingClientRect().top;\n const next = {};\n // Starts at -Infinity (not 0) so a comment whose anchor has scrolled\n // above the panel moves right along with it (off the top, out of\n // view) instead of getting clamped there permanently -- Math.max\n // against a real previous-card position only kicks in to prevent\n // two comments landing on top of each other, never as a floor at 0.\n let cursor = -Infinity;\n for (const thread of sorted) {\n const el = findAnchorEl(store, thread);\n // Falls back to 0 (not `cursor + 90`) when an anchor can't be\n // found at all (e.g. its highlight was stripped by a later edit) --\n // otherwise a single missing anchor at the very start would leave\n // `cursor` at -Infinity forever, propagating through every\n // following card too.\n const natural = el\n ? el.getBoundingClientRect().top - listTop\n : Number.isFinite(cursor)\n ? cursor + 90\n : 0;\n const top = Math.max(cursor, natural);\n next[thread.id] = top;\n cursor = top + 90; // rough per-card minimum before the next one can start, to avoid total overlap\n }\n setPositions(next);\n }\n recompute();\n window.addEventListener('scroll', recompute, true);\n window.addEventListener('resize', recompute);\n return () => {\n window.removeEventListener('scroll', recompute, true);\n window.removeEventListener('resize', recompute);\n };\n // `comments` (not the locally re-sorted `sorted`, a fresh array every\n // render) is the stable dependency -- useComments() only returns a new\n // reference when the underlying data actually changed.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [comments, store]);\n\n const totalHeight = Object.values(positions).reduce((max, v) => Math.max(max, v + 120), 0);\n\n if (sorted.length === 0) return null;\n\n return (\n <div className=\"be-comments-panel\" contentEditable={false}>\n <div ref={listRef} className=\"be-comments-panel-list\" style={{ height: totalHeight }}>\n {sorted.map((thread) => (\n <div\n key={thread.id}\n className=\"be-comments-panel-item\"\n style={{ top: positions[thread.id] ?? 0 }}\n >\n <CommentThreadCard\n store={store}\n thread={thread}\n authorId={authorId}\n onJumpToText={(id) => scrollToComment(store, id, setHoveredCommentId)}\n />\n </div>\n ))}\n </div>\n </div>\n );\n}\n"],"names":["COMMENTS_KEY","useComments","store","useEditorStore","subscribe","useCallback","onStoreChange","getSnapshot","useSyncExternalStore","findAnchorEl","thread","id","el","runId","_c","_b","_a","scrollToComment","commentId","setHoveredCommentId","current","CommentsPanel","authorId","comments","useHoveredComment","listRef","useRef","positions","setPositions","useState","sorted","a","b","useLayoutEffect","recompute","list","listTop","next","cursor","natural","top","totalHeight","max","v","jsx","CommentThreadCard"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ "use strict";const a=require("react/jsx-runtime"),l=require("react"),f=require("./icons-DtCsXuU6.cjs"),S=require("./CommentThreadCard-CAJ3oPdr.cjs"),R="$comments";function p(){const n=f.useEditorStore(),e=l.useCallback(r=>n.subscribe(R,r),[n]),o=l.useCallback(()=>n.getComments(),[n]);return l.useSyncExternalStore(e,o)}function g(n,e){var o,r,c;for(const s of e.anchorRunIds??[]){const u=document.querySelector(`[data-run-id="${s}"]`);if(u)return u}for(const s of n.getAllRunIds())if((c=(r=(o=n.getRun(s))==null?void 0:o.marks)==null?void 0:r.commentIds)!=null&&c.includes(e.id)){const u=document.querySelector(`[data-run-id="${s}"]`);if(u)return u}return null}function T(n,e,o){const r=n.getComment(e);if(!r)return;const c=g(n,r);c&&(c.scrollIntoView({behavior:"smooth",block:"center"}),o(e),setTimeout(()=>o(s=>s===e?null:s),1500))}function y({authorId:n}){const e=f.useEditorStore(),o=p(),[,r]=f.useHoveredComment(),c=l.useRef(null),[s,u]=l.useState({}),d=[...o].sort((t,i)=>Number(t.resolved)-Number(i.resolved));l.useLayoutEffect(()=>{function t(){const i=c.current;if(!i)return;const x=i.getBoundingClientRect().top,C={};let m=-1/0;for(const b of d){const h=g(e,b),w=h?h.getBoundingClientRect().top-x:Number.isFinite(m)?m+90:0,v=Math.max(m,w);C[b.id]=v,m=v+90}u(C)}return t(),window.addEventListener("scroll",t,!0),window.addEventListener("resize",t),()=>{window.removeEventListener("scroll",t,!0),window.removeEventListener("resize",t)}},[o,e]);const E=Object.values(s).reduce((t,i)=>Math.max(t,i+120),0);return d.length===0?null:a.jsx("div",{className:"be-comments-panel",contentEditable:!1,children:a.jsx("div",{ref:c,className:"be-comments-panel-list",style:{height:E},children:d.map(t=>a.jsx("div",{className:"be-comments-panel-item",style:{top:s[t.id]??0},children:a.jsx(S.CommentThreadCard,{store:e,thread:t,authorId:n,onJumpToText:i=>T(e,i,r)})},t.id))})})}exports.CommentsPanel=y;exports.useComments=p;
2
+ //# sourceMappingURL=CommentsPanel-CES4uzye.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CommentsPanel-CES4uzye.cjs","sources":["../../src/react/useComments.js","../../src/react/CommentsPanel.jsx"],"sourcesContent":["import { useCallback, useSyncExternalStore } from 'react';\nimport { useEditorStore } from './EditorProvider.jsx';\n\nconst COMMENTS_KEY = '$comments';\n\n/**\n * Subscribes to the store's comment threads (see src/comments/comments.js's\n * addComment/replyToComment/resolveComment/deleteComment) — reactive so a\n * comments sidebar/panel re-renders after any of those, the same way\n * useFieldTypes re-renders a field-type list.\n *\n * store.getComments() returns a referentially-stable array (cached,\n * invalidated only by a comments op) — required by useSyncExternalStore,\n * same contract as useFieldTypes/useBlock/useRun.\n */\nexport function useComments() {\n const store = useEditorStore();\n const subscribe = useCallback(\n (onStoreChange) => store.subscribe(COMMENTS_KEY, onStoreChange),\n [store],\n );\n const getSnapshot = useCallback(() => store.getComments(), [store]);\n return useSyncExternalStore(subscribe, getSnapshot);\n}\n","import { useLayoutEffect, useRef, useState } from 'react';\nimport { useEditorStore, useHoveredComment } from './EditorProvider.jsx';\nimport { useComments } from './useComments.js';\nimport { CommentThreadCard } from './CommentThreadCard.jsx';\n\n/** Best-effort DOM lookup for where a thread's highlight actually lives right now -- anchorRunIds first (cheap, usually still right), falling back to a full scan (see removeCommentMarkEverywhere's own reasoning on why anchorRunIds can go stale). */\nfunction findAnchorEl(store, thread) {\n for (const id of thread.anchorRunIds ?? []) {\n const el = document.querySelector(`[data-run-id=\"${id}\"]`);\n if (el) return el;\n }\n for (const runId of store.getAllRunIds()) {\n if (store.getRun(runId)?.marks?.commentIds?.includes(thread.id)) {\n const el = document.querySelector(`[data-run-id=\"${runId}\"]`);\n if (el) return el;\n }\n }\n return null;\n}\n\nfunction scrollToComment(store, commentId, setHoveredCommentId) {\n const thread = store.getComment(commentId);\n if (!thread) return;\n const el = findAnchorEl(store, thread);\n if (!el) return;\n el.scrollIntoView({ behavior: 'smooth', block: 'center' });\n setHoveredCommentId(commentId);\n setTimeout(\n () => setHoveredCommentId((current) => (current === commentId ? null : current)),\n 1500,\n );\n}\n\n/**\n * The opt-in right-side comments panel (Notion/Google Docs-style) — every\n * thread in the document, unresolved first, each rendered via the same\n * CommentThreadCard the click-to-view CommentPopover uses (one reply/\n * resolve/delete implementation, two surfaces). Mount automatically via\n * `<NoteloomEditor showCommentsPanel commentAuthorId=\"...\">`, or render it\n * yourself anywhere under an `<EditorProvider>` for the granular API.\n *\n * Each card aligns vertically with its own anchored text (margin-comment\n * style), recomputed on scroll/resize and whenever the thread list changes\n * — a best-effort match against the current DOM (see findAnchorEl), not an\n * exact layout algorithm; closely-spaced comments can still overlap.\n *\n * `position: fixed` by default (see `.be-comments-panel` in style.css) so\n * it works with zero host layout changes; override that rule (or wrap it\n * in your own positioned container) for a different placement.\n */\nexport function CommentsPanel({ authorId }) {\n const store = useEditorStore();\n const comments = useComments();\n const [, setHoveredCommentId] = useHoveredComment();\n const listRef = useRef(null);\n const [positions, setPositions] = useState({});\n const sorted = [...comments].sort((a, b) => Number(a.resolved) - Number(b.resolved));\n\n useLayoutEffect(() => {\n function recompute() {\n const list = listRef.current;\n if (!list) return;\n const listTop = list.getBoundingClientRect().top;\n const next = {};\n // Starts at -Infinity (not 0) so a comment whose anchor has scrolled\n // above the panel moves right along with it (off the top, out of\n // view) instead of getting clamped there permanently -- Math.max\n // against a real previous-card position only kicks in to prevent\n // two comments landing on top of each other, never as a floor at 0.\n let cursor = -Infinity;\n for (const thread of sorted) {\n const el = findAnchorEl(store, thread);\n // Falls back to 0 (not `cursor + 90`) when an anchor can't be\n // found at all (e.g. its highlight was stripped by a later edit) --\n // otherwise a single missing anchor at the very start would leave\n // `cursor` at -Infinity forever, propagating through every\n // following card too.\n const natural = el\n ? el.getBoundingClientRect().top - listTop\n : Number.isFinite(cursor)\n ? cursor + 90\n : 0;\n const top = Math.max(cursor, natural);\n next[thread.id] = top;\n cursor = top + 90; // rough per-card minimum before the next one can start, to avoid total overlap\n }\n setPositions(next);\n }\n recompute();\n window.addEventListener('scroll', recompute, true);\n window.addEventListener('resize', recompute);\n return () => {\n window.removeEventListener('scroll', recompute, true);\n window.removeEventListener('resize', recompute);\n };\n // `comments` (not the locally re-sorted `sorted`, a fresh array every\n // render) is the stable dependency -- useComments() only returns a new\n // reference when the underlying data actually changed.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [comments, store]);\n\n const totalHeight = Object.values(positions).reduce((max, v) => Math.max(max, v + 120), 0);\n\n if (sorted.length === 0) return null;\n\n return (\n <div className=\"be-comments-panel\" contentEditable={false}>\n <div ref={listRef} className=\"be-comments-panel-list\" style={{ height: totalHeight }}>\n {sorted.map((thread) => (\n <div\n key={thread.id}\n className=\"be-comments-panel-item\"\n style={{ top: positions[thread.id] ?? 0 }}\n >\n <CommentThreadCard\n store={store}\n thread={thread}\n authorId={authorId}\n onJumpToText={(id) => scrollToComment(store, id, setHoveredCommentId)}\n />\n </div>\n ))}\n </div>\n </div>\n );\n}\n"],"names":["COMMENTS_KEY","useComments","store","useEditorStore","subscribe","useCallback","onStoreChange","getSnapshot","useSyncExternalStore","findAnchorEl","thread","id","el","runId","_c","_b","_a","scrollToComment","commentId","setHoveredCommentId","current","CommentsPanel","authorId","comments","useHoveredComment","listRef","useRef","positions","setPositions","useState","sorted","a","b","useLayoutEffect","recompute","list","listTop","next","cursor","natural","top","totalHeight","max","v","jsx","CommentThreadCard"],"mappings":"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"}