@internetarchive/ia-book-actions 1.1.0-alpha9 → 1.1.1

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.
@@ -0,0 +1,26 @@
1
+ name: App CI
2
+
3
+ on:
4
+ push:
5
+ branches: [ main ]
6
+ pull_request:
7
+ branches: [ main ]
8
+
9
+ jobs:
10
+ build:
11
+ runs-on: ubuntu-latest
12
+
13
+ steps:
14
+ - uses: actions/checkout@v4
15
+ - uses: actions/setup-node@v3
16
+ with:
17
+ node-version: 16.x
18
+
19
+ - name: Install dependencies
20
+ run: yarn install
21
+
22
+ # - name: Run tests
23
+ # run: yarn run test
24
+
25
+ # - name: Upload reports
26
+ # run: npx codecov
@@ -0,0 +1,39 @@
1
+ # This workflow will generate the static page under `main` subdirectory inside the `gh-pages` branch
2
+
3
+ # This workflow will run every time new changes were pushed to the `main` branch
4
+
5
+ name: App build CI/CD to main branch
6
+
7
+ on:
8
+ push:
9
+ branches: [ main ]
10
+
11
+ jobs:
12
+ build:
13
+ runs-on: ubuntu-latest
14
+
15
+ steps:
16
+ - uses: actions/checkout@v4
17
+ with:
18
+ persist-credentials: false
19
+
20
+ - uses: actions/setup-node@v3
21
+ with:
22
+ node-version: 20
23
+
24
+ - name: Install dependencies
25
+ run: yarn install
26
+
27
+ - name: Create build files for gh-pages deploy
28
+ run: yarn prepare:ghpages
29
+
30
+ # Reference: https://github.com/JamesIves/github-pages-deploy-action
31
+ - name: Deploy 🚀
32
+ uses: JamesIves/github-pages-deploy-action@v4.4.3
33
+ with:
34
+ branch: gh-pages
35
+ folder: ghpages
36
+ clean-exclude: pr/
37
+ force: false
38
+ target-folder: main
39
+ token: ${{ secrets.GH_TOKEN }}
@@ -0,0 +1,37 @@
1
+ # This workflow will generate the static page under `pr` subdirectory inside the `gh-pages` branch
2
+
3
+ # This workflow will run every time there's a PR opened, reopened, synchronize, or closed
4
+
5
+ name: Deploy PR previews
6
+
7
+ on:
8
+ pull_request:
9
+ types:
10
+ - opened
11
+ - reopened
12
+ - synchronize
13
+ - closed
14
+
15
+ concurrency: preview-${{ github.ref }}
16
+
17
+ jobs:
18
+ deploy-preview:
19
+ runs-on: ubuntu-20.04
20
+ steps:
21
+ - name: Checkout
22
+ uses: actions/checkout@v4
23
+
24
+ - uses: actions/setup-node@v3
25
+ with:
26
+ node-version: 20.x
27
+
28
+ - name: Install and Build
29
+ run: |
30
+ yarn install
31
+ yarn prepare:ghpages
32
+ # Reference: https://github.com/rossjrw/pr-preview-action
33
+ - name: Deploy preview
34
+ uses: rossjrw/pr-preview-action@v1
35
+ with:
36
+ source-dir: ./ghpages/
37
+ umbrella-dir: pr
package/demo/index.html CHANGED
@@ -76,6 +76,13 @@
76
76
  display: inline-grid;
77
77
  }
78
78
 
79
+ .action-button button {
80
+ padding: 6px;
81
+ cursor: pointer;
82
+ border: 2px solid #194880;
83
+ border-radius: 10px;
84
+ }
85
+
79
86
  body.modal-manager-open {
80
87
  overflow: hidden;
81
88
  }
@@ -128,10 +135,11 @@
128
135
  iaBookActions.modalConfig = modalConfig;
129
136
  iaBookActions.lendingBarPostInit = lendingBarPostInit;
130
137
  iaBookActions.tokenDelay = 2; // 2 seconds
138
+ iaBookActions.timerExecutionSeconds = 4 // 4 seconds
131
139
  iaBookActions.returnUrl = '';
132
140
  iaBookActions.localCache = localCache;
133
141
 
134
- let loanTotalTime2Min = { loanTotalTime: 170, loanRenewAtLast: 80, pageChangedInLast: 15 };
142
+ let loanTotalTime2Min = { loanTotalTime: 120, loanRenewAtLast: 80, pageChangedInLast: 15 };
135
143
  let loanTotalTime5Min = { loanTotalTime: 300, loanRenewAtLast: 180, pageChangedInLast: 15 };
136
144
  let loanTotalTime1Hour = { loanTotalTime: 3600, loanRenewAtLast: 660, pageChangedInLast: 900 };
137
145
 
@@ -286,6 +294,19 @@
286
294
  iaBookActions.browseHasExpired();
287
295
  });
288
296
 
