@beyondwork/docx-react-component 1.0.1 → 1.0.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 (172) hide show
  1. package/README.md +44 -104
  2. package/package.json +76 -46
  3. package/src/README.md +85 -0
  4. package/src/api/README.md +22 -0
  5. package/src/api/public-types.ts +525 -0
  6. package/src/compare/diff-engine.ts +530 -0
  7. package/src/compare/export-redlines.ts +162 -0
  8. package/src/compare/snapshot.ts +37 -0
  9. package/src/component-inventory.md +99 -0
  10. package/src/core/README.md +10 -0
  11. package/src/core/commands/README.md +3 -0
  12. package/src/core/commands/formatting-commands.ts +161 -0
  13. package/src/core/commands/image-commands.ts +144 -0
  14. package/src/core/commands/index.ts +1013 -0
  15. package/src/core/commands/list-commands.ts +370 -0
  16. package/src/core/commands/review-commands.ts +108 -0
  17. package/src/core/commands/text-commands.ts +119 -0
  18. package/src/core/schema/README.md +3 -0
  19. package/src/core/schema/text-schema.ts +512 -0
  20. package/src/core/selection/README.md +3 -0
  21. package/src/core/selection/mapping.ts +238 -0
  22. package/src/core/selection/review-anchors.ts +94 -0
  23. package/src/core/state/README.md +3 -0
  24. package/src/core/state/editor-state.ts +580 -0
  25. package/src/core/state/text-transaction.ts +276 -0
  26. package/src/formats/xlsx/io/parse-shared-strings.ts +41 -0
  27. package/src/formats/xlsx/io/parse-sheet.ts +289 -0
  28. package/src/formats/xlsx/io/parse-styles.ts +57 -0
  29. package/src/formats/xlsx/io/parse-workbook.ts +75 -0
  30. package/src/formats/xlsx/io/xlsx-session.ts +306 -0
  31. package/src/formats/xlsx/model/cell.ts +189 -0
  32. package/src/formats/xlsx/model/sheet.ts +244 -0
  33. package/src/formats/xlsx/model/styles.ts +118 -0
  34. package/src/formats/xlsx/model/workbook.ts +449 -0
  35. package/src/index.ts +45 -0
  36. package/src/io/README.md +10 -0
  37. package/src/io/docx-session.ts +1763 -0
  38. package/src/io/export/README.md +3 -0
  39. package/src/io/export/export-session.ts +165 -0
  40. package/src/io/export/minimal-docx.ts +115 -0
  41. package/src/io/export/reattach-preserved-parts.ts +54 -0
  42. package/src/io/export/serialize-comments.ts +876 -0
  43. package/src/io/export/serialize-footnotes.ts +217 -0
  44. package/src/io/export/serialize-headers-footers.ts +200 -0
  45. package/src/io/export/serialize-main-document.ts +982 -0
  46. package/src/io/export/serialize-numbering.ts +97 -0
  47. package/src/io/export/serialize-revisions.ts +389 -0
  48. package/src/io/export/serialize-runtime-revisions.ts +265 -0
  49. package/src/io/export/serialize-tables.ts +147 -0
  50. package/src/io/export/split-review-boundaries.ts +194 -0
  51. package/src/io/normalize/README.md +3 -0
  52. package/src/io/normalize/normalize-text.ts +437 -0
  53. package/src/io/ooxml/README.md +3 -0
  54. package/src/io/ooxml/parse-comments.ts +779 -0
  55. package/src/io/ooxml/parse-complex-content.ts +287 -0
  56. package/src/io/ooxml/parse-fields.ts +438 -0
  57. package/src/io/ooxml/parse-footnotes.ts +403 -0
  58. package/src/io/ooxml/parse-headers-footers.ts +483 -0
  59. package/src/io/ooxml/parse-inline-media.ts +431 -0
  60. package/src/io/ooxml/parse-main-document.ts +1846 -0
  61. package/src/io/ooxml/parse-numbering.ts +425 -0
  62. package/src/io/ooxml/parse-revisions.ts +658 -0
  63. package/src/io/ooxml/parse-shapes.ts +271 -0
  64. package/src/io/ooxml/parse-tables.ts +568 -0
  65. package/src/io/ooxml/parse-theme.ts +314 -0
  66. package/src/io/ooxml/part-manifest.ts +136 -0
  67. package/src/io/ooxml/revision-boundaries.ts +351 -0
  68. package/src/io/opc/README.md +3 -0
  69. package/src/io/opc/corrupt-package.ts +166 -0
  70. package/src/io/opc/docx-package.ts +74 -0
  71. package/src/io/opc/package-reader.ts +320 -0
  72. package/src/io/opc/package-writer.ts +273 -0
  73. package/src/legal/bookmarks.ts +196 -0
  74. package/src/legal/cross-references.ts +356 -0
  75. package/src/legal/defined-terms.ts +203 -0
  76. package/src/model/README.md +3 -0
  77. package/src/model/canonical-document.ts +1911 -0
  78. package/src/model/cds-1.0.0.ts +196 -0
  79. package/src/model/snapshot.ts +393 -0
  80. package/src/preservation/README.md +3 -0
  81. package/src/preservation/markup-compatibility.ts +48 -0
  82. package/src/preservation/opaque-fragment-store.ts +89 -0
  83. package/src/preservation/opaque-region.ts +233 -0
  84. package/src/preservation/package-preservation.ts +120 -0
  85. package/src/preservation/preserved-part-manifest.ts +56 -0
  86. package/src/preservation/relationship-retention.ts +57 -0
  87. package/src/preservation/store.ts +185 -0
  88. package/src/review/README.md +16 -0
  89. package/src/review/store/README.md +3 -0
  90. package/src/review/store/comment-anchors.ts +70 -0
  91. package/src/review/store/comment-remapping.ts +154 -0
  92. package/src/review/store/comment-store.ts +331 -0
  93. package/src/review/store/comment-thread.ts +109 -0
  94. package/src/review/store/revision-actions.ts +394 -0
  95. package/src/review/store/revision-store.ts +303 -0
  96. package/src/review/store/revision-types.ts +168 -0
  97. package/src/review/store/runtime-comment-store.ts +43 -0
  98. package/src/runtime/README.md +3 -0
  99. package/src/runtime/ai-action-policy.ts +764 -0
  100. package/src/runtime/document-runtime.ts +967 -0
  101. package/src/runtime/read-only-diagnostics-runtime.ts +232 -0
  102. package/src/runtime/review-runtime.ts +44 -0
  103. package/src/runtime/revision-runtime.ts +107 -0
  104. package/src/runtime/session-capabilities.ts +138 -0
  105. package/src/runtime/surface-projection.ts +570 -0
  106. package/src/runtime/table-commands.ts +87 -0
  107. package/src/runtime/table-schema.ts +140 -0
  108. package/src/runtime/virtualized-rendering.ts +258 -0
  109. package/src/ui/README.md +30 -0
  110. package/src/ui/WordReviewEditor.tsx +1504 -0
  111. package/src/ui/comments/README.md +3 -0
  112. package/src/ui/compatibility/README.md +3 -0
  113. package/src/ui/editor-surface/README.md +3 -0
  114. package/src/ui/headless/comment-decoration-model.ts +124 -0
  115. package/src/ui/headless/revision-decoration-model.ts +128 -0
  116. package/src/ui/headless/selection-helpers.ts +34 -0
  117. package/src/ui/headless/use-editor-keyboard.ts +98 -0
  118. package/src/ui/review/README.md +3 -0
  119. package/src/ui/shared/revision-filters.ts +31 -0
  120. package/src/ui/status/README.md +3 -0
  121. package/src/ui/theme/README.md +3 -0
  122. package/src/ui/toolbar/README.md +3 -0
  123. package/src/ui-tailwind/chrome/tw-alert-banner.tsx +48 -0
  124. package/src/ui-tailwind/chrome/tw-selection-toolbar.tsx +44 -0
  125. package/src/ui-tailwind/chrome/tw-unsaved-modal.tsx +58 -0
  126. package/src/ui-tailwind/chrome/use-before-unload.ts +20 -0
  127. package/src/ui-tailwind/editor-surface/pm-command-bridge.ts +139 -0
  128. package/src/ui-tailwind/editor-surface/pm-decorations.ts +98 -0
  129. package/src/ui-tailwind/editor-surface/pm-position-map.ts +123 -0
  130. package/src/ui-tailwind/editor-surface/pm-schema.ts +452 -0
  131. package/src/ui-tailwind/editor-surface/pm-state-from-snapshot.ts +327 -0
  132. package/src/ui-tailwind/editor-surface/search-plugin.ts +157 -0
  133. package/src/ui-tailwind/editor-surface/tw-caret.tsx +12 -0
  134. package/src/ui-tailwind/editor-surface/tw-editor-surface.tsx +150 -0
  135. package/src/ui-tailwind/editor-surface/tw-inline-token.tsx +118 -0
  136. package/src/ui-tailwind/editor-surface/tw-opaque-block.tsx +52 -0
  137. package/src/ui-tailwind/editor-surface/tw-paragraph-block.tsx +151 -0
  138. package/src/ui-tailwind/editor-surface/tw-prosemirror-surface.tsx +215 -0
  139. package/src/ui-tailwind/editor-surface/tw-segment-view.tsx +111 -0
  140. package/src/ui-tailwind/editor-surface/tw-table-node-view.tsx +122 -0
  141. package/src/ui-tailwind/index.ts +61 -0
  142. package/src/ui-tailwind/review/tw-comment-sidebar.tsx +276 -0
  143. package/src/ui-tailwind/review/tw-health-panel.tsx +120 -0
  144. package/src/ui-tailwind/review/tw-review-rail.tsx +120 -0
  145. package/src/ui-tailwind/review/tw-revision-sidebar.tsx +164 -0
  146. package/src/ui-tailwind/status/tw-status-bar.tsx +58 -0
  147. package/src/ui-tailwind/theme/editor-theme.css +190 -0
  148. package/src/ui-tailwind/toolbar/tw-toolbar-icon-button.tsx +48 -0
  149. package/src/ui-tailwind/toolbar/tw-toolbar.tsx +231 -0
  150. package/src/ui-tailwind/tw-review-workspace.tsx +140 -0
  151. package/src/validation/README.md +3 -0
  152. package/src/validation/compatibility-engine.ts +317 -0
  153. package/src/validation/compatibility-report.ts +160 -0
  154. package/src/validation/diagnostics.ts +203 -0
  155. package/src/validation/import-diagnostics.ts +128 -0
  156. package/src/validation/low-priority-word-surfaces.ts +373 -0
  157. package/dist/chunk-32W6IVQE.js +0 -7725
  158. package/dist/chunk-32W6IVQE.js.map +0 -1
  159. package/dist/index.cjs +0 -23722
  160. package/dist/index.cjs.map +0 -1
  161. package/dist/index.d.cts +0 -7
  162. package/dist/index.d.ts +0 -7
  163. package/dist/index.js +0 -16011
  164. package/dist/index.js.map +0 -1
  165. package/dist/public-types-DqCURAz8.d.cts +0 -1152
  166. package/dist/public-types-DqCURAz8.d.ts +0 -1152
  167. package/dist/tailwind.cjs +0 -8295
  168. package/dist/tailwind.cjs.map +0 -1
  169. package/dist/tailwind.d.cts +0 -323
  170. package/dist/tailwind.d.ts +0 -323
  171. package/dist/tailwind.js +0 -553
  172. package/dist/tailwind.js.map +0 -1
