37
37
}
38
38
.clickable { cursor : pointer ; }
39
39
.nowrap { white-space : nowrap ; }
40
+ select .rightify { text-align-last : right ; }
40
41
41
42
div .filter {
42
43
position : sticky ;
57
58
.thelayout {
58
59
padding-top : 80px ;
59
60
display : grid ;
60
- grid-template-columns : 150px 1fr 350 px ;
61
+ grid-template-columns : 150px 1fr 425 px ;
61
62
grid-template-areas : " gleft gmiddle gright" ;
62
63
}
63
64
.theleft {
108
109
</div >
109
110
<div v-for =" m in messages" :key =" m.messageId" class =" ui raised fluid card" >
110
111
<div class =" content" >
111
- <div class =" right floated time" >
112
+ <div class =" right floated time nowrap " >
112
113
<a data-tooltip =' filter UNTIL this time' @click =" addTimeFilter(m, 'Until')" ><i class =" chevron left icon" ></i ></a >
113
114
<small >{{m.receivedText}}</small >
114
115
<a class =" icobut" data-tooltip =' filter SINCE this time' @click =" addTimeFilter(m, 'Since')" ><i class =" chevron right icon" ></i ></a >
145
146
</div >
146
147
</div >
147
148
<div v-if =" m.attachmentIds.length" class =" content" >
148
- <a :href = " `/api/vault/attachment/${a}/v1` " v-for =" (a, i) of m.attachmentIds" class =" butspacer ui compact mini button" >
149
+ <a @click = " getAttachment(m, i) " v-for =" (_, i) of m.attachmentIds" data-tooltip = " click to download " class =" butspacer ui compact mini button" >
149
150
<i class =" download icon" ></i > {{attachmentName(m, i)}}
150
151
</a >
151
152
</div >
171
172
<div class =" filter-section ui form" >
172
173
<form class =" ui form" >
173
174
<div class =" fields" >
174
- <select v-model =" pageSize" class =" ui selection dropdown" @change =" offset=0" >
175
- <option v-for =" limit in selectablePageSizes" :value =" limit" >{{limit + ' Messages per Page'}}</option >
175
+ <select v-model =" pageSize" class =" ui selection dropdown rightify " @change =" offset=0" >
176
+ <option v-for =" limit in selectablePageSizes" :value =" limit" >{{limit + ' Results / Page& nbsp ; '}}</option >
176
177
</select >
177
- <select v-model =" ascending" class =" ui fluid selection dropdown" >
178
+ <select v-model =" ascending" class =" ui selection dropdown" >
178
179
<option value =" yes" >Oldest First</option >
179
180
<option value =" no" >Newest First</option >
180
181
</select >
187
188
<div class =" fields" style =" margin-bottom :0 ;" >
188
189
<input class =" ui input" type =" text" v-model =" enteredText" placeholder =" Add and Update Text Filters" >
189
190
</div >
190
- <small ><em ><span class =" nowrap" >body words</span > | <span class =" nowrap" ><b >title:</b >words</span > | <span class =" nowrap" ><b >to:</b >fragment</span > | <span class =" nowrap" ><b >from:</b >fragment</span > | <span class =" nowrap" ><b >has:</b >[no] attach[ment[s]]</span ></em ></small >
191
+ <small ><em ><span class =" nowrap" >body words</span > | <span class =" nowrap" ><b >title:</b > words</span > | <span class =" nowrap" ><b >to:</b > fragment</span > | <span class =" nowrap" ><b >from: </b >fragment</span > | <span class =" nowrap" ><b >has: </b >[no] attach[ment[s]]</span ></em ></small >
191
192
</form >
192
193
</div >
193
194
<div class =" filter-section" v-if =" Object.keys(filters).length" >
199
200
<div class =" filter-section" >
200
201
</div >
201
202
<div v-if =" fullCount" class =" export" >
202
- <button class =" ui fluid button" @click =" exportData " >Export {{fullCount}} Result{{fullCount == 1 ? '' : 's'}}</button >
203
+ <button class =" ui fluid primary button" :class = " {loading: exporting} " @click =" getExport " >Export {{fullCount}} Result{{fullCount == 1 ? '' : 's'}}</button >
203
204
</div >
204
205
</div >
205
206
</div >
208
209
209
210
<script >
210
211
211
- moment = require (' moment' );
212
+ const moment = require (' moment' );
213
+ const util = require (' ../util' );
212
214
213
215
const REFRESH_POLL_RATE = 15000 ;
214
216
215
217
const PAGE_SIZES = [5 , 10 , 20 , 50 , 100 , 1000 ];
216
218
const DEFAULT_PAGE_SIZE = PAGE_SIZES [1 ];
217
219
220
+ async function getExport (queryString , acceptType ) {
221
+ let result;
222
+ try {
223
+ result = await util .fetch .call (this , ' /api/vault/export/v1?' + queryString, { headers: { ' Accept' : acceptType } });
224
+ } catch (err) {
225
+ console .error (' had error' , err);
226
+ return ;
227
+ }
228
+
229
+ if (result .ok ) {
230
+ const blob = await result .blob ();
231
+ const anchor = document .createElement (' a' );
232
+ const burl = window .URL .createObjectURL (blob);
233
+ anchor .href = burl;
234
+ anchor .style = " display: none" ;
235
+ anchor .download = result .headers .get (' content-disposition' ).match (/ filename="(. *? )"/ )[1 ];
236
+ document .body .appendChild (anchor);
237
+ anchor .click ();
238
+ setTimeout (() => {
239
+ document .body .removeChild (anchor);
240
+ window .URL .revokeObjectURL (burl);
241
+ }, 500 );
242
+ }
243
+ }
244
+
245
+ async function getAttachment (id , acceptType ) {
246
+ let result;
247
+ try {
248
+ result = await util .fetch .call (this , ` /api/vault/attachment/${ id} /v1` , { headers: { ' Accept' : acceptType } });
249
+ } catch (err) {
250
+ console .error (' had error' , err);
251
+ return ;
252
+ }
253
+
254
+ if (result .ok ) {
255
+ const blob = await result .blob ();
256
+ const anchor = document .createElement (' a' );
257
+ const burl = window .URL .createObjectURL (blob);
258
+ anchor .href = burl;
259
+ anchor .style = " display: none" ;
260
+ anchor .download = result .headers .get (' content-disposition' ).match (/ filename="(. *? )"/ )[1 ];
261
+ document .body .appendChild (anchor);
262
+ anchor .click ();
263
+ setTimeout (() => {
264
+ document .body .removeChild (anchor);
265
+ window .URL .revokeObjectURL (burl);
266
+ }, 500 );
267
+ }
268
+ }
269
+
218
270
function extract (text , regex , action ) {
219
271
let stripped = text;
220
272
let match;
@@ -239,6 +291,7 @@ module.exports = {
239
291
fullCount: 0 ,
240
292
offset: 0 ,
241
293
ascending: ' no' ,
294
+ exporting: false ,
242
295
messages: []
243
296
}),
244
297
computed: {
@@ -247,6 +300,7 @@ module.exports = {
247
300
q .push (` offset=${ this .offset } ` );
248
301
q .push (` limit=${ this .pageSize } ` );
249
302
q .push (` ascending=${ this .ascending } ` );
303
+ q .push (` tzoffset=${ (new Date ()).getTimezoneOffset ()} ` );
250
304
return q .join (' &' ).replace (" '" ," " );
251
305
},
252
306
pagerDots : function () {
@@ -349,6 +403,17 @@ module.exports = {
349
403
this .fullCount = (this .messages .length && this .messages [0 ].fullCount ) || 0 ;
350
404
});
351
405
},
406
+ getExport : function () {
407
+ this .exporting = true ;
408
+ const q = this .queryString ;
409
+ getExport (q, ' application/zip' ).then (() => { this .exporting = false ; });
410
+ },
411
+ getAttachment : function (m , idx ) {
412
+ const message = m .payload .find (x => x .version === 1 );
413
+ const attachment = message && message .data && message .data .attachments [idx];
414
+ const id = m .attachmentIds [idx];
415
+ getAttachment (m .attachmentIds [idx], attachment .type );
416
+ },
352
417
messageBody : function (m ) {
353
418
const message = m .payload .find (x => x .version === 1 );
354
419
const tmpText = message .data && message .data .body .find (x => x .type === ' text/plain' );
@@ -368,15 +433,11 @@ module.exports = {
368
433
return attachment && attachment .name ;
369
434
},
370
435
threadColor : function (id ) {
371
- // map id to a darkish color from a clumpy, visually-differentiable collection
436
+ // map id to a randomish darkish color from a clumpy, visually-differentiable collection
372
437
const val = parseInt (id .replace (' -' , ' ' ), 16 );
373
438
const hue = (val % 90 ) * 4 ;
374
439
const lum = (val % 5 ) * 10 + 20 ;
375
-
376
440
return { color: ` hsl(${ hue} , 100%, ${ lum} %)` };
377
- },
378
- exportData : function () {
379
- alert (' TBD' );
380
441
}
381
442
},
382
443
mounted : function () {
0 commit comments