-
Notifications
You must be signed in to change notification settings - Fork 0
/
Code.gs
1490 lines (1350 loc) · 66.9 KB
/
Code.gs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/**
* @OnlyCurrentDoc
*/
const VERSION = 51;
const ADDONSTATE = {
PRODUCTION: "production",
DEVELOPMENT: "development"
};
//const CURRENTSTATE = ADDONSTATE.DEVELOPMENT; //make sure to switch to PRODUCTION before publishing!
const CURRENTSTATE = ADDONSTATE.PRODUCTION;
const REQUESTPARAMS = {"rettype":"json","appid":"googledocs"};
const ENDPOINTURL = "https://query.bibleget.io/v3/";
const ENDPOINTURLMETADATA = "https://query.bibleget.io/v3/metadata.php";
const ENDPOINTURLSEARCH = "https://query.bibleget.io/v3/search.php";
const SETTINGSWINDOW = { HEIGHT: 580, WIDTH: 900 };
let AUTHMODE = null;
//DEFINE CONSTANTS FOR USER PREFERENCES
//these will be used throughout scripts
//and in the html interfaces and client javascript
//to ensure consistency
const BGET = {
ALIGN: {
LEFT: "left", //make sure these are text values
RIGHT: "right", //that correspond to actual CSS properties for the for text-align rule
CENTER: "center", //
JUSTIFY: "justify" //they will be used as is in the stylesheet definitions
},
VALIGN: {
SUPERSCRIPT: 1,
SUBSCRIPT: 2,
NORMAL: 3
},
WRAP: {
NONE: 1,
PARENTHESES: 2,
BRACKETS: 3
},
POS: {
TOP: 1,
BOTTOM: 2,
BOTTOMINLINE:3
},
FORMAT: {
USERLANG: 1, // if Google Docs is used in chinese, the names of the books of the bible will be given in chinese
BIBLELANG: 2, // if Google Docs is used in chinese, the abbreviated names of the books of the bible in chinese will be given
USERLANGABBREV: 3, // if you are quoting from a Latin Bible, the names of the books of the bible will be given in latin
BIBLELANGABBREV: 4 // if you are quoting from a Latin Bible, the abbreviated names of the books of the bible in latin will be given
},
VISIBILITY: {
SHOW: 1,
HIDE: 2
},
TEXTSTYLE: {
BOLD: 1,
ITALIC: 2,
UNDERLINE: 3,
STRIKETHROUGH: 4
},
PTYPE: { //PARAGRAPH TYPE : useful when creating a new paragraph,
BIBLEVERSION: 1, // to let the paragraph creation function newPar know what kind of paragraph we're dealing with
BOOKCHAPTER: 2, // and consequently choose which styles to set from user preferences
VERSENUMBER: 3,
VERSETEXT: 4
},
PREFERORIGIN: { //PREFER ORIGIN : most catholic editions of the Bible have texts based both on the Greek and on the Hebrew versions of the Bible
HEBREW: 1, // resulting in duplicate verses, which can be both be quoted with the same reference (e.g. Esther 1,1-10)
GREEK: 2 // Even though some Catholic editions place the Greek version in separate "books", for simplicity the BibleGet project places them as quotable verses following the CEI2008 layout. This preference allows the user to define which origin to prefer (if available) when making the quote
},
TYPECASTING: { //just for quality assurance and good measure, let's explicitly define typecasting of our UserProperties, don't just rely on JSON.parse
//this way we know that floats will be floats and ints will be ints and we don't have to worry about it every time in our code when we use the values
BOOLEANVALS : ["NOVERSIONFORMATTING","BOLD","ITALIC","UNDERLINE","STRIKETHROUGH","BOOKCHAPTERFULLQUERY","INTERFACEINCM"],
FLOATVALS : ["LINEHEIGHT","LEFTINDENT","RIGHTINDENT"],
INTVALS : ["FONT_SIZE","VALIGN","SHOWBIBLEVERSION","BIBLEVERSIONPOSITION","BIBLEVERSIONWRAP","BOOKCHAPTERPOSITION","BOOKCHAPTERWRAP","BOOKCHAPTERFORMAT","SHOWVERSENUMBERS","PREFERORIGIN"],
STRINGVALS : ["FONT_FAMILY","PARAGRAPHALIGN","FOREGROUND_COLOR","BACKGROUND_COLOR","BIBLEVERSIONALIGNMENT","BOOKCHAPTERALIGNMENT"],
STRINGARRAYS: ["RecentSelectedVersions"]
}
};
const DefaultUserProperties = {
//Will be handled first thing on TAB 1 of the Settings UI
ParagraphStyles: {
LINEHEIGHT: 1.5, //FLOAT
LEFTINDENT: 0, //FLOAT
RIGHTINDENT: 0, //FLOAT
FONT_FAMILY: "Times New Roman", //STRING
PARAGRAPHALIGN: BGET.ALIGN.JUSTIFY, //STRING possible vals 'left','center','right', 'justify' (use ENUM, e.g. BGET.ALIGN.LEFT)
NOVERSIONFORMATTING: false, //BOOLEAN
INTERFACEINCM: false
},
//Will be handled right under ParagraphStyles on TAB 1 of the Settings UI
BookChapterStyles: {
BOLD: true, //BOOLEAN
ITALIC: false, //BOOLEAN
UNDERLINE: false, //BOOLEAN
STRIKETHROUGH: false, //BOOLEAN
FOREGROUND_COLOR: "#000044", //STRING
BACKGROUND_COLOR: "#FFFFFF", //STRING
FONT_SIZE: 10, //INT
VALIGN: BGET.VALIGN.NORMAL //const will resolve to INT
},
//Will be handled right under BookChapterStyles on TAB 1 of the Settings UI
VerseNumberStyles: {
BOLD: true, //BOOLEAN
ITALIC: false, //BOOLEAN
UNDERLINE: false, //BOOLEAN
STRIKETHROUGH: false, //BOOLEAN
FOREGROUND_COLOR: "#AA0000", //STRING
BACKGROUND_COLOR: "#FFFFFF", //STRING
FONT_SIZE: 10, //INT
VALIGN: BGET.VALIGN.SUPERSCRIPT //const will resolve to INT
},
//Will be handled right under VerseNumberStyles on TAB 1 of the Settings UI
VerseTextStyles: {
BOLD: false, //BOOLEAN
ITALIC: false, //BOOLEAN
UNDERLINE: false, //BOOLEAN
STRIKETHROUGH: false, //BOOLEAN
FOREGROUND_COLOR: "#666666", //STRING
BACKGROUND_COLOR: "#FFFFFF", //STRING
FONT_SIZE: 10, //INT
VALIGN: BGET.VALIGN.NORMAL //const will resolve to INT
},
//Will be handled right on TAB 2 of the Settings UI
LayoutPrefs: {
SHOWBIBLEVERSION: BGET.VISIBILITY.SHOW, //const will resolve to INT (use ENUM, e.g. BGET.VISIBILITY.SHOW)
BIBLEVERSIONALIGNMENT: BGET.ALIGN.LEFT, //const will resolve to STRING (use ENUM, e.g. BGET.ALIGN.LEFT)
BIBLEVERSIONPOSITION: BGET.POS.TOP, //const will resolve to INT (use ENUM, e.g. BGET.POS.TOP. Can only be TOP or BOTTOM)
BIBLEVERSIONWRAP: BGET.WRAP.NONE, //const will resolve to INT (use ENUM, e.g. BGET.WRAP.NONE)
BOOKCHAPTERALIGNMENT: BGET.ALIGN.LEFT, //const will resolve to STRING (use ENUM, e.g. BGET.ALIGN.LEFT)
BOOKCHAPTERPOSITION: BGET.POS.TOP, //const will resolve to INT (use ENUM, e.g. BGET.POS.BOTTOMINLINE. Can be TOP, BOTTOM, or BOTTOMINLINE)
BOOKCHAPTERWRAP: BGET.WRAP.NONE, //const will resolve to INT (use ENUM, e.g. BGET.WRAP.NONE)
BOOKCHAPTERFORMAT: BGET.FORMAT.BIBLELANG,//const will resolve to INT (use ENUM, e.g. BGET.FORMAT.BIBLELANG
BOOKCHAPTERFULLQUERY: false, //false: just the name of the book and the chapter will be shown (i.e. 1 John 4)
//true: the full reference including the verses will be shown (i.e. 1 John 4:7-8)
SHOWVERSENUMBERS: BGET.VISIBILITY.SHOW, //const will resolve to INT (use ENUM, e.g. BGET.VISIBILITY.SHOW)
PREFERORIGIN: BGET.PREFERORIGIN.HEBREW //const will resolve to INT (use ENUM, e.g. BGET.PREFERORIGIN.HEBREW)
},
//Will be handled from the Sidebar UI when sending queries
RecentSelectedVersions: [] //Array of STRING values
};
function onInstall(e){
// Add plugin menu to the toolbar (according to AuthMode)
onOpen(e);
}
function onOpen(e) {
let locale = 'en';
try{ locale = getUserLocale(); }
catch(e){ alertMe("Error: " + e.message + "\r\nFile: " + e.fileName + "\r\nLine: " + e.lineNumber); }
if(e){
AUTHMODE = e.authMode;
}
if(e && (e.authMode == ScriptApp.AuthMode.LIMITED || e.authMode == ScriptApp.AuthMode.FULL)){
// Initialize user preferences ONLY after user has granted permission to the Properties Service!
// Check if preferences have been set, if not set defaults (check will be done one by one against default prefs)
if(CURRENTSTATE == ADDONSTATE.DEVELOPMENT){
consoleLog('about to run setDefaultProperties from onOpen');
}
//Don't add the Settings / Preferences menu item if we don't have access to the PropertiesService
if(setDefaultUserProperties() === false){
//The BibleGet add-on cannot access PropertiesService yet, but it can from the menu items. Just be sure to check and set default preferences from the clicked menu items...
DocumentApp.getUi().createAddonMenu()
.addItem(__('Start',locale), 'openDeferredSidebar')
.addSeparator()
.addItem(__('Instructions',locale), 'openHelpSidebar')
.addItem(__('Settings',locale), 'openDeferredSettings')
.addItem(__('Send Feedback',locale), 'openSendFeedback')
.addItem(__('Contribute',locale), 'openContributionModal')
.addToUi();
}
else{ //if we can access the PropertiesService, create UI's that take for granted that default user properties have been set
DocumentApp.getUi().createAddonMenu()
.addItem(__('Start',locale), 'openMainSidebar')
.addSeparator()
.addItem(__('Instructions',locale), 'openHelpSidebar')
.addItem(__('Settings',locale), 'openSettings')
.addItem(__('Send Feedback',locale), 'openSendFeedback')
.addItem(__('Contribute',locale), 'openContributionModal')
.addToUi();
}
}
else { //if (e && (e.authMode == ScriptApp.AuthMode.NONE))
//User has not yet granted permissions, we don't yet have access to PropertiesService
//But we should still be able to create menu items, and clicked menu items should be able to create UI and access PropertiesService
//So just check for PropertiesService in the UI file that is created from HtmlService and make sure to check and set defaults from there
try{
DocumentApp.getUi().createAddonMenu()
.addItem(__('Start',locale), 'openDeferredSidebar')
.addSeparator()
.addItem(__('Instructions',locale), 'openHelpSidebar')
.addItem(__('Settings',locale), 'openDeferredSettings')
.addItem(__('Send Feedback',locale), 'openSendFeedback')
.addItem(__('Contribute',locale), 'openContributionModal')
.addToUi();
}
catch(e){ alertMe("Error: " + e.message + "\r\nFile: " + e.fileName + "\r\nLine: " + e.lineNumber); }
}
}
/***************************************************/
/* UI CREATION FUNCTIONS (SIDEBARS, MODALS) */
/***************************************************/
function openSettings(){
let locale = "en";
try{ locale = getUserLocale(); }
catch(e){ alertMe("Error: " + e.message + "\r\nFile: " + e.fileName + "\r\nLine: " + e.lineNumber); }
let html = HtmlService.createTemplateFromFile('Settings');
//docLog(html.getCode());
html.activetab = 0;
let evaluated = html.evaluate()
.setWidth(SETTINGSWINDOW.WIDTH)
.setHeight(SETTINGSWINDOW.HEIGHT)
.setSandboxMode(HtmlService.SandboxMode.IFRAME);
DocumentApp.getUi().showModalDialog(evaluated, __('Settings',locale));
}
function openDeferredSettings(){
if(setDefaultUserProperties() === false){
alertMe('The BibleGet add-on cannot access the PropertiesService. This means that only it cannot store your user preferences, it cannot even handle Bible quotes because it cannot store any information about available Bible versions and their indexes.',locale);
}
else{
let locale = "en";
try{ locale = getUserLocale(); }
catch(e){ alertMe("Error: " + e.message + "\r\nFile: " + e.fileName + "\r\nLine: " + e.lineNumber); }
let html = HtmlService.createTemplateFromFile('Settings');
//docLog(html.getCode());
html.activetab = 0;
let evaluated = html.evaluate()
.setWidth(SETTINGSWINDOW.WIDTH)
.setHeight(SETTINGSWINDOW.HEIGHT)
.setSandboxMode(HtmlService.SandboxMode.IFRAME);
DocumentApp.getUi().showModalDialog(evaluated, __('Settings',locale));
}
}
/**
* Send email to plugin creator with feedback
* (email body from custom dialog prompt)
*/
function openSendFeedback(){
let locale = "en";
try{ locale = getUserLocale(); }
catch(e){ alertMe("Error: " + e.message + "\r\nFile: " + e.fileName + "\r\nLine: " + e.lineNumber); }
let html = HtmlService.createTemplateFromFile('Feedback')
.evaluate()
.setWidth(400)
.setHeight(300)
.setSandboxMode(HtmlService.SandboxMode.IFRAME);
DocumentApp.getUi() // Or DocumentApp or FormApp.
.showModalDialog(html, __('Send Feedback',locale));
}
function openContributionModal(){
let locale = "en";
try{ locale = getUserLocale(); }
catch(e){ alertMe("Error: " + e.message + "\r\nFile: " + e.fileName + "\r\nLine: " + e.lineNumber); }
let html = HtmlService.createTemplateFromFile('Contribute')
.evaluate()
.setWidth(400)
.setHeight(300)
.setSandboxMode(HtmlService.SandboxMode.IFRAME);
DocumentApp.getUi() // Or DocumentApp or FormApp.
.showModalDialog(html,__('Support BibleGet I/O',locale));
}
function openMainSidebar(){
let html = HtmlService.createTemplateFromFile('Sidebar');
DocumentApp.getUi().showSidebar(html.evaluate().setSandboxMode(HtmlService.SandboxMode.IFRAME).setTitle('BibleGet I/O'));
}
function openDeferredSidebar(){
if(setDefaultUserProperties() === false){
alertMe('The BibleGet add-on cannot access the PropertiesService. This means that only it cannot store your user preferences, it cannot even handle Bible quotes because it cannot store any information about available Bible versions and their indexes.',locale);
}
else{
let html = HtmlService.createTemplateFromFile('Sidebar');
DocumentApp.getUi().showSidebar(html.evaluate().setSandboxMode(HtmlService.SandboxMode.IFRAME).setTitle('BibleGet I/O'));
}
}
function openHelpSidebar(){
let locale = "en";
try{ locale = getUserLocale(); }
catch(e){ alertMe("Error: " + e.message + "\r\nFile: " + e.fileName + "\r\nLine: " + e.lineNumber); }
let html = HtmlService.createTemplateFromFile('Help');
//docLog(html.evaluate().getContent());
DocumentApp.getUi().showSidebar(html.evaluate().setSandboxMode(HtmlService.SandboxMode.IFRAME).setTitle('BibleGet I/O - '+__('Instructions',locale)));
}
function openSearchResults($searchresults){
let locale = "en";
try{ locale = getUserLocale(); }
catch(e){ alertMe("Error: " + e.message + "\r\nFile: " + e.fileName + "\r\nLine: " + e.lineNumber); }
let html = HtmlService.createTemplateFromFile('SearchResults');
html.searchresults = JSON.stringify($searchresults);
//consoleLog("stringified search results:");
//consoleLog(JSON.stringify($searchresults));
let evaluated = html.evaluate()
.setWidth(SETTINGSWINDOW.WIDTH+200)
.setHeight(SETTINGSWINDOW.HEIGHT)
.setSandboxMode(HtmlService.SandboxMode.IFRAME);
DocumentApp.getUi().showModalDialog(evaluated, __('Search Results',locale));
}
/********************************************************************/
/* FUNCTIONS THAT DEAL WITH USER PREFERENCES AND PROPERTIES SERVICE */
/********************************************************************/
/**
* FUNCTION setDefaultUserProperties
* checks current user properties against default user properties
* if a current property is missing compared to the default properties, the default will be defined
* otherwise current will be preserved
*/
function setDefaultUserProperties(){
if(CURRENTSTATE == ADDONSTATE.DEVELOPMENT){
consoleLog('running function setDefaultUserProperties');
}
let propsService = PropertiesService.getUserProperties(),
usrProperties = null;
if(propsService !== null){
let usrProperties = propsService.getProperties();
//Check if there are old properties that we no longer need, if so clean up to avoid trouble
if(usrProperties.hasOwnProperty('RientroSinistro')
|| usrProperties.hasOwnProperty('BookChapterAlignment')
|| usrProperties.hasOwnProperty('VerseNumberAlignment')
|| usrProperties.hasOwnProperty('VerseTextAlignment')
|| usrProperties.hasOwnProperty('Interlinea')
|| usrProperties.hasOwnProperty('ShowVerseNumbers')
|| usrProperties.hasOwnProperty('NoVersionFormatting')
|| usrProperties.hasOwnProperty('Lineheight')
|| usrProperties.hasOwnProperty('LeftIndent')
|| usrProperties.hasOwnProperty('RightIndent')
|| usrProperties.hasOwnProperty('ShowBibleVersion')
|| usrProperties.hasOwnProperty('BibleVersionPosition')
|| usrProperties.hasOwnProperty('BibleVersionWrap')
|| usrProperties.hasOwnProperty('BookChapterPosition')
|| usrProperties.hasOwnProperty('BookChapterWrap')
|| usrProperties.hasOwnProperty('BookChapterFormat')
|| usrProperties.hasOwnProperty('ParagraphAlign')
|| usrProperties.hasOwnProperty('BibleVersionAlignment')
|| usrProperties.hasOwnProperty('BookChapterAlignment')
|| usrProperties.hasOwnProperty('InterfaceInCM')
|| usrProperties.hasOwnProperty('BookChapterFullQuery')
){
propsService.deleteAllProperties();
}
let checkedUserProperties = {};
for(let [key,value] of Object.entries(DefaultUserProperties)){
if(!usrProperties.hasOwnProperty(key)){
if(CURRENTSTATE == ADDONSTATE.DEVELOPMENT){
consoleLog(key+' property not set, now getting from DefaultUserProperties');
}
checkedUserProperties[key] = JSON.stringify(value);
}
//if we do already have a value set in the user properties service
else if(key=="RecentSelectedVersions"){ //we will handle RecentSelectedVersions separately because it is an array, not an object ?
if(CURRENTSTATE == ADDONSTATE.DEVELOPMENT){
consoleLog(key+' property will be set based on current usrProperties else DefaultUserProperties');
}
try{
checkedUserProperties[key] = (JSON.parse(usrProperties[key]) != DefaultUserProperties[key] ? usrProperties[key] : JSON.stringify(DefaultUserProperties[key]));
}catch(e){
alertMe('error while checking against RecentSelectedVersions in User Properties Service from setDefaultUserProperties function: '+e);
return false;
}
}
else{
if(CURRENTSTATE == ADDONSTATE.DEVELOPMENT){
consoleLog(key+' property will be JSON parsed and the obj value will be checked key by key');
}
let decodedUserProperties;
if(typeof usrProperties[key] === 'string' && usrProperties[key].includes("{") && usrProperties[key].includes("}")){
try{
decodedUserProperties = JSON.parse(usrProperties[key]);
}catch(e){
alertMe('error while checking against property '+key+' in User Properties Service from setDefaultUserProperties function: '+e);
return false;
}
let propsObj = {};
for(let [key1,value1] of Object.entries(DefaultUserProperties[key])){
if(!decodedUserProperties.hasOwnProperty(key1) || decodedUserProperties[key1] === null || decodedUserProperties[key1] == ""){ propsObj[key1] = value1; }
else{ propsObj[key1] = decodedUserProperties[key1]; }
}
checkedUserProperties[key] = JSON.stringify(propsObj);
}
else{
checkedUserProperties[key] = JSON.stringify(DefaultUserProperties[key]);
}
}
}
propsService.setProperties(checkedUserProperties);
if(CURRENTSTATE == ADDONSTATE.DEVELOPMENT){
consoleLog('userProperties have now been set with the newly populated object between saved user preferences and default user preferences');
}
return true;
}
else{
return false;
}
/*
if(VERSION>20){
userProperties.deleteAllProperties();
}
*/
}
/*
* FUNCTION getDefaultUserProperties
* returns a JSON obj in which all values are JSON stringified
* unless "true" is passed in, in which case a pure JSON obj will be returned
*/
function getDefaultUserProperties(nostringify=false){
let defltUserProperties = {};
for(let [key, value] of Object.entries(DefaultUserProperties)){
defltUserProperties[key] = (nostringify ? value : JSON.stringify(value) );
}
return defltUserProperties;
}
/*
* FUNCTION getUserProperties
* returns a JSON obj in which all values are JSON stringified
* unless "true" is passed in, in which case a pure JSON obj will be returned
* and all values will be returned as true booleans, ints, floats...
*/
function getUserProperties(nostringify=false){
if(CURRENTSTATE == ADDONSTATE.DEVELOPMENT){ consoleLog('function getUserProperties starting with nostringify = ' + nostringify.toString()); }
let propsService = PropertiesService.getUserProperties();
if(propsService !== null){
let userProperties = propsService.getProperties();
let currentProperties = {};
for(let [key, value] of Object.entries(userProperties)){
if(CURRENTSTATE === ADDONSTATE.DEVELOPMENT && nostringify===true){ consoleLog('function getUserProperties will parse = ' + key + ' from saved user properties, with value ['+ (typeof value) +']: ' +value); }
if(nostringify){
try{
currentProperties[key] = JSON.parse(value);
}
catch(e){
alertMe('getUserProperties function: error while parsing key = '+key+' from User Properties Service, with string value = '+value+'. '+e);
sendMail('DEBUG INFO: User Properties from Properties Service = '+JSON.stringify(userProperties));
return false;
}
}
else{
currentProperties[key] = value;
}
//for quality insurance and for good measure
//let's just double check that JSON.parse is actually giving us the right typecasting
//and enforce it if not
if(nostringify){
for(let [key1, value1] of Object.entries(currentProperties[key])){
if(BGET.TYPECASTING.FLOATVALS.includes(key1) && typeof currentProperties[key][key1] !== 'float') { currentProperties[key][key1] = parseFloat(value1); }
else if(BGET.TYPECASTING.INTVALS.includes(key1) && typeof currentProperties[key][key1] !== 'int') { currentProperties[key][key1] = parseInt(value1); }
else if(BGET.TYPECASTING.BOOLEANVALS.includes(key1) && typeof currentProperties[key][key1] !== 'boolean'){ try{ currentProperties[key][key1] = JSON.parse(value1); }catch(e){
alertMe('getUserProperties function: error while enforcing boolean type for key = '+key+' & key1 = '+key1+'. '+e);
return false;
} }
else if(BGET.TYPECASTING.STRINGVALS.includes(key1) && typeof currentProperties[key][key1] !== 'string') { currentProperties[key][key1] = value1.toString(); }
else if(BGET.TYPECASTING.STRINGARRAYS.includes(key1) && typeof currentProperties[key][key1] !== 'object') { try{ currentProperties[key][key1] = JSON.parse(value1); }catch(e){
alertMe('getUserProperties function: error while enforcing boolean type for key = '+ket+' & key1 = '+key1+'. '+e);
return false;
} }
}
}
if(CURRENTSTATE === ADDONSTATE.DEVELOPMENT && nostringify===true){ consoleLog(key + ' parsed, result : '+currentProperties[key]); }
}
return currentProperties;
}
else{
return false;
}
}
/**
* Function setUserProperties
*
* Set User Properties based on a JSON obj
* stringify each one of the values that is an obj
*/
function setUserProperties(jsonobj){
//consoleLog("setting preferences in properties service");
let newProperties = {};
for (let [key, value] of Object.entries(jsonobj)) {
newProperties[key] = JSON.stringify(value);
}
let userProperties = PropertiesService.getUserProperties();
userProperties.setProperties(newProperties);
return newProperties;
}
/**
* Function setUserProperty
* (I believe this function is never actually used, we just set all the properties each time there is a change)
* Set User Properties based on a stringified JSON obj
* stringify each one of the values that is an obj
*/
function setUserProperty(propKey,propVal){
let userProperties = PropertiesService.getUserProperties();
if(typeof propVal === 'object'){
userProperties.setProperty(propKey, JSON.stringify(propVal));
}
else { userProperties.setProperty(propKey, propVal); }
}
/**
* Function getUserProperty
* (I believe this function is never actually used, we just get all the properties at once as one big object)
* Get User Property choosing whether to return a string or an object
*/
function getUserProperty(propKey,nostringify=false){
let userProperties = PropertiesService.getUserProperties();
let userProp = (nostringify ? JSON.parse(userProperties.getProperty(propKey)) : userProperties.getProperty(propKey));
//consoleLog(userProp);
return userProp;
}
function resetUserProperties(nostringify=false){
let locale = "en";
try{ locale = getUserLocale(); }
catch(e){ alertMe("Error: " + e.message + "\r\nFile: " + e.fileName + "\r\nLine: " + e.lineNumber); }
let userProperties = PropertiesService.getUserProperties();
userProperties.deleteAllProperties();
//when we set the properties in the properties service, they must be stringified
//so here we don't pass in any nostringify boolean, it must be false (or empty)
//nostringify=false = stringify=true, values that are obj's will be stringified
userProperties.setProperties(getDefaultUserProperties());
let html2 = HtmlService.createTemplateFromFile('Settings');
html2.activetab = 2;
let evaluated = html2.evaluate()
.setWidth(SETTINGSWINDOW.WIDTH)
.setHeight(SETTINGSWINDOW.HEIGHT)
.setSandboxMode(HtmlService.SandboxMode.IFRAME);
DocumentApp.getUi().showModalDialog(evaluated, __('Impostazioni',locale));
}
/***********************************************/
/* FUNCTIONS THAT RUN FROM THE UI SCRIPTS */
/***********************************************/
//Function alertMe @ shortcut for DocumentApp.getUi().alert() to show error messages to the end user
function alertMe(str){
DocumentApp.getUi().alert(str);
}
//Function sendmail @ used in feedback UI to actually send the email
function sendMail(txt) {
MailApp.sendEmail({
to: "bibleget.io@gmail.com",
subject: "Google Apps Script Feedback",
htmlBody: txt
});
}
//Function fetchData @ used in SidebarJS and in other scripts here in Code.gs (which are called from SidebarJS), to communicate with the BibleGet endpoints
function fetchData(request){
let {query,version,preferorigin} = request;
let {rettype,appid} = REQUESTPARAMS;
let payload = {'query':query,'version':version,'return':rettype,'appid':appid,'pluginversion':VERSION,'preferorigin':preferorigin};
try{
var response = UrlFetchApp.fetch(ENDPOINTURL,{'method':'post','payload':payload});
var responsecode = response.getResponseCode();
if(responsecode==200){
//consoleLog("Response code was 200.");
let content = response.getContentText();
//consoleLog("Contents:");
//consoleLog(content);
return content;
}
else{
alertMe('BIBLEGET SERVER ERROR (response '+responsecode+'). Please wait a few minutes and try again.');
}
}catch(e){
alertMe('ERROR in communication with BibleGet Server. Please wait a few minutes and try again. ('+e.message+')');
return false;
}
}
function fetchSearchResults(request){
let {query,version,keyword,exactmatch} = request;
let {rettype,appid} = REQUESTPARAMS;
let payload = {'query':query,'version':version,'exactmatch':exactmatch,'return':rettype,'appid':appid,'pluginversion':VERSION,'keyword':keyword};
let locale = "en";
try{ locale = getUserLocale(); }
catch(e){ alertMe("Error: " + e.message + "\r\nFile: " + e.fileName + "\r\nLine: " + e.lineNumber); }
//consoleLog(payload);
//consoleLog(ENDPOINTURLSEARCH);
try{
var response = UrlFetchApp.fetch(ENDPOINTURLSEARCH,{'method':'post','payload':payload});
var responsecode = response.getResponseCode();
if(responsecode==200){
//consoleLog("Response code was 200.");
let content = response.getContentText("UTF-8"),
contentObj;
try{
contentObj = JSON.parse(content);
if(contentObj.hasOwnProperty('results') && contentObj.results.length === 0){
alertMe(__('There were no results for the keyword {k} in the version {v}',locale).formatUnicorn({k:contentObj.info.keyword,v:contentObj.info.version}) );
}
else{
openSearchResults(content);
}
}
catch(e){ alertMe("Error: " + e.message + "\r\nFile: " + e.fileName + "\r\nLine: " + e.lineNumber); }
//consoleLog("Contents:");
//consoleLog(content);
}
else{
alertMe('BIBLEGET SERVER ERROR (response '+responsecode+'). Please wait a few minutes and try again.');
}
}catch(e){
alertMe('ERROR in communication with BibleGet Server. Please wait a few minutes and try again. ('+e.message+')');
return false;
}
}
//Function getUserLocale @ used in pretty much every UI in order to determine the Docs interface locale
function getUserLocale(){
let locale = Session.getActiveUserLocale();
if(locale.length > 2){
locale = locale.slice(0,2);
}
//Logger.log(locale);
return locale; //if user locale is country specific (i.e. "pt_pt") return only the general language portion, two letter ISO code without country specification
}
//TODO: I think I need help from a Google representative. I cannot for the life of me get Portugal Portuguese to work.
//It is returning "pt_PT" as locale, rather than a two letter ISO code. If I try slice or substring or substr the add-on doesn't even load for some reason!
//I get this error: "Google Apps Script: Exception: Argumento inválido: caption"
//If I don't slice or substring, I get the add-on menu loaded but it's in English (my translatables don't recognize "pt_PT") and if I try to "Start"
//I get another blocking error: "pt_PT" is invalid language code or something like that. This can't be on my end because I handle invalid language codes.
//It must be an error on Google's end.
function makeUnique(str) {
return String.prototype.concat(...new Set(str));
}
/**********************************************************
* FUNCTIONS THAT PROCESS THE BIBLE QUOTES IN JSON FORMAT *
* AFTER THEY ARE RETRIEVED FROM THE BIBLEGET ENDPOINT *
* AND PROCESS THE INJECTION INTO THE DOCUMENT *
* BASED ON USER PREFERENCE FOR ELEMENT STYLING *
**********************************************************/
function preparePropertiesForDocInjection(){
let userProperties = getUserProperties(true); //will get a pure JSON obj, all stringified values parsed and checked for typecasting
//Let's make sure we have the right typecasting for each value
for(let [key,value] of Object.entries(userProperties)){
for(let [key1,value1] of Object.entries(userProperties[key]) ){
if(key1 == 'LEFTINDENT' || key1 == 'RIGHTINDENT'){
//setIndentStart and setIndentEnd etc. take values in points
//while the ruler at the top of the document is in inches
//so we store our values in inches, then convert to points
userProperties[key][key1] = userProperties.ParagraphStyles.INTERFACEINCM ? centimetersToPoints(value1) : inchesToPoints(value1);
}
else if(key1 == 'VALIGN'){
switch(value1){
case BGET.VALIGN.NORMAL:
userProperties[key][key1] = DocumentApp.TextAlignment.NORMAL;
break;
case BGET.VALIGN.SUPERSCRIPT:
userProperties[key][key1] = DocumentApp.TextAlignment.SUPERSCRIPT;
break;
case BGET.VALIGN.SUBSCRIPT:
userProperties[key][key1] = DocumentApp.TextAlignment.SUBSCRIPT;
break;
}
}
else if(key1 == 'PARAGRAPHALIGN' || key1 =='BIBLEVERSIONALIGNMENT' || key1 == 'BOOKCHAPTERALIGNMENT'){
switch(value1){
case BGET.ALIGN.LEFT:
userProperties[key][key1] = DocumentApp.HorizontalAlignment.LEFT;
break;
case BGET.ALIGN.CENTER:
userProperties[key][key1] = DocumentApp.HorizontalAlignment.CENTER;
break;
case BGET.ALIGN.RIGHT:
userProperties[key][key1] = DocumentApp.HorizontalAlignment.RIGHT;
break;
case BGET.ALIGN.JUSTIFY:
userProperties[key][key1] = DocumentApp.HorizontalAlignment.JUSTIFY;
break;
default:
userProperties[key][key1] = DocumentApp.HorizontalAlignment.JUSTIFY;
}
}
}
}
return userProperties;
}
function docInsert(json){
//docLog("retrieved json object from server, now inserting into document... "+JSON.stringify(json));
var verses = json.results,
//docLog("retrieved json object from server, now inserting into document... "+JSON.stringify(verses));
biblequote = "",
newelement = {"thisversion":"","newversion":false,"thisbook":"","newbook":false,"thischapter":0,"newchapter":false,"thisverse":0,"newverse":false},
//returns an already parsed json object, whether from user preferences set in properties service, or from default values
BibleGetProperties = preparePropertiesForDocInjection(),
BibleGetGlobal = {"iterateNewPar":false,"currentPar":null};
BibleGetGlobal.body = DocumentApp.getActiveDocument().getBody();
BibleGetGlobal.idx = getCursorIndex();
BibleGetGlobal.locale = "en";
try{ BibleGetGlobal.locale = getUserLocale(); }
catch(e){ alertMe("Error: " + e.message + "\r\nFile: " + e.fileName + "\r\nLine: " + e.lineNumber); }
BibleGetGlobal.firstFmtVerse = false;
BibleGetGlobal.stack = { bibleversion: [], bookchapter: [] };
//docLog("got results from server, now preparing to elaborate... BibleGetGlobal object = "+JSON.stringify(BibleGetGlobal));
if(BibleGetProperties.LayoutPrefs.BOOKCHAPTERFORMAT === BGET.FORMAT.USERLANG || BibleGetProperties.LayoutPrefs.BOOKCHAPTERFORMAT === BGET.FORMAT.USERLANGABBREV){
let locale = BibleGetGlobal.locale;
BibleGetGlobal.l10n = getLocalizedBookNames(locale);
//the preceding statement will return the names and abbreviations of the books of the Bible localized in the specified locale
//which will be accessible in the properties BibleGetGlobal.l10n.biblebooks
}
var versenum,
newPar;
let bookChapterPar, versionPar;
for(var i=0;i<verses.length;i++){
verses[i].verse = parseInt(verses[i].verse);
verses[i].chapter = parseInt(verses[i].chapter);
//verses[i].partialverse_isdescr = parseInt(verses.partialverse_isdescr);
//docLog("initial value of newelement >> "+JSON.stringify(newelement));
//docLog("value of verses["+i+"] >> "+JSON.stringify(verses[i]));
newelement = checkNewElements(verses[i],newelement);
//docLog("checking new elements... >> "+JSON.stringify(newelement));
if(newelement.newversion){
switch(BibleGetProperties.LayoutPrefs.BIBLEVERSIONWRAP){
case BGET.WRAP.NONE:
break;
case BGET.WRAP.PARENTHESES:
verses[i].version = "("+verses[i].version+")";
break;
case BGET.WRAP.BRACKETS:
verses[i].version = "["+verses[i].version+"]";
break;
}
if(BibleGetProperties.LayoutPrefs.SHOWBIBLEVERSION === BGET.VISIBILITY.SHOW){
if(BibleGetGlobal.stack.bookchapter.length > 0){
switch(BibleGetProperties.LayoutPrefs.BOOKCHAPTERPOSITION){
case BGET.POS.BOTTOM:
case BGET.POS.TOP:
if((BibleGetGlobal = createNewPar(BibleGetGlobal,BibleGetProperties) ) === false){
DocumentApp.getUi().alert(__('Cannot insert text at this document location.',BibleGetGlobal.locale));
return;
}
BibleGetGlobal.currentPar.setAlignment(BibleGetProperties.LayoutPrefs.BOOKCHAPTERALIGNMENT);
bookChapterPargr = BibleGetGlobal.currentPar.appendText(BibleGetGlobal.stack.bookchapter.shift());
break;
case BGET.POS.BOTTOMINLINE:
bookChapterPargr = BibleGetGlobal.currentPar.appendText(' '+BibleGetGlobal.stack.bookchapter.shift());
break;
}
setTextStyles(bookChapterPargr,BibleGetProperties,BGET.PTYPE.BOOKCHAPTER);
BibleGetGlobal.firstFmtVerse = false; //why are we using this again?
}
switch(BibleGetProperties.LayoutPrefs.BIBLEVERSIONPOSITION){
case BGET.POS.BOTTOM:
BibleGetGlobal.stack.bibleversion.push(verses[i].version);
if(BibleGetGlobal.stack.bibleversion.length > 1){
//if we have started accumulating more than one element at this point,
//then we print one from the top of the stack (array.shift) to the document
//(i.e. if this is the first element we encounter we don't print anything yet)
if((BibleGetGlobal = createNewPar(BibleGetGlobal,BibleGetProperties) ) === false){
DocumentApp.getUi().alert(__('Cannot insert text at this document location.',BibleGetGlobal.locale));
return;
}
BibleGetGlobal.currentPar.setAlignment(BibleGetProperties.LayoutPrefs.BIBLEVERSIONALIGNMENT);
versionPar = BibleGetGlobal.currentPar.appendText(BibleGetGlobal.stack.bibleversion.shift());
setTextStyles(versionPar,BibleGetProperties,BGET.PTYPE.BIBLEVERSION);
BibleGetGlobal.firstFmtVerse = false;
}
break;
case BGET.POS.TOP:
if((BibleGetGlobal = createNewPar(BibleGetGlobal,BibleGetProperties) ) === false){
DocumentApp.getUi().alert(__('Cannot insert text at this document location.',BibleGetGlobal.locale));
return;
}
BibleGetGlobal.currentPar.setAlignment(BibleGetProperties.LayoutPrefs.BIBLEVERSIONALIGNMENT);
versionPar = BibleGetGlobal.currentPar.appendText(verses[i].version);
setTextStyles(versionPar,BibleGetProperties,BGET.PTYPE.BIBLEVERSION);
BibleGetGlobal.firstFmtVerse = false;
}
}
}
//docLog("so far so good");
if(newelement.newbook || newelement.newchapter){
let bkChStr;
switch(BibleGetProperties.LayoutPrefs.BOOKCHAPTERFORMAT){
case BGET.FORMAT.USERLANG:
bkChStr = BibleGetGlobal.l10n.biblebooks[verses[i].booknum] + " " + verses[i].chapter;
break;
case BGET.FORMAT.USERLANGABBREV:
bkChStr = BibleGetGlobal.l10n.abbreviations[verses[i].booknum] + " " + verses[i].chapter;
break;
case BGET.FORMAT.BIBLELANG:
bkChStr = verses[i].book + " " + verses[i].chapter;
break;
case BGET.FORMAT.BIBLELANGABBREV:
bkChStr = verses[i].bookabbrev + " " + verses[i].chapter;
break;
}
if(BibleGetProperties.LayoutPrefs.BOOKCHAPTERFULLQUERY){
//retrieve the orginal query from originalquery property in the json response received
let origQuery = verses[i].originalquery; //we need to remove the book name and chapter from this query
let regexA = hackRegex(/^[1-3]{0,1}((\p{L}\p{M}*)+)[1-9][0-9]{0,2}/); //match book and chapter
let regexB = hackRegex(/^[1-9][0-9]{0,2}/); //sometimes we will only have chapter and no book
if(verses[i].originalquery.match(regexA) === null){
bkChStr += verses[i].originalquery.replace(regexB,'');
}else{
bkChStr += verses[i].originalquery.replace(regexA,'');
}
}
switch(BibleGetProperties.LayoutPrefs.BOOKCHAPTERWRAP){
case BGET.WRAP.NONE:
break;
case BGET.WRAP.PARENTHESES:
bkChStr = "("+bkChStr+")";
break;
case BGET.WRAP.BRACKETS:
bkChStr = "["+bkChStr+"]";
break;
}
switch(BibleGetProperties.LayoutPrefs.BOOKCHAPTERPOSITION){
case BGET.POS.BOTTOM:
BibleGetGlobal.stack.bookchapter.push(bkChStr);
if(BibleGetGlobal.stack.bookchapter.length > 1){
//if we have started accumulating more than one element at this point,
//then we print one from the top of the stack (array.shift) to the document
//(i.e. if this is the first element we encounter we don't print anything yet)
if((BibleGetGlobal = createNewPar(BibleGetGlobal,BibleGetProperties) ) === false){
DocumentApp.getUi().alert(__('Cannot insert text at this document location.',BibleGetGlobal.locale));
return;
}
BibleGetGlobal.currentPar.setAlignment(BibleGetProperties.LayoutPrefs.BOOKCHAPTERALIGNMENT);
bookChapterPar = BibleGetGlobal.currentPar.appendText(BibleGetGlobal.stack.bookchapter.shift());
setTextStyles(bookChapterPar,BibleGetProperties,BGET.PTYPE.BOOKCHAPTER);
BibleGetGlobal.firstFmtVerse = false;
}
break;
case BGET.POS.TOP:
if((BibleGetGlobal = createNewPar(BibleGetGlobal,BibleGetProperties) ) === false){
DocumentApp.getUi().alert(__('Cannot insert text at this document location.',BibleGetGlobal.locale));
return;
}
BibleGetGlobal.currentPar.setAlignment(BibleGetProperties.LayoutPrefs.BOOKCHAPTERALIGNMENT);
bookChapterPar = BibleGetGlobal.currentPar.appendText(bkChStr);
setTextStyles(bookChapterPar,BibleGetProperties,BGET.PTYPE.BOOKCHAPTER);
BibleGetGlobal.firstFmtVerse = false;
break;
case BGET.POS.BOTTOMINLINE:
BibleGetGlobal.stack.bookchapter.push(bkChStr);
if(BibleGetGlobal.stack.bookchapter.length > 1){
//if we have started accumulating more than one element at this point,
//then we print one from the top of the stack (array.shift) to the document
//(i.e. if this is the first element we encounter we don't print anything yet)
bookChapterPar = BibleGetGlobal.currentPar.appendText(' '+BibleGetGlobal.stack.bookchapter.shift());
setTextStyles(bookChapterPar,BibleGetProperties,BGET.PTYPE.BOOKCHAPTER);
BibleGetGlobal.firstFmtVerse = false; //TODO: double check what we're doing with this variable, is it needed only when creating paragraphs? so perhaps not here?
}
}
}
//docLog("so far so good");
if(newelement.newverse){
if(BibleGetGlobal.iterateNewPar || newelement.newchapter){
BibleGetGlobal.iterateNewPar = false;
BibleGetGlobal = createNewPar(BibleGetGlobal,BibleGetProperties);
BibleGetGlobal.currentPar.setAlignment(BibleGetProperties.ParagraphStyles.PARAGRAPHALIGN);
}
if(BibleGetProperties.LayoutPrefs.SHOWVERSENUMBERS === BGET.VISIBILITY.SHOW){
let versenum = BibleGetGlobal.currentPar.appendText(" " + verses[i].verse + " ");
setTextStyles(versenum,BibleGetProperties,BGET.PTYPE.VERSENUMBER);
}
}
//docLog("so far so good");
//var partial = element.appendText(" " + verses[i].partialverse + " ");
//partial.setBold(false).setItalic(true).setFontSize(6).setForegroundColor("#FF0000");
//before appending the verse text, we need to parse it to see if it contains special markup
if(/<[\/]{0,1}(?:sm|po|speaker|i|pr)[f|l|s|i]{0,1}[f|l]{0,1}>/.test(verses[i].text)){
//consoleLog("looks like we have special formatting that we have to deal with in this verse... {"+verses[i].text+"}");
//We replace newline or carriage return characters with a blank space in any case
verses[i].text = verses[i].text.replace(/(\r\n|\n|\r)/gm," ");
//Now replace all multiple spaces with a single space
verses[i].text = verses[i].text.replace(/ +/g, ' ');
//However we will not deal with it in a special way if the user has chosen to override the special formatting
if(BibleGetProperties.ParagraphStyles.NOVERSIONFORMATTING){
//if user preferences ask to override version formatting, we just need to remove the formatting tags from the text
//We will first remove line breaks, operating on a multiline level, so that we have a single line of text to work with
verses[i].text = verses[i].text.replace(/<[\/]{0,1}(?:po|speaker|i|pr)[f|l|s|i]{0,1}[f|l]{0,1}>/g," "); //replace tags (whether opening or closing tags) with a blank space
verses[i].text = verses[i].text.replace(/<[\/]{0,1}sm>/g,""); //replace <sm> tags whether opening or closing
//I had to do the <sm> tag separately because it was not always being removed. However I have a theory that this is because of recursiveness
//when you have nested tags. We should do a recursive check for tags within tags.
//Now we're ready to append the text to the paragraph
let versetext = BibleGetGlobal.currentPar.appendText(verses[i].text);
setTextStyles(versetext,BibleGetProperties,BGET.PTYPE.VERSETEXT);
//consoleLog("NOVERSIONFORMATTING=true, simply removing the tags. Verse text is now : <"+verses[i].text+">");
}else{
BibleGetGlobal = formatSections(verses[i].text,BibleGetProperties,newelement,BibleGetGlobal);
}
}
//If we don't have special tags, we still want to check for newline characters however
//and deal with them according to the NOVERSIONFORMATTING user option
else{
//We will still replace newline and carriage return characters even if we don't have special tags
//as long as the NOVERSIONFORMATTING override is active
if(BibleGetProperties.ParagraphStyles.NOVERSIONFORMATTING){
verses[i].text = verses[i].text.replace(/(\r\n|\n|\r)/gm," "); //We replace newline or carriage return characters with a blank space in any case
verses[i].text = verses[i].text.replace(/ +/g, ' ');
}
BibleGetGlobal.firstFmtVerse = true;
let versetext = BibleGetGlobal.currentPar.appendText(verses[i].text);
setTextStyles(versetext,BibleGetProperties,BGET.PTYPE.VERSETEXT);
}
}
//Now that we're out of our loop, check if we still have things piled up on our stacks
//If so we need to print them out, starting from BookChapter and then going to BibleVersion
switch(BibleGetProperties.LayoutPrefs.BOOKCHAPTERPOSITION){
case BGET.POS.TOP:
break;
case BGET.POS.BOTTOM:
if(BibleGetGlobal.stack.bookchapter.length > 0){
//if we still have something on the stack, then we print it to the document
if((BibleGetGlobal = createNewPar(BibleGetGlobal,BibleGetProperties) ) === false){
DocumentApp.getUi().alert(__('Cannot insert text at this document location.',BibleGetGlobal.locale));
return;
}
BibleGetGlobal.currentPar.setAlignment(BibleGetProperties.LayoutPrefs.BOOKCHAPTERALIGNMENT);
bookChapterPar = BibleGetGlobal.currentPar.appendText(BibleGetGlobal.stack.bookchapter.shift());
setTextStyles(bookChapterPar,BibleGetProperties,BGET.PTYPE.BOOKCHAPTER);
BibleGetGlobal.firstFmtVerse = false;
}
break;
case BGET.POS.BOTTOMINLINE:
if(BibleGetGlobal.stack.bookchapter.length > 0){
//if we still have something on the stack, then we print it to the document
bookChapterPar = BibleGetGlobal.currentPar.appendText(' '+BibleGetGlobal.stack.bookchapter.shift());
setTextStyles(bookChapterPar,BibleGetProperties,BGET.PTYPE.BOOKCHAPTER);
BibleGetGlobal.firstFmtVerse = false; //TODO: double check what we're doing with this variable, is it needed only when creating paragraphs? so perhaps not here?
}
}
if(BibleGetProperties.LayoutPrefs.BIBLEVERSIONPOSITION === BGET.POS.BOTTOM && BibleGetProperties.LayoutPrefs.SHOWBIBLEVERSION === BGET.VISIBILITY.SHOW){
//we should still have one element on the stack, if so print it to the document
if(BibleGetGlobal.stack.bibleversion.length > 0){
if((BibleGetGlobal = createNewPar(BibleGetGlobal,BibleGetProperties) ) === false){
DocumentApp.getUi().alert(__('Cannot insert text at this document location.',BibleGetGlobal.locale));
return;