@beyondwork/docx-react-component 1.0.28 → 1.0.30

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 (92) hide show
  1. package/package.json +26 -37
  2. package/src/api/public-types.ts +531 -0
  3. package/src/api/session-state.ts +2 -0
  4. package/src/core/commands/index.ts +201 -79
  5. package/src/core/commands/table-structure-commands.ts +138 -5
  6. package/src/core/state/text-transaction.ts +370 -3
  7. package/src/index.ts +41 -0
  8. package/src/io/docx-session.ts +318 -25
  9. package/src/io/export/serialize-footnotes.ts +41 -46
  10. package/src/io/export/serialize-headers-footers.ts +36 -40
  11. package/src/io/export/serialize-main-document.ts +55 -89
  12. package/src/io/export/serialize-numbering.ts +104 -4
  13. package/src/io/export/serialize-runtime-revisions.ts +196 -2
  14. package/src/io/export/split-story-blocks-for-runtime-revisions.ts +252 -0
  15. package/src/io/export/table-properties-xml.ts +318 -0
  16. package/src/io/normalize/normalize-text.ts +34 -3
  17. package/src/io/ooxml/parse-comments.ts +6 -0
  18. package/src/io/ooxml/parse-footnotes.ts +69 -13
  19. package/src/io/ooxml/parse-headers-footers.ts +54 -11
  20. package/src/io/ooxml/parse-main-document.ts +112 -42
  21. package/src/io/ooxml/parse-numbering.ts +341 -26
  22. package/src/io/ooxml/parse-revisions.ts +118 -4
  23. package/src/io/ooxml/parse-styles.ts +176 -0
  24. package/src/io/ooxml/parse-tables.ts +34 -25
  25. package/src/io/ooxml/revision-boundaries.ts +127 -3
  26. package/src/io/ooxml/workflow-payload.ts +544 -0
  27. package/src/model/canonical-document.ts +91 -1
  28. package/src/model/snapshot.ts +112 -1
  29. package/src/preservation/store.ts +73 -3
  30. package/src/review/store/comment-store.ts +19 -1
  31. package/src/review/store/revision-actions.ts +29 -0
  32. package/src/review/store/revision-store.ts +12 -1
  33. package/src/review/store/revision-types.ts +11 -0
  34. package/src/runtime/context-analytics.ts +824 -0
  35. package/src/runtime/document-locations.ts +521 -0
  36. package/src/runtime/document-navigation.ts +14 -1
  37. package/src/runtime/document-outline.ts +440 -0
  38. package/src/runtime/document-runtime.ts +941 -45
  39. package/src/runtime/event-refresh-hints.ts +137 -0
  40. package/src/runtime/numbering-prefix.ts +67 -39
  41. package/src/runtime/page-layout-estimation.ts +100 -7
  42. package/src/runtime/resolved-numbering-geometry.ts +293 -0
  43. package/src/runtime/session-capabilities.ts +2 -2
  44. package/src/runtime/suggestions-snapshot.ts +137 -0
  45. package/src/runtime/surface-projection.ts +223 -27
  46. package/src/runtime/table-style-resolver.ts +409 -0
  47. package/src/runtime/view-state.ts +17 -1
  48. package/src/runtime/workflow-markup.ts +54 -14
  49. package/src/ui/WordReviewEditor.tsx +1269 -87
  50. package/src/ui/editor-command-bag.ts +7 -0
  51. package/src/ui/editor-runtime-boundary.ts +111 -10
  52. package/src/ui/editor-shell-view.tsx +17 -15
  53. package/src/ui/editor-surface-controller.tsx +5 -0
  54. package/src/ui/headless/selection-tool-context.ts +19 -0
  55. package/src/ui/headless/selection-tool-resolver.ts +752 -0
  56. package/src/ui/headless/selection-tool-types.ts +129 -0
  57. package/src/ui/headless/selection-toolbar-model.ts +10 -33
  58. package/src/ui/runtime-shortcut-dispatch.ts +365 -0
  59. package/src/ui-tailwind/chrome/chrome-preset-model.ts +107 -0
  60. package/src/ui-tailwind/chrome/chrome-preset-toolbar.tsx +15 -0
  61. package/src/ui-tailwind/chrome/review-queue-bar.tsx +97 -0
  62. package/src/ui-tailwind/chrome/tw-context-analytics-summary.tsx +122 -0
  63. package/src/ui-tailwind/chrome/tw-image-context-toolbar.tsx +1 -9
  64. package/src/ui-tailwind/chrome/tw-object-context-toolbar.tsx +1 -5
  65. package/src/ui-tailwind/chrome/tw-page-ruler.tsx +8 -29
  66. package/src/ui-tailwind/chrome/tw-selection-tool-blocked.tsx +23 -0
  67. package/src/ui-tailwind/chrome/tw-selection-tool-comment.tsx +35 -0
  68. package/src/ui-tailwind/chrome/tw-selection-tool-formatting.tsx +37 -0
  69. package/src/ui-tailwind/chrome/tw-selection-tool-host.tsx +298 -0
  70. package/src/ui-tailwind/chrome/tw-selection-tool-structure.tsx +116 -0
  71. package/src/ui-tailwind/chrome/tw-selection-tool-suggestion.tsx +29 -0
  72. package/src/ui-tailwind/chrome/tw-selection-tool-workflow.tsx +27 -0
  73. package/src/ui-tailwind/chrome/tw-selection-toolbar.tsx +3 -3
  74. package/src/ui-tailwind/chrome/tw-suggestion-card.tsx +3 -3
  75. package/src/ui-tailwind/chrome/tw-table-context-toolbar.tsx +86 -14
  76. package/src/ui-tailwind/editor-surface/pm-command-bridge.ts +57 -52
  77. package/src/ui-tailwind/editor-surface/pm-decorations.ts +36 -52
  78. package/src/ui-tailwind/editor-surface/pm-schema.ts +56 -5
  79. package/src/ui-tailwind/editor-surface/pm-state-from-snapshot.ts +87 -24
  80. package/src/ui-tailwind/editor-surface/surface-build-keys.ts +4 -0
  81. package/src/ui-tailwind/editor-surface/tw-prosemirror-surface.tsx +135 -32
  82. package/src/ui-tailwind/editor-surface/tw-table-node-view.tsx +74 -7
  83. package/src/ui-tailwind/review/tw-comment-sidebar.tsx +17 -17
  84. package/src/ui-tailwind/review/tw-review-rail.tsx +19 -17
  85. package/src/ui-tailwind/review/tw-revision-sidebar.tsx +10 -10
  86. package/src/ui-tailwind/status/tw-status-bar.tsx +10 -6
  87. package/src/ui-tailwind/theme/editor-theme.css +58 -40
  88. package/src/ui-tailwind/toolbar/tw-toolbar-icon-button.tsx +4 -4
  89. package/src/ui-tailwind/toolbar/tw-toolbar.tsx +250 -181
  90. package/src/ui-tailwind/tw-review-workspace.tsx +323 -280
  91. package/src/validation/compatibility-engine.ts +246 -2
  92. package/src/validation/docx-comment-proof.ts +24 -11
