FEATURE: Add last visit indication to topic view page. (#13471)

This PR also removes grey old unread bubble from the topic badges by
dropping `TopicUser#highest_seen_post_number`.
This commit is contained in:
Alan Guo Xiang Tan 2021-07-05 14:17:31 +08:00 committed by GitHub
parent 0f688f45bd
commit 37b8ce79c9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
64 changed files with 306 additions and 312 deletions

View File

@ -49,7 +49,9 @@ export default MountWidget.extend({
"selectedPostsCount", "selectedPostsCount",
"searchService", "searchService",
"showReadIndicator", "showReadIndicator",
"streamFilters" "streamFilters",
"lastReadPostNumber",
"highestPostNumber"
); );
}, },

View File

@ -142,8 +142,8 @@ export default Component.extend({
classes.push("unseen-topic"); classes.push("unseen-topic");
} }
if (topic.get("displayNewPosts")) { if (topic.unread_posts) {
classes.push("new-posts"); classes.push("unread-posts");
} }
["liked", "archived", "bookmarked", "pinned", "closed"].forEach((name) => { ["liked", "archived", "bookmarked", "pinned", "closed"].forEach((name) => {

View File

@ -1,13 +1,16 @@
import Component from "@ember/component"; import Component from "@ember/component";
import I18n from "I18n"; import I18n from "I18n";
import { or } from "@ember/object/computed";
export default Component.extend({ export default Component.extend({
tagName: "span", tagName: "span",
classNameBindings: [":topic-post-badges"], classNameBindings: [":topic-post-badges"],
rerenderTriggers: ["url", "unread", "newPosts", "unseen"], rerenderTriggers: ["url", "unread", "newPosts", "unreadPosts", "unseen"],
newDotText: null, newDotText: null,
init() { init() {
this._super(...arguments); this._super(...arguments);
this.set( this.set(
"newDotText", "newDotText",
this.currentUser && this.currentUser.trust_level > 0 this.currentUser && this.currentUser.trust_level > 0
@ -15,4 +18,6 @@ export default Component.extend({
: I18n.t("filters.new.lower_title") : I18n.t("filters.new.lower_title")
); );
}, },
displayUnreadPosts: or("newPosts", "unreadPosts"),
}); });

View File

@ -68,6 +68,8 @@ export default Controller.extend(bufferedProperty("model"), {
filter: null, filter: null,
quoteState: null, quoteState: null,
currentPostId: null, currentPostId: null,
userLastReadPostNumber: null,
highestPostNumber: null,
init() { init() {
this._super(...arguments); this._super(...arguments);

View File

@ -352,15 +352,14 @@ const TopicTrackingState = EmberObject.extend({
isSeen !== state.is_seen isSeen !== state.is_seen
) { ) {
const postsCount = topic.get("posts_count"); const postsCount = topic.get("posts_count");
let newPosts = postsCount - state.highest_post_number, let unread;
unread = postsCount - state.last_read_post_number;
if (newPosts < 0) { if (state.last_read_post_number) {
newPosts = 0; unread = postsCount - state.last_read_post_number;
} } else {
if (!state.last_read_post_number) {
unread = 0; unread = 0;
} }
if (unread < 0) { if (unread < 0) {
unread = 0; unread = 0;
} }
@ -368,8 +367,7 @@ const TopicTrackingState = EmberObject.extend({
topic.setProperties({ topic.setProperties({
highest_post_number: state.highest_post_number, highest_post_number: state.highest_post_number,
last_read_post_number: state.last_read_post_number, last_read_post_number: state.last_read_post_number,
new_posts: newPosts, unread_posts: unread,
unread: unread,
is_seen: state.is_seen, is_seen: state.is_seen,
unseen: !state.last_read_post_number && isUnseen(state), unseen: !state.last_read_post_number && isUnseen(state),
}); });
@ -654,14 +652,13 @@ const TopicTrackingState = EmberObject.extend({
newState.topic_id = topic.id; newState.topic_id = topic.id;
newState.notification_level = topic.notification_level; newState.notification_level = topic.notification_level;
// see ListableTopicSerializer for unread/unseen/new_posts and other // see ListableTopicSerializer for unread_posts/unseen and other
// topic property logic // topic property logic
if (topic.unseen) { if (topic.unseen) {
newState.last_read_post_number = null; newState.last_read_post_number = null;
} else if (topic.unread || topic.new_posts) { } else if (topic.unread_posts) {
newState.last_read_post_number = newState.last_read_post_number =
topic.highest_post_number - topic.highest_post_number - (topic.unread_posts || 0);
((topic.unread || 0) + (topic.new_posts || 0));
} else { } else {
// remove the topic if it is no longer unread/new (it has been seen) // remove the topic if it is no longer unread/new (it has been seen)
// and if there are too many topics in memory // and if there are too many topics in memory

View File

@ -7,7 +7,6 @@ import I18n from "I18n";
import PreloadStore from "discourse/lib/preload-store"; import PreloadStore from "discourse/lib/preload-store";
import { Promise } from "rsvp"; import { Promise } from "rsvp";
import RestModel from "discourse/models/rest"; import RestModel from "discourse/models/rest";
import Session from "discourse/models/session";
import Site from "discourse/models/site"; import Site from "discourse/models/site";
import User from "discourse/models/user"; import User from "discourse/models/user";
import { ajax } from "discourse/lib/ajax"; import { ajax } from "discourse/lib/ajax";
@ -21,6 +20,7 @@ import { longDate } from "discourse/lib/formatter";
import { popupAjaxError } from "discourse/lib/ajax-error"; import { popupAjaxError } from "discourse/lib/ajax-error";
import { resolveShareUrl } from "discourse/helpers/share-url"; import { resolveShareUrl } from "discourse/helpers/share-url";
import DiscourseURL, { userPath } from "discourse/lib/url"; import DiscourseURL, { userPath } from "discourse/lib/url";
import deprecated from "discourse-common/lib/deprecated";
export function loadTopicView(topic, args) { export function loadTopicView(topic, args) {
const data = deepMerge({}, args); const data = deepMerge({}, args);
@ -239,10 +239,16 @@ const Topic = RestModel.extend({
return url; return url;
}, },
@discourseComputed("new_posts", "unread") @discourseComputed("unread_posts", "new_posts")
totalUnread(newPosts, unread) { totalUnread(unreadPosts, newPosts) {
const count = (unread || 0) + (newPosts || 0); deprecated("The totalUnread property of the topic model is deprecated");
return count > 0 ? count : null; return unreadPosts || newPosts;
},
@discourseComputed("unread_posts", "new_posts")
displayNewPosts(unreadPosts, newPosts) {
deprecated("The displayNewPosts property of the topic model is deprecated");
return unreadPosts || newPosts;
}, },
@discourseComputed("last_read_post_number", "url") @discourseComputed("last_read_post_number", "url")
@ -284,25 +290,6 @@ const Topic = RestModel.extend({
return userPath(username); return userPath(username);
}, },
// The amount of new posts to display. It might be different than what the server
// tells us if we are still asynchronously flushing our "recently read" data.
// So take what the browser has seen into consideration.
@discourseComputed("new_posts", "id")
displayNewPosts(newPosts, id) {
const highestSeen = Session.currentProp("highestSeenByTopic")[id];
if (highestSeen) {
const delta = highestSeen - this.last_read_post_number;
if (delta > 0) {
let result = newPosts - delta;
if (result < 0) {
result = 0;
}
return result;
}
}
return newPosts;
},
@discourseComputed("views") @discourseComputed("views")
viewsHeat(v) { viewsHeat(v) {
if (v >= this.siteSettings.topic_views_heat_high) { if (v >= this.siteSettings.topic_views_heat_high) {

View File

@ -1,11 +1,10 @@
import { and, or } from "@ember/object/computed"; import { and } from "@ember/object/computed";
import EmberObject from "@ember/object"; import EmberObject from "@ember/object";
import I18n from "I18n"; import I18n from "I18n";
import discourseComputed from "discourse-common/utils/decorators"; import discourseComputed from "discourse-common/utils/decorators";
export default EmberObject.extend({ export default EmberObject.extend({
postCountsPresent: or("topic.unread", "topic.displayNewPosts"), showBadges: and("postBadgesEnabled", "topic.unread_posts"),
showBadges: and("postBadgesEnabled", "postCountsPresent"),
@discourseComputed @discourseComputed
newDotText() { newDotText() {

View File

@ -69,6 +69,8 @@ export default DiscourseRoute.extend({
"model.currentPost": closest, "model.currentPost": closest,
enteredIndex: topic.postStream.progressIndexOfPost(closestPost), enteredIndex: topic.postStream.progressIndexOfPost(closestPost),
enteredAt: Date.now().toString(), enteredAt: Date.now().toString(),
userLastReadPostNumber: topic.last_read_post_number,
highestPostNumber: topic.highest_post_number,
}); });
this.appEvents.trigger("page:topic-loaded", topic); this.appEvents.trigger("page:topic-loaded", topic);

View File

@ -1,5 +1,5 @@
{{raw "topic-status" topic=topic}} {{raw "topic-status" topic=topic}}
<a href={{topic.lastUnreadUrl}} class="title">{{html-safe topic.fancyTitle}}</a> <a href={{topic.lastUnreadUrl}} class="title">{{html-safe topic.fancyTitle}}</a>
{{topic-post-badges newPosts=topic.totalUnread unseen=topic.unseen url=topic.lastUnreadUrl}} {{topic-post-badges unreadPosts=topic.unread_posts unseen=topic.unseen url=topic.lastUnreadUrl}}
<a href={{topic.lastPostUrl}} class="last-posted-at">{{format-age topic.last_posted_at}}</a> <a href={{topic.lastPostUrl}} class="last-posted-at">{{format-age topic.last_posted_at}}</a>

View File

@ -11,7 +11,7 @@
{{#if topic.featured_link}} {{#if topic.featured_link}}
{{topic-featured-link topic}} {{topic-featured-link topic}}
{{/if}} {{/if}}
{{topic-post-badges newPosts=topic.totalUnread unseen=topic.unseen url=topic.lastUnreadUrl}} {{topic-post-badges unreadPosts=topic.unread_posts unseen=topic.unseen url=topic.lastUnreadUrl}}
</div> </div>
<div class="bottom-row"> <div class="bottom-row">
{{category-link topic.category}}{{discourse-tags topic mode="list"}}{{! intentionally inline to avoid whitespace}} {{category-link topic.category}}{{discourse-tags topic mode="list"}}{{! intentionally inline to avoid whitespace}}

View File

@ -1,8 +1,5 @@
{{#if unread }} {{#if displayUnreadPosts}}
&nbsp;<a href={{url}} title={{i18n "topic.unread_posts" count=unread}} class="badge badge-notification unread">{{unread}}</a> &nbsp;<a href={{url}} title={{i18n "topic.unread_posts" count=displayUnreadPosts}} class="badge badge-notification unread-posts">{{displayUnreadPosts}}</a>
{{/if}}
{{#if newPosts}}
&nbsp;<a href={{url}} title={{i18n "topic.total_unread_posts" count=newPosts}} class="badge badge-notification new-posts">{{newPosts}}</a>
{{/if}} {{/if}}
{{#if unseen}} {{#if unseen}}
&nbsp;<a href={{url}} title={{i18n "topic.new"}} class="badge badge-notification new-topic">{{newDotText}}</a> &nbsp;<a href={{url}} title={{i18n "topic.new"}} class="badge badge-notification new-topic">{{newDotText}}</a>

View File

@ -1,5 +1,5 @@
{{#if view.showBadges}} {{#if view.showBadges}}
{{raw "topic-post-badges" unread=topic.unread newPosts=topic.displayNewPosts unseen=topic.unseen url=topic.lastUnreadUrl newDotText=newDotText}} {{raw "topic-post-badges" unreadPosts=topic.unread_posts unseen=topic.unseen url=topic.lastUnreadUrl newDotText=newDotText}}
{{else}} {{else}}
{{raw "list/posts-count-column" topic=topic tagName="div"}} {{raw "list/posts-count-column" topic=topic tagName="div"}}
{{/if}} {{/if}}

View File

@ -29,7 +29,7 @@
topicId=topic.id topicId=topic.id
unreadClass=unreadClass~}} unreadClass=unreadClass~}}
{{~#if showTopicPostBadges}} {{~#if showTopicPostBadges}}
{{~raw "topic-post-badges" unread=topic.unread newPosts=topic.displayNewPosts unseen=topic.unseen url=topic.lastUnreadUrl newDotText=newDotText}} {{~raw "topic-post-badges" unreadPosts=topic.unread_posts unseen=topic.unseen url=topic.lastUnreadUrl newDotText=newDotText}}
{{~/if}} {{~/if}}
</span> </span>
<div class="link-bottom-line"> <div class="link-bottom-line">

View File

@ -1,9 +1,9 @@
<span class='topic-post-badges'> <span class='topic-post-badges'>
{{~#if unread ~}}
&nbsp;<a href='{{url}}' class='badge badge-notification unread' title='{{i18n "topic.unread_posts" count=unread}}'>{{unread}}</a>
{{~/if}}
{{~#if newPosts ~}} {{~#if newPosts ~}}
&nbsp;<a href='{{url}}' class='badge badge-notification new-posts' title='{{i18n "topic.total_unread_posts" count=newPosts}}'>{{newPosts}}</a> &nbsp;<a href='{{url}}' class='badge badge-notification unread-posts' title='{{i18n "topic.unread_posts" count=newPosts}}'>{{newPosts}}</a>
{{~/if}}
{{~#if unreadPosts ~}}
&nbsp;<a href='{{url}}' class='badge badge-notification unread-posts' title='{{i18n "topic.unread_posts" count=unreadPosts}}'>{{unreadPosts}}</a>
{{~/if}} {{~/if}}
{{~#if unseen ~}} {{~#if unseen ~}}
&nbsp;<a href='{{url}}' class='badge badge-notification new-topic' title='{{i18n "topic.new"}}'>{{newDotText}}</a> &nbsp;<a href='{{url}}' class='badge badge-notification new-topic' title='{{i18n "topic.new"}}'>{{newDotText}}</a>

View File

@ -208,6 +208,8 @@
gaps=model.postStream.gaps gaps=model.postStream.gaps
showReadIndicator=model.show_read_indicator showReadIndicator=model.show_read_indicator
streamFilters=model.postStream.streamFilters streamFilters=model.postStream.streamFilters
lastReadPostNumber=userLastReadPostNumber
highestPostNumber=highestPostNumber
showFlags=(action "showPostFlags") showFlags=(action "showPostFlags")
editPost=(action "editPost") editPost=(action "editPost")
showHistory=(route-action "showHistory") showHistory=(route-action "showHistory")

View File

@ -186,17 +186,19 @@ export default createWidget("post-stream", {
tagName: "div.post-stream", tagName: "div.post-stream",
html(attrs) { html(attrs) {
const posts = attrs.posts || [], const posts = attrs.posts || [];
postArray = posts.toArray(), const postArray = posts.toArray();
result = [], const postArrayLength = postArray.length;
before = attrs.gaps && attrs.gaps.before ? attrs.gaps.before : {}, const maxPostNumber = postArray[postArrayLength - 1].post_number;
after = attrs.gaps && attrs.gaps.after ? attrs.gaps.after : {}, const result = [];
mobileView = this.site.mobileView; const before = attrs.gaps && attrs.gaps.before ? attrs.gaps.before : {};
const after = attrs.gaps && attrs.gaps.after ? attrs.gaps.after : {};
const mobileView = this.site.mobileView;
let prevPost; let prevPost;
let prevDate; let prevDate;
for (let i = 0; i < postArray.length; i++) { for (let i = 0; i < postArrayLength; i++) {
const post = postArray[i]; const post = postArray[i];
if (post instanceof Placeholder) { if (post instanceof Placeholder) {
@ -276,6 +278,18 @@ export default createWidget("post-stream", {
); );
} }
if (
i !== postArrayLength - 1 &&
maxPostNumber <= attrs.highestPostNumber &&
attrs.lastReadPostNumber === post.post_number
) {
result.push(
this.attach("topic-post-visited-line", {
post_number: post.post_number,
})
);
}
prevPost = post; prevPost = post;
} }

View File

@ -0,0 +1,18 @@
import I18n from "I18n";
import { createWidget } from "discourse/widgets/widget";
import { h } from "virtual-dom";
export default createWidget("topic-post-visited-line", {
tagName: "div.topic-post-visited-line",
buildClasses(attrs) {
return [`post-${attrs.post_number}`];
},
html() {
return h(
"span.topic-post-visited-message",
I18n.t("topics.new_messages_marker")
);
},
});

View File

@ -53,8 +53,7 @@ acceptance("Tags", function (needs) {
archetype: "regular", archetype: "regular",
unseen: false, unseen: false,
last_read_post_number: 1, last_read_post_number: 1,
unread: 0, unread_posts: 1,
new_posts: 1,
pinned: false, pinned: false,
unpinned: null, unpinned: null,
visible: true, visible: true,

View File

@ -553,3 +553,23 @@ acceptance("Topic pinning/unpinning as a group moderator", function (needs) {
); );
}); });
}); });
acceptance("Topic last visit line", function (needs) {
needs.user({ moderator: false, admin: false, trust_level: 1 });
test("visit topic", async function (assert) {
await visit("/t/-/280");
assert.ok(
exists(".topic-post-visited-line.post-10"),
"shows the last visited line on the right post"
);
await visit("/t/-/9");
assert.ok(
!exists(".topic-post-visited-line"),
"does not show last visited line if post is the last post"
);
});
});

View File

@ -4033,8 +4033,7 @@ export default {
bumped_at: "2019-11-12T05:19:52.848Z", bumped_at: "2019-11-12T05:19:52.848Z",
unseen: false, unseen: false,
last_read_post_number: 1, last_read_post_number: 1,
unread: 0, unread_posts: 0,
new_posts: 0,
pinned: false, pinned: false,
unpinned: null, unpinned: null,
visible: true, visible: true,
@ -4076,8 +4075,7 @@ export default {
bumped_at: "2019-11-12T05:19:32.516Z", bumped_at: "2019-11-12T05:19:32.516Z",
unseen: false, unseen: false,
last_read_post_number: 1, last_read_post_number: 1,
unread: 0, unread_posts: 0,
new_posts: 0,
pinned: false, pinned: false,
unpinned: null, unpinned: null,
visible: true, visible: true,
@ -6398,8 +6396,7 @@ export default {
archetype: "regular", archetype: "regular",
unseen: false, unseen: false,
last_read_post_number: 5, last_read_post_number: 5,
unread: 0, unread_posts: 0,
new_posts: 0,
pinned: false, pinned: false,
unpinned: null, unpinned: null,
visible: true, visible: true,

View File

@ -38,8 +38,7 @@ export default {
bumped_at: "2019-07-26T01:29:24.177Z", bumped_at: "2019-07-26T01:29:24.177Z",
unseen: false, unseen: false,
last_read_post_number: 2, last_read_post_number: 2,
unread: 0, unread_posts: 0,
new_posts: 0,
pinned: false, pinned: false,
unpinned: null, unpinned: null,
visible: true, visible: true,

View File

@ -3018,8 +3018,7 @@ export default {
bumped_at: "2015-04-08T16:05:09.842Z", bumped_at: "2015-04-08T16:05:09.842Z",
unseen: false, unseen: false,
last_read_post_number: 3, last_read_post_number: 3,
unread: 0, unread_posts: 1,
new_posts: 1,
pinned: false, pinned: false,
unpinned: null, unpinned: null,
visible: true, visible: true,
@ -3062,8 +3061,7 @@ export default {
bumped_at: "2015-04-08T15:40:30.037Z", bumped_at: "2015-04-08T15:40:30.037Z",
unseen: false, unseen: false,
last_read_post_number: 2, last_read_post_number: 2,
unread: 0, unread_posts: 2,
new_posts: 2,
pinned: false, pinned: false,
unpinned: null, unpinned: null,
visible: true, visible: true,
@ -3143,8 +3141,7 @@ export default {
bumped_at: "2015-04-07T09:21:21.570Z", bumped_at: "2015-04-07T09:21:21.570Z",
unseen: false, unseen: false,
last_read_post_number: 8, last_read_post_number: 8,
unread: 0, unread_posts: 0,
new_posts: 0,
pinned: false, pinned: false,
unpinned: null, unpinned: null,
visible: true, visible: true,
@ -3187,8 +3184,7 @@ export default {
bumped_at: "2015-02-22T13:46:26.845Z", bumped_at: "2015-02-22T13:46:26.845Z",
unseen: false, unseen: false,
last_read_post_number: 65, last_read_post_number: 65,
unread: 0, unread_posts: 0,
new_posts: 0,
pinned: false, pinned: false,
unpinned: null, unpinned: null,
visible: true, visible: true,
@ -3266,8 +3262,7 @@ export default {
bumped_at: "2015-03-21T00:33:52.243Z", bumped_at: "2015-03-21T00:33:52.243Z",
unseen: false, unseen: false,
last_read_post_number: 1, last_read_post_number: 1,
unread: 0, unread_posts: 0,
new_posts: 0,
pinned: false, pinned: false,
unpinned: null, unpinned: null,
visible: true, visible: true,
@ -4087,8 +4082,7 @@ export default {
bumped_at: "2015-08-13T10:14:34.799Z", bumped_at: "2015-08-13T10:14:34.799Z",
unseen: false, unseen: false,
last_read_post_number: 5, last_read_post_number: 5,
unread: 0, unread_posts: 0,
new_posts: 0,
pinned: false, pinned: false,
unpinned: null, unpinned: null,
visible: true, visible: true,
@ -4117,8 +4111,7 @@ export default {
bumped_at: "2015-08-13T01:58:35.206Z", bumped_at: "2015-08-13T01:58:35.206Z",
unseen: false, unseen: false,
last_read_post_number: 3, last_read_post_number: 3,
unread: 0, unread_posts: 0,
new_posts: 0,
pinned: false, pinned: false,
unpinned: null, unpinned: null,
visible: true, visible: true,
@ -4175,8 +4168,7 @@ export default {
bumped_at: "2015-08-13T10:14:34.799Z", bumped_at: "2015-08-13T10:14:34.799Z",
unseen: false, unseen: false,
last_read_post_number: 5, last_read_post_number: 5,
unread: 0, unread_posts: 0,
new_posts: 0,
pinned: false, pinned: false,
unpinned: null, unpinned: null,
visible: true, visible: true,
@ -4413,8 +4405,7 @@ export default {
bumped_at: "2017-01-27T03:52:02.119Z", bumped_at: "2017-01-27T03:52:02.119Z",
unseen: false, unseen: false,
last_read_post_number: 1, last_read_post_number: 1,
unread: 0, unread_posts: 0,
new_posts: 0,
pinned: false, pinned: false,
unpinned: null, unpinned: null,
visible: true, visible: true,
@ -4690,8 +4681,7 @@ export default {
bumped_at: "2015-08-13T10:14:34.799Z", bumped_at: "2015-08-13T10:14:34.799Z",
unseen: false, unseen: false,
last_read_post_number: 5, last_read_post_number: 5,
unread: 0, unread_posts: 0,
new_posts: 0,
pinned: false, pinned: false,
unpinned: null, unpinned: null,
visible: true, visible: true,
@ -4720,8 +4710,7 @@ export default {
bumped_at: "2015-08-13T01:58:35.206Z", bumped_at: "2015-08-13T01:58:35.206Z",
unseen: false, unseen: false,
last_read_post_number: 3, last_read_post_number: 3,
unread: 0, unread_posts: 0,
new_posts: 0,
pinned: false, pinned: false,
unpinned: null, unpinned: null,
visible: true, visible: true,
@ -4982,8 +4971,7 @@ export default {
bumped_at: "2015-08-13T10:14:34.799Z", bumped_at: "2015-08-13T10:14:34.799Z",
unseen: false, unseen: false,
last_read_post_number: 5, last_read_post_number: 5,
unread: 0, unread_posts: 0,
new_posts: 0,
pinned: false, pinned: false,
unpinned: null, unpinned: null,
visible: true, visible: true,
@ -5012,8 +5000,7 @@ export default {
bumped_at: "2015-08-13T01:58:35.206Z", bumped_at: "2015-08-13T01:58:35.206Z",
unseen: false, unseen: false,
last_read_post_number: 3, last_read_post_number: 3,
unread: 0, unread_posts: 0,
new_posts: 0,
pinned: false, pinned: false,
unpinned: null, unpinned: null,
visible: true, visible: true,

View File

@ -247,8 +247,7 @@ discourseModule("Unit | Model | topic-tracking-state", function (hooks) {
{ {
highest_post_number: null, highest_post_number: null,
id: 111, id: 111,
unread: 10, unread_posts: 10,
new_posts: 10,
}, },
], ],
}; };
@ -271,8 +270,7 @@ discourseModule("Unit | Model | topic-tracking-state", function (hooks) {
Topic.create({ Topic.create({
highest_post_number: null, highest_post_number: null,
id: 111, id: 111,
unread: 10, unread_posts: 10,
new_posts: 10,
unseen: true, unseen: true,
prevent_sync: false, prevent_sync: false,
}), }),
@ -303,8 +301,7 @@ discourseModule("Unit | Model | topic-tracking-state", function (hooks) {
id: 111, id: 111,
unseen: false, unseen: false,
seen: true, seen: true,
unread: 0, unread_posts: 0,
new_posts: 0,
prevent_sync: false, prevent_sync: false,
}), }),
], ],
@ -338,8 +335,7 @@ discourseModule("Unit | Model | topic-tracking-state", function (hooks) {
id: 111, id: 111,
unseen: true, unseen: true,
seen: false, seen: false,
unread: 0, unread_posts: 0,
new_posts: 0,
highest_post_number: 20, highest_post_number: 20,
category_id: 1, category_id: 1,
tags: ["pending"], tags: ["pending"],
@ -348,8 +344,7 @@ discourseModule("Unit | Model | topic-tracking-state", function (hooks) {
id: 222, id: 222,
unseen: false, unseen: false,
seen: true, seen: true,
unread: 3, unread_posts: 3,
new_posts: 0,
highest_post_number: 20, highest_post_number: 20,
}), }),
], ],

View File

@ -396,7 +396,7 @@ div.education {
border-top: 3px solid var(--primary-low); border-top: 3px solid var(--primary-low);
border-bottom: 1px solid var(--primary-low); border-bottom: 1px solid var(--primary-low);
.badge-notification.new-posts { .badge-notification.unread-posts {
vertical-align: text-bottom; vertical-align: text-bottom;
} }
} }

View File

@ -1030,6 +1030,21 @@ a.mention-group {
} }
} }
.topic-post-visited-line {
border-bottom: 1px solid var(--danger-medium);
line-height: 0.1em;
text-align: center;
margin: 1rem 0px;
.topic-post-visited-message {
position: relative; // Chrome needs this, otherwise the line is above the text
background-color: var(--secondary);
color: var(--danger-medium);
font-size: $font-down-1;
padding: 0 8px;
}
}
// Select posts // Select posts
.topic-post { .topic-post {

View File

@ -178,9 +178,8 @@
color: var(--secondary); color: var(--secondary);
} }
// New posts // Unread posts
&.new-posts,
&.unread-posts { &.unread-posts {
background-color: var(--tertiary-med-or-tertiary); background-color: var(--tertiary-med-or-tertiary);
color: var(--secondary); color: var(--secondary);

View File

@ -62,7 +62,7 @@
.badge-wrapper { .badge-wrapper {
margin-right: 0.5em; margin-right: 0.5em;
} }
.badge-notification.new-posts { .badge-notification.unread-posts {
display: block; display: block;
padding: 0; padding: 0;
} }
@ -87,7 +87,7 @@
.topic-statuses { .topic-statuses {
margin-right: 0.15em; margin-right: 0.15em;
} }
.topic-post-badges .badge.new-posts, .topic-post-badges .badge.unread-posts,
.title { .title {
margin-right: 5px; margin-right: 5px;
} }

View File

@ -577,6 +577,20 @@ blockquote {
} }
} }
.topic-post-visited-line {
width: calc(
#{$topic-body-width} + #{$topic-avatar-width} +
(#{$topic-body-width-padding} * 2)
);
+ .topic-post {
.topic-avatar,
.topic-body {
border-top: none;
}
}
}
// variables are used to calculate the width of .gap // variables are used to calculate the width of .gap
.topic-body { .topic-body {
width: calc(#{$topic-body-width} + (#{$topic-body-width-padding} * 2)); width: calc(#{$topic-body-width} + (#{$topic-body-width-padding} * 2));

View File

@ -156,7 +156,6 @@ ol.category-breadcrumb {
} }
.category-topic-link td.num .badge-notification { .category-topic-link td.num .badge-notification {
&.new-posts,
&.unread-posts { &.unread-posts {
color: var(--secondary); color: var(--secondary);
} }

View File

@ -9,6 +9,12 @@
} }
} }
.topic-post-visited-line {
+ .topic-post article {
border-top: none;
}
}
.topic-post article { .topic-post article {
border-top: 1px solid var(--primary-low); border-top: 1px solid var(--primary-low);
padding: 15px 0 8px 0; padding: 15px 0 8px 0;

View File

@ -136,7 +136,6 @@ html {
background: var(--primary-medium); background: var(--primary-medium);
} }
.badge-notification.new-posts,
.badge-notification.unread-posts { .badge-notification.unread-posts {
background: var(--tertiary); background: var(--tertiary);
} }

View File

@ -352,7 +352,7 @@ class PostMover
} }
DB.exec(<<~SQL, params) DB.exec(<<~SQL, params)
INSERT INTO topic_users(user_id, topic_id, posted, last_read_post_number, highest_seen_post_number, INSERT INTO topic_users(user_id, topic_id, posted, last_read_post_number,
last_emailed_post_number, first_visited_at, last_visited_at, notification_level, last_emailed_post_number, first_visited_at, last_visited_at, notification_level,
notifications_changed_at, notifications_reason_id) notifications_changed_at, notifications_reason_id)
SELECT tu.user_id, SELECT tu.user_id,
@ -370,12 +370,6 @@ class PostMover
WHERE lr.old_topic_id = tu.topic_id WHERE lr.old_topic_id = tu.topic_id
AND lr.old_post_number <= tu.last_read_post_number AND lr.old_post_number <= tu.last_read_post_number
) AS last_read_post_number, ) AS last_read_post_number,
(
SELECT MAX(hs.new_post_number)
FROM moved_posts hs
WHERE hs.old_topic_id = tu.topic_id
AND hs.old_post_number <= tu.highest_seen_post_number
) AS highest_seen_post_number,
( (
SELECT MAX(le.new_post_number) SELECT MAX(le.new_post_number)
FROM moved_posts le FROM moved_posts le
@ -392,7 +386,6 @@ class PostMover
WHERE tu.topic_id = :old_topic_id WHERE tu.topic_id = :old_topic_id
AND GREATEST( AND GREATEST(
tu.last_read_post_number, tu.last_read_post_number,
tu.highest_seen_post_number,
tu.last_emailed_post_number tu.last_emailed_post_number
) >= (SELECT MIN(old_post_number) FROM moved_posts) ) >= (SELECT MIN(old_post_number) FROM moved_posts)
ON CONFLICT (topic_id, user_id) DO UPDATE ON CONFLICT (topic_id, user_id) DO UPDATE
@ -409,18 +402,6 @@ class PostMover
GREATEST(topic_users.last_read_post_number, GREATEST(topic_users.last_read_post_number,
excluded.last_read_post_number) excluded.last_read_post_number)
ELSE topic_users.last_read_post_number END, ELSE topic_users.last_read_post_number END,
highest_seen_post_number = CASE
WHEN topic_users.highest_seen_post_number = :old_highest_staff_post_number OR (
:old_highest_post_number < :old_highest_staff_post_number
AND topic_users.highest_seen_post_number = :old_highest_post_number
AND NOT EXISTS(SELECT 1
FROM users u
WHERE u.id = topic_users.user_id
AND (admin OR moderator))
) THEN
GREATEST(topic_users.highest_seen_post_number,
excluded.highest_seen_post_number)
ELSE topic_users.highest_seen_post_number END,
last_emailed_post_number = CASE last_emailed_post_number = CASE
WHEN topic_users.last_emailed_post_number = :old_highest_staff_post_number OR ( WHEN topic_users.last_emailed_post_number = :old_highest_staff_post_number OR (
:old_highest_post_number < :old_highest_staff_post_number :old_highest_post_number < :old_highest_staff_post_number

View File

@ -71,7 +71,6 @@ class PostTiming < ActiveRecord::Base
end end
TopicUser.where(user_id: user.id, topic_id: topic.id).update_all( TopicUser.where(user_id: user.id, topic_id: topic.id).update_all(
highest_seen_post_number: last_read,
last_read_post_number: last_read last_read_post_number: last_read
) )

View File

@ -810,11 +810,7 @@ class Topic < ActiveRecord::Base
SET last_read_post_number = CASE SET last_read_post_number = CASE
WHEN last_read_post_number > :highest THEN :highest WHEN last_read_post_number > :highest THEN :highest
ELSE last_read_post_number ELSE last_read_post_number
END, END
highest_seen_post_number = CASE
WHEN highest_seen_post_number > :highest THEN :highest
ELSE highest_seen_post_number
END
WHERE topic_id = :topic_id WHERE topic_id = :topic_id
SQL SQL
end end

View File

@ -1,6 +1,10 @@
# frozen_string_literal: true # frozen_string_literal: true
class TopicUser < ActiveRecord::Base class TopicUser < ActiveRecord::Base
self.ignored_columns = [
:highest_seen_post_number # Remove after 01 Jan 2022
]
belongs_to :user belongs_to :user
belongs_to :topic belongs_to :topic
@ -114,6 +118,11 @@ class TopicUser < ActiveRecord::Base
# since there's more likely to be an existing record than not. If the update returns 0 rows affected # since there's more likely to be an existing record than not. If the update returns 0 rows affected
# it then creates the row instead. # it then creates the row instead.
def change(user_id, topic_id, attrs) def change(user_id, topic_id, attrs)
# For plugin compatibility, remove after 01 Jan 2022
if attrs[:highest_seen_post_number]
attrs.delete(:highest_seen_post_number)
end
# Sometimes people pass objs instead of the ids. We can handle that. # Sometimes people pass objs instead of the ids. We can handle that.
topic_id = topic_id.id if topic_id.is_a?(::Topic) topic_id = topic_id.id if topic_id.is_a?(::Topic)
user_id = user_id.id if user_id.is_a?(::User) user_id = user_id.id if user_id.is_a?(::User)
@ -131,6 +140,7 @@ class TopicUser < ActiveRecord::Base
attrs_sql = attrs_array.map { |t| "#{t[0]} = ?" }.join(", ") attrs_sql = attrs_array.map { |t| "#{t[0]} = ?" }.join(", ")
vals = attrs_array.map { |t| t[1] } vals = attrs_array.map { |t| t[1] }
rows = TopicUser.where(topic_id: topic_id, user_id: user_id).update_all([attrs_sql, *vals]) rows = TopicUser.where(topic_id: topic_id, user_id: user_id).update_all([attrs_sql, *vals])
if rows == 0 if rows == 0
@ -252,7 +262,6 @@ class TopicUser < ActiveRecord::Base
UPDATE_TOPIC_USER_SQL = "UPDATE topic_users UPDATE_TOPIC_USER_SQL = "UPDATE topic_users
SET SET
last_read_post_number = GREATEST(:post_number, tu.last_read_post_number), last_read_post_number = GREATEST(:post_number, tu.last_read_post_number),
highest_seen_post_number = t.highest_post_number,
total_msecs_viewed = LEAST(tu.total_msecs_viewed + :msecs,86400000), total_msecs_viewed = LEAST(tu.total_msecs_viewed + :msecs,86400000),
notification_level = notification_level =
case when tu.notifications_reason_id is null and (tu.total_msecs_viewed + :msecs) > case when tu.notifications_reason_id is null and (tu.total_msecs_viewed + :msecs) >
@ -278,8 +287,8 @@ class TopicUser < ActiveRecord::Base
UPDATE_TOPIC_USER_SQL_STAFF = UPDATE_TOPIC_USER_SQL.gsub("highest_post_number", "highest_staff_post_number") UPDATE_TOPIC_USER_SQL_STAFF = UPDATE_TOPIC_USER_SQL.gsub("highest_post_number", "highest_staff_post_number")
INSERT_TOPIC_USER_SQL = "INSERT INTO topic_users (user_id, topic_id, last_read_post_number, highest_seen_post_number, last_visited_at, first_visited_at, notification_level) INSERT_TOPIC_USER_SQL = "INSERT INTO topic_users (user_id, topic_id, last_read_post_number, last_visited_at, first_visited_at, notification_level)
SELECT :user_id, :topic_id, :post_number, ft.highest_post_number, :now, :now, :new_status SELECT :user_id, :topic_id, :post_number, :now, :now, :new_status
FROM topics AS ft FROM topics AS ft
JOIN users u on u.id = :user_id JOIN users u on u.id = :user_id
WHERE ft.id = :topic_id WHERE ft.id = :topic_id
@ -303,11 +312,6 @@ class TopicUser < ActiveRecord::Base
threshold: SiteSetting.default_other_auto_track_topics_after_msecs threshold: SiteSetting.default_other_auto_track_topics_after_msecs
} }
# In case anyone sees "highest_seen_post_number" and gets confused, like I do.
# highest_seen_post_number represents the highest_post_number of the topic when
# the user visited it. It may be out of alignment with last_read, meaning
# ... user visited the topic but did not read the posts
#
# 86400000 = 1 day # 86400000 = 1 day
rows = rows =
if user.staff? if user.staff?
@ -424,12 +428,11 @@ class TopicUser < ActiveRecord::Base
builder.exec(action_type_id: PostActionType.types[action_type]) builder.exec(action_type_id: PostActionType.types[action_type])
end end
# cap number of unread topics at count, bumping up highest_seen / last_read if needed # cap number of unread topics at count, bumping up last_read if needed
def self.cap_unread!(user_id, count) def self.cap_unread!(user_id, count)
sql = <<SQL sql = <<SQL
UPDATE topic_users tu UPDATE topic_users tu
SET last_read_post_number = max_number, SET last_read_post_number = max_number
highest_seen_post_number = max_number
FROM ( FROM (
SELECT MAX(post_number) max_number, p.topic_id FROM posts p SELECT MAX(post_number) max_number, p.topic_id FROM posts p
WHERE deleted_at IS NULL WHERE deleted_at IS NULL
@ -456,8 +459,7 @@ SQL
builder = DB.build <<~SQL builder = DB.build <<~SQL
UPDATE topic_users t UPDATE topic_users t
SET SET
last_read_post_number = LEAST(GREATEST(last_read, last_read_post_number), max_post_number), last_read_post_number = LEAST(GREATEST(last_read, last_read_post_number), max_post_number)
highest_seen_post_number = LEAST(max_post_number,GREATEST(t.highest_seen_post_number, last_read))
FROM ( FROM (
SELECT topic_id, user_id, MAX(post_number) last_read SELECT topic_id, user_id, MAX(post_number) last_read
FROM post_timings FROM post_timings
@ -474,8 +476,7 @@ SQL
X.topic_id = t.topic_id AND X.topic_id = t.topic_id AND
X.user_id = t.user_id AND X.user_id = t.user_id AND
( (
last_read_post_number <> LEAST(GREATEST(last_read, last_read_post_number), max_post_number) OR last_read_post_number <> LEAST(GREATEST(last_read, last_read_post_number), max_post_number)
highest_seen_post_number <> LEAST(max_post_number,GREATEST(t.highest_seen_post_number, last_read))
) )
SQL SQL
@ -496,7 +497,6 @@ end
# topic_id :integer not null # topic_id :integer not null
# posted :boolean default(FALSE), not null # posted :boolean default(FALSE), not null
# last_read_post_number :integer # last_read_post_number :integer
# highest_seen_post_number :integer
# last_visited_at :datetime # last_visited_at :datetime
# first_visited_at :datetime # first_visited_at :datetime
# notification_level :integer default(1), not null # notification_level :integer default(1), not null

View File

@ -14,6 +14,7 @@ class ListableTopicSerializer < BasicTopicSerializer
:last_read_post_number, :last_read_post_number,
:unread, :unread,
:new_posts, :new_posts,
:unread_posts,
:pinned, :pinned,
:unpinned, :unpinned,
:excerpt, :excerpt,
@ -115,16 +116,25 @@ class ListableTopicSerializer < BasicTopicSerializer
alias :include_last_read_post_number? :has_user_data alias :include_last_read_post_number? :has_user_data
# TODO: For backwards compatibility with themes,
# Remove once Discourse 2.8 is released
def unread def unread
unread_helper.unread_posts 0
end end
alias :include_unread? :has_user_data alias :include_unread? :has_user_data
# TODO: For backwards compatibility with themes,
# Remove once Discourse 2.8 is released
def new_posts def new_posts
unread_helper.new_posts unread_helper.unread_posts
end end
alias :include_new_posts? :has_user_data alias :include_new_posts? :has_user_data
def unread_posts
unread_helper.unread_posts
end
alias :include_unread_posts? :has_user_data
def include_excerpt? def include_excerpt?
pinned || SiteSetting.always_include_topic_excerpts || theme_modifier_helper.serialize_topic_excerpts pinned || SiteSetting.always_include_topic_excerpts || theme_modifier_helper.serialize_topic_excerpts
end end

View File

@ -752,7 +752,12 @@ class PostAlerter
DiscourseEvent.trigger(:before_create_notifications_for_users, notify, post) DiscourseEvent.trigger(:before_create_notifications_for_users, notify, post)
already_seen_user_ids = Set.new TopicUser.where(topic_id: post.topic.id).where("highest_seen_post_number >= ?", post.post_number).pluck(:user_id) already_seen_user_ids = Set.new(
TopicUser
.where(topic_id: post.topic.id)
.where("last_read_post_number >= ?", post.post_number)
.pluck(:user_id)
)
each_user_in_batches(notify) do |user| each_user_in_batches(notify) do |user|
notification_type = !new_record && already_seen_user_ids.include?(user.id) ? Notification.types[:edited] : Notification.types[:posted] notification_type = !new_record && already_seen_user_ids.include?(user.id) ? Notification.types[:edited] : Notification.types[:posted]

View File

@ -2397,7 +2397,7 @@ en:
top: "There are no top topics." top: "There are no top topics."
educate: educate:
new: '<p>Your new topics will appear here. By default, topics are considered new and will show a <span class="badge new-topic badge-notification" style="vertical-align:middle;line-height:inherit;"></span> indicator if they were created in the last 2 days.</p><p>Visit your <a href="%{userPrefsUrl}">preferences</a> to change this.</p>' new: '<p>Your new topics will appear here. By default, topics are considered new and will show a <span class="badge new-topic badge-notification" style="vertical-align:middle;line-height:inherit;"></span> indicator if they were created in the last 2 days.</p><p>Visit your <a href="%{userPrefsUrl}">preferences</a> to change this.</p>'
unread: '<p>Your unread topics appear here.</p><p>By default, topics are considered unread and will show unread counts <span class="badge new-posts badge-notification">1</span> if you:</p><ul><li>Created the topic</li><li>Replied to the topic</li><li>Read the topic for more than 4 minutes</li></ul><p>Or if you have explicitly set the topic to Tracked or Watched via the 🔔 in each topic.</p><p>Visit your <a href="%{userPrefsUrl}">preferences</a> to change this.</p>' unread: '<p>Your unread topics appear here.</p><p>By default, topics are considered unread and will show unread counts <span class="badge unread-posts badge-notification">1</span> if you:</p><ul><li>Created the topic</li><li>Replied to the topic</li><li>Read the topic for more than 4 minutes</li></ul><p>Or if you have explicitly set the topic to Tracked or Watched via the 🔔 in each topic.</p><p>Visit your <a href="%{userPrefsUrl}">preferences</a> to change this.</p>'
bottom: bottom:
latest: "There are no more latest topics." latest: "There are no more latest topics."
posted: "There are no more posted topics." posted: "There are no more posted topics."
@ -2457,15 +2457,9 @@ en:
not_found: not_found:
title: "Topic not found" title: "Topic not found"
description: "Sorry, we couldn't find that topic. Perhaps it was removed by a moderator?" description: "Sorry, we couldn't find that topic. Perhaps it was removed by a moderator?"
total_unread_posts: unread_posts:
one: "you have %{count} unread post in this topic" one: "you have %{count} unread post in this topic"
other: "you have %{count} unread posts in this topic" other: "you have %{count} unread posts in this topic"
unread_posts:
one: "you have %{count} unread old post in this topic"
other: "you have %{count} unread old posts in this topic"
new_posts:
one: "there is %{count} new post in this topic since you last read it"
other: "there are %{count} new posts in this topic since you last read it"
likes: likes:
one: "there is %{count} like in this topic" one: "there is %{count} like in this topic"
other: "there are %{count} likes in this topic" other: "there are %{count} likes in this topic"

View File

@ -0,0 +1,19 @@
# frozen_string_literal: true
require 'migration/column_dropper'
class RemoveHighestSeenPostNumberFromTopicUsers < ActiveRecord::Migration[6.1]
DROPPED_COLUMNS = {
topic_users: %i{highest_seen_post_number}
}
def up
DROPPED_COLUMNS.each do |table, columns|
Migration::ColumnDropper.execute_drop(table, columns)
end
end
def down
raise ActiveRecord::IrreversibleMigration
end
end

View File

@ -646,7 +646,6 @@ class PostCreator
@topic.id, @topic.id,
posted: true, posted: true,
last_read_post_number: @post.post_number, last_read_post_number: @post.post_number,
highest_seen_post_number: @post.post_number,
last_posted_at: Time.zone.now) last_posted_at: Time.zone.now)
# assume it took us 5 seconds of reading time to make a post # assume it took us 5 seconds of reading time to make a post

View File

@ -57,8 +57,8 @@ def insert_topic_users
log "Inserting topic users..." log "Inserting topic users..."
DB.exec <<-SQL DB.exec <<-SQL
INSERT INTO topic_users (user_id, topic_id, posted, last_read_post_number, highest_seen_post_number, first_visited_at, last_visited_at, total_msecs_viewed) INSERT INTO topic_users (user_id, topic_id, posted, last_read_post_number, first_visited_at, last_visited_at, total_msecs_viewed)
SELECT user_id, topic_id, 't' , MAX(post_number), MAX(post_number), MIN(created_at), MAX(created_at), COUNT(id) * #{MS_SPEND_CREATING_POST} SELECT user_id, topic_id, 't' , MAX(post_number), MIN(created_at), MAX(created_at), COUNT(id) * #{MS_SPEND_CREATING_POST}
FROM posts FROM posts
WHERE user_id > 0 WHERE user_id > 0
GROUP BY user_id, topic_id GROUP BY user_id, topic_id

View File

@ -346,7 +346,6 @@ task 'posts:reorder_posts', [:topic_id] => [:environment] do |_, args|
["post_timings", "post_number"], ["post_timings", "post_number"],
["posts", "reply_to_post_number"], ["posts", "reply_to_post_number"],
["topic_users", "last_read_post_number"], ["topic_users", "last_read_post_number"],
["topic_users", "highest_seen_post_number"],
["topic_users", "last_emailed_post_number"], ["topic_users", "last_emailed_post_number"],
].each do |table, column| ].each do |table, column|
builder = DB.build <<~SQL builder = DB.build <<~SQL

View File

@ -72,7 +72,7 @@ class TopicsBulkAction
highest_number_source_column = @user.staff? ? 'highest_staff_post_number' : 'highest_post_number' highest_number_source_column = @user.staff? ? 'highest_staff_post_number' : 'highest_post_number'
sql = <<~SQL sql = <<~SQL
UPDATE topic_users tu UPDATE topic_users tu
SET highest_seen_post_number = t.#{highest_number_source_column} , last_read_post_number = t.#{highest_number_source_column} SET last_read_post_number = t.#{highest_number_source_column}
FROM topics t FROM topics t
WHERE t.id = tu.topic_id AND tu.user_id = :user_id AND t.id IN (:topic_ids) WHERE t.id = tu.topic_id AND tu.user_id = :user_id AND t.id IN (:topic_ids)
SQL SQL

View File

@ -2,7 +2,7 @@
class Unread class Unread
# This module helps us calculate unread and new post counts # This module helps us calculate unread post counts
def initialize(topic, topic_user, guardian) def initialize(topic, topic_user, guardian)
@guardian = guardian @guardian = guardian
@ -11,29 +11,27 @@ class Unread
end end
def unread_posts def unread_posts
return 0 if do_not_notify?(@topic_user.notification_level) return 0 if @topic_user.last_read_post_number.blank?
result = ((@topic_user.highest_seen_post_number || 0) - (@topic_user.last_read_post_number || 0))
result = 0 if result < 0
result
end
def new_posts
return 0 if @topic_user.highest_seen_post_number.blank?
return 0 if do_not_notify?(@topic_user.notification_level) return 0 if do_not_notify?(@topic_user.notification_level)
highest_post_number = @guardian.is_staff? ? @topic.highest_staff_post_number : @topic.highest_post_number highest_post_number = @guardian.is_staff? ? @topic.highest_staff_post_number : @topic.highest_post_number
return 0 if (@topic_user.last_read_post_number || 0) > highest_post_number return 0 if @topic_user.last_read_post_number > highest_post_number
new_posts = (highest_post_number - @topic_user.highest_seen_post_number) unread = (highest_post_number - @topic_user.last_read_post_number)
new_posts = 0 if new_posts < 0 unread = 0 if unread < 0
new_posts unread
end end
protected protected
DO_NOT_NOTIFY_LEVELS = [
TopicUser.notification_levels[:muted],
TopicUser.notification_levels[:regular]
]
def do_not_notify?(notification_level) def do_not_notify?(notification_level)
[TopicUser.notification_levels[:muted], TopicUser.notification_levels[:regular]].include?(notification_level) DO_NOT_NOTIFY_LEVELS.include?(notification_level)
end end
end end

View File

@ -192,7 +192,7 @@ after_initialize do
return if topic_id.blank? || data[:track] != DiscourseNarrativeBot::NewUserNarrative.to_s return if topic_id.blank? || data[:track] != DiscourseNarrativeBot::NewUserNarrative.to_s
topic_user = topic_users.find_by(topic_id: topic_id) topic_user = topic_users.find_by(topic_id: topic_id)
return if topic_user.present? && (topic_user.last_read_post_number.present? || topic_user.highest_seen_post_number.present?) return if topic_user.present? && topic_user.last_read_post_number.present?
topic = Topic.find_by(id: topic_id) topic = Topic.find_by(id: topic_id)
return if topic.blank? return if topic.blank?

View File

@ -150,8 +150,7 @@ acceptance("Poll in a post reply history", function (needs) {
archetype: "regular", archetype: "regular",
unseen: false, unseen: false,
last_read_post_number: 3, last_read_post_number: 3,
unread: 0, unread_posts: 0,
new_posts: 0,
pinned: false, pinned: false,
unpinned: null, unpinned: null,
visible: true, visible: true,
@ -205,8 +204,7 @@ acceptance("Poll in a post reply history", function (needs) {
archetype: "regular", archetype: "regular",
unseen: false, unseen: false,
last_read_post_number: 12, last_read_post_number: 12,
unread: 0, unread_posts: 0,
new_posts: 0,
pinned: false, pinned: false,
unpinned: null, unpinned: null,
visible: true, visible: true,

View File

@ -230,8 +230,7 @@ acceptance("Poll quote", function (needs) {
archetype: "regular", archetype: "regular",
unseen: false, unseen: false,
last_read_post_number: 1, last_read_post_number: 1,
unread: 0, unread_posts: 0,
new_posts: 0,
pinned: false, pinned: false,
unpinned: true, unpinned: true,
visible: true, visible: true,
@ -275,8 +274,7 @@ acceptance("Poll quote", function (needs) {
archetype: "regular", archetype: "regular",
unseen: false, unseen: false,
last_read_post_number: 1, last_read_post_number: 1,
unread: 0, unread_posts: 0,
new_posts: 0,
pinned: false, pinned: false,
unpinned: null, unpinned: null,
visible: true, visible: true,

View File

@ -239,8 +239,7 @@ acceptance("Poll results", function (needs) {
archetype: "regular", archetype: "regular",
unseen: false, unseen: false,
last_read_post_number: 9, last_read_post_number: 9,
unread: 0, unread_posts: 0,
new_posts: 0,
pinned: false, pinned: false,
unpinned: true, unpinned: true,
visible: true, visible: true,
@ -295,8 +294,7 @@ acceptance("Poll results", function (needs) {
archetype: "regular", archetype: "regular",
unseen: false, unseen: false,
last_read_post_number: 1, last_read_post_number: 1,
unread: 0, unread_posts: 0,
new_posts: 0,
pinned: false, pinned: false,
unpinned: null, unpinned: null,
visible: true, visible: true,
@ -343,8 +341,7 @@ acceptance("Poll results", function (needs) {
archetype: "regular", archetype: "regular",
unseen: false, unseen: false,
last_read_post_number: 1, last_read_post_number: 1,
unread: 0, unread_posts: 0,
new_posts: 0,
pinned: false, pinned: false,
unpinned: null, unpinned: null,
visible: true, visible: true,
@ -389,8 +386,7 @@ acceptance("Poll results", function (needs) {
archetype: "regular", archetype: "regular",
unseen: false, unseen: false,
last_read_post_number: 12, last_read_post_number: 12,
unread: 0, unread_posts: 0,
new_posts: 0,
pinned: false, pinned: false,
unpinned: null, unpinned: null,
visible: true, visible: true,

View File

@ -762,8 +762,8 @@ class ImportScripts::Base
puts "", "Updating topic users" puts "", "Updating topic users"
DB.exec <<~SQL DB.exec <<~SQL
INSERT INTO topic_users (user_id, topic_id, posted, last_read_post_number, highest_seen_post_number, first_visited_at, last_visited_at, total_msecs_viewed) INSERT INTO topic_users (user_id, topic_id, posted, last_read_post_number, first_visited_at, last_visited_at, total_msecs_viewed)
SELECT user_id, topic_id, 't' , MAX(post_number), MAX(post_number), MIN(created_at), MAX(created_at), COUNT(id) * 5000 SELECT user_id, topic_id, 't' , MAX(post_number), MIN(created_at), MAX(created_at), COUNT(id) * 5000
FROM posts FROM posts
WHERE user_id > 0 WHERE user_id > 0
GROUP BY user_id, topic_id GROUP BY user_id, topic_id

View File

@ -598,8 +598,7 @@ class ImportScripts::Telligent < ImportScripts::Base
# Mark all imported messages as read # Mark all imported messages as read
DB.exec(<<~SQL) DB.exec(<<~SQL)
UPDATE topic_users tu UPDATE topic_users tu
SET last_read_post_number = t.highest_post_number, SET last_read_post_number = t.highest_post_number
highest_seen_post_number = t.highest_post_number
FROM topics t FROM topics t
JOIN topic_custom_fields tcf ON t.id = tcf.topic_id JOIN topic_custom_fields tcf ON t.id = tcf.topic_id
WHERE tu.topic_id = t.id WHERE tu.topic_id = t.id

View File

@ -652,10 +652,6 @@ describe PostDestroyer do
it "sets the second user's last_read_post_number back to 1" do it "sets the second user's last_read_post_number back to 1" do
expect(topic_user.last_read_post_number).to eq(1) expect(topic_user.last_read_post_number).to eq(1)
end end
it "sets the second user's last_read_post_number back to 1" do
expect(topic_user.highest_seen_post_number).to eq(1)
end
end end
end end

View File

@ -82,7 +82,6 @@ describe TopicsBulkAction do
tu = TopicUser.find_by(user_id: post1.user_id, topic_id: post1.topic_id) tu = TopicUser.find_by(user_id: post1.user_id, topic_id: post1.topic_id)
expect(tu.last_read_post_number).to eq(3) expect(tu.last_read_post_number).to eq(3)
expect(tu.highest_seen_post_number).to eq(3)
end end
context "when the user is staff" do context "when the user is staff" do
@ -106,7 +105,6 @@ describe TopicsBulkAction do
tu = TopicUser.find_by(user_id: user.id, topic_id: post1.topic_id) tu = TopicUser.find_by(user_id: user.id, topic_id: post1.topic_id)
expect(tu.last_read_post_number).to eq(4) expect(tu.last_read_post_number).to eq(4)
expect(tu.highest_seen_post_number).to eq(4)
end end
end end
end end

View File

@ -27,62 +27,45 @@ describe Unread do
describe 'staff counts' do describe 'staff counts' do
it 'should correctly return based on staff post number' do it 'should correctly return based on staff post number' do
user.admin = true user.admin = true
topic_user.last_read_post_number = 13 topic_user.last_read_post_number = 13
topic_user.highest_seen_post_number = 13
expect(unread.unread_posts).to eq(0) expect(unread.unread_posts).to eq(2)
expect(unread.new_posts).to eq(2)
end end
end end
describe 'unread_posts' do describe 'unread_posts' do
it 'should have 0 unread posts if the user has seen all posts' do it 'should have 0 unread posts if the user has read all posts' do
topic_user.last_read_post_number = 13 topic_user.last_read_post_number = 13
topic_user.highest_seen_post_number = 13
expect(unread.unread_posts).to eq(0) expect(unread.unread_posts).to eq(0)
end end
it 'should have 6 unread posts if the user has seen all but 6 posts' do it 'returns the right unread posts for a user' do
topic_user.last_read_post_number = 5 topic_user.last_read_post_number = 10
topic_user.highest_seen_post_number = 11 expect(unread.unread_posts).to eq(3)
expect(unread.unread_posts).to eq(6)
end end
it 'should have 0 unread posts if the user has seen more posts than exist (deleted)' do it 'returns the right unread posts for a staff user' do
topic_user.last_read_post_number = 100 user.admin = true
topic_user.highest_seen_post_number = 13 topic_user.last_read_post_number = 10
expect(unread.unread_posts).to eq(5)
end
it 'should have 0 unread posts if the user has read more posts than exist (deleted)' do
topic_user.last_read_post_number = 14
expect(unread.unread_posts).to eq(0) expect(unread.unread_posts).to eq(0)
end end
end
describe 'new_posts' do it 'has 0 unread posts if the user has read 10 posts but is not tracking' do
it 'should have 0 new posts if the user has read all posts' do topic_user.last_read_post_number = 10
topic_user.last_read_post_number = 13
expect(unread.new_posts).to eq(0)
end
it 'returns 0 when the topic is the same length as when you last saw it' do
topic_user.highest_seen_post_number = 13
expect(unread.new_posts).to eq(0)
end
it 'has 3 new posts if the user has read 10 posts' do
topic_user.highest_seen_post_number = 10
expect(unread.new_posts).to eq(3)
end
it 'has 0 new posts if the user has read 10 posts but is not tracking' do
topic_user.highest_seen_post_number = 10
topic_user.notification_level = TopicUser.notification_levels[:regular] topic_user.notification_level = TopicUser.notification_levels[:regular]
expect(unread.new_posts).to eq(0) expect(unread.unread_posts).to eq(0)
end end
it 'has 0 new posts if the user read more posts than exist (deleted)' do it 'has 0 unread psots if the user has not seen the topic' do
topic_user.highest_seen_post_number = 16 topic_user.last_read_post_number = nil
expect(unread.new_posts).to eq(0) expect(unread.unread_posts).to eq(0)
end end
end end

File diff suppressed because one or more lines are too long

View File

@ -425,7 +425,6 @@ describe PostMover do
bookmarked: true, bookmarked: true,
notification_level: TopicUser.notification_levels[:watching], notification_level: TopicUser.notification_levels[:watching],
last_read_post_number: 4, last_read_post_number: 4,
highest_seen_post_number: 4,
last_emailed_post_number: 3 last_emailed_post_number: 3
) )
tu2 = Fabricate( tu2 = Fabricate(
@ -435,7 +434,6 @@ describe PostMover do
bookmarked: true, bookmarked: true,
notification_level: TopicUser.notification_levels[:watching], notification_level: TopicUser.notification_levels[:watching],
last_read_post_number: 4, last_read_post_number: 4,
highest_seen_post_number: 4,
last_emailed_post_number: 3 last_emailed_post_number: 3
) )
@ -470,21 +468,18 @@ describe PostMover do
create_topic_user( create_topic_user(
user1, user1,
last_read_post_number: 4, last_read_post_number: 4,
highest_seen_post_number: 4,
last_emailed_post_number: 3, last_emailed_post_number: 3,
notification_level: :tracking notification_level: :tracking
) )
create_topic_user( create_topic_user(
user2, user2,
last_read_post_number: 2, last_read_post_number: 2,
highest_seen_post_number: 2,
last_emailed_post_number: 2, last_emailed_post_number: 2,
notification_level: :tracking notification_level: :tracking
) )
create_topic_user( create_topic_user(
user3, user3,
last_read_post_number: 1, last_read_post_number: 1,
highest_seen_post_number: 2,
last_emailed_post_number: 4, last_emailed_post_number: 4,
notification_level: :watching notification_level: :watching
) )
@ -496,28 +491,24 @@ describe PostMover do
expect(TopicUser.find_by(topic: topic, user: user)) expect(TopicUser.find_by(topic: topic, user: user))
.to have_attributes( .to have_attributes(
last_read_post_number: 4, last_read_post_number: 4,
highest_seen_post_number: 4,
last_emailed_post_number: nil, last_emailed_post_number: nil,
notification_level: TopicUser.notification_levels[:tracking] notification_level: TopicUser.notification_levels[:tracking]
) )
expect(TopicUser.find_by(topic: topic, user: user1)) expect(TopicUser.find_by(topic: topic, user: user1))
.to have_attributes( .to have_attributes(
last_read_post_number: 4, last_read_post_number: 4,
highest_seen_post_number: 4,
last_emailed_post_number: 3, last_emailed_post_number: 3,
notification_level: TopicUser.notification_levels[:tracking] notification_level: TopicUser.notification_levels[:tracking]
) )
expect(TopicUser.find_by(topic: topic, user: user2)) expect(TopicUser.find_by(topic: topic, user: user2))
.to have_attributes( .to have_attributes(
last_read_post_number: 2, last_read_post_number: 2,
highest_seen_post_number: 2,
last_emailed_post_number: 2, last_emailed_post_number: 2,
notification_level: TopicUser.notification_levels[:tracking] notification_level: TopicUser.notification_levels[:tracking]
) )
expect(TopicUser.find_by(topic: topic, user: user3)) expect(TopicUser.find_by(topic: topic, user: user3))
.to have_attributes( .to have_attributes(
last_read_post_number: 1, last_read_post_number: 1,
highest_seen_post_number: 2,
last_emailed_post_number: 4, last_emailed_post_number: 4,
notification_level: TopicUser.notification_levels[:watching] notification_level: TopicUser.notification_levels[:watching]
) )
@ -526,7 +517,6 @@ describe PostMover do
expect(TopicUser.find_by(topic: new_topic, user: user)) expect(TopicUser.find_by(topic: new_topic, user: user))
.to have_attributes( .to have_attributes(
last_read_post_number: 1, last_read_post_number: 1,
highest_seen_post_number: 1,
last_emailed_post_number: nil, last_emailed_post_number: nil,
notification_level: TopicUser.notification_levels[:watching], notification_level: TopicUser.notification_levels[:watching],
posted: true posted: true
@ -534,7 +524,6 @@ describe PostMover do
expect(TopicUser.find_by(topic: new_topic, user: user1)) expect(TopicUser.find_by(topic: new_topic, user: user1))
.to have_attributes( .to have_attributes(
last_read_post_number: 2, last_read_post_number: 2,
highest_seen_post_number: 2,
last_emailed_post_number: 2, last_emailed_post_number: 2,
notification_level: TopicUser.notification_levels[:tracking], notification_level: TopicUser.notification_levels[:tracking],
posted: false posted: false
@ -542,7 +531,6 @@ describe PostMover do
expect(TopicUser.find_by(topic: new_topic, user: user2)) expect(TopicUser.find_by(topic: new_topic, user: user2))
.to have_attributes( .to have_attributes(
last_read_post_number: 2, last_read_post_number: 2,
highest_seen_post_number: 2,
last_emailed_post_number: 2, last_emailed_post_number: 2,
notification_level: TopicUser.notification_levels[:tracking], notification_level: TopicUser.notification_levels[:tracking],
posted: true posted: true
@ -550,7 +538,6 @@ describe PostMover do
expect(TopicUser.find_by(topic: new_topic, user: user3)) expect(TopicUser.find_by(topic: new_topic, user: user3))
.to have_attributes( .to have_attributes(
last_read_post_number: 1, last_read_post_number: 1,
highest_seen_post_number: 2,
last_emailed_post_number: 2, last_emailed_post_number: 2,
notification_level: TopicUser.notification_levels[:watching], notification_level: TopicUser.notification_levels[:watching],
posted: false posted: false
@ -810,52 +797,44 @@ describe PostMover do
create_topic_user( create_topic_user(
user1, topic, user1, topic,
last_read_post_number: 3, last_read_post_number: 3,
highest_seen_post_number: 3,
last_emailed_post_number: 3 last_emailed_post_number: 3
) )
create_topic_user( create_topic_user(
user1, destination_topic, user1, destination_topic,
last_read_post_number: 1, last_read_post_number: 1,
highest_seen_post_number: 2,
last_emailed_post_number: 1 last_emailed_post_number: 1
) )
create_topic_user( create_topic_user(
user2, topic, user2, topic,
last_read_post_number: 3, last_read_post_number: 3,
highest_seen_post_number: 3,
last_emailed_post_number: 3 last_emailed_post_number: 3
) )
create_topic_user( create_topic_user(
user2, destination_topic, user2, destination_topic,
last_read_post_number: 2, last_read_post_number: 2,
highest_seen_post_number: 1,
last_emailed_post_number: 2 last_emailed_post_number: 2
) )
create_topic_user( create_topic_user(
admin1, topic, admin1, topic,
last_read_post_number: 3, last_read_post_number: 3,
highest_seen_post_number: 3,
last_emailed_post_number: 3 last_emailed_post_number: 3
) )
create_topic_user( create_topic_user(
admin1, destination_topic, admin1, destination_topic,
last_read_post_number: 2, last_read_post_number: 2,
highest_seen_post_number: 3,
last_emailed_post_number: 1 last_emailed_post_number: 1
) )
create_topic_user( create_topic_user(
admin2, topic, admin2, topic,
last_read_post_number: 3, last_read_post_number: 3,
highest_seen_post_number: 3,
last_emailed_post_number: 3 last_emailed_post_number: 3
) )
create_topic_user( create_topic_user(
admin2, destination_topic, admin2, destination_topic,
last_read_post_number: 3, last_read_post_number: 3,
highest_seen_post_number: 2,
last_emailed_post_number: 3 last_emailed_post_number: 3
) )
@ -864,28 +843,24 @@ describe PostMover do
expect(TopicUser.find_by(topic: moved_to_topic, user: user1)) expect(TopicUser.find_by(topic: moved_to_topic, user: user1))
.to have_attributes( .to have_attributes(
last_read_post_number: 1, last_read_post_number: 1,
highest_seen_post_number: 5,
last_emailed_post_number: 1 last_emailed_post_number: 1
) )
expect(TopicUser.find_by(topic: moved_to_topic, user: user2)) expect(TopicUser.find_by(topic: moved_to_topic, user: user2))
.to have_attributes( .to have_attributes(
last_read_post_number: 5, last_read_post_number: 5,
highest_seen_post_number: 1,
last_emailed_post_number: 5 last_emailed_post_number: 5
) )
expect(TopicUser.find_by(topic: moved_to_topic, user: admin1)) expect(TopicUser.find_by(topic: moved_to_topic, user: admin1))
.to have_attributes( .to have_attributes(
last_read_post_number: 2, last_read_post_number: 2,
highest_seen_post_number: 5,
last_emailed_post_number: 1 last_emailed_post_number: 1
) )
expect(TopicUser.find_by(topic: moved_to_topic, user: admin2)) expect(TopicUser.find_by(topic: moved_to_topic, user: admin2))
.to have_attributes( .to have_attributes(
last_read_post_number: 5, last_read_post_number: 5,
highest_seen_post_number: 2,
last_emailed_post_number: 5 last_emailed_post_number: 5
) )
end end
@ -895,14 +870,14 @@ describe PostMover do
original_topic_user1 = create_topic_user( original_topic_user1 = create_topic_user(
user1, topic, user1, topic,
highest_seen_post_number: 5, last_read_post_number: 5,
first_visited_at: 5.hours.ago, first_visited_at: 5.hours.ago,
last_visited_at: 30.minutes.ago, last_visited_at: 30.minutes.ago,
notification_level: :tracking notification_level: :tracking
).reload ).reload
destination_topic_user1 = create_topic_user( destination_topic_user1 = create_topic_user(
user1, destination_topic, user1, destination_topic,
highest_seen_post_number: 5, last_read_post_number: 5,
first_visited_at: 7.hours.ago, first_visited_at: 7.hours.ago,
last_visited_at: 2.hours.ago, last_visited_at: 2.hours.ago,
notification_level: :watching notification_level: :watching
@ -910,14 +885,14 @@ describe PostMover do
original_topic_user2 = create_topic_user( original_topic_user2 = create_topic_user(
user2, topic, user2, topic,
highest_seen_post_number: 5, last_read_post_number: 5,
first_visited_at: 3.hours.ago, first_visited_at: 3.hours.ago,
last_visited_at: 1.hour.ago, last_visited_at: 1.hour.ago,
notification_level: :watching notification_level: :watching
).reload ).reload
destination_topic_user2 = create_topic_user( destination_topic_user2 = create_topic_user(
user2, destination_topic, user2, destination_topic,
highest_seen_post_number: 5, last_read_post_number: 5,
first_visited_at: 2.hours.ago, first_visited_at: 2.hours.ago,
last_visited_at: 1.hour.ago, last_visited_at: 1.hour.ago,
notification_level: :tracking notification_level: :tracking

View File

@ -20,12 +20,11 @@ describe PostTiming do
PostTiming.create!(topic_id: topic_id, user_id: user_id, post_number: post_number, msecs: 0) PostTiming.create!(topic_id: topic_id, user_id: user_id, post_number: post_number, msecs: 0)
end end
def topic_user(user_id, last_read_post_number, highest_seen_post_number) def topic_user(user_id, last_read_post_number)
TopicUser.create!( TopicUser.create!(
topic_id: topic_id, topic_id: topic_id,
user_id: user_id, user_id: user_id,
last_read_post_number: last_read_post_number, last_read_post_number: last_read_post_number,
highest_seen_post_number: highest_seen_post_number
) )
end end
@ -37,9 +36,9 @@ describe PostTiming do
timing(3, 2) timing(3, 2)
timing(3, 3) timing(3, 3)
_tu_one = topic_user(1, 1, 1) _tu_one = topic_user(1, 1)
_tu_two = topic_user(2, 2, 2) _tu_two = topic_user(2, 2)
_tu_three = topic_user(3, 3, 3) _tu_three = topic_user(3, 3)
PostTiming.pretend_read(topic_id, 2, 3) PostTiming.pretend_read(topic_id, 2, 3)
@ -49,15 +48,12 @@ describe PostTiming do
tu = TopicUser.find_by(topic_id: topic_id, user_id: 1) tu = TopicUser.find_by(topic_id: topic_id, user_id: 1)
expect(tu.last_read_post_number).to eq(1) expect(tu.last_read_post_number).to eq(1)
expect(tu.highest_seen_post_number).to eq(1)
tu = TopicUser.find_by(topic_id: topic_id, user_id: 2) tu = TopicUser.find_by(topic_id: topic_id, user_id: 2)
expect(tu.last_read_post_number).to eq(3) expect(tu.last_read_post_number).to eq(3)
expect(tu.highest_seen_post_number).to eq(3)
tu = TopicUser.find_by(topic_id: topic_id, user_id: 3) tu = TopicUser.find_by(topic_id: topic_id, user_id: 3)
expect(tu.last_read_post_number).to eq(3) expect(tu.last_read_post_number).to eq(3)
expect(tu.highest_seen_post_number).to eq(3)
end end
end end

View File

@ -1615,7 +1615,12 @@ describe Topic do
end end
it 'should generate the modified notification for the topic if already seen' do it 'should generate the modified notification for the topic if already seen' do
TopicUser.create!(topic_id: topic.id, highest_seen_post_number: topic.posts.first.post_number, user_id: user.id) TopicUser.create!(
topic_id: topic.id,
last_read_post_number: topic.posts.first.post_number,
user_id: user.id
)
expect do expect do
topic.change_category_to_id(new_category.id) topic.change_category_to_id(new_category.id)
end.to change { Notification.count }.by(2) end.to change { Notification.count }.by(2)

View File

@ -597,7 +597,6 @@ describe TopicTrackingState do
tracking = { tracking = {
notification_level: TopicUser.notification_levels[:tracking], notification_level: TopicUser.notification_levels[:tracking],
last_read_post_number: 1, last_read_post_number: 1,
highest_seen_post_number: 1
} }
TopicUser.change(user.id, post1.topic_id, tracking) TopicUser.change(user.id, post1.topic_id, tracking)

View File

@ -434,7 +434,7 @@ describe TopicUser do
p2 = Fabricate(:post, user: p1.user, topic: p1.topic, post_number: 2) p2 = Fabricate(:post, user: p1.user, topic: p1.topic, post_number: 2)
p1.topic.notifier.watch_topic!(p1.user_id) p1.topic.notifier.watch_topic!(p1.user_id)
DB.exec("UPDATE topic_users set highest_seen_post_number=1, last_read_post_number=0 DB.exec("UPDATE topic_users set last_read_post_number=0
WHERE topic_id = :topic_id AND user_id = :user_id", topic_id: p1.topic_id, user_id: p1.user_id) WHERE topic_id = :topic_id AND user_id = :user_id", topic_id: p1.topic_id, user_id: p1.user_id)
[p1, p2].each do |p| [p1, p2].each do |p|
@ -445,7 +445,6 @@ describe TopicUser do
tu = TopicUser.find_by(user_id: p1.user_id, topic_id: p1.topic_id) tu = TopicUser.find_by(user_id: p1.user_id, topic_id: p1.topic_id)
expect(tu.last_read_post_number).to eq(p2.post_number) expect(tu.last_read_post_number).to eq(p2.post_number)
expect(tu.highest_seen_post_number).to eq(2)
end end

View File

@ -64,8 +64,7 @@ describe 'private messages' do
archetype: { type: :string }, archetype: { type: :string },
unseen: { type: :boolean }, unseen: { type: :boolean },
last_read_post_number: { type: :integer }, last_read_post_number: { type: :integer },
unread: { type: :integer }, unread_posts: { type: :integer },
new_posts: { type: :integer },
pinned: { type: :boolean }, pinned: { type: :boolean },
unpinned: { type: :string, nullable: true }, unpinned: { type: :string, nullable: true },
visible: { type: :boolean }, visible: { type: :boolean },
@ -174,8 +173,7 @@ describe 'private messages' do
archetype: { type: :string }, archetype: { type: :string },
unseen: { type: :boolean }, unseen: { type: :boolean },
last_read_post_number: { type: :integer }, last_read_post_number: { type: :integer },
unread: { type: :integer }, unread_posts: { type: :integer },
new_posts: { type: :integer },
pinned: { type: :boolean }, pinned: { type: :boolean },
unpinned: { type: :string, nullable: true }, unpinned: { type: :string, nullable: true },
visible: { type: :boolean }, visible: { type: :boolean },

View File

@ -277,8 +277,7 @@ describe 'tags' do
archetype: { type: :string }, archetype: { type: :string },
unseen: { type: :boolean }, unseen: { type: :boolean },
last_read_post_number: { type: :integer }, last_read_post_number: { type: :integer },
unread: { type: :integer }, unread_posts: { type: :integer },
new_posts: { type: :integer },
pinned: { type: :boolean }, pinned: { type: :boolean },
unpinned: { type: :string, nullable: true }, unpinned: { type: :string, nullable: true },
visible: { type: :boolean }, visible: { type: :boolean },

View File

@ -225,8 +225,7 @@ describe 'topics' do
archetype: { type: :string }, archetype: { type: :string },
unseen: { type: :boolean }, unseen: { type: :boolean },
last_read_post_number: { type: :integer }, last_read_post_number: { type: :integer },
unread: { type: :integer }, unread_posts: { type: :integer },
new_posts: { type: :integer },
pinned: { type: :boolean }, pinned: { type: :boolean },
unpinned: { type: :boolean }, unpinned: { type: :boolean },
visible: { type: :boolean }, visible: { type: :boolean },
@ -603,8 +602,7 @@ describe 'topics' do
archetype: { type: :string }, archetype: { type: :string },
unseen: { type: :boolean }, unseen: { type: :boolean },
last_read_post_number: { type: :integer }, last_read_post_number: { type: :integer },
unread: { type: :integer }, unread_posts: { type: :integer },
new_posts: { type: :integer },
pinned: { type: :boolean }, pinned: { type: :boolean },
unpinned: { type: :string, nullable: true }, unpinned: { type: :string, nullable: true },
visible: { type: :boolean }, visible: { type: :boolean },
@ -704,8 +702,7 @@ describe 'topics' do
archetype: { type: :string }, archetype: { type: :string },
unseen: { type: :boolean }, unseen: { type: :boolean },
last_read_post_number: { type: :integer }, last_read_post_number: { type: :integer },
unread: { type: :integer }, unread_posts: { type: :integer },
new_posts: { type: :integer },
pinned: { type: :boolean }, pinned: { type: :boolean },
unpinned: { type: :boolean }, unpinned: { type: :boolean },
visible: { type: :boolean }, visible: { type: :boolean },
@ -807,8 +804,7 @@ describe 'topics' do
archetype: { type: :string }, archetype: { type: :string },
unseen: { type: :boolean }, unseen: { type: :boolean },
last_read_post_number: { type: :integer }, last_read_post_number: { type: :integer },
unread: { type: :integer }, unread_posts: { type: :integer },
new_posts: { type: :integer },
pinned: { type: :boolean }, pinned: { type: :boolean },
unpinned: { type: :boolean }, unpinned: { type: :boolean },
visible: { type: :boolean }, visible: { type: :boolean },

View File

@ -1010,7 +1010,6 @@ RSpec.describe TopicsController do
topic_user.update!( topic_user.update!(
last_read_post_number: 2, last_read_post_number: 2,
highest_seen_post_number: 2
) )
# ensure we have 2 notifications # ensure we have 2 notifications
@ -1036,7 +1035,7 @@ RSpec.describe TopicsController do
expect(PostTiming.where(topic: topic, user: user, post_number: 2).exists?).to eq(false) expect(PostTiming.where(topic: topic, user: user, post_number: 2).exists?).to eq(false)
expect(PostTiming.where(topic: topic, user: user, post_number: 1).exists?).to eq(true) expect(PostTiming.where(topic: topic, user: user, post_number: 1).exists?).to eq(true)
expect(TopicUser.where(topic: topic, user: user, last_read_post_number: 1, highest_seen_post_number: 1).exists?).to eq(true) expect(TopicUser.where(topic: topic, user: user, last_read_post_number: 1).exists?).to eq(true)
user.user_stat.reload user.user_stat.reload
expect(user.user_stat.first_unread_at).to eq_time(topic.updated_at) expect(user.user_stat.first_unread_at).to eq_time(topic.updated_at)
@ -1051,7 +1050,7 @@ RSpec.describe TopicsController do
delete "/t/#{topic.id}/timings.json?last=1" delete "/t/#{topic.id}/timings.json?last=1"
expect(PostTiming.where(topic: topic, user: user, post_number: 1).exists?).to eq(false) expect(PostTiming.where(topic: topic, user: user, post_number: 1).exists?).to eq(false)
expect(TopicUser.where(topic: topic, user: user, last_read_post_number: nil, highest_seen_post_number: nil).exists?).to eq(true) expect(TopicUser.where(topic: topic, user: user, last_read_post_number: nil).exists?).to eq(true)
end end
end end

View File

@ -1259,7 +1259,13 @@ describe PostAlerter do
fab!(:category) { Fabricate(:category) } fab!(:category) { Fabricate(:category) }
it 'creates single edit notification when post is modified' do it 'creates single edit notification when post is modified' do
TopicUser.create!(user_id: user.id, topic_id: topic.id, notification_level: TopicUser.notification_levels[:watching], highest_seen_post_number: post.post_number) TopicUser.create!(
user_id: user.id,
topic_id: topic.id,
notification_level: TopicUser.notification_levels[:watching],
last_read_post_number: post.post_number
)
PostRevisor.new(post).revise!(last_editor, tags: [tag.name]) PostRevisor.new(post).revise!(last_editor, tags: [tag.name])
PostAlerter.new.notify_post_users(post, []) PostAlerter.new.notify_post_users(post, [])
expect(Notification.count).to eq(1) expect(Notification.count).to eq(1)
@ -1280,7 +1286,7 @@ describe PostAlerter do
category: category.id category: category.id
) )
TopicUser.change(user, post.topic_id, highest_seen_post_number: post.post_number) TopicUser.change(user, post.topic_id, last_read_post_number: post.post_number)
# Manually run job after the user read the topic to simulate a slow # Manually run job after the user read the topic to simulate a slow
# Sidekiq. # Sidekiq.