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",
"searchService",
"showReadIndicator",
"streamFilters"
"streamFilters",
"lastReadPostNumber",
"highestPostNumber"
);
},

View File

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

View File

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

View File

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

View File

@ -352,15 +352,14 @@ const TopicTrackingState = EmberObject.extend({
isSeen !== state.is_seen
) {
const postsCount = topic.get("posts_count");
let newPosts = postsCount - state.highest_post_number,
unread = postsCount - state.last_read_post_number;
let unread;
if (newPosts < 0) {
newPosts = 0;
}
if (!state.last_read_post_number) {
if (state.last_read_post_number) {
unread = postsCount - state.last_read_post_number;
} else {
unread = 0;
}
if (unread < 0) {
unread = 0;
}
@ -368,8 +367,7 @@ const TopicTrackingState = EmberObject.extend({
topic.setProperties({
highest_post_number: state.highest_post_number,
last_read_post_number: state.last_read_post_number,
new_posts: newPosts,
unread: unread,
unread_posts: unread,
is_seen: state.is_seen,
unseen: !state.last_read_post_number && isUnseen(state),
});
@ -654,14 +652,13 @@ const TopicTrackingState = EmberObject.extend({
newState.topic_id = topic.id;
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
if (topic.unseen) {
newState.last_read_post_number = null;
} else if (topic.unread || topic.new_posts) {
} else if (topic.unread_posts) {
newState.last_read_post_number =
topic.highest_post_number -
((topic.unread || 0) + (topic.new_posts || 0));
topic.highest_post_number - (topic.unread_posts || 0);
} else {
// remove the topic if it is no longer unread/new (it has been seen)
// 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 { Promise } from "rsvp";
import RestModel from "discourse/models/rest";
import Session from "discourse/models/session";
import Site from "discourse/models/site";
import User from "discourse/models/user";
import { ajax } from "discourse/lib/ajax";
@ -21,6 +20,7 @@ import { longDate } from "discourse/lib/formatter";
import { popupAjaxError } from "discourse/lib/ajax-error";
import { resolveShareUrl } from "discourse/helpers/share-url";
import DiscourseURL, { userPath } from "discourse/lib/url";
import deprecated from "discourse-common/lib/deprecated";
export function loadTopicView(topic, args) {
const data = deepMerge({}, args);
@ -239,10 +239,16 @@ const Topic = RestModel.extend({
return url;
},
@discourseComputed("new_posts", "unread")
totalUnread(newPosts, unread) {
const count = (unread || 0) + (newPosts || 0);
return count > 0 ? count : null;
@discourseComputed("unread_posts", "new_posts")
totalUnread(unreadPosts, newPosts) {
deprecated("The totalUnread property of the topic model is deprecated");
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")
@ -284,25 +290,6 @@ const Topic = RestModel.extend({
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")
viewsHeat(v) {
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 I18n from "I18n";
import discourseComputed from "discourse-common/utils/decorators";
export default EmberObject.extend({
postCountsPresent: or("topic.unread", "topic.displayNewPosts"),
showBadges: and("postBadgesEnabled", "postCountsPresent"),
showBadges: and("postBadgesEnabled", "topic.unread_posts"),
@discourseComputed
newDotText() {

View File

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

View File

@ -1,5 +1,5 @@
{{raw "topic-status" topic=topic}}
<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>

View File

@ -11,7 +11,7 @@
{{#if topic.featured_link}}
{{topic-featured-link topic}}
{{/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 class="bottom-row">
{{category-link topic.category}}{{discourse-tags topic mode="list"}}{{! intentionally inline to avoid whitespace}}

View File

@ -1,8 +1,5 @@
{{#if unread }}
&nbsp;<a href={{url}} title={{i18n "topic.unread_posts" count=unread}} class="badge badge-notification unread">{{unread}}</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 displayUnreadPosts}}
&nbsp;<a href={{url}} title={{i18n "topic.unread_posts" count=displayUnreadPosts}} class="badge badge-notification unread-posts">{{displayUnreadPosts}}</a>
{{/if}}
{{#if unseen}}
&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}}
{{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}}
{{raw "list/posts-count-column" topic=topic tagName="div"}}
{{/if}}

View File

@ -29,7 +29,7 @@
topicId=topic.id
unreadClass=unreadClass~}}
{{~#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}}
</span>
<div class="link-bottom-line">

View File

@ -1,9 +1,9 @@
<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 ~}}
&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 unseen ~}}
&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
showReadIndicator=model.show_read_indicator
streamFilters=model.postStream.streamFilters
lastReadPostNumber=userLastReadPostNumber
highestPostNumber=highestPostNumber
showFlags=(action "showPostFlags")
editPost=(action "editPost")
showHistory=(route-action "showHistory")

View File

@ -186,17 +186,19 @@ export default createWidget("post-stream", {
tagName: "div.post-stream",
html(attrs) {
const posts = attrs.posts || [],
postArray = posts.toArray(),
result = [],
before = attrs.gaps && attrs.gaps.before ? attrs.gaps.before : {},
after = attrs.gaps && attrs.gaps.after ? attrs.gaps.after : {},
mobileView = this.site.mobileView;
const posts = attrs.posts || [];
const postArray = posts.toArray();
const postArrayLength = postArray.length;
const maxPostNumber = postArray[postArrayLength - 1].post_number;
const result = [];
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 prevDate;
for (let i = 0; i < postArray.length; i++) {
for (let i = 0; i < postArrayLength; i++) {
const post = postArray[i];
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;
}

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

View File

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

View File

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

View File

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

View File

@ -396,7 +396,7 @@ div.education {
border-top: 3px solid var(--primary-low);
border-bottom: 1px solid var(--primary-low);
.badge-notification.new-posts {
.badge-notification.unread-posts {
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
.topic-post {

View File

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

View File

@ -62,7 +62,7 @@
.badge-wrapper {
margin-right: 0.5em;
}
.badge-notification.new-posts {
.badge-notification.unread-posts {
display: block;
padding: 0;
}
@ -87,7 +87,7 @@
.topic-statuses {
margin-right: 0.15em;
}
.topic-post-badges .badge.new-posts,
.topic-post-badges .badge.unread-posts,
.title {
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
.topic-body {
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 {
&.new-posts,
&.unread-posts {
color: var(--secondary);
}

View File

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

View File

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

View File

@ -352,7 +352,7 @@ class PostMover
}
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,
notifications_changed_at, notifications_reason_id)
SELECT tu.user_id,
@ -370,12 +370,6 @@ class PostMover
WHERE lr.old_topic_id = tu.topic_id
AND lr.old_post_number <= tu.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)
FROM moved_posts le
@ -392,7 +386,6 @@ class PostMover
WHERE tu.topic_id = :old_topic_id
AND GREATEST(
tu.last_read_post_number,
tu.highest_seen_post_number,
tu.last_emailed_post_number
) >= (SELECT MIN(old_post_number) FROM moved_posts)
ON CONFLICT (topic_id, user_id) DO UPDATE
@ -409,18 +402,6 @@ class PostMover
GREATEST(topic_users.last_read_post_number,
excluded.last_read_post_number)
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
WHEN topic_users.last_emailed_post_number = :old_highest_staff_post_number OR (
:old_highest_post_number < :old_highest_staff_post_number

View File

@ -71,7 +71,6 @@ class PostTiming < ActiveRecord::Base
end
TopicUser.where(user_id: user.id, topic_id: topic.id).update_all(
highest_seen_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
WHEN last_read_post_number > :highest THEN :highest
ELSE last_read_post_number
END,
highest_seen_post_number = CASE
WHEN highest_seen_post_number > :highest THEN :highest
ELSE highest_seen_post_number
END
END
WHERE topic_id = :topic_id
SQL
end

View File

@ -1,6 +1,10 @@
# frozen_string_literal: true
class TopicUser < ActiveRecord::Base
self.ignored_columns = [
:highest_seen_post_number # Remove after 01 Jan 2022
]
belongs_to :user
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
# it then creates the row instead.
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.
topic_id = topic_id.id if topic_id.is_a?(::Topic)
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(", ")
vals = attrs_array.map { |t| t[1] }
rows = TopicUser.where(topic_id: topic_id, user_id: user_id).update_all([attrs_sql, *vals])
if rows == 0
@ -252,7 +262,6 @@ class TopicUser < ActiveRecord::Base
UPDATE_TOPIC_USER_SQL = "UPDATE topic_users
SET
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),
notification_level =
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")
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)
SELECT :user_id, :topic_id, :post_number, ft.highest_post_number, :now, :now, :new_status
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, :now, :now, :new_status
FROM topics AS ft
JOIN users u on u.id = :user_id
WHERE ft.id = :topic_id
@ -303,11 +312,6 @@ class TopicUser < ActiveRecord::Base
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
rows =
if user.staff?
@ -424,12 +428,11 @@ class TopicUser < ActiveRecord::Base
builder.exec(action_type_id: PostActionType.types[action_type])
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)
sql = <<SQL
UPDATE topic_users tu
SET last_read_post_number = max_number,
highest_seen_post_number = max_number
SET last_read_post_number = max_number
FROM (
SELECT MAX(post_number) max_number, p.topic_id FROM posts p
WHERE deleted_at IS NULL
@ -456,8 +459,7 @@ SQL
builder = DB.build <<~SQL
UPDATE topic_users t
SET
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))
last_read_post_number = LEAST(GREATEST(last_read, last_read_post_number), max_post_number)
FROM (
SELECT topic_id, user_id, MAX(post_number) last_read
FROM post_timings
@ -474,8 +476,7 @@ SQL
X.topic_id = t.topic_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
highest_seen_post_number <> LEAST(max_post_number,GREATEST(t.highest_seen_post_number, last_read))
last_read_post_number <> LEAST(GREATEST(last_read, last_read_post_number), max_post_number)
)
SQL
@ -496,7 +497,6 @@ end
# topic_id :integer not null
# posted :boolean default(FALSE), not null
# last_read_post_number :integer
# highest_seen_post_number :integer
# last_visited_at :datetime
# first_visited_at :datetime
# notification_level :integer default(1), not null

View File

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

View File

@ -752,7 +752,12 @@ class PostAlerter
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|
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."
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>'
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:
latest: "There are no more latest topics."
posted: "There are no more posted topics."
@ -2457,15 +2457,9 @@ en:
not_found:
title: "Topic not found"
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"
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:
one: "there is %{count} like 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,
posted: true,
last_read_post_number: @post.post_number,
highest_seen_post_number: @post.post_number,
last_posted_at: Time.zone.now)
# 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..."
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)
SELECT user_id, topic_id, 't' , MAX(post_number), MAX(post_number), MIN(created_at), MAX(created_at), COUNT(id) * #{MS_SPEND_CREATING_POST}
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), MIN(created_at), MAX(created_at), COUNT(id) * #{MS_SPEND_CREATING_POST}
FROM posts
WHERE user_id > 0
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"],
["posts", "reply_to_post_number"],
["topic_users", "last_read_post_number"],
["topic_users", "highest_seen_post_number"],
["topic_users", "last_emailed_post_number"],
].each do |table, column|
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'
sql = <<~SQL
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
WHERE t.id = tu.topic_id AND tu.user_id = :user_id AND t.id IN (:topic_ids)
SQL

View File

@ -2,7 +2,7 @@
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)
@guardian = guardian
@ -11,29 +11,27 @@ class Unread
end
def unread_posts
return 0 if do_not_notify?(@topic_user.notification_level)
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 @topic_user.last_read_post_number.blank?
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
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)
new_posts = 0 if new_posts < 0
new_posts
unread = (highest_post_number - @topic_user.last_read_post_number)
unread = 0 if unread < 0
unread
end
protected
DO_NOT_NOTIFY_LEVELS = [
TopicUser.notification_levels[:muted],
TopicUser.notification_levels[:regular]
]
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

View File

@ -192,7 +192,7 @@ after_initialize do
return if topic_id.blank? || data[:track] != DiscourseNarrativeBot::NewUserNarrative.to_s
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)
return if topic.blank?

View File

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

View File

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

View File

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

View File

@ -762,8 +762,8 @@ class ImportScripts::Base
puts "", "Updating topic users"
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)
SELECT user_id, topic_id, 't' , MAX(post_number), MAX(post_number), MIN(created_at), MAX(created_at), COUNT(id) * 5000
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), MIN(created_at), MAX(created_at), COUNT(id) * 5000
FROM posts
WHERE user_id > 0
GROUP BY user_id, topic_id

View File

@ -598,8 +598,7 @@ class ImportScripts::Telligent < ImportScripts::Base
# Mark all imported messages as read
DB.exec(<<~SQL)
UPDATE topic_users tu
SET last_read_post_number = t.highest_post_number,
highest_seen_post_number = t.highest_post_number
SET last_read_post_number = t.highest_post_number
FROM topics t
JOIN topic_custom_fields tcf ON t.id = tcf.topic_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
expect(topic_user.last_read_post_number).to eq(1)
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

View File

@ -82,7 +82,6 @@ describe TopicsBulkAction do
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.highest_seen_post_number).to eq(3)
end
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)
expect(tu.last_read_post_number).to eq(4)
expect(tu.highest_seen_post_number).to eq(4)
end
end
end

View File

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

File diff suppressed because one or more lines are too long

View File

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

View File

@ -1615,7 +1615,12 @@ describe Topic do
end
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
topic.change_category_to_id(new_category.id)
end.to change { Notification.count }.by(2)

View File

@ -597,7 +597,6 @@ describe TopicTrackingState do
tracking = {
notification_level: TopicUser.notification_levels[:tracking],
last_read_post_number: 1,
highest_seen_post_number: 1
}
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)
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)
[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)
expect(tu.last_read_post_number).to eq(p2.post_number)
expect(tu.highest_seen_post_number).to eq(2)
end

View File

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

View File

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

View File

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

View File

@ -1010,7 +1010,6 @@ RSpec.describe TopicsController do
topic_user.update!(
last_read_post_number: 2,
highest_seen_post_number: 2
)
# 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: 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
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"
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

View File

@ -1259,7 +1259,13 @@ describe PostAlerter do
fab!(:category) { Fabricate(:category) }
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])
PostAlerter.new.notify_post_users(post, [])
expect(Notification.count).to eq(1)
@ -1280,7 +1286,7 @@ describe PostAlerter do
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
# Sidekiq.