@@ -0,0 +1,293 @@
1
+ import type {
2
+ NumberingCatalog,
3
+ NumberingLevelDefinition,
4
+ NumberingLevelOverrideDefinition,
5
+ NumberingLevelParagraphGeometry,
6
+ ParagraphIndentation,
7
+ ParagraphNode,
8
+ ParagraphSpacing,
9
+ TabStop,
10
+ } from "../model/canonical-document.ts";
11
+
12
+ export const DEFAULT_NUMBERING_START_AT = 1;
13
+
14
+ export interface ResolvedNumberingGeometry {
15
+ markerJustification?: NumberingLevelParagraphGeometry["justification"];
16
+ spacing?: ParagraphSpacing;
17
+ indentation?: ParagraphIndentation;
18
+ tabStops?: TabStop[];
19
+ markerLane?: {
20
+ start: number;
21
+ width: number;
22
+ textStart: number;
23
+ };
24
+ textColumn?: {
25
+ start: number;
26
+ right?: number;
27
+ firstLine?: number;
28
+ hanging?: number;
29
+ };
30
+ }
31
+
32
+ export interface ResolvedNumberingDefinitionSet {
33
+ instance: NumberingCatalog["instances"][string];
34
+ abstractDefinition: NumberingCatalog["abstractDefinitions"][string];
35
+ effectiveLevels: Map<number, NumberingLevelDefinition>;
36
+ effectiveLevel: NumberingLevelDefinition;
37
+ geometry: ResolvedNumberingGeometry;
38
+ }
39
+
40
+ export function resolveNumberingDefinitionSet(
41
+ catalog: NumberingCatalog,
42
+ numbering: ParagraphNode["numbering"] | undefined,
43
+ paragraph?: Pick<ParagraphNode, "spacing" | "indentation" | "tabStops">,
44
+ ): ResolvedNumberingDefinitionSet | null {
45
+ if (!numbering) {
46
+ return null;
47
+ }
48
+
49
+ const instance = catalog.instances[numbering.numberingInstanceId];
50
+ if (!instance) {
51
+ return null;
52
+ }
53
+
54
+ const abstractDefinition = catalog.abstractDefinitions[instance.abstractNumberingId];
55
+ if (!abstractDefinition) {
56
+ return null;
57
+ }
58
+
59
+ const effectiveLevels = buildEffectiveLevelDefinitions(abstractDefinition.levels, instance.overrides);
60
+ const effectiveLevel = effectiveLevels.get(numbering.level);
61
+ if (!effectiveLevel) {
62
+ return null;
63
+ }
64
+
65
+ return {
66
+ instance,
67
+ abstractDefinition,
68
+ effectiveLevels,
69
+ effectiveLevel,
70
+ geometry: resolveNumberingGeometry(effectiveLevel.paragraphGeometry, paragraph),
71
+ };
72
+ }
73
+
74
+ function buildEffectiveLevelDefinitions(
75
+ abstractLevels: readonly NumberingLevelDefinition[],
76
+ overrides: NumberingCatalog["instances"][string]["overrides"],
77
+ ): Map<number, NumberingLevelDefinition> {
78
+ const effectiveLevels = new Map<number, NumberingLevelDefinition>();
79
+
80
+ for (const level of abstractLevels) {
81
+ effectiveLevels.set(level.level, withDefaultStartAt(level));
82
+ }
83
+
84
+ for (const override of overrides) {
85
+ const base = effectiveLevels.get(override.level);
86
+ const merged = mergeLevelDefinition(base, override.levelDefinition, override.startAt, override.level);
87
+ if (merged) {
88
+ effectiveLevels.set(override.level, merged);
89
+ }
90
+ }
91
+
92
+ return effectiveLevels;
93
+ }
94
+
95
+ function mergeLevelDefinition(
96
+ base: NumberingLevelDefinition | undefined,
97
+ override: NumberingLevelOverrideDefinition | undefined,
98
+ startOverride: number | undefined,
99
+ fallbackLevel: number,
100
+ ): NumberingLevelDefinition | null {
101
+ if (!base && !override) {
102
+ return null;
103
+ }
104
+
105
+ const level = base?.level ?? override?.level ?? fallbackLevel;
106
+ const format = override?.format ?? base?.format ?? "decimal";
107
+ const text = override?.text ?? base?.text ?? `%${level + 1}.`;
108
+ const startAt = startOverride ?? override?.startAt ?? base?.startAt ?? DEFAULT_NUMBERING_START_AT;
109
+ const paragraphGeometry = mergeLevelParagraphGeometry(
110
+ base?.paragraphGeometry,
111
+ override?.paragraphGeometry,
112
+ );
113
+
114
+ return {
115
+ level,
116
+ format,
117
+ text,
118
+ startAt,
119
+ ...(override?.paragraphStyleId ?? base?.paragraphStyleId
120
+ ? { paragraphStyleId: override?.paragraphStyleId ?? base?.paragraphStyleId }
121
+ : {}),
122
+ ...((override?.isLegalNumbering ?? base?.isLegalNumbering) === true
123
+ ? { isLegalNumbering: true }
124
+ : {}),
125
+ ...(override?.suffix ?? base?.suffix ? { suffix: override?.suffix ?? base?.suffix } : {}),
126
+ ...(paragraphGeometry ? { paragraphGeometry } : {}),
127
+ };
128
+ }
129
+
130
+ function withDefaultStartAt(level: NumberingLevelDefinition): NumberingLevelDefinition {
131
+ return {
132
+ ...level,
133
+ startAt: level.startAt ?? DEFAULT_NUMBERING_START_AT,
134
+ ...(level.paragraphGeometry
135
+ ? { paragraphGeometry: cloneLevelParagraphGeometry(level.paragraphGeometry) }
136
+ : {}),
137
+ };
138
+ }
139
+
140
+ function cloneLevelParagraphGeometry(
141
+ geometry: NumberingLevelParagraphGeometry,
142
+ ): NumberingLevelParagraphGeometry {
143
+ return {
144
+ ...(geometry.justification ? { justification: geometry.justification } : {}),
145
+ ...(geometry.spacing ? { spacing: { ...geometry.spacing } } : {}),
146
+ ...(geometry.indentation ? { indentation: { ...geometry.indentation } } : {}),
147
+ ...(geometry.tabStops && geometry.tabStops.length > 0
148
+ ? { tabStops: cloneTabStops(geometry.tabStops) }
149
+ : {}),
150
+ };
151
+ }
152
+
153
+ function mergeLevelParagraphGeometry(
154
+ base: NumberingLevelParagraphGeometry | undefined,
155
+ override: NumberingLevelParagraphGeometry | undefined,
156
+ ): NumberingLevelParagraphGeometry | undefined {
157
+ if (!base && !override) {
158
+ return undefined;
159
+ }
160
+
161
+ const spacing = mergeParagraphSpacing(base?.spacing, override?.spacing);
162
+ const indentation = mergeParagraphIndentation(base?.indentation, override?.indentation);
163
+ const tabStops = override?.tabStops?.length
164
+ ? cloneTabStops(override.tabStops)
165
+ : base?.tabStops?.length
166
+ ? cloneTabStops(base.tabStops)
167
+ : undefined;
168
+
169
+ return {
170
+ ...(override?.justification ?? base?.justification
171
+ ? { justification: override?.justification ?? base?.justification }
172
+ : {}),
173
+ ...(spacing ? { spacing } : {}),
174
+ ...(indentation ? { indentation } : {}),
175
+ ...(tabStops && tabStops.length > 0 ? { tabStops } : {}),
176
+ };
177
+ }
178
+
179
+ function resolveNumberingGeometry(
180
+ levelGeometry: NumberingLevelParagraphGeometry | undefined,
181
+ paragraph: Pick<ParagraphNode, "spacing" | "indentation" | "tabStops"> | undefined,
182
+ ): ResolvedNumberingGeometry {
183
+ const spacing = mergeParagraphSpacing(levelGeometry?.spacing, paragraph?.spacing);
184
+ const indentation = mergeParagraphIndentation(levelGeometry?.indentation, paragraph?.indentation);
185
+ const tabStops = paragraph?.tabStops?.length
186
+ ? cloneTabStops(paragraph.tabStops)
187
+ : levelGeometry?.tabStops?.length
188
+ ? cloneTabStops(levelGeometry.tabStops)
189
+ : undefined;
190
+ const markerLane = deriveMarkerLane(indentation);
191
+ const textColumn = deriveTextColumn(indentation);
192
+
193
+ return {
194
+ ...(levelGeometry?.justification ? { markerJustification: levelGeometry.justification } : {}),
195
+ ...(spacing ? { spacing } : {}),
196
+ ...(indentation ? { indentation } : {}),
197
+ ...(tabStops && tabStops.length > 0 ? { tabStops } : {}),
198
+ ...(markerLane ? { markerLane } : {}),
199
+ ...(textColumn ? { textColumn } : {}),
200
+ };
201
+ }
202
+
203
+ function mergeParagraphSpacing(
204
+ base: ParagraphSpacing | undefined,
205
+ override: ParagraphSpacing | undefined,
206
+ ): ParagraphSpacing | undefined {
207
+ if (!base && !override) {
208
+ return undefined;
209
+ }
210
+
211
+ const spacing: ParagraphSpacing = {
212
+ ...(base ?? {}),
213
+ ...(override ?? {}),
214
+ };
215
+
216
+ return Object.keys(spacing).length > 0 ? spacing : undefined;
217
+ }
218
+
219
+ function mergeParagraphIndentation(
220
+ base: ParagraphIndentation | undefined,
221
+ override: ParagraphIndentation | undefined,
222
+ ): ParagraphIndentation | undefined {
223
+ if (!base && !override) {
224
+ return undefined;
225
+ }
226
+
227
+ const indentation: ParagraphIndentation = {
228
+ ...(base ?? {}),
229
+ ...(override ?? {}),
230
+ };
231
+
232
+ return Object.keys(indentation).length > 0 ? indentation : undefined;
233
+ }
234
+
235
+ function cloneTabStops(tabStops: readonly TabStop[]): TabStop[] {
236
+ return tabStops.map((tabStop) => ({ ...tabStop }));
237
+ }
238
+
239
+ function deriveMarkerLane(
240
+ indentation: ParagraphIndentation | undefined,
241
+ ): ResolvedNumberingGeometry["markerLane"] | undefined {
242
+ if (!indentation) {
243
+ return undefined;
244
+ }
245
+
246
+ const width = resolveHangingWidth(indentation);
247
+ if (width === undefined || width <= 0) {
248
+ return undefined;
249
+ }
250
+
251
+ const textStart = indentation.left ?? 0;
252
+ return {
253
+ start: textStart - width,
254
+ width,
255
+ textStart,
256
+ };
257
+ }
258
+
259
+ function deriveTextColumn(
260
+ indentation: ParagraphIndentation | undefined,
261
+ ): ResolvedNumberingGeometry["textColumn"] | undefined {
262
+ if (!indentation) {
263
+ return undefined;
264
+ }
265
+
266
+ const hanging = resolveHangingWidth(indentation);
267
+ const hasGeometry =
268
+ indentation.left !== undefined ||
269
+ indentation.right !== undefined ||
270
+ indentation.firstLine !== undefined ||
271
+ hanging !== undefined;
272
+
273
+ if (!hasGeometry) {
274
+ return undefined;
275
+ }
276
+
277
+ return {
278
+ start: indentation.left ?? 0,
279
+ ...(indentation.right !== undefined ? { right: indentation.right } : {}),
280
+ ...(indentation.firstLine !== undefined ? { firstLine: indentation.firstLine } : {}),
281
+ ...(hanging !== undefined ? { hanging } : {}),
282
+ };
283
+ }
284
+
285
+ function resolveHangingWidth(indentation: ParagraphIndentation): number | undefined {
286
+ if (indentation.hanging !== undefined) {
287
+ return indentation.hanging;
288
+ }
289
+ if (indentation.firstLine !== undefined && indentation.firstLine < 0) {
290
+ return Math.abs(indentation.firstLine);
291
+ }
292
+ return undefined;
293
+ }
@@ -140,10 +140,10 @@ export function deriveCapabilities(
140
140
  // stays reachable in both modes).
141
141
  const hasTrustConcerns = exportBlocked || preserveOnlyCount > 0 || unsupportedFatalCount > 0
142
142
  || snapshot.warnings.length > 0 || hasFatalError;
143
- const reviewRailVisible = workflowOverlayPresent
143
+ const reviewRailVisible = isReady && (workflowOverlayPresent
144
144
  ? false
145
145
  : mode === "review" || mode === "read-only-diagnostics"
146
- || (mode === "editing" && hasTrustConcerns);
146
+ || (mode === "editing" && hasTrustConcerns));
147
147
 
148
148
  const healthIssueCount = preserveOnlyCount + unsupportedFatalCount + snapshot.warnings.length;
149
149
 
@@ -0,0 +1,137 @@
1
+ import type {
2
+ SuggestionEntrySnapshot,
3
+ SuggestionsSnapshot,
4
+ TrackedChangeEntrySnapshot,
5
+ TrackedChangesSnapshot,
6
+ } from "../api/public-types";
7
+
8
+ function compareSuggestions(
9
+ left: SuggestionEntrySnapshot,
10
+ right: SuggestionEntrySnapshot,
11
+ ): number {
12
+ return (
13
+ Date.parse(right.createdAt) - Date.parse(left.createdAt) ||
14
+ left.suggestionId.localeCompare(right.suggestionId)
15
+ );
16
+ }
17
+
18
+ function toSemanticKind(
19
+ revision: TrackedChangeEntrySnapshot,
20
+ ): SuggestionEntrySnapshot["kind"] {
21
+ if (revision.semanticKind) {
22
+ return revision.semanticKind;
23
+ }
24
+ switch (revision.kind) {
25
+ case "insertion":
26
+ return "insertion";
27
+ case "deletion":
28
+ return "deletion";
29
+ case "formatting":
30
+ return "formatting-change";
31
+ case "property-change":
32
+ return "paragraph-property-change";
33
+ case "move":
34
+ return "structural-change";
35
+ }
36
+ }
37
+
38
+ function pickPrimaryRevision(
39
+ revisions: TrackedChangeEntrySnapshot[],
40
+ ): TrackedChangeEntrySnapshot {
41
+ return (
42
+ revisions.find((revision) => toSemanticKind(revision) === "replacement" && revision.kind === "insertion") ??
43
+ revisions.find((revision) => revision.kind === "insertion") ??
44
+ revisions[0]!
45
+ );
46
+ }
47
+
48
+ function summarizeStatus(
49
+ revisions: TrackedChangeEntrySnapshot[],
50
+ ): SuggestionEntrySnapshot["status"] {
51
+ if (revisions.every((revision) => revision.status === "accepted")) {
52
+ return "accepted";
53
+ }
54
+ if (revisions.every((revision) => revision.status === "rejected")) {
55
+ return "rejected";
56
+ }
57
+ if (revisions.every((revision) => revision.status === "detached")) {
58
+ return "detached";
59
+ }
60
+ return "active";
61
+ }
62
+
63
+ function summarizeActionability(
64
+ revisions: TrackedChangeEntrySnapshot[],
65
+ ): SuggestionEntrySnapshot["actionability"] {
66
+ return revisions.every((revision) => revision.actionability === "preserve-only")
67
+ ? "preserve-only"
68
+ : "actionable";
69
+ }
70
+
71
+ export function createSuggestionsSnapshot(
72
+ trackedChanges: TrackedChangesSnapshot,
73
+ ): SuggestionsSnapshot {
74
+ const groups = new Map<string, TrackedChangeEntrySnapshot[]>();
75
+
76
+ for (const revision of trackedChanges.revisions) {
77
+ const suggestionId = revision.suggestionId ?? revision.revisionId;
78
+ const group = groups.get(suggestionId);
79
+ if (group) {
80
+ group.push(revision);
81
+ } else {
82
+ groups.set(suggestionId, [revision]);
83
+ }
84
+ }
85
+
86
+ const suggestions = Array.from(groups.entries())
87
+ .map(([suggestionId, revisions]): SuggestionEntrySnapshot => {
88
+ const primary = pickPrimaryRevision(revisions);
89
+ const kind = toSemanticKind(primary);
90
+ const status = summarizeStatus(revisions);
91
+ const actionability = summarizeActionability(revisions);
92
+ return {
93
+ suggestionId,
94
+ kind,
95
+ source: primary.source ?? "runtime",
96
+ status,
97
+ actionability,
98
+ storyTarget: primary.storyTarget ?? { kind: "main" },
99
+ anchor: primary.anchor,
100
+ anchorLabel: primary.anchorLabel,
101
+ createdAt: primary.createdAt,
102
+ authorId: primary.authorId,
103
+ changeIds: revisions.map((revision) => revision.revisionId),
104
+ editable: status === "active" && actionability === "actionable",
105
+ canAccept: revisions.some((revision) => revision.canAccept),
106
+ canReject: revisions.some((revision) => revision.canReject),
107
+ isReplacement: kind === "replacement",
108
+ preserveOnlyReason: primary.preserveOnlyReason,
109
+ excerpt: primary.excerpt,
110
+ detail: primary.detail,
111
+ };
112
+ })
113
+ .sort(compareSuggestions);
114
+
115
+ return {
116
+ totalCount: suggestions.length,
117
+ actionableSuggestionIds: suggestions
118
+ .filter((suggestion) => suggestion.actionability === "actionable")
119
+ .map((suggestion) => suggestion.suggestionId),
120
+ preserveOnlySuggestionIds: suggestions
121
+ .filter((suggestion) => suggestion.actionability === "preserve-only")
122
+ .map((suggestion) => suggestion.suggestionId),
123
+ activeSuggestionIds: suggestions
124
+ .filter((suggestion) => suggestion.status === "active")
125
+ .map((suggestion) => suggestion.suggestionId),
126
+ acceptedSuggestionIds: suggestions
127
+ .filter((suggestion) => suggestion.status === "accepted")
128
+ .map((suggestion) => suggestion.suggestionId),
129
+ rejectedSuggestionIds: suggestions
130
+ .filter((suggestion) => suggestion.status === "rejected")
131
+ .map((suggestion) => suggestion.suggestionId),
132
+ detachedSuggestionIds: suggestions
133
+ .filter((suggestion) => suggestion.status === "detached")
134
+ .map((suggestion) => suggestion.suggestionId),
135
+ suggestions,
136
+ };
137
+ }