This repository has been archived on 2023-03-18. You can view files and clone it, but cannot push or open issues or pull requests.
osr-discourse-src/app/assets/javascripts/discourse/models/topic-tracking-state.js.es6
Sam 720e1965e3 FEATURE: add category suppress from latest
In the past we used suppress_from_homepage, it had mixed semantics
it would remove from category list if category list was on home and
unconditionally remove from latest.

New setting explicitly only removes from latest list but leaves the
category list alond
2018-02-22 09:56:35 +11:00

384 lines
11 KiB
JavaScript

import { NotificationLevels } from 'discourse/lib/notification-levels';
import computed from "ember-addons/ember-computed-decorators";
import { on } from "ember-addons/ember-computed-decorators";
import { defaultHomepage } from 'discourse/lib/utilities';
import PreloadStore from 'preload-store';
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,
@on("init")
_setup() {
this.unreadSequence = [];
this.newSequence = [];
this.states = {};
},
establishChannels() {
const tracker = this;
const process = data => {
if (data.message_type === "delete") {
tracker.removeTopic(data.topic_id);
tracker.incrementMessageCount();
}
if (data.message_type === "new_topic" || data.message_type === "latest") {
const muted_category_ids = Discourse.User.currentProp("muted_category_ids");
if (_.include(muted_category_ids, data.payload.category_id)) {
return;
}
}
// fill parent_category_id we need it for counting new/unread
if (data.payload && data.payload.category_id) {
var category = Discourse.Category.findById(data.payload.category_id);
if (category && category.parent_category_id) {
data.payload.parent_category_id = category.parent_category_id;
}
}
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];
// don't add tracking state for read stuff that was not tracked in first place
if (old || data.message_type !== "read") {
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);
}
this.messageBus.subscribe("/delete", msg => {
const old = tracker.states["t" + msg.topic_id];
if (old) {
old.deleted = true;
}
tracker.incrementMessageCount();
});
this.messageBus.subscribe("/recover", msg => {
const old = tracker.states["t" + msg.topic_id];
if (old) {
delete old.deleted;
}
tracker.incrementMessageCount();
});
},
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; }
if (data.payload && data.payload.archetype === "private_message") { return; }
const filter = this.get("filter");
const filterCategory = this.get("filterCategory");
const categoryId = data.payload && data.payload.category_id;
if (filterCategory && filterCategory.get("id") !== categoryId) {
const category = categoryId && Discourse.Category.findById(categoryId);
if (!category || category.get("parentCategory.id") !== filterCategory.get('id')) {
return;
}
}
if (filter === defaultHomepage()) {
const suppressed_from_latest_category_ids = Discourse.Site.currentProp("suppressed_from_latest_category_ids");
if (_.include(suppressed_from_latest_category_ids, data.payload.category_id)) {
return;
}
}
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 = [];
const split = filter.split('/');
if (split.length >= 4) {
filter = split[split.length-1];
// c/cat/subcat/l/latest
var category = Discourse.Category.findSingleBySlug(split.splice(1,split.length - 3).join('/'));
this.set("filterCategory", category);
} else {
this.set("filterCategory", null);
}
this.set("filter", filter);
this.set("incomingCount", 0);
},
@computed("incomingCount")
hasIncoming(incomingCount) {
return incomingCount && incomingCount > 0;
},
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(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].set('unseen', false);
list.topics[i].set('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(r => ids["t" + r.id] = true);
_.each(tracker.states, (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.incrementProperty("messageCount");
},
countNew(category_id) {
return _.chain(this.states)
.where(isNew)
.where(topic =>
topic.archetype !== "private_message" &&
!topic.deleted && (
topic.category_id === category_id ||
topic.parent_category_id === category_id ||
!category_id)
)
.value()
.length;
},
resetNew() {
Object.keys(this.states).forEach(id => {
if (this.states[id].last_read_post_number === null) {
delete this.states[id];
}
});
},
countUnread(category_id) {
return _.chain(this.states)
.where(isUnread)
.where(topic =>
topic.archetype !== "private_message" &&
!topic.deleted && (
topic.category_id === category_id ||
topic.parent_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 && !topic.deleted) {
sum += (topic.last_read_post_number === null ||
topic.last_read_post_number < topic.highest_post_number) ? 1 : 0;
}
});
return sum;
},
lookupCount(name, category) {
if (name === "latest") {
return this.lookupCount("new", category) +
this.lookupCount("unread", category);
}
let categoryId = category ? Em.get(category, "id") : null;
let categoryName = category ? Em.get(category, "name") : null;
if (name === "new") {
return this.countNew(categoryId);
} else if (name === "unread") {
return this.countUnread(categoryId);
} else {
categoryName = name.split("/")[1];
if (categoryName) {
return this.countCategory(categoryId);
}
}
},
loadStates(data) {
const states = this.states;
const idMap = Discourse.Category.idMap();
// I am taking some shortcuts here to avoid 500 gets for a large list
if (data) {
_.each(data,topic => {
var category = idMap[topic.category_id];
if (category && category.parent_category_id) {
topic.parent_category_id = category.parent_category_id;
}
states["t" + topic.topic_id] = topic;
});
}
}
});
export function startTracking(tracking) {
const data = PreloadStore.get('topicTrackingStates');
tracking.loadStates(data);
tracking.initialStatesLength = data && data.length;
tracking.establishChannels();
PreloadStore.remove('topicTrackingStates');
}
export default TopicTrackingState;