@burdenoff/microfe-planmagnet 2026.1001.8 → 2026.1002.2

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 (143) hide show
  1. package/dist/generated/global-types.d.ts +5 -0
  2. package/dist/generated/global-types.d.ts.map +1 -1
  3. package/dist/generated/wspace-operations.d.ts +1814 -401
  4. package/dist/generated/wspace-operations.d.ts.map +1 -1
  5. package/dist/generated/wspace-types.d.ts +5167 -54
  6. package/dist/generated/wspace-types.d.ts.map +1 -1
  7. package/dist/index.js +8 -8
  8. package/dist/planmagnet/PlanMagnetRoot.d.ts.map +1 -1
  9. package/dist/planmagnet/PlanMagnetRoot.js +172 -160
  10. package/dist/planmagnet/PlanMagnetRoot.js.map +1 -1
  11. package/dist/planmagnet/PlanMagnetRoutes.d.ts.map +1 -1
  12. package/dist/planmagnet/PlanMagnetRoutes.js +95 -83
  13. package/dist/planmagnet/PlanMagnetRoutes.js.map +1 -1
  14. package/dist/planmagnet/comments/CommentAttachmentUpload.d.ts +16 -0
  15. package/dist/planmagnet/comments/CommentAttachmentUpload.d.ts.map +1 -0
  16. package/dist/planmagnet/comments/CommentAttachmentUpload.js +218 -0
  17. package/dist/planmagnet/comments/CommentAttachmentUpload.js.map +1 -0
  18. package/dist/planmagnet/comments/CommentAttachments.d.ts +15 -0
  19. package/dist/planmagnet/comments/CommentAttachments.d.ts.map +1 -0
  20. package/dist/planmagnet/comments/CommentAttachments.js +74 -0
  21. package/dist/planmagnet/comments/CommentAttachments.js.map +1 -0
  22. package/dist/planmagnet/comments/CommentBody.d.ts +11 -0
  23. package/dist/planmagnet/comments/CommentBody.d.ts.map +1 -0
  24. package/dist/planmagnet/comments/CommentBody.js +130 -0
  25. package/dist/planmagnet/comments/CommentBody.js.map +1 -0
  26. package/dist/planmagnet/comments/CommentThread.d.ts +7 -2
  27. package/dist/planmagnet/comments/CommentThread.d.ts.map +1 -1
  28. package/dist/planmagnet/comments/CommentThread.js +288 -233
  29. package/dist/planmagnet/comments/CommentThread.js.map +1 -1
  30. package/dist/planmagnet/comments/MentionComposer.d.ts +0 -7
  31. package/dist/planmagnet/comments/MentionComposer.d.ts.map +1 -1
  32. package/dist/planmagnet/comments/MentionComposer.js +225 -86
  33. package/dist/planmagnet/comments/MentionComposer.js.map +1 -1
  34. package/dist/planmagnet/comments/TargetComments.d.ts.map +1 -1
  35. package/dist/planmagnet/comments/TargetComments.js +44 -6
  36. package/dist/planmagnet/comments/TargetComments.js.map +1 -1
  37. package/dist/planmagnet/comments/WorkItemComments.d.ts.map +1 -1
  38. package/dist/planmagnet/comments/WorkItemComments.js +22 -3
  39. package/dist/planmagnet/comments/WorkItemComments.js.map +1 -1
  40. package/dist/planmagnet/comments/commentAttachmentUploadClient.d.ts +102 -0
  41. package/dist/planmagnet/comments/commentAttachmentUploadClient.d.ts.map +1 -0
  42. package/dist/planmagnet/comments/commentAttachmentUploadClient.js +298 -0
  43. package/dist/planmagnet/comments/commentAttachmentUploadClient.js.map +1 -0
  44. package/dist/planmagnet/comments/commentImageTokens.d.ts +5 -0
  45. package/dist/planmagnet/comments/commentImageTokens.d.ts.map +1 -0
  46. package/dist/planmagnet/comments/commentImageTokens.js +14 -0
  47. package/dist/planmagnet/comments/commentImageTokens.js.map +1 -0
  48. package/dist/planmagnet/comments/commentTokens.d.ts +31 -0
  49. package/dist/planmagnet/comments/commentTokens.d.ts.map +1 -0
  50. package/dist/planmagnet/comments/commentTokens.js +142 -0
  51. package/dist/planmagnet/comments/commentTokens.js.map +1 -0
  52. package/dist/planmagnet/comments/fileDisplay.d.ts +22 -0
  53. package/dist/planmagnet/comments/fileDisplay.d.ts.map +1 -0
  54. package/dist/planmagnet/comments/fileDisplay.js +48 -0
  55. package/dist/planmagnet/comments/fileDisplay.js.map +1 -0
  56. package/dist/planmagnet/comments/useCommentFileUrl.d.ts +22 -0
  57. package/dist/planmagnet/comments/useCommentFileUrl.d.ts.map +1 -0
  58. package/dist/planmagnet/comments/useCommentFileUrl.js +54 -0
  59. package/dist/planmagnet/comments/useCommentFileUrl.js.map +1 -0
  60. package/dist/planmagnet/controls/Pickers.d.ts +7 -2
  61. package/dist/planmagnet/controls/Pickers.d.ts.map +1 -1
  62. package/dist/planmagnet/controls/Pickers.js +10 -9
  63. package/dist/planmagnet/controls/Pickers.js.map +1 -1
  64. package/dist/planmagnet/index.d.ts +1 -1
  65. package/dist/planmagnet/index.d.ts.map +1 -1
  66. package/dist/planmagnet/pages/ProductsFeatureAnalyticsPagePage.js +4 -4
  67. package/dist/planmagnet/pages/ProductsFeatureAnalyticsPagePage.js.map +1 -1
  68. package/dist/planmagnet/pages/ProductsFeatureDetailPagePage.d.ts.map +1 -1
  69. package/dist/planmagnet/pages/ProductsFeatureDetailPagePage.js +133 -131
  70. package/dist/planmagnet/pages/ProductsFeatureDetailPagePage.js.map +1 -1
  71. package/dist/planmagnet/pages/ProductsFeaturesPagePage.d.ts.map +1 -1
  72. package/dist/planmagnet/pages/ProductsFeaturesPagePage.js +5 -5
  73. package/dist/planmagnet/pages/ProductsFeaturesPagePage.js.map +1 -1
  74. package/dist/planmagnet/pages/ProductsNewFeaturePagePage.js +6 -6
  75. package/dist/planmagnet/pages/ProductsNewFeaturePagePage.js.map +1 -1
  76. package/dist/planmagnet/pages/ProductsProductDetailPagePage.d.ts.map +1 -1
  77. package/dist/planmagnet/pages/ProductsProductDetailPagePage.js +105 -82
  78. package/dist/planmagnet/pages/ProductsProductDetailPagePage.js.map +1 -1
  79. package/dist/planmagnet/pages/ProductsProductFeatureDetailPagePage.d.ts +2 -0
  80. package/dist/planmagnet/pages/ProductsProductFeatureDetailPagePage.d.ts.map +1 -0
  81. package/dist/planmagnet/pages/ProductsProductFeatureDetailPagePage.js +174 -0
  82. package/dist/planmagnet/pages/ProductsProductFeatureDetailPagePage.js.map +1 -0
  83. package/dist/planmagnet/pages/ProductsProductHierarchyPagePage.d.ts +2 -0
  84. package/dist/planmagnet/pages/ProductsProductHierarchyPagePage.d.ts.map +1 -0
  85. package/dist/planmagnet/pages/ProductsProductHierarchyPagePage.js +350 -0
  86. package/dist/planmagnet/pages/ProductsProductHierarchyPagePage.js.map +1 -0
  87. package/dist/planmagnet/pages/ProductsProductLinesPagePage.d.ts +2 -0
  88. package/dist/planmagnet/pages/ProductsProductLinesPagePage.d.ts.map +1 -0
  89. package/dist/planmagnet/pages/ProductsProductLinesPagePage.js +168 -0
  90. package/dist/planmagnet/pages/ProductsProductLinesPagePage.js.map +1 -0
  91. package/dist/planmagnet/pages/ProductsProductsPagePage.d.ts.map +1 -1
  92. package/dist/planmagnet/pages/ProductsProductsPagePage.js +1 -1
  93. package/dist/planmagnet/pages/ProductsProductsPagePage.js.map +1 -1
  94. package/dist/planmagnet/pages/TasksTaskDetailPagePage.d.ts.map +1 -1
  95. package/dist/planmagnet/pages/TasksTaskDetailPagePage.js +156 -154
  96. package/dist/planmagnet/pages/TasksTaskDetailPagePage.js.map +1 -1
  97. package/dist/planmagnet/product/FeatureLinkPanels.d.ts +29 -0
  98. package/dist/planmagnet/product/FeatureLinkPanels.d.ts.map +1 -0
  99. package/dist/planmagnet/product/FeatureLinkPanels.js +373 -0
  100. package/dist/planmagnet/product/FeatureLinkPanels.js.map +1 -0
  101. package/dist/planmagnet/product/ProductControls.js +1 -1
  102. package/dist/planmagnet/product/ProductControls.js.map +1 -1
  103. package/dist/planmagnet/product/ProductFeatureControls.d.ts +39 -0
  104. package/dist/planmagnet/product/ProductFeatureControls.d.ts.map +1 -0
  105. package/dist/planmagnet/product/ProductFeatureControls.js +315 -0
  106. package/dist/planmagnet/product/ProductFeatureControls.js.map +1 -0
  107. package/dist/planmagnet/product/ProductFeatureLinkCard.d.ts +4 -0
  108. package/dist/planmagnet/product/ProductFeatureLinkCard.d.ts.map +1 -0
  109. package/dist/planmagnet/product/ProductFeatureLinkCard.js +192 -0
  110. package/dist/planmagnet/product/ProductFeatureLinkCard.js.map +1 -0
  111. package/dist/planmagnet/product/ProductLineControls.d.ts +52 -0
  112. package/dist/planmagnet/product/ProductLineControls.d.ts.map +1 -0
  113. package/dist/planmagnet/product/ProductLineControls.js +417 -0
  114. package/dist/planmagnet/product/ProductLineControls.js.map +1 -0
  115. package/dist/planmagnet/product/ProductTeamsPanel.d.ts +4 -0
  116. package/dist/planmagnet/product/ProductTeamsPanel.d.ts.map +1 -0
  117. package/dist/planmagnet/product/ProductTeamsPanel.js +129 -0
  118. package/dist/planmagnet/product/ProductTeamsPanel.js.map +1 -0
  119. package/dist/planmagnet/product/WorkItemProductFeatureChip.d.ts +4 -0
  120. package/dist/planmagnet/product/WorkItemProductFeatureChip.d.ts.map +1 -0
  121. package/dist/planmagnet/product/WorkItemProductFeatureChip.js +41 -0
  122. package/dist/planmagnet/product/WorkItemProductFeatureChip.js.map +1 -0
  123. package/dist/planmagnet/product/hierarchy.d.ts +127 -0
  124. package/dist/planmagnet/product/hierarchy.d.ts.map +1 -0
  125. package/dist/planmagnet/product/hierarchy.js +173 -0
  126. package/dist/planmagnet/product/hierarchy.js.map +1 -0
  127. package/dist/planmagnet/product/hierarchyOperations.d.ts +35 -0
  128. package/dist/planmagnet/product/hierarchyOperations.d.ts.map +1 -0
  129. package/dist/planmagnet/product/hierarchyOperations.js +440 -0
  130. package/dist/planmagnet/product/hierarchyOperations.js.map +1 -0
  131. package/dist/planmagnet/product/refusal.d.ts +14 -0
  132. package/dist/planmagnet/product/refusal.d.ts.map +1 -0
  133. package/dist/planmagnet/product/refusal.js +10 -0
  134. package/dist/planmagnet/product/refusal.js.map +1 -0
  135. package/dist/planmagnet/product/routes.d.ts +4 -0
  136. package/dist/planmagnet/product/routes.d.ts.map +1 -1
  137. package/dist/planmagnet/product/routes.js +2 -2
  138. package/dist/planmagnet/product/routes.js.map +1 -1
  139. package/dist/shared/utils/i18n.d.ts +3 -0
  140. package/dist/shared/utils/i18n.d.ts.map +1 -1
  141. package/dist/shared/utils/i18n.js +1 -1
  142. package/dist/shared/utils/i18n.js.map +1 -1
  143. package/package.json +5 -4
