@deckflow/deckuse-pptx 0.1.0 → 1.1.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.
- package/LICENSE +662 -0
- package/dist/.tsbuildinfo +1 -1
- package/dist/adapter.d.ts +70 -3
- package/dist/adapter.d.ts.map +1 -1
- package/dist/adapter.js +519 -86
- package/dist/adapter.js.map +1 -1
- package/dist/addressing.d.ts +45 -0
- package/dist/addressing.d.ts.map +1 -0
- package/dist/addressing.js +401 -0
- package/dist/addressing.js.map +1 -0
- package/dist/chart-classify.d.ts +10 -0
- package/dist/chart-classify.d.ts.map +1 -0
- package/dist/chart-classify.js +54 -0
- package/dist/chart-classify.js.map +1 -0
- package/dist/chart.d.ts +33 -0
- package/dist/chart.d.ts.map +1 -0
- package/dist/chart.js +492 -0
- package/dist/chart.js.map +1 -0
- package/dist/edition.d.ts +20 -0
- package/dist/edition.d.ts.map +1 -0
- package/dist/edition.js +46 -0
- package/dist/edition.js.map +1 -0
- package/dist/elements.d.ts.map +1 -1
- package/dist/elements.js +61 -46
- package/dist/elements.js.map +1 -1
- package/dist/hyperlink.d.ts +7 -0
- package/dist/hyperlink.d.ts.map +1 -0
- package/dist/hyperlink.js +83 -0
- package/dist/hyperlink.js.map +1 -0
- package/dist/index-sync.d.ts +7 -0
- package/dist/index-sync.d.ts.map +1 -0
- package/dist/index-sync.js +24 -0
- package/dist/index-sync.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/indexer.d.ts +4 -0
- package/dist/indexer.d.ts.map +1 -1
- package/dist/indexer.js +105 -27
- package/dist/indexer.js.map +1 -1
- package/dist/media.d.ts +15 -0
- package/dist/media.d.ts.map +1 -0
- package/dist/media.js +141 -0
- package/dist/media.js.map +1 -0
- package/dist/mutations.d.ts +8 -2
- package/dist/mutations.d.ts.map +1 -1
- package/dist/mutations.js +695 -72
- package/dist/mutations.js.map +1 -1
- package/dist/picture.d.ts.map +1 -1
- package/dist/picture.js +1 -3
- package/dist/picture.js.map +1 -1
- package/dist/properties.d.ts +6 -1
- package/dist/properties.d.ts.map +1 -1
- package/dist/properties.js +159 -10
- package/dist/properties.js.map +1 -1
- package/dist/resolve-properties.d.ts +17 -0
- package/dist/resolve-properties.d.ts.map +1 -0
- package/dist/resolve-properties.js +545 -0
- package/dist/resolve-properties.js.map +1 -0
- package/dist/slides.d.ts +3 -1
- package/dist/slides.d.ts.map +1 -1
- package/dist/slides.js +77 -4
- package/dist/slides.js.map +1 -1
- package/dist/table.d.ts +15 -0
- package/dist/table.d.ts.map +1 -0
- package/dist/table.js +456 -0
- package/dist/table.js.map +1 -0
- package/dist/types.d.ts +2 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/workspace.d.ts +9 -9
- package/dist/workspace.d.ts.map +1 -1
- package/dist/workspace.js +67 -47
- package/dist/workspace.js.map +1 -1
- package/dist/xml.d.ts +7 -0
- package/dist/xml.d.ts.map +1 -1
- package/dist/xml.js +86 -13
- package/dist/xml.js.map +1 -1
- package/package.json +12 -4
package/dist/adapter.js
CHANGED
|
@@ -1,23 +1,84 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { basename, dirname, join, resolve } from 'node:path';
|
|
1
|
+
import { copyFile } from 'node:fs/promises';
|
|
2
|
+
import { resolve } from 'node:path';
|
|
4
3
|
import { err, ok, } from '@deckflow/deckuse-core';
|
|
5
4
|
import { OpcArchive } from '@deckflow/deckuse-opc';
|
|
6
|
-
import {
|
|
5
|
+
import { isIntegerRevision, nextRevision } from '@deckflow/deckuse-workspace';
|
|
6
|
+
import { cNvPrIdOf, resolveTarget, targetPathForItem, uidForItem, } from './addressing.js';
|
|
7
|
+
import { editionMetadata } from './edition.js';
|
|
8
|
+
import { buildIndex, findIndexed, matchesSelector, mergeSlides } from './indexer.js';
|
|
9
|
+
import { loadIndex } from './index-sync.js';
|
|
7
10
|
import { mutate } from './mutations.js';
|
|
8
|
-
import {
|
|
9
|
-
|
|
11
|
+
import { resolveProperties } from './resolve-properties.js';
|
|
12
|
+
import { initializeWorkspace, packagePath, persistWrite, readHistory, readManifest, revision, sourceDir, undoWrites, withWriteLock, } from './workspace.js';
|
|
13
|
+
const VERSION = '0.5.0';
|
|
14
|
+
const WRITE_TYPES = new Set([
|
|
15
|
+
'setText',
|
|
16
|
+
'replaceText',
|
|
17
|
+
'setTransform',
|
|
18
|
+
'setProperties',
|
|
19
|
+
'set',
|
|
20
|
+
'xfrmSet',
|
|
21
|
+
'zMove',
|
|
22
|
+
'add',
|
|
23
|
+
'addSlide',
|
|
24
|
+
'addShape',
|
|
25
|
+
'remove',
|
|
26
|
+
'replacePicture',
|
|
27
|
+
'duplicate',
|
|
28
|
+
'batch',
|
|
29
|
+
'applyTransaction',
|
|
30
|
+
]);
|
|
10
31
|
export const pptxCapabilities = {
|
|
32
|
+
protocol: '2.0',
|
|
33
|
+
...editionMetadata,
|
|
11
34
|
slides: { add: true, duplicate: true, remove: true },
|
|
12
|
-
|
|
35
|
+
// Master/layout/theme: list + resolve only in community; writes gated in mutations.
|
|
36
|
+
masters: { list: true, edit: false },
|
|
37
|
+
layouts: { list: true, edit: false },
|
|
38
|
+
theme: { list: true, edit: false },
|
|
39
|
+
elements: [
|
|
40
|
+
'shape',
|
|
41
|
+
'textbox',
|
|
42
|
+
'connector',
|
|
43
|
+
'group',
|
|
44
|
+
'picture',
|
|
45
|
+
'video',
|
|
46
|
+
'audio',
|
|
47
|
+
'table',
|
|
48
|
+
'chart',
|
|
49
|
+
],
|
|
13
50
|
pictureInput: ['base64', 'path'],
|
|
14
51
|
picture: { replacePicture: true, cleanupUnreferencedMedia: true },
|
|
15
|
-
|
|
52
|
+
media: { video: true, audio: true, autoPoster: true },
|
|
53
|
+
chart: {
|
|
54
|
+
create: true,
|
|
55
|
+
families: ['bar', 'column', 'line', 'pie'],
|
|
56
|
+
basicOnly: true,
|
|
57
|
+
title: true,
|
|
58
|
+
seriesCache: true,
|
|
59
|
+
seriesColor: true,
|
|
60
|
+
textColor: true,
|
|
61
|
+
gapWidth: true,
|
|
62
|
+
showMajorGridlines: true,
|
|
63
|
+
fill: true,
|
|
64
|
+
embeddedWorkbook: false,
|
|
65
|
+
},
|
|
66
|
+
table: {
|
|
67
|
+
insertRow: true,
|
|
68
|
+
deleteRow: true,
|
|
69
|
+
insertColumn: true,
|
|
70
|
+
deleteColumn: true,
|
|
71
|
+
cellFill: true,
|
|
72
|
+
},
|
|
73
|
+
placeholder: { index: true, address: true, roleOnAdd: true },
|
|
74
|
+
hyperlink: { set: true, clear: true },
|
|
75
|
+
notes: { ensureOnWrite: true },
|
|
16
76
|
properties: {
|
|
17
77
|
text: true,
|
|
18
78
|
name: true,
|
|
19
79
|
srgbClr: true,
|
|
20
80
|
fill: true,
|
|
81
|
+
fillTransparency: true,
|
|
21
82
|
stroke: true,
|
|
22
83
|
textColor: true,
|
|
23
84
|
fontFamily: true,
|
|
@@ -26,6 +87,30 @@ export const pptxCapabilities = {
|
|
|
26
87
|
italic: true,
|
|
27
88
|
underline: true,
|
|
28
89
|
hidden: true,
|
|
90
|
+
paragraphAlign: true,
|
|
91
|
+
paragraphLevel: true,
|
|
92
|
+
bullet: true,
|
|
93
|
+
hyperlink: true,
|
|
94
|
+
dotted: [
|
|
95
|
+
'font.family',
|
|
96
|
+
'font.size',
|
|
97
|
+
'font.weight',
|
|
98
|
+
'font.italic',
|
|
99
|
+
'font.underline',
|
|
100
|
+
'font.color',
|
|
101
|
+
'fill.color',
|
|
102
|
+
'fill.kind',
|
|
103
|
+
'fill.transparency',
|
|
104
|
+
'line.color',
|
|
105
|
+
'line.kind',
|
|
106
|
+
'line.width',
|
|
107
|
+
'text.value',
|
|
108
|
+
'visible',
|
|
109
|
+
'paragraph.align',
|
|
110
|
+
'paragraph.level',
|
|
111
|
+
'bullet',
|
|
112
|
+
'hyperlink',
|
|
113
|
+
],
|
|
29
114
|
aliases: {
|
|
30
115
|
stroke: ['border', 'outline', 'line'],
|
|
31
116
|
fontFamily: ['font', 'typeface'],
|
|
@@ -34,23 +119,206 @@ export const pptxCapabilities = {
|
|
|
34
119
|
},
|
|
35
120
|
},
|
|
36
121
|
query: { matchAll: ['*', 'all'], textRegex: true, hasText: true },
|
|
37
|
-
text: { setText: true, replaceText: true },
|
|
38
|
-
|
|
122
|
+
text: { setText: true, replaceText: true, multiParagraph: true },
|
|
123
|
+
history: { undo: true },
|
|
124
|
+
addressing: { targetPath: true, uid: true, placeholder: true, notes: true },
|
|
125
|
+
resolve: { effective: true, direct: true, both: true, provenance: true },
|
|
39
126
|
preservation: 'unknown parts and untouched XML are preserved; ZIP entries are recompressed',
|
|
40
127
|
};
|
|
128
|
+
const openWorkspaceArchive = async (workspace) => OpcArchive.openDirectory(sourceDir(workspace));
|
|
129
|
+
const validateArchive = (archive) => {
|
|
130
|
+
const diagnostics = [];
|
|
131
|
+
for (const [source, rels] of archive.relationships)
|
|
132
|
+
for (const rel of rels)
|
|
133
|
+
if (!rel.external && rel.resolvedTarget && !archive.getPart(rel.resolvedTarget))
|
|
134
|
+
diagnostics.push({
|
|
135
|
+
severity: 'error',
|
|
136
|
+
code: 'BROKEN_RELATIONSHIP',
|
|
137
|
+
message: `Missing target ${rel.resolvedTarget}`,
|
|
138
|
+
details: { source, relationshipId: rel.id },
|
|
139
|
+
});
|
|
140
|
+
return diagnostics;
|
|
141
|
+
};
|
|
142
|
+
const slidesFromOutcome = (outcome) => outcome?.slides ?? [];
|
|
143
|
+
const assertExpectRevision = (command, current) => {
|
|
144
|
+
if (command.expectRevision !== undefined) {
|
|
145
|
+
const expected = String(command.expectRevision);
|
|
146
|
+
if (expected !== current && expected !== 'latest')
|
|
147
|
+
return err('TRANSACTION_CONFLICT', `Expected revision ${expected}, current ${current}`);
|
|
148
|
+
}
|
|
149
|
+
if (command.transactionId !== undefined &&
|
|
150
|
+
command.transactionId !== current &&
|
|
151
|
+
command.transactionId !== 'latest')
|
|
152
|
+
return err('TRANSACTION_CONFLICT', `Expected transactionId ${current}`);
|
|
153
|
+
return undefined;
|
|
154
|
+
};
|
|
155
|
+
const listResource = (index, resource, slide) => {
|
|
156
|
+
if (resource === 'slides') {
|
|
157
|
+
return index.elements
|
|
158
|
+
.filter((item) => item.kind === 'slide')
|
|
159
|
+
.map((item, i) => ({
|
|
160
|
+
target: `slide:${i + 1}`,
|
|
161
|
+
uid: uidForItem(item),
|
|
162
|
+
index: i + 1,
|
|
163
|
+
slideId: item.slideId,
|
|
164
|
+
name: item.name,
|
|
165
|
+
titlePreview: item.text?.slice(0, 80),
|
|
166
|
+
partUri: item.partUri,
|
|
167
|
+
}));
|
|
168
|
+
}
|
|
169
|
+
if (resource === 'shapes') {
|
|
170
|
+
if (slide === undefined)
|
|
171
|
+
return err('INVALID_COMMAND', 'list shapes requires --slide', [], {
|
|
172
|
+
hint: 'deckuse list shapes --slide <n> --json',
|
|
173
|
+
});
|
|
174
|
+
const slides = index.elements.filter((item) => item.kind === 'slide');
|
|
175
|
+
const slideItem = slides[slide - 1];
|
|
176
|
+
if (!slideItem)
|
|
177
|
+
return err('TARGET_NOT_FOUND', `slide:${slide} does not exist`, [], {
|
|
178
|
+
target: `slide:${slide}`,
|
|
179
|
+
});
|
|
180
|
+
return index.elements
|
|
181
|
+
.filter((item) => item.partUri === slideItem.partUri &&
|
|
182
|
+
!['slide', 'notes', 'master', 'layout', 'theme', 'tableCell'].includes(item.kind))
|
|
183
|
+
.map((item, z) => ({
|
|
184
|
+
target: targetPathForItem(index, item),
|
|
185
|
+
uid: uidForItem(item),
|
|
186
|
+
id: cNvPrIdOf(item),
|
|
187
|
+
name: item.name,
|
|
188
|
+
kind: item.kind,
|
|
189
|
+
role: item.payload?.['placeholder'] ?? item.payload?.['role'],
|
|
190
|
+
bbox: item.transform,
|
|
191
|
+
zOrder: z,
|
|
192
|
+
textPreview: item.text?.slice(0, 80),
|
|
193
|
+
parentId: item.parentId,
|
|
194
|
+
}));
|
|
195
|
+
}
|
|
196
|
+
if (resource === 'layouts' || resource === 'masters') {
|
|
197
|
+
const kind = resource === 'layouts' ? 'layout' : 'master';
|
|
198
|
+
return index.elements
|
|
199
|
+
.filter((item) => item.kind === kind)
|
|
200
|
+
.map((item) => ({
|
|
201
|
+
target: targetPathForItem(index, item),
|
|
202
|
+
uid: uidForItem(item),
|
|
203
|
+
name: item.partUri.split('/').pop()?.replace(/\.xml$/, ''),
|
|
204
|
+
partUri: item.partUri,
|
|
205
|
+
}));
|
|
206
|
+
}
|
|
207
|
+
return index.elements
|
|
208
|
+
.filter((item) => item.kind === 'theme')
|
|
209
|
+
.map((item) => ({
|
|
210
|
+
target: 'theme',
|
|
211
|
+
uid: uidForItem(item),
|
|
212
|
+
partUri: item.partUri,
|
|
213
|
+
colors: item.payload?.['colors'],
|
|
214
|
+
}));
|
|
215
|
+
};
|
|
216
|
+
const transactionOpsToAtomic = (ops, workspaceId, transactionId) => {
|
|
217
|
+
const commands = [];
|
|
218
|
+
for (const op of ops) {
|
|
219
|
+
const kind = String(op['op'] ?? op['type'] ?? '');
|
|
220
|
+
const base = {
|
|
221
|
+
version: '2.0',
|
|
222
|
+
workspaceId,
|
|
223
|
+
transactionId,
|
|
224
|
+
};
|
|
225
|
+
if (kind === 'add-shape' || kind === 'addShape') {
|
|
226
|
+
const shapeType = String(op['shapeType'] ?? op['type'] ?? 'text');
|
|
227
|
+
const allowed = new Set([
|
|
228
|
+
'text',
|
|
229
|
+
'rect',
|
|
230
|
+
'rounded-rect',
|
|
231
|
+
'ellipse',
|
|
232
|
+
'line',
|
|
233
|
+
'image',
|
|
234
|
+
'group',
|
|
235
|
+
'table',
|
|
236
|
+
'chart',
|
|
237
|
+
'video',
|
|
238
|
+
'audio',
|
|
239
|
+
]);
|
|
240
|
+
if (!allowed.has(shapeType))
|
|
241
|
+
return err('UNSUPPORTED_CAPABILITY', `Unsupported shape type: ${shapeType}`);
|
|
242
|
+
commands.push({
|
|
243
|
+
...base,
|
|
244
|
+
type: 'addShape',
|
|
245
|
+
slide: Number(op['slide']),
|
|
246
|
+
shapeType: shapeType,
|
|
247
|
+
...(typeof op['name'] === 'string' ? { name: op['name'] } : {}),
|
|
248
|
+
...(typeof op['role'] === 'string' ? { role: op['role'] } : {}),
|
|
249
|
+
...(typeof op['x'] === 'number' ? { x: op['x'] } : {}),
|
|
250
|
+
...(typeof op['y'] === 'number' ? { y: op['y'] } : {}),
|
|
251
|
+
...(typeof op['width'] === 'number' ? { width: op['width'] } : {}),
|
|
252
|
+
...(typeof op['height'] === 'number' ? { height: op['height'] } : {}),
|
|
253
|
+
...(typeof op['file'] === 'string' ? { file: op['file'] } : {}),
|
|
254
|
+
...(typeof op['text'] === 'string' ? { text: op['text'] } : {}),
|
|
255
|
+
...(Array.isArray(op['rows']) ? { rows: op['rows'] } : {}),
|
|
256
|
+
...(typeof op['chartType'] === 'string'
|
|
257
|
+
? { chartType: op['chartType'] }
|
|
258
|
+
: {}),
|
|
259
|
+
...(op['data'] !== undefined && typeof op['data'] === 'object' && op['data'] !== null
|
|
260
|
+
? {
|
|
261
|
+
data: op['data'],
|
|
262
|
+
}
|
|
263
|
+
: {}),
|
|
264
|
+
});
|
|
265
|
+
continue;
|
|
266
|
+
}
|
|
267
|
+
if (kind === 'set-text' || kind === 'setText') {
|
|
268
|
+
commands.push({
|
|
269
|
+
...base,
|
|
270
|
+
type: 'setText',
|
|
271
|
+
target: String(op['target']),
|
|
272
|
+
value: String(op['value'] ?? op['text'] ?? ''),
|
|
273
|
+
});
|
|
274
|
+
continue;
|
|
275
|
+
}
|
|
276
|
+
if (kind === 'set') {
|
|
277
|
+
const { op: _op, type: _type, target, ...rest } = op;
|
|
278
|
+
const properties = op['properties'] ?? rest;
|
|
279
|
+
const cleaned = { ...properties };
|
|
280
|
+
delete cleaned['target'];
|
|
281
|
+
delete cleaned['op'];
|
|
282
|
+
delete cleaned['type'];
|
|
283
|
+
commands.push({
|
|
284
|
+
...base,
|
|
285
|
+
type: 'set',
|
|
286
|
+
target: String(target),
|
|
287
|
+
properties: cleaned,
|
|
288
|
+
scope: 'local',
|
|
289
|
+
});
|
|
290
|
+
continue;
|
|
291
|
+
}
|
|
292
|
+
if (kind === 'add-slide' || kind === 'addSlide') {
|
|
293
|
+
commands.push({
|
|
294
|
+
...base,
|
|
295
|
+
type: 'addSlide',
|
|
296
|
+
...(typeof op['after'] === 'number' ? { after: op['after'] } : {}),
|
|
297
|
+
...(typeof op['layout'] === 'string' ? { layout: op['layout'] } : {}),
|
|
298
|
+
...(typeof op['name'] === 'string' ? { name: op['name'] } : {}),
|
|
299
|
+
});
|
|
300
|
+
continue;
|
|
301
|
+
}
|
|
302
|
+
if (kind === 'remove') {
|
|
303
|
+
commands.push({ ...base, type: 'remove', target: String(op['target']) });
|
|
304
|
+
continue;
|
|
305
|
+
}
|
|
306
|
+
return err('UNSUPPORTED_CAPABILITY', `Unsupported transaction op: ${kind || '(missing)'}`);
|
|
307
|
+
}
|
|
308
|
+
return ok(commands);
|
|
309
|
+
};
|
|
41
310
|
export const pptxAdapter = {
|
|
42
311
|
format: 'pptx',
|
|
43
312
|
version: VERSION,
|
|
44
313
|
async init(command) {
|
|
45
314
|
try {
|
|
46
315
|
const workspace = resolve(command.workspaceId);
|
|
47
|
-
await mkdir(deckuseDir(workspace), { recursive: true });
|
|
48
316
|
const archive = await OpcArchive.openFile(resolve(command.source));
|
|
49
317
|
if (!archive.getPart('/ppt/presentation.xml'))
|
|
50
318
|
return err('VALIDATION_FAILED', 'Not a PPTX presentation');
|
|
51
319
|
const created = new Date().toISOString(), rev = revision();
|
|
52
320
|
const manifest = {
|
|
53
|
-
schemaVersion: '
|
|
321
|
+
schemaVersion: '2.0',
|
|
54
322
|
workspaceId: workspace,
|
|
55
323
|
format: 'pptx',
|
|
56
324
|
source: resolve(command.source),
|
|
@@ -61,8 +329,15 @@ export const pptxAdapter = {
|
|
|
61
329
|
files: [],
|
|
62
330
|
metadata: { capabilities: pptxCapabilities },
|
|
63
331
|
};
|
|
64
|
-
const
|
|
65
|
-
|
|
332
|
+
const index = buildIndex(archive, workspace, rev);
|
|
333
|
+
const saved = await initializeWorkspace(workspace, archive, manifest, index);
|
|
334
|
+
return ok({
|
|
335
|
+
workspaceId: saved.workspaceId,
|
|
336
|
+
format: saved.format,
|
|
337
|
+
source: saved.source,
|
|
338
|
+
revision: saved.revision,
|
|
339
|
+
elementCount: index.elements.length,
|
|
340
|
+
});
|
|
66
341
|
}
|
|
67
342
|
catch (cause) {
|
|
68
343
|
return err('IO_ERROR', cause instanceof Error ? cause.message : 'PPTX init failed');
|
|
@@ -71,15 +346,135 @@ export const pptxAdapter = {
|
|
|
71
346
|
async execute(command) {
|
|
72
347
|
const workspace = resolve(command.workspaceId);
|
|
73
348
|
try {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
349
|
+
if (command.type === 'undo') {
|
|
350
|
+
return await withWriteLock(workspace, async () => {
|
|
351
|
+
try {
|
|
352
|
+
const result = await undoWrites(workspace, command.steps);
|
|
353
|
+
return ok(result);
|
|
354
|
+
}
|
|
355
|
+
catch (cause) {
|
|
356
|
+
return err('VALIDATION_FAILED', cause instanceof Error ? cause.message : 'Undo failed');
|
|
357
|
+
}
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
if (command.type === 'history') {
|
|
361
|
+
const { records, total } = await readHistory(workspace, command.limit, command.offset);
|
|
362
|
+
const filtered = command.slide !== undefined
|
|
363
|
+
? records.filter((record) => record.slides.includes(command.slide))
|
|
364
|
+
: records;
|
|
365
|
+
return ok({ records: filtered, total: command.slide !== undefined ? filtered.length : total });
|
|
78
366
|
}
|
|
79
|
-
|
|
80
|
-
|
|
367
|
+
const manifest = await readManifest(workspace);
|
|
368
|
+
if (manifest.schemaVersion !== '2.0' || !isIntegerRevision(manifest.revision)) {
|
|
369
|
+
if (command.type === 'status') {
|
|
370
|
+
return ok({
|
|
371
|
+
workspaceId: workspace,
|
|
372
|
+
format: manifest.format,
|
|
373
|
+
revision: manifest.revision,
|
|
374
|
+
schemaVersion: manifest.schemaVersion,
|
|
375
|
+
compatible: false,
|
|
376
|
+
hint: 'Re-run deckuse init to upgrade this workspace to protocol 2.0.',
|
|
377
|
+
capabilities: manifest.metadata?.['capabilities'],
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
const archive = await openWorkspaceArchive(workspace);
|
|
382
|
+
const index = await loadIndex(workspace, archive, manifest, { persist: true });
|
|
383
|
+
if (command.type === 'status') {
|
|
384
|
+
return ok({
|
|
385
|
+
workspaceId: workspace,
|
|
386
|
+
format: manifest.format,
|
|
387
|
+
revision: manifest.revision,
|
|
388
|
+
schemaVersion: manifest.schemaVersion,
|
|
389
|
+
compatible: true,
|
|
390
|
+
adapterVersion: manifest.adapterVersion,
|
|
391
|
+
source: manifest.source,
|
|
392
|
+
package: packagePath(workspace),
|
|
393
|
+
elementCount: index.elements.length,
|
|
394
|
+
capabilities: pptxCapabilities,
|
|
395
|
+
...editionMetadata,
|
|
396
|
+
branch: 'main',
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
if (command.type === 'list') {
|
|
400
|
+
const listed = listResource(index, command.resource, command.slide);
|
|
401
|
+
if (listed && typeof listed === 'object' && 'ok' in listed && listed.ok === false)
|
|
402
|
+
return listed;
|
|
403
|
+
return ok({ resource: command.resource, items: listed });
|
|
404
|
+
}
|
|
405
|
+
if (command.type === 'get') {
|
|
406
|
+
const resolved = resolveTarget(index, command.target);
|
|
407
|
+
if (!resolved.ok)
|
|
408
|
+
return resolved;
|
|
409
|
+
const details = resolveProperties(archive, resolved.value, {
|
|
410
|
+
resolve: command.resolve,
|
|
411
|
+
...(command.props ? { props: command.props } : {}),
|
|
412
|
+
provenance: command.provenance ?? true,
|
|
413
|
+
});
|
|
414
|
+
return ok(details);
|
|
415
|
+
}
|
|
416
|
+
if (command.type === 'search') {
|
|
417
|
+
const items = index.elements.filter((item) => {
|
|
418
|
+
if (command.kind === 'text') {
|
|
419
|
+
if (!item.text)
|
|
420
|
+
return false;
|
|
421
|
+
if (command.query && !item.text.toLowerCase().includes(command.query.toLowerCase()))
|
|
422
|
+
return false;
|
|
423
|
+
}
|
|
424
|
+
else {
|
|
425
|
+
if (command.name && item.name !== command.name)
|
|
426
|
+
return false;
|
|
427
|
+
if (command.query &&
|
|
428
|
+
!(item.name ?? '').toLowerCase().includes(command.query.toLowerCase()))
|
|
429
|
+
return false;
|
|
430
|
+
}
|
|
431
|
+
return true;
|
|
432
|
+
});
|
|
433
|
+
if (command.kind === 'shape' && command.name) {
|
|
434
|
+
const named = items.filter((item) => item.name === command.name);
|
|
435
|
+
if (named.length > 1)
|
|
436
|
+
return err('AMBIGUOUS_NAME', `Shape name is ambiguous: ${command.name}`);
|
|
437
|
+
}
|
|
438
|
+
return ok({
|
|
439
|
+
matches: items.slice(0, command.limit).map((item) => ({
|
|
440
|
+
target: targetPathForItem(index, item),
|
|
441
|
+
uid: uidForItem(item),
|
|
442
|
+
name: item.name,
|
|
443
|
+
kind: item.kind,
|
|
444
|
+
text: item.text?.slice(0, 200),
|
|
445
|
+
context: item.partUri,
|
|
446
|
+
})),
|
|
447
|
+
});
|
|
81
448
|
}
|
|
82
449
|
if (command.type === 'inspect') {
|
|
450
|
+
if (command.target) {
|
|
451
|
+
const resolved = resolveTarget(index, command.target);
|
|
452
|
+
if (!resolved.ok)
|
|
453
|
+
return resolved;
|
|
454
|
+
const details = resolveProperties(archive, resolved.value, {
|
|
455
|
+
resolve: 'both',
|
|
456
|
+
provenance: true,
|
|
457
|
+
});
|
|
458
|
+
return ok({
|
|
459
|
+
target: resolved.value.target,
|
|
460
|
+
uid: resolved.value.uid,
|
|
461
|
+
kind: resolved.value.item.kind,
|
|
462
|
+
name: resolved.value.item.name,
|
|
463
|
+
partUri: resolved.value.item.partUri,
|
|
464
|
+
transform: resolved.value.item.transform,
|
|
465
|
+
visualTree: command.visualTree
|
|
466
|
+
? index.elements
|
|
467
|
+
.filter((item) => item.partUri === resolved.value.item.partUri)
|
|
468
|
+
.map((item) => ({
|
|
469
|
+
target: targetPathForItem(index, item),
|
|
470
|
+
kind: item.kind,
|
|
471
|
+
name: item.name,
|
|
472
|
+
}))
|
|
473
|
+
: undefined,
|
|
474
|
+
properties: details.properties,
|
|
475
|
+
warnings: details.warnings,
|
|
476
|
+
});
|
|
477
|
+
}
|
|
83
478
|
if (command.ref) {
|
|
84
479
|
const item = findIndexed(index, command.ref);
|
|
85
480
|
return item ? ok(item) : err('ELEMENT_NOT_FOUND', 'Element reference was not found');
|
|
@@ -98,88 +493,126 @@ export const pptxAdapter = {
|
|
|
98
493
|
return item
|
|
99
494
|
? ok({
|
|
100
495
|
ref: item.ref,
|
|
496
|
+
target: targetPathForItem(index, item),
|
|
497
|
+
uid: uidForItem(item),
|
|
101
498
|
slide: item.slideId,
|
|
102
499
|
text: item.text ?? '',
|
|
103
500
|
kind: item.kind,
|
|
104
501
|
geometry: item.transform,
|
|
105
502
|
location: item.location,
|
|
106
|
-
payload: item.kind === 'chart' ||
|
|
503
|
+
payload: item.kind === 'chart' ||
|
|
504
|
+
item.kind === 'picture' ||
|
|
505
|
+
item.kind === 'video' ||
|
|
506
|
+
item.kind === 'audio'
|
|
507
|
+
? item.payload
|
|
508
|
+
: undefined,
|
|
107
509
|
})
|
|
108
510
|
: err('ELEMENT_NOT_FOUND', 'Element reference was not found');
|
|
109
511
|
}
|
|
110
512
|
if (command.type === 'validate') {
|
|
111
|
-
const diagnostics =
|
|
112
|
-
for (const [source, rels] of archive.relationships)
|
|
113
|
-
for (const rel of rels)
|
|
114
|
-
if (!rel.external && rel.resolvedTarget && !archive.getPart(rel.resolvedTarget))
|
|
115
|
-
diagnostics.push({
|
|
116
|
-
severity: 'error',
|
|
117
|
-
code: 'BROKEN_RELATIONSHIP',
|
|
118
|
-
message: `Missing target ${rel.resolvedTarget}`,
|
|
119
|
-
details: { source, relationshipId: rel.id },
|
|
120
|
-
});
|
|
513
|
+
const diagnostics = validateArchive(archive);
|
|
121
514
|
return diagnostics.length
|
|
122
515
|
? err('VALIDATION_FAILED', 'PPTX validation failed', diagnostics)
|
|
123
|
-
: ok({
|
|
516
|
+
: ok({
|
|
517
|
+
valid: true,
|
|
518
|
+
revision: index.revision,
|
|
519
|
+
capabilities: pptxCapabilities,
|
|
520
|
+
});
|
|
124
521
|
}
|
|
125
|
-
if (command.type === '
|
|
126
|
-
if (command.
|
|
127
|
-
return err('
|
|
128
|
-
const
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
}
|
|
135
|
-
catch (error) {
|
|
136
|
-
if (error.code !== 'ENOENT')
|
|
137
|
-
throw error;
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
await mkdir(dirname(destination), { recursive: true });
|
|
141
|
-
const temp = `${destination}.${randomUUID()}.tmp`;
|
|
142
|
-
await copyFile(packagePath(workspace), temp);
|
|
143
|
-
await OpcArchive.openFile(temp);
|
|
144
|
-
await rename(temp, destination);
|
|
145
|
-
return ok({ destination, revision: manifest.revision });
|
|
522
|
+
if (command.type === 'export') {
|
|
523
|
+
if (command.revision !== undefined && String(command.revision) !== manifest.revision)
|
|
524
|
+
return err('UNSUPPORTED_CAPABILITY', 'Export of historical revisions is not available in Phase 1a', [], { hint: 'Omit --revision to export the current workspace package.' });
|
|
525
|
+
const output = resolve(command.output);
|
|
526
|
+
await copyFile(packagePath(workspace), output);
|
|
527
|
+
return ok({
|
|
528
|
+
output,
|
|
529
|
+
revision: manifest.revision,
|
|
530
|
+
});
|
|
146
531
|
}
|
|
147
|
-
if (
|
|
148
|
-
return err('
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
532
|
+
if (!WRITE_TYPES.has(command.type))
|
|
533
|
+
return err('INVALID_COMMAND', `Unsupported command type: ${command.type}`);
|
|
534
|
+
const conflict = assertExpectRevision(command, manifest.revision);
|
|
535
|
+
if (conflict)
|
|
536
|
+
return conflict;
|
|
537
|
+
return await withWriteLock(workspace, async () => {
|
|
538
|
+
const currentManifest = await readManifest(workspace);
|
|
539
|
+
const locked = assertExpectRevision(command, currentManifest.revision);
|
|
540
|
+
if (locked)
|
|
541
|
+
return locked;
|
|
542
|
+
const working = await openWorkspaceArchive(workspace);
|
|
543
|
+
const currentIndex = await loadIndex(workspace, working, currentManifest);
|
|
544
|
+
const runBatch = async (nestedCommands, dryRun, operation) => {
|
|
545
|
+
const results = [];
|
|
546
|
+
const diagnostics = [];
|
|
547
|
+
const slidePages = [];
|
|
548
|
+
const changedTargets = [];
|
|
549
|
+
const changedParts = [];
|
|
550
|
+
for (const nested of nestedCommands) {
|
|
551
|
+
const result = await mutate(nested, working, currentIndex);
|
|
552
|
+
diagnostics.push(...result.diagnostics);
|
|
553
|
+
if (!result.ok)
|
|
554
|
+
return result;
|
|
555
|
+
results.push(result.value);
|
|
556
|
+
slidePages.push(...slidesFromOutcome(result.value));
|
|
557
|
+
if (result.value.changedTargets)
|
|
558
|
+
changedTargets.push(...result.value.changedTargets);
|
|
559
|
+
if (result.value.changedParts)
|
|
560
|
+
changedParts.push(...result.value.changedParts);
|
|
168
561
|
}
|
|
169
|
-
|
|
562
|
+
const validation = validateArchive(working);
|
|
563
|
+
if (validation.length)
|
|
564
|
+
return err('VALIDATION_FAILED', 'PPTX validation failed', validation);
|
|
565
|
+
if (dryRun)
|
|
566
|
+
return ok({
|
|
567
|
+
results,
|
|
568
|
+
revision: currentManifest.revision,
|
|
569
|
+
dryRun: true,
|
|
570
|
+
changedTargets: [...new Set(changedTargets)],
|
|
571
|
+
changedParts: [...new Set(changedParts)],
|
|
572
|
+
affectedSlides: mergeSlides(slidePages),
|
|
573
|
+
}, diagnostics);
|
|
574
|
+
const rev = nextRevision(currentManifest.revision);
|
|
575
|
+
const nextIndex = buildIndex(working, currentManifest.workspaceId, rev);
|
|
576
|
+
const saved = await persistWrite(workspace, working, currentManifest, nextIndex, operation, mergeSlides(slidePages));
|
|
577
|
+
return ok({
|
|
578
|
+
results,
|
|
579
|
+
revision: saved.revision,
|
|
580
|
+
changedTargets: [...new Set(changedTargets)],
|
|
581
|
+
changedParts: [...new Set(changedParts)],
|
|
582
|
+
affectedSlides: mergeSlides(slidePages),
|
|
583
|
+
}, diagnostics);
|
|
584
|
+
};
|
|
585
|
+
if (command.type === 'applyTransaction') {
|
|
586
|
+
const mapped = transactionOpsToAtomic(command.operations, command.workspaceId, command.transactionId);
|
|
587
|
+
if (!mapped.ok)
|
|
588
|
+
return mapped;
|
|
589
|
+
return runBatch(mapped.value, command.dryRun, command);
|
|
590
|
+
}
|
|
591
|
+
if (command.type === 'batch') {
|
|
592
|
+
return runBatch(command.commands, command.dryRun, command);
|
|
170
593
|
}
|
|
594
|
+
const result = await mutate(command, working, currentIndex);
|
|
595
|
+
if (!result.ok)
|
|
596
|
+
return result;
|
|
597
|
+
const validation = validateArchive(working);
|
|
598
|
+
if (validation.length)
|
|
599
|
+
return err('VALIDATION_FAILED', 'PPTX validation failed', validation);
|
|
171
600
|
if (command.dryRun)
|
|
172
|
-
return ok({
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
601
|
+
return ok({
|
|
602
|
+
...result.value,
|
|
603
|
+
revision: currentManifest.revision,
|
|
604
|
+
dryRun: true,
|
|
605
|
+
affectedSlides: slidesFromOutcome(result.value),
|
|
606
|
+
}, result.diagnostics);
|
|
607
|
+
const rev = nextRevision(currentManifest.revision);
|
|
608
|
+
const nextIndex = buildIndex(working, currentManifest.workspaceId, rev);
|
|
609
|
+
const saved = await persistWrite(workspace, working, currentManifest, nextIndex, command, slidesFromOutcome(result.value));
|
|
610
|
+
return ok({
|
|
611
|
+
...result.value,
|
|
612
|
+
revision: saved.revision,
|
|
613
|
+
affectedSlides: slidesFromOutcome(result.value),
|
|
614
|
+
}, result.diagnostics);
|
|
615
|
+
});
|
|
183
616
|
}
|
|
184
617
|
catch (cause) {
|
|
185
618
|
return err('IO_ERROR', cause instanceof Error ? cause.message : 'PPTX operation failed');
|