@@ -0,0 +1,3 @@
1
+ # IO Export
2
+
3
+ OOXML serialization, part regeneration, and package writing helpers belong here.
@@ -0,0 +1,165 @@
1
+ import {
2
+ CONTENT_TYPES_PATH,
3
+ PACKAGE_RELATIONSHIPS_PATH,
4
+ OpcCompressionMethod,
5
+ OpcPackagePart,
6
+ OpcRelationship,
7
+ comparePartPaths,
8
+ getRelationshipsPartPath,
9
+ normalizePartPath,
10
+ } from "../ooxml/part-manifest.ts";
11
+ import type { OpcPackage } from "../opc/package-reader.ts";
12
+ import { writeOpcPackage } from "../opc/package-writer.ts";
13
+ import { reattachPreservedParts } from "./reattach-preserved-parts.ts";
14
+
15
+ export interface ExportPartReplacement {
16
+ path: string;
17
+ bytes: Uint8Array;
18
+ contentType: string;
19
+ relationships?: OpcRelationship[];
20
+ compression?: OpcCompressionMethod;
21
+ }
22
+
23
+ export interface ExportSessionSnapshot {
24
+ manifest: OpcPackage["manifest"];
25
+ parts: Map<string, OpcPackagePart>;
26
+ }
27
+
28
+ export class ExportSession {
29
+ private readonly ownedPaths: Set<string>;
30
+ private readonly workingParts: Map<string, OpcPackagePart>;
31
+ private readonly packageRelationships: OpcRelationship[];
32
+
33
+ public constructor(private readonly sourcePackage: OpcPackage, ownedOutputPaths: Iterable<string>) {
34
+ this.ownedPaths = new Set(
35
+ [...ownedOutputPaths].map((path) => {
36
+ const normalized = normalizePartPath(path);
37
+ if (normalized === CONTENT_TYPES_PATH || normalized.endsWith(".rels") || normalized === PACKAGE_RELATIONSHIPS_PATH) {
38
+ throw new Error(`ExportSession cannot directly own reserved OPC manifest path ${normalized}.`);
39
+ }
40
+
41
+ return normalized;
42
+ }),
43
+ );
44
+ this.workingParts = cloneParts(sourcePackage.parts);
45
+ this.packageRelationships = sourcePackage.manifest.packageRelationships.map(cloneRelationship);
46
+ }
47
+
48
+ public replaceOwnedPart(replacement: ExportPartReplacement): void {
49
+ const normalizedPath = normalizePartPath(replacement.path);
50
+ if (!this.ownedPaths.has(normalizedPath)) {
51
+ throw new Error(`Attempted to rewrite non-owned OPC path ${normalizedPath}.`);
52
+ }
53
+
54
+ const existing = this.workingParts.get(normalizedPath);
55
+ const nextPart: OpcPackagePart = {
56
+ path: normalizedPath,
57
+ surfaceKind: "content",
58
+ contentType: replacement.contentType,
59
+ relationshipsPartPath: getRelationshipsPartPath(normalizedPath),
60
+ relationships: (replacement.relationships ?? existing?.relationships ?? []).map(cloneRelationship),
61
+ compression: replacement.compression ?? existing?.compression ?? "deflate",
62
+ bytes: new Uint8Array(replacement.bytes),
63
+ crc32: 0,
64
+ };
65
+
66
+ this.workingParts.set(normalizedPath, nextPart);
67
+ }
68
+
69
+ public withOwnedPart(replacement: ExportPartReplacement): ExportSession {
70
+ this.replaceOwnedPart(replacement);
71
+ return this;
72
+ }
73
+
74
+ public toPackage(): OpcPackage {
75
+ reattachPreservedParts(
76
+ this.sourcePackage,
77
+ this.workingParts,
78
+ this.packageRelationships,
79
+ this.ownedPaths,
80
+ );
81
+ const parts = pruneReservedManifestParts(this.workingParts);
82
+ const manifest = buildManifest(parts, this.packageRelationships, this.sourcePackage.manifest.contentTypes);
83
+ return {
84
+ manifest,
85
+ parts,
86
+ sourceByteLength: this.sourcePackage.sourceByteLength,
87
+ };
88
+ }
89
+
90
+ public serialize(): Uint8Array {
91
+ return writeOpcPackage(this.toPackage());
92
+ }
93
+ }
94
+
95
+ export function createExportSession(
96
+ sourcePackage: OpcPackage,
97
+ ownedOutputPaths: Iterable<string>,
98
+ ): ExportSession {
99
+ return new ExportSession(sourcePackage, ownedOutputPaths);
100
+ }
101
+
102
+ function cloneParts(parts: Map<string, OpcPackagePart>): Map<string, OpcPackagePart> {
103
+ return new Map(
104
+ [...parts.entries()].map(([path, part]) => [
105
+ path,
106
+ {
107
+ ...part,
108
+ relationships: part.relationships.map(cloneRelationship),
109
+ bytes: new Uint8Array(part.bytes),
110
+ },
111
+ ]),
112
+ );
113
+ }
114
+
115
+ function pruneReservedManifestParts(parts: Map<string, OpcPackagePart>): Map<string, OpcPackagePart> {
116
+ return new Map(
117
+ [...parts.entries()].filter(([path]) => path !== CONTENT_TYPES_PATH && path !== PACKAGE_RELATIONSHIPS_PATH && !path.includes("/_rels/")),
118
+ );
119
+ }
120
+
121
+ function buildManifest(
122
+ parts: Map<string, OpcPackagePart>,
123
+ packageRelationships: OpcRelationship[],
124
+ contentTypesSeed: OpcPackage["manifest"]["contentTypes"],
125
+ ): OpcPackage["manifest"] {
126
+ const partsList = [...parts.values()].sort((left, right) => comparePartPaths(left.path, right.path));
127
+
128
+ const overrides = { ...contentTypesSeed.overrides };
129
+ for (const key of Object.keys(overrides)) {
130
+ if (!parts.has(normalizePartPath(key))) {
131
+ delete overrides[key];
132
+ }
133
+ }
134
+
135
+ for (const part of partsList) {
136
+ if (!part.contentType) {
137
+ throw new Error(`Cannot export part without content type: ${part.path}`);
138
+ }
139
+
140
+ overrides[part.path] = part.contentType;
141
+ }
142
+
143
+ return {
144
+ contentTypes: {
145
+ defaults: { ...contentTypesSeed.defaults },
146
+ overrides,
147
+ },
148
+ packageRelationships: packageRelationships.map(cloneRelationship),
149
+ parts: partsList.map((part) => ({
150
+ path: part.path,
151
+ surfaceKind: part.surfaceKind,
152
+ contentType: part.contentType,
153
+ relationshipsPartPath: part.relationshipsPartPath,
154
+ relationships: part.relationships.map(cloneRelationship),
155
+ compression: part.compression,
156
+ byteLength: part.bytes.byteLength,
157
+ })),
158
+ };
159
+ }
160
+
161
+ function cloneRelationship(relationship: OpcRelationship): OpcRelationship {
162
+ return { ...relationship };
163
+ }
164
+
165
+ export { buildManifest };
@@ -0,0 +1,115 @@
1
+ import type { PersistedEditorSnapshot } from "../../api/public-types";
2
+ import {
3
+ DOCX_DOCUMENT_CONTENT_TYPE,
4
+ DOCX_DOCUMENT_RELATIONSHIP_TYPE,
5
+ writeDocxPackage,
6
+ } from "../opc/docx-package.ts";
7
+
8
+ export function exportSnapshotToMinimalDocx(
9
+ snapshot: PersistedEditorSnapshot,
10
+ ): Uint8Array {
11
+ const paragraphs = collectParagraphTexts(snapshot.canonicalDocument.content);
12
+ const documentXml = buildDocumentXml(paragraphs);
13
+
14
+ return writeDocxPackage({
15
+ documentXml,
16
+ documentContentType: DOCX_DOCUMENT_CONTENT_TYPE,
17
+ packageRelationshipType: DOCX_DOCUMENT_RELATIONSHIP_TYPE,
18
+ });
19
+ }
20
+
21
+ function collectParagraphTexts(content: unknown): string[] {
22
+ const paragraphs = collectParagraphs(content).map((paragraph) =>
23
+ paragraph.length > 0 ? paragraph : "",
24
+ );
25
+ return paragraphs.length > 0 ? paragraphs : [""];
26
+ }
27
+
28
+ function collectParagraphs(value: unknown): string[] {
29
+ if (Array.isArray(value)) {
30
+ return value.flatMap((entry) => collectParagraphs(entry));
31
+ }
32
+
33
+ if (typeof value === "string") {
34
+ return [value];
35
+ }
36
+
37
+ if (!isRecord(value)) {
38
+ return [];
39
+ }
40
+
41
+ if (value.type === "doc" && Array.isArray(value.children)) {
42
+ return value.children.flatMap((entry) => collectParagraphs(entry));
43
+ }
44
+
45
+ if (value.type === "paragraph") {
46
+ return [collectInlineText(value.children)];
47
+ }
48
+
49
+ return [];
50
+ }
51
+
52
+ function collectInlineText(value: unknown): string {
53
+ if (Array.isArray(value)) {
54
+ return value.map((entry) => collectInlineText(entry)).join("");
55
+ }
56
+
57
+ if (typeof value === "string") {
58
+ return value;
59
+ }
60
+
61
+ if (!isRecord(value)) {
62
+ return "";
63
+ }
64
+
65
+ switch (value.type) {
66
+ case "text":
67
+ return typeof value.text === "string" ? value.text : "";
68
+ case "tab":
69
+ return "\t";
70
+ case "hard_break":
71
+ return "\n";
72
+ case "hyperlink":
73
+ return collectInlineText(value.children);
74
+ case "image":
75
+ return typeof value.altText === "string" ? value.altText : "[Image]";
76
+ case "opaque_inline":
77
+ return "[Preserved content]";
78
+ default:
79
+ return Array.isArray(value.children) ? collectInlineText(value.children) : "";
80
+ }
81
+ }
82
+
83
+ function buildDocumentXml(paragraphs: string[]): string {
84
+ const body = paragraphs
85
+ .map(
86
+ (paragraph) =>
87
+ ` <w:p><w:r><w:t xml:space="preserve">${escapeXml(
88
+ paragraph,
89
+ )}</w:t></w:r></w:p>`,
90
+ )
91
+ .join("\n");
92
+
93
+ return [
94
+ `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>`,
95
+ `<w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">`,
96
+ ` <w:body>`,
97
+ body,
98
+ ` <w:sectPr/>`,
99
+ ` </w:body>`,
100
+ `</w:document>`,
101
+ ].join("\n");
102
+ }
103
+
104
+ function escapeXml(value: string): string {
105
+ return value
106
+ .replace(/&/g, "&amp;")
107
+ .replace(/</g, "&lt;")
108
+ .replace(/>/g, "&gt;")
109
+ .replace(/"/g, "&quot;")
110
+ .replace(/'/g, "&apos;");
111
+ }
112
+
113
+ function isRecord(value: unknown): value is Record<string, any> {
114
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
115
+ }
@@ -0,0 +1,54 @@
1
+ import type { OpcPackage } from "../opc/package-reader.ts";
2
+ import type { OpcPackagePart, OpcRelationship } from "../ooxml/part-manifest.ts";
3
+
4
+ export function reattachPreservedParts(
5
+ sourcePackage: OpcPackage,
6
+ workingParts: Map<string, OpcPackagePart>,
7
+ packageRelationships: OpcRelationship[],
8
+ ownedPaths: ReadonlySet<string>,
9
+ ): void {
10
+ for (const [path, sourcePart] of sourcePackage.parts.entries()) {
11
+ if (ownedPaths.has(path) || sourcePart.surfaceKind !== "content") {
12
+ continue;
13
+ }
14
+
15
+ if (!workingParts.has(path)) {
16
+ workingParts.set(path, clonePart(sourcePart));
17
+ continue;
18
+ }
19
+
20
+ const currentPart = workingParts.get(path);
21
+ if (!currentPart) {
22
+ continue;
23
+ }
24
+
25
+ currentPart.contentType = sourcePart.contentType;
26
+ currentPart.relationships = sourcePart.relationships.map(cloneRelationship);
27
+ currentPart.relationshipsPartPath = sourcePart.relationshipsPartPath;
28
+ currentPart.compression = sourcePart.compression;
29
+ currentPart.bytes = new Uint8Array(sourcePart.bytes);
30
+ currentPart.crc32 = sourcePart.crc32;
31
+ }
32
+
33
+ const knownPackageRelationshipIds = new Set(packageRelationships.map((relationship) => relationship.id));
34
+ for (const relationship of sourcePackage.manifest.packageRelationships) {
35
+ if (knownPackageRelationshipIds.has(relationship.id)) {
36
+ continue;
37
+ }
38
+
39
+ packageRelationships.push(cloneRelationship(relationship));
40
+ knownPackageRelationshipIds.add(relationship.id);
41
+ }
42
+ }
43
+
44
+ function clonePart(part: OpcPackagePart): OpcPackagePart {
45
+ return {
46
+ ...part,
47
+ relationships: part.relationships.map(cloneRelationship),
48
+ bytes: new Uint8Array(part.bytes),
49
+ };
50
+ }
51
+
52
+ function cloneRelationship(relationship: OpcRelationship): OpcRelationship {
53
+ return { ...relationship };
54
+ }