@@ -0,0 +1,74 @@
1
+ import { useCommentFileUrl as e } from "./useCommentFileUrl.js";
2
+ import { formatBytes as t, mimeTypeIcon as n } from "./fileDisplay.js";
3
+ import { useEffect as r, useRef as i, useState as a } from "react";
4
+ import { jsx as o, jsxs as s } from "react/jsx-runtime";
5
+ //#region src/planmagnet/comments/CommentAttachments.tsx
6
+ function c({ attachments: e, target: t }) {
7
+ return e.length === 0 ? null : /* @__PURE__ */ o("ul", {
8
+ className: "flex flex-wrap gap-1.5",
9
+ "data-testid": "comment-attachments",
10
+ children: e.map((e) => /* @__PURE__ */ o("li", { children: /* @__PURE__ */ o(l, {
11
+ attachment: e,
12
+ target: t
13
+ }) }, e.id))
14
+ });
15
+ }
16
+ function l({ attachment: c, target: l }) {
17
+ let u = e(), [d, f] = a(!1), [p, m] = a(!1), h = i(null);
18
+ r(() => () => {
19
+ let e = h.current;
20
+ h.current = null, e?.close();
21
+ }, []);
22
+ let g = async () => {
23
+ if (d) return;
24
+ m(!1);
25
+ let e = window.open("about:blank", "_blank");
26
+ if (!e) {
27
+ m(!0);
28
+ return;
29
+ }
30
+ e.opener = null, h.current = e, f(!0);
31
+ try {
32
+ let t = await u(l, c.id);
33
+ h.current === e && !e.closed && e.location.replace(t);
34
+ } catch {
35
+ h.current === e && (e.close(), m(!0));
36
+ } finally {
37
+ h.current === e && (h.current = null, f(!1));
38
+ }
39
+ };
40
+ return /* @__PURE__ */ s("div", {
41
+ className: "space-y-0.5",
42
+ children: [/* @__PURE__ */ s("button", {
43
+ type: "button",
44
+ disabled: d,
45
+ title: c.name,
46
+ "data-testid": "comment-attachment",
47
+ className: "inline-flex max-w-[14rem] items-center gap-1.5 rounded-md border border-border-default px-2 py-1 text-xs hover:bg-muted disabled:opacity-70",
48
+ onClick: () => void g(),
49
+ children: [
50
+ n(c.mimeType),
51
+ /* @__PURE__ */ o("span", {
52
+ className: "truncate",
53
+ children: c.name
54
+ }),
55
+ /* @__PURE__ */ o("span", {
56
+ className: "shrink-0 text-muted-foreground",
57
+ children: t(c.size)
58
+ }),
59
+ d && /* @__PURE__ */ o("span", {
60
+ className: "sr-only",
61
+ children: "Opening…"
62
+ })
63
+ ]
64
+ }), p && /* @__PURE__ */ o("p", {
65
+ role: "alert",
66
+ className: "max-w-[14rem] text-xs text-destructive",
67
+ children: "Attachment unavailable. Check your access and pop-up settings."
68
+ })]
69
+ });
70
+ }
71
+ //#endregion
72
+ export { c as CommentAttachments };
73
+
74
+ //# sourceMappingURL=CommentAttachments.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CommentAttachments.js","names":[],"sources":["../../../src/planmagnet/comments/CommentAttachments.tsx"],"sourcesContent":["import { useEffect, useRef, useState } from 'react';\nimport { formatBytes, mimeTypeIcon } from './fileDisplay';\nimport { useCommentFileUrl, type CommentFileTarget } from './useCommentFileUrl';\n\n/** Server-snapshotted metadata; the attachment ID, not its raw Files ID, authorizes each download. */\nexport interface CommentAttachmentSummary {\n id: string;\n fileId: string;\n name: string;\n mimeType: string;\n size: number;\n}\n\nexport function CommentAttachments({\n attachments,\n target,\n}: {\n attachments: CommentAttachmentSummary[];\n target: CommentFileTarget;\n}) {\n if (attachments.length === 0) return null;\n return (\n <ul className=\"flex flex-wrap gap-1.5\" data-testid=\"comment-attachments\">\n {attachments.map((attachment) => (\n <li key={attachment.id}>\n <CommentAttachmentLink attachment={attachment} target={target} />\n </li>\n ))}\n </ul>\n );\n}\n\nfunction CommentAttachmentLink({\n attachment,\n target,\n}: {\n attachment: CommentAttachmentSummary;\n target: CommentFileTarget;\n}) {\n const getUrl = useCommentFileUrl();\n const [opening, setOpening] = useState(false);\n const [error, setError] = useState(false);\n const pendingTabRef = useRef<Window | null>(null);\n\n useEffect(() => {\n return () => {\n // Navigating away must not leave a blank tab that a finished request could still populate.\n const tab = pendingTabRef.current;\n pendingTabRef.current = null;\n tab?.close();\n };\n }, []);\n\n const open = async () => {\n if (opening) return;\n setError(false);\n // Open in the user's click gesture; browsers often block window.open after an awaited GraphQL request.\n // Sever the opener before making a network request, then navigate only after fresh authorization succeeds.\n const tab = window.open('about:blank', '_blank');\n if (!tab) {\n setError(true);\n return;\n }\n tab.opener = null;\n pendingTabRef.current = tab;\n setOpening(true);\n try {\n const url = await getUrl(target, attachment.id);\n if (pendingTabRef.current === tab && !tab.closed) tab.location.replace(url);\n } catch {\n if (pendingTabRef.current === tab) {\n tab.close();\n setError(true);\n }\n } finally {\n if (pendingTabRef.current === tab) {\n pendingTabRef.current = null;\n setOpening(false);\n }\n }\n };\n\n return (\n <div className=\"space-y-0.5\">\n <button\n type=\"button\"\n disabled={opening}\n title={attachment.name}\n data-testid=\"comment-attachment\"\n className=\"inline-flex max-w-[14rem] items-center gap-1.5 rounded-md border border-border-default px-2 py-1 text-xs hover:bg-muted disabled:opacity-70\"\n onClick={() => void open()}\n >\n {mimeTypeIcon(attachment.mimeType)}\n <span className=\"truncate\">{attachment.name}</span>\n <span className=\"shrink-0 text-muted-foreground\">{formatBytes(attachment.size)}</span>\n {opening && <span className=\"sr-only\">Opening…</span>}\n </button>\n {error && (\n <p role=\"alert\" className=\"max-w-[14rem] text-xs text-destructive\">\n Attachment unavailable. Check your access and pop-up settings.\n </p>\n )}\n </div>\n );\n}\n"],"mappings":";;;;;AAaA,SAAgB,EAAmB,EACjC,gBACA,aAIC;AAED,QADI,EAAY,WAAW,IAAU,OAEnC,kBAAC,MAAD;EAAI,WAAU;EAAyB,eAAY;YAChD,EAAY,KAAK,MAChB,kBAAC,MAAD,EAAA,UACE,kBAAC,GAAD;GAAmC;GAAoB;GAAU,CAAA,EAC9D,EAFI,EAAW,GAEf,CACL;EACC,CAAA;;AAIT,SAAS,EAAsB,EAC7B,eACA,aAIC;CACD,IAAM,IAAS,GAAmB,EAC5B,CAAC,GAAS,KAAc,EAAS,GAAM,EACvC,CAAC,GAAO,KAAY,EAAS,GAAM,EACnC,IAAgB,EAAsB,KAAK;AAEjD,eACe;EAEX,IAAM,IAAM,EAAc;AAE1B,EADA,EAAc,UAAU,MACxB,GAAK,OAAO;IAEb,EAAE,CAAC;CAEN,IAAM,IAAO,YAAY;AACvB,MAAI,EAAS;AACb,IAAS,GAAM;EAGf,IAAM,IAAM,OAAO,KAAK,eAAe,SAAS;AAChD,MAAI,CAAC,GAAK;AACR,KAAS,GAAK;AACd;;AAIF,EAFA,EAAI,SAAS,MACb,EAAc,UAAU,GACxB,EAAW,GAAK;AAChB,MAAI;GACF,IAAM,IAAM,MAAM,EAAO,GAAQ,EAAW,GAAG;AAC/C,GAAI,EAAc,YAAY,KAAO,CAAC,EAAI,UAAQ,EAAI,SAAS,QAAQ,EAAI;UACrE;AACN,GAAI,EAAc,YAAY,MAC5B,EAAI,OAAO,EACX,EAAS,GAAK;YAER;AACR,GAAI,EAAc,YAAY,MAC5B,EAAc,UAAU,MACxB,EAAW,GAAM;;;AAKvB,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf,CACE,kBAAC,UAAD;GACE,MAAK;GACL,UAAU;GACV,OAAO,EAAW;GAClB,eAAY;GACZ,WAAU;GACV,eAAe,KAAK,GAAM;aAN5B;IAQG,EAAa,EAAW,SAAS;IAClC,kBAAC,QAAD;KAAM,WAAU;eAAY,EAAW;KAAY,CAAA;IACnD,kBAAC,QAAD;KAAM,WAAU;eAAkC,EAAY,EAAW,KAAK;KAAQ,CAAA;IACrF,KAAW,kBAAC,QAAD;KAAM,WAAU;eAAU;KAAe,CAAA;IAC9C;MACR,KACC,kBAAC,KAAD;GAAG,MAAK;GAAQ,WAAU;aAAyC;GAE/D,CAAA,CAEF"}
@@ -0,0 +1,11 @@
1
+ import { CommentFileTarget } from './useCommentFileUrl';
2
+ import { CommentAttachmentSummary } from './CommentAttachments';
3
+
4
+ export interface CommentBodyProps {
5
+ text: string;
6
+ attachments: CommentAttachmentSummary[];
7
+ target: CommentFileTarget;
8
+ className?: string;
9
+ }
10
+ export declare function CommentBody({ text, attachments, target, className }: CommentBodyProps): import("react/jsx-runtime").JSX.Element;
11
+ //# sourceMappingURL=CommentBody.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CommentBody.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/comments/CommentBody.tsx"],"names":[],"mappings":"AAIA,OAAO,EAAqB,KAAK,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAChF,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,sBAAsB,CAAC;AAkKrE,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,wBAAwB,EAAE,CAAC;IACxC,MAAM,EAAE,iBAAiB,CAAC;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,wBAAgB,WAAW,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,EAAE,SAAS,EAAE,EAAE,gBAAgB,2CAsBrF"}
@@ -0,0 +1,130 @@
1
+ import { PersonName as e } from "../people/People.js";
2
+ import { tokenizeCommentBody as t } from "./commentTokens.js";
3
+ import { useCommentFileUrl as n } from "./useCommentFileUrl.js";
4
+ import { Fragment as r, useEffect as i, useRef as a, useState as o } from "react";
5
+ import { ImageOff as s } from "lucide-react";
6
+ import { Fragment as c, jsx as l, jsxs as u } from "react/jsx-runtime";
7
+ //#region src/planmagnet/comments/CommentBody.tsx
8
+ function d({ token: t, attachment: n, target: r }) {
9
+ switch (t.kind) {
10
+ case "text": return /* @__PURE__ */ l(c, { children: t.value });
11
+ case "mention": return /* @__PURE__ */ u("span", {
12
+ className: "rounded bg-primary/10 px-1 py-0.5 font-medium text-primary",
13
+ children: ["@", /* @__PURE__ */ l(e, {
14
+ id: t.actorId,
15
+ className: "text-primary"
16
+ })]
17
+ });
18
+ case "link": return /* @__PURE__ */ l("a", {
19
+ href: t.url,
20
+ target: "_blank",
21
+ rel: "noopener noreferrer nofollow",
22
+ className: "text-primary underline underline-offset-2 hover:no-underline",
23
+ children: t.text
24
+ });
25
+ case "image": return n?.mimeType.startsWith("image/") ? /* @__PURE__ */ l(p, {
26
+ attachment: n,
27
+ target: r,
28
+ alt: t.alt
29
+ }, n.id) : /* @__PURE__ */ l(f, { alt: t.alt });
30
+ case "inline-code": return /* @__PURE__ */ l("code", {
31
+ className: "rounded bg-muted px-1 py-0.5 font-mono text-[0.85em]",
32
+ children: t.value
33
+ });
34
+ case "code-block": return /* @__PURE__ */ l("pre", {
35
+ className: "my-1 overflow-x-auto rounded-md bg-muted p-2 font-mono text-[0.85em] whitespace-pre",
36
+ children: /* @__PURE__ */ l("code", { children: t.value })
37
+ });
38
+ case "bold": return /* @__PURE__ */ l("strong", {
39
+ className: "font-semibold",
40
+ children: t.value
41
+ });
42
+ case "italic": return /* @__PURE__ */ l("em", { children: t.value });
43
+ }
44
+ }
45
+ function f({ alt: e }) {
46
+ return /* @__PURE__ */ u("span", {
47
+ className: "my-1 inline-flex items-center gap-1 rounded-md border border-border-default px-2 py-1 align-bottom text-xs text-muted-foreground",
48
+ children: [/* @__PURE__ */ l(s, {
49
+ className: "h-3.5 w-3.5",
50
+ "aria-hidden": "true"
51
+ }), e || "Image unavailable"]
52
+ });
53
+ }
54
+ function p({ attachment: e, target: t, alt: r }) {
55
+ let s = n(), c = t.type, u = t.id, d = a(null), [p, m] = o(!1), [h, g] = o(null), [_, v] = o(!1), [y, b] = o(0);
56
+ return i(() => {
57
+ let e = d.current;
58
+ if (!e) return;
59
+ if (typeof IntersectionObserver > "u") {
60
+ let e = !0;
61
+ return queueMicrotask(() => {
62
+ e && m(!0);
63
+ }), () => {
64
+ e = !1;
65
+ };
66
+ }
67
+ let t = new IntersectionObserver((e) => {
68
+ e.some((e) => e.isIntersecting) && (m(!0), t.disconnect());
69
+ }, { rootMargin: "256px" });
70
+ return t.observe(e), () => t.disconnect();
71
+ }, []), i(() => {
72
+ if (!p) return;
73
+ let t = !0;
74
+ return s({
75
+ type: c,
76
+ id: u
77
+ }, e.id).then((e) => {
78
+ t && g(e);
79
+ }, () => {
80
+ t && v(!0);
81
+ }), () => {
82
+ t = !1;
83
+ };
84
+ }, [
85
+ p,
86
+ s,
87
+ c,
88
+ u,
89
+ e.id,
90
+ y
91
+ ]), /* @__PURE__ */ l("span", {
92
+ ref: d,
93
+ children: _ ? /* @__PURE__ */ l(f, { alt: r }) : h ? /* @__PURE__ */ l("img", {
94
+ src: h,
95
+ alt: r,
96
+ className: "my-1 max-h-64 max-w-full rounded-md border border-border-default object-contain align-bottom",
97
+ loading: "eager",
98
+ onError: () => {
99
+ y === 0 ? (g(null), b(1)) : v(!0);
100
+ }
101
+ }) : /* @__PURE__ */ l("span", {
102
+ className: "my-1 inline-flex h-20 w-32 animate-pulse items-center justify-center rounded-md bg-muted align-bottom text-xs text-muted-foreground",
103
+ "aria-hidden": "true"
104
+ })
105
+ });
106
+ }
107
+ function m({ text: e, attachments: n, target: i, className: a }) {
108
+ let o;
109
+ try {
110
+ o = t(e);
111
+ } catch {
112
+ o = [{
113
+ kind: "text",
114
+ value: e
115
+ }];
116
+ }
117
+ let s = new Map(n.map((e) => [e.fileId.toLowerCase(), e]));
118
+ return /* @__PURE__ */ l("div", {
119
+ className: a ?? "text-sm whitespace-pre-wrap break-words",
120
+ children: o.map((e, t) => /* @__PURE__ */ l(r, { children: /* @__PURE__ */ l(d, {
121
+ token: e,
122
+ attachment: e.kind === "image" ? s.get(e.fileId.toLowerCase()) : void 0,
123
+ target: i
124
+ }) }, t))
125
+ });
126
+ }
127
+ //#endregion
128
+ export { m as CommentBody };
129
+
130
+ //# sourceMappingURL=CommentBody.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CommentBody.js","names":[],"sources":["../../../src/planmagnet/comments/CommentBody.tsx"],"sourcesContent":["import { Fragment, useEffect, useRef, useState } from 'react';\nimport { ImageOff } from 'lucide-react';\nimport { PersonName } from '../people/People';\nimport { tokenizeCommentBody, type CommentToken } from './commentTokens';\nimport { useCommentFileUrl, type CommentFileTarget } from './useCommentFileUrl';\nimport type { CommentAttachmentSummary } from './CommentAttachments';\n\n/** Render only the server's constrained plain-text grammar; raw HTML is always inert React text. */\nfunction TokenView({\n token,\n attachment,\n target,\n}: {\n token: CommentToken;\n attachment?: CommentAttachmentSummary;\n target: CommentFileTarget;\n}) {\n switch (token.kind) {\n case 'text':\n return <>{token.value}</>;\n case 'mention':\n return (\n <span className=\"rounded bg-primary/10 px-1 py-0.5 font-medium text-primary\">\n @<PersonName id={token.actorId} className=\"text-primary\" />\n </span>\n );\n case 'link':\n return (\n <a\n href={token.url}\n target=\"_blank\"\n rel=\"noopener noreferrer nofollow\"\n className=\"text-primary underline underline-offset-2 hover:no-underline\"\n >\n {token.text}\n </a>\n );\n case 'image':\n // A text token is not itself a grant to read any Files ID. Only this comment's attachment row\n // permits a download; an unbound fileId in a hand-edited body remains inert.\n return attachment?.mimeType.startsWith('image/') ? (\n <CommentBodyImage\n key={attachment.id}\n attachment={attachment}\n target={target}\n alt={token.alt}\n />\n ) : (\n <UnavailableImage alt={token.alt} />\n );\n case 'inline-code':\n return (\n <code className=\"rounded bg-muted px-1 py-0.5 font-mono text-[0.85em]\">{token.value}</code>\n );\n case 'code-block':\n return (\n <pre className=\"my-1 overflow-x-auto rounded-md bg-muted p-2 font-mono text-[0.85em] whitespace-pre\">\n <code>{token.value}</code>\n </pre>\n );\n case 'bold':\n return <strong className=\"font-semibold\">{token.value}</strong>;\n case 'italic':\n return <em>{token.value}</em>;\n }\n}\n\nfunction UnavailableImage({ alt }: { alt: string }) {\n return (\n <span className=\"my-1 inline-flex items-center gap-1 rounded-md border border-border-default px-2 py-1 align-bottom text-xs text-muted-foreground\">\n <ImageOff className=\"h-3.5 w-3.5\" aria-hidden=\"true\" />\n {alt || 'Image unavailable'}\n </span>\n );\n}\n\n/** Fetch a fresh, short-lived URL only as an attached image enters the viewport. */\nfunction CommentBodyImage({\n attachment,\n target,\n alt,\n}: {\n attachment: CommentAttachmentSummary;\n target: CommentFileTarget;\n alt: string;\n}) {\n const getUrl = useCommentFileUrl();\n const targetType = target.type;\n const targetId = target.id;\n const containerRef = useRef<HTMLSpanElement>(null);\n const [visible, setVisible] = useState(false);\n const [url, setUrl] = useState<string | null>(null);\n const [failed, setFailed] = useState(false);\n const [retry, setRetry] = useState(0);\n\n useEffect(() => {\n const container = containerRef.current;\n if (!container) return;\n if (typeof IntersectionObserver === 'undefined') {\n let active = true;\n queueMicrotask(() => {\n if (active) setVisible(true);\n });\n return () => {\n active = false;\n };\n }\n const observer = new IntersectionObserver(\n (entries) => {\n if (entries.some((entry) => entry.isIntersecting)) {\n setVisible(true);\n observer.disconnect();\n }\n },\n { rootMargin: '256px' }\n );\n observer.observe(container);\n return () => observer.disconnect();\n }, []);\n\n useEffect(() => {\n if (!visible) return;\n let active = true;\n void getUrl({ type: targetType, id: targetId }, attachment.id).then(\n (next) => {\n if (active) setUrl(next);\n },\n () => {\n if (active) setFailed(true);\n }\n );\n return () => {\n active = false;\n };\n }, [visible, getUrl, targetType, targetId, attachment.id, retry]);\n\n return (\n <span ref={containerRef}>\n {failed ? (\n <UnavailableImage alt={alt} />\n ) : url ? (\n <img\n src={url}\n alt={alt}\n className=\"my-1 max-h-64 max-w-full rounded-md border border-border-default object-contain align-bottom\"\n loading=\"eager\"\n onError={() => {\n if (retry === 0) {\n // The browser may have delayed an image request until its presign expired. Retry once via\n // the comment-reader query, never by reusing a cached URL or looping forever on failure.\n setUrl(null);\n setRetry(1);\n } else {\n setFailed(true);\n }\n }}\n />\n ) : (\n <span\n className=\"my-1 inline-flex h-20 w-32 animate-pulse items-center justify-center rounded-md bg-muted align-bottom text-xs text-muted-foreground\"\n aria-hidden=\"true\"\n />\n )}\n </span>\n );\n}\n\nexport interface CommentBodyProps {\n text: string;\n attachments: CommentAttachmentSummary[];\n target: CommentFileTarget;\n className?: string;\n}\n\nexport function CommentBody({ text, attachments, target, className }: CommentBodyProps) {\n let tokens: CommentToken[];\n try {\n tokens = tokenizeCommentBody(text);\n } catch {\n tokens = [{ kind: 'text', value: text }];\n }\n const files = new Map(attachments.map((item) => [item.fileId.toLowerCase(), item]));\n\n return (\n <div className={className ?? 'text-sm whitespace-pre-wrap break-words'}>\n {tokens.map((token, index) => (\n <Fragment key={index}>\n <TokenView\n token={token}\n attachment={token.kind === 'image' ? files.get(token.fileId.toLowerCase()) : undefined}\n target={target}\n />\n </Fragment>\n ))}\n </div>\n );\n}\n"],"mappings":"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"}
@@ -1,4 +1,6 @@
1
1
  import { DocumentNode } from 'graphql';
