33 lines
1.6 KiB
JavaScript
33 lines
1.6 KiB
JavaScript
'use strict';
|
|
// Shared pure policy. No speed limit; credit only loaded pages actually observed,
|
|
// not pages skipped between two scroll positions. Coordinates never leave reader.
|
|
(function (root) {
|
|
const IDLE_MS = 30000, EXPOSURE_MS = 100;
|
|
function exposureTracker() {
|
|
const seen = new Set(), exposures = new Map();
|
|
function update(pages, now, active) {
|
|
const visible = new Set(active ? pages.filter(page => page.loaded).map(page => page.index) : []);
|
|
for (const [page, entry] of exposures) {
|
|
if (entry.visible) entry.ms += Math.max(0, now - entry.at);
|
|
entry.at = now; entry.visible = visible.has(page);
|
|
if (entry.ms >= EXPOSURE_MS) { seen.add(page); exposures.delete(page); }
|
|
}
|
|
for (const page of visible) if (!seen.has(page) && !exposures.has(page)) exposures.set(page, { ms: 0, at: now, visible: true });
|
|
return seen;
|
|
}
|
|
return { seen, update };
|
|
}
|
|
function stateFor({ visible, focused, lastInputAt, now, owned, view }) {
|
|
if (!visible) return 'hidden';
|
|
if (!focused) return 'unfocused';
|
|
if (now - lastInputAt >= IDLE_MS) return 'idle';
|
|
if (!owned) return 'other-tab';
|
|
if (view.atEnd) return 'finished';
|
|
if (view.pages.some(page => page.loaded)) return 'reading';
|
|
return view.pages.some(page => !page.failed) || !view.pages.length ? 'loading' : 'page-error';
|
|
}
|
|
const policy = { IDLE_MS, EXPOSURE_MS, exposureTracker, stateFor };
|
|
if (typeof module !== 'undefined' && module.exports) module.exports = policy;
|
|
else root.ReadingClockPolicy = policy;
|
|
})(typeof window === 'undefined' ? globalThis : window);
|