pptx-viewer-mcp 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/README.md +146 -0
- package/dist/codec/index.d.mts +26 -0
- package/dist/codec/index.d.ts +26 -0
- package/dist/codec/index.js +241 -0
- package/dist/codec/index.mjs +238 -0
- package/dist/index.d.mts +480 -0
- package/dist/index.d.ts +480 -0
- package/dist/index.js +1581 -0
- package/dist/index.mjs +1547 -0
- package/dist/mcp/index.d.mts +1 -0
- package/dist/mcp/index.d.ts +1 -0
- package/dist/mcp/index.js +2339 -0
- package/dist/mcp/index.mjs +2337 -0
- package/dist/schemas/index.d.mts +728 -0
- package/dist/schemas/index.d.ts +728 -0
- package/dist/schemas/index.js +285 -0
- package/dist/schemas/index.mjs +259 -0
- package/package.json +87 -0
|
@@ -0,0 +1,2339 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
var stdio_js = require('@modelcontextprotocol/sdk/server/stdio.js');
|
|
5
|
+
var mcp_js = require('@modelcontextprotocol/sdk/server/mcp.js');
|
|
6
|
+
var zod = require('zod');
|
|
7
|
+
var pptxViewerCore = require('pptx-viewer-core');
|
|
8
|
+
var promises = require('fs/promises');
|
|
9
|
+
|
|
10
|
+
var GetSlideSchema = zod.z.object({
|
|
11
|
+
filePath: zod.z.string().describe("Path to the PPTX file"),
|
|
12
|
+
slideIndex: zod.z.number().int().min(0).describe("Zero-based slide index")
|
|
13
|
+
});
|
|
14
|
+
var AddSlideSchema = zod.z.object({
|
|
15
|
+
filePath: zod.z.string().describe("Path to the PPTX file"),
|
|
16
|
+
insertAfterIndex: zod.z.number().int().min(0).optional().describe("Insert after this slide index"),
|
|
17
|
+
backgroundColor: zod.z.string().optional().describe("Background color (hex)")
|
|
18
|
+
});
|
|
19
|
+
var DeleteSlidesSchema = zod.z.object({
|
|
20
|
+
filePath: zod.z.string().describe("Path to the PPTX file"),
|
|
21
|
+
slideIndexes: zod.z.array(zod.z.number().int().min(0)).min(1).describe("Slide indexes to delete")
|
|
22
|
+
});
|
|
23
|
+
var ReorderSlidesSchema = zod.z.object({
|
|
24
|
+
filePath: zod.z.string().describe("Path to the PPTX file"),
|
|
25
|
+
newOrder: zod.z.array(zod.z.number().int().min(0)).min(1).describe("New slide order as array of indexes")
|
|
26
|
+
});
|
|
27
|
+
var DuplicateSlideSchema = zod.z.object({
|
|
28
|
+
filePath: zod.z.string().describe("Path to the PPTX file"),
|
|
29
|
+
slideIndex: zod.z.number().int().min(0).describe("Slide to duplicate"),
|
|
30
|
+
targetIndex: zod.z.number().int().min(0).optional().describe("Where to insert the duplicate")
|
|
31
|
+
});
|
|
32
|
+
var UpdateSlidePropertiesSchema = zod.z.object({
|
|
33
|
+
filePath: zod.z.string().describe("Path to the PPTX file"),
|
|
34
|
+
slideIndex: zod.z.number().int().min(0).describe("Zero-based slide index"),
|
|
35
|
+
backgroundColor: zod.z.string().optional().describe("Background color (hex)"),
|
|
36
|
+
backgroundGradient: zod.z.string().optional().describe("Background gradient CSS"),
|
|
37
|
+
backgroundImage: zod.z.string().optional().describe("Background image data URL"),
|
|
38
|
+
notes: zod.z.string().optional().describe("Speaker notes text"),
|
|
39
|
+
hidden: zod.z.boolean().optional().describe("Whether slide is hidden")
|
|
40
|
+
});
|
|
41
|
+
var SetSlideTransitionSchema = zod.z.object({
|
|
42
|
+
filePath: zod.z.string().describe("Path to the PPTX file"),
|
|
43
|
+
slideIndex: zod.z.number().int().min(0).describe("Zero-based slide index"),
|
|
44
|
+
type: zod.z.string().describe('Transition type (e.g. "fade", "wipe", "none")'),
|
|
45
|
+
durationMs: zod.z.number().int().min(0).optional().describe("Duration in ms"),
|
|
46
|
+
direction: zod.z.string().optional().describe('Direction (e.g. "left", "right")'),
|
|
47
|
+
advanceOnClick: zod.z.boolean().optional().describe("Advance on mouse click"),
|
|
48
|
+
advanceAfterMs: zod.z.number().int().min(0).optional().describe("Auto-advance after ms")
|
|
49
|
+
});
|
|
50
|
+
var SetCanvasSizeSchema = zod.z.object({
|
|
51
|
+
filePath: zod.z.string().describe("Path to the PPTX file"),
|
|
52
|
+
width: zod.z.number().positive().describe("Canvas width in points"),
|
|
53
|
+
height: zod.z.number().positive().describe("Canvas height in points")
|
|
54
|
+
});
|
|
55
|
+
var ConvertToMarkdownSchema = zod.z.object({
|
|
56
|
+
filePath: zod.z.string().describe("Path to the PPTX file"),
|
|
57
|
+
outputDir: zod.z.string().optional().describe("Output directory for markdown and media"),
|
|
58
|
+
mediaFolderName: zod.z.string().optional().describe("Name for extracted media folder"),
|
|
59
|
+
includeMetadata: zod.z.boolean().optional().describe("Include presentation metadata"),
|
|
60
|
+
slideRange: zod.z.object({
|
|
61
|
+
start: zod.z.number().int().min(0).optional(),
|
|
62
|
+
end: zod.z.number().int().min(0).optional()
|
|
63
|
+
}).optional().describe("Slide range to convert"),
|
|
64
|
+
includeSpeakerNotes: zod.z.boolean().optional().describe("Include speaker notes"),
|
|
65
|
+
semanticMode: zod.z.boolean().optional().describe("Use semantic (clean) markdown mode")
|
|
66
|
+
});
|
|
67
|
+
var AccessibilityCheckSchema = zod.z.object({
|
|
68
|
+
filePath: zod.z.string().describe("Path to the PPTX file")
|
|
69
|
+
});
|
|
70
|
+
var AddElementSchema = zod.z.object({
|
|
71
|
+
filePath: zod.z.string().describe("Path to the PPTX file"),
|
|
72
|
+
slideIndex: zod.z.number().int().min(0).describe("Zero-based slide index"),
|
|
73
|
+
type: zod.z.enum(["text", "shape", "image", "table", "connector"]).describe("Element type to add"),
|
|
74
|
+
x: zod.z.number().optional().describe("X position in points"),
|
|
75
|
+
y: zod.z.number().optional().describe("Y position in points"),
|
|
76
|
+
width: zod.z.number().optional().describe("Width in points"),
|
|
77
|
+
height: zod.z.number().optional().describe("Height in points"),
|
|
78
|
+
text: zod.z.string().optional().describe("Text content"),
|
|
79
|
+
fontSize: zod.z.number().optional().describe("Font size in points"),
|
|
80
|
+
fontFamily: zod.z.string().optional().describe("Font family name"),
|
|
81
|
+
fontColor: zod.z.string().optional().describe("Font color (hex)"),
|
|
82
|
+
bold: zod.z.boolean().optional().describe("Bold text"),
|
|
83
|
+
italic: zod.z.boolean().optional().describe("Italic text"),
|
|
84
|
+
underline: zod.z.boolean().optional().describe("Underline text"),
|
|
85
|
+
alignment: zod.z.enum(["left", "center", "right", "justify"]).optional().describe("Text alignment"),
|
|
86
|
+
shapeType: zod.z.string().optional().describe('Shape type (e.g. "rect", "ellipse")'),
|
|
87
|
+
fillColor: zod.z.string().optional().describe("Fill color (hex)"),
|
|
88
|
+
strokeColor: zod.z.string().optional().describe("Stroke color (hex)"),
|
|
89
|
+
strokeWidth: zod.z.number().optional().describe("Stroke width in points"),
|
|
90
|
+
imageData: zod.z.string().optional().describe("Image as base64 data URL"),
|
|
91
|
+
altText: zod.z.string().optional().describe("Alt text for accessibility"),
|
|
92
|
+
rows: zod.z.number().int().min(1).optional().describe("Number of table rows"),
|
|
93
|
+
columns: zod.z.number().int().min(1).optional().describe("Number of table columns"),
|
|
94
|
+
cellData: zod.z.array(zod.z.array(zod.z.string())).optional().describe("Table cell data as 2D array"),
|
|
95
|
+
headerRow: zod.z.boolean().optional().describe("Style first row as header"),
|
|
96
|
+
startArrow: zod.z.string().optional().describe("Start arrow type"),
|
|
97
|
+
endArrow: zod.z.string().optional().describe("End arrow type"),
|
|
98
|
+
startShapeId: zod.z.string().optional().describe("Connect start to shape ID"),
|
|
99
|
+
endShapeId: zod.z.string().optional().describe("Connect end to shape ID")
|
|
100
|
+
});
|
|
101
|
+
var UpdateElementSchema = zod.z.object({
|
|
102
|
+
filePath: zod.z.string().describe("Path to the PPTX file"),
|
|
103
|
+
slideIndex: zod.z.number().int().min(0).describe("Zero-based slide index"),
|
|
104
|
+
elementId: zod.z.string().describe("Element ID to update"),
|
|
105
|
+
x: zod.z.number().optional(),
|
|
106
|
+
y: zod.z.number().optional(),
|
|
107
|
+
width: zod.z.number().optional(),
|
|
108
|
+
height: zod.z.number().optional(),
|
|
109
|
+
rotation: zod.z.number().optional(),
|
|
110
|
+
text: zod.z.string().optional(),
|
|
111
|
+
fontSize: zod.z.number().optional(),
|
|
112
|
+
fontFamily: zod.z.string().optional(),
|
|
113
|
+
fontColor: zod.z.string().optional(),
|
|
114
|
+
bold: zod.z.boolean().optional(),
|
|
115
|
+
italic: zod.z.boolean().optional(),
|
|
116
|
+
underline: zod.z.boolean().optional(),
|
|
117
|
+
alignment: zod.z.enum(["left", "center", "right", "justify"]).optional(),
|
|
118
|
+
fillColor: zod.z.string().optional(),
|
|
119
|
+
strokeColor: zod.z.string().optional(),
|
|
120
|
+
strokeWidth: zod.z.number().optional(),
|
|
121
|
+
opacity: zod.z.number().min(0).max(1).optional(),
|
|
122
|
+
hidden: zod.z.boolean().optional(),
|
|
123
|
+
flipH: zod.z.boolean().optional(),
|
|
124
|
+
flipV: zod.z.boolean().optional()
|
|
125
|
+
});
|
|
126
|
+
var DeleteElementsSchema = zod.z.object({
|
|
127
|
+
filePath: zod.z.string().describe("Path to the PPTX file"),
|
|
128
|
+
slideIndex: zod.z.number().int().min(0),
|
|
129
|
+
elementIds: zod.z.array(zod.z.string()).min(1).describe("Element IDs to delete")
|
|
130
|
+
});
|
|
131
|
+
var ArrangeElementsSchema = zod.z.object({
|
|
132
|
+
filePath: zod.z.string().describe("Path to the PPTX file"),
|
|
133
|
+
slideIndex: zod.z.number().int().min(0),
|
|
134
|
+
action: zod.z.enum(["align", "reorderLayer"]),
|
|
135
|
+
elementIds: zod.z.array(zod.z.string()).optional().describe("Element IDs (for align)"),
|
|
136
|
+
alignment: zod.z.enum(["left", "right", "top", "bottom", "centerH", "centerV"]).optional(),
|
|
137
|
+
elementId: zod.z.string().optional().describe("Element ID (for reorderLayer)"),
|
|
138
|
+
layerAction: zod.z.enum(["bringForward", "sendBackward", "bringToFront", "sendToBack"]).optional()
|
|
139
|
+
});
|
|
140
|
+
var CloneElementSchema = zod.z.object({
|
|
141
|
+
filePath: zod.z.string().describe("Path to the PPTX file"),
|
|
142
|
+
slideIndex: zod.z.number().int().min(0),
|
|
143
|
+
elementId: zod.z.string(),
|
|
144
|
+
targetSlideIndexes: zod.z.array(zod.z.number().int().min(0)).optional(),
|
|
145
|
+
offsetX: zod.z.number().optional(),
|
|
146
|
+
offsetY: zod.z.number().optional()
|
|
147
|
+
});
|
|
148
|
+
var SetElementAnimationSchema = zod.z.object({
|
|
149
|
+
filePath: zod.z.string().describe("Path to the PPTX file"),
|
|
150
|
+
slideIndex: zod.z.number().int().min(0),
|
|
151
|
+
elementId: zod.z.string(),
|
|
152
|
+
entrance: zod.z.string().optional(),
|
|
153
|
+
exit: zod.z.string().optional(),
|
|
154
|
+
durationMs: zod.z.number().optional(),
|
|
155
|
+
delayMs: zod.z.number().optional(),
|
|
156
|
+
order: zod.z.number().optional()
|
|
157
|
+
});
|
|
158
|
+
var GroupElementsSchema = zod.z.object({
|
|
159
|
+
filePath: zod.z.string().describe("Path to the PPTX file"),
|
|
160
|
+
slideIndex: zod.z.number().int().min(0),
|
|
161
|
+
elementIds: zod.z.array(zod.z.string()).min(2).describe("At least 2 element IDs to group")
|
|
162
|
+
});
|
|
163
|
+
var UngroupElementsSchema = zod.z.object({
|
|
164
|
+
filePath: zod.z.string().describe("Path to the PPTX file"),
|
|
165
|
+
slideIndex: zod.z.number().int().min(0),
|
|
166
|
+
groupElementId: zod.z.string().describe("Group element ID to ungroup")
|
|
167
|
+
});
|
|
168
|
+
var BatchUpdateElementsSchema = zod.z.object({
|
|
169
|
+
filePath: zod.z.string().describe("Path to the PPTX file"),
|
|
170
|
+
slideIndex: zod.z.number().int().min(0),
|
|
171
|
+
elementIds: zod.z.array(zod.z.string()).min(1),
|
|
172
|
+
x: zod.z.number().optional(),
|
|
173
|
+
y: zod.z.number().optional(),
|
|
174
|
+
width: zod.z.number().optional(),
|
|
175
|
+
height: zod.z.number().optional(),
|
|
176
|
+
rotation: zod.z.number().optional(),
|
|
177
|
+
opacity: zod.z.number().min(0).max(1).optional(),
|
|
178
|
+
hidden: zod.z.boolean().optional()
|
|
179
|
+
});
|
|
180
|
+
var UpdateTableCellsSchema = zod.z.object({
|
|
181
|
+
filePath: zod.z.string().describe("Path to the PPTX file"),
|
|
182
|
+
slideIndex: zod.z.number().int().min(0),
|
|
183
|
+
elementId: zod.z.string().describe("Table element ID"),
|
|
184
|
+
cells: zod.z.array(
|
|
185
|
+
zod.z.object({
|
|
186
|
+
row: zod.z.number().int().min(0),
|
|
187
|
+
col: zod.z.number().int().min(0),
|
|
188
|
+
text: zod.z.string()
|
|
189
|
+
})
|
|
190
|
+
).min(1).describe("Cells to update")
|
|
191
|
+
});
|
|
192
|
+
var ManageTableStructureSchema = zod.z.object({
|
|
193
|
+
filePath: zod.z.string().describe("Path to the PPTX file"),
|
|
194
|
+
slideIndex: zod.z.number().int().min(0),
|
|
195
|
+
elementId: zod.z.string().describe("Table element ID"),
|
|
196
|
+
action: zod.z.enum(["insertRow", "deleteRow", "insertColumn", "deleteColumn"]),
|
|
197
|
+
position: zod.z.number().int().min(0).optional().describe("Insertion position index"),
|
|
198
|
+
referenceIndex: zod.z.number().int().min(0).optional(),
|
|
199
|
+
cellTexts: zod.z.array(zod.z.string()).optional()
|
|
200
|
+
});
|
|
201
|
+
var UpdateElementStyleSchema = zod.z.object({
|
|
202
|
+
filePath: zod.z.string().describe("Path to the PPTX file"),
|
|
203
|
+
slideIndex: zod.z.number().int().min(0),
|
|
204
|
+
elementId: zod.z.string(),
|
|
205
|
+
fillColor: zod.z.string().optional(),
|
|
206
|
+
fillMode: zod.z.string().optional(),
|
|
207
|
+
fillGradientStops: zod.z.array(
|
|
208
|
+
zod.z.object({
|
|
209
|
+
color: zod.z.string(),
|
|
210
|
+
position: zod.z.number(),
|
|
211
|
+
opacity: zod.z.number().optional()
|
|
212
|
+
})
|
|
213
|
+
).optional(),
|
|
214
|
+
fillGradientAngle: zod.z.number().optional(),
|
|
215
|
+
fillGradientType: zod.z.string().optional(),
|
|
216
|
+
fillOpacity: zod.z.number().optional(),
|
|
217
|
+
strokeColor: zod.z.string().optional(),
|
|
218
|
+
strokeWidth: zod.z.number().optional(),
|
|
219
|
+
strokeDash: zod.z.string().optional(),
|
|
220
|
+
strokeOpacity: zod.z.number().optional(),
|
|
221
|
+
shadowColor: zod.z.string().optional(),
|
|
222
|
+
shadowBlur: zod.z.number().optional(),
|
|
223
|
+
shadowOffsetX: zod.z.number().optional(),
|
|
224
|
+
shadowOffsetY: zod.z.number().optional(),
|
|
225
|
+
shadowOpacity: zod.z.number().optional(),
|
|
226
|
+
glowColor: zod.z.string().optional(),
|
|
227
|
+
glowRadius: zod.z.number().optional(),
|
|
228
|
+
glowOpacity: zod.z.number().optional(),
|
|
229
|
+
softEdgeRadius: zod.z.number().optional(),
|
|
230
|
+
cropLeft: zod.z.number().optional(),
|
|
231
|
+
cropTop: zod.z.number().optional(),
|
|
232
|
+
cropRight: zod.z.number().optional(),
|
|
233
|
+
cropBottom: zod.z.number().optional(),
|
|
234
|
+
brightness: zod.z.number().optional(),
|
|
235
|
+
contrast: zod.z.number().optional(),
|
|
236
|
+
grayscale: zod.z.boolean().optional(),
|
|
237
|
+
altText: zod.z.string().optional()
|
|
238
|
+
});
|
|
239
|
+
var FindTextSchema = zod.z.object({
|
|
240
|
+
filePath: zod.z.string().describe("Path to the PPTX file"),
|
|
241
|
+
query: zod.z.string().describe("Text to search for"),
|
|
242
|
+
useRegex: zod.z.boolean().optional().describe("Treat query as regex"),
|
|
243
|
+
caseSensitive: zod.z.boolean().optional().describe("Case-sensitive search"),
|
|
244
|
+
slideIndexes: zod.z.array(zod.z.number().int().min(0)).optional().describe("Limit search to specific slides")
|
|
245
|
+
});
|
|
246
|
+
var ReplaceTextSchema = zod.z.object({
|
|
247
|
+
filePath: zod.z.string().describe("Path to the PPTX file"),
|
|
248
|
+
query: zod.z.string().describe("Text to find"),
|
|
249
|
+
replacement: zod.z.string().describe("Replacement text"),
|
|
250
|
+
useRegex: zod.z.boolean().optional(),
|
|
251
|
+
caseSensitive: zod.z.boolean().optional(),
|
|
252
|
+
slideIndexes: zod.z.array(zod.z.number().int().min(0)).optional()
|
|
253
|
+
});
|
|
254
|
+
var ManageCommentsSchema = zod.z.object({
|
|
255
|
+
filePath: zod.z.string().describe("Path to the PPTX file"),
|
|
256
|
+
action: zod.z.enum(["list", "add", "delete", "resolve"]),
|
|
257
|
+
slideIndex: zod.z.number().int().min(0).optional(),
|
|
258
|
+
text: zod.z.string().optional(),
|
|
259
|
+
author: zod.z.string().optional(),
|
|
260
|
+
commentId: zod.z.string().optional(),
|
|
261
|
+
resolved: zod.z.boolean().optional(),
|
|
262
|
+
includeResolved: zod.z.boolean().optional()
|
|
263
|
+
});
|
|
264
|
+
function generateElementId() {
|
|
265
|
+
return `el-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
266
|
+
}
|
|
267
|
+
function generateSlideId() {
|
|
268
|
+
return `slide-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`;
|
|
269
|
+
}
|
|
270
|
+
function describeElement(el) {
|
|
271
|
+
const base = {
|
|
272
|
+
id: el.id,
|
|
273
|
+
type: el.type,
|
|
274
|
+
x: Math.round(el.x),
|
|
275
|
+
y: Math.round(el.y),
|
|
276
|
+
width: Math.round(el.width),
|
|
277
|
+
height: Math.round(el.height)
|
|
278
|
+
};
|
|
279
|
+
if (el.rotation) {
|
|
280
|
+
base.rotation = el.rotation;
|
|
281
|
+
}
|
|
282
|
+
if (el.hidden) {
|
|
283
|
+
base.hidden = true;
|
|
284
|
+
}
|
|
285
|
+
if (el.opacity !== void 0 && el.opacity !== 1) {
|
|
286
|
+
base.opacity = el.opacity;
|
|
287
|
+
}
|
|
288
|
+
if (pptxViewerCore.hasTextProperties(el)) {
|
|
289
|
+
base.text = el.text ?? "";
|
|
290
|
+
if (el.textStyle) {
|
|
291
|
+
const ts = {};
|
|
292
|
+
if (el.textStyle.fontSize) {
|
|
293
|
+
ts.fontSize = el.textStyle.fontSize;
|
|
294
|
+
}
|
|
295
|
+
if (el.textStyle.fontFamily) {
|
|
296
|
+
ts.fontFamily = el.textStyle.fontFamily;
|
|
297
|
+
}
|
|
298
|
+
if (el.textStyle.bold) {
|
|
299
|
+
ts.bold = true;
|
|
300
|
+
}
|
|
301
|
+
if (el.textStyle.italic) {
|
|
302
|
+
ts.italic = true;
|
|
303
|
+
}
|
|
304
|
+
if (el.textStyle.underline) {
|
|
305
|
+
ts.underline = el.textStyle.underline;
|
|
306
|
+
}
|
|
307
|
+
if (el.textStyle.align) {
|
|
308
|
+
ts.align = el.textStyle.align;
|
|
309
|
+
}
|
|
310
|
+
if (el.textStyle.color) {
|
|
311
|
+
ts.color = el.textStyle.color;
|
|
312
|
+
}
|
|
313
|
+
if (Object.keys(ts).length > 0) {
|
|
314
|
+
base.textStyle = ts;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
if ("shapeType" in el && el.shapeType) {
|
|
319
|
+
base.shapeType = el.shapeType;
|
|
320
|
+
}
|
|
321
|
+
if ("shapeStyle" in el && el.shapeStyle) {
|
|
322
|
+
const ss = {};
|
|
323
|
+
const style = el.shapeStyle;
|
|
324
|
+
if (style.fillColor) {
|
|
325
|
+
ss.fillColor = style.fillColor;
|
|
326
|
+
}
|
|
327
|
+
if (style.strokeColor) {
|
|
328
|
+
ss.strokeColor = style.strokeColor;
|
|
329
|
+
}
|
|
330
|
+
if (style.strokeWidth) {
|
|
331
|
+
ss.strokeWidth = style.strokeWidth;
|
|
332
|
+
}
|
|
333
|
+
if (Object.keys(ss).length > 0) {
|
|
334
|
+
base.shapeStyle = ss;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
if (el.type === "image" || el.type === "picture") {
|
|
338
|
+
const img = el;
|
|
339
|
+
if (img.altText) {
|
|
340
|
+
base.altText = img.altText;
|
|
341
|
+
}
|
|
342
|
+
if (img.imagePath) {
|
|
343
|
+
base.imagePath = img.imagePath;
|
|
344
|
+
}
|
|
345
|
+
base.hasImageData = Boolean(img.imageData);
|
|
346
|
+
}
|
|
347
|
+
if (el.type === "table") {
|
|
348
|
+
const tbl = el;
|
|
349
|
+
if (tbl.tableData) {
|
|
350
|
+
base.rowCount = tbl.tableData.rows.length;
|
|
351
|
+
base.columnCount = tbl.tableData.columnWidths.length;
|
|
352
|
+
base.cells = tbl.tableData.rows.map((row) => row.cells.map((cell) => cell.text ?? ""));
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
if (el.type === "chart") {
|
|
356
|
+
base.chartType = el.chartData?.type;
|
|
357
|
+
}
|
|
358
|
+
if (el.type === "group") {
|
|
359
|
+
const grp = el;
|
|
360
|
+
base.childCount = grp.children?.length ?? 0;
|
|
361
|
+
}
|
|
362
|
+
return base;
|
|
363
|
+
}
|
|
364
|
+
function validateSlideIndex(slideIndex, slideCount) {
|
|
365
|
+
if (slideIndex < 0 || slideIndex >= slideCount) {
|
|
366
|
+
return `Slide index ${slideIndex} out of range (0\u2013${slideCount - 1}).`;
|
|
367
|
+
}
|
|
368
|
+
return null;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// src/tools/content-tools.ts
|
|
372
|
+
function buildSearchRegex(query, useRegex, caseSensitive) {
|
|
373
|
+
const pattern = useRegex ? query : query.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
374
|
+
const flags = caseSensitive ? "g" : "gi";
|
|
375
|
+
return new RegExp(pattern, flags);
|
|
376
|
+
}
|
|
377
|
+
function findText(ctx, params) {
|
|
378
|
+
const regex = buildSearchRegex(
|
|
379
|
+
params.query,
|
|
380
|
+
params.useRegex === true,
|
|
381
|
+
params.caseSensitive === true
|
|
382
|
+
);
|
|
383
|
+
const targetIndexes = params.slideIndexes ? params.slideIndexes : ctx.pptxData.slides.map((_, i) => i);
|
|
384
|
+
const matches = [];
|
|
385
|
+
for (const si of targetIndexes) {
|
|
386
|
+
const err = validateSlideIndex(si, ctx.pptxData.slides.length);
|
|
387
|
+
if (err) {
|
|
388
|
+
throw new Error(err);
|
|
389
|
+
}
|
|
390
|
+
const slide = ctx.pptxData.slides[si];
|
|
391
|
+
for (const el of slide.elements) {
|
|
392
|
+
if (pptxViewerCore.hasTextProperties(el)) {
|
|
393
|
+
const textEl = el;
|
|
394
|
+
if (textEl.text) {
|
|
395
|
+
const localMatches = Array.from(textEl.text.matchAll(regex));
|
|
396
|
+
for (const m of localMatches) {
|
|
397
|
+
matches.push({
|
|
398
|
+
slideIndex: si,
|
|
399
|
+
elementId: el.id,
|
|
400
|
+
elementType: el.type,
|
|
401
|
+
matchedText: m[0],
|
|
402
|
+
context: textEl.text.slice(
|
|
403
|
+
Math.max(0, (m.index ?? 0) - 20),
|
|
404
|
+
(m.index ?? 0) + m[0].length + 20
|
|
405
|
+
)
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
if (el.type === "table") {
|
|
411
|
+
const tbl = el;
|
|
412
|
+
if (tbl.tableData) {
|
|
413
|
+
for (const row of tbl.tableData.rows) {
|
|
414
|
+
for (const cell of row.cells) {
|
|
415
|
+
if (cell.text) {
|
|
416
|
+
const localMatches = Array.from(cell.text.matchAll(regex));
|
|
417
|
+
for (const m of localMatches) {
|
|
418
|
+
matches.push({
|
|
419
|
+
slideIndex: si,
|
|
420
|
+
elementId: el.id,
|
|
421
|
+
elementType: "table-cell",
|
|
422
|
+
matchedText: m[0],
|
|
423
|
+
context: cell.text.slice(
|
|
424
|
+
Math.max(0, (m.index ?? 0) - 20),
|
|
425
|
+
(m.index ?? 0) + m[0].length + 20
|
|
426
|
+
)
|
|
427
|
+
});
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
if (slide.notes) {
|
|
436
|
+
const noteMatches = Array.from(slide.notes.matchAll(regex));
|
|
437
|
+
for (const m of noteMatches) {
|
|
438
|
+
matches.push({
|
|
439
|
+
slideIndex: si,
|
|
440
|
+
elementId: "notes",
|
|
441
|
+
elementType: "notes",
|
|
442
|
+
matchedText: m[0],
|
|
443
|
+
context: slide.notes.slice(
|
|
444
|
+
Math.max(0, (m.index ?? 0) - 20),
|
|
445
|
+
(m.index ?? 0) + m[0].length + 20
|
|
446
|
+
)
|
|
447
|
+
});
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
return {
|
|
452
|
+
pptxData: ctx.pptxData,
|
|
453
|
+
dirty: false,
|
|
454
|
+
result: {
|
|
455
|
+
query: params.query,
|
|
456
|
+
matchCount: matches.length,
|
|
457
|
+
matches
|
|
458
|
+
}
|
|
459
|
+
};
|
|
460
|
+
}
|
|
461
|
+
function replaceText(ctx, params) {
|
|
462
|
+
const regex = buildSearchRegex(
|
|
463
|
+
params.query,
|
|
464
|
+
params.useRegex === true,
|
|
465
|
+
params.caseSensitive === true
|
|
466
|
+
);
|
|
467
|
+
const targetIndexes = params.slideIndexes ? params.slideIndexes : ctx.pptxData.slides.map((_, i) => i);
|
|
468
|
+
let count = 0;
|
|
469
|
+
for (const si of targetIndexes) {
|
|
470
|
+
const err = validateSlideIndex(si, ctx.pptxData.slides.length);
|
|
471
|
+
if (err) {
|
|
472
|
+
throw new Error(err);
|
|
473
|
+
}
|
|
474
|
+
const slide = ctx.pptxData.slides[si];
|
|
475
|
+
for (const el of slide.elements) {
|
|
476
|
+
if (pptxViewerCore.hasTextProperties(el)) {
|
|
477
|
+
const textEl = el;
|
|
478
|
+
if (textEl.text) {
|
|
479
|
+
const before = textEl.text;
|
|
480
|
+
const after = before.replace(regex, params.replacement);
|
|
481
|
+
if (after !== before) {
|
|
482
|
+
const matchCount = Array.from(before.matchAll(regex)).length;
|
|
483
|
+
count += matchCount;
|
|
484
|
+
textEl.text = after;
|
|
485
|
+
if (textEl.textSegments && textEl.textSegments.length > 0) {
|
|
486
|
+
const firstSeg = textEl.textSegments[0];
|
|
487
|
+
const updatedFirst = firstSeg.text.replace(regex, params.replacement);
|
|
488
|
+
if (updatedFirst !== firstSeg.text) {
|
|
489
|
+
firstSeg.text = updatedFirst;
|
|
490
|
+
} else {
|
|
491
|
+
textEl.textSegments = [{ text: after, style: firstSeg.style }];
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
if (el.type === "table") {
|
|
498
|
+
const tbl = el;
|
|
499
|
+
if (tbl.tableData) {
|
|
500
|
+
for (const row of tbl.tableData.rows) {
|
|
501
|
+
for (const cell of row.cells) {
|
|
502
|
+
if (cell.text) {
|
|
503
|
+
const before = cell.text;
|
|
504
|
+
const after = before.replace(regex, params.replacement);
|
|
505
|
+
if (after !== before) {
|
|
506
|
+
count += Array.from(before.matchAll(regex)).length;
|
|
507
|
+
cell.text = after;
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
if (slide.notes) {
|
|
516
|
+
const before = slide.notes;
|
|
517
|
+
const after = before.replace(regex, params.replacement);
|
|
518
|
+
if (after !== before) {
|
|
519
|
+
count += Array.from(before.matchAll(regex)).length;
|
|
520
|
+
slide.notes = after;
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
return {
|
|
525
|
+
pptxData: ctx.pptxData,
|
|
526
|
+
dirty: count > 0,
|
|
527
|
+
result: {
|
|
528
|
+
query: params.query,
|
|
529
|
+
replacement: params.replacement,
|
|
530
|
+
replacementCount: count
|
|
531
|
+
}
|
|
532
|
+
};
|
|
533
|
+
}
|
|
534
|
+
function manageComments(ctx, params) {
|
|
535
|
+
switch (params.action) {
|
|
536
|
+
case "list": {
|
|
537
|
+
const all = [];
|
|
538
|
+
for (let si = 0; si < ctx.pptxData.slides.length; si++) {
|
|
539
|
+
if (params.slideIndex !== void 0 && si !== params.slideIndex) {
|
|
540
|
+
continue;
|
|
541
|
+
}
|
|
542
|
+
const slide = ctx.pptxData.slides[si];
|
|
543
|
+
for (const c of slide.comments ?? []) {
|
|
544
|
+
if (!params.includeResolved && c.resolved) {
|
|
545
|
+
continue;
|
|
546
|
+
}
|
|
547
|
+
all.push({
|
|
548
|
+
id: c.id,
|
|
549
|
+
slideIndex: si,
|
|
550
|
+
text: c.text,
|
|
551
|
+
author: c.author,
|
|
552
|
+
createdAt: c.createdAt,
|
|
553
|
+
resolved: c.resolved
|
|
554
|
+
});
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
return {
|
|
558
|
+
pptxData: ctx.pptxData,
|
|
559
|
+
dirty: false,
|
|
560
|
+
result: { action: "list", comments: all, count: all.length }
|
|
561
|
+
};
|
|
562
|
+
}
|
|
563
|
+
case "add": {
|
|
564
|
+
if (params.slideIndex === void 0) {
|
|
565
|
+
throw new Error("slideIndex is required for add action.");
|
|
566
|
+
}
|
|
567
|
+
const err = validateSlideIndex(params.slideIndex, ctx.pptxData.slides.length);
|
|
568
|
+
if (err) {
|
|
569
|
+
throw new Error(err);
|
|
570
|
+
}
|
|
571
|
+
if (!params.text) {
|
|
572
|
+
throw new Error("text is required for add action.");
|
|
573
|
+
}
|
|
574
|
+
const slide = ctx.pptxData.slides[params.slideIndex];
|
|
575
|
+
if (!slide.comments) {
|
|
576
|
+
slide.comments = [];
|
|
577
|
+
}
|
|
578
|
+
const newId = `comment-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
579
|
+
slide.comments.push({
|
|
580
|
+
id: newId,
|
|
581
|
+
text: params.text,
|
|
582
|
+
author: params.author,
|
|
583
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
584
|
+
resolved: false
|
|
585
|
+
});
|
|
586
|
+
return {
|
|
587
|
+
pptxData: ctx.pptxData,
|
|
588
|
+
dirty: true,
|
|
589
|
+
result: { action: "add", commentId: newId }
|
|
590
|
+
};
|
|
591
|
+
}
|
|
592
|
+
case "delete": {
|
|
593
|
+
if (!params.commentId) {
|
|
594
|
+
throw new Error("commentId is required for delete action.");
|
|
595
|
+
}
|
|
596
|
+
let deleted = false;
|
|
597
|
+
for (const slide of ctx.pptxData.slides) {
|
|
598
|
+
if (!slide.comments) {
|
|
599
|
+
continue;
|
|
600
|
+
}
|
|
601
|
+
const before = slide.comments.length;
|
|
602
|
+
slide.comments = slide.comments.filter((c) => c.id !== params.commentId);
|
|
603
|
+
if (slide.comments.length < before) {
|
|
604
|
+
deleted = true;
|
|
605
|
+
break;
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
if (!deleted) {
|
|
609
|
+
throw new Error(`Comment '${params.commentId}' not found.`);
|
|
610
|
+
}
|
|
611
|
+
return {
|
|
612
|
+
pptxData: ctx.pptxData,
|
|
613
|
+
dirty: true,
|
|
614
|
+
result: { action: "delete", commentId: params.commentId }
|
|
615
|
+
};
|
|
616
|
+
}
|
|
617
|
+
case "resolve": {
|
|
618
|
+
if (!params.commentId) {
|
|
619
|
+
throw new Error("commentId is required for resolve action.");
|
|
620
|
+
}
|
|
621
|
+
let found = false;
|
|
622
|
+
for (const slide of ctx.pptxData.slides) {
|
|
623
|
+
const comment = slide.comments?.find((c) => c.id === params.commentId);
|
|
624
|
+
if (comment) {
|
|
625
|
+
comment.resolved = params.resolved !== false;
|
|
626
|
+
found = true;
|
|
627
|
+
break;
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
if (!found) {
|
|
631
|
+
throw new Error(`Comment '${params.commentId}' not found.`);
|
|
632
|
+
}
|
|
633
|
+
return {
|
|
634
|
+
pptxData: ctx.pptxData,
|
|
635
|
+
dirty: true,
|
|
636
|
+
result: { action: "resolve", commentId: params.commentId }
|
|
637
|
+
};
|
|
638
|
+
}
|
|
639
|
+
default: {
|
|
640
|
+
throw new Error(`Unknown action: ${String(params.action)}`);
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
async function convertToMarkdown(ctx, params) {
|
|
645
|
+
const options = {
|
|
646
|
+
mediaFolderName: params.mediaFolderName ?? "media",
|
|
647
|
+
includeMetadata: params.includeMetadata !== false,
|
|
648
|
+
slideRange: params.slideRange,
|
|
649
|
+
includeSpeakerNotes: params.includeSpeakerNotes !== false,
|
|
650
|
+
semanticMode: params.semanticMode === true,
|
|
651
|
+
sourceName: params.sourceName ?? "unknown"
|
|
652
|
+
};
|
|
653
|
+
const converter = new pptxViewerCore.PptxMarkdownConverter(params.outputDir ?? ".", options);
|
|
654
|
+
const markdown = await converter.convert(ctx.pptxData);
|
|
655
|
+
return {
|
|
656
|
+
pptxData: ctx.pptxData,
|
|
657
|
+
dirty: false,
|
|
658
|
+
result: {
|
|
659
|
+
markdown,
|
|
660
|
+
slidesConverted: converter.slidesConverted,
|
|
661
|
+
totalSlides: converter.presentationSlides,
|
|
662
|
+
imagesExtracted: converter.imagesExtracted
|
|
663
|
+
}
|
|
664
|
+
};
|
|
665
|
+
}
|
|
666
|
+
function addElement(ctx, params) {
|
|
667
|
+
const err = validateSlideIndex(params.slideIndex, ctx.pptxData.slides.length);
|
|
668
|
+
if (err) {
|
|
669
|
+
throw new Error(err);
|
|
670
|
+
}
|
|
671
|
+
const slide = ctx.pptxData.slides[params.slideIndex];
|
|
672
|
+
const id = generateElementId();
|
|
673
|
+
const x = params.x ?? 100;
|
|
674
|
+
const y = params.y ?? 100;
|
|
675
|
+
const width = params.width ?? 300;
|
|
676
|
+
const height = params.height ?? 60;
|
|
677
|
+
switch (params.type) {
|
|
678
|
+
case "text": {
|
|
679
|
+
const el = {
|
|
680
|
+
id,
|
|
681
|
+
type: "text",
|
|
682
|
+
x,
|
|
683
|
+
y,
|
|
684
|
+
width,
|
|
685
|
+
height,
|
|
686
|
+
text: params.text ?? "",
|
|
687
|
+
textStyle: {
|
|
688
|
+
fontSize: params.fontSize,
|
|
689
|
+
fontFamily: params.fontFamily,
|
|
690
|
+
color: params.fontColor,
|
|
691
|
+
bold: params.bold,
|
|
692
|
+
italic: params.italic,
|
|
693
|
+
underline: params.underline,
|
|
694
|
+
align: params.alignment
|
|
695
|
+
},
|
|
696
|
+
textSegments: [
|
|
697
|
+
{
|
|
698
|
+
text: params.text ?? "",
|
|
699
|
+
style: {
|
|
700
|
+
fontSize: params.fontSize,
|
|
701
|
+
fontFamily: params.fontFamily,
|
|
702
|
+
color: params.fontColor,
|
|
703
|
+
bold: params.bold,
|
|
704
|
+
italic: params.italic,
|
|
705
|
+
underline: params.underline
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
]
|
|
709
|
+
};
|
|
710
|
+
slide.elements.push(el);
|
|
711
|
+
break;
|
|
712
|
+
}
|
|
713
|
+
case "shape": {
|
|
714
|
+
const el = {
|
|
715
|
+
id,
|
|
716
|
+
type: "shape",
|
|
717
|
+
x,
|
|
718
|
+
y,
|
|
719
|
+
width,
|
|
720
|
+
height,
|
|
721
|
+
shapeType: params.shapeType ?? "rect",
|
|
722
|
+
text: params.text,
|
|
723
|
+
textStyle: params.text ? {
|
|
724
|
+
fontSize: params.fontSize,
|
|
725
|
+
fontFamily: params.fontFamily,
|
|
726
|
+
color: params.fontColor,
|
|
727
|
+
bold: params.bold,
|
|
728
|
+
italic: params.italic,
|
|
729
|
+
align: params.alignment
|
|
730
|
+
} : void 0,
|
|
731
|
+
shapeStyle: {
|
|
732
|
+
fillColor: params.fillColor,
|
|
733
|
+
strokeColor: params.strokeColor,
|
|
734
|
+
strokeWidth: params.strokeWidth
|
|
735
|
+
}
|
|
736
|
+
};
|
|
737
|
+
slide.elements.push(el);
|
|
738
|
+
break;
|
|
739
|
+
}
|
|
740
|
+
case "image": {
|
|
741
|
+
const el = {
|
|
742
|
+
id,
|
|
743
|
+
type: "image",
|
|
744
|
+
x,
|
|
745
|
+
y,
|
|
746
|
+
width,
|
|
747
|
+
height,
|
|
748
|
+
imageData: params.imageData,
|
|
749
|
+
altText: params.altText
|
|
750
|
+
};
|
|
751
|
+
slide.elements.push(el);
|
|
752
|
+
break;
|
|
753
|
+
}
|
|
754
|
+
case "table": {
|
|
755
|
+
const rowCount = params.rows ?? 2;
|
|
756
|
+
const colCount = params.columns ?? 2;
|
|
757
|
+
const colWidth = 1 / colCount;
|
|
758
|
+
const tableRows = Array.from({ length: rowCount }, (_, r) => ({
|
|
759
|
+
height: 40,
|
|
760
|
+
cells: Array.from({ length: colCount }, (__, c) => ({
|
|
761
|
+
text: params.cellData?.[r]?.[c] ?? ""
|
|
762
|
+
}))
|
|
763
|
+
}));
|
|
764
|
+
const tableEl = {
|
|
765
|
+
id,
|
|
766
|
+
type: "table",
|
|
767
|
+
x,
|
|
768
|
+
y,
|
|
769
|
+
width,
|
|
770
|
+
height: params.height ?? rowCount * 40,
|
|
771
|
+
tableData: {
|
|
772
|
+
rows: tableRows,
|
|
773
|
+
columnWidths: Array.from({ length: colCount }, () => colWidth),
|
|
774
|
+
firstRowHeader: params.headerRow ?? false
|
|
775
|
+
}
|
|
776
|
+
};
|
|
777
|
+
slide.elements.push(tableEl);
|
|
778
|
+
break;
|
|
779
|
+
}
|
|
780
|
+
case "connector": {
|
|
781
|
+
const el = {
|
|
782
|
+
id,
|
|
783
|
+
type: "connector",
|
|
784
|
+
x,
|
|
785
|
+
y,
|
|
786
|
+
width,
|
|
787
|
+
height,
|
|
788
|
+
shapeStyle: {
|
|
789
|
+
strokeColor: params.strokeColor ?? "#000000",
|
|
790
|
+
strokeWidth: params.strokeWidth ?? 1,
|
|
791
|
+
connectorStartArrow: params.startArrow,
|
|
792
|
+
connectorEndArrow: params.endArrow
|
|
793
|
+
}
|
|
794
|
+
};
|
|
795
|
+
slide.elements.push(el);
|
|
796
|
+
break;
|
|
797
|
+
}
|
|
798
|
+
default: {
|
|
799
|
+
throw new Error(`Unknown element type: ${String(params.type)}`);
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
return {
|
|
803
|
+
pptxData: ctx.pptxData,
|
|
804
|
+
dirty: true,
|
|
805
|
+
result: { elementId: id, slideIndex: params.slideIndex }
|
|
806
|
+
};
|
|
807
|
+
}
|
|
808
|
+
function updateElement(ctx, params) {
|
|
809
|
+
const err = validateSlideIndex(params.slideIndex, ctx.pptxData.slides.length);
|
|
810
|
+
if (err) {
|
|
811
|
+
throw new Error(err);
|
|
812
|
+
}
|
|
813
|
+
const slide = ctx.pptxData.slides[params.slideIndex];
|
|
814
|
+
const el = slide.elements.find((e) => e.id === params.elementId);
|
|
815
|
+
if (!el) {
|
|
816
|
+
throw new Error(`Element '${params.elementId}' not found on slide ${params.slideIndex}.`);
|
|
817
|
+
}
|
|
818
|
+
if (params.x !== void 0) {
|
|
819
|
+
el.x = params.x;
|
|
820
|
+
}
|
|
821
|
+
if (params.y !== void 0) {
|
|
822
|
+
el.y = params.y;
|
|
823
|
+
}
|
|
824
|
+
if (params.width !== void 0) {
|
|
825
|
+
el.width = params.width;
|
|
826
|
+
}
|
|
827
|
+
if (params.height !== void 0) {
|
|
828
|
+
el.height = params.height;
|
|
829
|
+
}
|
|
830
|
+
if (params.rotation !== void 0) {
|
|
831
|
+
el.rotation = params.rotation;
|
|
832
|
+
}
|
|
833
|
+
if (params.opacity !== void 0) {
|
|
834
|
+
el.opacity = params.opacity;
|
|
835
|
+
}
|
|
836
|
+
if (params.hidden !== void 0) {
|
|
837
|
+
el.hidden = params.hidden;
|
|
838
|
+
}
|
|
839
|
+
if (params.flipH !== void 0) {
|
|
840
|
+
el.flipHorizontal = params.flipH;
|
|
841
|
+
}
|
|
842
|
+
if (params.flipV !== void 0) {
|
|
843
|
+
el.flipVertical = params.flipV;
|
|
844
|
+
}
|
|
845
|
+
if (pptxViewerCore.hasTextProperties(el)) {
|
|
846
|
+
const textEl = el;
|
|
847
|
+
if (params.text !== void 0) {
|
|
848
|
+
textEl.text = params.text;
|
|
849
|
+
if (textEl.textSegments && textEl.textSegments.length > 0) {
|
|
850
|
+
textEl.textSegments[0].text = params.text;
|
|
851
|
+
} else {
|
|
852
|
+
textEl.textSegments = [{ text: params.text, style: {} }];
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
if (!textEl.textStyle) {
|
|
856
|
+
textEl.textStyle = {};
|
|
857
|
+
}
|
|
858
|
+
if (params.fontSize !== void 0) {
|
|
859
|
+
textEl.textStyle.fontSize = params.fontSize;
|
|
860
|
+
}
|
|
861
|
+
if (params.fontFamily !== void 0) {
|
|
862
|
+
textEl.textStyle.fontFamily = params.fontFamily;
|
|
863
|
+
}
|
|
864
|
+
if (params.fontColor !== void 0) {
|
|
865
|
+
textEl.textStyle.color = params.fontColor;
|
|
866
|
+
}
|
|
867
|
+
if (params.bold !== void 0) {
|
|
868
|
+
textEl.textStyle.bold = params.bold;
|
|
869
|
+
}
|
|
870
|
+
if (params.italic !== void 0) {
|
|
871
|
+
textEl.textStyle.italic = params.italic;
|
|
872
|
+
}
|
|
873
|
+
if (params.underline !== void 0) {
|
|
874
|
+
textEl.textStyle.underline = params.underline;
|
|
875
|
+
}
|
|
876
|
+
if (params.alignment !== void 0) {
|
|
877
|
+
textEl.textStyle.align = params.alignment;
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
if ("shapeStyle" in el && el.shapeStyle) {
|
|
881
|
+
const ss = el.shapeStyle;
|
|
882
|
+
if (params.fillColor !== void 0) {
|
|
883
|
+
ss.fillColor = params.fillColor;
|
|
884
|
+
}
|
|
885
|
+
if (params.strokeColor !== void 0) {
|
|
886
|
+
ss.strokeColor = params.strokeColor;
|
|
887
|
+
}
|
|
888
|
+
if (params.strokeWidth !== void 0) {
|
|
889
|
+
ss.strokeWidth = params.strokeWidth;
|
|
890
|
+
}
|
|
891
|
+
} else if (params.fillColor !== void 0 || params.strokeColor !== void 0 || params.strokeWidth !== void 0) {
|
|
892
|
+
if ("shapeStyle" in el) {
|
|
893
|
+
el.shapeStyle = {
|
|
894
|
+
fillColor: params.fillColor,
|
|
895
|
+
strokeColor: params.strokeColor,
|
|
896
|
+
strokeWidth: params.strokeWidth
|
|
897
|
+
};
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
return {
|
|
901
|
+
pptxData: ctx.pptxData,
|
|
902
|
+
dirty: true,
|
|
903
|
+
result: { elementId: params.elementId }
|
|
904
|
+
};
|
|
905
|
+
}
|
|
906
|
+
function deleteElements(ctx, params) {
|
|
907
|
+
const err = validateSlideIndex(params.slideIndex, ctx.pptxData.slides.length);
|
|
908
|
+
if (err) {
|
|
909
|
+
throw new Error(err);
|
|
910
|
+
}
|
|
911
|
+
const slide = ctx.pptxData.slides[params.slideIndex];
|
|
912
|
+
const idSet = new Set(params.elementIds);
|
|
913
|
+
const notFound = params.elementIds.filter((id) => !slide.elements.some((e) => e.id === id));
|
|
914
|
+
if (notFound.length > 0) {
|
|
915
|
+
throw new Error(`Elements not found: ${notFound.join(", ")}`);
|
|
916
|
+
}
|
|
917
|
+
const before = slide.elements.length;
|
|
918
|
+
slide.elements = slide.elements.filter((e) => !idSet.has(e.id));
|
|
919
|
+
return {
|
|
920
|
+
pptxData: ctx.pptxData,
|
|
921
|
+
dirty: true,
|
|
922
|
+
result: { deletedCount: before - slide.elements.length }
|
|
923
|
+
};
|
|
924
|
+
}
|
|
925
|
+
function arrangeElements(ctx, params) {
|
|
926
|
+
const err = validateSlideIndex(params.slideIndex, ctx.pptxData.slides.length);
|
|
927
|
+
if (err) {
|
|
928
|
+
throw new Error(err);
|
|
929
|
+
}
|
|
930
|
+
const slide = ctx.pptxData.slides[params.slideIndex];
|
|
931
|
+
if (params.action === "align") {
|
|
932
|
+
if (!params.elementIds || params.elementIds.length === 0) {
|
|
933
|
+
throw new Error("elementIds is required for align action.");
|
|
934
|
+
}
|
|
935
|
+
const targets = slide.elements.filter((e) => params.elementIds.includes(e.id));
|
|
936
|
+
if (targets.length === 0) {
|
|
937
|
+
throw new Error("No matching elements found for align.");
|
|
938
|
+
}
|
|
939
|
+
switch (params.alignment) {
|
|
940
|
+
case "left": {
|
|
941
|
+
const minX = Math.min(...targets.map((e) => e.x));
|
|
942
|
+
for (const t of targets) {
|
|
943
|
+
t.x = minX;
|
|
944
|
+
}
|
|
945
|
+
break;
|
|
946
|
+
}
|
|
947
|
+
case "right": {
|
|
948
|
+
const maxRight = Math.max(...targets.map((e) => e.x + e.width));
|
|
949
|
+
for (const t of targets) {
|
|
950
|
+
t.x = maxRight - t.width;
|
|
951
|
+
}
|
|
952
|
+
break;
|
|
953
|
+
}
|
|
954
|
+
case "top": {
|
|
955
|
+
const minY = Math.min(...targets.map((e) => e.y));
|
|
956
|
+
for (const t of targets) {
|
|
957
|
+
t.y = minY;
|
|
958
|
+
}
|
|
959
|
+
break;
|
|
960
|
+
}
|
|
961
|
+
case "bottom": {
|
|
962
|
+
const maxBottom = Math.max(...targets.map((e) => e.y + e.height));
|
|
963
|
+
for (const t of targets) {
|
|
964
|
+
t.y = maxBottom - t.height;
|
|
965
|
+
}
|
|
966
|
+
break;
|
|
967
|
+
}
|
|
968
|
+
case "centerH": {
|
|
969
|
+
const avgCX = targets.reduce((s, e) => s + e.x + e.width / 2, 0) / targets.length;
|
|
970
|
+
for (const t of targets) {
|
|
971
|
+
t.x = avgCX - t.width / 2;
|
|
972
|
+
}
|
|
973
|
+
break;
|
|
974
|
+
}
|
|
975
|
+
case "centerV": {
|
|
976
|
+
const avgCY = targets.reduce((s, e) => s + e.y + e.height / 2, 0) / targets.length;
|
|
977
|
+
for (const t of targets) {
|
|
978
|
+
t.y = avgCY - t.height / 2;
|
|
979
|
+
}
|
|
980
|
+
break;
|
|
981
|
+
}
|
|
982
|
+
default: {
|
|
983
|
+
throw new Error(`Unknown alignment: ${String(params.alignment)}`);
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
} else if (params.action === "reorderLayer") {
|
|
987
|
+
if (!params.elementId) {
|
|
988
|
+
throw new Error("elementId is required for reorderLayer action.");
|
|
989
|
+
}
|
|
990
|
+
const idx = slide.elements.findIndex((e) => e.id === params.elementId);
|
|
991
|
+
if (idx < 0) {
|
|
992
|
+
throw new Error(`Element '${params.elementId}' not found.`);
|
|
993
|
+
}
|
|
994
|
+
const [el] = slide.elements.splice(idx, 1);
|
|
995
|
+
switch (params.layerAction) {
|
|
996
|
+
case "bringToFront":
|
|
997
|
+
slide.elements.push(el);
|
|
998
|
+
break;
|
|
999
|
+
case "sendToBack":
|
|
1000
|
+
slide.elements.unshift(el);
|
|
1001
|
+
break;
|
|
1002
|
+
case "bringForward": {
|
|
1003
|
+
const newIdx = Math.min(idx + 1, slide.elements.length);
|
|
1004
|
+
slide.elements.splice(newIdx, 0, el);
|
|
1005
|
+
break;
|
|
1006
|
+
}
|
|
1007
|
+
case "sendBackward": {
|
|
1008
|
+
const newIdx = Math.max(idx - 1, 0);
|
|
1009
|
+
slide.elements.splice(newIdx, 0, el);
|
|
1010
|
+
break;
|
|
1011
|
+
}
|
|
1012
|
+
default: {
|
|
1013
|
+
slide.elements.splice(idx, 0, el);
|
|
1014
|
+
throw new Error(`Unknown layerAction: ${String(params.layerAction)}`);
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
} else {
|
|
1018
|
+
throw new Error(`Unknown action: ${String(params.action)}`);
|
|
1019
|
+
}
|
|
1020
|
+
return {
|
|
1021
|
+
pptxData: ctx.pptxData,
|
|
1022
|
+
dirty: true,
|
|
1023
|
+
result: { slideIndex: params.slideIndex }
|
|
1024
|
+
};
|
|
1025
|
+
}
|
|
1026
|
+
function cloneElement(ctx, params) {
|
|
1027
|
+
const err = validateSlideIndex(params.slideIndex, ctx.pptxData.slides.length);
|
|
1028
|
+
if (err) {
|
|
1029
|
+
throw new Error(err);
|
|
1030
|
+
}
|
|
1031
|
+
const srcSlide = ctx.pptxData.slides[params.slideIndex];
|
|
1032
|
+
const original = srcSlide.elements.find((e) => e.id === params.elementId);
|
|
1033
|
+
if (!original) {
|
|
1034
|
+
throw new Error(`Element '${params.elementId}' not found on slide ${params.slideIndex}.`);
|
|
1035
|
+
}
|
|
1036
|
+
const targets = params.targetSlideIndexes ?? [params.slideIndex];
|
|
1037
|
+
const clonedIds = [];
|
|
1038
|
+
const offsetX = params.offsetX ?? 20;
|
|
1039
|
+
const offsetY = params.offsetY ?? 20;
|
|
1040
|
+
for (const targetIdx of targets) {
|
|
1041
|
+
const terr = validateSlideIndex(targetIdx, ctx.pptxData.slides.length);
|
|
1042
|
+
if (terr) {
|
|
1043
|
+
throw new Error(terr);
|
|
1044
|
+
}
|
|
1045
|
+
const clone = structuredClone(original);
|
|
1046
|
+
clone.id = generateElementId();
|
|
1047
|
+
clone.x += offsetX;
|
|
1048
|
+
clone.y += offsetY;
|
|
1049
|
+
ctx.pptxData.slides[targetIdx].elements.push(clone);
|
|
1050
|
+
clonedIds.push(clone.id);
|
|
1051
|
+
}
|
|
1052
|
+
return {
|
|
1053
|
+
pptxData: ctx.pptxData,
|
|
1054
|
+
dirty: true,
|
|
1055
|
+
result: { clonedIds }
|
|
1056
|
+
};
|
|
1057
|
+
}
|
|
1058
|
+
function setElementAnimation(ctx, params) {
|
|
1059
|
+
const err = validateSlideIndex(params.slideIndex, ctx.pptxData.slides.length);
|
|
1060
|
+
if (err) {
|
|
1061
|
+
throw new Error(err);
|
|
1062
|
+
}
|
|
1063
|
+
const slide = ctx.pptxData.slides[params.slideIndex];
|
|
1064
|
+
const elExists = slide.elements.some((e) => e.id === params.elementId);
|
|
1065
|
+
if (!elExists) {
|
|
1066
|
+
throw new Error(`Element '${params.elementId}' not found on slide ${params.slideIndex}.`);
|
|
1067
|
+
}
|
|
1068
|
+
if (!slide.animations) {
|
|
1069
|
+
slide.animations = [];
|
|
1070
|
+
}
|
|
1071
|
+
const existing = slide.animations.find((a) => a.elementId === params.elementId);
|
|
1072
|
+
if (existing) {
|
|
1073
|
+
if (params.entrance !== void 0) {
|
|
1074
|
+
existing.entrance = params.entrance;
|
|
1075
|
+
}
|
|
1076
|
+
if (params.exit !== void 0) {
|
|
1077
|
+
existing.exit = params.exit;
|
|
1078
|
+
}
|
|
1079
|
+
if (params.durationMs !== void 0) {
|
|
1080
|
+
existing.durationMs = params.durationMs;
|
|
1081
|
+
}
|
|
1082
|
+
if (params.delayMs !== void 0) {
|
|
1083
|
+
existing.delayMs = params.delayMs;
|
|
1084
|
+
}
|
|
1085
|
+
if (params.order !== void 0) {
|
|
1086
|
+
existing.order = params.order;
|
|
1087
|
+
}
|
|
1088
|
+
} else {
|
|
1089
|
+
const anim = {
|
|
1090
|
+
elementId: params.elementId,
|
|
1091
|
+
entrance: params.entrance,
|
|
1092
|
+
exit: params.exit,
|
|
1093
|
+
durationMs: params.durationMs,
|
|
1094
|
+
delayMs: params.delayMs,
|
|
1095
|
+
order: params.order
|
|
1096
|
+
};
|
|
1097
|
+
slide.animations.push(anim);
|
|
1098
|
+
}
|
|
1099
|
+
return {
|
|
1100
|
+
pptxData: ctx.pptxData,
|
|
1101
|
+
dirty: true,
|
|
1102
|
+
result: { elementId: params.elementId }
|
|
1103
|
+
};
|
|
1104
|
+
}
|
|
1105
|
+
function groupElements(ctx, params) {
|
|
1106
|
+
const err = validateSlideIndex(params.slideIndex, ctx.pptxData.slides.length);
|
|
1107
|
+
if (err) {
|
|
1108
|
+
throw new Error(err);
|
|
1109
|
+
}
|
|
1110
|
+
if (params.elementIds.length < 2) {
|
|
1111
|
+
throw new Error("At least 2 elements are required to form a group.");
|
|
1112
|
+
}
|
|
1113
|
+
const slide = ctx.pptxData.slides[params.slideIndex];
|
|
1114
|
+
const idSet = new Set(params.elementIds);
|
|
1115
|
+
const children = slide.elements.filter((e) => idSet.has(e.id));
|
|
1116
|
+
if (children.length !== params.elementIds.length) {
|
|
1117
|
+
const missing = params.elementIds.filter((id) => !slide.elements.some((e) => e.id === id));
|
|
1118
|
+
throw new Error(`Elements not found: ${missing.join(", ")}`);
|
|
1119
|
+
}
|
|
1120
|
+
const minX = Math.min(...children.map((e) => e.x));
|
|
1121
|
+
const minY = Math.min(...children.map((e) => e.y));
|
|
1122
|
+
const maxX = Math.max(...children.map((e) => e.x + e.width));
|
|
1123
|
+
const maxY = Math.max(...children.map((e) => e.y + e.height));
|
|
1124
|
+
const localChildren = children.map((e) => {
|
|
1125
|
+
const c = structuredClone(e);
|
|
1126
|
+
c.x -= minX;
|
|
1127
|
+
c.y -= minY;
|
|
1128
|
+
return c;
|
|
1129
|
+
});
|
|
1130
|
+
slide.elements = slide.elements.filter((e) => !idSet.has(e.id));
|
|
1131
|
+
const groupId = generateElementId();
|
|
1132
|
+
const group = {
|
|
1133
|
+
id: groupId,
|
|
1134
|
+
type: "group",
|
|
1135
|
+
x: minX,
|
|
1136
|
+
y: minY,
|
|
1137
|
+
width: maxX - minX,
|
|
1138
|
+
height: maxY - minY,
|
|
1139
|
+
children: localChildren
|
|
1140
|
+
};
|
|
1141
|
+
slide.elements.push(group);
|
|
1142
|
+
return {
|
|
1143
|
+
pptxData: ctx.pptxData,
|
|
1144
|
+
dirty: true,
|
|
1145
|
+
result: { groupId }
|
|
1146
|
+
};
|
|
1147
|
+
}
|
|
1148
|
+
function ungroupElements(ctx, params) {
|
|
1149
|
+
const err = validateSlideIndex(params.slideIndex, ctx.pptxData.slides.length);
|
|
1150
|
+
if (err) {
|
|
1151
|
+
throw new Error(err);
|
|
1152
|
+
}
|
|
1153
|
+
const slide = ctx.pptxData.slides[params.slideIndex];
|
|
1154
|
+
const groupIdx = slide.elements.findIndex((e) => e.id === params.groupElementId);
|
|
1155
|
+
if (groupIdx < 0) {
|
|
1156
|
+
throw new Error(`Group element '${params.groupElementId}' not found.`);
|
|
1157
|
+
}
|
|
1158
|
+
const group = slide.elements[groupIdx];
|
|
1159
|
+
if (group.type !== "group") {
|
|
1160
|
+
throw new Error(`Element '${params.groupElementId}' is not a group.`);
|
|
1161
|
+
}
|
|
1162
|
+
const grp = group;
|
|
1163
|
+
const restored = grp.children.map((child) => {
|
|
1164
|
+
const c = structuredClone(child);
|
|
1165
|
+
c.x += grp.x;
|
|
1166
|
+
c.y += grp.y;
|
|
1167
|
+
return c;
|
|
1168
|
+
});
|
|
1169
|
+
slide.elements.splice(groupIdx, 1, ...restored);
|
|
1170
|
+
return {
|
|
1171
|
+
pptxData: ctx.pptxData,
|
|
1172
|
+
dirty: true,
|
|
1173
|
+
result: { restoredIds: restored.map((c) => c.id) }
|
|
1174
|
+
};
|
|
1175
|
+
}
|
|
1176
|
+
function batchUpdateElements(ctx, params) {
|
|
1177
|
+
const err = validateSlideIndex(params.slideIndex, ctx.pptxData.slides.length);
|
|
1178
|
+
if (err) {
|
|
1179
|
+
throw new Error(err);
|
|
1180
|
+
}
|
|
1181
|
+
const slide = ctx.pptxData.slides[params.slideIndex];
|
|
1182
|
+
const idSet = new Set(params.elementIds);
|
|
1183
|
+
let count = 0;
|
|
1184
|
+
for (const el of slide.elements) {
|
|
1185
|
+
if (!idSet.has(el.id)) {
|
|
1186
|
+
continue;
|
|
1187
|
+
}
|
|
1188
|
+
if (params.x !== void 0) {
|
|
1189
|
+
el.x = params.x;
|
|
1190
|
+
}
|
|
1191
|
+
if (params.y !== void 0) {
|
|
1192
|
+
el.y = params.y;
|
|
1193
|
+
}
|
|
1194
|
+
if (params.width !== void 0) {
|
|
1195
|
+
el.width = params.width;
|
|
1196
|
+
}
|
|
1197
|
+
if (params.height !== void 0) {
|
|
1198
|
+
el.height = params.height;
|
|
1199
|
+
}
|
|
1200
|
+
if (params.rotation !== void 0) {
|
|
1201
|
+
el.rotation = params.rotation;
|
|
1202
|
+
}
|
|
1203
|
+
if (params.opacity !== void 0) {
|
|
1204
|
+
el.opacity = params.opacity;
|
|
1205
|
+
}
|
|
1206
|
+
if (params.hidden !== void 0) {
|
|
1207
|
+
el.hidden = params.hidden;
|
|
1208
|
+
}
|
|
1209
|
+
count++;
|
|
1210
|
+
}
|
|
1211
|
+
return {
|
|
1212
|
+
pptxData: ctx.pptxData,
|
|
1213
|
+
dirty: true,
|
|
1214
|
+
result: { updatedCount: count }
|
|
1215
|
+
};
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1218
|
+
// src/tools/slide-tools.ts
|
|
1219
|
+
function getSlide(ctx, params) {
|
|
1220
|
+
const err = validateSlideIndex(params.slideIndex, ctx.pptxData.slides.length);
|
|
1221
|
+
if (err) {
|
|
1222
|
+
throw new Error(err);
|
|
1223
|
+
}
|
|
1224
|
+
const slide = ctx.pptxData.slides[params.slideIndex];
|
|
1225
|
+
return {
|
|
1226
|
+
pptxData: ctx.pptxData,
|
|
1227
|
+
dirty: false,
|
|
1228
|
+
result: {
|
|
1229
|
+
slideIndex: params.slideIndex,
|
|
1230
|
+
slideNumber: slide.slideNumber,
|
|
1231
|
+
hidden: slide.hidden ?? false,
|
|
1232
|
+
layoutName: slide.layoutName,
|
|
1233
|
+
backgroundColor: slide.backgroundColor,
|
|
1234
|
+
notes: slide.notes ?? "",
|
|
1235
|
+
commentCount: slide.comments?.length ?? 0,
|
|
1236
|
+
elements: slide.elements.map(describeElement)
|
|
1237
|
+
}
|
|
1238
|
+
};
|
|
1239
|
+
}
|
|
1240
|
+
function addSlide(ctx, params) {
|
|
1241
|
+
const { slides } = ctx.pptxData;
|
|
1242
|
+
const insertAt = params.insertAfterIndex !== void 0 && params.insertAfterIndex >= 0 ? Math.min(params.insertAfterIndex + 1, slides.length) : slides.length;
|
|
1243
|
+
const newSlide = {
|
|
1244
|
+
id: generateSlideId(),
|
|
1245
|
+
rId: "",
|
|
1246
|
+
slideNumber: insertAt + 1,
|
|
1247
|
+
elements: [],
|
|
1248
|
+
backgroundColor: params.backgroundColor
|
|
1249
|
+
};
|
|
1250
|
+
slides.splice(insertAt, 0, newSlide);
|
|
1251
|
+
slides.forEach((s, i) => {
|
|
1252
|
+
s.slideNumber = i + 1;
|
|
1253
|
+
});
|
|
1254
|
+
return {
|
|
1255
|
+
pptxData: ctx.pptxData,
|
|
1256
|
+
dirty: true,
|
|
1257
|
+
result: { newSlideIndex: insertAt, slideCount: slides.length }
|
|
1258
|
+
};
|
|
1259
|
+
}
|
|
1260
|
+
function deleteSlides(ctx, params) {
|
|
1261
|
+
const { slides } = ctx.pptxData;
|
|
1262
|
+
if (!params.slideIndexes || params.slideIndexes.length === 0) {
|
|
1263
|
+
throw new Error("slideIndexes is required for the delete action.");
|
|
1264
|
+
}
|
|
1265
|
+
const indexSet = new Set(params.slideIndexes);
|
|
1266
|
+
const invalid = params.slideIndexes.filter((i) => i < 0 || i >= slides.length);
|
|
1267
|
+
if (invalid.length > 0) {
|
|
1268
|
+
throw new Error(
|
|
1269
|
+
`Invalid slide indexes: ${invalid.join(", ")}. Valid range: 0\u2013${slides.length - 1}.`
|
|
1270
|
+
);
|
|
1271
|
+
}
|
|
1272
|
+
if (indexSet.size >= slides.length) {
|
|
1273
|
+
throw new Error("Cannot delete all slides. At least one slide must remain.");
|
|
1274
|
+
}
|
|
1275
|
+
ctx.pptxData.slides = slides.filter((_, i) => !indexSet.has(i));
|
|
1276
|
+
ctx.pptxData.slides.forEach((s, i) => {
|
|
1277
|
+
s.slideNumber = i + 1;
|
|
1278
|
+
});
|
|
1279
|
+
return {
|
|
1280
|
+
pptxData: ctx.pptxData,
|
|
1281
|
+
dirty: true,
|
|
1282
|
+
result: { deletedCount: indexSet.size, slideCount: ctx.pptxData.slides.length }
|
|
1283
|
+
};
|
|
1284
|
+
}
|
|
1285
|
+
function reorderSlides(ctx, params) {
|
|
1286
|
+
const { slides } = ctx.pptxData;
|
|
1287
|
+
if (!params.newOrder || params.newOrder.length !== slides.length) {
|
|
1288
|
+
throw new Error(
|
|
1289
|
+
`newOrder must have exactly ${slides.length} indexes. Got ${params.newOrder?.length ?? 0}.`
|
|
1290
|
+
);
|
|
1291
|
+
}
|
|
1292
|
+
const sorted = [...params.newOrder].sort((a, b) => a - b);
|
|
1293
|
+
const expected = slides.map((_, i) => i);
|
|
1294
|
+
if (JSON.stringify(sorted) !== JSON.stringify(expected)) {
|
|
1295
|
+
throw new Error(
|
|
1296
|
+
`newOrder must contain each index from 0 to ${slides.length - 1} exactly once.`
|
|
1297
|
+
);
|
|
1298
|
+
}
|
|
1299
|
+
ctx.pptxData.slides = params.newOrder.map((idx) => slides[idx]);
|
|
1300
|
+
ctx.pptxData.slides.forEach((s, i) => {
|
|
1301
|
+
s.slideNumber = i + 1;
|
|
1302
|
+
});
|
|
1303
|
+
return {
|
|
1304
|
+
pptxData: ctx.pptxData,
|
|
1305
|
+
dirty: true,
|
|
1306
|
+
result: { slideCount: ctx.pptxData.slides.length }
|
|
1307
|
+
};
|
|
1308
|
+
}
|
|
1309
|
+
function duplicateSlide(ctx, params) {
|
|
1310
|
+
const { slides } = ctx.pptxData;
|
|
1311
|
+
const err = validateSlideIndex(params.slideIndex, slides.length);
|
|
1312
|
+
if (err) {
|
|
1313
|
+
throw new Error(err);
|
|
1314
|
+
}
|
|
1315
|
+
const original = slides[params.slideIndex];
|
|
1316
|
+
const clone = structuredClone(original);
|
|
1317
|
+
clone.id = generateSlideId();
|
|
1318
|
+
clone.rId = "";
|
|
1319
|
+
for (const el of clone.elements) {
|
|
1320
|
+
el.id = generateElementId();
|
|
1321
|
+
}
|
|
1322
|
+
const insertIndex = params.targetIndex ?? params.slideIndex + 1;
|
|
1323
|
+
slides.splice(insertIndex, 0, clone);
|
|
1324
|
+
slides.forEach((s, i) => {
|
|
1325
|
+
s.slideNumber = i + 1;
|
|
1326
|
+
});
|
|
1327
|
+
return {
|
|
1328
|
+
pptxData: ctx.pptxData,
|
|
1329
|
+
dirty: true,
|
|
1330
|
+
result: { newSlideIndex: insertIndex, slideCount: slides.length }
|
|
1331
|
+
};
|
|
1332
|
+
}
|
|
1333
|
+
function updateSlideProperties(ctx, params) {
|
|
1334
|
+
const err = validateSlideIndex(params.slideIndex, ctx.pptxData.slides.length);
|
|
1335
|
+
if (err) {
|
|
1336
|
+
throw new Error(err);
|
|
1337
|
+
}
|
|
1338
|
+
const slide = ctx.pptxData.slides[params.slideIndex];
|
|
1339
|
+
if (params.backgroundColor !== void 0) {
|
|
1340
|
+
slide.backgroundColor = params.backgroundColor;
|
|
1341
|
+
}
|
|
1342
|
+
if (params.backgroundGradient !== void 0) {
|
|
1343
|
+
slide.backgroundGradient = params.backgroundGradient;
|
|
1344
|
+
}
|
|
1345
|
+
if (params.backgroundImage !== void 0) {
|
|
1346
|
+
slide.backgroundImage = params.backgroundImage;
|
|
1347
|
+
}
|
|
1348
|
+
if (params.notes !== void 0) {
|
|
1349
|
+
slide.notes = params.notes;
|
|
1350
|
+
}
|
|
1351
|
+
if (params.hidden !== void 0) {
|
|
1352
|
+
slide.hidden = params.hidden;
|
|
1353
|
+
}
|
|
1354
|
+
return {
|
|
1355
|
+
pptxData: ctx.pptxData,
|
|
1356
|
+
dirty: true,
|
|
1357
|
+
result: { slideIndex: params.slideIndex }
|
|
1358
|
+
};
|
|
1359
|
+
}
|
|
1360
|
+
function setSlideTransition(ctx, params) {
|
|
1361
|
+
const err = validateSlideIndex(params.slideIndex, ctx.pptxData.slides.length);
|
|
1362
|
+
if (err) {
|
|
1363
|
+
throw new Error(err);
|
|
1364
|
+
}
|
|
1365
|
+
const slide = ctx.pptxData.slides[params.slideIndex];
|
|
1366
|
+
if (params.type === "none") {
|
|
1367
|
+
slide.transition = void 0;
|
|
1368
|
+
} else {
|
|
1369
|
+
const transition = {
|
|
1370
|
+
type: params.type
|
|
1371
|
+
};
|
|
1372
|
+
if (params.durationMs !== void 0) {
|
|
1373
|
+
transition.durationMs = params.durationMs;
|
|
1374
|
+
}
|
|
1375
|
+
if (params.direction !== void 0) {
|
|
1376
|
+
transition.direction = params.direction;
|
|
1377
|
+
}
|
|
1378
|
+
if (params.advanceOnClick !== void 0) {
|
|
1379
|
+
transition.advanceOnClick = params.advanceOnClick;
|
|
1380
|
+
}
|
|
1381
|
+
if (params.advanceAfterMs !== void 0) {
|
|
1382
|
+
transition.advanceAfterMs = params.advanceAfterMs;
|
|
1383
|
+
}
|
|
1384
|
+
slide.transition = transition;
|
|
1385
|
+
}
|
|
1386
|
+
return {
|
|
1387
|
+
pptxData: ctx.pptxData,
|
|
1388
|
+
dirty: true,
|
|
1389
|
+
result: { slideIndex: params.slideIndex, transitionType: params.type }
|
|
1390
|
+
};
|
|
1391
|
+
}
|
|
1392
|
+
function setCanvasSize(ctx, params) {
|
|
1393
|
+
ctx.pptxData.width = params.width;
|
|
1394
|
+
ctx.pptxData.height = params.height;
|
|
1395
|
+
if (ctx.pptxData.widthEmu !== void 0) {
|
|
1396
|
+
ctx.pptxData.widthEmu = Math.round(params.width * 12700);
|
|
1397
|
+
}
|
|
1398
|
+
if (ctx.pptxData.heightEmu !== void 0) {
|
|
1399
|
+
ctx.pptxData.heightEmu = Math.round(params.height * 12700);
|
|
1400
|
+
}
|
|
1401
|
+
return {
|
|
1402
|
+
pptxData: ctx.pptxData,
|
|
1403
|
+
dirty: true,
|
|
1404
|
+
result: { canvasWidth: params.width, canvasHeight: params.height }
|
|
1405
|
+
};
|
|
1406
|
+
}
|
|
1407
|
+
function updateElementStyle(ctx, params) {
|
|
1408
|
+
const err = validateSlideIndex(params.slideIndex, ctx.pptxData.slides.length);
|
|
1409
|
+
if (err) {
|
|
1410
|
+
throw new Error(err);
|
|
1411
|
+
}
|
|
1412
|
+
const slide = ctx.pptxData.slides[params.slideIndex];
|
|
1413
|
+
const el = slide.elements.find((e) => e.id === params.elementId);
|
|
1414
|
+
if (!el) {
|
|
1415
|
+
throw new Error(`Element '${params.elementId}' not found on slide ${params.slideIndex}.`);
|
|
1416
|
+
}
|
|
1417
|
+
if ("shapeStyle" in el) {
|
|
1418
|
+
if (!el.shapeStyle) {
|
|
1419
|
+
el.shapeStyle = {};
|
|
1420
|
+
}
|
|
1421
|
+
const ss = el.shapeStyle;
|
|
1422
|
+
if (params.fillColor !== void 0) {
|
|
1423
|
+
ss.fillColor = params.fillColor;
|
|
1424
|
+
}
|
|
1425
|
+
if (params.fillMode !== void 0) {
|
|
1426
|
+
ss.fillMode = params.fillMode;
|
|
1427
|
+
}
|
|
1428
|
+
if (params.fillGradientStops !== void 0) {
|
|
1429
|
+
ss.fillGradientStops = params.fillGradientStops;
|
|
1430
|
+
}
|
|
1431
|
+
if (params.fillGradientAngle !== void 0) {
|
|
1432
|
+
ss.fillGradientAngle = params.fillGradientAngle;
|
|
1433
|
+
}
|
|
1434
|
+
if (params.fillGradientType !== void 0) {
|
|
1435
|
+
ss.fillGradientType = params.fillGradientType;
|
|
1436
|
+
}
|
|
1437
|
+
if (params.fillOpacity !== void 0) {
|
|
1438
|
+
ss.fillOpacity = params.fillOpacity;
|
|
1439
|
+
}
|
|
1440
|
+
if (params.strokeColor !== void 0) {
|
|
1441
|
+
ss.strokeColor = params.strokeColor;
|
|
1442
|
+
}
|
|
1443
|
+
if (params.strokeWidth !== void 0) {
|
|
1444
|
+
ss.strokeWidth = params.strokeWidth;
|
|
1445
|
+
}
|
|
1446
|
+
if (params.strokeDash !== void 0) {
|
|
1447
|
+
ss.strokeDash = params.strokeDash;
|
|
1448
|
+
}
|
|
1449
|
+
if (params.strokeOpacity !== void 0) {
|
|
1450
|
+
ss.strokeOpacity = params.strokeOpacity;
|
|
1451
|
+
}
|
|
1452
|
+
if (params.shadowColor !== void 0) {
|
|
1453
|
+
ss.shadowColor = params.shadowColor;
|
|
1454
|
+
}
|
|
1455
|
+
if (params.shadowBlur !== void 0) {
|
|
1456
|
+
ss.shadowBlur = params.shadowBlur;
|
|
1457
|
+
}
|
|
1458
|
+
if (params.shadowOffsetX !== void 0) {
|
|
1459
|
+
ss.shadowOffsetX = params.shadowOffsetX;
|
|
1460
|
+
}
|
|
1461
|
+
if (params.shadowOffsetY !== void 0) {
|
|
1462
|
+
ss.shadowOffsetY = params.shadowOffsetY;
|
|
1463
|
+
}
|
|
1464
|
+
if (params.shadowOpacity !== void 0) {
|
|
1465
|
+
ss.shadowOpacity = params.shadowOpacity;
|
|
1466
|
+
}
|
|
1467
|
+
if (params.glowColor !== void 0) {
|
|
1468
|
+
ss.glowColor = params.glowColor;
|
|
1469
|
+
}
|
|
1470
|
+
if (params.glowRadius !== void 0) {
|
|
1471
|
+
ss.glowRadius = params.glowRadius;
|
|
1472
|
+
}
|
|
1473
|
+
if (params.glowOpacity !== void 0) {
|
|
1474
|
+
ss.glowOpacity = params.glowOpacity;
|
|
1475
|
+
}
|
|
1476
|
+
if (params.softEdgeRadius !== void 0) {
|
|
1477
|
+
ss.softEdgeRadius = params.softEdgeRadius;
|
|
1478
|
+
}
|
|
1479
|
+
}
|
|
1480
|
+
if (el.type === "image" || el.type === "picture") {
|
|
1481
|
+
const img = el;
|
|
1482
|
+
if (params.altText !== void 0) {
|
|
1483
|
+
img.altText = params.altText;
|
|
1484
|
+
}
|
|
1485
|
+
if (params.cropLeft !== void 0) {
|
|
1486
|
+
img.cropLeft = params.cropLeft;
|
|
1487
|
+
}
|
|
1488
|
+
if (params.cropTop !== void 0) {
|
|
1489
|
+
img.cropTop = params.cropTop;
|
|
1490
|
+
}
|
|
1491
|
+
if (params.cropRight !== void 0) {
|
|
1492
|
+
img.cropRight = params.cropRight;
|
|
1493
|
+
}
|
|
1494
|
+
if (params.cropBottom !== void 0) {
|
|
1495
|
+
img.cropBottom = params.cropBottom;
|
|
1496
|
+
}
|
|
1497
|
+
if (params.brightness !== void 0 || params.contrast !== void 0 || params.grayscale !== void 0) {
|
|
1498
|
+
if (!img.imageEffects) {
|
|
1499
|
+
img.imageEffects = {};
|
|
1500
|
+
}
|
|
1501
|
+
if (params.brightness !== void 0) {
|
|
1502
|
+
img.imageEffects.brightness = params.brightness;
|
|
1503
|
+
}
|
|
1504
|
+
if (params.contrast !== void 0) {
|
|
1505
|
+
img.imageEffects.contrast = params.contrast;
|
|
1506
|
+
}
|
|
1507
|
+
if (params.grayscale !== void 0) {
|
|
1508
|
+
img.imageEffects.grayscale = params.grayscale;
|
|
1509
|
+
}
|
|
1510
|
+
}
|
|
1511
|
+
}
|
|
1512
|
+
return {
|
|
1513
|
+
pptxData: ctx.pptxData,
|
|
1514
|
+
dirty: true,
|
|
1515
|
+
result: { elementId: params.elementId }
|
|
1516
|
+
};
|
|
1517
|
+
}
|
|
1518
|
+
function runAccessibilityCheck(ctx) {
|
|
1519
|
+
const issues = [];
|
|
1520
|
+
for (let si = 0; si < ctx.pptxData.slides.length; si++) {
|
|
1521
|
+
const slide = ctx.pptxData.slides[si];
|
|
1522
|
+
let slideHasText = false;
|
|
1523
|
+
for (const el of slide.elements) {
|
|
1524
|
+
if (el.type === "image" || el.type === "picture") {
|
|
1525
|
+
const img = el;
|
|
1526
|
+
if (!img.altText) {
|
|
1527
|
+
issues.push({
|
|
1528
|
+
slideIndex: si,
|
|
1529
|
+
elementId: el.id,
|
|
1530
|
+
severity: "error",
|
|
1531
|
+
message: `Image element '${el.id}' has no alt text.`
|
|
1532
|
+
});
|
|
1533
|
+
}
|
|
1534
|
+
}
|
|
1535
|
+
if (pptxViewerCore.hasTextProperties(el)) {
|
|
1536
|
+
const textEl = el;
|
|
1537
|
+
slideHasText = true;
|
|
1538
|
+
if (!textEl.text || textEl.text.trim() === "") {
|
|
1539
|
+
issues.push({
|
|
1540
|
+
slideIndex: si,
|
|
1541
|
+
elementId: el.id,
|
|
1542
|
+
severity: "info",
|
|
1543
|
+
message: `Text element '${el.id}' is empty.`
|
|
1544
|
+
});
|
|
1545
|
+
}
|
|
1546
|
+
const fs = textEl.textStyle?.fontSize;
|
|
1547
|
+
if (fs !== void 0 && fs < 10) {
|
|
1548
|
+
issues.push({
|
|
1549
|
+
slideIndex: si,
|
|
1550
|
+
elementId: el.id,
|
|
1551
|
+
severity: "warning",
|
|
1552
|
+
message: `Text element '${el.id}' has very small font size (${fs}pt < 10pt).`
|
|
1553
|
+
});
|
|
1554
|
+
}
|
|
1555
|
+
const textColor = textEl.textStyle?.color?.toLowerCase();
|
|
1556
|
+
const bgColor = slide.backgroundColor?.toLowerCase();
|
|
1557
|
+
if (textColor && bgColor && textColor === bgColor) {
|
|
1558
|
+
issues.push({
|
|
1559
|
+
slideIndex: si,
|
|
1560
|
+
elementId: el.id,
|
|
1561
|
+
severity: "error",
|
|
1562
|
+
message: `Text element '${el.id}' has same color as slide background (${textColor}).`
|
|
1563
|
+
});
|
|
1564
|
+
}
|
|
1565
|
+
}
|
|
1566
|
+
}
|
|
1567
|
+
if (!slideHasText) {
|
|
1568
|
+
issues.push({
|
|
1569
|
+
slideIndex: si,
|
|
1570
|
+
severity: "warning",
|
|
1571
|
+
message: `Slide ${si + 1} contains no text elements.`
|
|
1572
|
+
});
|
|
1573
|
+
}
|
|
1574
|
+
}
|
|
1575
|
+
const errorCount = issues.filter((i) => i.severity === "error").length;
|
|
1576
|
+
const warningCount = issues.filter((i) => i.severity === "warning").length;
|
|
1577
|
+
const infoCount = issues.filter((i) => i.severity === "info").length;
|
|
1578
|
+
return {
|
|
1579
|
+
pptxData: ctx.pptxData,
|
|
1580
|
+
dirty: false,
|
|
1581
|
+
result: {
|
|
1582
|
+
slideCount: ctx.pptxData.slides.length,
|
|
1583
|
+
totalIssues: issues.length,
|
|
1584
|
+
errorCount,
|
|
1585
|
+
warningCount,
|
|
1586
|
+
infoCount,
|
|
1587
|
+
issues
|
|
1588
|
+
}
|
|
1589
|
+
};
|
|
1590
|
+
}
|
|
1591
|
+
|
|
1592
|
+
// src/tools/table-tools.ts
|
|
1593
|
+
function updateTableCells(ctx, params) {
|
|
1594
|
+
const err = validateSlideIndex(params.slideIndex, ctx.pptxData.slides.length);
|
|
1595
|
+
if (err) {
|
|
1596
|
+
throw new Error(err);
|
|
1597
|
+
}
|
|
1598
|
+
const slide = ctx.pptxData.slides[params.slideIndex];
|
|
1599
|
+
const el = slide.elements.find((e) => e.id === params.elementId);
|
|
1600
|
+
if (!el) {
|
|
1601
|
+
throw new Error(`Element '${params.elementId}' not found on slide ${params.slideIndex}.`);
|
|
1602
|
+
}
|
|
1603
|
+
if (el.type !== "table") {
|
|
1604
|
+
throw new Error(`Element '${params.elementId}' is not a table.`);
|
|
1605
|
+
}
|
|
1606
|
+
const tbl = el;
|
|
1607
|
+
if (!tbl.tableData) {
|
|
1608
|
+
throw new Error(`Table '${params.elementId}' has no tableData.`);
|
|
1609
|
+
}
|
|
1610
|
+
let count = 0;
|
|
1611
|
+
for (const cellUpdate of params.cells) {
|
|
1612
|
+
const row = tbl.tableData.rows[cellUpdate.row];
|
|
1613
|
+
if (!row) {
|
|
1614
|
+
throw new Error(
|
|
1615
|
+
`Row ${cellUpdate.row} out of range (table has ${tbl.tableData.rows.length} rows).`
|
|
1616
|
+
);
|
|
1617
|
+
}
|
|
1618
|
+
const cell = row.cells[cellUpdate.col];
|
|
1619
|
+
if (!cell) {
|
|
1620
|
+
throw new Error(`Column ${cellUpdate.col} out of range (row has ${row.cells.length} cells).`);
|
|
1621
|
+
}
|
|
1622
|
+
cell.text = cellUpdate.text;
|
|
1623
|
+
count++;
|
|
1624
|
+
}
|
|
1625
|
+
return {
|
|
1626
|
+
pptxData: ctx.pptxData,
|
|
1627
|
+
dirty: true,
|
|
1628
|
+
result: { updatedCount: count }
|
|
1629
|
+
};
|
|
1630
|
+
}
|
|
1631
|
+
function manageTableStructure(ctx, params) {
|
|
1632
|
+
const err = validateSlideIndex(params.slideIndex, ctx.pptxData.slides.length);
|
|
1633
|
+
if (err) {
|
|
1634
|
+
throw new Error(err);
|
|
1635
|
+
}
|
|
1636
|
+
const slide = ctx.pptxData.slides[params.slideIndex];
|
|
1637
|
+
const el = slide.elements.find((e) => e.id === params.elementId);
|
|
1638
|
+
if (!el) {
|
|
1639
|
+
throw new Error(`Element '${params.elementId}' not found on slide ${params.slideIndex}.`);
|
|
1640
|
+
}
|
|
1641
|
+
if (el.type !== "table") {
|
|
1642
|
+
throw new Error(`Element '${params.elementId}' is not a table.`);
|
|
1643
|
+
}
|
|
1644
|
+
const tbl = el;
|
|
1645
|
+
if (!tbl.tableData) {
|
|
1646
|
+
throw new Error(`Table '${params.elementId}' has no tableData.`);
|
|
1647
|
+
}
|
|
1648
|
+
const { rows, columnWidths } = tbl.tableData;
|
|
1649
|
+
const colCount = columnWidths.length;
|
|
1650
|
+
const rowCount = rows.length;
|
|
1651
|
+
switch (params.action) {
|
|
1652
|
+
case "insertRow": {
|
|
1653
|
+
const pos = params.position !== void 0 ? Math.min(Math.max(params.position, 0), rowCount) : rowCount;
|
|
1654
|
+
const newRow = {
|
|
1655
|
+
height: 40,
|
|
1656
|
+
cells: Array.from({ length: colCount }, (_, c) => ({
|
|
1657
|
+
text: params.cellTexts?.[c] ?? ""
|
|
1658
|
+
}))
|
|
1659
|
+
};
|
|
1660
|
+
rows.splice(pos, 0, newRow);
|
|
1661
|
+
break;
|
|
1662
|
+
}
|
|
1663
|
+
case "deleteRow": {
|
|
1664
|
+
if (rowCount <= 1) {
|
|
1665
|
+
throw new Error("Cannot delete the last row of a table.");
|
|
1666
|
+
}
|
|
1667
|
+
const ref = params.referenceIndex ?? rowCount - 1;
|
|
1668
|
+
if (ref < 0 || ref >= rowCount) {
|
|
1669
|
+
throw new Error(`Row index ${ref} out of range (0\u2013${rowCount - 1}).`);
|
|
1670
|
+
}
|
|
1671
|
+
rows.splice(ref, 1);
|
|
1672
|
+
break;
|
|
1673
|
+
}
|
|
1674
|
+
case "insertColumn": {
|
|
1675
|
+
const pos = params.position !== void 0 ? Math.min(Math.max(params.position, 0), colCount) : colCount;
|
|
1676
|
+
const newWidth = 1 / (colCount + 1);
|
|
1677
|
+
const scaleFactor = colCount / (colCount + 1);
|
|
1678
|
+
for (let i = 0; i < columnWidths.length; i++) {
|
|
1679
|
+
columnWidths[i] *= scaleFactor;
|
|
1680
|
+
}
|
|
1681
|
+
columnWidths.splice(pos, 0, newWidth);
|
|
1682
|
+
for (let r = 0; r < rows.length; r++) {
|
|
1683
|
+
rows[r].cells.splice(pos, 0, {
|
|
1684
|
+
text: params.cellTexts?.[r] ?? ""
|
|
1685
|
+
});
|
|
1686
|
+
}
|
|
1687
|
+
break;
|
|
1688
|
+
}
|
|
1689
|
+
case "deleteColumn": {
|
|
1690
|
+
if (colCount <= 1) {
|
|
1691
|
+
throw new Error("Cannot delete the last column of a table.");
|
|
1692
|
+
}
|
|
1693
|
+
const ref = params.referenceIndex ?? colCount - 1;
|
|
1694
|
+
if (ref < 0 || ref >= colCount) {
|
|
1695
|
+
throw new Error(`Column index ${ref} out of range (0\u2013${colCount - 1}).`);
|
|
1696
|
+
}
|
|
1697
|
+
const removedWidth = columnWidths[ref];
|
|
1698
|
+
columnWidths.splice(ref, 1);
|
|
1699
|
+
const remaining = columnWidths.length;
|
|
1700
|
+
if (remaining > 0) {
|
|
1701
|
+
const extra = removedWidth / remaining;
|
|
1702
|
+
for (let i = 0; i < columnWidths.length; i++) {
|
|
1703
|
+
columnWidths[i] += extra;
|
|
1704
|
+
}
|
|
1705
|
+
}
|
|
1706
|
+
for (const row of rows) {
|
|
1707
|
+
row.cells.splice(ref, 1);
|
|
1708
|
+
}
|
|
1709
|
+
break;
|
|
1710
|
+
}
|
|
1711
|
+
default: {
|
|
1712
|
+
throw new Error(`Unknown action: ${String(params.action)}`);
|
|
1713
|
+
}
|
|
1714
|
+
}
|
|
1715
|
+
return {
|
|
1716
|
+
pptxData: ctx.pptxData,
|
|
1717
|
+
dirty: true,
|
|
1718
|
+
result: {
|
|
1719
|
+
rowCount: tbl.tableData.rows.length,
|
|
1720
|
+
columnCount: tbl.tableData.columnWidths.length
|
|
1721
|
+
}
|
|
1722
|
+
};
|
|
1723
|
+
}
|
|
1724
|
+
var PPTX_MIME_TYPE = "application/vnd.openxmlformats-officedocument.presentationml.presentation";
|
|
1725
|
+
async function loadPresentation(filePath, execCtx) {
|
|
1726
|
+
const rawBytes = await execCtx.filesystem.readFile(filePath);
|
|
1727
|
+
if (execCtx.collaboration) {
|
|
1728
|
+
const room = execCtx.collaboration.getRoom(filePath);
|
|
1729
|
+
if (room) {
|
|
1730
|
+
const codec = execCtx.collaboration.getCodec(filePath);
|
|
1731
|
+
if (codec) {
|
|
1732
|
+
const currentBytes = await codec.dehydrate(room.ydoc);
|
|
1733
|
+
const handler2 = new pptxViewerCore.PptxHandler();
|
|
1734
|
+
const pptxData2 = await handler2.load(currentBytes.buffer);
|
|
1735
|
+
return { pptxData: pptxData2, rawBytes };
|
|
1736
|
+
}
|
|
1737
|
+
}
|
|
1738
|
+
}
|
|
1739
|
+
const handler = new pptxViewerCore.PptxHandler();
|
|
1740
|
+
const pptxData = await handler.load(rawBytes.buffer);
|
|
1741
|
+
return { pptxData, rawBytes };
|
|
1742
|
+
}
|
|
1743
|
+
async function savePresentation(filePath, pptxData, rawBytes, execCtx) {
|
|
1744
|
+
const handler = new pptxViewerCore.PptxHandler();
|
|
1745
|
+
await handler.load(rawBytes.buffer);
|
|
1746
|
+
const outputBytes = await handler.save(pptxData.slides, {
|
|
1747
|
+
headerFooter: pptxData.headerFooter
|
|
1748
|
+
});
|
|
1749
|
+
if (execCtx.collaboration) {
|
|
1750
|
+
const room = execCtx.collaboration.getRoom(filePath);
|
|
1751
|
+
if (room) {
|
|
1752
|
+
const codec = execCtx.collaboration.getCodec(filePath);
|
|
1753
|
+
if (codec) {
|
|
1754
|
+
const origin = execCtx.collaboration.agentOrigin(execCtx.agentName ?? "pptx-tool");
|
|
1755
|
+
await codec.hydrate(room.ydoc, outputBytes, origin);
|
|
1756
|
+
if (execCtx.viewer) {
|
|
1757
|
+
execCtx.viewer.replaceContent(filePath, outputBytes, {
|
|
1758
|
+
markDirty: false,
|
|
1759
|
+
mimeType: PPTX_MIME_TYPE
|
|
1760
|
+
});
|
|
1761
|
+
}
|
|
1762
|
+
return { savedToDisk: false, routedThroughCollaboration: true };
|
|
1763
|
+
}
|
|
1764
|
+
}
|
|
1765
|
+
}
|
|
1766
|
+
await execCtx.filesystem.writeFile(filePath, outputBytes);
|
|
1767
|
+
if (execCtx.viewer) {
|
|
1768
|
+
execCtx.viewer.replaceContent(filePath, outputBytes, {
|
|
1769
|
+
markDirty: false,
|
|
1770
|
+
mimeType: PPTX_MIME_TYPE
|
|
1771
|
+
});
|
|
1772
|
+
}
|
|
1773
|
+
return { savedToDisk: true, routedThroughCollaboration: false };
|
|
1774
|
+
}
|
|
1775
|
+
async function executeToolWithContext(filePath, execCtx, toolFn) {
|
|
1776
|
+
const { pptxData, rawBytes } = await loadPresentation(filePath, execCtx);
|
|
1777
|
+
const toolResult = await toolFn({ pptxData });
|
|
1778
|
+
if (toolResult.dirty) {
|
|
1779
|
+
const saveResult = await savePresentation(filePath, toolResult.pptxData, rawBytes, execCtx);
|
|
1780
|
+
return { ...toolResult.result, ...saveResult };
|
|
1781
|
+
}
|
|
1782
|
+
return toolResult.result;
|
|
1783
|
+
}
|
|
1784
|
+
|
|
1785
|
+
// src/mcp/handlers.ts
|
|
1786
|
+
var nodeFs = {
|
|
1787
|
+
readFile: async (path) => new Uint8Array(await promises.readFile(path)),
|
|
1788
|
+
writeFile: async (path, data) => {
|
|
1789
|
+
await promises.writeFile(path, data);
|
|
1790
|
+
}
|
|
1791
|
+
};
|
|
1792
|
+
function createMcpContext() {
|
|
1793
|
+
return { filesystem: nodeFs };
|
|
1794
|
+
}
|
|
1795
|
+
async function runMcpTool(filePath, fn) {
|
|
1796
|
+
const ctx = createMcpContext();
|
|
1797
|
+
return executeToolWithContext(filePath, ctx, fn);
|
|
1798
|
+
}
|
|
1799
|
+
|
|
1800
|
+
// src/mcp/server.ts
|
|
1801
|
+
function createServer() {
|
|
1802
|
+
const server2 = new mcp_js.McpServer({
|
|
1803
|
+
name: "pptx-viewer-tools",
|
|
1804
|
+
version: "1.0.0"
|
|
1805
|
+
});
|
|
1806
|
+
server2.registerTool(
|
|
1807
|
+
"get_slide",
|
|
1808
|
+
{
|
|
1809
|
+
description: "Inspect a single slide \u2014 returns layout, notes, elements",
|
|
1810
|
+
inputSchema: GetSlideSchema.shape
|
|
1811
|
+
},
|
|
1812
|
+
async (params) => {
|
|
1813
|
+
const result = await runMcpTool(
|
|
1814
|
+
params.filePath,
|
|
1815
|
+
(ctx) => getSlide(ctx, { slideIndex: params.slideIndex })
|
|
1816
|
+
);
|
|
1817
|
+
return {
|
|
1818
|
+
content: [
|
|
1819
|
+
{
|
|
1820
|
+
type: "text",
|
|
1821
|
+
text: JSON.stringify(result, null, 2)
|
|
1822
|
+
}
|
|
1823
|
+
]
|
|
1824
|
+
};
|
|
1825
|
+
}
|
|
1826
|
+
);
|
|
1827
|
+
server2.registerTool(
|
|
1828
|
+
"add_slide",
|
|
1829
|
+
{
|
|
1830
|
+
description: "Add a new blank slide to the presentation",
|
|
1831
|
+
inputSchema: AddSlideSchema.shape
|
|
1832
|
+
},
|
|
1833
|
+
async (params) => {
|
|
1834
|
+
const result = await runMcpTool(
|
|
1835
|
+
params.filePath,
|
|
1836
|
+
(ctx) => addSlide(ctx, params)
|
|
1837
|
+
);
|
|
1838
|
+
return {
|
|
1839
|
+
content: [
|
|
1840
|
+
{
|
|
1841
|
+
type: "text",
|
|
1842
|
+
text: JSON.stringify(result, null, 2)
|
|
1843
|
+
}
|
|
1844
|
+
]
|
|
1845
|
+
};
|
|
1846
|
+
}
|
|
1847
|
+
);
|
|
1848
|
+
server2.registerTool(
|
|
1849
|
+
"delete_slides",
|
|
1850
|
+
{
|
|
1851
|
+
description: "Delete one or more slides by index",
|
|
1852
|
+
inputSchema: DeleteSlidesSchema.shape
|
|
1853
|
+
},
|
|
1854
|
+
async (params) => {
|
|
1855
|
+
const result = await runMcpTool(
|
|
1856
|
+
params.filePath,
|
|
1857
|
+
(ctx) => deleteSlides(ctx, {
|
|
1858
|
+
slideIndexes: params.slideIndexes
|
|
1859
|
+
})
|
|
1860
|
+
);
|
|
1861
|
+
return {
|
|
1862
|
+
content: [
|
|
1863
|
+
{
|
|
1864
|
+
type: "text",
|
|
1865
|
+
text: JSON.stringify(result, null, 2)
|
|
1866
|
+
}
|
|
1867
|
+
]
|
|
1868
|
+
};
|
|
1869
|
+
}
|
|
1870
|
+
);
|
|
1871
|
+
server2.registerTool(
|
|
1872
|
+
"reorder_slides",
|
|
1873
|
+
{
|
|
1874
|
+
description: "Reorder slides by providing a new index array",
|
|
1875
|
+
inputSchema: ReorderSlidesSchema.shape
|
|
1876
|
+
},
|
|
1877
|
+
async (params) => {
|
|
1878
|
+
const result = await runMcpTool(
|
|
1879
|
+
params.filePath,
|
|
1880
|
+
(ctx) => reorderSlides(ctx, { newOrder: params.newOrder })
|
|
1881
|
+
);
|
|
1882
|
+
return {
|
|
1883
|
+
content: [
|
|
1884
|
+
{
|
|
1885
|
+
type: "text",
|
|
1886
|
+
text: JSON.stringify(result, null, 2)
|
|
1887
|
+
}
|
|
1888
|
+
]
|
|
1889
|
+
};
|
|
1890
|
+
}
|
|
1891
|
+
);
|
|
1892
|
+
server2.registerTool(
|
|
1893
|
+
"duplicate_slide",
|
|
1894
|
+
{
|
|
1895
|
+
description: "Duplicate a slide with new element IDs",
|
|
1896
|
+
inputSchema: DuplicateSlideSchema.shape
|
|
1897
|
+
},
|
|
1898
|
+
async (params) => {
|
|
1899
|
+
const result = await runMcpTool(
|
|
1900
|
+
params.filePath,
|
|
1901
|
+
(ctx) => duplicateSlide(ctx, params)
|
|
1902
|
+
);
|
|
1903
|
+
return {
|
|
1904
|
+
content: [
|
|
1905
|
+
{
|
|
1906
|
+
type: "text",
|
|
1907
|
+
text: JSON.stringify(result, null, 2)
|
|
1908
|
+
}
|
|
1909
|
+
]
|
|
1910
|
+
};
|
|
1911
|
+
}
|
|
1912
|
+
);
|
|
1913
|
+
server2.registerTool(
|
|
1914
|
+
"update_slide_properties",
|
|
1915
|
+
{
|
|
1916
|
+
description: "Update slide background, notes, or visibility",
|
|
1917
|
+
inputSchema: UpdateSlidePropertiesSchema.shape
|
|
1918
|
+
},
|
|
1919
|
+
async (params) => {
|
|
1920
|
+
const result = await runMcpTool(
|
|
1921
|
+
params.filePath,
|
|
1922
|
+
(ctx) => updateSlideProperties(ctx, params)
|
|
1923
|
+
);
|
|
1924
|
+
return {
|
|
1925
|
+
content: [
|
|
1926
|
+
{
|
|
1927
|
+
type: "text",
|
|
1928
|
+
text: JSON.stringify(result, null, 2)
|
|
1929
|
+
}
|
|
1930
|
+
]
|
|
1931
|
+
};
|
|
1932
|
+
}
|
|
1933
|
+
);
|
|
1934
|
+
server2.registerTool(
|
|
1935
|
+
"set_slide_transition",
|
|
1936
|
+
{
|
|
1937
|
+
description: "Set or remove a slide transition effect",
|
|
1938
|
+
inputSchema: SetSlideTransitionSchema.shape
|
|
1939
|
+
},
|
|
1940
|
+
async (params) => {
|
|
1941
|
+
const result = await runMcpTool(
|
|
1942
|
+
params.filePath,
|
|
1943
|
+
(ctx) => setSlideTransition(ctx, params)
|
|
1944
|
+
);
|
|
1945
|
+
return {
|
|
1946
|
+
content: [
|
|
1947
|
+
{
|
|
1948
|
+
type: "text",
|
|
1949
|
+
text: JSON.stringify(result, null, 2)
|
|
1950
|
+
}
|
|
1951
|
+
]
|
|
1952
|
+
};
|
|
1953
|
+
}
|
|
1954
|
+
);
|
|
1955
|
+
server2.registerTool(
|
|
1956
|
+
"set_canvas_size",
|
|
1957
|
+
{
|
|
1958
|
+
description: "Change the presentation canvas dimensions",
|
|
1959
|
+
inputSchema: SetCanvasSizeSchema.shape
|
|
1960
|
+
},
|
|
1961
|
+
async (params) => {
|
|
1962
|
+
const result = await runMcpTool(
|
|
1963
|
+
params.filePath,
|
|
1964
|
+
(ctx) => setCanvasSize(ctx, params)
|
|
1965
|
+
);
|
|
1966
|
+
return {
|
|
1967
|
+
content: [
|
|
1968
|
+
{
|
|
1969
|
+
type: "text",
|
|
1970
|
+
text: JSON.stringify(result, null, 2)
|
|
1971
|
+
}
|
|
1972
|
+
]
|
|
1973
|
+
};
|
|
1974
|
+
}
|
|
1975
|
+
);
|
|
1976
|
+
server2.registerTool(
|
|
1977
|
+
"add_element",
|
|
1978
|
+
{
|
|
1979
|
+
description: "Add a text, shape, image, table, or connector element",
|
|
1980
|
+
inputSchema: AddElementSchema.shape
|
|
1981
|
+
},
|
|
1982
|
+
async (params) => {
|
|
1983
|
+
const result = await runMcpTool(
|
|
1984
|
+
params.filePath,
|
|
1985
|
+
(ctx) => addElement(ctx, params)
|
|
1986
|
+
);
|
|
1987
|
+
return {
|
|
1988
|
+
content: [
|
|
1989
|
+
{
|
|
1990
|
+
type: "text",
|
|
1991
|
+
text: JSON.stringify(result, null, 2)
|
|
1992
|
+
}
|
|
1993
|
+
]
|
|
1994
|
+
};
|
|
1995
|
+
}
|
|
1996
|
+
);
|
|
1997
|
+
server2.registerTool(
|
|
1998
|
+
"update_element",
|
|
1999
|
+
{
|
|
2000
|
+
description: "Update element position, size, text, or style",
|
|
2001
|
+
inputSchema: UpdateElementSchema.shape
|
|
2002
|
+
},
|
|
2003
|
+
async (params) => {
|
|
2004
|
+
const result = await runMcpTool(
|
|
2005
|
+
params.filePath,
|
|
2006
|
+
(ctx) => updateElement(ctx, params)
|
|
2007
|
+
);
|
|
2008
|
+
return {
|
|
2009
|
+
content: [
|
|
2010
|
+
{
|
|
2011
|
+
type: "text",
|
|
2012
|
+
text: JSON.stringify(result, null, 2)
|
|
2013
|
+
}
|
|
2014
|
+
]
|
|
2015
|
+
};
|
|
2016
|
+
}
|
|
2017
|
+
);
|
|
2018
|
+
server2.registerTool(
|
|
2019
|
+
"delete_elements",
|
|
2020
|
+
{
|
|
2021
|
+
description: "Delete one or more elements by ID",
|
|
2022
|
+
inputSchema: DeleteElementsSchema.shape
|
|
2023
|
+
},
|
|
2024
|
+
async (params) => {
|
|
2025
|
+
const result = await runMcpTool(
|
|
2026
|
+
params.filePath,
|
|
2027
|
+
(ctx) => deleteElements(ctx, params)
|
|
2028
|
+
);
|
|
2029
|
+
return {
|
|
2030
|
+
content: [
|
|
2031
|
+
{
|
|
2032
|
+
type: "text",
|
|
2033
|
+
text: JSON.stringify(result, null, 2)
|
|
2034
|
+
}
|
|
2035
|
+
]
|
|
2036
|
+
};
|
|
2037
|
+
}
|
|
2038
|
+
);
|
|
2039
|
+
server2.registerTool(
|
|
2040
|
+
"arrange_elements",
|
|
2041
|
+
{
|
|
2042
|
+
description: "Align elements or change z-order (layer)",
|
|
2043
|
+
inputSchema: ArrangeElementsSchema.shape
|
|
2044
|
+
},
|
|
2045
|
+
async (params) => {
|
|
2046
|
+
const result = await runMcpTool(
|
|
2047
|
+
params.filePath,
|
|
2048
|
+
(ctx) => arrangeElements(ctx, params)
|
|
2049
|
+
);
|
|
2050
|
+
return {
|
|
2051
|
+
content: [
|
|
2052
|
+
{
|
|
2053
|
+
type: "text",
|
|
2054
|
+
text: JSON.stringify(result, null, 2)
|
|
2055
|
+
}
|
|
2056
|
+
]
|
|
2057
|
+
};
|
|
2058
|
+
}
|
|
2059
|
+
);
|
|
2060
|
+
server2.registerTool(
|
|
2061
|
+
"clone_element",
|
|
2062
|
+
{
|
|
2063
|
+
description: "Clone an element within or across slides",
|
|
2064
|
+
inputSchema: CloneElementSchema.shape
|
|
2065
|
+
},
|
|
2066
|
+
async (params) => {
|
|
2067
|
+
const result = await runMcpTool(
|
|
2068
|
+
params.filePath,
|
|
2069
|
+
(ctx) => cloneElement(ctx, params)
|
|
2070
|
+
);
|
|
2071
|
+
return {
|
|
2072
|
+
content: [
|
|
2073
|
+
{
|
|
2074
|
+
type: "text",
|
|
2075
|
+
text: JSON.stringify(result, null, 2)
|
|
2076
|
+
}
|
|
2077
|
+
]
|
|
2078
|
+
};
|
|
2079
|
+
}
|
|
2080
|
+
);
|
|
2081
|
+
server2.registerTool(
|
|
2082
|
+
"set_element_animation",
|
|
2083
|
+
{
|
|
2084
|
+
description: "Set entrance/exit animation on an element",
|
|
2085
|
+
inputSchema: SetElementAnimationSchema.shape
|
|
2086
|
+
},
|
|
2087
|
+
async (params) => {
|
|
2088
|
+
const result = await runMcpTool(
|
|
2089
|
+
params.filePath,
|
|
2090
|
+
(ctx) => setElementAnimation(ctx, params)
|
|
2091
|
+
);
|
|
2092
|
+
return {
|
|
2093
|
+
content: [
|
|
2094
|
+
{
|
|
2095
|
+
type: "text",
|
|
2096
|
+
text: JSON.stringify(result, null, 2)
|
|
2097
|
+
}
|
|
2098
|
+
]
|
|
2099
|
+
};
|
|
2100
|
+
}
|
|
2101
|
+
);
|
|
2102
|
+
server2.registerTool(
|
|
2103
|
+
"group_elements",
|
|
2104
|
+
{
|
|
2105
|
+
description: "Group multiple elements into a group",
|
|
2106
|
+
inputSchema: GroupElementsSchema.shape
|
|
2107
|
+
},
|
|
2108
|
+
async (params) => {
|
|
2109
|
+
const result = await runMcpTool(
|
|
2110
|
+
params.filePath,
|
|
2111
|
+
(ctx) => groupElements(ctx, params)
|
|
2112
|
+
);
|
|
2113
|
+
return {
|
|
2114
|
+
content: [
|
|
2115
|
+
{
|
|
2116
|
+
type: "text",
|
|
2117
|
+
text: JSON.stringify(result, null, 2)
|
|
2118
|
+
}
|
|
2119
|
+
]
|
|
2120
|
+
};
|
|
2121
|
+
}
|
|
2122
|
+
);
|
|
2123
|
+
server2.registerTool(
|
|
2124
|
+
"ungroup_elements",
|
|
2125
|
+
{
|
|
2126
|
+
description: "Ungroup a group element back to individual elements",
|
|
2127
|
+
inputSchema: UngroupElementsSchema.shape
|
|
2128
|
+
},
|
|
2129
|
+
async (params) => {
|
|
2130
|
+
const result = await runMcpTool(
|
|
2131
|
+
params.filePath,
|
|
2132
|
+
(ctx) => ungroupElements(ctx, params)
|
|
2133
|
+
);
|
|
2134
|
+
return {
|
|
2135
|
+
content: [
|
|
2136
|
+
{
|
|
2137
|
+
type: "text",
|
|
2138
|
+
text: JSON.stringify(result, null, 2)
|
|
2139
|
+
}
|
|
2140
|
+
]
|
|
2141
|
+
};
|
|
2142
|
+
}
|
|
2143
|
+
);
|
|
2144
|
+
server2.registerTool(
|
|
2145
|
+
"batch_update_elements",
|
|
2146
|
+
{
|
|
2147
|
+
description: "Apply the same properties to multiple elements",
|
|
2148
|
+
inputSchema: BatchUpdateElementsSchema.shape
|
|
2149
|
+
},
|
|
2150
|
+
async (params) => {
|
|
2151
|
+
const result = await runMcpTool(
|
|
2152
|
+
params.filePath,
|
|
2153
|
+
(ctx) => batchUpdateElements(ctx, params)
|
|
2154
|
+
);
|
|
2155
|
+
return {
|
|
2156
|
+
content: [
|
|
2157
|
+
{
|
|
2158
|
+
type: "text",
|
|
2159
|
+
text: JSON.stringify(result, null, 2)
|
|
2160
|
+
}
|
|
2161
|
+
]
|
|
2162
|
+
};
|
|
2163
|
+
}
|
|
2164
|
+
);
|
|
2165
|
+
server2.registerTool(
|
|
2166
|
+
"update_table_cells",
|
|
2167
|
+
{
|
|
2168
|
+
description: "Update text content of table cells",
|
|
2169
|
+
inputSchema: UpdateTableCellsSchema.shape
|
|
2170
|
+
},
|
|
2171
|
+
async (params) => {
|
|
2172
|
+
const result = await runMcpTool(
|
|
2173
|
+
params.filePath,
|
|
2174
|
+
(ctx) => updateTableCells(ctx, params)
|
|
2175
|
+
);
|
|
2176
|
+
return {
|
|
2177
|
+
content: [
|
|
2178
|
+
{
|
|
2179
|
+
type: "text",
|
|
2180
|
+
text: JSON.stringify(result, null, 2)
|
|
2181
|
+
}
|
|
2182
|
+
]
|
|
2183
|
+
};
|
|
2184
|
+
}
|
|
2185
|
+
);
|
|
2186
|
+
server2.registerTool(
|
|
2187
|
+
"manage_table_structure",
|
|
2188
|
+
{
|
|
2189
|
+
description: "Insert or delete table rows and columns",
|
|
2190
|
+
inputSchema: ManageTableStructureSchema.shape
|
|
2191
|
+
},
|
|
2192
|
+
async (params) => {
|
|
2193
|
+
const result = await runMcpTool(
|
|
2194
|
+
params.filePath,
|
|
2195
|
+
(ctx) => manageTableStructure(ctx, params)
|
|
2196
|
+
);
|
|
2197
|
+
return {
|
|
2198
|
+
content: [
|
|
2199
|
+
{
|
|
2200
|
+
type: "text",
|
|
2201
|
+
text: JSON.stringify(result, null, 2)
|
|
2202
|
+
}
|
|
2203
|
+
]
|
|
2204
|
+
};
|
|
2205
|
+
}
|
|
2206
|
+
);
|
|
2207
|
+
server2.registerTool(
|
|
2208
|
+
"update_element_style",
|
|
2209
|
+
{
|
|
2210
|
+
description: "Update fill, stroke, shadow, glow, or image effects",
|
|
2211
|
+
inputSchema: UpdateElementStyleSchema.shape
|
|
2212
|
+
},
|
|
2213
|
+
async (params) => {
|
|
2214
|
+
const result = await runMcpTool(
|
|
2215
|
+
params.filePath,
|
|
2216
|
+
(ctx) => updateElementStyle(ctx, params)
|
|
2217
|
+
);
|
|
2218
|
+
return {
|
|
2219
|
+
content: [
|
|
2220
|
+
{
|
|
2221
|
+
type: "text",
|
|
2222
|
+
text: JSON.stringify(result, null, 2)
|
|
2223
|
+
}
|
|
2224
|
+
]
|
|
2225
|
+
};
|
|
2226
|
+
}
|
|
2227
|
+
);
|
|
2228
|
+
server2.registerTool(
|
|
2229
|
+
"run_accessibility_check",
|
|
2230
|
+
{
|
|
2231
|
+
description: "Audit the presentation for accessibility issues",
|
|
2232
|
+
inputSchema: AccessibilityCheckSchema.shape
|
|
2233
|
+
},
|
|
2234
|
+
async (params) => {
|
|
2235
|
+
const result = await runMcpTool(
|
|
2236
|
+
params.filePath,
|
|
2237
|
+
(ctx) => runAccessibilityCheck(ctx)
|
|
2238
|
+
);
|
|
2239
|
+
return {
|
|
2240
|
+
content: [
|
|
2241
|
+
{
|
|
2242
|
+
type: "text",
|
|
2243
|
+
text: JSON.stringify(result, null, 2)
|
|
2244
|
+
}
|
|
2245
|
+
]
|
|
2246
|
+
};
|
|
2247
|
+
}
|
|
2248
|
+
);
|
|
2249
|
+
server2.registerTool(
|
|
2250
|
+
"find_text",
|
|
2251
|
+
{
|
|
2252
|
+
description: "Search for text across all slides",
|
|
2253
|
+
inputSchema: FindTextSchema.shape
|
|
2254
|
+
},
|
|
2255
|
+
async (params) => {
|
|
2256
|
+
const result = await runMcpTool(
|
|
2257
|
+
params.filePath,
|
|
2258
|
+
(ctx) => findText(ctx, params)
|
|
2259
|
+
);
|
|
2260
|
+
return {
|
|
2261
|
+
content: [
|
|
2262
|
+
{
|
|
2263
|
+
type: "text",
|
|
2264
|
+
text: JSON.stringify(result, null, 2)
|
|
2265
|
+
}
|
|
2266
|
+
]
|
|
2267
|
+
};
|
|
2268
|
+
}
|
|
2269
|
+
);
|
|
2270
|
+
server2.registerTool(
|
|
2271
|
+
"replace_text",
|
|
2272
|
+
{
|
|
2273
|
+
description: "Find and replace text across slides",
|
|
2274
|
+
inputSchema: ReplaceTextSchema.shape
|
|
2275
|
+
},
|
|
2276
|
+
async (params) => {
|
|
2277
|
+
const result = await runMcpTool(
|
|
2278
|
+
params.filePath,
|
|
2279
|
+
(ctx) => replaceText(ctx, params)
|
|
2280
|
+
);
|
|
2281
|
+
return {
|
|
2282
|
+
content: [
|
|
2283
|
+
{
|
|
2284
|
+
type: "text",
|
|
2285
|
+
text: JSON.stringify(result, null, 2)
|
|
2286
|
+
}
|
|
2287
|
+
]
|
|
2288
|
+
};
|
|
2289
|
+
}
|
|
2290
|
+
);
|
|
2291
|
+
server2.registerTool(
|
|
2292
|
+
"manage_comments",
|
|
2293
|
+
{
|
|
2294
|
+
description: "List, add, delete, or resolve slide comments",
|
|
2295
|
+
inputSchema: ManageCommentsSchema.shape
|
|
2296
|
+
},
|
|
2297
|
+
async (params) => {
|
|
2298
|
+
const result = await runMcpTool(
|
|
2299
|
+
params.filePath,
|
|
2300
|
+
(ctx) => manageComments(ctx, params)
|
|
2301
|
+
);
|
|
2302
|
+
return {
|
|
2303
|
+
content: [
|
|
2304
|
+
{
|
|
2305
|
+
type: "text",
|
|
2306
|
+
text: JSON.stringify(result, null, 2)
|
|
2307
|
+
}
|
|
2308
|
+
]
|
|
2309
|
+
};
|
|
2310
|
+
}
|
|
2311
|
+
);
|
|
2312
|
+
server2.registerTool(
|
|
2313
|
+
"convert_to_markdown",
|
|
2314
|
+
{
|
|
2315
|
+
description: "Convert the presentation to Markdown format",
|
|
2316
|
+
inputSchema: ConvertToMarkdownSchema.shape
|
|
2317
|
+
},
|
|
2318
|
+
async (params) => {
|
|
2319
|
+
const result = await runMcpTool(
|
|
2320
|
+
params.filePath,
|
|
2321
|
+
(ctx) => convertToMarkdown(ctx, params)
|
|
2322
|
+
);
|
|
2323
|
+
return {
|
|
2324
|
+
content: [
|
|
2325
|
+
{
|
|
2326
|
+
type: "text",
|
|
2327
|
+
text: JSON.stringify(result, null, 2)
|
|
2328
|
+
}
|
|
2329
|
+
]
|
|
2330
|
+
};
|
|
2331
|
+
}
|
|
2332
|
+
);
|
|
2333
|
+
return server2;
|
|
2334
|
+
}
|
|
2335
|
+
|
|
2336
|
+
// src/mcp/index.ts
|
|
2337
|
+
var server = createServer();
|
|
2338
|
+
var transport = new stdio_js.StdioServerTransport();
|
|
2339
|
+
void server.connect(transport);
|