297
+ document.querySelector('#resync_timer').addEventListener('click', async (e) => {
298
+ if (iaBookActions.lendingStatus.user_has_browsed) {
299
+ document.querySelector('ia-book-actions').dispatchEvent(
300
+ new Event('visibilitychange', {
301
+ detail: {},
302
+ bubbles: true,
303
+ composed: true,
304
+ })
305
+ );
306
+ }
307
+ await new Promise(resolve => setTimeout(resolve, 5000));
308
+ });
309
+
289
310
  document.querySelectorAll('.availableToBorrow input[type=radio]').forEach((item) => {
290
311
  item.addEventListener('click', (e) => {
291
312
  if (e.target.value === 'available_to_borrow') {
@@ -359,8 +380,9 @@
359
380
  </td>
360
381
  <td class="searchParam">
361
382
  <label><input type="checkbox" name="search" value="Search Param" />Search Param</label>
383
+ </td>
362
384
  <td>
363
- <button class="pageChangedEvent">Page Change Event</button>
385
+ <label class="ia-button"><button class="pageChangedEvent">Page Change Event</button></label>
364
386
  </td>
365
387
  </tr>
366
388
  </table>
@@ -385,12 +407,6 @@
385
407
  <label><input type="radio" name="browse" value="available_to_browse" />available_to_browse</label>
386
408
  <label><input type="radio" name="browse" value="user_has_browsed" />user_has_browsed</label>
387
409
  <label><input type="radio" name="browse" value="browsingExpired" />browsingExpired</label>
388
- <div>
389
- <button id="show_warning_modal">When Browsing (`user_has_browsed`), show Warning Modal</button>
390
- </div>
391
- <div>
392
- <button id="show_expired_modal">While Browsing (`user_has_browsed`), Expire book & show its modal</button>
393
- </div>
394
410
  </td>
395
411
  <td class="availableToBorrow">
396
412
  <label><input type="radio" name="borrow" value="available_to_borrow" />available_to_borrow</label>
@@ -410,6 +426,26 @@
410
426
  font-weight: bold; display: none" name="purchase" value="Reflect" />
411
427
  </td>
412
428
  </tr>
429
+ <tr>
430
+ <td>
431
+ <fieldset style="margin-top: 10px;">
432
+ <legend style="color: black;">controls for browsed book</legend>
433
+ <table aria-hidden="true">
434
+ <tbody><tr>
435
+ <td class="action-button">
436
+ <button id="show_warning_modal">When Browsing (`user_has_browsed`),<br> show Warning Modal</button>
437
+ </td>
438
+ <td class="action-button">
439
+ <button id="show_expired_modal">While Browsing (`user_has_browsed`),<br> Expire book & show its modal</button>
440
+ </td>
441
+ <td class="action-button">
442
+ <button id="resync_timer">While Browsing (`user_has_browsed`),<br> resync timer</button>
443
+ </td>
444
+ </tr>
445
+ </tbody></table>
446
+ </fieldset>
447
+ </td>
448
+ </tr>
413
449
  </table>
414
450
  </fieldset>
415
451
  </div>
package/index.html ADDED
@@ -0,0 +1,474 @@
1
+ <!doctype html>
2
+ <html lang="en-GB">
3
+
4
+ <head>
5
+ <meta charset="utf-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
7
+ <style>
8
+ html {
9
+ font-size: 10px;
10
+ /* This is to match petabox's base font size */
11
+ }
12
+ body {
13
+ margin: 0;
14
+ text-align: center;
15
+ font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
16
+ }
17
+ #demo {
18
+ height: 100px;
19
+ }
20
+ ia-book-actions {
21
+ --white: #fff;
22
+ --black: #000;
23
+ --primaryBGColor: var(--black);
24
+ --secondaryBGColor: #222;
25
+ --tertiaryBGColor: #333;
26
+ --primaryTextColor: var(--white);
27
+ --primaryCTAFill: #194880;
28
+ --primaryCTABorder: #c5d1df;
29
+ --secondaryCTAFill: #333;
30
+ --secondaryCTABorder: #999;
31
+ --secondaryCTAFillRGB: 51, 51, 51;
32
+ --primaryErrorCTAFill: #e51c26;
33
+ --primaryErrorCTABorder: #f8c6c8;
34
+ --primaryCTAFillRGB: 25, 72, 128;
35
+ --primaryErrorCTAFillRGB: 229, 28, 38;
36
+ --primaryDisableCTAFill: #767676;
37
+ }
38
+
39
+ fieldset {
40
+ width: fit-content;
41
+ margin-top: 50px;
42
+ }
43
+ fieldset legend {
44
+ text-align: left;
45
+ font-size: 12px;
46
+ }
47
+ td {
48
+ color: white;
49
+ font-size: 14px;
50
+ border-radius: 5%;
51
+ display: table-cell;
52
+ text-align: initial;
53
+ margin: 5px 0;
54
+ width: max-content;
55
+ display: block;
56
+ }
57
+ label {
58
+ padding: 10px;
59
+ display: block;
60
+ cursor: pointer;
61
+ margin: 2px;
62
+ background: #4e545a;
63
+ border-radius: 10px;
64
+ }
65
+ input[type="radio"], input[type="checkbox"] {
66
+ margin-top: 0;
67
+ vertical-align: middle;
68
+ }
69
+
70
+ .initial td {
71
+ float: left;
72
+ margin: 2px;
73
+ }
74
+
75
+ fieldset {
76
+ display: inline-grid;
77
+ }
78
+
79
+ .action-button button {
80
+ padding: 6px;
81
+ cursor: pointer;
82
+ border: 2px solid #194880;
83
+ border-radius: 10px;
84
+ }
85
+
86
+ body.modal-manager-open {
87
+ overflow: hidden;
88
+ }
89
+
90
+ modal-manager[mode='closed'] {
91
+ display: none;
92
+ }
93
+ </style>
94
+ </head>
95
+
96
+ <body>
97
+ <ia-book-actions></ia-book-actions>
98
+
99
+ <script type="module">
100
+ /* eslint-disable */
101
+
102
+ import './src/ia-book-actions.js';
103
+ import {
104
+ defaultLendingStatus,
105
+ canBrowseAndBorrow,
106
+ } from './src/assets/data/lending-get-status-service.js';
107
+ import { ModalConfig } from '@internetarchive/modal-manager';
108
+ import { LocalCache } from '@internetarchive/local-cache';
109
+
110
+ const localCache = new LocalCache({
111
+ namespace: 'loanRenew',
112
+ defaultTTL: 1 * 60, // 1 minute
113
+ });
114
+
115
+ const userid = '@neeraj-archive';
116
+ const identifier = 'naturalhistoryof00unse_4111';
117
+ let bwbPurchaseUrl = 'https://www.betterworldbooks.com/product/detail/cambridge-ancient-hist-v04-0521044863';
118
+
119
+
120
+ const modalConfig = new ModalConfig();
121
+ modalConfig.headerColor = '#d9534f';
122
+
123
+ let lendingBarPostInit = function lendingBarPostInit() {
124
+ setTimeout(function() {}, 100);
125
+ }
126
+
127
+ // set defaultLendingStatus for unavailable (without borrowables) book
128
+ let iaBookActions = document.querySelector('ia-book-actions');
129
+
130
+ iaBookActions.userid = userid;
131
+ iaBookActions.identifier = identifier;
132
+ iaBookActions.bookTitle = 'Contemporary Black biography. Volume 39 : profiles from the interContemporary Black biography. Volume 39';
133
+ iaBookActions.lendingStatus = canBrowseAndBorrow;
134
+ iaBookActions.bwbPurchaseUrl = '';
135
+ iaBookActions.modalConfig = modalConfig;
136
+ iaBookActions.lendingBarPostInit = lendingBarPostInit;
137
+ iaBookActions.tokenDelay = 10; // 2 seconds
138
+ iaBookActions.timerExecutionSeconds = 3 // 4 seconds
139
+ iaBookActions.returnUrl = '';
140
+ iaBookActions.localCache = localCache;
141
+
142
+ let loanTotalTime2Min = { loanTotalTime: 120, loanRenewAtLast: 110, pageChangedInLast: 30 };
143
+ let loanTotalTime5Min = { loanTotalTime: 300, loanRenewAtLast: 240, pageChangedInLast: 60 };
144
+ let loanTotalTime10Min = { loanTotalTime: 600, loanRenewAtLast: 480, pageChangedInLast: 120 };
145
+ let loanTotalTime30Min = { loanTotalTime: 1800, loanRenewAtLast: 1500, pageChangedInLast: 600 };
146
+ let loanTotalTime1Hour = { loanTotalTime: 3600, loanRenewAtLast: 660, pageChangedInLast: 900 };
147
+
148
+ iaBookActions.loanRenewTimeConfig = loanTotalTime2Min;
149
+ let params = new URLSearchParams(document.location.search);
150
+ switch (params.get("timer")) {
151
+ case '5':
152
+ iaBookActions.loanRenewTimeConfig = loanTotalTime5Min;
153
+ break;
154
+ case '10':
155
+ iaBookActions.loanRenewTimeConfig = loanTotalTime10Min;
156
+ break;
157
+ case '30':
158
+ iaBookActions.loanRenewTimeConfig = loanTotalTime30Min;
159
+ break;
160
+ case '60':
161
+ iaBookActions.loanRenewTimeConfig = loanTotalTime1Hour;
162
+ break;
163
+ default:
164
+ iaBookActions.loanRenewTimeConfig = loanTotalTime2Min;
165
+ break;
166
+ }
167
+
168
+ let lendingStatus1 = iaBookActions.lendingStatus;
169
+ let secondsLeftOnLoan = iaBookActions.loanRenewTimeConfig.loanTotalTime;
170
+
171
+ document.querySelectorAll('.titleBar input[type=checkbox]').forEach((item) => {
172
+ item.addEventListener('click', (e) => {
173
+ if (e.target.checked) {
174
+ iaBookActions.barType = 'title';
175
+ } else {
176
+ iaBookActions.barType = 'action';
177
+ }
178
+ });
179
+ });
180
+
181
+ document.querySelectorAll('.searchParam input[type=checkbox]').forEach((item) => {
182
+ item.addEventListener('click', (e) => {
183
+ var searchParams = new URLSearchParams(window.location.search);
184
+ var separate = '';
185
+ if (e.target.checked) {
186
+ separate = '?';
187
+ searchParams.set('q', 'test');
188
+ } else {
189
+ separate = '';
190
+ searchParams.delete('q');
191
+ }
192
+
193
+ if (history.pushState) {
194
+ var newurl = window.location.protocol + "//" + window.location.host + window.location.pathname + separate + searchParams;
195
+ window.history.pushState({path:newurl},'',newurl);
196
+ }
197
+ });
198
+ });
199
+
200
+ document.querySelectorAll('.errorEnable input[type=checkbox]').forEach((item) => {
201
+ if (window.location.href.indexOf('?error=true') !== -1) {
202
+ item.checked = true;
203
+ }
204
+ item.addEventListener('click', (e) => {
205
+ var searchParams = new URLSearchParams(window.location.search);
206
+ var separate = '';
207
+ if (e.target.checked) {
208
+ separate = '?';
209
+ searchParams.set('error', true);
210
+ } else {
211
+ separate = '';
212
+ searchParams.delete('error');
213
+ }
214
+
215
+ if (history.pushState) {
216
+ var newurl = window.location.protocol + "//" + window.location.host + window.location.pathname + separate + searchParams;
217
+ window.history.pushState({path:newurl},'',newurl);
218
+ }
219
+ });
220
+ });
221
+
222
+
223
+ document.querySelectorAll('.userState input[type=checkbox]').forEach((item) => {
224
+ item.addEventListener('click', (e) => {
225
+ if (e.target.checked) {
226
+ if (e.target.value === 'isAdmin') lendingStatus1.isAdmin = true;
227
+ if (e.target.value === 'isLoggedIn') iaBookActions.userid = '@neeraj';
228
+ } else {
229
+ if (e.target.value === 'isAdmin') lendingStatus1.isAdmin = false;
230
+ if (e.target.value === 'isLoggedIn') iaBookActions.userid = '';
231
+ }
232
+ let lending = {...defaultLendingStatus, ...lendingStatus1};
233
+ iaBookActions.lendingStatus = lending;
234
+ });
235
+ });
236
+
237
+ document.querySelectorAll('.printDisabled input[type=checkbox]').forEach((item) => {
238
+ item.addEventListener('click', (e) => {
239
+ if (e.target.checked) {
240
+ if (e.target.value === 'is_printdisabled') lendingStatus1.is_printdisabled = true;
241
+ if (e.target.value === 'user_is_printdisabled') lendingStatus1.user_is_printdisabled = true;
242
+ } else {
243
+ if (e.target.value === 'is_printdisabled') lendingStatus1.is_printdisabled = false;
244
+ if (e.target.value === 'user_is_printdisabled') lendingStatus1.user_is_printdisabled = false;
245
+ }
246
+
247
+ let lending = {...defaultLendingStatus, ...lendingStatus1};
248
+ iaBookActions.lendingStatus = lending;
249
+ });
250
+ });
251
+
252
+ document.querySelectorAll('.availableToBrowse input[type=radio]').forEach((item) => {
253
+ item.addEventListener('click', async (e) => {
254
+ if (e.target.value === 'user_has_browsed') {
255
+ lendingStatus1.user_has_browsed = true;
256
+ lendingStatus1.available_to_browse = false;
257
+ lendingStatus1.secondsLeftOnLoan = secondsLeftOnLoan;
258
+ lendingStatus1.browsingExpired = false;
259
+
260
+ const expireDate = new Date(
261
+ new Date().getTime() + iaBookActions.loanRenewTimeConfig.loanTotalTime * 1000
262
+ );
263
+ try {
264
+ // set a value
265
+ await localCache.set({
266
+ key: `${identifier}-loanTime`,
267
+ value: expireDate,
268
+ ttl: Number(iaBookActions.loanRenewTimeConfig.loanTotalTime),
269
+ });
270
+
271
+ await localCache.delete(`${identifier}-pageChangedTime`);
272
+ } catch (error) {}
273
+
274
+ } else if(e.target.value === 'browsingExpired') {
275
+ lendingStatus1.user_has_browsed = true;
276
+ lendingStatus1.available_to_browse = false;
277
+ lendingStatus1.secondsLeftOnLoan = 0;
278
+ lendingStatus1.browsingExpired = true;
279
+ } else if (e.target.value === 'available_to_browse') {
280
+ lendingStatus1.available_to_browse = true;
281
+ lendingStatus1.user_has_browsed = false;
282
+ }
283
+
284
+ // wait for a while
285
+ setTimeout(() => {
286
+ let lending = {...defaultLendingStatus, ...lendingStatus1};
287
+ iaBookActions.lendingStatus = lending;
288
+ }, 10);
289
+ });
290
+ });
291
+
292
+ document.querySelector('#show_warning_modal').addEventListener('click', async () => {
293
+ if (!iaBookActions.lendingStatus.user_has_browsed) {
294
+ const newLendingStatus = { ...iaBookActions.lendingStatus, user_has_browsed: true };
295
+ iaBookActions.lendingStatus = newLendingStatus;
296
+ await iaBookActions.updateComplete;
297
+ }
298
+
299
+ iaBookActions.showWarningModal();
300
+
301
+ if (iaBookActions.lendingStatus.user_has_browsed) {
302
+ iaBookActions.showWarningModal();
303
+ }
304
+ });
305
+
306
+ document.querySelector('#show_expired_modal').addEventListener('click', async () => {
307
+ if (!iaBookActions.lendingStatus.user_has_browsed) {
308
+ const newLendingStatus = { ...iaBookActions.lendingStatus, user_has_browsed: true };
309
+ iaBookActions.lendingStatus = newLendingStatus;
310
+ await iaBookActions.updateComplete;
311
+ }
312
+ await new Promise(resolve => setTimeout(resolve, 5000));
313
+
314
+ iaBookActions.browseHasExpired();
315
+ });
316
+
317
+ document.querySelector('#resync_timer').addEventListener('click', async (e) => {
318
+ if (iaBookActions.lendingStatus.user_has_browsed) {
319
+ document.querySelector('ia-book-actions').dispatchEvent(
320
+ new Event('visibilitychange', {
321
+ detail: {},
322
+ bubbles: true,
323
+ composed: true,
324
+ })
325
+ );
326
+ }
327
+ await new Promise(resolve => setTimeout(resolve, 5000));
328
+ });
329
+
330
+ document.querySelectorAll('.availableToBorrow input[type=radio]').forEach((item) => {
331
+ item.addEventListener('click', (e) => {
332
+ if (e.target.value === 'available_to_borrow') {
333
+ lendingStatus1.available_to_borrow = true;
334
+ lendingStatus1.user_on_waitlist = false;
335
+ lendingStatus1.available_to_waitlist = false;
336
+ lendingStatus1.user_has_borrowed = false;
337
+ } else if (e.target.value === 'user_can_claim_waitlist') {
338
+ lendingStatus1.available_to_borrow = true;
339
+ lendingStatus1.user_on_waitlist = true;
340
+ lendingStatus1.user_can_claim_waitlist = true;
341
+ lendingStatus1.user_has_borrowed = false;
342
+ lendingStatus1.available_to_waitlist = false;
343
+ } else if (e.target.value === 'user_on_waitlist') {
344
+ lendingStatus1.available_to_borrow = false;
345
+ lendingStatus1.user_on_waitlist = true;
346
+ lendingStatus1.available_to_waitlist = false;
347
+ lendingStatus1.user_has_borrowed = false;
348
+ } else if (e.target.value === 'available_to_waitlist') {
349
+ lendingStatus1.available_to_borrow = false;
350
+ lendingStatus1.user_on_waitlist = false;
351
+ lendingStatus1.available_to_waitlist = true;
352
+ lendingStatus1.user_has_borrowed = false;
353
+ } else if (e.target.value === 'user_has_borrowed') {
354
+ lendingStatus1.available_to_borrow = false;
355
+ lendingStatus1.user_on_waitlist = false;
356
+ lendingStatus1.available_to_waitlist = false;
357
+ lendingStatus1.user_has_borrowed = true;
358
+ }
359
+
360
+ let lending = {...defaultLendingStatus, ...lendingStatus1};
361
+ iaBookActions.lendingStatus = lending;
362
+ });
363
+ });
364
+
365
+ document.querySelectorAll('.purchase input[type=checkbox]').forEach((item) => {
366
+ item.addEventListener('click', (e) => {
367
+ if (e.target.checked) {
368
+ bwbPurchaseUrl = 'https://www.google.com';
369
+ } else {
370
+ bwbPurchaseUrl = ''
371
+ }
372
+ iaBookActions.bwbPurchaseUrl = bwbPurchaseUrl;
373
+ })
374
+ });
375
+
376
+ document.querySelector('.pageChangedEvent').addEventListener('click', () => {
377
+ // dispatch event to say book page has flipped
378
+ document.querySelector('ia-book-actions').dispatchEvent(
379
+ new CustomEvent('BookReader:userAction', {
380
+ detail: {},
381
+ bubbles: true,
382
+ composed: true,
383
+ })
384
+ );
385
+ })
386
+
387
+ // listen for IABookReader:BrowsingHasExpired
388
+ window.addEventListener('IABookReader:BrowsingHasExpired', () => {
389
+ console.log('IABookReader:BrowsingHasExpired EVENT FIRED');
390
+ });
391
+ </script>
392
+
393
+ <div>
394
+ <fieldset>
395
+ <legend>Dev testing for book title bar</legend>
396
+ <table class="initial">
397
+ <tr>
398
+ <td class="titleBar">
399
+ <label><input type="checkbox" name="title" value="title" />Title Bar</label>
400
+ </td>
401
+ <td class="searchParam">
402
+ <label><input type="checkbox" name="search" value="Search Param" />Search Param</label>
403
+ </td>
404
+ <td>
405
+ <label class="ia-button"><button class="pageChangedEvent">Page Change Event</button></label>
406
+ </td>
407
+ </tr>
408
+ </table>
409
+ </fieldset>
410
+
411
+ <fieldset>
412
+ <legend>Dev testing for book action bar</legend>
413
+ <table class="initial">
414
+ <tr colspan='5'>
415
+ <td class="errorEnable"><label><input type="checkbox" name="errorEnable" value="errorEnable" />Do you want to see error response?</label></td>
416
+ </tr>
417
+ <tr>
418
+ <td class="userState">
419
+ <label><input type="checkbox" name="user" value="isAdmin" />isAdmin</label>
420
+ <label><input type="checkbox" name="user" value="isLoggedIn" />isLoggedIn</label>
421
+ </td>
422
+ <td class="printDisabled">
423
+ <label><input type="checkbox" name="is_printdisabled" value="is_printdisabled" />is_printdisabled</label>
424
+ <label><input type="checkbox" name="user_is_printdisabled" value="user_is_printdisabled" />user_is_printdisabled</label>
425
+ </td>
426
+ <td class="availableToBrowse">
427
+ <label><input type="radio" name="browse" value="available_to_browse" />available_to_browse</label>
428
+ <label><input type="radio" name="browse" value="user_has_browsed" />user_has_browsed</label>
429
+ <label><input type="radio" name="browse" value="browsingExpired" />browsingExpired</label>
430
+ </td>
431
+ <td class="availableToBorrow">
432
+ <label><input type="radio" name="borrow" value="available_to_borrow" />available_to_borrow</label>
433
+ <label><input type="radio" name="borrow" value="user_on_waitlist" />user_on_waitlist</label>
434
+ <label><input type="radio" name="borrow" value="user_can_claim_waitlist" />user_can_claim_waitlist</label>
435
+ <label><input type="radio" name="borrow" value="available_to_waitlist" />available_to_waitlist</label>
436
+ <label><input type="radio" name="borrow" value="user_has_borrowed" />user_has_borrowed</label>
437
+ </td>
438
+ <td class="purchase">
439
+ <label><input type="checkbox" name="purchase" value="purchase" />purchase</label>
440
+ </td>
441
+
442
+ <td class="reflect">
443
+ <input type="button" style="padding: 10px;
444
+ background: #194880;
445
+ color: white;
446
+ font-weight: bold; display: none" name="purchase" value="Reflect" />
447
+ </td>
448
+ </tr>
449
+ <tr>
450
+ <td>
451
+ <fieldset style="margin-top: 10px;">
452
+ <legend style="color: black;">controls for browsed book</legend>
453
+ <table aria-hidden="true">
454
+ <tbody><tr>
455
+ <td class="action-button">
456
+ <button id="show_warning_modal">When Browsing (`user_has_browsed`),<br> show Warning Modal</button>
457
+ </td>
458
+ <td class="action-button">
459
+ <button id="show_expired_modal">While Browsing (`user_has_browsed`),<br> Expire book & show its modal</button>
460
+ </td>
461
+ <td class="action-button">
462
+ <button id="resync_timer">While Browsing (`user_has_browsed`),<br> resync timer</button>
463
+ </td>
464
+ </tr>
465
+ </tbody></table>
466
+ </fieldset>
467
+ </td>
468
+ </tr>
469
+ </table>
470
+ </fieldset>
471
+ </div>
472
+ <modal-manager></modal-manager>
473
+ </body>
474
+ </html>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@internetarchive/ia-book-actions",
3
- "version": "1.1.0-alpha9",
3
+ "version": "1.1.1",
4
4
  "description": "Bookreader actions for borrowable book",
5
5
  "author": "Internet Archive",
6
6
  "license": "AGPL-3.0-only",
@@ -11,14 +11,19 @@
11
11
  },
12
12
  "scripts": {
13
13
  "start": "web-dev-server",
14
- "lint": "eslint --ext .js,.html . --ignore-path .gitignore && prettier \"**/*.js\" --check --ignore-path .gitignore",
15
- "format": "eslint --ext .js,.html . --fix --ignore-path .gitignore && prettier \"**/*.js\" --write --ignore-path .gitignore",
14
+ "prepare:ghpages": "rimraf ghpages && yarn run prepare && vite build",
15
+ "prepare": "rimraf dist && husky install",
16
+ "lint": "eslint --ext \"./src/*.js\",.html . --ignore-path .gitignore && prettier \"./src/*.js\" --check --ignore-path .gitignore",
17
+ "format": "eslint --ext \"./src/*.js\",.html . --fix --ignore-path .gitignore && prettier \"./src/*.js\" --write --ignore-path .gitignore",
16
18
  "test": "yarn run lint && yarn test:fast",
17
- "test:fast": "web-test-runner --coverage",
18
- "test:watch": "web-test-runner --watch"
19
+ "test:fast": "web-test-runner --coverage --playwright --browsers chromium firefox webkit",
20
+ "test:watch": "web-test-runner --watch --playwright --browsers chromium firefox webkit",
21
+ "deploy": "yarn run deploy:run -e $(git branch --show-current)",
22
+ "deploy:run": "yarn run prepare:ghpages && touch ghpages/.nojekyll && yarn run deploy:gh",
23
+ "deploy:gh": "gh-pages -t -d ghpages -m \"Build for $(git log --pretty=format:\"%h %an %ai %s\" -n1) [skip ci]\""
19
24
  },
20
25
  "dependencies": {
21
- "@internetarchive/icon-info": "^1.2.6",
26
+ "@internetarchive/icon-info": "1.3.5",
22
27
  "@internetarchive/local-cache": "^0.2.1",
23
28
  "@internetarchive/modal-manager": "2.0.0",
24
29
  "@internetarchive/shared-resize-observer": "^0.2.0",
@@ -38,10 +43,13 @@
38
43
  "eslint-plugin-lit": "^1.3.0",
39
44
  "eslint-plugin-lit-a11y": "^1.0.1",
40
45
  "eslint-plugin-wc": "^1.3.0",
41
- "husky": "^1.0.0",
46
+ "gh-pages": "^4.0.0",
47
+ "husky": "^7.0.0",
42
48
  "lint-staged": "^10.0.0",
43
49
  "prettier": "^2.0.4",
44
- "sinon": "^13.0.2"
50
+ "rimraf": "^3.0.2",
51
+ "sinon": "^13.0.2",
52
+ "vite": "^2.9.9"
45
53
  },
46
54
  "eslintConfig": {
47
55
  "extends": [
@@ -118,7 +118,7 @@ export const canBrowseAndBorrow = {
118
118
  loanStartDate: '2021-07-30 09:57:40',
119
119
  loansUrl: '/details/@neeraj-archive?tab=loans#loans-on-loan',
120
120
  maxLoans: 10,
121
- secondsLeftOnLoan: 170,
121
+ secondsLeftOnLoan: 0,
122
122
  shouldProtectImages: true,
123
123
  totalWaitlistCount: 0,
124
124
  userHasBorrowed: false,
@@ -47,7 +47,7 @@ export default class TimerCountdown extends LitElement {
47
47
  }
48
48
 
49
49
  render() {
50
- const viewClass = this.displayTime ? 'view' : 'hide1';
50
+ const viewClass = this.displayTime ? 'view' : 'hide';
51
51
  return html`
52
52
  <button
53
53
  id="timer-counter"
@@ -57,7 +57,8 @@ export default class TimerCountdown extends LitElement {
57
57
  }}
58
58
  role="timer"
59
59
  >
60
- <span>${this.minutesLeftOnLoan}</span>
60
+ <span>${this.minutesLeftOnLoan} - </span>
61
+ <span class="second">${Number(this.secondsLeftOnLoan)}</span>
61
62
  <span class="sr-only">${this.remainingTime} left</span>
62
63
  </button>
63
64
  `;
@@ -36,7 +36,7 @@ export default class ActionsConfig {
36
36
  firstBrowseConfig() {
37
37
  return {
38
38
  id: 'browseBook',
39
- text: 'Borrow',
39
+ text: 'Borrow (auto-renewing)',
40
40
  className: 'primary',
41
41
  analyticsEvent: {
42
42
  category: this.analyticsCategories.preview,
@@ -32,7 +32,12 @@ export default async function ActionsHandlerService(options) {
32
32
  location?.href?.indexOf('?error=true') !== -1 &&
33
33
  location?.hostname !== 'archive.org';
34
34
 
35
- if (location?.pathname === '/demo/') baseHost = `/demo/`;
35
+ const testHostname = ['localhost', 'internetarchive.github.io'];
36
+ let isTest = false;
37
+ if (testHostname.includes(location.hostname)) {
38
+ isTest = true;
39
+ baseHost = location.href;
40
+ }
36
41
 
37
42
  let formData = new FormData();
38
43
  formData.append('action', option.action);
@@ -51,8 +56,8 @@ export default async function ActionsHandlerService(options) {
51
56
  };
52
57
  }
53
58
 
54
- // return success response for /demo/ server...
55
- if (baseHost == '/demo/1' || baseHost == '/demo/') {
59
+ // return success response for localhost server...
60
+ if (isTest) {
56
61
  if (
57
62
  option?.action == 'renew_loan' ||
58
63
  option?.action == 'return_loan'
@@ -8,7 +8,8 @@ const log =
8
8
  location.host.match(/^(www|cat)-[a-z0-9]+\.archive\.org$/) ||
9
9
  location.host.match(/\.code\.archive\.org$/) ||
10
10
  location.host.match(/\.dev\.archive\.org$/) ||
11
- location.host.match(/^ia-petabox-/)
11
+ location.host.match(/^ia-petabox-/) ||
12
+ location.host.match(/^internetarchive/)
12
13
  ? // eslint-disable-next-line no-console
13
14
  console.log.bind(console) // convenient, no? Stateless function
14
15
  : () => {};
@@ -59,6 +59,7 @@ export default class IABookActions extends LitElement {
59
59
  disableActionGroup: { type: Boolean },
60
60
  modal: { Object },
61
61
  tokenDelay: { type: Number },
62
+ timerExecutionSeconds: { type: Number },
62
63
  localCache: { type: Object },
63
64
  loanRenewTimeConfig: { type: Object },
64
65
  loanRenewResult: { type: Object },
@@ -79,6 +80,7 @@ export default class IABookActions extends LitElement {
79
80
  this.sharedObserver = undefined;
80
81
  this.disableActionGroup = false;
81
82
  this.tokenDelay = 120; // in seconds
83
+ this.timerExecutionSeconds = 30;
82
84
 
83
85
  // private props
84
86
  this.postInitComplete = false;
@@ -89,10 +91,7 @@ export default class IABookActions extends LitElement {
89
91
  this.lendingOptions = {};
90
92
  this.borrowType = null; // 'browsed'|'borrowed'
91
93
  this.browseTimer = undefined; // timeout
92
- this.timerExecutionSeconds = 30;
93
-
94
- /** @deprecated */
95
- this.toast = undefined;
94
+ this.timeWhenTimerStart = undefined;
96
95
 
97
96
  /**
98
97
  * when user click on [return the book] button on warning modal
@@ -117,7 +116,7 @@ export default class IABookActions extends LitElement {
117
116
  * contains one hour auto-loan-renew response
118
117
  *
119
118
  * @type {object} loanRenewResult
120
- * @property {string} texts - texts messages need to show in toast message
119
+ * @property {string} texts - texts messages shows in modal
121
120
  * @property {boolean} renewNow - key to determine if need to renew now
122
121
  * @property {number} secondsLeft - seconds left in active loan
123
122
  */
@@ -128,10 +127,19 @@ export default class IABookActions extends LitElement {
128
127
  // clear all intervals for lending system
129
128
  window?.IALendingIntervals?.clearAll();
130
129
 
131
- window?.Sentry?.captureMessage(sentryLogs.disconnectedCallback);
130
+ this.sentryCaptureMsg(sentryLogs.disconnectedCallback);
132
131
  this.disconnectResizeObserver();
133
132
  }
134
133
 
134
+ /**
135
+ * send log messages to sentry
136
+ * @param {string} msg
137
+ */
138
+ sentryCaptureMsg(msg) {
139
+ log(window?.Sentry);
140
+ window?.Sentry?.captureMessage(msg);
141
+ }
142
+
135
143
  firstUpdated() {
136
144
  // bind auto loan renew events
137
145
  this.bindLoanRenewEvents();
@@ -223,7 +231,7 @@ export default class IABookActions extends LitElement {
223
231
  log('setupLendingToolbarActions > hasExpired --- ');
224
232
 
225
233
  if (!this.tokenPoller) {
226
- window?.Sentry?.captureMessage(sentryLogs.bookWasExpired);
234
+ this.sentryCaptureMsg(sentryLogs.bookWasExpired);
227
235
  }
228
236
  window?.IALendingIntervals?.clearAll();
229
237
 
@@ -285,16 +293,36 @@ export default class IABookActions extends LitElement {
285
293
 
286
294
  /**
287
295
  * auto-renew interval may stale when tab is in background,
288
- * so [visibilitychange] event can trigger timer to show relavent messages
296
+ * so when user open that tab again,
297
+ * the [visibilitychange] event can trigger timer to show relavent messages
289
298
  */
290
- document.addEventListener("visibilitychange", async () => {
299
+ document.addEventListener('visibilitychange', async () => {
291
300
  if (!document.hidden) {
292
- log('visibilitychange', new Date().getMinutes, new Date().getSeconds());
293
-
294
- const loanTime = await this.localCache.get(`${this.identifier}-loanTime`);
295
- const secondsLeft = Math.round((loanTime - new Date()) / 1000); // different in seconds
296
-
297
- this.setIntervalExecutionBlock(Number(secondsLeft));
301
+ log(
302
+ 'visibilitychange event execute:------------------ ',
303
+ new Date().getMinutes(),
304
+ new Date().getSeconds(),
305
+ this.borrowType
306
+ );
307
+
308
+ if (
309
+ this.borrowType === 'browsed' &&
310
+ this.lendingStatus.browsingExpired === false
311
+ ) {
312
+ const loanTime = await this.localCache.get(
313
+ `${this.identifier}-loanTime`
314
+ );
315
+
316
+ // number of seconds left in current loan
317
+ const secondsLeft = Math.round((loanTime - new Date()) / 1000);
318
+
319
+ if (secondsLeft >= this.timerExecutionSeconds) {
320
+ this.loanStatusCheckInterval(Number(secondsLeft));
321
+ } else {
322
+ this.browseHasExpired();
323
+ this.disconnectedCallback();
324
+ }
325
+ }
298
326
  }
299
327
  });
300
328
  }
@@ -476,31 +504,6 @@ export default class IABookActions extends LitElement {
476
504
  await this.modal?.showModal({ config, customModalContent });
477
505
  }
478
506
 
479
- /**
480
- * @deprecated
481
- * create / select the toast-template component on DOM
482
- *
483
- * @memberof IABookActions
484
- */
485
- async useToastManager() {
486
- if (!this.toast) {
487
- this.toast = this.shadowRoot.querySelector('toast-template');
488
-
489
- if (!this.toast) this.toast = document.createElement('toast-template');
490
- }
491
-
492
- await this.shadowRoot.appendChild(this.toast);
493
- }
494
-
495
- /**
496
- * @deprecated
497
- * close/hide toast message
498
- */
499
- async closeToastManager() {
500
- this.toast = this.shadowRoot.querySelector('toast-template');
501
- this.toast?.remove();
502
- }
503
-
504
507
  /**
505
508
  * Execute when loan is expired
506
509
  */
@@ -526,7 +529,7 @@ export default class IABookActions extends LitElement {
526
529
 
527
530
  await this.showExpiredModal();
528
531
 
529
- window?.Sentry?.captureMessage(sentryLogs.browseHasExpired);
532
+ this.sentryCaptureMsg(sentryLogs.browseHasExpired);
530
533
  }
531
534
 
532
535
  async startBrowseTimer() {
@@ -629,7 +632,10 @@ export default class IABookActions extends LitElement {
629
632
  if (this.loanRenewResult.renewNow) {
630
633
  // Now, let's reset loan duration & this.lendingStatus
631
634
  const loanTime = await this.localCache.get(`${this.identifier}-loanTime`);
632
- const secondsLeft = Math.round((loanTime - new Date()) / 1000); // different in seconds
635
+
636
+ // number of seconds left in current loan
637
+ const secondsLeft = Math.round((loanTime - new Date()) / 1000);
638
+ log(loanTime, secondsLeft);
633
639
 
634
640
  log('IABookActions: handleLoanAutoRenewed --- ', {
635
641
  ajaxResponse: detail?.data,
@@ -649,7 +655,7 @@ export default class IABookActions extends LitElement {
649
655
  this.modal?.closeModal();
650
656
  this.modal.removeAttribute('id');
651
657
  this.modal.customModalContent = nothing;
652
- window?.Sentry?.captureMessage(sentryLogs.bookHasRenewed);
658
+ this.sentryCaptureMsg(sentryLogs.bookHasRenewed);
653
659
  }
654
660
  }
655
661
 
@@ -658,7 +664,7 @@ export default class IABookActions extends LitElement {
658
664
  *
659
665
  * @memberof IABookActions
660
666
  */
661
- startTimerCountdown() {
667
+ async startTimerCountdown() {
662
668
  window?.IALendingIntervals?.clearTimerCountdown();
663
669
 
664
670
  const secondsLeft = Number(this.lendingStatus.secondsLeftOnLoan);
@@ -666,7 +672,7 @@ export default class IABookActions extends LitElement {
666
672
 
667
673
  window.IALendingIntervals.timerCountdown = setInterval(async () => {
668
674
  // interval execution block
669
- await this.setIntervalExecutionBlock(secondsLeft);
675
+ await this.loanStatusCheckInterval(secondsLeft);
670
676
  }, this.timerExecutionSeconds * 1000);
671
677
  }
672
678
 
@@ -675,10 +681,10 @@ export default class IABookActions extends LitElement {
675
681
  * - if setInterval timer gone off, let's resync
676
682
  * - loan-renew attempt to renew the loan OR show relavant message
677
683
  * - if loan has expired, clear interval timers
678
- *
679
- * @param {Number} secondsLeftOnLoan
684
+ *
685
+ * @param {Number} secondsLeftOnLoan
680
686
  */
681
- async setIntervalExecutionBlock(secondsLeftOnLoan) {
687
+ async loanStatusCheckInterval(secondsLeftOnLoan) {
682
688
  let secondsLeft = secondsLeftOnLoan;
683
689
  secondsLeft -= this.timerExecutionSeconds;
684
690
  secondsLeft = Math.round(secondsLeft); // round number
@@ -688,22 +694,30 @@ export default class IABookActions extends LitElement {
688
694
  // if really updated, escape from here
689
695
  const resyncd = await this.reSyncTimerIfGoneOff(secondsLeft);
690
696
 
691
- if (resyncd?.hasSynced) {
697
+ if (resyncd.hasSynced) {
692
698
  secondsLeft = resyncd.whatShouldLeft;
693
-
694
699
  log('startTimerCountdown --- stale, timer has resyncd', {
695
700
  secondsLeft,
696
701
  });
697
702
  }
698
703
 
699
- log('startTimerCountdown --- countdown still valid. continue...', {
700
- resyncd,
701
- secondsLeft,
702
- loanRenewAtLast: this.loanRenewTimeConfig.loanRenewAtLast,
703
- }, new Date().getMinutes(), new Date().getSeconds());
704
+ log(
705
+ 'startTimerCountdown --- countdown still valid. continue...',
706
+ {
707
+ resyncd,
708
+ secondsLeft,
709
+ loanRenewAtLast: this.loanRenewTimeConfig.loanRenewAtLast,
710
+ },
711
+ 'time: ',
712
+ new Date().getMinutes(),
713
+ ':',
714
+ new Date().getSeconds(),
715
+ ', timerDelay: ',
716
+ this.timerExecutionSeconds
717
+ );
704
718
 
705
719
  /**
706
- * execute from last 10th minute to 0th minute
720
+ * execute from last 10th minutes to 0th minute
707
721
  * - 10th - to check if user has viewed
708
722
  * - till 0th - to show warning msg with remaining time to auto expired
709
723
  * @see IABookActions::bindLoanRenewEvents
@@ -715,7 +729,7 @@ export default class IABookActions extends LitElement {
715
729
  // clear interval in secondsLeft if less
716
730
  if (secondsLeft <= this.timerExecutionSeconds) {
717
731
  this.disconnectedCallback();
718
- window?.Sentry?.captureMessage(sentryLogs.clearOneHourTimer);
732
+ this.sentryCaptureMsg(sentryLogs.clearOneHourTimer);
719
733
  }
720
734
  }
721
735
 
@@ -723,8 +737,7 @@ export default class IABookActions extends LitElement {
723
737
  * helper function to determine if timer is not in sync properly
724
738
  *
725
739
  * @param {number} timerSecondsLeft - actual seconds left get from setInterval
726
- *
727
- * @memberof TimerCountdown
740
+ * @returns {Object} { hasSynced: [boolean], whatShouldLeft: [number] }
728
741
  */
729
742
  async reSyncTimerIfGoneOff(timerSecondsLeft) {
730
743
  const currentTime = new Date();
@@ -736,6 +749,15 @@ export default class IABookActions extends LitElement {
736
749
  const secondsShouldLeft =
737
750
  this.lendingStatus.secondsLeftOnLoan - diffInSeconds;
738
751
 
752
+ log('currentTime: ', currentTime);
753
+ log('timeWhenTimerStart: ', this.timeWhenTimerStart);
754
+ log('diffInSeconds: ', diffInSeconds);
755
+ log('secondsShouldLeft: ', secondsShouldLeft);
756
+ log(
757
+ 'this.lendingStatus.secondsLeftOnLoan: ',
758
+ this.lendingStatus.secondsLeftOnLoan
759
+ );
760
+
739
761
  // convert in minutes
740
762
  const whatIsleft = Math.round(timerSecondsLeft);
741
763
  const whatShouldLeft = Math.round(secondsShouldLeft);
@@ -759,10 +781,10 @@ export default class IABookActions extends LitElement {
759
781
 
760
782
  if (whatIsleft !== whatShouldLeft) {
761
783
  log(`reSyncTimerIfGoneOff --- let's re-sync.`);
762
-
763
784
  return { hasSynced: true, whatShouldLeft };
764
785
  }
765
- return false;
786
+
787
+ return { hasSynced: false, whatShouldLeft };
766
788
  }
767
789
 
768
790
  /**
@@ -981,12 +1003,6 @@ export default class IABookActions extends LitElement {
981
1003
  justify-content: center;
982
1004
  flex-wrap: wrap;
983
1005
  }
984
-
985
- toast-template {
986
- --toastTopMargin: 80px;
987
- --toastBGColor: #333;
988
- --toastFontColor: #fff;
989
- }
990
1006
  `;
991
1007
  }
992
1008
  }
@@ -13,7 +13,9 @@ const container = ({
13
13
  .loanRenewAtLast=${loanRenewAtLast}
14
14
  ></timer-countdown>`;
15
15
 
16
- describe('<timer-countdown>', () => {
16
+ describe('<timer-countdown>', async () => {
17
+ await aTimeout(5000);
18
+
17
19
  it('timer interval is undefined when loan is expired', async () => {
18
20
  const el = await fixture(
19
21
  container({
@@ -36,7 +38,6 @@ describe('<timer-countdown>', () => {
36
38
  })
37
39
  );
38
40
 
39
- await aTimeout(1900);
40
41
  await el.updateComplete;
41
42
 
42
43
  expect(el.secondsLeftOnLoan).to.equal(2);
@@ -376,7 +376,8 @@ describe('Browsing expired status', () => {
376
376
  });
377
377
  });
378
378
 
379
- describe('Auto renew one hour loan', () => {
379
+ describe('Auto renew one hour loan', async () => {
380
+ await aTimeout(5000);
380
381
  it('Book is browsing and renewed it now', async () => {
381
382
  const loanTime = 88;
382
383
  const el = await fixture(
@@ -449,6 +450,61 @@ describe('Auto renew one hour loan', () => {
449
450
  });
450
451
  });
451
452
 
453
+ describe('Visibility change API for document', async () => {
454
+ await aTimeout(5000);
455
+
456
+ beforeEach(() => {
457
+ Object.defineProperty(document, 'hidden', {
458
+ value: false,
459
+ configurable: true,
460
+ });
461
+ });
462
+
463
+ afterEach(() => {
464
+ Object.defineProperty(document, 'hidden', {
465
+ value: true,
466
+ configurable: true,
467
+ });
468
+ });
469
+
470
+ it('when book is not expired', async () => {
471
+ const el = await fixture(
472
+ container({
473
+ userid: '@user1',
474
+ identifier: 'foobar',
475
+ lendingStatus: {
476
+ user_has_browsed: true,
477
+ browsingExpired: false,
478
+ },
479
+ })
480
+ );
481
+ await el.updateComplete;
482
+
483
+ const spy = Sinon.spy(el, 'loanStatusCheckInterval');
484
+ document.dispatchEvent(new Event('visibilitychange'));
485
+
486
+ expect(spy.calledOnce).to.be.true;
487
+ });
488
+
489
+ it('when book is already expired', async () => {
490
+ const el = await fixture(
491
+ container({
492
+ userid: '@user1',
493
+ identifier: 'foobar',
494
+ lendingStatus: {
495
+ user_has_browsed: true,
496
+ },
497
+ })
498
+ );
499
+ await el.updateComplete;
500
+
501
+ const spy = Sinon.spy(el, 'browseHasExpired');
502
+ document.dispatchEvent(new Event('visibilitychange'));
503
+
504
+ expect(spy.calledOnce).to.be.true;
505
+ });
506
+ });
507
+
452
508
  describe('Shared Resize Observer', () => {
453
509
  it('can receive a Shared Resize Observer', async () => {
454
510
  const sharedObserverStub = new SharedResizeObserver();
package/vite.config.ts ADDED
@@ -0,0 +1,22 @@
1
+ import { defineConfig } from 'vite';
2
+ import { resolve } from 'path';
3
+
4
+ // https://vitejs.dev/config/
5
+ export default defineConfig({
6
+ base: '',
7
+ build: {
8
+ outDir: 'ghpages',
9
+ manifest: true,
10
+ rollupOptions: {
11
+ input: {
12
+ main: resolve(__dirname, 'index.html'),
13
+ },
14
+ },
15
+ },
16
+ server: {
17
+ host: true,
18
+ port: 8080,
19
+ open: true,
20
+ cors: true,
21
+ },
22
+ });
@@ -5,7 +5,7 @@ const hmr = process.argv.includes('--hmr');
5
5
 
6
6
  export default /** @type {import('@web/dev-server').DevServerConfig} */ ({
7
7
  nodeResolve: true,
8
- open: '/demo/',
8
+ open: '/',
9
9
  watch: !hmr,
10
10
 
11
11
  /** Compile JS for older browsers. Requires @web/dev-server-esbuild plugin */