├── CSE2Sheets.gs
├── Sheet Formatting
├── sheetsManager.gs
└── sortDataInTargetSheet.gs
├── Sheet Styling
└── setStandardSheetLayout.js
├── ebay_monitor.js
├── file management
└── demo_scripts.gs
├── getAverageTenureAtTargetCompany.gs
├── image to text
├── code.gs
└── view.html
├── parseCandidateResumes_fromEmail.js
├── parseCandidateResumes_fromEmail_v2.gs
├── peopledatalabs_fetch.js
└── sheets
└── openLinksInSheets.gs
/CSE2Sheets.gs:
--------------------------------------------------------------------------------
1 | const target_column_name = 'CSE URL';
2 |
3 | const getTableValuesBy = (sheet) => sheet.getRange(1,1,1,1).isBlank() ? [] : Array.from(sheet.getRange(1,1,sheet.getLastRow(),sheet.getLastColumn()).getValues());
4 | /* gets full sheet as Table, by the sheetObject. This only retrieves the max rows and columns containing data. Returns an empty array if the first cell is blank */
5 | const getColIndexBy = (table,header_name) => table[0].indexOf(header_name);
6 | /* gets the index number (not sheet col number) of the specified header within a given sheet */
7 |
8 | function getDataFromCSEURLs() {
9 | var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
10 |
11 | var table = getTableValuesBy(sheet);
12 |
13 | var target_col_index = getColIndexBy(table,target_column_name);
14 |
15 | var updated_table = [
16 | [
17 | ...table[0],
18 | ...[
19 | 'Result Link',
20 | 'Result Snippet',
21 | 'Result Title'
22 | ]
23 | ]
24 | ];
25 |
26 | for(let i=1; i
table.map(col=> col[i]);
2 | /* utility sheets
3 | returns column as an array. arguments are the column index and the table.
4 | */
5 |
6 | const getTableValuesBy = (sheet) => sheet.getRange(1,1,1,1).isBlank() ? [] : Array.from(sheet.getRange(1,1,sheet.getLastRow(),sheet.getLastColumn()).getValues());
7 | /* utility sheets
8 | getTableValuesBy => gets full sheet as Table, by the sheetObject. This only retrieves the max rows and columns containing data. Returns an empty array if the first cell is blank
9 | */
10 |
11 | const getColIndexBy = (table,header_name) => table[0].indexOf(header_name);
12 | /* utility sheets
13 | getColIndexBy => gets the index number (not sheet col number) of the specified header within a given sheet
14 | */
15 |
16 | const getRowIndexBy = (table,header_name,query) => table.findIndex(r=> r[getColIndexBy(table,header_name)] == query);
17 | /* utility sheets
18 | getRowIndexBy => gets the index number of the first string matching row value within a specified headername within a given sheet.
19 | */
20 |
21 | const getRowIndexRegX = (table,header_name,x) => table.findIndex(r=> x.test(r[getColIndexBy(table,header_name)]));
22 | /* utility sheets
23 | getRowIndexByX => gets the index number of the first matching regular expression on a row value within a specified headername within a given sheet.
24 | */
25 |
26 | function insertRowByColumnLooper(sheet,arr,row){
27 | var table = getTableValuesBy(sheet);
28 | for(var i=0; i -1){//insures we found a matching headername
31 | var val = typeof arr[i][1] == 'object' ? JSON.stringify(arr[i][1]) : arr[i][1]; // this converts objects to strings so we can insert them into cells
32 | sheet.getRange(row, (colIndex+1)).setValue(val);
33 | }
34 | }
35 | }
36 | /* utility sheets
37 | insertRowByColumnLooper => takes a sheetTable, 2D array with the [[header_name,value_to_set]], and the designated row to set, and sets the data in a loop. The headername is passed through another function to gain the appropriate column index to set.
38 | */
39 |
40 | function insertAdditionalHeaders(sheet,headers){
41 | if(sheet.getRange(1,1,3,3).isBlank()){ //assumes that we need to add headers if the first three rows and columns are blank
42 | sheet.getRange(1,1,1,headers.length).setValues([headers]);
43 | sheet.setFrozenRows(1);
44 | }else{
45 | var head = Array.from(getTableValuesBy(sheet))[0];
46 | var addThese = headers.filter(itm=> head.every(cell=> cell != itm ) );
47 | if(addThese.length > 0){
48 | sheet.getRange(1,(sheet.getLastColumn()+1),1,addThese.length).setValues([addThese]);
49 | }
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/Sheet Formatting/sortDataInTargetSheet.gs:
--------------------------------------------------------------------------------
1 | const transpose = (a)=> a[0].map((_, c)=> a.map(r=> r[c])); //https://gist.github.com/femto113/1784503
2 | const nameCase = (s) => s && typeof s == 'string' ? s.split(/(?=[^ğᴀғʀńŃŌŌŚŠśšŪūÿłžźżŁŽŹŻçćčÇĆČáāàâäãåÁÀÂÄÃĀĀÅĀÆæéèêëęēėÉÈÊËíìîïīįñÑóòôöõøœÓÒÔÖÕØŒßÚÙÛÜúùûüřa-zA-Z])\b/).map(el=> el.replace(/\w\S*/g, txt=> txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase())).join('').replace(/(?<=\bMc)\w/ig, t=> t.charAt(0).toUpperCase()) : s;
3 | const millisecondsToYears = (m) => m && /^\d[\d.]*$/.test(m) ? Math.round((m/3.154e+10)*1000)/1000 : m;
4 |
5 | function table2JSON(table){
6 | const arr = [];
7 | for(let i=1; i {
34 | let target_index = getColIndexBy(target_table,obj.key);
35 | let col = getColumn(target_index,target_table);
36 | return obj.action && obj.action == 'nameCase' ? col.map(cell=> nameCase(cell)) : obj.action && obj.action == 'millisecondsToYears' ? col.map(cell=> millisecondsToYears(cell)) : col;
37 | }));
38 | let rename_index = getColIndexBy(key_table,'display');
39 | let renamed_header = key_table.map(row=> row[rename_index]);
40 | renamed_header.shift();
41 | mapped_table.shift();
42 | let new_table = [...[renamed_header],...mapped_table];
43 |
44 | ss_parsed.insertSheet(target_sheet.getSheetName()+' parsed_sheet '+now_date).getRange(1,1,new_table.length,new_table[0].length).setValues(new_table);
45 |
46 | let parsed_sheet = ss_parsed.getSheetByName(target_sheet.getSheetName()+' parsed_sheet '+now_date);
47 |
48 | let styling_indexes = getColumn(getColIndexBy(key_table,'background color'),key_table);
49 | styling_indexes.shift();
50 |
51 | styling_indexes.forEach((color,i,r)=> {
52 | let rows = parsed_sheet.getLastRow();
53 | if(color){
54 | let colors = Array(rows).fill([color]);
55 | parsed_sheet.getRange(1,(i+1),parsed_sheet.getLastRow(),1).setBackgrounds(colors);
56 | }
57 | })
58 | parsed_sheet.setFrozenRows(1);
59 | parsed_sheet.setFrozenColumns(4);
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/Sheet Styling/setStandardSheetLayout.js:
--------------------------------------------------------------------------------
1 | /*
2 | view a review of the code and demo @ https://youtu.be/C6OgbYen1_s
3 | */
4 | function setStandardSheetLayout() {
5 | const header_background_color = '#009bad';
6 | const header_text_color = '#ffffff';
7 | const header_border_color = '#ffffff';
8 | const header_rows_to_freeze = 1;
9 | const column_border_color = '#7c7c7c';
10 | const number_of_columns_to_freeze = 3;
11 |
12 | const ss = SpreadsheetApp.getActiveSpreadsheet();
13 | const sheets = ss.getSheets();
14 | for(let i=0; i {
30 | let jobs_before_match = [];
31 | let jobs_after_match = [];
32 | record.jobs.forEach((j,i,r)=> {
33 | if(xarr.every(x=> x.test(j[key]))) {
34 | if(r[(i-1)]) jobs_before_match.push(r[(i-1)])
35 | if(r[(i+1)]) jobs_after_match.push(r[(i+1)])
36 | }
37 | })
38 | return cleanObject({...record,...{jobs_after_match:jobs_after_match},...{jobs_before_match:jobs_before_match}});
39 | });
40 | }
41 | function countKeys(records,key,matchkey){
42 | const mapped = records.filter(r=> r[matchkey]).map(r=> r[matchkey] ? r[matchkey].map(j=> j[key]) : []).flat();
43 | let counted = unqHsh(mapped,{}).map(j=> {
44 | return {
45 | ...{match:j},
46 | ...{ count: mapped.filter(m=> m == j).length},
47 | }
48 | });
49 | counted.sort((a,b)=> a.count - b.count);
50 | counted.reverse();
51 | return counted;
52 | }
53 | function getAverageTimeWithKeySearch(booleanString,key){
54 | const ss = SpreadsheetApp.openById(your_spreadsheet_id);
55 | const sheet = ss.getSheetByName(your_sheet_name);
56 | const table = getTableValuesBy(sheet);
57 | const renested = renestJobs(table);
58 | const xarr = buildSearchSet(booleanString);
59 | const work = renested.filter( record=> record.jobs.filter( job=> xarr.every( x=> x.test(job[key]) ) ).length );
60 | const times = renested.map(record=> {
61 | let matching = record.jobs.filter( job=> xarr.every( x=> x.test(job[key]) ) ).map( job=> job.years_in_job ? parseFloat(job.years_in_job) : 0.02);
62 | return matching.length ? matching.reduce((a,b)=> a+b) : 0;
63 | });
64 | return times.reduce((a,b)=> a+b) / work.length;
65 | }
66 |
67 | /*This translates the jobs back into a nested array so we can filter down on jobs by candidate record */
68 | function renestJobs(table){
69 | const reg = (o, n) => o ? o[n] : '';
70 | return table2JSON(table).map(record=> {
71 | let jobs = [];
72 | Object.entries(record).filter(kv=> /job_/.test(kv[0])).forEach(keyvalpair=> {
73 | let key = keyvalpair[0];
74 | let val = keyvalpair[1];
75 | let is_job_record = /^job_\d+_/.test(key);
76 | let placement_index = reg(/^job_(\d+)_/.exec(key),1) ? (parseInt(reg(/^job_(\d+)_/.exec(key),1)) -1) : 0;
77 | if(is_job_record && reg(/^job_(\d+)_/.exec(key),1)){
78 | if(jobs[placement_index]){
79 | jobs[placement_index][key.replace(/^job_\d+_/,'')] = val;
80 | }else{
81 | jobs[placement_index] = {};
82 | jobs[placement_index][key.replace(/^job_\d+_/,'')] = val;
83 | }
84 | }
85 | });
86 | return {...record,...{jobs: jobs}};
87 | }).filter(r=> r.job_1_job_company_name);
88 | }
89 |
90 | const parseStringAsXset = (s) => s
91 | .split(/\s+\band\b\s+|(?
93 | el.split(/\s+\bor\b\s+/i).map(ii=>
94 | ii.replace(/\s*\)\s*/g,'')
95 | .replace(/\s*\(\s*/g,'')
96 | .replace(/\s+/g,'.{0,3}')
97 | .replace(/"/g,'\\b')
98 | .replace(/\*/g,'\\w*')
99 | .replace(/\*\*\*/g,'.{0,60}'))
100 | .reduce((a,b)=> a+'|'+b)).filter(el=> el);
101 |
102 | function permutateNear(input,joiner){
103 | var nearx = /(?<=\||^)\S+?(?=\||$)/g;
104 | var base = input.replace(nearx, '').replace(/[\|]+/g, '|');
105 | var near_or = input.match(nearx) ? input.match(nearx).map(str=> {
106 | var arr = str.split(/~/);
107 | if(arr.length > 5){
108 | return str.replace(/[~]+/,'.');
109 | }else{
110 | var cont = [];
111 | var containArr = [];
112 | function comboLoop(arr, cont){
113 | if (arr.length == 0) {
114 | var row = cont.join(joiner);
115 | containArr.push(row)
116 | }
117 | for (var i = 0; i < arr.length; i++) {
118 | var x = arr.splice(i, 1);
119 | cont.push(x);
120 | comboLoop(arr, cont);
121 | cont.pop();
122 | arr.splice(i, 0, x);
123 | }
124 | }
125 | comboLoop(arr, cont);
126 | return containArr.reduce((a,b)=> a+'|'+b);
127 | }
128 | }).flat().reduce((a,b)=> a+'|'+b) : '';
129 | return base + near_or;
130 | }
131 | function buildSearchSet(str){
132 | if(str){
133 | var set = parseStringAsXset(str);
134 | var xset = set.map(r=> permutateNear(r,'.{0,39}')).map(r=> tryRegExp(r.replace(/^\||\|$/g,''),'i'));
135 | return xset;
136 | }else{return null}
137 | }
138 | function tryRegExp(s,f){
139 | try{return new RegExp(s,f)}
140 | catch(err){return err}
141 | }
142 |
143 | function table2JSON(table){
144 | const arr = [];
145 | for(let i=1; i a.filter(i=> o.hasOwnProperty(i) ? false : (o[i] = true));
155 |
156 | const cleanObject = (ob) =>
157 | Object.entries(ob).reduce((r, [k, v]) => {
158 | if(v != null && v != undefined && v != "" && ( typeof v == 'boolean' || typeof v == 'string' || typeof v == 'symbol' || typeof v == 'number' || typeof v == 'function' || (typeof v == 'object' && ((Array.isArray(v) && v.length) || (Array.isArray(v) != true)) ) ) ) {
159 | r[k] = v;
160 | return r;
161 | } else {
162 | return r;
163 | }
164 | }, {});
165 |
166 | const getColumn = (i,table) => table.map(col=> col[i]);
167 | /* utility sheets
168 | returns column as an array. arguments are the column index and the table.
169 | */
170 |
171 | const getTableValuesBy = (sheet) => sheet.getRange(1,1,1,1).isBlank() ? [] : Array.from(sheet.getRange(1,1,sheet.getLastRow(),sheet.getLastColumn()).getValues());
172 | /* utility sheets
173 | getTableValuesBy => gets full sheet as Table, by the sheetObject. This only retrieves the max rows and columns containing data. Returns an empty array if the first cell is blank
174 | */
175 |
176 | const getColIndexBy = (table,header_name) => table[0].indexOf(header_name);
177 | /* utility sheets
178 | getColIndexBy => gets the index number (not sheet col number) of the specified header within a given sheet
179 | */
180 |
181 | const getRowIndexBy = (table,header_name,query) => table.findIndex(r=> r[getColIndexBy(table,header_name)] == query);
182 | /* utility sheets
183 | getRowIndexBy => gets the index number of the first string matching row value within a specified headername within a given sheet.
184 | */
185 |
186 | const getRowIndexRegX = (table,header_name,x) => table.findIndex(r=> x.test(r[getColIndexBy(table,header_name)]));
187 | /* utility sheets
188 | getRowIndexByX => gets the index number of the first matching regular expression on a row value within a specified headername within a given sheet.
189 | */
190 |
--------------------------------------------------------------------------------
/image to text/code.gs:
--------------------------------------------------------------------------------
1 | function doGet(e) { //this function serves up the view HTML file
2 | return HtmlService.createTemplateFromFile('view')
3 | .evaluate()
4 | .setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL);
5 | }
6 |
7 | function processResumeFile(filename,dataURI){ //this function is called within the view.html file using the google.script.run.withSuccessHandler method
8 | var blob = dataURItoBlob(dataURI, filename);
9 | return fileImageBlob(blob);
10 | }
11 |
12 | function dataURItoBlob(dataURI, filename) { // code swiped from https://stackoverflow.com/a/36949118/1027723
13 | var byteString;
14 | if (dataURI.split(',')[0].indexOf('base64') >= 0){
15 | byteString = Utilities.base64Decode(dataURI.split(',')[1]);
16 | } else {
17 | byteString = decodeURI(dataURI.split(',')[1]);
18 | }
19 | var mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0];
20 | return Utilities.newBlob(byteString, mimeString, filename);
21 | }
22 |
23 | function fileImageBlob(blob){ //TODO: we are overwriteing the file variable. Fix this later and test to ensure it doesnt break anything.
24 | var filename = blob.getName();
25 | var type = /docx$/i.test(filename) ? 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' : /pdf$/i.test(filename) ? 'application/pdf' : /png$/i.test(filename) ? 'image/png' : /jpeg$|jpg$/i.test(filename) ? 'image/jpeg' : /bmp$/i.test(filename) ? 'image/bmp' : 'text/plain';
26 | if(type){
27 | var file = {
28 | title: 'OCR File',
29 | mimeType: type
30 | };
31 | file = Drive.Files.insert(file, blob, {ocr: true});
32 | var doc = DocumentApp.openByUrl(file.embedLink);
33 | var body = doc.getBody().getText().replace(/\n/g,'
');
34 | deleteFileById(file.getId());
35 | return body;
36 | }else{
37 | return 'file type not supported';
38 | }
39 | }
40 |
41 | function deleteFileById(id){
42 | var file = DriveApp.getFileById(id);
43 | file.setTrashed(true);
44 | }
45 |
--------------------------------------------------------------------------------
/image to text/view.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
Image and PDF to Text converter
10 |
Upload a file, wait for the processing, then download the text file. Text content is editable prior to downloading.
11 |
12 |
17 |
20 |
21 |
22 |
23 |
177 |
--------------------------------------------------------------------------------
/parseCandidateResumes_fromEmail.js:
--------------------------------------------------------------------------------
1 | /*
2 | GOOGLE APPS SCRIPT
3 | This script will monitor inbound emails for a given subject name, then will parse those resumes as a Google Doc, and add a record to a spreadsheet
4 |
5 | Full build video:
6 | https://youtu.be/r05EwELmymE
7 |
8 | Set Time-Based Triggers:
9 | https://youtu.be/RvUyyDpXxuE
10 | */
11 |
12 | var sheetId = 'YOUR_SPREADSHEET_ID_GOES_HERE';
13 | var ss = SpreadsheetApp.openById(sheetId); //https://developers.google.com/apps-script/reference/spreadsheet/spreadsheet-app#openById(String)
14 | var mainSheet = ss.getSheetByName('Main'); //https://developers.google.com/apps-script/reference/spreadsheet/sheet#getsheetbyname
15 |
16 | function reg(o,n){if(o){return o[n].trim()}else{return '';}}
17 | function unq(arr){ return arr.filter(function(e, p, a) { return a.indexOf(e) == p }) }
18 | function fixCase(s){ return s.replace(/\w\S*/g, function(txt) { return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase()} );}
19 |
20 | function parseAsRegexArr(bool) {
21 | function rxReady(s){ return s ? s.replace(/"/g, '\\b').trim().replace(/\)/g, '').replace(/\(/g, '').replace(/\s+/g, '.{0,2}').replace(/\//g, '\\/').replace(/\+/g, '\\+').replace(/\s*\*\s*/g, '\\s*\\w*\\s+') : s;}
22 | function checkSimpleOR(s) { return /\bor\b/i.test(s) && /\(/.test(s) === false;}
23 | if (checkSimpleOR(bool)) {
24 | var x = new RegExp(bool.replace(/\s+OR\s+|\s*\|\s*/gi, '|').replace(/\//g, '\\/').replace(/"/g, '\\b').replace(/\s+/g, '.{0,2}').replace(/\s*\*\s*/g, '\\s*\\w*\\s+'), 'i');
25 | var xArr = [x];
26 | return xArr;
27 | } else {
28 | var orx = "\\(.+?\\)|(\\(\\w+\\s{0,1}OR\\s|\\w+\\s{0,1}OR\\s)+((\\w+\s)+?|(\\w+)\\)+)+?";
29 | var orMatch = bool ? bool.match(new RegExp(orx, 'g')) : [];
30 | var orArr = orMatch ? orMatch.map(function(b) {return rxReady(b.replace(/\s+OR\s+|\s*\|\s*/gi, '|'))}) : [];
31 | var noOrs = bool ? bool.replace(new RegExp(orx, 'g'), '').split(/\s+[AND\s+]+/i) : bool;
32 | var ands = noOrs ? noOrs.map(function(a) { return rxReady(a)}) : [];
33 | var xArr = ands.concat(orArr).filter(function(i){ return i != ''}).map(function(x){return new RegExp(x, 'i')});
34 | return xArr;
35 | }
36 | }
37 | function booleanSearch(bool,target){
38 | var arr = parseAsRegexArr(bool);
39 | return arr.every(function(x){
40 | return x.test(target);
41 | });
42 | }
43 |
44 | function getFolderByName(x){
45 | var folders = DriveApp.getFolders(); //https://developers.google.com/apps-script/reference/drive/folder-iterator
46 | while(folders.hasNext()){
47 | var folder = folders.next();
48 | if(x.test(folder.getName())) return folder.getId();
49 | }
50 | }
51 |
52 | function getEmailThreadsBySubject(searchString){
53 | var matches = [];
54 | var threads = GmailApp.getInboxThreads(0, 50); //https://developers.google.com/apps-script/reference/gmail/gmail-app#getInboxThreads(Integer,Integer)
55 | for(var i=0; i 0) { //https://developers.google.com/apps-script/reference/gmail/gmail-thread#isUnread()
60 | matches.push(msgs[0]);
61 | } // end if msgs unread && match params
62 | } //end forloop
63 | return matches;
64 | }
65 |
66 | function parseCandidateSubmissions(){
67 | var targetEmails = getEmailThreadsBySubject('Candidate Submission OR candidate submittal');
68 | for(var i=0; i=0; i--){ if (data[i][0] != null && data[i][0] != ''){ rowArr.push(i+1); } }
23 | if(rowArr.length <1) { return 0; }else{ return Math.max.apply(null, rowArr); }
24 | }
25 |
26 | function peopleDataMapper() {
27 | var lr = s1.getLastRow();
28 | var lc = s1.getLastColumn();
29 | var lrByCol = lastRowNum_bySpecifiedCol(lc);
30 | var table = s1.getRange(1,1,lr,s1.getLastColumn()).getValues();
31 |
32 | if(arr(table[0]).some(function(el){ return el == 'Likelihood_score'}) === false){
33 |
34 | var header = [['Likelihood_score','birthdate','personal_phones','personal_emails','work_phones','work_emails','other_phones','other_emails']];
35 | s1.getRange(1,(lc+1),1,header[0].length).setValues(header);
36 |
37 | } else {
38 |
39 | var t = getIndexOfPubUrl(table[1]);
40 | var stop = table.length - lrByCol < numberToQuery ? table.length : lrByCol + numberToQuery;
41 |
42 | for(var i=lrByCol; i 0){
64 | emails.forEach(function(el){
65 | if(el.type != 'personal' || el.type != 'professional'){n_email.push(el.address);}
66 | if(el.type == 'professional'){ work_email.push(el.address); }
67 | if(el.type == 'personal'){ personal_email.push(el.address); }
68 | });
69 | }
70 | if(phones.length > 0){
71 | phones.forEach(function(el){
72 | if(/personal/i.test(el.type) === false || /professional/i.test(el.type) === false){n_phone.push(el.national_number);}
73 | if(/professional/i.test(el.type)){ work_phone.push(el.national_number); }
74 | if(/personal/i.test(el.type)){ personal_phone.push(el.national_number); }
75 | });
76 | }
77 |
78 | var emailstr_person = personal_email.length > 0 ? '[' + personal_email.toString() + ']' : '[]';
79 | var phonestr_person = personal_phone.length > 0 ? '[' + personal_phone.toString() + ']' : '[]';
80 |
81 | var emailstr_work = work_email.length > 0 ? '[' + work_email.toString() + ']' : '[]';
82 | var phonestr_work = work_phone.length > 0 ? '[' + work_phone.toString() + ']' : '[]';
83 |
84 | var emailstr_n = n_email.length > 0 ? '[' + n_email.toString() + ']' : '[]';
85 | var phonestr_n = n_phone.length > 0 ? '[' + n_phone.toString() + ']' : '[]';
86 |
87 |
88 | var birthdate = dat.data.birth_date ? dat.data.birth_date : '';
89 | var likelihood = dat.likelihood ? dat.likelihood : '';
90 | var output = [[likelihood,birthdate,phonestr_person,emailstr_person,phonestr_work,emailstr_work,phonestr_n,emailstr_n]];
91 |
92 | s1.getRange((i+1),(apiLC+1),output.length, output[0].length).setValues(output);
93 | Logger.log(dat);
94 | Logger.log(output);
95 | }
96 | }
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/sheets/openLinksInSheets.gs:
--------------------------------------------------------------------------------
1 | /*
2 | https://www.youtube.com/watch?v=ZrM2agJqyn8
3 | */
4 | var your_sheet_id = 'YOUR_SHEET_ID_NG51Lm7YUMCG3coJFYx9fgU';
5 | var your_sheet_name = 'YOUR_SHEET_NAME';
6 | var ss = SpreadsheetApp.openById(your_sheet_id);
7 | var report = ss.getSheetByName(your_sheet_name);
8 |
9 | function onOpen(e) {
10 | var ui = SpreadsheetApp.getUi();
11 | ui.createMenu('Quickli Tools')
12 | .addItem('Links Viewer', 'pagePanel')
13 | .addToUi();
14 | }
15 |
16 | function pagePanel() {
17 | const link_table = getLinksInCols(0);
18 | const jdat= JSON.stringify(link_table).replace(/"/g,""");
19 | var header = `
20 |
33 |
36 | `;
37 | var page = UrlFetchApp.fetch(link_table.link_table[0].cell).toString();
38 | var html = HtmlService.createHtmlOutput(page.replace(/\n|\r/g,'').replace(//gi,'').replace(//i,''+header))
39 | .setWidth(720)
40 | .setHeight(500)
41 | SpreadsheetApp.getUi() // Or DocumentApp or SlidesApp or FormApp.
42 | .showModalDialog(html,link_table.link_table[0].cell.slice(0,52));
43 | }
44 | function pagerPanel(data,page){
45 | const link_table = getLinksInCols(data.current_index+page);
46 | const jdat= JSON.stringify(link_table).replace(/"/g,""");
47 | var header = `
48 |
64 |
67 | `;
68 | var page = UrlFetchApp.fetch(link_table.link_table[data.current_index+1].cell).toString();
69 | var html = HtmlService.createHtmlOutput(page.replace(/\n|\r/g,'').replace(//gi,'').replace(//i,''+header))
70 | .setWidth(720)
71 | .setHeight(500)
72 | SpreadsheetApp.getUi()
73 | .showModalDialog(html,link_table.link_table[data.current_index+1].cell.slice(0,52));
74 | }
75 |
76 |
77 | function getLinksInCols(cindex) {
78 | var header = Array.from(report.getRange(1, 1, 1, report.getLastColumn()).getValues())[0];
79 | var rows = report.getLastRow();
80 | var cols_with_links = header.map((i,n,r) => { //i == the current value on the iteration. n == the index, r == the full array/header
81 | return Array.from(report.getRange(1,(n+1),rows,1).getRichTextValues()).map((row,ix,rr)=> {return {col_head:i,row_index:ix,col_index:n,cell:row[0].getLinkUrl()}});
82 | }).flat().filter(r=> r.cell)
83 | return {current_index: cindex, link_table: cols_with_links};
84 | }
85 |
--------------------------------------------------------------------------------