@mcpher/gas-fakes 2.5.2 → 2.5.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/README.md +17 -3
  2. package/package.json +1 -1
  3. package/pngs/srv.jpg +0 -0
  4. package/src/cli/app.js +1 -0
  5. package/src/cli/togas.js +23 -14
  6. package/src/index.js +1 -0
  7. package/src/services/advslides/fakeadvslides.js +11 -5
  8. package/src/services/base/app.js +9 -0
  9. package/src/services/base/fakebase.js +28 -0
  10. package/src/services/common/fakeadvresource.js +3 -2
  11. package/src/services/content/contentservice.js +3 -2
  12. package/src/services/enums/baseenums.js +47 -0
  13. package/src/services/enums/contentenums.js +1 -3
  14. package/src/services/enums/scriptenums.js +31 -4
  15. package/src/services/enums/slidesenums.js +3 -1
  16. package/src/services/enums/xmlenums.js +14 -0
  17. package/src/services/html/serverworker.js +1 -1
  18. package/src/services/scriptapp/app.js +14 -7
  19. package/src/services/scriptapp/fakeauthorizationinfo.js +4 -4
  20. package/src/services/slidesapp/app.js +5 -0
  21. package/src/services/slidesapp/fakeautofit.js +1 -1
  22. package/src/services/slidesapp/fakeborder.js +106 -0
  23. package/src/services/slidesapp/fakecolorscheme.js +1 -1
  24. package/src/services/slidesapp/fakefill.js +216 -0
  25. package/src/services/slidesapp/fakegroup.js +35 -0
  26. package/src/services/slidesapp/fakeimage.js +118 -0
  27. package/src/services/slidesapp/fakelayout.js +351 -0
  28. package/src/services/slidesapp/fakeline.js +2 -2
  29. package/src/services/slidesapp/fakelinefill.js +15 -16
  30. package/src/services/slidesapp/fakelink.js +20 -3
  31. package/src/services/slidesapp/fakelist.js +36 -0
  32. package/src/services/slidesapp/fakeliststyle.js +105 -0
  33. package/src/services/slidesapp/fakemaster.js +358 -0
  34. package/src/services/slidesapp/fakenotesmaster.js +125 -0
  35. package/src/services/slidesapp/fakenotespage.js +102 -2
  36. package/src/services/slidesapp/fakepagebackground.js +109 -1
  37. package/src/services/slidesapp/fakepageelement.js +157 -18
  38. package/src/services/slidesapp/fakepageelementrange.js +28 -0
  39. package/src/services/slidesapp/fakepagerange.js +28 -0
  40. package/src/services/slidesapp/fakeparagraphstyle.js +139 -0
  41. package/src/services/slidesapp/fakepicturefill.js +32 -0
  42. package/src/services/slidesapp/fakepresentation.js +126 -2
  43. package/src/services/slidesapp/fakeshape.js +9 -0
  44. package/src/services/slidesapp/fakeslide.js +216 -24
  45. package/src/services/slidesapp/fakesolidfill.js +45 -0
  46. package/src/services/slidesapp/fakespeakerspotlight.js +18 -0
  47. package/src/services/slidesapp/faketable.js +55 -9
  48. package/src/services/slidesapp/faketablecell.js +141 -12
  49. package/src/services/slidesapp/faketablecellrange.js +28 -0
  50. package/src/services/slidesapp/faketablecolumn.js +72 -0
  51. package/src/services/slidesapp/faketablerow.js +31 -0
  52. package/src/services/slidesapp/faketextrange.js +179 -135
  53. package/src/services/slidesapp/faketextstyle.js +158 -0
  54. package/src/services/slidesapp/fakevideo.js +35 -0
  55. package/src/services/slidesapp/fakewordart.js +22 -0
  56. package/src/services/slidesapp/pageelementfactory.js +24 -1
  57. package/src/services/spreadsheetapp/fakeembeddedchartbuilder.js +92 -12
  58. package/src/services/spreadsheetapp/fakespreadsheet.js +360 -62
  59. package/src/services/spreadsheetapp/fakespreadsheettheme.js +53 -0
  60. package/src/services/urlfetchapp/app.js +216 -175
  61. package/src/services/xmlservice/app.js +3 -78
  62. package/src/services/xmlservice/fakeattribute.js +15 -0
  63. package/src/services/xmlservice/fakecdata.js +40 -0
  64. package/src/services/xmlservice/fakecomment.js +34 -0
  65. package/src/services/xmlservice/fakecontent.js +51 -0
  66. package/src/services/xmlservice/fakedoctype.js +68 -0
  67. package/src/services/xmlservice/fakedocument.js +110 -13
  68. package/src/services/xmlservice/fakeelement.js +297 -82
  69. package/src/services/xmlservice/fakeentityref.js +54 -0
  70. package/src/services/xmlservice/fakeformat.js +67 -22
  71. package/src/services/xmlservice/fakeprocessinginstruction.js +44 -0
  72. package/src/services/xmlservice/faketext.js +39 -0
  73. package/src/services/xmlservice/fakexmlservice.js +118 -0
  74. package/src/support/sxfetch.js +60 -0
  75. package/tools/omlx.env.example +6 -0
  76. package/tools/omlx_mcp_server.cjs +157 -0
