@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.
- package/README.md +17 -3
- package/package.json +1 -1
- package/pngs/srv.jpg +0 -0
- package/src/cli/app.js +1 -0
- package/src/cli/togas.js +23 -14
- package/src/index.js +1 -0
- package/src/services/advslides/fakeadvslides.js +11 -5
- package/src/services/base/app.js +9 -0
- package/src/services/base/fakebase.js +28 -0
- package/src/services/common/fakeadvresource.js +3 -2
- package/src/services/content/contentservice.js +3 -2
- package/src/services/enums/baseenums.js +47 -0
- package/src/services/enums/contentenums.js +1 -3
- package/src/services/enums/scriptenums.js +31 -4
- package/src/services/enums/slidesenums.js +3 -1
- package/src/services/enums/xmlenums.js +14 -0
- package/src/services/html/serverworker.js +1 -1
- package/src/services/scriptapp/app.js +14 -7
- package/src/services/scriptapp/fakeauthorizationinfo.js +4 -4
- package/src/services/slidesapp/app.js +5 -0
- package/src/services/slidesapp/fakeautofit.js +1 -1
- package/src/services/slidesapp/fakeborder.js +106 -0
- package/src/services/slidesapp/fakecolorscheme.js +1 -1
- package/src/services/slidesapp/fakefill.js +216 -0
- package/src/services/slidesapp/fakegroup.js +35 -0
- package/src/services/slidesapp/fakeimage.js +118 -0
- package/src/services/slidesapp/fakelayout.js +351 -0
- package/src/services/slidesapp/fakeline.js +2 -2
- package/src/services/slidesapp/fakelinefill.js +15 -16
- package/src/services/slidesapp/fakelink.js +20 -3
- package/src/services/slidesapp/fakelist.js +36 -0
- package/src/services/slidesapp/fakeliststyle.js +105 -0
- package/src/services/slidesapp/fakemaster.js +358 -0
- package/src/services/slidesapp/fakenotesmaster.js +125 -0
- package/src/services/slidesapp/fakenotespage.js +102 -2
- package/src/services/slidesapp/fakepagebackground.js +109 -1
- package/src/services/slidesapp/fakepageelement.js +157 -18
- package/src/services/slidesapp/fakepageelementrange.js +28 -0
- package/src/services/slidesapp/fakepagerange.js +28 -0
- package/src/services/slidesapp/fakeparagraphstyle.js +139 -0
- package/src/services/slidesapp/fakepicturefill.js +32 -0
- package/src/services/slidesapp/fakepresentation.js +126 -2
- package/src/services/slidesapp/fakeshape.js +9 -0
- package/src/services/slidesapp/fakeslide.js +216 -24
- package/src/services/slidesapp/fakesolidfill.js +45 -0
- package/src/services/slidesapp/fakespeakerspotlight.js +18 -0
- package/src/services/slidesapp/faketable.js +55 -9
- package/src/services/slidesapp/faketablecell.js +141 -12
- package/src/services/slidesapp/faketablecellrange.js +28 -0
- package/src/services/slidesapp/faketablecolumn.js +72 -0
- package/src/services/slidesapp/faketablerow.js +31 -0
- package/src/services/slidesapp/faketextrange.js +179 -135
- package/src/services/slidesapp/faketextstyle.js +158 -0
- package/src/services/slidesapp/fakevideo.js +35 -0
- package/src/services/slidesapp/fakewordart.js +22 -0
- package/src/services/slidesapp/pageelementfactory.js +24 -1
- package/src/services/spreadsheetapp/fakeembeddedchartbuilder.js +92 -12
- package/src/services/spreadsheetapp/fakespreadsheet.js +360 -62
- package/src/services/spreadsheetapp/fakespreadsheettheme.js +53 -0
- package/src/services/urlfetchapp/app.js +216 -175
- package/src/services/xmlservice/app.js +3 -78
- package/src/services/xmlservice/fakeattribute.js +15 -0
- package/src/services/xmlservice/fakecdata.js +40 -0
- package/src/services/xmlservice/fakecomment.js +34 -0
- package/src/services/xmlservice/fakecontent.js +51 -0
- package/src/services/xmlservice/fakedoctype.js +68 -0
- package/src/services/xmlservice/fakedocument.js +110 -13
- package/src/services/xmlservice/fakeelement.js +297 -82
- package/src/services/xmlservice/fakeentityref.js +54 -0
- package/src/services/xmlservice/fakeformat.js +67 -22
- package/src/services/xmlservice/fakeprocessinginstruction.js +44 -0
- package/src/services/xmlservice/faketext.js +39 -0
- package/src/services/xmlservice/fakexmlservice.js +118 -0
- package/src/support/sxfetch.js +60 -0
- package/tools/omlx.env.example +6 -0
- 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.
|
|
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
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
117
|
-
|
|
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
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
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;
|
|
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,
|
|
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)
|
|
189
|
-
|
|
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
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
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
|
-
|
|
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;
|