@internetarchive/ia-book-actions 1.4.3 → 1.4.4-alpha10
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.
- package/index.html +416 -118
- package/package.json +2 -2
- package/src/assets/data/info.js +12 -0
- package/src/core/services/actions-handler/actions-handler-service.js +5 -1
- package/src/core/services/actions-handler/actions-handler.js +25 -19
- package/src/core/services/loan-analytics.js +1 -1
- package/src/core/services/loan-renew-helper.js +2 -3
- package/src/core/services/loan-token-poller.js +63 -16
- package/src/ia-book-actions.js +337 -247
- package/test/core/services/actions-handler.test.js +110 -1
- package/test/core/services/loan-renew-helper.test.js +1 -1
- package/test/core/services/loan-token-poller.test.js +76 -0
- package/test/ia-book-actions.test.js +716 -7
package/index.html
CHANGED
|
@@ -38,11 +38,13 @@
|
|
|
38
38
|
|
|
39
39
|
fieldset {
|
|
40
40
|
width: fit-content;
|
|
41
|
+
max-width: 100%;
|
|
42
|
+
box-sizing: border-box;
|
|
41
43
|
margin-top: 50px;
|
|
42
44
|
}
|
|
43
45
|
fieldset legend {
|
|
44
46
|
text-align: left;
|
|
45
|
-
font-size:
|
|
47
|
+
font-size: 14px;
|
|
46
48
|
}
|
|
47
49
|
td {
|
|
48
50
|
color: white;
|
|
@@ -52,7 +54,7 @@
|
|
|
52
54
|
text-align: initial;
|
|
53
55
|
margin: 5px 0;
|
|
54
56
|
width: max-content;
|
|
55
|
-
display:
|
|
57
|
+
display: inline;
|
|
56
58
|
}
|
|
57
59
|
label {
|
|
58
60
|
padding: 10px;
|
|
@@ -83,9 +85,107 @@
|
|
|
83
85
|
border-radius: 10px;
|
|
84
86
|
}
|
|
85
87
|
|
|
88
|
+
.timer-presets {
|
|
89
|
+
display: flex;
|
|
90
|
+
gap: 6px;
|
|
91
|
+
flex-wrap: wrap;
|
|
92
|
+
max-width: 100%;
|
|
93
|
+
}
|
|
94
|
+
.timer-presets button {
|
|
95
|
+
padding: 6px 10px;
|
|
96
|
+
cursor: pointer;
|
|
97
|
+
border: 2px solid #194880;
|
|
98
|
+
border-radius: 10px;
|
|
99
|
+
background: white;
|
|
100
|
+
height: 50px;
|
|
101
|
+
}
|
|
102
|
+
.timer-presets button.active-preset {
|
|
103
|
+
background: #194880;
|
|
104
|
+
color: white;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
#live-status-wrapper {
|
|
108
|
+
display: flex;
|
|
109
|
+
align-items: center;
|
|
110
|
+
gap: 10px;
|
|
111
|
+
flex-wrap: wrap;
|
|
112
|
+
/* width: min(400px, 100%); */
|
|
113
|
+
|
|
114
|
+
/* display: flex; gap: 3rem; margin: 0 3rem; flex-wrap: wrap; */
|
|
115
|
+
}
|
|
116
|
+
#loan-status-row,
|
|
117
|
+
#lending-bar-controls {
|
|
118
|
+
display: grid;
|
|
119
|
+
grid-template-columns: repeat(2, 1fr);
|
|
120
|
+
gap: 3rem;
|
|
121
|
+
margin: 0 3rem;
|
|
122
|
+
}
|
|
123
|
+
@media (max-width: 700px) {
|
|
124
|
+
#loan-status-row,
|
|
125
|
+
#lending-bar-controls {
|
|
126
|
+
grid-template-columns: 1fr;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
.loan-status-badge {
|
|
130
|
+
display: inline-block;
|
|
131
|
+
padding: 8px 14px;
|
|
132
|
+
border-radius: 6px;
|
|
133
|
+
font-size: 14px;
|
|
134
|
+
font-weight: bold;
|
|
135
|
+
letter-spacing: 0.5px;
|
|
136
|
+
color: white;
|
|
137
|
+
}
|
|
138
|
+
.loan-status-badge.status-active {
|
|
139
|
+
background: #2e7d32; /* green: loan is currently checked out */
|
|
140
|
+
}
|
|
141
|
+
.loan-status-badge.status-renewing {
|
|
142
|
+
background: #b8860b; /* amber: a renew_loan attempt is in flight */
|
|
143
|
+
}
|
|
144
|
+
.loan-status-badge.status-returned {
|
|
145
|
+
background: #d9534f; /* red: auto-returned / not on loan */
|
|
146
|
+
}
|
|
147
|
+
.loan-status-badge.status-none {
|
|
148
|
+
background: #666; /* gray: nothing borrowed yet */
|
|
149
|
+
}
|
|
150
|
+
#live-status-detail {
|
|
151
|
+
color: white;
|
|
152
|
+
background: #333;
|
|
153
|
+
padding: 8px 12px;
|
|
154
|
+
border-radius: 6px;
|
|
155
|
+
font-size: 12px;
|
|
156
|
+
text-align: left;
|
|
157
|
+
flex: 1;
|
|
158
|
+
}
|
|
159
|
+
#exec-log {
|
|
160
|
+
text-align: left;
|
|
161
|
+
background: #111;
|
|
162
|
+
color: #9fef9f;
|
|
163
|
+
font-family: monospace;
|
|
164
|
+
font-size: 11px;
|
|
165
|
+
max-height: 160px;
|
|
166
|
+
overflow-y: auto;
|
|
167
|
+
padding: 8px;
|
|
168
|
+
border-radius: 6px;
|
|
169
|
+
margin-top: 8px;
|
|
170
|
+
white-space: pre-wrap;
|
|
171
|
+
word-break: break-word;
|
|
172
|
+
}
|
|
173
|
+
#exec-log div + div {
|
|
174
|
+
border-top: 1px solid #333;
|
|
175
|
+
padding-top: 2px;
|
|
176
|
+
margin-top: 2px;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
#auto-renew-controls {
|
|
180
|
+
display: none1;
|
|
181
|
+
}
|
|
86
182
|
body.modal-manager-open {
|
|
87
183
|
overflow: hidden;
|
|
88
184
|
}
|
|
185
|
+
|
|
186
|
+
#action-bar-modal {
|
|
187
|
+
--modalWidth: 35rem;
|
|
188
|
+
}
|
|
89
189
|
|
|
90
190
|
modal-manager[mode='closed'] {
|
|
91
191
|
display: none;
|
|
@@ -96,6 +196,44 @@
|
|
|
96
196
|
<body>
|
|
97
197
|
<ia-book-actions></ia-book-actions>
|
|
98
198
|
|
|
199
|
+
<script>
|
|
200
|
+
/* eslint-disable */
|
|
201
|
+
// WEBDEV-8322: capture the library's `log()` lines — API calls
|
|
202
|
+
// (create_token/browse_book/borrow_book/renew_loan/return_loan and their
|
|
203
|
+
// success/error from ActionsHandlerService), lifecycle events
|
|
204
|
+
// ([IABookActions]: page-turn/BookReader:userAction received,
|
|
205
|
+
// visibilitychange backgrounded/foregrounded, browseHasExpired,
|
|
206
|
+
// showWarningModal, handleLoanAutoRenewed, etc), and anything the demo
|
|
207
|
+
// itself triggers ([Demo]: user_has_browsed set, error-mode toggled,
|
|
208
|
+
// etc) — into the live status panel below, so QA can see everything
|
|
209
|
+
// that happened without opening devtools. GA event-mirroring noise
|
|
210
|
+
// (`eventCategory:- ...`) is the one thing still filtered out.
|
|
211
|
+
//
|
|
212
|
+
// This MUST run as a plain (non-module) script BEFORE the `type="module"`
|
|
213
|
+
// script's imports execute: `log.js` binds its own reference to
|
|
214
|
+
// `console.log` the moment it's imported, so overriding console.log any
|
|
215
|
+
// later (even earlier in the module script's own source) would miss it —
|
|
216
|
+
// ES module imports always run before the importing module's own
|
|
217
|
+
// top-level code, regardless of source order.
|
|
218
|
+
window.__execLog = [];
|
|
219
|
+
const __nativeConsoleLog = console.log.bind(console);
|
|
220
|
+
console.log = (...consoleArgs) => {
|
|
221
|
+
__nativeConsoleLog(...consoleArgs);
|
|
222
|
+
|
|
223
|
+
const loggedPrefixes = ['[IABookActions]', '[IABookActions]', '[Demo]'];
|
|
224
|
+
if (typeof consoleArgs[0] !== 'string' || !loggedPrefixes.some((prefix) => consoleArgs[0].startsWith(prefix))) {
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const time = new Date().toISOString().split('T')[1].replace('Z', '');
|
|
229
|
+
const text = `[${time}] ` + consoleArgs
|
|
230
|
+
.map((arg) => (typeof arg === 'object' ? JSON.stringify(arg) : String(arg)))
|
|
231
|
+
.join(' ');
|
|
232
|
+
window.__execLog.push(text);
|
|
233
|
+
if (window.__renderExecLogEntry) window.__renderExecLogEntry(text);
|
|
234
|
+
};
|
|
235
|
+
</script>
|
|
236
|
+
|
|
99
237
|
<script type="module">
|
|
100
238
|
/* eslint-disable */
|
|
101
239
|
|
|
@@ -139,31 +277,32 @@
|
|
|
139
277
|
iaBookActions.returnUrl = '';
|
|
140
278
|
iaBookActions.localCache = localCache;
|
|
141
279
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
280
|
+
// Loan-duration presets for QA — pick a short one (15s/30s) to run through
|
|
281
|
+
// the full warn -> auto-return -> silent-reborrow lifecycle quickly, or a
|
|
282
|
+
// longer one (60min matches the real 1-hour production default) to check
|
|
283
|
+
// the timing math. `?timer=` accepts any key below (e.g. ?timer=15s).
|
|
284
|
+
const timerPresets = {
|
|
285
|
+
'15s': { loanTotalTime: 15, loanRenewAtLast: 13, pageChangedInLast: 5 },
|
|
286
|
+
'30s': { loanTotalTime: 30, loanRenewAtLast: 27, pageChangedInLast: 10 },
|
|
287
|
+
'2': { loanTotalTime: 120, loanRenewAtLast: 95, pageChangedInLast: 15 },
|
|
288
|
+
'5': { loanTotalTime: 300, loanRenewAtLast: 240, pageChangedInLast: 60 },
|
|
289
|
+
'10': { loanTotalTime: 600, loanRenewAtLast: 480, pageChangedInLast: 120 },
|
|
290
|
+
'30': { loanTotalTime: 1800, loanRenewAtLast: 1500, pageChangedInLast: 600 },
|
|
291
|
+
'60': { loanTotalTime: 3600, loanRenewAtLast: 660, pageChangedInLast: 900 }, // production default
|
|
292
|
+
};
|
|
147
293
|
|
|
148
|
-
iaBookActions.loanRenewTimeConfig = loanTotalTime2Min;
|
|
149
294
|
let params = new URLSearchParams(document.location.search);
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
iaBookActions.loanRenewTimeConfig = loanTotalTime1Hour;
|
|
162
|
-
break;
|
|
163
|
-
default:
|
|
164
|
-
iaBookActions.loanRenewTimeConfig = loanTotalTime2Min;
|
|
165
|
-
break;
|
|
166
|
-
}
|
|
295
|
+
const activeTimerKey = timerPresets[params.get('timer')] ? params.get('timer') : '2';
|
|
296
|
+
iaBookActions.loanRenewTimeConfig = timerPresets[activeTimerKey];
|
|
297
|
+
|
|
298
|
+
document.querySelectorAll('.timer-presets button').forEach((button) => {
|
|
299
|
+
if (button.dataset.timer === activeTimerKey) button.classList.add('active-preset');
|
|
300
|
+
button.addEventListener('click', () => {
|
|
301
|
+
const searchParams = new URLSearchParams(window.location.search);
|
|
302
|
+
searchParams.set('timer', button.dataset.timer);
|
|
303
|
+
window.location.search = searchParams.toString();
|
|
304
|
+
});
|
|
305
|
+
});
|
|
167
306
|
|
|
168
307
|
let lendingStatus1 = iaBookActions.lendingStatus;
|
|
169
308
|
let secondsLeftOnLoan = iaBookActions.loanRenewTimeConfig.loanTotalTime;
|
|
@@ -202,6 +341,8 @@
|
|
|
202
341
|
item.checked = true;
|
|
203
342
|
}
|
|
204
343
|
item.addEventListener('click', (e) => {
|
|
344
|
+
console.log(`[Demo] error-response mode ${e.target.checked ? 'enabled' : 'disabled'}`);
|
|
345
|
+
|
|
205
346
|
var searchParams = new URLSearchParams(window.location.search);
|
|
206
347
|
var separate = '';
|
|
207
348
|
if (e.target.checked) {
|
|
@@ -251,6 +392,8 @@
|
|
|
251
392
|
|
|
252
393
|
document.querySelectorAll('.availableToBrowse input[type=radio]').forEach((item) => {
|
|
253
394
|
item.addEventListener('click', async (e) => {
|
|
395
|
+
console.log(`[Demo] "${e.target.value}" selected under availableToBrowse`);
|
|
396
|
+
|
|
254
397
|
if (e.target.value === 'user_has_browsed') {
|
|
255
398
|
lendingStatus1.user_has_browsed = true;
|
|
256
399
|
lendingStatus1.available_to_browse = false;
|
|
@@ -289,32 +432,48 @@
|
|
|
289
432
|
});
|
|
290
433
|
});
|
|
291
434
|
|
|
435
|
+
// WEBDEV-8322 scenario 1: informational-only warning modal.
|
|
436
|
+
// "Okay" (or closing it) must NOT renew the loan — only interacting
|
|
437
|
+
// with the book (scenario 3 below) does that.
|
|
292
438
|
document.querySelector('#show_warning_modal').addEventListener('click', async () => {
|
|
439
|
+
console.log('[Demo] scenario 1 button clicked: show warning modal');
|
|
293
440
|
if (!iaBookActions.lendingStatus.user_has_browsed) {
|
|
294
441
|
const newLendingStatus = { ...iaBookActions.lendingStatus, user_has_browsed: true };
|
|
295
442
|
iaBookActions.lendingStatus = newLendingStatus;
|
|
296
443
|
await iaBookActions.updateComplete;
|
|
297
444
|
}
|
|
298
445
|
|
|
446
|
+
// showWarningModal() reads its message text from
|
|
447
|
+
// loanRenewHelper.getMessageTexts(loanRenewResult.texts, ...) — that
|
|
448
|
+
// helper only exists, and loanRenewResult.texts only gets populated,
|
|
449
|
+
// once autoLoanRenewChecker() has run at least once (this is what the
|
|
450
|
+
// real periodic timer calls before ever showing the modal). Run it
|
|
451
|
+
// here too so the button shows the same populated message a real
|
|
452
|
+
// warning would.
|
|
453
|
+
await iaBookActions.autoLoanRenewChecker(false);
|
|
299
454
|
iaBookActions.showWarningModal();
|
|
300
|
-
|
|
301
|
-
if (iaBookActions.lendingStatus.user_has_browsed) {
|
|
302
|
-
iaBookActions.showWarningModal();
|
|
303
|
-
}
|
|
304
455
|
});
|
|
305
456
|
|
|
457
|
+
// WEBDEV-8322 scenario 2: loan auto-returned due to inactivity.
|
|
458
|
+
// No modal should appear and the page should NOT refresh — the reader
|
|
459
|
+
// just silently sits on whatever page it was on.
|
|
306
460
|
document.querySelector('#show_expired_modal').addEventListener('click', async () => {
|
|
461
|
+
console.log('[Demo] scenario 2 button clicked: force auto-return');
|
|
307
462
|
if (!iaBookActions.lendingStatus.user_has_browsed) {
|
|
308
463
|
const newLendingStatus = { ...iaBookActions.lendingStatus, user_has_browsed: true };
|
|
309
464
|
iaBookActions.lendingStatus = newLendingStatus;
|
|
310
465
|
await iaBookActions.updateComplete;
|
|
311
466
|
}
|
|
312
|
-
await new Promise(resolve => setTimeout(resolve,
|
|
467
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
313
468
|
|
|
314
469
|
iaBookActions.browseHasExpired();
|
|
315
470
|
});
|
|
316
471
|
|
|
472
|
+
// WEBDEV-8322 scenario 4: patron returns to the tab after time away.
|
|
473
|
+
// If the loan already expired while hidden, this silently attempts to
|
|
474
|
+
// re-borrow (same success/failure paths as scenario 3).
|
|
317
475
|
document.querySelector('#resync_timer').addEventListener('click', async (e) => {
|
|
476
|
+
console.log('[Demo] scenario 4 button clicked: simulate visibilitychange (tab return)');
|
|
318
477
|
if (iaBookActions.lendingStatus.user_has_browsed) {
|
|
319
478
|
document.querySelector('ia-book-actions').dispatchEvent(
|
|
320
479
|
new Event('visibilitychange', {
|
|
@@ -324,7 +483,7 @@
|
|
|
324
483
|
})
|
|
325
484
|
);
|
|
326
485
|
}
|
|
327
|
-
await new Promise(resolve => setTimeout(resolve,
|
|
486
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
328
487
|
});
|
|
329
488
|
|
|
330
489
|
document.querySelectorAll('.availableToBorrow input[type=radio]').forEach((item) => {
|
|
@@ -373,101 +532,240 @@
|
|
|
373
532
|
})
|
|
374
533
|
});
|
|
375
534
|
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
535
|
+
// WEBDEV-8322 scenario 3: patron interacts with the book (turns a page,
|
|
536
|
+
// etc). If the loan already expired (run scenario 2 first), this
|
|
537
|
+
// silently retries the loan — enable the error checkbox above beforehand
|
|
538
|
+
// to simulate someone else having already borrowed the book, which
|
|
539
|
+
// should surface the "Due to inactivity..." modal instead.
|
|
540
|
+
document.querySelectorAll('.pageChangedEvent').forEach((button) => {
|
|
541
|
+
button.addEventListener('click', () => {
|
|
542
|
+
console.log('[Demo] page-change / BookReader:userAction dispatched');
|
|
543
|
+
document.querySelector('ia-book-actions').dispatchEvent(
|
|
544
|
+
new CustomEvent('BookReader:userAction', {
|
|
545
|
+
detail: {},
|
|
546
|
+
bubbles: true,
|
|
547
|
+
composed: true,
|
|
548
|
+
})
|
|
549
|
+
);
|
|
550
|
+
});
|
|
551
|
+
});
|
|
386
552
|
|
|
387
553
|
// listen for IABookReader:BrowsingHasExpired
|
|
388
554
|
window.addEventListener('IABookReader:BrowsingHasExpired', () => {
|
|
389
555
|
console.log('IABookReader:BrowsingHasExpired EVENT FIRED');
|
|
390
556
|
});
|
|
557
|
+
|
|
558
|
+
// WEBDEV-8322 live status panel — the action bar alone doesn't always
|
|
559
|
+
// visibly change (e.g. right after auto-return), so mirror what the
|
|
560
|
+
// library's own log() calls already say is happening (create_token,
|
|
561
|
+
// renew_loan, browse_book, return_loan, and their success/error — see
|
|
562
|
+
// ActionsHandlerService) into an on-page log QA can read without
|
|
563
|
+
// opening devtools. The console.log interception itself lives in the
|
|
564
|
+
// plain <script> above this module script — see the comment there for
|
|
565
|
+
// why it can't live here.
|
|
566
|
+
const execLogEl = document.querySelector('#exec-log');
|
|
567
|
+
const loanStatusBadgeEl = document.querySelector('#loan-status-badge');
|
|
568
|
+
const liveStatusDetailEl = document.querySelector('#live-status-detail');
|
|
569
|
+
|
|
570
|
+
function renderExecLogEntry(text) {
|
|
571
|
+
const line = document.createElement('div');
|
|
572
|
+
line.textContent = text;
|
|
573
|
+
execLogEl.appendChild(line);
|
|
574
|
+
execLogEl.scrollTop = execLogEl.scrollHeight;
|
|
575
|
+
}
|
|
576
|
+
window.__renderExecLogEntry = renderExecLogEntry;
|
|
577
|
+
// flush anything logged before this module script finished loading
|
|
578
|
+
window.__execLog.forEach(renderExecLogEntry);
|
|
579
|
+
|
|
580
|
+
document.querySelector('#clear-exec-log').addEventListener('click', () => {
|
|
581
|
+
execLogEl.textContent = '';
|
|
582
|
+
window.__execLog = [];
|
|
583
|
+
});
|
|
584
|
+
|
|
585
|
+
// The loan-status badge answers one question only: is the loan ACTIVE
|
|
586
|
+
// or has it been RETURNED? `browsingExpired` is the exact flag the
|
|
587
|
+
// library flips the instant auto-return executes (browseHasExpired()),
|
|
588
|
+
// so the badge changes in the same tick — even though the action bar
|
|
589
|
+
// itself doesn't show a modal or refresh at that moment.
|
|
590
|
+
function computeLoanStatus() {
|
|
591
|
+
const lendingStatus = iaBookActions.lendingStatus || {};
|
|
592
|
+
|
|
593
|
+
// Checked first: autoRenewExpiredLoan() optimistically flips
|
|
594
|
+
// browsingExpired back to false the moment a retry starts (before the
|
|
595
|
+
// renew_loan call has actually landed), so without this check the
|
|
596
|
+
// badge would misleadingly jump straight to ACTIVE during the retry.
|
|
597
|
+
if (iaBookActions.loanRenewInProgress) {
|
|
598
|
+
return { badge: 'RENEWING', className: 'status-renewing' };
|
|
599
|
+
}
|
|
600
|
+
if (lendingStatus.browsingExpired) {
|
|
601
|
+
return { badge: 'RETURNED', className: 'status-returned' };
|
|
602
|
+
}
|
|
603
|
+
if (lendingStatus.user_has_browsed) {
|
|
604
|
+
return { badge: 'ACTIVE', className: 'status-active' };
|
|
605
|
+
}
|
|
606
|
+
return { badge: 'NO LOAN', className: 'status-none' };
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
function computeLiveStatusDetail() {
|
|
610
|
+
const lendingStatus = iaBookActions.lendingStatus || {};
|
|
611
|
+
|
|
612
|
+
if (iaBookActions.loanRenewInProgress) {
|
|
613
|
+
return '🔄 Attempting silent re-borrow (create_token → renew_loan)…';
|
|
614
|
+
}
|
|
615
|
+
if (iaBookActions.warningModalOpen) {
|
|
616
|
+
return '⚠️ Warning modal open — closing it will NOT renew the loan';
|
|
617
|
+
}
|
|
618
|
+
if (lendingStatus.browsingExpired) {
|
|
619
|
+
return 'Auto-returned due to inactivity. By design: no modal, no refresh — only this badge and the action bar (Borrow) reflect it, until the patron interacts again.';
|
|
620
|
+
}
|
|
621
|
+
if (lendingStatus.user_has_browsed) {
|
|
622
|
+
const secondsLeft = Math.round(Number(lendingStatus.secondsLeftOnLoan || 0));
|
|
623
|
+
return `~${secondsLeft}s left on this loan`;
|
|
624
|
+
}
|
|
625
|
+
return 'Not currently browsing this book';
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
setInterval(() => {
|
|
629
|
+
const { badge, className } = computeLoanStatus();
|
|
630
|
+
loanStatusBadgeEl.textContent = badge;
|
|
631
|
+
loanStatusBadgeEl.className = `loan-status-badge ${className}`;
|
|
632
|
+
liveStatusDetailEl.textContent = computeLiveStatusDetail();
|
|
633
|
+
}, 250);
|
|
391
634
|
</script>
|
|
392
635
|
|
|
393
636
|
<div>
|
|
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
|
-
|
|
637
|
+
<div id="loan-status-row">
|
|
638
|
+
<fieldset>
|
|
639
|
+
<legend>WEBDEV-8322: loan duration for testing (reloads page)</legend>
|
|
640
|
+
<div class="timer-presets">
|
|
641
|
+
<button data-timer="15s">15 sec</button>
|
|
642
|
+
<button data-timer="30s">30 sec</button>
|
|
643
|
+
<button data-timer="2">2 min (default)</button>
|
|
644
|
+
<button data-timer="5">5 min</button>
|
|
645
|
+
<button data-timer="10">10 min</button>
|
|
646
|
+
<button data-timer="30">30 min</button>
|
|
647
|
+
<button data-timer="60">60 min (production default)</button>
|
|
648
|
+
</div>
|
|
649
|
+
<p style="color: #666; font-size: 11px; margin: 4px 0 0;">
|
|
650
|
+
Pick 15s/30s to watch the whole warn → auto-return → silent-reborrow
|
|
651
|
+
lifecycle play out on its own in under a minute, without clicking the scenario
|
|
652
|
+
buttons below.
|
|
653
|
+
</p>
|
|
654
|
+
</fieldset>
|
|
655
|
+
|
|
656
|
+
<fieldset>
|
|
657
|
+
<legend>WEBDEV-8322: live loan status (the action bar alone won't always make this obvious)</legend>
|
|
658
|
+
<div id="live-status-wrapper">
|
|
659
|
+
<span id="loan-status-badge" class="loan-status-badge status-none">NO LOAN</span>
|
|
660
|
+
<div id="live-status-detail">Not currently browsing this book</div>
|
|
661
|
+
</div>
|
|
662
|
+
<div id="exec-log"></div>
|
|
663
|
+
<p style="color: #666; font-size: 11px; margin: 4px 0 0; text-align: left;">
|
|
664
|
+
The badge flips to RETURNED the instant auto-return fires (scenario 2) — even though
|
|
665
|
+
no modal appears and the page doesn't refresh, per the ticket. The log below mirrors
|
|
666
|
+
every create_token / browse_book / renew_loan / return_loan call and its success/error
|
|
667
|
+
result as it happens.
|
|
668
|
+
</p>
|
|
669
|
+
<button id="clear-exec-log" style="margin-top: 6px; padding: 4px 8px; cursor: pointer;">Clear log</button>
|
|
670
|
+
</fieldset>
|
|
671
|
+
</div>
|
|
672
|
+
<div id="auto-renew-controls">
|
|
673
|
+
<fieldset style="margin-top: 10px;">
|
|
674
|
+
<legend style="color: black;">WEBDEV-8322: loan auto-renew QA scenarios (requires `user_has_browsed`)</legend>
|
|
675
|
+
<table aria-hidden="true">
|
|
676
|
+
<tbody><tr>
|
|
677
|
+
<td class="action-button">
|
|
678
|
+
<button id="show_warning_modal">1. Show "Are you still there?" modal<br>(Okay does NOT renew the loan)</button>
|
|
679
|
+
</td>
|
|
680
|
+
<td class="action-button">
|
|
681
|
+
<button id="show_expired_modal">2. Auto-return due to inactivity<br>(no modal, page stays put)</button>
|
|
682
|
+
</td>
|
|
683
|
+
<td class="action-button">
|
|
684
|
+
<button class="pageChangedEvent">
|
|
685
|
+
3. Interact with book (turn page)<br>
|
|
686
|
+
silently renew while active or after auto-return
|
|
687
|
+
</button>
|
|
688
|
+
</td>
|
|
689
|
+
<td class="action-button">
|
|
690
|
+
<button id="resync_timer">4. Return to tab after time away<br>(visibilitychange — silent retry if expired)</button>
|
|
691
|
+
</td>
|
|
692
|
+
</tr>
|
|
693
|
+
</tbody></table>
|
|
694
|
+
<p style="color: #666; font-size: 11px; margin: 8px 0 0;">
|
|
695
|
+
Try 2 → 3 (or 2 → 4) in sequence to walk through the full "auto-returned, then patron
|
|
696
|
+
keeps reading" flow the ticket describes.
|
|
697
|
+
</p>
|
|
698
|
+
</fieldset>
|
|
699
|
+
</div>
|
|
700
|
+
|
|
701
|
+
|
|
702
|
+
<div id="lending-bar-controls">
|
|
703
|
+
<fieldset>
|
|
704
|
+
<legend>Dev testing for book title bar</legend>
|
|
705
|
+
<table class="initial">
|
|
706
|
+
<tr>
|
|
707
|
+
<td class="titleBar">
|
|
708
|
+
<label><input type="checkbox" name="title" value="title" />Title Bar</label>
|
|
709
|
+
</td>
|
|
710
|
+
<td class="searchParam">
|
|
711
|
+
<label><input type="checkbox" name="search" value="Search Param" />Search Param</label>
|
|
712
|
+
</td>
|
|
713
|
+
</tr>
|
|
714
|
+
</table>
|
|
715
|
+
</fieldset>
|
|
716
|
+
|
|
717
|
+
<fieldset>
|
|
718
|
+
<legend>Dev testing for book action bar</legend>
|
|
719
|
+
<table class="initial">
|
|
720
|
+
<tr colspan='5'>
|
|
721
|
+
<td class="errorEnable">
|
|
722
|
+
<label><input type="checkbox" name="errorEnable" value="errorEnable" />Do you want to see error response?</label>
|
|
723
|
+
<span style="color: #666; font-size: 11px; display: block; padding: 0 10px 10px;">
|
|
724
|
+
Tip: turn this on before scenarios 3/4 below to simulate someone else having
|
|
725
|
+
already borrowed the book (shows the "Due to inactivity..." modal).
|
|
726
|
+
</span>
|
|
727
|
+
</td>
|
|
728
|
+
</tr>
|
|
729
|
+
<tr>
|
|
730
|
+
<td class="userState">
|
|
731
|
+
<label><input type="checkbox" name="user" value="isAdmin" />isAdmin</label>
|
|
732
|
+
<label><input type="checkbox" name="user" value="isLoggedIn" />isLoggedIn</label>
|
|
733
|
+
</td>
|
|
734
|
+
<td class="printDisabled">
|
|
735
|
+
<label><input type="checkbox" name="is_printdisabled" value="is_printdisabled" />is_printdisabled</label>
|
|
736
|
+
<label><input type="checkbox" name="user_is_printdisabled" value="user_is_printdisabled" />user_is_printdisabled</label>
|
|
737
|
+
</td>
|
|
738
|
+
<td class="availableToBrowse">
|
|
739
|
+
<label><input type="radio" name="browse" value="available_to_browse" />available_to_browse</label>
|
|
740
|
+
<label><input type="radio" name="browse" value="user_has_browsed" />user_has_browsed</label>
|
|
741
|
+
<label><input type="radio" name="browse" value="browsingExpired" />browsingExpired</label>
|
|
742
|
+
</td>
|
|
743
|
+
<td class="availableToBorrow">
|
|
744
|
+
<label><input type="radio" name="borrow" value="available_to_borrow" />available_to_borrow</label>
|
|
745
|
+
<label><input type="radio" name="borrow" value="user_on_waitlist" />user_on_waitlist</label>
|
|
746
|
+
<label><input type="radio" name="borrow" value="user_can_claim_waitlist" />user_can_claim_waitlist</label>
|
|
747
|
+
<label><input type="radio" name="borrow" value="available_to_waitlist" />available_to_waitlist</label>
|
|
748
|
+
<label><input type="radio" name="borrow" value="user_has_borrowed" />user_has_borrowed</label>
|
|
749
|
+
</td>
|
|
750
|
+
<td class="purchase">
|
|
751
|
+
<label><input type="checkbox" name="purchase" value="purchase" />purchase</label>
|
|
752
|
+
</td>
|
|
753
|
+
|
|
754
|
+
<td class="reflect">
|
|
755
|
+
<input type="button" style="padding: 10px;
|
|
756
|
+
background: #194880;
|
|
757
|
+
color: white;
|
|
758
|
+
font-weight: bold; display: none" name="purchase" value="Reflect" />
|
|
759
|
+
</td>
|
|
760
|
+
</tr>
|
|
761
|
+
<tr>
|
|
762
|
+
<td>
|
|
763
|
+
</td>
|
|
764
|
+
</tr>
|
|
765
|
+
</table>
|
|
766
|
+
</fieldset>
|
|
767
|
+
</div>
|
|
768
|
+
|
|
471
769
|
</div>
|
|
472
770
|
<modal-manager></modal-manager>
|
|
473
771
|
</body>
|
package/package.json
CHANGED
|
@@ -3,11 +3,11 @@
|
|
|
3
3
|
"description": "Bookreader actions for borrowable book",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
|
-
"url": "git+https://github.com/internetarchive/iaux-
|
|
6
|
+
"url": "git+https://github.com/internetarchive/iaux-book-actions.git"
|
|
7
7
|
},
|
|
8
8
|
"author": "Internet Archive",
|
|
9
9
|
"license": "AGPL-3.0-only",
|
|
10
|
-
"version": "1.4.
|
|
10
|
+
"version": "1.4.4-alpha10",
|
|
11
11
|
"main": "index.js",
|
|
12
12
|
"module": "index.js",
|
|
13
13
|
"publishConfig": {
|