google-spreadsheet 3.2.0 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,239 +0,0 @@
1
- const _ = require('lodash');
2
-
3
- const { columnToLetter } = require('./utils');
4
-
5
- const { GoogleSpreadsheetFormulaError } = require('./errors');
6
-
7
- class GoogleSpreadsheetCell {
8
- constructor(parentSheet, rowIndex, columnIndex, cellData) {
9
- this._sheet = parentSheet; // the parent GoogleSpreadsheetWorksheet instance
10
- this._row = rowIndex;
11
- this._column = columnIndex;
12
-
13
- this._updateRawData(cellData);
14
- return this;
15
- }
16
-
17
- // newData can be undefined/null if the cell is totally empty and unformatted
18
- _updateRawData(newData = {}) {
19
- this._rawData = newData;
20
- this._draftData = {}; // stuff to save
21
- this._error = null;
22
- if (_.get(this._rawData, 'effectiveValue.errorValue')) {
23
- this._error = new GoogleSpreadsheetFormulaError(this._rawData.effectiveValue.errorValue);
24
- }
25
- }
26
-
27
- // CELL LOCATION/ADDRESS /////////////////////////////////////////////////////////////////////////
28
- get rowIndex() { return this._row; }
29
- get columnIndex() { return this._column; }
30
- get a1Column() { return columnToLetter(this._column + 1); }
31
- get a1Row() { return this._row + 1; } // a1 row numbers start at 1 instead of 0
32
- get a1Address() { return `${this.a1Column}${this.a1Row}`; }
33
-
34
- // CELL CONTENTS - VALUE/FORMULA/NOTES ///////////////////////////////////////////////////////////
35
- get value() {
36
- // const typeKey = _.keys(this._rawData.effectiveValue)[0];
37
- if (this._draftData.value !== undefined) throw new Error('Value has been changed');
38
- if (this._error) return this._error;
39
- if (!this._rawData.effectiveValue) return null;
40
- return _.values(this._rawData.effectiveValue)[0];
41
- }
42
-
43
- set value(newValue) {
44
- if (_.isBoolean(newValue)) {
45
- this._draftData.valueType = 'boolValue';
46
- } else if (_.isString(newValue)) {
47
- if (newValue.substr(0, 1) === '=') this._draftData.valueType = 'formulaValue';
48
- else this._draftData.valueType = 'stringValue';
49
- } else if (_.isFinite(newValue)) {
50
- this._draftData.valueType = 'numberValue';
51
- } else if (_.isNil(newValue)) {
52
- // null or undefined
53
- this._draftData.valueType = 'stringValue';
54
- newValue = '';
55
- } else {
56
- throw new Error('Set value to boolean, string, or number');
57
- }
58
- this._draftData.value = newValue;
59
- }
60
-
61
- get valueType() {
62
- // an error only happens with a formula
63
- if (this._error) return 'errorValue';
64
- if (!this._rawData.effectiveValue) return null;
65
- return _.keys(this._rawData.effectiveValue)[0];
66
- }
67
-
68
- get formattedValue() { return this._rawData.formattedValue || null; }
69
- set formattedValue(newVal) {
70
- throw new Error('You cannot modify the formatted value directly');
71
- }
72
-
73
- get formula() { return _.get(this._rawData, 'userEnteredValue.formulaValue', null); }
74
- set formula(newValue) {
75
- if (newValue.substr(0, 1) !== '=') throw new Error('formula must begin with "="');
76
- this.value = newValue; // use existing value setter
77
- }
78
- get formulaError() { return this._error; }
79
-
80
- get hyperlink() {
81
- if (this._draftData.value) throw new Error('Save cell to be able to read hyperlink');
82
- return this._rawData.hyperlink;
83
- }
84
- set hyperlink(val) {
85
- throw new Error('Do not set hyperlink directly. Instead set cell.formula, for example `cell.formula = \'=HYPERLINK("http://google.com", "Google")\'`');
86
- }
87
-
88
- get note() {
89
- return this._draftData.note !== undefined ? this._draftData.note : this._rawData.note;
90
- }
91
-
92
- set note(newVal) {
93
- if (newVal === null || newVal === undefined) newVal = '';
94
- if (!_.isString(newVal)) throw new Error('Note must be a string');
95
- if (newVal === this._rawData.note) delete this._draftData.note;
96
- else this._draftData.note = newVal;
97
- }
98
-
99
- // CELL FORMATTING ///////////////////////////////////////////////////////////////////////////////
100
- get userEnteredFormat() { return this._rawData.userEnteredFormat; }
101
- get effectiveFormat() { return this._rawData.effectiveFormat; }
102
- set userEnteredFormat(newVal) { throw new Error('Do not modify directly, instead use format properties'); }
103
- set effectiveFormat(newVal) { throw new Error('Read-only'); }
104
-
105
- _getFormatParam(param) {
106
- // we freeze the object so users don't change nested props accidentally
107
- // TODO: figure out something that would throw an error if you try to update it?
108
- if (_.get(this._draftData, `userEnteredFormat.${param}`)) {
109
- throw new Error('User format is unsaved - save the cell to be able to read it again');
110
- }
111
- return Object.freeze(this._rawData.userEnteredFormat[param]);
112
- }
113
-
114
- _setFormatParam(param, newVal) {
115
- if (_.isEqual(newVal, _.get(this._rawData, `userEnteredFormat.${param}`))) {
116
- _.unset(this._draftData, `userEnteredFormat.${param}`);
117
- } else {
118
- _.set(this._draftData, `userEnteredFormat.${param}`, newVal);
119
- this._draftData.clearFormat = false;
120
- }
121
- }
122
-
123
- // format getters
124
- get numberFormat() { return this._getFormatParam('numberFormat'); }
125
- get backgroundColor() { return this._getFormatParam('backgroundColor'); }
126
- get borders() { return this._getFormatParam('borders'); }
127
- get padding() { return this._getFormatParam('padding'); }
128
- get horizontalAlignment() { return this._getFormatParam('horizontalAlignment'); }
129
- get verticalAlignment() { return this._getFormatParam('verticalAlignment'); }
130
- get wrapStrategy() { return this._getFormatParam('wrapStrategy'); }
131
- get textDirection() { return this._getFormatParam('textDirection'); }
132
- get textFormat() { return this._getFormatParam('textFormat'); }
133
- get hyperlinkDisplayType() { return this._getFormatParam('hyperlinkDisplayType'); }
134
- get textRotation() { return this._getFormatParam('textRotation'); }
135
-
136
- // format setters
137
- set numberFormat(newVal) { return this._setFormatParam('numberFormat', newVal); }
138
- set backgroundColor(newVal) { return this._setFormatParam('backgroundColor', newVal); }
139
- set borders(newVal) { return this._setFormatParam('borders', newVal); }
140
- set padding(newVal) { return this._setFormatParam('padding', newVal); }
141
- set horizontalAlignment(newVal) { return this._setFormatParam('horizontalAlignment', newVal); }
142
- set verticalAlignment(newVal) { return this._setFormatParam('verticalAlignment', newVal); }
143
- set wrapStrategy(newVal) { return this._setFormatParam('wrapStrategy', newVal); }
144
- set textDirection(newVal) { return this._setFormatParam('textDirection', newVal); }
145
- set textFormat(newVal) { return this._setFormatParam('textFormat', newVal); }
146
- set hyperlinkDisplayType(newVal) { return this._setFormatParam('hyperlinkDisplayType', newVal); }
147
- set textRotation(newVal) { return this._setFormatParam('textRotation', newVal); }
148
-
149
- clearAllFormatting() {
150
- // need to track this separately since by setting/unsetting things, we may end up with
151
- // this._draftData.userEnteredFormat as an empty object, but not an intent to clear it
152
- this._draftData.clearFormat = true;
153
- delete this._draftData.userEnteredFormat;
154
- }
155
-
156
- // SAVING + UTILS ////////////////////////////////////////////////////////////////////////////////
157
-
158
- // returns true if there are any updates that have not been saved yet
159
- get _isDirty() {
160
- // have to be careful about checking undefined rather than falsy
161
- // in case a new value is empty string or 0 or false
162
- if (this._draftData.note !== undefined) return true;
163
- if (_.keys(this._draftData.userEnteredFormat).length) return true;
164
- if (this._draftData.clearFormat) return true;
165
- if (this._draftData.value !== undefined) return true;
166
- return false;
167
- }
168
-
169
- discardUnsavedChanges() {
170
- this._draftData = {};
171
- }
172
-
173
- async save() {
174
- await this._sheet.saveUpdatedCells([this]);
175
- }
176
-
177
- // used by worksheet when saving cells
178
- // returns an individual batchUpdate request to update the cell
179
- _getUpdateRequest() {
180
- // this logic should match the _isDirty logic above
181
- // but we need it broken up to build the request below
182
- const isValueUpdated = this._draftData.value !== undefined;
183
- const isNoteUpdated = this._draftData.note !== undefined;
184
- const isFormatUpdated = !!_.keys(this._draftData.userEnteredFormat || {}).length;
185
- const isFormatCleared = this._draftData.clearFormat;
186
-
187
- // if no updates, we return null, which we can filter out later before sending requests
188
- if (!_.some([isValueUpdated, isNoteUpdated, isFormatUpdated, isFormatCleared])) {
189
- return null;
190
- }
191
-
192
- // build up the formatting object, which has some quirks...
193
- const format = {
194
- // have to pass the whole object or it will clear existing properties
195
- ...this._rawData.userEnteredFormat,
196
- ...this._draftData.userEnteredFormat,
197
- };
198
- // if background color already set, cell has backgroundColor and backgroundColorStyle
199
- // but backgroundColorStyle takes precendence so we must remove to set the color
200
- // see https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets/cells#CellFormat
201
- if (_.get(this._draftData, 'userEnteredFormat.backgroundColor')) {
202
- delete (format.backgroundColorStyle);
203
- }
204
-
205
- return {
206
- updateCells: {
207
- rows: [{
208
- values: [{
209
- ...isValueUpdated && {
210
- userEnteredValue: { [this._draftData.valueType]: this._draftData.value },
211
- },
212
- ...isNoteUpdated && {
213
- note: this._draftData.note,
214
- },
215
- ...isFormatUpdated && {
216
- userEnteredFormat: format,
217
- },
218
- ...isFormatCleared && {
219
- userEnteredFormat: {},
220
- },
221
- }],
222
- }],
223
- // turns into a string of which fields to update ex "note,userEnteredFormat"
224
- fields: _.keys(_.pickBy({
225
- userEnteredValue: isValueUpdated,
226
- note: isNoteUpdated,
227
- userEnteredFormat: isFormatUpdated || isFormatCleared,
228
- })).join(','),
229
- start: {
230
- sheetId: this._sheet.sheetId,
231
- rowIndex: this.rowIndex,
232
- columnIndex: this.columnIndex,
233
- },
234
- },
235
- };
236
- }
237
- }
238
-
239
- module.exports = GoogleSpreadsheetCell;
@@ -1,72 +0,0 @@
1
- const { columnToLetter } = require('./utils');
2
-
3
- class GoogleSpreadsheetRow {
4
- constructor(parentSheet, rowNumber, data) {
5
- this._sheet = parentSheet; // the parent GoogleSpreadsheetWorksheet instance
6
- this._rowNumber = rowNumber; // the A1 row (1-indexed)
7
- this._rawData = data;
8
-
9
- for (let i = 0; i < this._sheet.headerValues.length; i++) {
10
- const propName = this._sheet.headerValues[i];
11
- if (!propName) continue; // skip empty header
12
- Object.defineProperty(this, propName, {
13
- get: () => this._rawData[i],
14
- set: (newVal) => { this._rawData[i] = newVal; },
15
- enumerable: true,
16
- });
17
- }
18
-
19
- return this;
20
- }
21
-
22
- get rowNumber() { return this._rowNumber; }
23
- // TODO: deprecate rowIndex - the name implies it should be zero indexed :(
24
- get rowIndex() { return this._rowNumber; }
25
- get a1Range() {
26
- return [
27
- this._sheet.a1SheetName,
28
- '!',
29
- `A${this._rowNumber}`,
30
- ':',
31
- `${columnToLetter(this._sheet.headerValues.length)}${this._rowNumber}`,
32
- ].join('');
33
- }
34
-
35
- async save(options = {}) {
36
- if (this._deleted) throw new Error('This row has been deleted - call getRows again before making updates.');
37
-
38
- const response = await this._sheet._spreadsheet.axios.request({
39
- method: 'put',
40
- url: `/values/${encodeURIComponent(this.a1Range)}`,
41
- params: {
42
- valueInputOption: options.raw ? 'RAW' : 'USER_ENTERED',
43
- includeValuesInResponse: true,
44
- },
45
- data: {
46
- range: this.a1Range,
47
- majorDimension: 'ROWS',
48
- values: [this._rawData],
49
- },
50
- });
51
- this._rawData = response.data.updatedData.values[0];
52
- }
53
-
54
- // delete this row
55
- async delete() {
56
- if (this._deleted) throw new Error('This row has been deleted - call getRows again before making updates.');
57
-
58
- const result = await this._sheet._makeSingleUpdateRequest('deleteRange', {
59
- range: {
60
- sheetId: this._sheet.sheetId,
61
- startRowIndex: this._rowNumber - 1, // this format is zero indexed, because of course...
62
- endRowIndex: this._rowNumber,
63
- },
64
- shiftDimension: 'ROWS',
65
- });
66
- this._deleted = true;
67
- return result;
68
- }
69
- async del() { return this.delete(); } // alias to mimic old version of this module
70
- }
71
-
72
- module.exports = GoogleSpreadsheetRow;
package/lib/errors.js DELETED
@@ -1,10 +0,0 @@
1
- class GoogleSpreadsheetFormulaError {
2
- constructor(errorInfo) {
3
- this.type = errorInfo.type;
4
- this.message = errorInfo.message;
5
- }
6
- }
7
-
8
- module.exports = {
9
- GoogleSpreadsheetFormulaError,
10
- };
package/lib/utils.js DELETED
@@ -1,32 +0,0 @@
1
- const _ = require('lodash');
2
-
3
- function getFieldMask(obj) {
4
- return _.keys(obj).join(',');
5
- }
6
-
7
- function columnToLetter(column) {
8
- let temp;
9
- let letter = '';
10
- let col = column;
11
- while (col > 0) {
12
- temp = (col - 1) % 26;
13
- letter = String.fromCharCode(temp + 65) + letter;
14
- col = (col - temp - 1) / 26;
15
- }
16
- return letter;
17
- }
18
-
19
- function letterToColumn(letter) {
20
- let column = 0;
21
- const { length } = letter;
22
- for (let i = 0; i < length; i++) {
23
- column += (letter.charCodeAt(i) - 64) * 26 ** (length - i - 1);
24
- }
25
- return column;
26
- }
27
-
28
- module.exports = {
29
- getFieldMask,
30
- columnToLetter,
31
- letterToColumn,
32
- };