-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathBookOrganizer.js
More file actions
200 lines (174 loc) · 6.54 KB
/
BookOrganizer.js
File metadata and controls
200 lines (174 loc) · 6.54 KB
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
//Tests whether the book name follows the "{title}_[{author}]_({year})" format with year being optional
const standard_filter = /^([^\[]+)[_]*\[([^\]]+)]/m
/**
* Callback for rendering the homepage card.
* @param {Object} e an event object
* @return {CardService.Card} The card to show to the user.
*/
function onHomepage(e) {
return createInstructionsCard();
}
/**
* Callback for rendering card when an item is selected. Depending on the selection
* - instructions card
* - book organizer card
* - book display card
* are returned.
*
* @param {Object} e an event object
* @return {CardService.Card} The card to show to the user.
*/
function onDriveItemsSelected(e) {
var item_id = e.drive.activeCursorItem.id,
item_title = e.drive.activeCursorItem.title,
mimeType = e.drive.activeCursorItem.mimeType;
var fregex = new RegExp(getSetting('special_folder_name'), 'gim');
if (mimeType.match(/folder/gim) && item_title.match(fregex)){
//we are in the special folder, show organizer card
return createBookOrganizerCard(item_id, item_title);
} else if (mimeType.match(/pdf|epub/gim)){
//epub or pdf selected, return display book card
return createDisplayBookCard(item_id, item_title);
} else {
//neither selected, return instructions card
return createInstructionsCard();
}
}
/**
* Callback for rendering a list of 10 non-conformant books
* the card can be accessed from the instructions card.
*
* @param {Object} e an event object
* @return {CardService.Card} The card to show to the user.
*/
function onListNonConformantBooks(){
const card = createNonConformantBooksCard();
let navigation = CardService.newNavigation()
.pushCard(card);
let actionResponse = CardService.newActionResponseBuilder()
.setNavigation(navigation);
return actionResponse.build();
}
/**
* Create the instructions card shown on the homepage or when nothing relevant is selected.
*
* @return {CardService.Card} The card to show to the user.
*/
function createInstructionsCard(){
let text_section = CardService.newCardSection();
text_section.addWidget(CardService.newTextParagraph().setText('Select a book file or the folder containing books to organize.'));
text_section.addWidget(CardService.newTextParagraph().setText('The folder for books to organize must be named: "'+getSetting('special_folder_name')+'".'));
let button_section = CardService.newCardSection()
.addWidget(
CardService.newTextButton()
.setText('List non-conformant books')
.setOnClickAction(CardService.newAction().setFunctionName('onListNonConformantBooks')
)
)
let card = CardService.newCardBuilder()
.setName('home')
.addSection(text_section)
.addSection(button_section);
return card.build();
}
/**
* Creates the book organizer card listing files in a special folder.
* It is shown when the special folder is selected in the drive
*
* @param {String} folder_id The id of the special folder.
*
* @return {CardService.Card} The assembled card.
*/
function createBookOrganizerCard(folder_id) {
let section = CardService.newCardSection(),
texts = [],
peekHeader;
const folder = DriveApp.getFolderById(folder_id);
let files = folder.getFiles();
if (!files.hasNext()){
// folder is empty: Text: Folder is empty, nothing to organize
section.addWidget(CardService.newTextParagraph().setText('Folder is empty, nothing to organize.'));
} else {
// folder contains files: List of files and button: edit
section.addWidget(CardService.newTextParagraph().setText('Select book and click "Edit".'));
while (files.hasNext()){
file = files.next();
let act = CardService.newAction().setFunctionName('onDisplayBook').setParameters({"book_name": file.getName(), "book_id": file.getId()});
let btn = CardService.newTextButton().setText('Edit').setOnClickAction(act);
let dt = CardService.newDecoratedText()
.setText(file.getName())
.setBottomLabel('Current title')
.setWrapText(true)
.setButton(btn);
section.addWidget(dt)
}
}
let card = CardService.newCardBuilder()
.setName('organizer')
.addSection(section);
return card.build();
}
/**
* Creates a card listing 10 that do not conform to the naming specifications
*
* @return {CardService.Card} The assembled card.
*/
function createNonConformantBooksCard(){
let section = CardService.newCardSection(),
books_to_list = [];
const [folders, books] = getLibraryData();
for(book of books){
if (!book.name.match(standard_filter)){
books_to_list.push(book);
}
if(books_to_list.length > 10){
break;
}
}
if (books_to_list.length == 0){
// no books to edit, nothing to organize
section.addWidget(CardService.newTextParagraph().setText('All books are conformant, nothing to do.'));
} else {
// non conformant files found: List the files and add a button to edit or go to the drive folder
section.addWidget(CardService.newTextParagraph().setText('Select book and click "Edit".'));
for(book of books_to_list){
let act = CardService.newAction().setFunctionName('onDisplayBook').setParameters({"book_name": book.name, "book_id": book.id});
let btn = CardService.newTextButton().setText('Edit').setOnClickAction(act);
let dt = CardService.newDecoratedText()
.setText(book.name)
.setBottomLabel('Current title')
.setWrapText(true)
.setButton(btn);
section.addWidget(dt);
let parent = DriveApp.getFileById(book.id).getParents().next();
section.addWidget(CardService.newDecoratedText()
.setText('In Folder: '+parent.getName())
.setBottomLabel('Opens in new tab')
.setWrapText(true)
.setButton(CardService.newTextButton()
.setText('Open').setOpenLink(CardService.newOpenLink().setUrl(parent.getUrl())
)
));
section.addWidget(CardService.newDivider());
}
}
let card = CardService.newCardBuilder()
.setName('non-conformant')
.addSection(section);
return card.build();
}
/**
* Helper function: Splits an array into multiple arrays containing a specified number of elements of the source array.
*
* @param {array} arr The array to be split into chunks
* @param {Integer} chunksize The size of each chunk
*
* @return {array} An array of arrays containing the source array in chunks
*/
function chunk(arr, chunkSize) {
if (chunkSize <= 0) throw "Invalid chunk size";
let chunks = [];
for (let i=0; i<arr.length; i+=chunkSize)
chunks.push(arr.slice(i,i+chunkSize));
return chunks;
}