mirror of
https://github.com/discourse/discourse.git
synced 2025-02-14 18:02:47 +08:00
197 lines
5.3 KiB
JavaScript
197 lines
5.3 KiB
JavaScript
import { ajax } from 'discourse/lib/ajax';
|
|
// We use this class to track how long posts in a topic are on the screen.
|
|
const PAUSE_UNLESS_SCROLLED = 1000 * 60 * 3;
|
|
const MAX_TRACKING_TIME = 1000 * 60 * 6;
|
|
const ANON_MAX_TOPIC_IDS = 5;
|
|
|
|
const getTime = () => new Date().getTime();
|
|
|
|
export default class {
|
|
constructor(topicTrackingState, siteSettings, session, currentUser) {
|
|
this.topicTrackingState = topicTrackingState;
|
|
this.siteSettings = siteSettings;
|
|
this.session = session;
|
|
this.currentUser = currentUser;
|
|
this.reset();
|
|
}
|
|
|
|
start(topicId, topicController) {
|
|
const currentTopicId = this._topicId;
|
|
if (currentTopicId && (currentTopicId !== topicId)) {
|
|
this.tick();
|
|
this.flush();
|
|
}
|
|
|
|
this.reset();
|
|
|
|
// Create an interval timer if we don't have one.
|
|
if (!this._interval) {
|
|
this._interval = setInterval(() => this.tick(), 1000);
|
|
$(window).on('scroll.screentrack', () => this.scrolled());
|
|
}
|
|
|
|
this._topicId = topicId;
|
|
this._topicController = topicController;
|
|
}
|
|
|
|
stop() {
|
|
// already stopped no need to "extra stop"
|
|
if(!this._topicId) { return; }
|
|
|
|
$(window).off('scroll.screentrack');
|
|
this.tick();
|
|
this.flush();
|
|
this.reset();
|
|
|
|
this._topicId = null;
|
|
this._topicController = null;
|
|
|
|
if (this._interval) {
|
|
clearInterval(this._interval);
|
|
this._interval = null;
|
|
}
|
|
}
|
|
|
|
setOnscreen(onscreen) {
|
|
this._onscreen = onscreen;
|
|
}
|
|
|
|
// Reset our timers
|
|
reset() {
|
|
const now = getTime();
|
|
this._lastTick = now;
|
|
this._lastScrolled = now;
|
|
this._lastFlush = 0;
|
|
this._timings = {};
|
|
this._totalTimings = {};
|
|
this._topicTime = 0;
|
|
this._onscreen = [];
|
|
}
|
|
|
|
scrolled() {
|
|
this._lastScrolled = getTime();
|
|
}
|
|
|
|
registerAnonCallback(cb) {
|
|
this._anonCallback = cb;
|
|
}
|
|
|
|
flush() {
|
|
const newTimings = {};
|
|
const totalTimings = this._totalTimings;
|
|
|
|
const timings = this._timings;
|
|
Object.keys(this._timings).forEach(postNumber => {
|
|
const time = timings[postNumber];
|
|
totalTimings[postNumber] = totalTimings[postNumber] || 0;
|
|
|
|
if (time > 0 && totalTimings[postNumber] < MAX_TRACKING_TIME) {
|
|
totalTimings[postNumber] += time;
|
|
newTimings[postNumber] = time;
|
|
}
|
|
timings[postNumber] = 0;
|
|
});
|
|
|
|
const topicId = parseInt(this._topicId, 10);
|
|
let highestSeen = 0;
|
|
|
|
Object.keys(newTimings).forEach(postNumber => {
|
|
highestSeen = Math.max(highestSeen, parseInt(postNumber, 10));
|
|
});
|
|
|
|
const highestSeenByTopic = this.session.get('highestSeenByTopic');
|
|
if ((highestSeenByTopic[topicId] || 0) < highestSeen) {
|
|
highestSeenByTopic[topicId] = highestSeen;
|
|
}
|
|
|
|
this.topicTrackingState.updateSeen(topicId, highestSeen);
|
|
|
|
if (!$.isEmptyObject(newTimings)) {
|
|
if (this.currentUser) {
|
|
ajax('/topics/timings', {
|
|
data: {
|
|
timings: newTimings,
|
|
topic_time: this._topicTime,
|
|
topic_id: topicId
|
|
},
|
|
cache: false,
|
|
type: 'POST',
|
|
headers: {
|
|
'X-SILENCE-LOGGER': 'true'
|
|
}
|
|
}).then(() => {
|
|
const controller = this._topicController;
|
|
if (controller) {
|
|
const postNumbers = Object.keys(newTimings).map(v => parseInt(v, 10));
|
|
controller.readPosts(topicId, postNumbers);
|
|
}
|
|
}).catch(e => {
|
|
const error = e.jqXHR;
|
|
if (error.status === 405 && error.responseJSON.error_type === "read_only") return;
|
|
});
|
|
} else if (this._anonCallback) {
|
|
// Anonymous viewer - save to localStorage
|
|
const storage = this.keyValueStore;
|
|
|
|
// Save total time
|
|
const existingTime = storage.getInt('anon-topic-time');
|
|
storage.setItem('anon-topic-time', existingTime + this._topicTime);
|
|
|
|
// Save unique topic IDs up to a max
|
|
let topicIds = storage.get('anon-topic-ids');
|
|
if (topicIds) {
|
|
topicIds = topicIds.split(',').map(e => parseInt(e));
|
|
} else {
|
|
topicIds = [];
|
|
}
|
|
if (topicIds.indexOf(topicId) === -1 && topicIds.length < ANON_MAX_TOPIC_IDS) {
|
|
topicIds.push(topicId);
|
|
storage.setItem('anon-topic-ids', topicIds.join(','));
|
|
}
|
|
|
|
// Inform the observer
|
|
this._anonCallback();
|
|
|
|
// No need to call controller.readPosts()
|
|
}
|
|
|
|
this._topicTime = 0;
|
|
}
|
|
|
|
this._lastFlush = 0;
|
|
}
|
|
|
|
tick() {
|
|
const now = new Date().getTime();
|
|
|
|
// If the user hasn't scrolled the browser in a long time, stop tracking time read
|
|
const sinceScrolled = now - this._lastScrolled;
|
|
if (sinceScrolled > PAUSE_UNLESS_SCROLLED) {
|
|
return;
|
|
}
|
|
|
|
const diff = now - this._lastTick;
|
|
this._lastFlush += diff;
|
|
this._lastTick = now;
|
|
|
|
const totalTimings = this._totalTimings;
|
|
const timings = this._timings;
|
|
const nextFlush = this.siteSettings.flush_timings_secs * 1000;
|
|
|
|
const rush = Object.keys(timings).some(postNumber => {
|
|
return timings[postNumber] > 0 && !totalTimings[postNumber];
|
|
});
|
|
|
|
if (this._lastFlush > nextFlush || rush) {
|
|
this.flush();
|
|
}
|
|
|
|
// Don't track timings if we're not in focus
|
|
if (!Discourse.get("hasFocus")) return;
|
|
|
|
this._topicTime += diff;
|
|
|
|
this._onscreen.forEach(postNumber => timings[postNumber] = (timings[postNumber] || 0) + diff);
|
|
}
|
|
}
|