We can use DI for this, which makes it easier for plugins to subscribe to the message bus.
318 lines
8.7 KiB
JavaScript
318 lines
8.7 KiB
JavaScript
import NotificationLevels from 'discourse/lib/notification-levels';
|
|
|
|
function isNew(topic) {
|
|
return topic.last_read_post_number === null &&
|
|
((topic.notification_level !== 0 && !topic.notification_level) ||
|
|
topic.notification_level >= NotificationLevels.TRACKING);
|
|
}
|
|
|
|
function isUnread(topic) {
|
|
return topic.last_read_post_number !== null &&
|
|
topic.last_read_post_number < topic.highest_post_number &&
|
|
topic.notification_level >= NotificationLevels.TRACKING;
|
|
}
|
|
|
|
const TopicTrackingState = Discourse.Model.extend({
|
|
messageCount: 0,
|
|
|
|
_setup: function() {
|
|
this.unreadSequence = [];
|
|
this.newSequence = [];
|
|
this.states = {};
|
|
}.on('init'),
|
|
|
|
establishChannels() {
|
|
const tracker = this;
|
|
|
|
const process = function(data){
|
|
if (data.message_type === "delete") {
|
|
tracker.removeTopic(data.topic_id);
|
|
tracker.incrementMessageCount();
|
|
}
|
|
|
|
if (data.message_type === "new_topic" || data.message_type === "latest") {
|
|
const ignored_categories = Discourse.User.currentProp("muted_category_ids");
|
|
if(_.include(ignored_categories, data.payload.category_id)){
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (data.message_type === "latest"){
|
|
tracker.notify(data);
|
|
}
|
|
|
|
if (data.message_type === "new_topic" || data.message_type === "unread" || data.message_type === "read") {
|
|
tracker.notify(data);
|
|
const old = tracker.states["t" + data.topic_id];
|
|
|
|
if(!_.isEqual(old, data.payload)){
|
|
tracker.states["t" + data.topic_id] = data.payload;
|
|
tracker.incrementMessageCount();
|
|
}
|
|
}
|
|
};
|
|
|
|
this.messageBus.subscribe("/new", process);
|
|
this.messageBus.subscribe("/latest", process);
|
|
if (this.currentUser) {
|
|
this.messageBus.subscribe("/unread/" + this.currentUser.get('id'), process);
|
|
}
|
|
},
|
|
|
|
updateSeen(topicId, highestSeen) {
|
|
if(!topicId || !highestSeen) { return; }
|
|
const state = this.states["t" + topicId];
|
|
if(state && (!state.last_read_post_number || state.last_read_post_number < highestSeen)) {
|
|
state.last_read_post_number = highestSeen;
|
|
this.incrementMessageCount();
|
|
}
|
|
},
|
|
|
|
notify(data){
|
|
if (!this.newIncoming) { return; }
|
|
|
|
const filter = this.get("filter");
|
|
|
|
if ((filter === "all" || filter === "latest" || filter === "new") && data.message_type === "new_topic" ) {
|
|
this.addIncoming(data.topic_id);
|
|
}
|
|
|
|
if ((filter === "all" || filter === "unread") && data.message_type === "unread") {
|
|
const old = this.states["t" + data.topic_id];
|
|
if(!old || old.highest_post_number === old.last_read_post_number) {
|
|
this.addIncoming(data.topic_id);
|
|
}
|
|
}
|
|
|
|
if(filter === "latest" && data.message_type === "latest") {
|
|
this.addIncoming(data.topic_id);
|
|
}
|
|
|
|
this.set("incomingCount", this.newIncoming.length);
|
|
},
|
|
|
|
addIncoming(topicId) {
|
|
if(this.newIncoming.indexOf(topicId) === -1){
|
|
this.newIncoming.push(topicId);
|
|
}
|
|
},
|
|
|
|
resetTracking(){
|
|
this.newIncoming = [];
|
|
this.set("incomingCount", 0);
|
|
},
|
|
|
|
// track how many new topics came for this filter
|
|
trackIncoming(filter) {
|
|
this.newIncoming = [];
|
|
this.set("filter", filter);
|
|
this.set("incomingCount", 0);
|
|
},
|
|
|
|
hasIncoming: function(){
|
|
const count = this.get('incomingCount');
|
|
return count && count > 0;
|
|
}.property('incomingCount'),
|
|
|
|
removeTopic(topic_id) {
|
|
delete this.states["t" + topic_id];
|
|
},
|
|
|
|
// If we have a cached topic list, we can update it from our tracking
|
|
// information.
|
|
updateTopics(topics) {
|
|
if (Em.isEmpty(topics)) { return; }
|
|
|
|
const states = this.states;
|
|
topics.forEach(function(t) {
|
|
const state = states['t' + t.get('id')];
|
|
|
|
if (state) {
|
|
const lastRead = t.get('last_read_post_number');
|
|
if (lastRead !== state.last_read_post_number) {
|
|
const postsCount = t.get('posts_count');
|
|
let newPosts = postsCount - state.highest_post_number,
|
|
unread = postsCount - state.last_read_post_number;
|
|
|
|
if (newPosts < 0) { newPosts = 0; }
|
|
if (!state.last_read_post_number) {
|
|
unread = 0;
|
|
}
|
|
if (unread < 0) { unread = 0; }
|
|
|
|
t.setProperties({
|
|
highest_post_number: state.highest_post_number,
|
|
last_read_post_number: state.last_read_post_number,
|
|
new_posts: newPosts,
|
|
unread: unread,
|
|
unseen: !state.last_read_post_number
|
|
});
|
|
}
|
|
}
|
|
});
|
|
},
|
|
|
|
sync(list, filter) {
|
|
const tracker = this,
|
|
states = tracker.states;
|
|
|
|
if (!list || !list.topics) { return; }
|
|
|
|
// compensate for delayed "new" topics
|
|
// client side we know they are not new, server side we think they are
|
|
for (let i=list.topics.length-1; i>=0; i--) {
|
|
const state = states["t"+ list.topics[i].id];
|
|
if (state && state.last_read_post_number > 0) {
|
|
if (filter === "new") {
|
|
list.topics.splice(i, 1);
|
|
} else {
|
|
list.topics[i].unseen = false;
|
|
list.topics[i].dont_sync = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
list.topics.forEach(function(topic){
|
|
const row = tracker.states["t" + topic.id] || {};
|
|
row.topic_id = topic.id;
|
|
row.notification_level = topic.notification_level;
|
|
|
|
|
|
if (topic.unseen) {
|
|
row.last_read_post_number = null;
|
|
} else if (topic.unread || topic.new_posts) {
|
|
row.last_read_post_number = topic.highest_post_number - ((topic.unread||0) + (topic.new_posts||0));
|
|
} else {
|
|
if (!topic.dont_sync) {
|
|
delete tracker.states["t" + topic.id];
|
|
}
|
|
return;
|
|
}
|
|
|
|
row.highest_post_number = topic.highest_post_number;
|
|
if (topic.category) {
|
|
row.category_id = topic.category.id;
|
|
}
|
|
|
|
tracker.states["t" + topic.id] = row;
|
|
});
|
|
|
|
// Correct missing states, safeguard in case message bus is corrupt
|
|
if((filter === "new" || filter === "unread") && !list.more_topics_url){
|
|
|
|
const ids = {};
|
|
list.topics.forEach(function(r){
|
|
ids["t" + r.id] = true;
|
|
});
|
|
|
|
_.each(tracker.states, function(v, k){
|
|
|
|
// we are good if we are on the list
|
|
if (ids[k]) { return; }
|
|
|
|
if (filter === "unread" && isUnread(v)) {
|
|
// pretend read
|
|
v.last_read_post_number = v.highest_post_number;
|
|
}
|
|
|
|
if (filter === "new" && isNew(v)) {
|
|
// pretend not new
|
|
v.last_read_post_number = 1;
|
|
}
|
|
});
|
|
}
|
|
|
|
this.incrementMessageCount();
|
|
},
|
|
|
|
incrementMessageCount() {
|
|
this.set("messageCount", this.get("messageCount") + 1);
|
|
},
|
|
|
|
countNew(category_id){
|
|
return _.chain(this.states)
|
|
.where(isNew)
|
|
.where(function(topic){ return topic.category_id === category_id || !category_id;})
|
|
.value()
|
|
.length;
|
|
},
|
|
|
|
resetNew() {
|
|
const self = this;
|
|
Object.keys(this.states).forEach(function (id) {
|
|
if (self.states[id].last_read_post_number === null) {
|
|
delete self.states[id];
|
|
}
|
|
});
|
|
},
|
|
|
|
countUnread(category_id){
|
|
return _.chain(this.states)
|
|
.where(isUnread)
|
|
.where(function(topic){ return topic.category_id === category_id || !category_id;})
|
|
.value()
|
|
.length;
|
|
},
|
|
|
|
countCategory(category_id) {
|
|
let sum = 0;
|
|
_.each(this.states, function(topic){
|
|
if (topic.category_id === category_id) {
|
|
sum += (topic.last_read_post_number === null ||
|
|
topic.last_read_post_number < topic.highest_post_number) ? 1 : 0;
|
|
}
|
|
});
|
|
return sum;
|
|
},
|
|
|
|
lookupCount(name, category){
|
|
let categoryName = category ? Em.get(category, "name") : null;
|
|
if(name === "new") {
|
|
return this.countNew(categoryName);
|
|
} else if(name === "unread") {
|
|
return this.countUnread(categoryName);
|
|
} else {
|
|
categoryName = name.split("/")[1];
|
|
if(categoryName) {
|
|
return this.countCategory(categoryName);
|
|
}
|
|
}
|
|
},
|
|
loadStates(data) {
|
|
// not exposed
|
|
const states = this.states;
|
|
|
|
if(data) {
|
|
_.each(data,function(topic){
|
|
states["t" + topic.topic_id] = topic;
|
|
});
|
|
}
|
|
}
|
|
});
|
|
|
|
|
|
TopicTrackingState.reopenClass({
|
|
createFromStates(data) {
|
|
|
|
// TODO: This should be a model that does injection automatically
|
|
const container = Discourse.__container__,
|
|
messageBus = container.lookup('message-bus:main'),
|
|
currentUser = container.lookup('current-user:main'),
|
|
instance = Discourse.TopicTrackingState.create({ messageBus, currentUser });
|
|
|
|
instance.loadStates(data);
|
|
instance.establishChannels();
|
|
return instance;
|
|
},
|
|
current(){
|
|
if (!this.tracker) {
|
|
const data = PreloadStore.get('topicTrackingStates');
|
|
this.tracker = this.createFromStates(data);
|
|
PreloadStore.remove('topicTrackingStates');
|
|
}
|
|
return this.tracker;
|
|
}
|
|
});
|
|
|
|
export default TopicTrackingState;
|