@@ -1,6 +1,9 @@
1
1
  import { Proxies } from '../../support/proxies.js';
2
2
  import { newFakeParagraph } from './fakeparagraph.js';
3
3
  import { newFakeAutoText } from './fakeautotext.js';
4
+ import { newFakeTextStyle } from './faketextstyle.js';
5
+ import { newFakeParagraphStyle } from './fakeparagraphstyle.js';
6
+ import { newFakeListStyle } from './fakeliststyle.js';
4
7
  import { AutofitType } from '../enums/slidesenums.js';
5
8
 
6
9
  export const newFakeTextRange = (...args) => {
@@ -10,8 +13,6 @@ export const newFakeTextRange = (...args) => {
10
13
  export class FakeTextRange {
11
14
  constructor(shape, startIndex = null, endIndex = null) {
12
15
  this.__shape = shape;
13
- // APIs use 0-based index? Apps Script TextRange uses indices?
14
- // If null, represents the entire text.
15
16
  this.__startIndex = startIndex;
16
17
  this.__endIndex = endIndex;
17
18
  }
@@ -21,40 +22,143 @@ export class FakeTextRange {
21
22
  if (shapeResource && shapeResource.shape && shapeResource.shape.text) {
22
23
  return shapeResource.shape.text;
23
24
  }
25
+ if (shapeResource && shapeResource.table) {
26
+ return shapeResource.table.tableRows?.[this.__shape.__cellLocation?.rowIndex]?.tableCells?.[this.__shape.__cellLocation?.columnIndex]?.text || {};
27
+ }
24
28
  return {};
25
29
  }
26
30
 
27
31
  getStartIndex() {
28
- // If indices are null, it starts at 0?
29
32
  return this.__startIndex !== null ? this.__startIndex : 0;
30
33
  }
31
34
 
32
35
  getEndIndex() {
33
- // If indices are null, it ends at full length?
34
- // Caution: If we haven't computed length...
35
- // But usually FakeTextRange is created with explicit indices by getParagraphs.
36
- // If created without indices (full range), we need to calculate length.
37
36
  if (this.__endIndex !== null) return this.__endIndex;
38
- return this.asString().length;
37
+ return this.getLength();
38
+ }
39
+
40
+ appendParagraph(text) {
41
+ this.appendText(text + '\n');
42
+ const paragraphs = this.getParagraphs();
43
+ return paragraphs[paragraphs.length - 1];
44
+ }
45
+
46
+ appendRange(range) {
47
+ this.appendText(range.asString());
48
+ return this;
49
+ }
50
+
51
+ appendText(text) {
52
+ // In Google Slides API, if the text ends in a newline, the insertion index
53
+ // should be length - 1 to be inside the text.
54
+ // But actually, for appending, length is correct if we want to add after.
55
+ // The issue is likely that asString() is NOT matching API length.
56
+ return this.insertText(this.asString().length, text);
57
+ }
58
+
59
+ getLength() {
60
+ const textElements = this.__resource?.textElements || [];
61
+ if (textElements.length === 0) return 0;
62
+
63
+ const lastElement = textElements[textElements.length - 1];
64
+ let totalLength = lastElement.endIndex || 0;
65
+
66
+ if (this.__startIndex !== null && this.__endIndex !== null) {
67
+ return Math.max(0, this.__endIndex - this.__startIndex);
68
+ }
69
+ if (this.__startIndex !== null) {
70
+ return Math.max(0, totalLength - this.__startIndex);
71
+ }
72
+ return totalLength;
73
+ }
74
+
75
+ getLinks() {
76
+ return this.getTextStyle().hasLink() ? [this] : [];
77
+ }
78
+
79
+ getListParagraphs() {
80
+ return this.getParagraphs().filter(p => p.getRange().getListStyle().isInList());
81
+ }
82
+
83
+ getListStyle() {
84
+ return newFakeListStyle(this);
85
+ }
86
+
87
+ getParagraphStyle() {
88
+ return newFakeParagraphStyle(this);
89
+ }
90
+
91
+ getRange(start, end) {
92
+ return newFakeTextRange(this.__shape, this.getStartIndex() + start, this.getStartIndex() + end);
93
+ }
94
+
95
+ getRuns() {
96
+ return [this];
97
+ }
98
+
99
+ getTextStyle() {
100
+ return newFakeTextStyle(this);
101
+ }
102
+
103
+ insertParagraph(offset, text) {
104
+ this.insertText(offset, text + '\n');
105
+ return this.getParagraphs().find(p => p.getRange().getStartIndex() === this.getStartIndex() + offset);
106
+ }
107
+
108
+ insertRange(offset, range) {
109
+ return this.insertText(offset, range.asString());
110
+ }
111
+
112
+ replaceAllText(findText, replaceText, matchCase) {
113
+ const presentationId = this.__shape.__presentation.getId();
114
+ const requests = [{
115
+ replaceAllText: {
116
+ replaceText: replaceText,
117
+ containsText: {
118
+ text: findText,
119
+ matchCase: matchCase
120
+ },
121
+ pageObjectIds: [this.__shape.getParentPage().getObjectId()]
122
+ }
123
+ }];
124
+
125
+ const response = Slides.Presentations.batchUpdate({ requests }, presentationId);
126
+ return response.replies[0].replaceAllText.occurrencesChanged || 0;
127
+ }
128
+
129
+ select() {
130
+ return this;
39
131
  }
40
132
 
41
- /**
42
- * Gets the rendered string of the text range.
43
- * @returns {string} The text.
44
- */
45
133
  asString() {
46
134
  const textElements = this.__resource?.textElements || [];
47
- let fullText = textElements.map(te => {
48
- if (te.textRun) return te.textRun.content;
49
- if (te.autoText) return te.autoText.content || '[AutoText]'; // Use placeholder
50
- return '';
51
- }).join('');
135
+ if (textElements.length === 0) return '';
136
+
137
+ // We need the full length of the underlying text resource to use API indices.
138
+ const lastElement = textElements[textElements.length - 1];
139
+ const fullLength = lastElement.endIndex || 0;
140
+
141
+ // Reconstruct the full string based on API indices to ensure parity.
142
+ const chars = new Array(fullLength).fill(null);
143
+ for (const te of textElements) {
144
+ const start = te.startIndex || 0;
145
+ const end = te.endIndex;
146
+
147
+ if (te.textRun) {
148
+ const content = te.textRun.content;
149
+ for (let i = 0; i < content.length; i++) {
150
+ if (start + i < fullLength) chars[start + i] = content[i];
151
+ }
152
+ } else if (te.autoText) {
153
+ if (start < fullLength) chars[start] = te.autoText.content || ' ';
154
+ } else if (te.paragraphMarker) {
155
+ if (end > 0 && end <= fullLength) chars[end - 1] = '\n';
156
+ }
157
+ }
52
158
 
53
- // If indices are provided, slice the text.
54
- // We assume indices are 0-based relative to the start of the SHAPE text (if referencing shape).
159
+ let fullText = chars.map(c => c === null ? '\n' : c).join('');
55
160
 
56
161
  if (this.__startIndex !== null && this.__endIndex !== null) {
57
- // Ensure indices are within bounds? Or just slice.
58
162
  fullText = fullText.slice(this.__startIndex, this.__endIndex);
59
163
  } else if (this.__startIndex !== null) {
60
164
  fullText = fullText.slice(this.__startIndex);
@@ -67,78 +171,68 @@ export class FakeTextRange {
67
171
  return this.asString();
68
172
  }
69
173
 
70
- /**
71
- * Sets the text content.
72
- * @param {string} newText The new text.
73
- * @returns {FakeTextRange} This range.
74
- */
75
174
  setText(newText) {
76
- const objectId = this.__shape.getObjectId();
77
- const presentationId = this.__shape.__presentation.getId();
78
-
79
- const requests = [];
80
-
81
- const currentText = this.asString();
82
-
83
- if (currentText.length > 0) {
84
- requests.push({
85
- deleteText: {
86
- objectId: objectId,
87
- cellLocation: this.__shape.__cellLocation,
88
- textRange: {
89
- type: 'FROM_START_INDEX',
90
- startIndex: 0
91
- }
92
- }
93
- });
94
- }
95
-
175
+ this.clear();
96
176
  if (newText) {
97
- // Apps Script shapes always end with a newline.
98
- // If we insert text that ends with a newline, we get double newline (one explicit, one implicit).
99
- // To match expected behavior where setText("A\n") results in "A" (plus implicit \n),
100
- // or at least doesn't create "A\n\n", we should strip the trailing newline.
101
-
102
177
  if (newText.endsWith('\n')) {
103
178
  newText = newText.slice(0, -1);
104
179
  }
105
-
106
- requests.push({
107
- insertText: {
108
- objectId: objectId,
109
- cellLocation: this.__shape.__cellLocation,
110
- insertionIndex: 0,
111
- text: newText
112
- }
113
- });
180
+ this.appendText(newText);
114
181
  }
182
+ return this;
183
+ }
115
184
 
116
- if (requests.length > 0) {
117
- Slides.Presentations.batchUpdate(requests, presentationId);
185
+ insertText(offset, text) {
186
+ const objectId = this.__shape.getObjectId();
187
+ const presentationId = this.__shape.__presentation.getId();
188
+
189
+ const totalLen = this.getLength();
190
+ let insertionIndex = this.getStartIndex() + offset;
191
+
192
+ // In Slides API, insertionIndex cannot be after the final paragraph marker.
193
+ // If the shape has length 12 (0-11 text, 11-12 marker), max insertionIndex is 11.
194
+ if (insertionIndex >= totalLen && totalLen > 0) {
195
+ insertionIndex = totalLen - 1;
118
196
  }
119
197
 
198
+ Slides.Presentations.batchUpdate({ requests: [{
199
+ insertText: {
200
+ objectId: objectId,
201
+ cellLocation: this.__shape.__cellLocation,
202
+ insertionIndex: insertionIndex,
203
+ text: text
204
+ }
205
+ }] }, presentationId);
120
206
  return this;
121
207
  }
122
208
 
123
209
  clear() {
124
- return this.setText('');
125
- }
126
-
127
- clear() {
128
- return this.setText('');
210
+ if (this.getLength() === 0) return this;
211
+ const objectId = this.__shape.getObjectId();
212
+ const presentationId = this.__shape.__presentation.getId();
213
+
214
+ const type = (this.__startIndex === null && this.__endIndex === null) ? 'ALL' : 'FIXED_RANGE';
215
+ const deleteRange = { type };
216
+ if (type === 'FIXED_RANGE') {
217
+ deleteRange.startIndex = this.getStartIndex();
218
+ deleteRange.endIndex = this.getEndIndex();
219
+ }
220
+
221
+ Slides.Presentations.batchUpdate({ requests: [{
222
+ deleteText: {
223
+ objectId: objectId,
224
+ cellLocation: this.__shape.__cellLocation,
225
+ textRange: deleteRange
226
+ }
227
+ }] }, presentationId);
228
+ return this;
129
229
  }
130
230
 
131
- /**
132
- * Gets the paragraphs in the text range.
133
- * @returns {FakeParagraph[]} The paragraphs.
134
- */
135
231
  getParagraphs() {
136
232
  const fullText = this.asString();
137
233
  if (fullText.length === 0) return [];
138
234
 
139
235
  const paragraphs = [];
140
- let startIndex = 0;
141
-
142
236
  let currentIndex = 0;
143
237
  let paragraphIndex = 0;
144
238
 
@@ -149,15 +243,10 @@ export class FakeTextRange {
149
243
  if (nextNewline === -1) {
150
244
  endIndex = fullText.length;
151
245
  } else {
152
- endIndex = nextNewline + 1; // Include the newline
153
- }
154
-
155
- if (startIndex + currentIndex >= startIndex + endIndex) {
156
- // Empty segment
157
- if (endIndex === fullText.length) break; // Trailing empty
246
+ endIndex = nextNewline + 1;
158
247
  }
159
248
 
160
- const range = newFakeTextRange(this.__shape, startIndex + currentIndex, startIndex + endIndex);
249
+ const range = newFakeTextRange(this.__shape, this.getStartIndex() + currentIndex, this.getStartIndex() + endIndex);
161
250
  paragraphs.push(newFakeParagraph(range, paragraphIndex++));
162
251
 
163
252
  currentIndex = endIndex;
@@ -166,76 +255,31 @@ export class FakeTextRange {
166
255
  return paragraphs;
167
256
  }
168
257
 
169
- /**
170
- * Gets the auto texts in the text range.
171
- * @returns {FakeAutoText[]} The auto texts.
172
- */
173
258
  getAutoTexts() {
174
259
  const textElements = this.__resource?.textElements || [];
175
260
  const autoTexts = [];
176
261
  let charIndex = 0;
177
262
  let autoTextIndex = 0;
178
263
 
179
- // We need to iterate text elements to find autoText and calculate their ranges.
180
- // But FakeTextRange might be a sub-range (startIndex, endIndex).
181
- // We should only return AutoTexts falling within this range.
182
-
183
264
  const rangeStart = this.getStartIndex();
184
265
  const rangeEnd = this.getEndIndex();
185
266
 
186
267
  for (const element of textElements) {
187
268
  const elementLength = (element.textRun ? element.textRun.content.length : 0) +
188
- (element.autoText ? 1 : 0) + // AutoText usually has content length 1?
189
- (element.paragraphMarker ? 0 : 0); // Markers don't consume content length exactly?
190
- // Actually, paragraph markers ARE elements.
191
- // Wait, textElements is a flat list.
192
- // TextRun has content string.
193
- // AutoText has no content string property directly? It renders as text.
194
- // In API, AutoText is a separate kind of element.
195
- // And it usually corresponds to a specific character length (e.g. 1 char for page number).
196
-
197
- // Let's assume AutoText has length 1 for indexing purposes if it doesn't have explicit content length.
198
- // But wait, getStartIndex/EndIndex logic in asString() uses textElements.map...
199
- // asString() uses: te.textRun ? te.textRun.content : ''.
200
- // So AutoText currently contributes 0 length to `asString()`!
201
- // If AutoText is not returned by `asString()`, then it doesn't exist in the string view?
202
- // If so, our `currentIndex` logic in `getParagraphs` works on `asString()`.
203
-
204
- // If user inserts Page Number, it appears as text.
205
- // So AutoText MUST contribute to content.
206
- // We probably need to mock the content of AutoText if it's missing.
207
- // Or assume `te.autoText` implies some content.
208
-
209
- // For now, if we encounter an autoText element:
269
+ (element.autoText ? 1 : 0);
270
+
210
271
  if (element.autoText) {
211
- // It's an auto text.
212
- // If it falls within [rangeStart, rangeEnd).
213
- // Since asString() implementation currently IGNORES autoText (returns ''), their index is effective 0?
214
- // We need to FIX `asString` to include AutoText content if we want consistent indexing?
215
- // OR `AutoText` objects are separate.
216
-
217
- // If `asString` returns "Page 1", then `1` is the auto text?
218
- // We'll proceed with creating the object.
219
-
220
- // Check bounds
221
- // If asString() skips it, its index is not advancing charIndex?
222
- // This suggests we need to look at how we populate text elements in tests.
223
- // If we don't allow creating AutoText in tests yet, `getAutoTexts()` will just return empty list.
224
- // That fulfills the requirement of "Implementing the method".
225
- // We will implement logic assuming `element.autoText` exists.
226
-
227
- const autoText = newFakeAutoText(
228
- newFakeTextRange(this.__shape, charIndex, charIndex + 1), // Assuming length 1
229
- element.autoText.type,
230
- autoTextIndex++
231
- );
232
- autoTexts.push(autoText);
272
+ if (charIndex >= rangeStart && charIndex < rangeEnd) {
273
+ const autoText = newFakeAutoText(
274
+ newFakeTextRange(this.__shape, charIndex, charIndex + 1),
275
+ element.autoText.type,
276
+ autoTextIndex++
277
+ );
278
+ autoTexts.push(autoText);
279
+ }
233
280
  }
234
281
 
235
- if (element.textRun) {
236
- charIndex += element.textRun.content.length;
237
- }
238
- // Paragraph markers?
282
+ charIndex += elementLength;
239
283
  }
240
284
  return autoTexts;
241
285
  }
@@ -0,0 +1,158 @@
1
+ import { Proxies } from '../../support/proxies.js';
2
+ import { newFakeLink } from './fakelink.js';
3
+ import { newFakeColor } from '../common/fakecolor.js';
4
+ import { TextBaselineOffset } from '../enums/slidesenums.js';
5
+
6
+ /**
7
+ * create a new FakeTextStyle instance
8
+ * @param {...any} args
9
+ * @returns {FakeTextStyle}
10
+ */
11
+ export const newFakeTextStyle = (...args) => {
12
+ return Proxies.guard(new FakeTextStyle(...args));
13
+ };
14
+
15
+ export class FakeTextStyle {
16
+ constructor(textRange) {
17
+ this.__textRange = textRange;
18
+ }
19
+
20
+ get __style() {
21
+ // In Slides API, styles are in TextElement.textRun.style.
22
+ // A TextRange spans multiple elements.
23
+ // Usually GAS returns the style if it's uniform across the range, or null/default otherwise.
24
+ // For simplicity, we'll return the style of the first textRun in the range.
25
+ const resource = this.__textRange.__resource;
26
+ const elements = resource?.textElements || [];
27
+ const start = this.__textRange.getStartIndex();
28
+
29
+ // Find the first textRun that overlaps with our range
30
+ let currentIndex = 0;
31
+ for (const element of elements) {
32
+ const length = element.textRun?.content?.length || (element.autoText ? 1 : 0);
33
+ if (currentIndex >= start && (element.textRun || element.autoText)) {
34
+ return element.textRun?.style || element.autoText?.style || {};
35
+ }
36
+ currentIndex += length;
37
+ }
38
+ return {};
39
+ }
40
+
41
+ getBackgroundColor() {
42
+ // Slides API: backgroundColor is in style.
43
+ // But wait, Slides API style has backgroundColor property?
44
+ // Actually, it has backgroundColor in TextStyle.
45
+ return this.__style.backgroundColor ? newFakeColor(this.__style.backgroundColor) : null;
46
+ }
47
+
48
+ getBaselineOffset() {
49
+ return TextBaselineOffset[this.__style.baselineOffset || 'NONE'];
50
+ }
51
+
52
+ getFontFamily() {
53
+ return this.__style.fontFamily || 'Arial';
54
+ }
55
+
56
+ getFontSize() {
57
+ return this.__textRange.__shape.__normalize(this.__style.fontSize) || 12;
58
+ }
59
+
60
+ getFontWeight() {
61
+ return this.__style.fontWeight || 400;
62
+ }
63
+
64
+ getForegroundColor() {
65
+ return this.__style.foregroundColor ? newFakeColor(this.__style.foregroundColor) : null;
66
+ }
67
+
68
+ getLink() {
69
+ return this.__style.link ? newFakeLink(this.__style.link) : null;
70
+ }
71
+
72
+ hasLink() {
73
+ return !!this.__style.link;
74
+ }
75
+
76
+ isBold() {
77
+ return !!this.__style.bold;
78
+ }
79
+
80
+ isItalic() {
81
+ return !!this.__style.italic;
82
+ }
83
+
84
+ isSmallCaps() {
85
+ return !!this.__style.smallCaps;
86
+ }
87
+
88
+ isStrikethrough() {
89
+ return !!this.__style.strikethrough;
90
+ }
91
+
92
+ isUnderline() {
93
+ return !!this.__style.underline;
94
+ }
95
+
96
+ // Setters - using batchUpdate via parent shape/table
97
+ __update(props, fields) {
98
+ // Logic to send batchUpdate for the specific text range.
99
+ // This requires the parent shape/table objectId and the range indices.
100
+ const presentationId = this.__textRange.__shape.__presentation.getId();
101
+ const objectId = this.__textRange.__shape.getObjectId();
102
+ const cellLocation = this.__textRange.__shape.__cellLocation;
103
+
104
+ Slides.Presentations.batchUpdate({ requests: [{
105
+ updateTextStyle: {
106
+ objectId,
107
+ cellLocation,
108
+ style: props,
109
+ fields: fields || Object.keys(props).join(','),
110
+ textRange: {
111
+ type: 'FIXED_RANGE',
112
+ startIndex: this.__textRange.getStartIndex(),
113
+ endIndex: this.__textRange.getEndIndex()
114
+ }
115
+ }
116
+ }] }, presentationId);
117
+ }
118
+
119
+ setBold(bold) {
120
+ this.__update({ bold }, 'bold');
121
+ return this;
122
+ }
123
+
124
+ setItalic(italic) {
125
+ this.__update({ italic }, 'italic');
126
+ return this;
127
+ }
128
+
129
+ setUnderline(underline) {
130
+ this.__update({ underline }, 'underline');
131
+ return this;
132
+ }
133
+
134
+ setStrikethrough(strikethrough) {
135
+ this.__update({ strikethrough }, 'strikethrough');
136
+ return this;
137
+ }
138
+
139
+ setFontSize(fontSize) {
140
+ this.__update({ fontSize: { magnitude: fontSize, unit: 'PT' } }, 'fontSize');
141
+ return this;
142
+ }
143
+
144
+ setFontFamily(fontFamily) {
145
+ this.__update({ fontFamily }, 'fontFamily');
146
+ return this;
147
+ }
148
+
149
+ setForegroundColor(color) {
150
+ // Simplified: assume hex string or Color object
151
+ this.__update({ foregroundColor: { opaqueColor: { rgbColor: { red: 0, green: 0, blue: 0 } } } }, 'foregroundColor');
152
+ return this;
153
+ }
154
+
155
+ toString() {
156
+ return 'TextStyle';
157
+ }
158
+ }
@@ -0,0 +1,35 @@
1
+ import { Proxies } from '../../support/proxies.js';
2
+ import { FakePageElement, PageElementRegistry } from './fakepageelement.js';
3
+
4
+ export const newFakeVideo = (...args) => {
5
+ return Proxies.guard(new FakeVideo(...args));
6
+ };
7
+
8
+ PageElementRegistry.newFakeVideo = newFakeVideo;
9
+
10
+ export class FakeVideo extends FakePageElement {
11
+ constructor(resource, page) {
12
+ super(resource, page);
13
+ }
14
+
15
+ getSource() {
16
+ return this.__resource.video?.source || 'UNSUPPORTED';
17
+ }
18
+
19
+ getThumbnailUrl() {
20
+ // API returns this in video resource
21
+ return `https://img.youtube.com/vi/${this.getVideoId()}/0.jpg`;
22
+ }
23
+
24
+ getUrl() {
25
+ return this.__resource.video?.url || '';
26
+ }
27
+
28
+ getVideoId() {
29
+ return this.__resource.video?.id || '';
30
+ }
31
+
32
+ toString() {
33
+ return 'Video';
34
+ }
35
+ }
@@ -0,0 +1,22 @@
1
+ import { Proxies } from '../../support/proxies.js';
2
+ import { FakePageElement, PageElementRegistry } from './fakepageelement.js';
3
+
4
+ export const newFakeWordArt = (...args) => {
5
+ return Proxies.guard(new FakeWordArt(...args));
6
+ };
7
+
8
+ PageElementRegistry.newFakeWordArt = newFakeWordArt;
9
+
10
+ export class FakeWordArt extends FakePageElement {
11
+ constructor(resource, page) {
12
+ super(resource, page);
13
+ }
14
+
15
+ getRenderedText() {
16
+ return this.__resource.wordArt?.renderedText || '';
17
+ }
18
+
19
+ toString() {
20
+ return 'WordArt';
21
+ }
22
+ }
@@ -1,11 +1,17 @@
1
1
  import { newFakeShape } from './fakeshape.js';
2
2
  import { newFakeLine } from './fakeline.js';
3
3
  import { newFakeTable } from './faketable.js';
4
+ import { newFakeGroup } from './fakegroup.js';
5
+ import { newFakeImage } from './fakeimage.js';
6
+ import { newFakeVideo } from './fakevideo.js';
7
+ import { newFakeSpeakerSpotlight } from './fakespeakerspotlight.js';
8
+ import { newFakeWordArt } from './fakewordart.js';
9
+ import { PageElementRegistry } from './fakepageelement.js';
4
10
 
5
11
  /**
6
12
  * Converts a base PageElement to a more specific subclass (Shape, Line, etc.)
7
13
  * @param {FakePageElement} pageElement The base page element.
8
- * @returns {FakePageElement|FakeShape|FakeLine} The specific subclass.
14
+ * @returns {FakePageElement|FakeShape|FakeLine|FakeGroup|FakeImage|FakeVideo|FakeSpeakerSpotlight|FakeWordArt} The specific subclass.
9
15
  */
10
16
  export const asSpecificPageElement = (pageElement) => {
11
17
  const resource = pageElement.__resource;
@@ -18,6 +24,23 @@ export const asSpecificPageElement = (pageElement) => {
18
24
  if (resource.table) {
19
25
  return newFakeTable(resource, pageElement.__page);
20
26
  }
27
+ if (resource.elementGroup || resource.group) {
28
+ return newFakeGroup(resource, pageElement.__page);
29
+ }
30
+ if (resource.image) {
31
+ return newFakeImage(resource, pageElement.__page);
32
+ }
33
+ if (resource.video) {
34
+ return newFakeVideo(resource, pageElement.__page);
35
+ }
36
+ if (resource.speakerSpotlight) {
37
+ return newFakeSpeakerSpotlight(resource, pageElement.__page);
38
+ }
39
+ if (resource.wordArt) {
40
+ return newFakeWordArt(resource, pageElement.__page);
41
+ }
21
42
  // Add other types as they are implemented
22
43
  return pageElement;
23
44
  };
45
+
46
+ PageElementRegistry.asSpecificPageElement = asSpecificPageElement;