2
+ import { CommentAttachmentSummary } from './CommentAttachments';
3
+ import { CommentFileTarget } from './useCommentFileUrl';
2
4
 
3
5
  type CommentStatus = 'OPEN' | 'RESOLVED';
4
6
  export interface ThreadComment {
@@ -12,9 +14,12 @@ export interface ThreadComment {
12
14
  resolvedBy: string | null;
13
15
  resolvedAt: string | null;
14
16
  mentions: string[];
17
+ attachments: CommentAttachmentSummary[];
15
18
  replies: ThreadComment[];
16
19
  }
17
20
  export interface CommentThreadProps {
21
+ /** Verified by the matching product-read resource before an attachment can be downloaded. */
22
+ target: CommentFileTarget;
18
23
  /** What this thread is attached to, used only in accessible copy ("Add a comment on this task…"). */
19
24
  targetNoun: string;
20
25
  /** Seeds every form-control id this instance renders, so two threads on the same page never collide. */
@@ -28,7 +33,7 @@ export interface CommentThreadProps {
28
33
  };
29
34
  add: {
30
35
  document: DocumentNode;
31
- buildVariables: (body: string, parentId: string | null) => Record<string, unknown>;
36
+ buildVariables: (body: string, parentId: string | null, attachmentFileIds: string[]) => Record<string, unknown>;
32
37
  };
33
38
  edit: DocumentNode;
34
39
  remove: DocumentNode;
@@ -46,6 +51,6 @@ export interface CommentThreadProps {
46
51
  notifyTyping: () => void;
47
52
  };
48
53
  }
49
- export declare function CommentThread({ targetNoun, idPrefix, query, add, edit, remove, resolve, refetchQueries, onTotalCount, presence, }: CommentThreadProps): import("react/jsx-runtime").JSX.Element;
54
+ export declare function CommentThread({ target, targetNoun, idPrefix, query, add, edit, remove, resolve, refetchQueries, onTotalCount, presence, }: CommentThreadProps): import("react/jsx-runtime").JSX.Element;
50
55
  export {};
51
56
  //# sourceMappingURL=CommentThread.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"CommentThread.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/comments/CommentThread.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAqC5C,KAAK,aAAa,GAAG,MAAM,GAAG,UAAU,CAAC;AAEzC,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,MAAM,EAAE,aAAa,CAAC;IACtB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,OAAO,EAAE,aAAa,EAAE,CAAC;CAC1B;AAUD,MAAM,WAAW,kBAAkB;IACjC,qGAAqG;IACrG,UAAU,EAAE,MAAM,CAAC;IACnB,wGAAwG;IACxG,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE;QACL,QAAQ,EAAE,YAAY,CAAC;QACvB,0FAA0F;QAC1F,OAAO,EAAE,MAAM,CAAC;QAChB,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QACnC,IAAI,CAAC,EAAE,OAAO,CAAC;KAChB,CAAC;IACF,GAAG,EAAE;QACH,QAAQ,EAAE,YAAY,CAAC;QACvB,cAAc,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,KAAK,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;KACpF,CAAC;IACF,IAAI,EAAE,YAAY,CAAC;IACnB,MAAM,EAAE,YAAY,CAAC;IACrB,OAAO,EAAE,YAAY,CAAC;IACtB;mEAC+D;IAC/D,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,8FAA8F;IAC9F,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC;;uGAEmG;IACnG,QAAQ,CAAC,EAAE;QACT,cAAc,EAAE,SAAS,MAAM,EAAE,CAAC;QAClC,YAAY,EAAE,MAAM,IAAI,CAAC;KAC1B,CAAC;CACH;AAID,wBAAgB,aAAa,CAAC,EAC5B,UAAU,EACV,QAAQ,EACR,KAAK,EACL,GAAG,EACH,IAAI,EACJ,MAAM,EACN,OAAO,EACP,cAAc,EACd,YAAY,EACZ,QAAQ,GACT,EAAE,kBAAkB,2CA+KpB"}
1
+ {"version":3,"file":"CommentThread.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/comments/CommentThread.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAY5C,OAAO,EAAsB,KAAK,wBAAwB,EAAE,MAAM,sBAAsB,CAAC;AACzF,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAoC7D,KAAK,aAAa,GAAG,MAAM,GAAG,UAAU,CAAC;AAEzC,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,MAAM,EAAE,aAAa,CAAC;IACtB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,WAAW,EAAE,wBAAwB,EAAE,CAAC;IACxC,OAAO,EAAE,aAAa,EAAE,CAAC;CAC1B;AAUD,MAAM,WAAW,kBAAkB;IACjC,6FAA6F;IAC7F,MAAM,EAAE,iBAAiB,CAAC;IAC1B,qGAAqG;IACrG,UAAU,EAAE,MAAM,CAAC;IACnB,wGAAwG;IACxG,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE;QACL,QAAQ,EAAE,YAAY,CAAC;QACvB,0FAA0F;QAC1F,OAAO,EAAE,MAAM,CAAC;QAChB,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QACnC,IAAI,CAAC,EAAE,OAAO,CAAC;KAChB,CAAC;IACF,GAAG,EAAE;QACH,QAAQ,EAAE,YAAY,CAAC;QACvB,cAAc,EAAE,CACd,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,GAAG,IAAI,EACvB,iBAAiB,EAAE,MAAM,EAAE,KACxB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;KAC9B,CAAC;IACF,IAAI,EAAE,YAAY,CAAC;IACnB,MAAM,EAAE,YAAY,CAAC;IACrB,OAAO,EAAE,YAAY,CAAC;IACtB;mEAC+D;IAC/D,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,8FAA8F;IAC9F,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC;;uGAEmG;IACnG,QAAQ,CAAC,EAAE;QACT,cAAc,EAAE,SAAS,MAAM,EAAE,CAAC;QAClC,YAAY,EAAE,MAAM,IAAI,CAAC;KAC1B,CAAC;CACH;AAID,wBAAgB,aAAa,CAAC,EAC5B,MAAM,EACN,UAAU,EACV,QAAQ,EACR,KAAK,EACL,GAAG,EACH,IAAI,EACJ,MAAM,EACN,OAAO,EACP,cAAc,EACd,YAAY,EACZ,QAAQ,GACT,EAAE,kBAAkB,2CAkNpB"}