@mcpher/gas-fakes 1.0.19 → 1.0.21
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 +63 -30
- package/gasmess/bruce/pbx.js +53 -2
- package/gprompts/gas-inventory.js +92 -0
- package/gprompts/gas-inventory.json +176 -0
- package/gprompts/inventory-list.json +1 -0
- package/gprompts/model.json +34 -0
- package/gprompts/package-lock.json +1103 -0
- package/gprompts/package.json +18 -0
- package/gprompts/temp_fetch.mjs +9 -0
- package/gprompts/update-progress.js +142 -0
- package/package.json +6 -2
- package/setup.sh +147 -0
- package/src/index.js +9 -2
- package/src/services/advdocs/app.js +4 -23
- package/src/services/advdrive/app.js +6 -28
- package/src/services/advforms/app.js +6 -25
- package/src/services/advgmail/app.js +11 -0
- package/src/services/advgmail/fakeadvgmail.js +39 -0
- package/src/services/advgmail/fakeadvgmaillabels.js +119 -0
- package/src/services/advgmail/fakeadvgmailusers.js +23 -0
- package/src/services/advgmail/gmailapis.js +15 -0
- package/src/services/advsheets/app.js +6 -26
- package/src/services/advslides/app.js +6 -28
- package/src/services/common/lazyloader.js +22 -0
- package/src/services/documentapp/app.js +8 -42
- package/src/services/documentapp/appenderhelpers.js +21 -23
- package/src/services/documentapp/elementhelpers.js +0 -1
- package/src/services/documentapp/elementoptions.js +22 -32
- package/src/services/documentapp/fakeelement.js +20 -3
- package/src/services/documentapp/fakelistitem.js +177 -28
- package/src/services/documentapp/fakeparagraph.js +194 -7
- package/src/services/documentapp/faketable.js +16 -0
- package/src/services/documentapp/faketablerow.js +15 -0
- package/src/services/documentapp/nrhelpers.js +1 -0
- package/src/services/documentapp/shadowdocument.js +10 -0
- package/src/services/driveapp/app.js +6 -28
- package/src/services/enums/gmailenums.js +8 -0
- package/src/services/formapp/app.js +5 -40
- package/src/services/gmailapp/app.js +11 -0
- package/src/services/gmailapp/fakegmailapp.js +35 -0
- package/src/services/gmailapp/fakegmaillabel.js +44 -0
- package/src/services/logger/app.js +8 -0
- package/src/services/logger/fakelogger.js +162 -0
- package/src/services/scriptapp/app.js +7 -1
- package/src/services/scriptapp/behavior.js +1 -1
- package/src/services/session/app.js +10 -0
- package/src/services/slidesapp/app.js +5 -40
- package/src/services/spreadsheetapp/app.js +6 -50
- package/src/services/spreadsheetapp/fakeprotection.js +6 -7
- package/src/services/spreadsheetapp/fakesheet.js +3 -4
- package/src/services/stores/app.js +0 -1
- package/src/services/urlfetchapp/app.js +0 -1
- package/src/services/utilities/app.js +6 -20
- package/src/support/gmailcacher.js +7 -0
- package/src/support/helpers.js +2 -2
- package/src/support/proxies.js +20 -1
- package/src/support/sxgmail.js +55 -0
- package/src/support/syncit.js +5 -2
- package/src/support/utils.js +46 -15
- package/src/support/workersync/sxfunctions.js +5 -10
- package/togas.bash +18 -5
- package/ghissues/image-size-inconsistency-issue.sh +0 -46
- package/ghissues/issue-formapp-create-title-inconsistency.sh +0 -51
- package/ghissues/issue-positioned-image.sh +0 -25
- package/ghissues/post-issue.sh +0 -53
- package/ghissues/protection-editors-issue.sh +0 -33
- package/ghissues/review-sandbox-listing-issue.sh +0 -45
- package/ghissues/sandbox-issue.sh +0 -31
- package/ghissues/setup-under-construction.sh +0 -107
- package/src/services/base/app.js +0 -33
- /package/{regenerate-progress-reports.sh → gprompts/regenerate-progress-reports.sh} +0 -0
- /package/src/services/{base → session}/fakesession.js +0 -0
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
* @file Provides a fake implementation of the ListItem class.
|
|
3
3
|
*/
|
|
4
4
|
import { Proxies } from '../../support/proxies.js';
|
|
5
|
-
import {
|
|
5
|
+
import { FakeParagraph } from './fakeparagraph.js';
|
|
6
6
|
import { registerElement } from './elementRegistry.js';
|
|
7
|
-
import { getText, updateParagraphStyle, getAttributes as getAttributesHelper } from './elementhelpers.js';
|
|
8
|
-
import { appendText } from './appenderhelpers.js';
|
|
7
|
+
import { getText, updateParagraphStyle, getAttributes as getAttributesHelper, findItem } from './elementhelpers.js';
|
|
8
|
+
import { appendText, insertImage } from './appenderhelpers.js';
|
|
9
9
|
import { notYetImplemented, signatureArgs } from '../../support/helpers.js';
|
|
10
10
|
import { Utils } from '../../support/utils.js';
|
|
11
|
-
const { is } = Utils;
|
|
11
|
+
const { is, lobify } = Utils;
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* Creates a new proxied FakeListItem instance.
|
|
@@ -22,11 +22,11 @@ export const newFakeListItem = (...args) => {
|
|
|
22
22
|
/**
|
|
23
23
|
* A fake implementation of the ListItem class for DocumentApp.
|
|
24
24
|
* @class FakeListItem
|
|
25
|
-
* @extends {
|
|
25
|
+
* @extends {FakeParagraph}
|
|
26
26
|
* @implements {GoogleAppsScript.Document.ListItem}
|
|
27
27
|
* @see https://developers.google.com/apps-script/reference/document/list-item
|
|
28
28
|
*/
|
|
29
|
-
export class FakeListItem extends
|
|
29
|
+
export class FakeListItem extends FakeParagraph {
|
|
30
30
|
/**
|
|
31
31
|
* @param {import('./shadowdocument.js').ShadowDocument} shadowDocument The shadow document manager.
|
|
32
32
|
* @param {string|object} nameOrItem The name of the element or the element's API resource.
|
|
@@ -36,25 +36,6 @@ export class FakeListItem extends FakeContainerElement {
|
|
|
36
36
|
super(shadowDocument, nameOrItem);
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
/**
|
|
40
|
-
* Gets the text content of the list item, flattening all child text elements.
|
|
41
|
-
* @returns {string} The text content.
|
|
42
|
-
* @see https://developers.google.com/apps-script/reference/document/list-item#getText()
|
|
43
|
-
*/
|
|
44
|
-
getText() {
|
|
45
|
-
return getText(this);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Appends text to the list item.
|
|
50
|
-
* @param {string|GoogleAppsScript.Document.Text} textOrTextElement The text to append.
|
|
51
|
-
* @returns {GoogleAppsScript.Document.ListItem} The list item, for chaining.
|
|
52
|
-
*/
|
|
53
|
-
appendText(textOrTextElement) {
|
|
54
|
-
appendText(this, textOrTextElement);
|
|
55
|
-
return this;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
39
|
/**
|
|
59
40
|
* Gets a copy of the element's attributes.
|
|
60
41
|
* @returns {object} The attributes.
|
|
@@ -77,12 +58,12 @@ export class FakeListItem extends FakeContainerElement {
|
|
|
77
58
|
const nestingLevel = this.getNestingLevel();
|
|
78
59
|
// The shadowDocument is only available on attached elements.
|
|
79
60
|
if (this.__isDetached) return null;
|
|
80
|
-
const docResource = this.shadowDocument.resource;
|
|
61
|
+
const docResource = this.shadowDocument.resource;
|
|
81
62
|
// The lists can be at the top level or inside the first tab.
|
|
82
|
-
const {lists} = this.shadowDocument.__unpackDocumentTab(docResource);
|
|
63
|
+
const { lists } = this.shadowDocument.__unpackDocumentTab(docResource);
|
|
83
64
|
// Find the list by ID.
|
|
84
65
|
const list = lists?.[listId];
|
|
85
|
-
if (!list) return null;
|
|
66
|
+
if (!list) return null;
|
|
86
67
|
|
|
87
68
|
const levelProps = list.listProperties?.nestingLevels?.[nestingLevel];
|
|
88
69
|
// For a default list, the API might not return explicit level properties.
|
|
@@ -198,6 +179,107 @@ export class FakeListItem extends FakeContainerElement {
|
|
|
198
179
|
return this;
|
|
199
180
|
}
|
|
200
181
|
|
|
182
|
+
getAlignment() {
|
|
183
|
+
const { nargs, matchThrow } = signatureArgs(arguments, 'ListItem.getAlignment');
|
|
184
|
+
if (nargs !== 0) matchThrow();
|
|
185
|
+
return this.getAttributes()[DocumentApp.Attribute.HORIZONTAL_ALIGNMENT];
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
setAlignment(alignment) {
|
|
189
|
+
const { nargs, matchThrow } = signatureArgs(arguments, 'ListItem.setAlignment');
|
|
190
|
+
if (nargs !== 1) matchThrow();
|
|
191
|
+
|
|
192
|
+
const alignmentMap = {
|
|
193
|
+
[DocumentApp.HorizontalAlignment.LEFT]: 'START',
|
|
194
|
+
[DocumentApp.HorizontalAlignment.CENTER]: 'CENTER',
|
|
195
|
+
[DocumentApp.HorizontalAlignment.RIGHT]: 'END',
|
|
196
|
+
[DocumentApp.HorizontalAlignment.JUSTIFIED]: 'JUSTIFY',
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
const apiAlignment = alignmentMap[alignment];
|
|
200
|
+
if (!apiAlignment) {
|
|
201
|
+
throw new Error(`Invalid argument: ${alignment}`);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return updateParagraphStyle(this, { alignment: apiAlignment }, 'alignment');
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
getIndentEnd() {
|
|
208
|
+
const { nargs, matchThrow } = signatureArgs(arguments, 'ListItem.getIndentEnd');
|
|
209
|
+
if (nargs !== 0) matchThrow();
|
|
210
|
+
return this.getAttributes()[DocumentApp.Attribute.INDENT_END];
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
setIndentEnd(indentEnd) {
|
|
214
|
+
const { nargs, matchThrow } = signatureArgs(arguments, 'ListItem.setIndentEnd');
|
|
215
|
+
if (nargs !== 1 || !is.number(indentEnd)) matchThrow();
|
|
216
|
+
return updateParagraphStyle(this, { indentEnd: { magnitude: indentEnd, unit: 'PT' } }, 'indentEnd');
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
getIndentFirstLine() {
|
|
220
|
+
const { nargs, matchThrow } = signatureArgs(arguments, 'ListItem.getIndentFirstLine');
|
|
221
|
+
if (nargs !== 0) matchThrow();
|
|
222
|
+
return this.getAttributes()[DocumentApp.Attribute.INDENT_FIRST_LINE];
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
getIndentStart() {
|
|
226
|
+
const { nargs, matchThrow } = signatureArgs(arguments, 'ListItem.getIndentStart');
|
|
227
|
+
if (nargs !== 0) matchThrow();
|
|
228
|
+
return this.getAttributes()[DocumentApp.Attribute.INDENT_START];
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
getLineSpacing() {
|
|
232
|
+
const { nargs, matchThrow } = signatureArgs(arguments, 'ListItem.getLineSpacing');
|
|
233
|
+
if (nargs !== 0) matchThrow();
|
|
234
|
+
return this.getAttributes()[DocumentApp.Attribute.LINE_SPACING];
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
setLineSpacing(multiplier) {
|
|
238
|
+
const { nargs, matchThrow } = signatureArgs(arguments, 'ListItem.setLineSpacing');
|
|
239
|
+
if (nargs !== 1 || !is.number(multiplier)) matchThrow();
|
|
240
|
+
return updateParagraphStyle(this, { lineSpacing: multiplier * 100 }, 'lineSpacing');
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
getSpacingAfter() {
|
|
244
|
+
const { nargs, matchThrow } = signatureArgs(arguments, 'ListItem.getSpacingAfter');
|
|
245
|
+
if (nargs !== 0) matchThrow();
|
|
246
|
+
return this.getAttributes()[DocumentApp.Attribute.SPACING_AFTER];
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
setSpacingAfter(spacingAfter) {
|
|
250
|
+
const { nargs, matchThrow } = signatureArgs(arguments, 'ListItem.setSpacingAfter');
|
|
251
|
+
if (nargs !== 1 || !is.number(spacingAfter)) matchThrow();
|
|
252
|
+
return updateParagraphStyle(this, { spaceBelow: { magnitude: spacingAfter, unit: 'PT' } }, 'spaceBelow');
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
getSpacingBefore() {
|
|
256
|
+
const { nargs, matchThrow } = signatureArgs(arguments, 'ListItem.getSpacingBefore');
|
|
257
|
+
if (nargs !== 0) matchThrow();
|
|
258
|
+
return this.getAttributes()[DocumentApp.Attribute.SPACING_BEFORE];
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
setSpacingBefore(spacingBefore) {
|
|
262
|
+
const { nargs, matchThrow } = signatureArgs(arguments, 'ListItem.setSpacingBefore');
|
|
263
|
+
if (nargs !== 1 || !is.number(spacingBefore)) matchThrow();
|
|
264
|
+
return updateParagraphStyle(this, { spaceAbove: { magnitude: spacingBefore, unit: 'PT' } }, 'spaceAbove');
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
isLeftToRight() {
|
|
268
|
+
const { nargs, matchThrow } = signatureArgs(arguments, 'ListItem.isLeftToRight');
|
|
269
|
+
if (nargs !== 0) matchThrow();
|
|
270
|
+
// The getAttributes helper will return true/false/null.
|
|
271
|
+
const ltr = this.getAttributes()[DocumentApp.Attribute.LEFT_TO_RIGHT];
|
|
272
|
+
// Default to true if null, which matches live behavior for new paragraphs.
|
|
273
|
+
return ltr === null ? true : ltr;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
setLeftToRight(leftToRight) {
|
|
277
|
+
const { nargs, matchThrow } = signatureArgs(arguments, 'ListItem.setLeftToRight');
|
|
278
|
+
if (nargs !== 1 || !is.boolean(leftToRight)) matchThrow();
|
|
279
|
+
const direction = leftToRight ? 'LEFT_TO_RIGHT' : 'RIGHT_TO_LEFT';
|
|
280
|
+
return updateParagraphStyle(this, { direction }, 'direction');
|
|
281
|
+
}
|
|
282
|
+
|
|
201
283
|
|
|
202
284
|
/**
|
|
203
285
|
* Sets the glyph type of the list item.
|
|
@@ -236,6 +318,73 @@ export class FakeListItem extends FakeContainerElement {
|
|
|
236
318
|
return updateParagraphStyle(this, { indentStart: { magnitude: indentStart, unit: 'PT' } }, 'indentStart');
|
|
237
319
|
}
|
|
238
320
|
|
|
321
|
+
clear() {
|
|
322
|
+
const { nargs, matchThrow } = signatureArgs(arguments, 'ListItem.clear');
|
|
323
|
+
if (nargs !== 0) matchThrow();
|
|
324
|
+
|
|
325
|
+
const item = this.__elementMapItem;
|
|
326
|
+
const requests = [];
|
|
327
|
+
|
|
328
|
+
// Find the named range associated with this element to protect it.
|
|
329
|
+
const nr = this.shadowDocument.getNamedRange(item.__name);
|
|
330
|
+
if (!nr) {
|
|
331
|
+
throw new Error(`Internal error: Could not find named range for element ${item.__name}`);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// Delete the content, but not the final newline that defines the paragraph.
|
|
335
|
+
if (item.endIndex - 1 > item.startIndex) {
|
|
336
|
+
requests.push({
|
|
337
|
+
deleteContentRange: {
|
|
338
|
+
range: { startIndex: item.startIndex, endIndex: item.endIndex - 1, segmentId: this.__segmentId }
|
|
339
|
+
}
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
// Protect the named range by deleting the old one and recreating it with the same name but the new, smaller range.
|
|
343
|
+
requests.push({ deleteNamedRange: { namedRangeId: nr.namedRangeId } });
|
|
344
|
+
requests.push({
|
|
345
|
+
createNamedRange: { name: nr.name, range: { startIndex: item.startIndex, endIndex: item.startIndex + 1, segmentId: this.__segmentId } },
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
Docs.Documents.batchUpdate({ requests }, this.shadowDocument.getId());
|
|
349
|
+
this.shadowDocument.refresh();
|
|
350
|
+
}
|
|
351
|
+
return this;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
setText(text) {
|
|
355
|
+
const { nargs, matchThrow } = signatureArgs(arguments, 'ListItem.setText');
|
|
356
|
+
if (nargs !== 1 || !is.string(text)) matchThrow();
|
|
357
|
+
|
|
358
|
+
const item = this.__elementMapItem;
|
|
359
|
+
const requests = [];
|
|
360
|
+
|
|
361
|
+
// Find the named range associated with this element to protect it.
|
|
362
|
+
const nr = this.shadowDocument.getNamedRange(item.__name);
|
|
363
|
+
if (!nr) {
|
|
364
|
+
throw new Error(`Internal error: Could not find named range for element ${item.__name}`);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
if (item.endIndex - 1 > item.startIndex) {
|
|
368
|
+
requests.push({ deleteContentRange: { range: { startIndex: item.startIndex, endIndex: item.endIndex - 1, segmentId: this.__segmentId } } });
|
|
369
|
+
}
|
|
370
|
+
if (text) {
|
|
371
|
+
requests.push({ insertText: { location: { index: item.startIndex, segmentId: this.__segmentId }, text } });
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// As you correctly pointed out, we must protect the named range.
|
|
375
|
+
// Delete the old one and recreate it with the same name but the new, updated range.
|
|
376
|
+
requests.push({ deleteNamedRange: { namedRangeId: nr.namedRangeId } });
|
|
377
|
+
requests.push({
|
|
378
|
+
createNamedRange: {
|
|
379
|
+
name: nr.name,
|
|
380
|
+
range: { startIndex: item.startIndex, endIndex: item.startIndex + (text ? text.length : 0) + 1, segmentId: this.__segmentId },
|
|
381
|
+
},
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
Docs.Documents.batchUpdate({ requests }, this.shadowDocument.getId());
|
|
385
|
+
this.shadowDocument.refresh();
|
|
386
|
+
}
|
|
387
|
+
|
|
239
388
|
/**
|
|
240
389
|
* Sets the nesting level of the list item.
|
|
241
390
|
* @param {number} nestingLevel The new nesting level, starting from 0.
|
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
import { Proxies } from '../../support/proxies.js';
|
|
2
2
|
import { signatureArgs } from '../../support/helpers.js';
|
|
3
3
|
import { Utils } from '../../support/utils.js';
|
|
4
|
+
import { imageOptions } from './elementoptions.js';
|
|
4
5
|
import { FakeContainerElement } from './fakecontainerelement.js';
|
|
5
6
|
import { registerElement } from './elementRegistry.js';
|
|
6
7
|
import { appendText, addPositionedImage, appendImage, insertImage } from './appenderhelpers.js';
|
|
7
|
-
import { getText as getTextHelper, getAttributes as getAttributesHelper } from './elementhelpers.js';
|
|
8
|
+
import { getText as getTextHelper, getAttributes as getAttributesHelper, updateParagraphStyle } from './elementhelpers.js';
|
|
8
9
|
|
|
9
10
|
const { is } = Utils;
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
13
|
* @implements {GoogleAppsScript.Document.Paragraph}
|
|
13
14
|
*/
|
|
14
|
-
class FakeParagraph extends FakeContainerElement {
|
|
15
|
+
export class FakeParagraph extends FakeContainerElement {
|
|
15
16
|
constructor(shadowDocument, name) {
|
|
16
17
|
const { nargs, matchThrow } = signatureArgs(arguments, 'Paragraph');
|
|
17
18
|
// An attached element has a shadowDocument and a string name (its ID).
|
|
@@ -52,15 +53,32 @@ class FakeParagraph extends FakeContainerElement {
|
|
|
52
53
|
appendInlineImage(image) {
|
|
53
54
|
const { nargs, matchThrow } = signatureArgs(arguments, 'Paragraph.appendInlineImage');
|
|
54
55
|
if (nargs !== 1) matchThrow();
|
|
55
|
-
|
|
56
|
-
|
|
56
|
+
|
|
57
|
+
// The generic appendImage helper creates a new paragraph. We need to insert into the existing one.
|
|
58
|
+
// We can use the insertImage helper, which inserts at a specific child index.
|
|
59
|
+
return this.insertInlineImage(this.getNumChildren(), image);
|
|
57
60
|
}
|
|
58
61
|
|
|
59
62
|
insertInlineImage(childIndex, image) {
|
|
60
63
|
const { nargs, matchThrow } = signatureArgs(arguments, 'Paragraph.insertInlineImage');
|
|
61
64
|
if (nargs !== 2) matchThrow();
|
|
62
|
-
|
|
63
|
-
|
|
65
|
+
|
|
66
|
+
// The generic insertImage helper is for top-level containers. We need a specific implementation.
|
|
67
|
+
const children = [];
|
|
68
|
+
for (let i = 0; i < this.getNumChildren(); i++) {
|
|
69
|
+
children.push(this.getChild(i));
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (childIndex < 0 || childIndex > children.length) {
|
|
73
|
+
throw new Error(`Child index (${childIndex}) must be less than or equal to the number of child elements (${children.length}).`);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Determine the character index for insertion.
|
|
77
|
+
const insertIndex = (childIndex === children.length)
|
|
78
|
+
? this.__elementMapItem.endIndex - 1 // Append before the final newline
|
|
79
|
+
: children[childIndex].__elementMapItem.startIndex; // Insert before the specified child
|
|
80
|
+
|
|
81
|
+
return this.__insertImageAtIndex(image, insertIndex);
|
|
64
82
|
}
|
|
65
83
|
|
|
66
84
|
setHeading(heading) {
|
|
@@ -131,12 +149,181 @@ class FakeParagraph extends FakeContainerElement {
|
|
|
131
149
|
return apiToEnumMap[namedStyleType] || DocumentApp.ParagraphHeading.NORMAL;
|
|
132
150
|
}
|
|
133
151
|
|
|
152
|
+
getAlignment() {
|
|
153
|
+
const { nargs, matchThrow } = signatureArgs(arguments, 'Paragraph.getAlignment');
|
|
154
|
+
if (nargs !== 0) matchThrow();
|
|
155
|
+
return this.getAttributes()[DocumentApp.Attribute.HORIZONTAL_ALIGNMENT];
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
setAlignment(alignment) {
|
|
159
|
+
const { nargs, matchThrow } = signatureArgs(arguments, 'Paragraph.setAlignment');
|
|
160
|
+
if (nargs !== 1) matchThrow();
|
|
161
|
+
|
|
162
|
+
const alignmentMap = {
|
|
163
|
+
[DocumentApp.HorizontalAlignment.LEFT]: 'START',
|
|
164
|
+
[DocumentApp.HorizontalAlignment.CENTER]: 'CENTER',
|
|
165
|
+
[DocumentApp.HorizontalAlignment.RIGHT]: 'END',
|
|
166
|
+
[DocumentApp.HorizontalAlignment.JUSTIFIED]: 'JUSTIFY',
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
const apiAlignment = alignmentMap[alignment];
|
|
170
|
+
if (!apiAlignment) {
|
|
171
|
+
throw new Error(`Invalid argument: ${alignment}`);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return updateParagraphStyle(this, { alignment: apiAlignment }, 'alignment');
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
getIndentEnd() {
|
|
178
|
+
const { nargs, matchThrow } = signatureArgs(arguments, 'Paragraph.getIndentEnd');
|
|
179
|
+
if (nargs !== 0) matchThrow();
|
|
180
|
+
return this.getAttributes()[DocumentApp.Attribute.INDENT_END];
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
setIndentEnd(indentEnd) {
|
|
184
|
+
const { nargs, matchThrow } = signatureArgs(arguments, 'Paragraph.setIndentEnd');
|
|
185
|
+
if (nargs !== 1 || !is.number(indentEnd)) matchThrow();
|
|
186
|
+
return updateParagraphStyle(this, { indentEnd: { magnitude: indentEnd, unit: 'PT' } }, 'indentEnd');
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
getIndentFirstLine() {
|
|
190
|
+
const { nargs, matchThrow } = signatureArgs(arguments, 'Paragraph.getIndentFirstLine');
|
|
191
|
+
if (nargs !== 0) matchThrow();
|
|
192
|
+
return this.getAttributes()[DocumentApp.Attribute.INDENT_FIRST_LINE];
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
setIndentFirstLine(indentFirstLine) {
|
|
196
|
+
const { nargs, matchThrow } = signatureArgs(arguments, 'Paragraph.setIndentFirstLine');
|
|
197
|
+
if (nargs !== 1 || !is.number(indentFirstLine)) matchThrow();
|
|
198
|
+
return updateParagraphStyle(this, { indentFirstLine: { magnitude: indentFirstLine, unit: 'PT' } }, 'indentFirstLine');
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
getIndentStart() {
|
|
202
|
+
const { nargs, matchThrow } = signatureArgs(arguments, 'Paragraph.getIndentStart');
|
|
203
|
+
if (nargs !== 0) matchThrow();
|
|
204
|
+
return this.getAttributes()[DocumentApp.Attribute.INDENT_START];
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
setIndentStart(indentStart) {
|
|
208
|
+
const { nargs, matchThrow } = signatureArgs(arguments, 'Paragraph.setIndentStart');
|
|
209
|
+
if (nargs !== 1 || !is.number(indentStart)) matchThrow();
|
|
210
|
+
return updateParagraphStyle(this, { indentStart: { magnitude: indentStart, unit: 'PT' } }, 'indentStart');
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
getLineSpacing() {
|
|
214
|
+
const { nargs, matchThrow } = signatureArgs(arguments, 'Paragraph.getLineSpacing');
|
|
215
|
+
if (nargs !== 0) matchThrow();
|
|
216
|
+
return this.getAttributes()[DocumentApp.Attribute.LINE_SPACING];
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
setLineSpacing(multiplier) {
|
|
220
|
+
const { nargs, matchThrow } = signatureArgs(arguments, 'Paragraph.setLineSpacing');
|
|
221
|
+
if (nargs !== 1 || !is.number(multiplier)) matchThrow();
|
|
222
|
+
return updateParagraphStyle(this, { lineSpacing: multiplier * 100 }, 'lineSpacing');
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
getSpacingAfter() {
|
|
226
|
+
const { nargs, matchThrow } = signatureArgs(arguments, 'Paragraph.getSpacingAfter');
|
|
227
|
+
if (nargs !== 0) matchThrow();
|
|
228
|
+
return this.getAttributes()[DocumentApp.Attribute.SPACING_AFTER];
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
setSpacingAfter(spacingAfter) {
|
|
232
|
+
const { nargs, matchThrow } = signatureArgs(arguments, 'Paragraph.setSpacingAfter');
|
|
233
|
+
if (nargs !== 1 || !is.number(spacingAfter)) matchThrow();
|
|
234
|
+
return updateParagraphStyle(this, { spaceBelow: { magnitude: spacingAfter, unit: 'PT' } }, 'spaceBelow');
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
getSpacingBefore() {
|
|
238
|
+
const { nargs, matchThrow } = signatureArgs(arguments, 'Paragraph.getSpacingBefore');
|
|
239
|
+
if (nargs !== 0) matchThrow();
|
|
240
|
+
return this.getAttributes()[DocumentApp.Attribute.SPACING_BEFORE];
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
setSpacingBefore(spacingBefore) {
|
|
244
|
+
const { nargs, matchThrow } = signatureArgs(arguments, 'Paragraph.setSpacingBefore');
|
|
245
|
+
if (nargs !== 1 || !is.number(spacingBefore)) matchThrow();
|
|
246
|
+
return updateParagraphStyle(this, { spaceAbove: { magnitude: spacingBefore, unit: 'PT' } }, 'spaceAbove');
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
isLeftToRight() {
|
|
250
|
+
const { nargs, matchThrow } = signatureArgs(arguments, 'Paragraph.isLeftToRight');
|
|
251
|
+
if (nargs !== 0) matchThrow();
|
|
252
|
+
// The getAttributes helper will return true/false/null.
|
|
253
|
+
const ltr = this.getAttributes()[DocumentApp.Attribute.LEFT_TO_RIGHT];
|
|
254
|
+
// Default to true if null, which matches live behavior for new paragraphs.
|
|
255
|
+
return ltr === null ? true : ltr;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
setLeftToRight(leftToRight) {
|
|
259
|
+
const { nargs, matchThrow } = signatureArgs(arguments, 'Paragraph.setLeftToRight');
|
|
260
|
+
if (nargs !== 1 || !is.boolean(leftToRight)) matchThrow();
|
|
261
|
+
const direction = leftToRight ? 'LEFT_TO_RIGHT' : 'RIGHT_TO_LEFT';
|
|
262
|
+
return updateParagraphStyle(this, { direction }, 'direction');
|
|
263
|
+
}
|
|
264
|
+
|
|
134
265
|
toString() {
|
|
135
266
|
return 'Paragraph';
|
|
136
267
|
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Helper to insert an image at a specific character index.
|
|
271
|
+
* @param {GoogleAppsScript.Base.BlobSource} image The image to insert.
|
|
272
|
+
* @param {number} index The character index.
|
|
273
|
+
* @returns {GoogleAppsScript.Document.InlineImage} The new image.
|
|
274
|
+
* @private
|
|
275
|
+
*/
|
|
276
|
+
__insertImageAtIndex(image, index) {
|
|
277
|
+
// As you correctly pointed out, we must protect the named range of the parent paragraph.
|
|
278
|
+
const item = this.__elementMapItem;
|
|
279
|
+
const nr = this.shadowDocument.getNamedRange(item.__name);
|
|
280
|
+
if (!nr) {
|
|
281
|
+
throw new Error(`Internal error: Could not find named range for element ${item.__name}`);
|
|
282
|
+
}
|
|
283
|
+
// This is a simplified and corrected version of the logic in imageOptions.getMainRequest.
|
|
284
|
+
// It directly creates the insertInlineImage request without the complex logic
|
|
285
|
+
// for adding new paragraphs, which was causing the empty request array.
|
|
286
|
+
const { uri, size, cleanup } = imageOptions.__getImageUriAndSize(image);
|
|
287
|
+
|
|
288
|
+
const requests = [{
|
|
289
|
+
insertInlineImage: {
|
|
290
|
+
uri,
|
|
291
|
+
location: { index, segmentId: this.__segmentId },
|
|
292
|
+
objectSize: size,
|
|
293
|
+
},
|
|
294
|
+
}];
|
|
295
|
+
|
|
296
|
+
// Add the protection requests to the batch update.
|
|
297
|
+
// This tells the API to preserve the identity of the paragraph being modified.
|
|
298
|
+
requests.push({ deleteNamedRange: { namedRangeId: nr.namedRangeId } });
|
|
299
|
+
requests.push({
|
|
300
|
+
createNamedRange: {
|
|
301
|
+
name: nr.name,
|
|
302
|
+
// The new range will be expanded by 1 character for the inserted image.
|
|
303
|
+
range: { startIndex: item.startIndex, endIndex: item.endIndex + 1, segmentId: this.__segmentId },
|
|
304
|
+
},
|
|
305
|
+
});
|
|
306
|
+
try {
|
|
307
|
+
Docs.Documents.batchUpdate({ requests }, this.shadowDocument.getId());
|
|
308
|
+
this.shadowDocument.refresh();
|
|
309
|
+
// After refresh, find the newly created image element to return it.
|
|
310
|
+
// This is a simplification; a more robust solution would find the image
|
|
311
|
+
// based on its new objectId from the batchUpdate response.
|
|
312
|
+
const children = [];
|
|
313
|
+
for (let i = 0; i < this.getNumChildren(); i++) {
|
|
314
|
+
children.push(this.getChild(i));
|
|
315
|
+
}
|
|
316
|
+
// The source 'image' can be a Blob or a detached InlineImage.
|
|
317
|
+
// We need to get the name from the underlying blob in either case.
|
|
318
|
+
const sourceImageName = image.getName ? image.getName() : image.getBlob().getName();
|
|
319
|
+
return children.find(c => c.getType() === DocumentApp.ElementType.INLINE_IMAGE && c.getBlob().getName() === sourceImageName);
|
|
320
|
+
} finally {
|
|
321
|
+
if (cleanup) cleanup();
|
|
322
|
+
}
|
|
323
|
+
}
|
|
137
324
|
}
|
|
138
325
|
|
|
139
326
|
export const newFakeParagraph = (...args) => Proxies.guard(new FakeParagraph(...args));
|
|
140
327
|
|
|
141
328
|
registerElement('PARAGRAPH', newFakeParagraph);
|
|
142
|
-
registerElement('LIST_ITEM', newFakeParagraph); // ListItems are also paragraphs
|
|
329
|
+
registerElement('LIST_ITEM', newFakeParagraph); // ListItems are also paragraphs
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
import { Proxies } from '../../support/proxies.js';
|
|
6
6
|
import { FakeContainerElement } from './fakecontainerelement.js';
|
|
7
7
|
import { registerElement } from './elementRegistry.js';
|
|
8
|
+
import { newFakeElement } from './fakeelement.js';
|
|
8
9
|
import { signatureArgs } from '../../support/helpers.js';
|
|
9
10
|
import { Utils } from '../../support/utils.js';
|
|
10
11
|
const { is } = Utils;
|
|
@@ -45,6 +46,21 @@ export class FakeTable extends FakeContainerElement {
|
|
|
45
46
|
return this.getChild(rowIndex);
|
|
46
47
|
}
|
|
47
48
|
|
|
49
|
+
/**
|
|
50
|
+
* Gets the text content of the table.
|
|
51
|
+
* @returns {string} The text content.
|
|
52
|
+
* @see https://developers.google.com/apps-script/reference/document/table#getText()
|
|
53
|
+
*/
|
|
54
|
+
getText() {
|
|
55
|
+
const { nargs, matchThrow } = signatureArgs(arguments, 'Table.getText');
|
|
56
|
+
if (nargs !== 0) matchThrow();
|
|
57
|
+
// Live behavior: A Table's text is the concatenation of its non-empty rows' text, separated by newlines.
|
|
58
|
+
return this.__children
|
|
59
|
+
.map(childTwig => newFakeElement(this.shadowDocument, childTwig.name).__cast().getText())
|
|
60
|
+
.filter(text => text.length > 0) // Ignore empty rows
|
|
61
|
+
.join('\n');
|
|
62
|
+
}
|
|
63
|
+
|
|
48
64
|
getValues() {
|
|
49
65
|
return Array.from({ length: this.getNumRows() }, (_, r) => {
|
|
50
66
|
const row = this.getRow(r);
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
* @file Provides a fake implementation of the TableRow class.
|
|
3
3
|
*/
|
|
4
4
|
import { registerElement } from './elementRegistry.js';
|
|
5
|
+
import { newFakeElement } from './fakeelement.js';
|
|
6
|
+
import { signatureArgs } from '../../support/helpers.js';
|
|
5
7
|
import { Proxies } from '../../support/proxies.js';
|
|
6
8
|
import { FakeContainerElement } from './fakecontainerelement.js';
|
|
7
9
|
|
|
@@ -35,6 +37,19 @@ export class FakeTableRow extends FakeContainerElement {
|
|
|
35
37
|
getNumCells() {
|
|
36
38
|
return this.getNumChildren();
|
|
37
39
|
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Gets the text content of the table row.
|
|
43
|
+
* @returns {string} The text content.
|
|
44
|
+
*/
|
|
45
|
+
getText() {
|
|
46
|
+
const { nargs, matchThrow } = signatureArgs(arguments, 'TableRow.getText');
|
|
47
|
+
if (nargs !== 0) matchThrow();
|
|
48
|
+
// Live Apps Script joins cell text with a newline character.
|
|
49
|
+
return this.__children
|
|
50
|
+
.map(childTwig => newFakeElement(this.shadowDocument, childTwig.name).__cast().getText())
|
|
51
|
+
.join('\n');
|
|
52
|
+
}
|
|
38
53
|
}
|
|
39
54
|
|
|
40
55
|
/**
|
|
@@ -55,6 +55,7 @@ const addNrRequest = (type, element, addRequests, segmentId) => {
|
|
|
55
55
|
};
|
|
56
56
|
|
|
57
57
|
// either finds a matching named range, or adds it to the creation queue
|
|
58
|
+
|
|
58
59
|
export const findOrCreateNamedRangeName = (element, type, currentNr, addRequests, segmentId) => {
|
|
59
60
|
const { endIndex, startIndex } = element;
|
|
60
61
|
const prefix = makeNrPrefix(type, segmentId);
|
|
@@ -320,6 +320,16 @@ class ShadowDocument {
|
|
|
320
320
|
return null;
|
|
321
321
|
}
|
|
322
322
|
|
|
323
|
+
get namedRanges () {
|
|
324
|
+
return this.__unpackDocumentTab(this.resource).documentTab.namedRanges || {}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
getNamedRange (name) {
|
|
328
|
+
const nrs = this.namedRanges
|
|
329
|
+
if (!nrs) return null
|
|
330
|
+
return nrs[name]?.namedRanges?.[0] || null
|
|
331
|
+
}
|
|
332
|
+
|
|
323
333
|
/**
|
|
324
334
|
* Gets all footnotes in the document.
|
|
325
335
|
* @returns {FakeFootnote[]} An array of footnotes.
|
|
@@ -1,32 +1,10 @@
|
|
|
1
|
-
// fake Apps Script DriveApp
|
|
2
1
|
/**
|
|
3
|
-
* the idea here is to create
|
|
4
|
-
*
|
|
5
|
-
* We do this by using a proxy, intercepting calls to the
|
|
6
|
-
* initial sigleton and diverting them to a completed one
|
|
2
|
+
* the idea here is to create an empty global entry for the singleton
|
|
3
|
+
* but only load it when it is actually used.
|
|
7
4
|
*/
|
|
8
|
-
import { newFakeDriveApp } from './fakedrive.js'
|
|
9
|
-
import { Proxies } from '../../support/proxies.js'
|
|
10
5
|
|
|
11
|
-
|
|
12
|
-
|
|
6
|
+
import { lazyLoaderApp } from '../common/lazyloader.js'
|
|
7
|
+
import { newFakeDriveApp as maker } from './fakedrive.js'
|
|
13
8
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
*/
|
|
17
|
-
const name = "DriveApp"
|
|
18
|
-
if (typeof globalThis[name] === typeof undefined) {
|
|
19
|
-
|
|
20
|
-
const getApp = () => {
|
|
21
|
-
// if it hasne been intialized yet then do that
|
|
22
|
-
if (!_app) {
|
|
23
|
-
console.log('...activating proxy for', name)
|
|
24
|
-
_app = newFakeDriveApp()
|
|
25
|
-
}
|
|
26
|
-
// this is the actual driveApp we'll return from the proxy
|
|
27
|
-
return _app
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
Proxies.registerProxy(name, getApp)
|
|
31
|
-
|
|
32
|
-
}
|
|
9
|
+
let _app = null;
|
|
10
|
+
_app = lazyLoaderApp(_app, 'DriveApp', maker)
|
|
@@ -1,44 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* the idea here is to create
|
|
3
|
-
*
|
|
4
|
-
* We do this by using a proxy, intercepting calls to the
|
|
5
|
-
* initial singleton and diverting them to a completed one
|
|
2
|
+
* the idea here is to create an empty global entry for the singleton
|
|
3
|
+
* but only load it when it is actually used.
|
|
6
4
|
*/
|
|
7
|
-
import { newFakeFormApp } from './fakeformapp.js';
|
|
8
|
-
import {
|
|
5
|
+
import { newFakeFormApp as maker} from './fakeformapp.js';
|
|
6
|
+
import { lazyLoaderApp } from '../common/lazyloader.js'
|
|
9
7
|
|
|
10
8
|
let _app = null;
|
|
11
|
-
|
|
12
|
-
const name = 'FormApp';
|
|
13
|
-
const serviceName = 'FormApp';
|
|
14
|
-
|
|
15
|
-
if (typeof globalThis[name] === typeof undefined) {
|
|
16
|
-
const getApp = () => {
|
|
17
|
-
if (!_app) {
|
|
18
|
-
const realApp = newFakeFormApp();
|
|
19
|
-
|
|
20
|
-
_app = new Proxy(realApp, {
|
|
21
|
-
get(target, prop, receiver) {
|
|
22
|
-
if (prop === 'toString') {
|
|
23
|
-
return () => name;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
const serviceBehavior = ScriptApp.__behavior.sandboxService[serviceName];
|
|
27
|
-
|
|
28
|
-
if (!serviceBehavior.enabled) {
|
|
29
|
-
throw new Error(`${name} service is disabled by sandbox settings.`);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const allowedMethods = serviceBehavior.methods;
|
|
33
|
-
if (allowedMethods && typeof target[prop] === 'function' && !allowedMethods.includes(prop)) {
|
|
34
|
-
throw new Error(`Method ${name}.${prop} is not allowed by sandbox settings.`);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
return Reflect.get(target, prop, receiver);
|
|
38
|
-
},
|
|
39
|
-
});
|
|
40
|
-
}
|
|
41
|
-
return _app;
|
|
42
|
-
};
|
|
43
|
-
Proxies.registerProxy(name, getApp);
|
|
44
|
-
}
|
|
9
|
+
_app = lazyLoaderApp(_app, 'FormApp', maker)
|