@beyondwork/docx-react-component 1.0.2 → 1.0.3
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.
- package/package.json +18 -28
- package/src/io/opc/package-reader.ts +32 -27
- package/src/ui/WordReviewEditor.tsx +2 -0
package/package.json
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@beyondwork/docx-react-component",
|
|
3
3
|
"publisher": "beyondwork",
|
|
4
|
-
"version": "1.0.
|
|
4
|
+
"version": "1.0.3",
|
|
5
5
|
"description": "Embeddable React Word (docx) editor with review, comments, tracked changes, and round-trip OOXML fidelity.",
|
|
6
|
-
"packageManager": "pnpm@10.30.3",
|
|
7
6
|
"type": "module",
|
|
8
7
|
"files": [
|
|
9
8
|
"README.md",
|
|
@@ -33,23 +32,6 @@
|
|
|
33
32
|
"./package.json": "./package.json"
|
|
34
33
|
},
|
|
35
34
|
"types": "./src/index.ts",
|
|
36
|
-
"scripts": {
|
|
37
|
-
"build": "tsup",
|
|
38
|
-
"test": "bash scripts/run-workspace-tests.sh",
|
|
39
|
-
"test:repo": "pnpm exec tsx --test $(find test -type f \\( -name '*.test.ts' -o -name '*.test.tsx' \\) | sort)",
|
|
40
|
-
"test:harness": "pnpm --filter @docx-react-component/react-word-editor-harness test",
|
|
41
|
-
"lint:no-authored-js": "bash scripts/check-no-authored-js.sh",
|
|
42
|
-
"context7:api-check": "bash scripts/context7-export-env.sh run bash scripts/context7-api-check.sh",
|
|
43
|
-
"wave:doctor": "bash scripts/context7-export-env.sh run pnpm exec wave doctor --json",
|
|
44
|
-
"wave:dry-run": "bash scripts/context7-export-env.sh run pnpm exec wave launch --lane main --dry-run --no-dashboard",
|
|
45
|
-
"wave:launch": "bash scripts/context7-export-env.sh run pnpm exec wave launch --lane main",
|
|
46
|
-
"wave:launch:managed": "bash scripts/wave-launch.sh",
|
|
47
|
-
"wave:status": "bash scripts/wave-status.sh",
|
|
48
|
-
"wave:watch": "bash scripts/wave-watch.sh --follow",
|
|
49
|
-
"wave:dashboard:current": "bash scripts/wave-dashboard-attach.sh current",
|
|
50
|
-
"wave:dashboard:global": "bash scripts/wave-dashboard-attach.sh global",
|
|
51
|
-
"harness:dev": "pnpm --filter @docx-react-component/react-word-editor-harness dev"
|
|
52
|
-
},
|
|
53
35
|
"keywords": [
|
|
54
36
|
"docx",
|
|
55
37
|
"word",
|
|
@@ -71,6 +53,7 @@
|
|
|
71
53
|
"@radix-ui/react-toggle": "^1.1.10",
|
|
72
54
|
"@radix-ui/react-toggle-group": "^1.1.11",
|
|
73
55
|
"@radix-ui/react-tooltip": "^1.2.8",
|
|
56
|
+
"fflate": "^0.8.2",
|
|
74
57
|
"lucide-react": "^1.7.0"
|
|
75
58
|
},
|
|
76
59
|
"peerDependencies": {
|
|
@@ -101,14 +84,21 @@
|
|
|
101
84
|
"tsup": "^8.3.0",
|
|
102
85
|
"tsx": "^4.21.0"
|
|
103
86
|
},
|
|
104
|
-
"
|
|
105
|
-
"
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
"
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
87
|
+
"scripts": {
|
|
88
|
+
"build": "tsup",
|
|
89
|
+
"test": "bash scripts/run-workspace-tests.sh",
|
|
90
|
+
"test:repo": "pnpm exec tsx --test $(find test -type f \\( -name '*.test.ts' -o -name '*.test.tsx' \\) | sort)",
|
|
91
|
+
"test:harness": "pnpm --filter @docx-react-component/react-word-editor-harness test",
|
|
92
|
+
"lint:no-authored-js": "bash scripts/check-no-authored-js.sh",
|
|
93
|
+
"context7:api-check": "bash scripts/context7-export-env.sh run bash scripts/context7-api-check.sh",
|
|
94
|
+
"wave:doctor": "bash scripts/context7-export-env.sh run pnpm exec wave doctor --json",
|
|
95
|
+
"wave:dry-run": "bash scripts/context7-export-env.sh run pnpm exec wave launch --lane main --dry-run --no-dashboard",
|
|
96
|
+
"wave:launch": "bash scripts/context7-export-env.sh run pnpm exec wave launch --lane main",
|
|
97
|
+
"wave:launch:managed": "bash scripts/wave-launch.sh",
|
|
98
|
+
"wave:status": "bash scripts/wave-status.sh",
|
|
99
|
+
"wave:watch": "bash scripts/wave-watch.sh --follow",
|
|
100
|
+
"wave:dashboard:current": "bash scripts/wave-dashboard-attach.sh current",
|
|
101
|
+
"wave:dashboard:global": "bash scripts/wave-dashboard-attach.sh global",
|
|
102
|
+
"harness:dev": "pnpm --filter @docx-react-component/react-word-editor-harness dev"
|
|
113
103
|
}
|
|
114
104
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { inflateRawSync } from "
|
|
1
|
+
import { inflateRawSync } from "fflate";
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
4
|
CONTENT_TYPES_PATH,
|
|
@@ -35,8 +35,7 @@ const LOCAL_FILE_HEADER_SIGNATURE = 0x04034b50;
|
|
|
35
35
|
|
|
36
36
|
export function readOpcPackage(source: Uint8Array | ArrayBuffer): OpcPackage {
|
|
37
37
|
const bytes = source instanceof Uint8Array ? source : new Uint8Array(source);
|
|
38
|
-
const
|
|
39
|
-
const centralDirectory = readCentralDirectory(archive);
|
|
38
|
+
const centralDirectory = readCentralDirectory(bytes);
|
|
40
39
|
const parts = new Map<string, OpcPackagePart>();
|
|
41
40
|
|
|
42
41
|
for (const entry of centralDirectory) {
|
|
@@ -44,7 +43,7 @@ export function readOpcPackage(source: Uint8Array | ArrayBuffer): OpcPackage {
|
|
|
44
43
|
continue;
|
|
45
44
|
}
|
|
46
45
|
|
|
47
|
-
const storedBytes = readZipEntry(
|
|
46
|
+
const storedBytes = readZipEntry(bytes, entry);
|
|
48
47
|
const normalizedPath = normalizePartPath(entry.path);
|
|
49
48
|
const surfaceKind = getSurfaceKind(normalizedPath);
|
|
50
49
|
|
|
@@ -112,30 +111,31 @@ export function readOpcPackage(source: Uint8Array | ArrayBuffer): OpcPackage {
|
|
|
112
111
|
};
|
|
113
112
|
}
|
|
114
113
|
|
|
115
|
-
function readCentralDirectory(archive:
|
|
116
|
-
const
|
|
117
|
-
const
|
|
118
|
-
const
|
|
114
|
+
function readCentralDirectory(archive: Uint8Array): ZipCentralDirectoryEntry[] {
|
|
115
|
+
const view = new DataView(archive.buffer, archive.byteOffset, archive.byteLength);
|
|
116
|
+
const eocdOffset = findEndOfCentralDirectory(archive, view);
|
|
117
|
+
const entryCount = view.getUint16(eocdOffset + 10, true);
|
|
118
|
+
const centralDirectoryOffset = view.getUint32(eocdOffset + 16, true);
|
|
119
119
|
const entries: ZipCentralDirectoryEntry[] = [];
|
|
120
120
|
let offset = centralDirectoryOffset;
|
|
121
121
|
|
|
122
122
|
for (let index = 0; index < entryCount; index += 1) {
|
|
123
|
-
const signature =
|
|
123
|
+
const signature = view.getUint32(offset, true);
|
|
124
124
|
if (signature !== CENTRAL_DIRECTORY_SIGNATURE) {
|
|
125
125
|
throw new Error(`Invalid ZIP central directory signature at offset ${offset}.`);
|
|
126
126
|
}
|
|
127
127
|
|
|
128
|
-
const generalPurposeBitFlag =
|
|
129
|
-
const compressionMethod =
|
|
130
|
-
const crc32 =
|
|
131
|
-
const compressedSize =
|
|
132
|
-
const uncompressedSize =
|
|
133
|
-
const fileNameLength =
|
|
134
|
-
const extraFieldLength =
|
|
135
|
-
const fileCommentLength =
|
|
136
|
-
const localHeaderOffset =
|
|
128
|
+
const generalPurposeBitFlag = view.getUint16(offset + 8, true);
|
|
129
|
+
const compressionMethod = view.getUint16(offset + 10, true);
|
|
130
|
+
const crc32 = view.getUint32(offset + 16, true);
|
|
131
|
+
const compressedSize = view.getUint32(offset + 20, true);
|
|
132
|
+
const uncompressedSize = view.getUint32(offset + 24, true);
|
|
133
|
+
const fileNameLength = view.getUint16(offset + 28, true);
|
|
134
|
+
const extraFieldLength = view.getUint16(offset + 30, true);
|
|
135
|
+
const fileCommentLength = view.getUint16(offset + 32, true);
|
|
136
|
+
const localHeaderOffset = view.getUint32(offset + 42, true);
|
|
137
137
|
const fileNameOffset = offset + 46;
|
|
138
|
-
const fileName = archive.subarray(fileNameOffset, fileNameOffset + fileNameLength)
|
|
138
|
+
const fileName = decodeUtf8(archive.subarray(fileNameOffset, fileNameOffset + fileNameLength));
|
|
139
139
|
|
|
140
140
|
entries.push({
|
|
141
141
|
path: fileName,
|
|
@@ -153,13 +153,13 @@ function readCentralDirectory(archive: Buffer): ZipCentralDirectoryEntry[] {
|
|
|
153
153
|
return entries;
|
|
154
154
|
}
|
|
155
155
|
|
|
156
|
-
function findEndOfCentralDirectory(archive:
|
|
156
|
+
function findEndOfCentralDirectory(archive: Uint8Array, view: DataView): number {
|
|
157
157
|
const minimumEocdSize = 22;
|
|
158
158
|
const maximumCommentSize = 0xffff;
|
|
159
159
|
const searchStart = Math.max(0, archive.length - minimumEocdSize - maximumCommentSize);
|
|
160
160
|
|
|
161
161
|
for (let offset = archive.length - minimumEocdSize; offset >= searchStart; offset -= 1) {
|
|
162
|
-
if (
|
|
162
|
+
if (view.getUint32(offset, true) === EOCD_SIGNATURE) {
|
|
163
163
|
return offset;
|
|
164
164
|
}
|
|
165
165
|
}
|
|
@@ -167,15 +167,16 @@ function findEndOfCentralDirectory(archive: Buffer): number {
|
|
|
167
167
|
throw new Error("Invalid ZIP archive: end of central directory not found.");
|
|
168
168
|
}
|
|
169
169
|
|
|
170
|
-
function readZipEntry(archive:
|
|
170
|
+
function readZipEntry(archive: Uint8Array, entry: ZipCentralDirectoryEntry): Uint8Array {
|
|
171
|
+
const view = new DataView(archive.buffer, archive.byteOffset, archive.byteLength);
|
|
171
172
|
const headerOffset = entry.localHeaderOffset;
|
|
172
|
-
const signature =
|
|
173
|
+
const signature = view.getUint32(headerOffset, true);
|
|
173
174
|
if (signature !== LOCAL_FILE_HEADER_SIGNATURE) {
|
|
174
175
|
throw new Error(`Invalid ZIP local file header signature at offset ${headerOffset}.`);
|
|
175
176
|
}
|
|
176
177
|
|
|
177
|
-
const fileNameLength =
|
|
178
|
-
const extraFieldLength =
|
|
178
|
+
const fileNameLength = view.getUint16(headerOffset + 26, true);
|
|
179
|
+
const extraFieldLength = view.getUint16(headerOffset + 28, true);
|
|
179
180
|
const dataOffset = headerOffset + 30 + fileNameLength + extraFieldLength;
|
|
180
181
|
const compressed = archive.subarray(dataOffset, dataOffset + entry.compressedSize);
|
|
181
182
|
|
|
@@ -183,7 +184,7 @@ function readZipEntry(archive: Buffer, entry: ZipCentralDirectoryEntry): Uint8Ar
|
|
|
183
184
|
case "store":
|
|
184
185
|
return new Uint8Array(compressed);
|
|
185
186
|
case "deflate":
|
|
186
|
-
return
|
|
187
|
+
return inflateRawSync(compressed);
|
|
187
188
|
default:
|
|
188
189
|
throw new Error(`Unsupported ZIP compression for ${entry.path}.`);
|
|
189
190
|
}
|
|
@@ -213,7 +214,11 @@ function getSurfaceKind(path: string): OpcPartManifestEntry["surfaceKind"] {
|
|
|
213
214
|
}
|
|
214
215
|
|
|
215
216
|
function decodeXml(bytes: Uint8Array): string {
|
|
216
|
-
return
|
|
217
|
+
return new TextDecoder("utf-8").decode(bytes);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function decodeUtf8(bytes: Uint8Array): string {
|
|
221
|
+
return new TextDecoder("utf-8").decode(bytes);
|
|
217
222
|
}
|
|
218
223
|
|
|
219
224
|
function parseContentTypesXml(xml: string): OpcPackageManifest["contentTypes"] {
|
|
@@ -880,6 +880,8 @@ function findRegionFocusTarget(
|
|
|
880
880
|
if (regionId === "document" || regionId === "toolbar" || regionId === "review-rail" || regionId === "status") {
|
|
881
881
|
return region;
|
|
882
882
|
}
|
|
883
|
+
|
|
884
|
+
return null;
|
|
883
885
|
}
|
|
884
886
|
|
|
885
887
|
function isAccessibleRegionId(value: string | undefined): value is AccessibleRegionId {
|