diff --git a/app/assets/javascripts/discourse/app/controllers/topic.js b/app/assets/javascripts/discourse/app/controllers/topic.js
index ae881ebfaf4a2..f48dda9823199 100644
--- a/app/assets/javascripts/discourse/app/controllers/topic.js
+++ b/app/assets/javascripts/discourse/app/controllers/topic.js
@@ -154,10 +154,14 @@ export default Controller.extend(bufferedProperty("model"), {
showCategoryChooser: not("model.isPrivateMessage"),
gotoInbox(name) {
- let url = userPath(this.get("currentUser.username_lower") + "/messages");
+ let url = userPath(`${this.get("currentUser.username_lower")}/messages`);
+
if (name) {
- url = url + "/group/" + name;
+ url = `${url}/group/${name}`;
+ } else {
+ url = `${url}/personal`;
}
+
DiscourseURL.routeTo(url);
},
diff --git a/app/assets/javascripts/discourse/app/controllers/user-private-messages.js b/app/assets/javascripts/discourse/app/controllers/user-private-messages.js
index 22f99d27bf706..bcdece0d7e9fd 100644
--- a/app/assets/javascripts/discourse/app/controllers/user-private-messages.js
+++ b/app/assets/javascripts/discourse/app/controllers/user-private-messages.js
@@ -3,6 +3,10 @@ import { action } from "@ember/object";
import { alias, and, equal } from "@ember/object/computed";
import discourseComputed from "discourse-common/utils/decorators";
import { VIEW_NAME_WARNINGS } from "discourse/routes/user-private-messages-warnings";
+import I18n from "I18n";
+
+export const PERSONAL_INBOX = "__personal_inbox__";
+const ALL_INBOX = "__all_inbox__";
export default Controller.extend({
user: controller(),
@@ -10,19 +14,101 @@ export default Controller.extend({
pmView: false,
viewingSelf: alias("user.viewingSelf"),
isGroup: equal("pmView", "groups"),
+ group: null,
+ groupFilter: alias("group.name"),
currentPath: alias("router._router.currentPath"),
pmTaggingEnabled: alias("site.can_tag_pms"),
tagId: null,
showNewPM: and("user.viewingSelf", "currentUser.can_send_private_messages"),
+ @discourseComputed("inboxes", "isAllInbox")
+ displayGlobalFilters(inboxes, isAllInbox) {
+ if (inboxes.length === 0) {
+ return true;
+ }
+ if (inboxes.length && isAllInbox) {
+ return true;
+ }
+ return false;
+ },
+
+ @discourseComputed("inboxes")
+ sectionClass(inboxes) {
+ const defaultClass = "user-secondary-navigation user-messages";
+
+ return inboxes.length
+ ? `${defaultClass} user-messages-inboxes`
+ : defaultClass;
+ },
+
+ @discourseComputed("pmView")
+ isPersonalInbox(pmView) {
+ return pmView && pmView.startsWith("personal");
+ },
+
+ @discourseComputed("isPersonalInbox", "group.name")
+ isAllInbox(isPersonalInbox, groupName) {
+ return !this.isPersonalInbox && !groupName;
+ },
+
+ @discourseComputed("isPersonalInbox", "group.name")
+ selectedInbox(isPersonalInbox, groupName) {
+ if (groupName) {
+ return groupName;
+ }
+
+ return isPersonalInbox ? PERSONAL_INBOX : ALL_INBOX;
+ },
+
@discourseComputed("viewingSelf", "pmView", "currentUser.admin")
showWarningsWarning(viewingSelf, pmView, isAdmin) {
return pmView === VIEW_NAME_WARNINGS && !viewingSelf && !isAdmin;
},
+ @discourseComputed("model.groups")
+ inboxes(groups) {
+ const groupsWithMessages = groups?.filter((group) => {
+ return group.has_messages;
+ });
+
+ if (!groupsWithMessages || groupsWithMessages.length === 0) {
+ return [];
+ }
+
+ const inboxes = [];
+
+ inboxes.push({
+ id: ALL_INBOX,
+ name: I18n.t("user.messages.all"),
+ });
+
+ inboxes.push({
+ id: PERSONAL_INBOX,
+ name: I18n.t("user.messages.personal"),
+ icon: "envelope",
+ });
+
+ groupsWithMessages.forEach((group) => {
+ inboxes.push({ id: group.name, name: group.name, icon: "users" });
+ });
+
+ return inboxes;
+ },
+
@action
changeGroupNotificationLevel(notificationLevel) {
this.group.setNotification(notificationLevel, this.get("user.model.id"));
},
+
+ @action
+ updateInbox(inbox) {
+ if (inbox === ALL_INBOX) {
+ this.transitionToRoute("userPrivateMessages.index");
+ } else if (inbox === PERSONAL_INBOX) {
+ this.transitionToRoute("userPrivateMessages.personal");
+ } else {
+ this.transitionToRoute("userPrivateMessages.group", inbox);
+ }
+ },
});
diff --git a/app/assets/javascripts/discourse/app/lib/cached-topic-list.js b/app/assets/javascripts/discourse/app/lib/cached-topic-list.js
index 9ff608c2e649d..38148314d3ce0 100644
--- a/app/assets/javascripts/discourse/app/lib/cached-topic-list.js
+++ b/app/assets/javascripts/discourse/app/lib/cached-topic-list.js
@@ -1,5 +1,6 @@
export function findOrResetCachedTopicList(session, filter) {
const lastTopicList = session.get("topicList");
+
if (lastTopicList && lastTopicList.filter === filter) {
return lastTopicList;
} else {
diff --git a/app/assets/javascripts/discourse/app/routes/app-route-map.js b/app/assets/javascripts/discourse/app/routes/app-route-map.js
index 9bbb446410bc0..42eb98b454086 100644
--- a/app/assets/javascripts/discourse/app/routes/app-route-map.js
+++ b/app/assets/javascripts/discourse/app/routes/app-route-map.js
@@ -140,11 +140,20 @@ export default function () {
"userPrivateMessages",
{ path: "/messages", resetNamespace: true },
function () {
- this.route("sent");
+ this.route("new");
+ this.route("unread");
this.route("archive");
+ this.route("sent");
+ this.route("personal");
+ this.route("personalSent", { path: "personal/sent" });
+ this.route("personalNew", { path: "personal/new" });
+ this.route("personalUnread", { path: "personal/unread" });
+ this.route("personalArchive", { path: "personal/archive" });
this.route("warnings");
this.route("group", { path: "group/:name" });
this.route("groupArchive", { path: "group/:name/archive" });
+ this.route("groupNew", { path: "group/:name/new" });
+ this.route("groupUnread", { path: "group/:name/unread" });
this.route("tags");
this.route("tagsShow", { path: "tags/:id" });
}
diff --git a/app/assets/javascripts/discourse/app/routes/build-private-messages-group-route.js b/app/assets/javascripts/discourse/app/routes/build-private-messages-group-route.js
new file mode 100644
index 0000000000000..d1aa356eebf65
--- /dev/null
+++ b/app/assets/javascripts/discourse/app/routes/build-private-messages-group-route.js
@@ -0,0 +1,68 @@
+import I18n from "I18n";
+import createPMRoute from "discourse/routes/build-private-messages-route";
+import { findOrResetCachedTopicList } from "discourse/lib/cached-topic-list";
+
+export default (viewName, channel) => {
+ return createPMRoute("groups", "private-messages-groups").extend({
+ groupName: null,
+
+ titleToken() {
+ const groupName = this.groupName;
+
+ if (groupName) {
+ let title = groupName.capitalize();
+
+ if (viewName !== "index") {
+ title = `${title} ${I18n.t("user.messages." + viewName)}`;
+ }
+
+ return [title, I18n.t(`user.private_messages`)];
+ }
+ },
+
+ model(params) {
+ const username = this.modelFor("user").get("username_lower");
+ let filter = `topics/private-messages-group/${username}/${params.name}`;
+
+ if (viewName !== "index") {
+ filter = `${filter}/${viewName}`;
+ }
+
+ const lastTopicList = findOrResetCachedTopicList(this.session, filter);
+
+ return lastTopicList
+ ? lastTopicList
+ : this.store.findFiltered("topicList", { filter });
+ },
+
+ afterModel(model) {
+ const filters = model.get("filter").split("/");
+ let groupName;
+
+ if (viewName !== "index") {
+ groupName = filters[filters.length - 2];
+ } else {
+ groupName = filters.pop();
+ }
+
+ const group = this.modelFor("user")
+ .get("groups")
+ .filterBy("name", groupName)[0];
+
+ this.setProperties({ groupName: groupName, group });
+ },
+
+ setupController() {
+ this._super.apply(this, arguments);
+ this.controllerFor("user-private-messages").set("group", this.group);
+
+ if (channel) {
+ this.controllerFor("user-topics-list").subscribe(
+ `/private-messages/group/${this.get(
+ "groupName"
+ ).toLowerCase()}/${channel}`
+ );
+ }
+ },
+ });
+};
diff --git a/app/assets/javascripts/discourse/app/routes/build-private-messages-route.js b/app/assets/javascripts/discourse/app/routes/build-private-messages-route.js
index b62178ec0ac33..2eed30a80da0a 100644
--- a/app/assets/javascripts/discourse/app/routes/build-private-messages-route.js
+++ b/app/assets/javascripts/discourse/app/routes/build-private-messages-route.js
@@ -23,7 +23,9 @@ export default (viewName, path, channel) => {
model() {
const filter =
"topics/" + path + "/" + this.modelFor("user").get("username_lower");
+
const lastTopicList = findOrResetCachedTopicList(this.session, filter);
+
return lastTopicList
? lastTopicList
: this.store.findFiltered("topicList", { filter });
@@ -49,6 +51,7 @@ export default (viewName, path, channel) => {
this.controllerFor("user-private-messages").setProperties({
archive: false,
pmView: viewName,
+ group: null,
});
this.searchService.set("contextType", "private_messages");
diff --git a/app/assets/javascripts/discourse/app/routes/user-private-messages-archive.js b/app/assets/javascripts/discourse/app/routes/user-private-messages-archive.js
index 81981c9863163..d12d153a5bbee 100644
--- a/app/assets/javascripts/discourse/app/routes/user-private-messages-archive.js
+++ b/app/assets/javascripts/discourse/app/routes/user-private-messages-archive.js
@@ -1,3 +1,7 @@
import createPMRoute from "discourse/routes/build-private-messages-route";
-export default createPMRoute("archive", "private-messages-archive", "archive");
+export default createPMRoute(
+ "archive",
+ "private-messages-all-archive",
+ "archive"
+);
diff --git a/app/assets/javascripts/discourse/app/routes/user-private-messages-group-archive.js b/app/assets/javascripts/discourse/app/routes/user-private-messages-group-archive.js
index e1c743452f6b1..42b0d95c916d2 100644
--- a/app/assets/javascripts/discourse/app/routes/user-private-messages-group-archive.js
+++ b/app/assets/javascripts/discourse/app/routes/user-private-messages-group-archive.js
@@ -1,48 +1,3 @@
-import I18n from "I18n";
-import createPMRoute from "discourse/routes/build-private-messages-route";
-import { findOrResetCachedTopicList } from "discourse/lib/cached-topic-list";
+import createPMRoute from "discourse/routes/build-private-messages-group-route";
-export default createPMRoute("groups", "private-messages-groups").extend({
- groupName: null,
-
- titleToken() {
- const groupName = this.groupName;
-
- if (groupName) {
- return [
- `${groupName.capitalize()} ${I18n.t("user.messages.archive")}`,
- I18n.t("user.private_messages"),
- ];
- }
- },
-
- model(params) {
- const username = this.modelFor("user").get("username_lower");
- const filter = `topics/private-messages-group/${username}/${params.name}/archive`;
- const lastTopicList = findOrResetCachedTopicList(this.session, filter);
- return lastTopicList
- ? lastTopicList
- : this.store.findFiltered("topicList", { filter });
- },
-
- afterModel(model) {
- const split = model.get("filter").split("/");
- const groupName = split[split.length - 2];
- this.set("groupName", groupName);
- const group = this.modelFor("user")
- .get("groups")
- .filterBy("name", groupName)[0];
- this.controllerFor("user-private-messages").set("group", group);
- },
-
- setupController(controller, model) {
- this._super.apply(this, arguments);
- const split = model.get("filter").split("/");
- const group = split[split.length - 2];
- this.controllerFor("user-private-messages").set("groupFilter", group);
- this.controllerFor("user-private-messages").set("archive", true);
- this.controllerFor("user-topics-list").subscribe(
- `/private-messages/group/${group}/archive`
- );
- },
-});
+export default createPMRoute("archive", "archive");
diff --git a/app/assets/javascripts/discourse/app/routes/user-private-messages-group-new.js b/app/assets/javascripts/discourse/app/routes/user-private-messages-group-new.js
new file mode 100644
index 0000000000000..25d9693cbed2b
--- /dev/null
+++ b/app/assets/javascripts/discourse/app/routes/user-private-messages-group-new.js
@@ -0,0 +1,3 @@
+import createPMRoute from "discourse/routes/build-private-messages-group-route";
+
+export default createPMRoute("new", null /* no message bus notifications */);
diff --git a/app/assets/javascripts/discourse/app/routes/user-private-messages-group-unread.js b/app/assets/javascripts/discourse/app/routes/user-private-messages-group-unread.js
new file mode 100644
index 0000000000000..9bee9aa999431
--- /dev/null
+++ b/app/assets/javascripts/discourse/app/routes/user-private-messages-group-unread.js
@@ -0,0 +1,3 @@
+import createPMRoute from "discourse/routes/build-private-messages-group-route";
+
+export default createPMRoute("unread", null /* no message bus notifications */);
diff --git a/app/assets/javascripts/discourse/app/routes/user-private-messages-group.js b/app/assets/javascripts/discourse/app/routes/user-private-messages-group.js
index 022d12de16023..ea93a1fa9192c 100644
--- a/app/assets/javascripts/discourse/app/routes/user-private-messages-group.js
+++ b/app/assets/javascripts/discourse/app/routes/user-private-messages-group.js
@@ -1,42 +1,3 @@
-import I18n from "I18n";
-import createPMRoute from "discourse/routes/build-private-messages-route";
-import { findOrResetCachedTopicList } from "discourse/lib/cached-topic-list";
+import createPMRoute from "discourse/routes/build-private-messages-group-route";
-export default createPMRoute("groups", "private-messages-groups").extend({
- groupName: null,
-
- titleToken() {
- const groupName = this.groupName;
- if (groupName) {
- return [groupName.capitalize(), I18n.t("user.private_messages")];
- }
- },
-
- model(params) {
- const username = this.modelFor("user").get("username_lower");
- const filter = `topics/private-messages-group/${username}/${params.name}`;
- const lastTopicList = findOrResetCachedTopicList(this.session, filter);
- return lastTopicList
- ? lastTopicList
- : this.store.findFiltered("topicList", { filter });
- },
-
- afterModel(model) {
- const groupName = model.get("filter").split("/").pop();
- this.set("groupName", groupName);
- const group = this.modelFor("user")
- .get("groups")
- .filterBy("name", groupName)[0];
- this.controllerFor("user-private-messages").set("group", group);
- },
-
- setupController(controller, model) {
- this._super.apply(this, arguments);
- const group = model.get("filter").split("/").pop();
- this.controllerFor("user-private-messages").set("groupFilter", group);
- this.controllerFor("user-private-messages").set("archive", false);
- this.controllerFor("user-topics-list").subscribe(
- `/private-messages/group/${group}`
- );
- },
-});
+export default createPMRoute("index", "inbox");
diff --git a/app/assets/javascripts/discourse/app/routes/user-private-messages-index.js b/app/assets/javascripts/discourse/app/routes/user-private-messages-index.js
index 322f8ace40f11..e973160ef8fa6 100644
--- a/app/assets/javascripts/discourse/app/routes/user-private-messages-index.js
+++ b/app/assets/javascripts/discourse/app/routes/user-private-messages-index.js
@@ -1,3 +1,3 @@
import createPMRoute from "discourse/routes/build-private-messages-route";
-export default createPMRoute("index", "private-messages", "inbox");
+export default createPMRoute("index", "private-messages-all", "inbox");
diff --git a/app/assets/javascripts/discourse/app/routes/user-private-messages-new.js b/app/assets/javascripts/discourse/app/routes/user-private-messages-new.js
new file mode 100644
index 0000000000000..b6f538f6e5044
--- /dev/null
+++ b/app/assets/javascripts/discourse/app/routes/user-private-messages-new.js
@@ -0,0 +1,7 @@
+import createPMRoute from "discourse/routes/build-private-messages-route";
+
+export default createPMRoute(
+ "new",
+ "private-messages-all-new",
+ null /* no message bus notifications */
+);
diff --git a/app/assets/javascripts/discourse/app/routes/user-private-messages-personal-archive.js b/app/assets/javascripts/discourse/app/routes/user-private-messages-personal-archive.js
new file mode 100644
index 0000000000000..1999282a5cee7
--- /dev/null
+++ b/app/assets/javascripts/discourse/app/routes/user-private-messages-personal-archive.js
@@ -0,0 +1,3 @@
+import createPMRoute from "discourse/routes/build-private-messages-route";
+
+export default createPMRoute("personal", "private-messages-archive", "archive");
diff --git a/app/assets/javascripts/discourse/app/routes/user-private-messages-personal-new.js b/app/assets/javascripts/discourse/app/routes/user-private-messages-personal-new.js
new file mode 100644
index 0000000000000..e1bb8d75fab57
--- /dev/null
+++ b/app/assets/javascripts/discourse/app/routes/user-private-messages-personal-new.js
@@ -0,0 +1,7 @@
+import createPMRoute from "discourse/routes/build-private-messages-route";
+
+export default createPMRoute(
+ "personal",
+ "private-messages-new",
+ null /* no message bus notifications */
+);
diff --git a/app/assets/javascripts/discourse/app/routes/user-private-messages-personal-sent.js b/app/assets/javascripts/discourse/app/routes/user-private-messages-personal-sent.js
new file mode 100644
index 0000000000000..a4c68b3a01567
--- /dev/null
+++ b/app/assets/javascripts/discourse/app/routes/user-private-messages-personal-sent.js
@@ -0,0 +1,3 @@
+import createPMRoute from "discourse/routes/build-private-messages-route";
+
+export default createPMRoute("personal", "private-messages-sent", "sent");
diff --git a/app/assets/javascripts/discourse/app/routes/user-private-messages-personal-unread.js b/app/assets/javascripts/discourse/app/routes/user-private-messages-personal-unread.js
new file mode 100644
index 0000000000000..24dd211d32ec8
--- /dev/null
+++ b/app/assets/javascripts/discourse/app/routes/user-private-messages-personal-unread.js
@@ -0,0 +1,7 @@
+import createPMRoute from "discourse/routes/build-private-messages-route";
+
+export default createPMRoute(
+ "personal",
+ "private-messages-unread",
+ null /* no message bus notifications */
+);
diff --git a/app/assets/javascripts/discourse/app/routes/user-private-messages-personal.js b/app/assets/javascripts/discourse/app/routes/user-private-messages-personal.js
new file mode 100644
index 0000000000000..7a13e5d5211af
--- /dev/null
+++ b/app/assets/javascripts/discourse/app/routes/user-private-messages-personal.js
@@ -0,0 +1,3 @@
+import createPMRoute from "discourse/routes/build-private-messages-route";
+
+export default createPMRoute("personal", "private-messages", "inbox");
diff --git a/app/assets/javascripts/discourse/app/routes/user-private-messages-sent.js b/app/assets/javascripts/discourse/app/routes/user-private-messages-sent.js
index b3fa314e3ddef..6ed2c2d14bebb 100644
--- a/app/assets/javascripts/discourse/app/routes/user-private-messages-sent.js
+++ b/app/assets/javascripts/discourse/app/routes/user-private-messages-sent.js
@@ -1,3 +1,7 @@
import createPMRoute from "discourse/routes/build-private-messages-route";
-export default createPMRoute("sent", "private-messages-sent", "sent");
+export default createPMRoute(
+ "sent",
+ "private-messages-all-sent",
+ null /* no message bus notifications */
+);
diff --git a/app/assets/javascripts/discourse/app/routes/user-private-messages-unread.js b/app/assets/javascripts/discourse/app/routes/user-private-messages-unread.js
new file mode 100644
index 0000000000000..29b1aba2a8ed9
--- /dev/null
+++ b/app/assets/javascripts/discourse/app/routes/user-private-messages-unread.js
@@ -0,0 +1,7 @@
+import createPMRoute from "discourse/routes/build-private-messages-route";
+
+export default createPMRoute(
+ "unread",
+ "private-messages-all-unread",
+ null /* no message bus notifications */
+);
diff --git a/app/assets/javascripts/discourse/app/routes/user-private-messages.js b/app/assets/javascripts/discourse/app/routes/user-private-messages.js
index bedaa51e073e2..34e8b488c1d84 100644
--- a/app/assets/javascripts/discourse/app/routes/user-private-messages.js
+++ b/app/assets/javascripts/discourse/app/routes/user-private-messages.js
@@ -12,9 +12,11 @@ export default DiscourseRoute.extend({
},
setupController(controller, user) {
- const composerController = this.controllerFor("composer");
controller.set("model", user);
+
if (this.currentUser) {
+ const composerController = this.controllerFor("composer");
+
Draft.get("new_private_message").then((data) => {
if (data.draft) {
composerController.open({
diff --git a/app/assets/javascripts/discourse/app/templates/user/messages.hbs b/app/assets/javascripts/discourse/app/templates/user/messages.hbs
index 5a845eafdc210..3ac8f46b6592f 100644
--- a/app/assets/javascripts/discourse/app/templates/user/messages.hbs
+++ b/app/assets/javascripts/discourse/app/templates/user/messages.hbs
@@ -1,81 +1,148 @@
-{{#d-section class="user-secondary-navigation" pageClass="user-messages"}}
- {{#unless site.mobileView}}
- {{#if showNewPM}}
- {{d-button class="btn-primary new-private-message" action=(route-action "composePrivateMessage") icon="envelope" label="user.new_private_message"}}
- {{/if}}
- {{/unless}}
+{{#d-section class=sectionClass pageClass="user-messages"}}
+ {{#if inboxes.length}}
+
+ {{combo-box
+ content=inboxes
+ classNames="user-messages-inboxes-drop"
+ value=selectedInbox
+ onChange=(action "updateInbox")
+ options=(hash
+ filterable=true
+ )
+ }}
+ {{#if (and group site.mobileView)}}
+ {{group-notifications-button
+ value=group.group_user.notification_level
+ onChange=(action "changeGroupNotificationLevel")
+ }}
+ {{/if}}
+
+ {{/if}}
{{#mobile-nav class="messages-nav" desktopClass="nav-stacked action-list"}}
-
- {{#link-to "userPrivateMessages.index" model}}
- {{i18n "user.messages.inbox"}}
- {{/link-to}}
-
-
- {{#link-to "userPrivateMessages.sent" model}}
- {{i18n "user.messages.sent"}}
- {{/link-to}}
-
-
- {{#link-to "userPrivateMessages.archive" model}}
- {{i18n "user.messages.archive"}}
- {{/link-to}}
-
- {{plugin-outlet name="user-messages-nav" connectorTagName="li" args=(hash model=model)}}
- {{#each model.groups as |group|}}
- {{#if group.has_messages}}
-
- {{#link-to "userPrivateMessages.group" group.name}}
- {{d-icon "users"}}
- {{capitalize-string group.name}}
- {{/link-to}}
-
-
- {{#link-to "userPrivateMessages.groupArchive" group.name}}
- {{i18n "user.messages.archive"}}
- {{/link-to}}
-
- {{/if}}
- {{/each}}
+ {{#if isAllInbox}}
+
+ {{#link-to "userPrivateMessages.index" model}}
+ {{i18n "user.messages.latest"}}
+ {{/link-to}}
+
+
+ {{#link-to "userPrivateMessages.sent" model}}
+ {{i18n "user.messages.sent"}}
+ {{/link-to}}
+
+
+ {{#link-to "userPrivateMessages.new" model}}
+ {{i18n "user.messages.new"}}
+ {{/link-to}}
+
+
+ {{#link-to "userPrivateMessages.unread" model}}
+ {{i18n "user.messages.unread"}}
+ {{/link-to}}
+
+
+ {{#link-to "userPrivateMessages.archive" model}}
+ {{i18n "user.messages.archive"}}
+ {{/link-to}}
+
+ {{/if}}
+
+ {{#if group}}
+
+ {{#link-to "userPrivateMessages.group" group.name}}
+ {{i18n "user.messages.latest"}}
+ {{/link-to}}
+
+
+ {{#link-to "userPrivateMessages.groupNew" group.name}}
+ {{i18n "user.messages.new"}}
+ {{/link-to}}
+
+
+ {{#link-to "userPrivateMessages.groupUnread" group.name}}
+ {{i18n "user.messages.unread"}}
+ {{/link-to}}
+
+
+ {{#link-to "userPrivateMessages.groupArchive" group.name}}
+ {{i18n "user.messages.archive"}}
+ {{/link-to}}
+
+ {{/if}}
- {{#if pmTaggingEnabled}}
+ {{#if isPersonalInbox}}
+
+ {{#link-to "userPrivateMessages.personal" model}}
+ {{i18n "user.messages.latest"}}
+ {{/link-to}}
+
+
+ {{#link-to "userPrivateMessages.personalSent" model}}
+ {{i18n "user.messages.sent"}}
+ {{/link-to}}
+
+
+ {{#link-to "userPrivateMessages.personalNew" model}}
+ {{i18n "user.messages.new"}}
+ {{/link-to}}
+
- {{#link-to "userPrivateMessages.tags" model}}
- {{i18n "user.messages.tags"}}
+ {{#link-to "userPrivateMessages.personalUnread" model}}
+ {{i18n "user.messages.unread"}}
{{/link-to}}
- {{#if tagId}}
-
- {{#link-to "userPrivateMessages.tagsShow" tagId}}
- {{tagId}}
+
+ {{#link-to "userPrivateMessages.personalArchive" model}}
+ {{i18n "user.messages.archive"}}
+ {{/link-to}}
+
+ {{/if}}
+
+ {{#if displayGlobalFilters}}
+ {{#if pmTaggingEnabled}}
+
+ {{#link-to "userPrivateMessages.tags" model}}
+ {{i18n "user.messages.tags"}}
{{/link-to}}
+
+ {{#if tagId}}
+
+ {{#link-to "userPrivateMessages.tagsShow" tagId}}
+ {{tagId}}
+ {{/link-to}}
+
+ {{/if}}
{{/if}}
+
+ {{plugin-outlet name="user-messages-nav" connectorTagName="li" args=(hash model=model)}}
{{/if}}
{{/mobile-nav}}
{{/d-section}}
-
-
- {{#if site.mobileView}}
- {{#if showNewPM}}
- {{d-button
- class="btn-primary new-private-message"
- action=(route-action "composePrivateMessage")
- icon="envelope"
- label="user.new_private_message"}}
- {{/if}}
- {{/if}}
+{{#if (and site.mobileView showNewPM)}}
+ {{d-button class="btn-primary new-private-message" action=(route-action "composePrivateMessage") icon="envelope" label="user.new_private_message"}}
+{{/if}}
- {{#if isGroup}}
+{{#unless site.mobileView}}
+
+ {{#if group}}
{{group-notifications-button
value=group.group_user.notification_level
onChange=(action "changeGroupNotificationLevel")
}}
{{/if}}
-
+ {{#if showNewPM}}
+ {{d-button class="btn-primary new-private-message" action=(route-action "composePrivateMessage") icon="envelope" label="user.new_private_message"}}
+ {{/if}}
+
+{{/unless}}
+
+
{{#if showWarningsWarning}}
{{html-safe (i18n "admin.user.warnings_list_warning")}}
{{/if}}
+
{{outlet}}
diff --git a/app/assets/javascripts/discourse/tests/acceptance/user-private-messages-test.js b/app/assets/javascripts/discourse/tests/acceptance/user-private-messages-test.js
new file mode 100644
index 0000000000000..b73c982025c63
--- /dev/null
+++ b/app/assets/javascripts/discourse/tests/acceptance/user-private-messages-test.js
@@ -0,0 +1,131 @@
+import {
+ acceptance,
+ count,
+ exists,
+} from "discourse/tests/helpers/qunit-helpers";
+import { visit } from "@ember/test-helpers";
+import { test } from "qunit";
+import selectKit from "discourse/tests/helpers/select-kit-helper";
+import { PERSONAL_INBOX } from "discourse/controllers/user-private-messages";
+
+acceptance(
+ "User Private Messages - user with no group messages",
+ function (needs) {
+ needs.user();
+
+ needs.site({
+ can_tag_pms: true,
+ });
+
+ test("viewing messages", async function (assert) {
+ await visit("/u/eviltrout/messages");
+
+ assert.equal(count(".topic-list-item"), 1, "displays the topic list");
+
+ assert.ok(
+ !exists(".user-messages-inboxes-drop"),
+ "does not display inboxes dropdown"
+ );
+
+ assert.ok(exists(".messages-nav .tags"), "displays the tags filter");
+
+ assert.ok(
+ !exists(".group-notifications-button"),
+ "displays the group notifications button"
+ );
+ });
+ }
+);
+
+acceptance(
+ "User Private Messages - user with group messages",
+ function (needs) {
+ needs.user();
+
+ needs.site({
+ can_tag_pms: true,
+ });
+
+ needs.pretender((server, helper) => {
+ server.get("/topics/private-messages-all/:username.json", () => {
+ return helper.response({
+ topic_list: {
+ topics: [
+ { id: 1, posters: [] },
+ { id: 2, posters: [] },
+ { id: 3, posters: [] },
+ ],
+ },
+ });
+ });
+
+ server.get(
+ "/topics/private-messages-group/:username/:group_name.json",
+ () => {
+ return helper.response({
+ topic_list: {
+ topics: [
+ { id: 1, posters: [] },
+ { id: 2, posters: [] },
+ ],
+ },
+ });
+ }
+ );
+ });
+
+ test("viewing messages", async function (assert) {
+ await visit("/u/charlie/messages");
+
+ assert.equal(
+ count(".topic-list-item"),
+ 3,
+ "displays the right topic list"
+ );
+
+ assert.ok(
+ exists(".user-messages-inboxes-drop"),
+ "displays inboxes dropdown"
+ );
+
+ assert.ok(exists(".messages-nav .tags"), "displays the tags filter");
+
+ await selectKit(".user-messages-inboxes-drop").expand();
+ await selectKit(".user-messages-inboxes-drop").selectRowByValue(
+ PERSONAL_INBOX
+ );
+
+ assert.equal(
+ count(".topic-list-item"),
+ 1,
+ "displays the right topic list"
+ );
+
+ assert.ok(
+ !exists(".messages-nav .tags"),
+ "does not display the tags filter"
+ );
+
+ await selectKit(".user-messages-inboxes-drop").expand();
+ await selectKit(".user-messages-inboxes-drop").selectRowByValue(
+ "awesome_group"
+ );
+
+ assert.equal(
+ count(".topic-list-item"),
+ 2,
+ "displays the right topic list"
+ );
+
+ assert.ok(
+ exists(".group-notifications-button"),
+ "displays the group notifications button"
+ );
+
+ assert.ok(
+ !exists(".messages-nav .tags"),
+ "does not display the tags filter"
+ );
+ });
+ }
+);
diff --git a/app/assets/javascripts/discourse/tests/acceptance/user-test.js b/app/assets/javascripts/discourse/tests/acceptance/user-test.js
index 371c8be8f36c2..6c8c2acfcbc9d 100644
--- a/app/assets/javascripts/discourse/tests/acceptance/user-test.js
+++ b/app/assets/javascripts/discourse/tests/acceptance/user-test.js
@@ -38,11 +38,6 @@ acceptance("User Routes", function (needs) {
assert.ok($("body.user-invites-page").length, "has the body class");
});
- test("Messages", async function (assert) {
- await visit("/u/eviltrout/messages");
- assert.ok($("body.user-messages-page").length, "has the body class");
- });
-
test("Notifications", async function (assert) {
await visit("/u/eviltrout/notifications");
assert.ok($("body.user-notifications-page").length, "has the body class");
diff --git a/app/assets/javascripts/discourse/tests/fixtures/user-fixtures.js b/app/assets/javascripts/discourse/tests/fixtures/user-fixtures.js
index 7c23b4631fded..1ff14845c8f80 100644
--- a/app/assets/javascripts/discourse/tests/fixtures/user-fixtures.js
+++ b/app/assets/javascripts/discourse/tests/fixtures/user-fixtures.js
@@ -2648,6 +2648,33 @@ export default {
default_notification_level: 3,
membership_request_template: null,
},
+ {
+ id: 14,
+ automatic: false,
+ name: "awesome_group",
+ display_name: "awesome_group",
+ user_count: 3,
+ mentionable_level: 0,
+ messageable_level: 0,
+ visibility_level: 0,
+ automatic_membership_email_domains: null,
+ primary_group: false,
+ title: null,
+ grant_trust_level: null,
+ incoming_email: null,
+ has_messages: true,
+ flair_url: null,
+ flair_bg_color: null,
+ flair_color: null,
+ bio_raw: null,
+ bio_cooked: null,
+ public_admission: false,
+ public_exit: false,
+ allow_membership_requests: false,
+ full_name: null,
+ default_notification_level: 3,
+ membership_request_template: null,
+ },
],
group_users: [
{ group_id: 10, user_id: 5, notification_level: 3 },
diff --git a/app/assets/javascripts/discourse/tests/helpers/create-pretender.js b/app/assets/javascripts/discourse/tests/helpers/create-pretender.js
index f6167df657735..123eaa9c217c5 100644
--- a/app/assets/javascripts/discourse/tests/helpers/create-pretender.js
+++ b/app/assets/javascripts/discourse/tests/helpers/create-pretender.js
@@ -207,12 +207,14 @@ export function applyDefaultHandlers(pretender) {
});
});
- pretender.get("/topics/private-messages/eviltrout.json", () => {
- return response(fixturesByUrl["/topics/private-messages/eviltrout.json"]);
- });
-
- pretender.get("/topics/private-messages-warnings/eviltrout.json", () => {
- return response(fixturesByUrl["/topics/private-messages/eviltrout.json"]);
+ [
+ "/topics/private-messages-all/:username.json",
+ "/topics/private-messages/:username.json",
+ "/topics/private-messages-warnings/eviltrout.json",
+ ].forEach((url) => {
+ pretender.get(url, () => {
+ return response(fixturesByUrl["/topics/private-messages/eviltrout.json"]);
+ });
});
pretender.get("/topics/feature_stats.json", () => {
diff --git a/app/assets/stylesheets/common/base/user.scss b/app/assets/stylesheets/common/base/user.scss
index 4bdf5f39c48e0..5c1e478ee518a 100644
--- a/app/assets/stylesheets/common/base/user.scss
+++ b/app/assets/stylesheets/common/base/user.scss
@@ -31,6 +31,7 @@
.user-content {
min-width: 100%;
}
+
.user-additional-controls + .user-content,
.user-secondary-navigation + .user-content {
grid-column-start: 2;
diff --git a/app/assets/stylesheets/common/components/navs.scss b/app/assets/stylesheets/common/components/navs.scss
index 7763081b4b7cb..c1d7f39173675 100644
--- a/app/assets/stylesheets/common/components/navs.scss
+++ b/app/assets/stylesheets/common/components/navs.scss
@@ -66,12 +66,10 @@
.nav-stacked {
@extend %nav;
padding: 0;
- overflow: hidden;
background: var(--primary-low);
li {
border-bottom: 1px solid var(--primary-low);
- position: relative;
&:last-of-type {
border-bottom: 0;
@@ -89,6 +87,7 @@
line-height: $line-height-small;
cursor: pointer;
color: var(--primary);
+ @include ellipsis;
&.active {
color: var(--secondary);
diff --git a/app/assets/stylesheets/desktop/user.scss b/app/assets/stylesheets/desktop/user.scss
index de3568b37f6dd..b23fa9d486430 100644
--- a/app/assets/stylesheets/desktop/user.scss
+++ b/app/assets/stylesheets/desktop/user.scss
@@ -6,10 +6,6 @@
margin-top: 10px;
}
}
-
- .show-mores {
- position: absolute;
- }
}
.form-horizontal .control-group.category {
@@ -20,10 +16,20 @@
font-size: 1.5em;
text-align: center;
}
+
.user-secondary-navigation {
min-width: 150px;
+
+ .combo-box {
+ width: 100%;
+ &:not(:last-of-type) {
+ margin-bottom: 0.875em;
+ }
+ }
+
.nav-stacked {
background-color: transparent;
+ margin: 0;
li {
border-bottom: none;
@@ -47,6 +53,42 @@
}
}
}
+
+ .select-kit + .messages-nav {
+ margin-top: 1em;
+ }
+
+ .inboxes-controls {
+ margin-bottom: 0.75em;
+ }
+
+ &.user-messages {
+ --left-padding: 0.8em;
+ .user-messages-inboxes-drop {
+ padding: 0 1em 0 0;
+
+ .select-kit-header {
+ padding-left: var(--left-padding);
+ }
+
+ .select-kit-selected-name {
+ overflow: hidden;
+ }
+ }
+
+ .nav-stacked {
+ a {
+ padding-left: calc(
+ var(--left-padding) - 1px
+ ); // 1px accounts for border on select-kit elements above
+ }
+ }
+ }
+}
+.user-additional-controls {
+ button {
+ margin-bottom: 1em;
+ }
}
.user-content {
@@ -226,6 +268,20 @@ table.user-invite-list {
}
}
+.user-messages-page {
+ .topic-list th {
+ padding-top: 4px;
+ }
+
+ .show-mores {
+ position: absolute;
+ }
+}
+
+.user-messages {
+ margin-right: 0.2em;
+}
+
.user-preferences {
padding-top: 10px;
padding-left: 30px;
diff --git a/app/assets/stylesheets/mobile/user.scss b/app/assets/stylesheets/mobile/user.scss
index a955b9aeba74b..47ff10bddbf97 100644
--- a/app/assets/stylesheets/mobile/user.scss
+++ b/app/assets/stylesheets/mobile/user.scss
@@ -4,8 +4,7 @@
display: grid;
grid-template-columns: 1fr 1fr;
grid-template-rows: auto auto auto;
- grid-row-gap: 20px;
- grid-column-gap: 16px;
+ grid-gap: 16px;
.user-primary-navigation {
grid-column-start: 1;
grid-row-start: 1;
@@ -30,6 +29,71 @@
grid-row-start: 3;
grid-column-start: 1;
}
+
+ // specific to messages
+
+ .user-messages.user-messages-inboxes {
+ grid-row-start: 2;
+ grid-column-start: 1;
+ grid-column-end: 3;
+
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 16px;
+
+ + .user-additional-controls {
+ grid-row-start: 2;
+ grid-column-start: 1;
+ }
+ }
+
+ .inboxes-controls {
+ display: flex;
+ }
+
+ .user-messages-inboxes-drop {
+ padding: 0;
+ flex: 1 1 auto;
+ .select-kit-header {
+ padding: 8px 10px;
+
+ .caret-icon {
+ color: var(--primary-medium);
+ }
+ }
+ }
+
+ .messages-nav {
+ grid-column-start: 2;
+ grid-column-end: 3;
+ grid-row-start: 1;
+ }
+
+ .new-private-message {
+ grid-row-start: 1;
+ grid-column-start: 2;
+ }
+
+ .group-notifications-button {
+ margin-left: 8px;
+
+ .select-kit-header {
+ height: 100%;
+
+ .selected-name .name {
+ display: none;
+ }
+ }
+ }
+}
+
+.user-messages-page {
+ .paginated-topics-list {
+ margin-top: 0;
+ }
+ .show-mores {
+ margin-top: 0.5em;
+ }
}
.user-main {
@@ -166,6 +230,10 @@
flex: 1 1 25%;
margin-left: auto;
+ .btn {
+ margin-bottom: 16px;
+ }
+
ul {
margin: 0;
display: flex;
@@ -223,6 +291,7 @@
.user-main .collapsed-info.about .details {
display: flex;
+ margin-bottom: 16px;
.user-profile-avatar {
margin: 0;
flex: 0 0 auto;
diff --git a/app/controllers/list_controller.rb b/app/controllers/list_controller.rb
index 1fc891ea76758..34c505bb194dd 100644
--- a/app/controllers/list_controller.rb
+++ b/app/controllers/list_controller.rb
@@ -146,35 +146,27 @@ def group_topics
end
def self.generate_message_route(action)
- case action
- when :private_messages_tag
- define_method("#{action}") do
- raise Discourse::NotFound if !guardian.can_tag_pms?
- message_route(action)
- end
- when :private_messages_group, :private_messages_group_archive
- define_method("#{action}") do
- group = Group.find_by("LOWER(name) = ?", params[:group_name].downcase)
- raise Discourse::NotFound if !group
- raise Discourse::NotFound unless guardian.can_see_group_messages?(group)
-
- message_route(action)
- end
- else
- define_method("#{action}") do
- message_route(action)
- end
+ define_method action do
+ message_route(action)
end
end
def message_route(action)
target_user = fetch_user_from_params({ include_inactive: current_user.try(:staff?) }, [:user_stat, :user_option])
+
case action
+ when :private_messages_tag
+ raise Discourse::NotFound if !guardian.can_tag_pms?
when :private_messages_warnings
guardian.ensure_can_see_warnings!(target_user)
+ when :private_messages_group, :private_messages_group_archive
+ group = Group.find_by("LOWER(name) = ?", params[:group_name].downcase)
+ raise Discourse::NotFound if !group
+ raise Discourse::NotFound unless guardian.can_see_group_messages?(group)
else
guardian.ensure_can_see_private_messages!(target_user.id)
end
+
list_opts = build_topic_list_options
list = generate_list_for(action.to_s, target_user, list_opts)
url_prefix = "topics"
@@ -187,11 +179,19 @@ def message_route(action)
private_messages
private_messages_sent
private_messages_unread
+ private_messages_new
private_messages_archive
private_messages_group
+ private_messages_group_new
+ private_messages_group_unread
private_messages_group_archive
- private_messages_tag
private_messages_warnings
+ private_messages_all
+ private_messages_all_sent
+ private_messages_all_unread
+ private_messages_all_new
+ private_messages_all_archive
+ private_messages_tag
}.each do |action|
generate_message_route(action)
end
diff --git a/app/models/tag.rb b/app/models/tag.rb
index 44c23734a05a6..f489747138d69 100644
--- a/app/models/tag.rb
+++ b/app/models/tag.rb
@@ -136,16 +136,16 @@ def self.pm_tags(limit: 1000, guardian: nil, allowed_user: nil)
WHERE topic_tags.topic_id IN (
SELECT topic_id
FROM topic_allowed_users
- WHERE user_id = #{user_id}
+ WHERE user_id = #{user_id.to_i}
UNION
SELECT tg.topic_id
FROM topic_allowed_groups tg
- JOIN group_users gu ON gu.user_id = #{user_id}
+ JOIN group_users gu ON gu.user_id = #{user_id.to_i}
AND gu.group_id = tg.group_id
)
GROUP BY tags.name
ORDER BY count DESC
- LIMIT #{limit}
+ LIMIT #{limit.to_i}
SQL
end
diff --git a/app/models/topic_tracking_state.rb b/app/models/topic_tracking_state.rb
index 77c57662fd92f..15199c9745c92 100644
--- a/app/models/topic_tracking_state.rb
+++ b/app/models/topic_tracking_state.rb
@@ -545,8 +545,9 @@ def self.publish_private_message(topic, archive_user_id: nil,
group_user_ids = group.users.pluck(:id)
next if group_user_ids.blank?
group_channels = []
- group_channels << "/private-messages/group/#{group.name.downcase}"
- group_channels << "#{group_channels.first}/archive" if group_archive
+ channel_prefix = "/private-messages/group/#{group.name.downcase}"
+ group_channels << "#{channel_prefix}/inbox"
+ group_channels << "#{channel_prefix}/archive" if group_archive
group_channels.each { |channel| channels[channel] = group_user_ids }
end
diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml
index ad3949804f694..9bfbeab182b90 100644
--- a/config/locales/client.en.yml
+++ b/config/locales/client.en.yml
@@ -1171,9 +1171,13 @@ en:
rejected_posts: "rejected posts"
messages:
- all: "All"
+ all: "all inboxes"
inbox: "Inbox"
+ personal: "Personal"
+ latest: "Latest"
sent: "Sent"
+ unread: "Unread"
+ new: "New"
archive: "Archive"
groups: "My Groups"
move_to_inbox: "Move to Inbox"
diff --git a/config/routes.rb b/config/routes.rb
index f48ad3d5ea9c7..fd833b8bf5146 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -447,9 +447,10 @@
get "#{root_path}/:username/private-messages/:filter" => "user_actions#private_messages", constraints: { username: RouteFormat.username }
get "#{root_path}/:username/messages" => "user_actions#private_messages", constraints: { username: RouteFormat.username }
get "#{root_path}/:username/messages/:filter" => "user_actions#private_messages", constraints: { username: RouteFormat.username }
+ get "#{root_path}/:username/messages/personal" => "user_actions#private_messages", constraints: { username: RouteFormat.username }
+ get "#{root_path}/:username/messages/personal/:filter" => "user_actions#private_messages", constraints: { username: RouteFormat.username }
get "#{root_path}/:username/messages/group/:group_name" => "user_actions#private_messages", constraints: { username: RouteFormat.username, group_name: RouteFormat.username }
- get "#{root_path}/:username/messages/group/:group_name/archive" => "user_actions#private_messages", constraints: { username: RouteFormat.username, group_name: RouteFormat.username }
- get "#{root_path}/:username/messages/tags/:tag_id" => "user_actions#private_messages", constraints: StaffConstraint.new
+ get "#{root_path}/:username/messages/group/:group_name/:filter" => "user_actions#private_messages", constraints: { username: RouteFormat.username, group_name: RouteFormat.username }
get "#{root_path}/:username.json" => "users#show", constraints: { username: RouteFormat.username }, defaults: { format: :json }
get({ "#{root_path}/:username" => "users#show", constraints: { username: RouteFormat.username } }.merge(index == 1 ? { as: 'user' } : {}))
put "#{root_path}/:username" => "users#update", constraints: { username: RouteFormat.username }, defaults: { format: :json }
@@ -764,17 +765,25 @@
scope "/topics", username: RouteFormat.username do
get "created-by/:username" => "list#topics_by", as: "topics_by", defaults: { format: :json }
+ get "private-messages-all/:username" => "list#private_messages_all", as: "topics_private_messages_all", defaults: { format: :json }
+ get "private-messages-all-sent/:username" => "list#private_messages_all_sent", as: "topics_private_messages_all_sent", defaults: { format: :json }
+ get "private-messages-all-new/:username" => "list#private_messages_all_new", as: "topics_private_messages_all_new", defaults: { format: :json }
+ get "private-messages-all-unread/:username" => "list#private_messages_all_unread", as: "topics_private_messages_all_unread", defaults: { format: :json }
+ get "private-messages-all-archive/:username" => "list#private_messages_all_archive", as: "topics_private_messages_all_archive", defaults: { format: :json }
get "private-messages/:username" => "list#private_messages", as: "topics_private_messages", defaults: { format: :json }
get "private-messages-sent/:username" => "list#private_messages_sent", as: "topics_private_messages_sent", defaults: { format: :json }
get "private-messages-archive/:username" => "list#private_messages_archive", as: "topics_private_messages_archive", defaults: { format: :json }
get "private-messages-unread/:username" => "list#private_messages_unread", as: "topics_private_messages_unread", defaults: { format: :json }
get "private-messages-tags/:username/:tag_id.json" => "list#private_messages_tag", as: "topics_private_messages_tag", defaults: { format: :json }
+ get "private-messages-new/:username" => "list#private_messages_new", as: "topics_private_messages_new", defaults: { format: :json }
get "private-messages-warnings/:username" => "list#private_messages_warnings", as: "topics_private_messages_warnings", defaults: { format: :json }
get "groups/:group_name" => "list#group_topics", as: "group_topics", group_name: RouteFormat.username
scope "/private-messages-group/:username", group_name: RouteFormat.username do
get ":group_name.json" => "list#private_messages_group", as: "topics_private_messages_group"
get ":group_name/archive.json" => "list#private_messages_group_archive", as: "topics_private_messages_group_archive"
+ get ":group_name/new.json" => "list#private_messages_group_new", as: "topics_private_messages_group_new"
+ get ":group_name/unread.json" => "list#private_messages_group_unread", as: "topics_private_messages_group_unread"
end
end
diff --git a/lib/topic_query.rb b/lib/topic_query.rb
index 82b76cf28a9e6..44b821dd57c71 100644
--- a/lib/topic_query.rb
+++ b/lib/topic_query.rb
@@ -6,6 +6,8 @@
#
class TopicQuery
+ include PrivateMessageLists
+
PG_MAX_INT ||= 2147483647
DEFAULT_PER_PAGE_COUNT ||= 30
@@ -293,12 +295,6 @@ def list_topics_by(user)
end
end
- def not_archived(list, user)
- list.joins("LEFT JOIN user_archived_messages um
- ON um.user_id = #{user.id.to_i} AND um.topic_id = topics.id")
- .where('um.user_id IS NULL')
- end
-
def list_group_topics(group)
list = default_results.where("
topics.user_id IN (
@@ -309,79 +305,6 @@ def list_group_topics(group)
create_list(:group_topics, {}, list)
end
- def list_private_messages(user)
- list = private_messages_for(user, :user)
-
- list = not_archived(list, user)
- .where('NOT (topics.participant_count = 1 AND topics.user_id = ? AND topics.moderator_posts_count = 0)', user.id)
-
- create_list(:private_messages, {}, list)
- end
-
- def list_private_messages_archive(user)
- list = private_messages_for(user, :user)
- list = list.joins(:user_archived_messages).where('user_archived_messages.user_id = ?', user.id)
- create_list(:private_messages, {}, list)
- end
-
- def list_private_messages_sent(user)
- list = private_messages_for(user, :user)
- list = list.where('EXISTS (
- SELECT 1 FROM posts
- WHERE posts.topic_id = topics.id AND
- posts.user_id = ?
- )', user.id)
- list = not_archived(list, user)
- create_list(:private_messages, {}, list)
- end
-
- def list_private_messages_unread(user)
- list = private_messages_for(user, :user)
-
- list = TopicQuery.unread_filter(
- list,
- staff: user.staff?
- )
-
- first_unread_pm_at = UserStat.where(user_id: user.id).pluck_first(:first_unread_pm_at)
- list = list.where("topics.updated_at >= ?", first_unread_pm_at) if first_unread_pm_at
- create_list(:private_messages, {}, list)
- end
-
- def list_private_messages_group(user)
- list = private_messages_for(user, :group)
- group = Group.where('name ilike ?', @options[:group_name]).select(:id, :publish_read_state).first
- publish_read_state = !!group&.publish_read_state
- list = list.joins("LEFT JOIN group_archived_messages gm ON gm.topic_id = topics.id AND
- gm.group_id = #{group&.id&.to_i}")
- list = list.where("gm.id IS NULL")
- list = append_read_state(list, group) if publish_read_state
- create_list(:private_messages, { publish_read_state: publish_read_state }, list)
- end
-
- def list_private_messages_group_archive(user)
- list = private_messages_for(user, :group)
- group_id = Group.where('name ilike ?', @options[:group_name]).pluck_first(:id)
- list = list.joins("JOIN group_archived_messages gm ON gm.topic_id = topics.id AND
- gm.group_id = #{group_id.to_i}")
- create_list(:private_messages, {}, list)
- end
-
- def list_private_messages_tag(user)
- list = private_messages_for(user, :all)
- list = list.joins("JOIN topic_tags tt ON tt.topic_id = topics.id
- JOIN tags t ON t.id = tt.tag_id AND t.name = '#{@options[:tags][0]}'")
- create_list(:private_messages, {}, list)
- end
-
- def list_private_messages_warnings(user)
- list = private_messages_for(user, :user)
- list = list.where('topics.subtype = ?', TopicSubtype.moderator_warning)
- # Exclude official warnings that the user created, instead of received
- list = list.where('topics.user_id <> ?', user.id)
- create_list(:private_messages, {}, list)
- end
-
def list_category_topic_ids(category)
query = default_results(category: category.id)
pinned_ids = query.where('topics.pinned_at IS NOT NULL AND topics.category_id = ?', category.id).limit(nil).order('pinned_at DESC').pluck(:id)
@@ -590,50 +513,6 @@ def per_page_setting
DEFAULT_PER_PAGE_COUNT
end
- def private_messages_for(user, type)
- options = @options
- options.reverse_merge!(per_page: per_page_setting)
-
- result = Topic.includes(:allowed_users)
- result = result.includes(:tags) if SiteSetting.tagging_enabled
-
- if type == :group
- result = result.joins(
- "INNER JOIN topic_allowed_groups tag ON tag.topic_id = topics.id AND tag.group_id IN (SELECT id FROM groups WHERE LOWER(name) = '#{PG::Connection.escape_string(@options[:group_name].downcase)}')"
- )
-
- unless user.admin?
- result = result.joins("INNER JOIN group_users gu ON gu.group_id = tag.group_id AND gu.user_id = #{user.id.to_i}")
- end
- elsif type == :user
- result = result.where("topics.id IN (SELECT topic_id FROM topic_allowed_users WHERE user_id = #{user.id.to_i})")
- elsif type == :all
- result = result.where("topics.id IN (
- SELECT topic_id
- FROM topic_allowed_users
- WHERE user_id = #{user.id.to_i}
- UNION ALL
- SELECT topic_id FROM topic_allowed_groups
- WHERE group_id IN (
- SELECT group_id FROM group_users WHERE user_id = #{user.id.to_i}
- )
- )")
- end
-
- result = result.joins("LEFT OUTER JOIN topic_users AS tu ON (topics.id = tu.topic_id AND tu.user_id = #{user.id.to_i})")
- .order("topics.bumped_at DESC")
- .private_messages
-
- result = result.limit(options[:per_page]) unless options[:limit] == false
- result = result.visible if options[:visible] || @user.nil? || @user.regular?
-
- if options[:page]
- offset = options[:page].to_i * options[:per_page]
- result = result.offset(offset) if offset > 0
- end
- result
- end
-
def apply_shared_drafts(result, category_id, options)
# PERF: avoid any penalty if there are no shared drafts enabled
@@ -955,7 +834,7 @@ def remove_muted_categories(list, user, opts = nil)
list
end
- def remove_muted_tags(list, user, opts = nil)
+ def remove_muted_tags(list, user, opts = {})
if !SiteSetting.tagging_enabled || SiteSetting.remove_muted_tags_from_latest == 'never'
return list
end
@@ -1149,18 +1028,4 @@ def suggested_ordering(result, options)
result.order('topics.bumped_at DESC')
end
-
- private
-
- def append_read_state(list, group)
- group_id = group&.id
- return list if group_id.nil?
-
- selected_values = list.select_values.empty? ? ['topics.*'] : list.select_values
- selected_values << "COALESCE(tg.last_read_post_number, 0) AS last_read_post_number"
-
- list
- .joins("LEFT OUTER JOIN topic_groups tg ON topics.id = tg.topic_id AND tg.group_id = #{group_id}")
- .select(*selected_values)
- end
end
diff --git a/lib/topic_query/private_message_lists.rb b/lib/topic_query/private_message_lists.rb
new file mode 100644
index 0000000000000..28534735ada1d
--- /dev/null
+++ b/lib/topic_query/private_message_lists.rb
@@ -0,0 +1,256 @@
+# frozen_string_literal: true
+
+class TopicQuery
+ module PrivateMessageLists
+ def list_private_messages_all(user)
+ list = private_messages_for(user, :all)
+ list = filter_archived(list, user, archived: false)
+ create_list(:private_messages, {}, list)
+ end
+
+ def list_private_messages_all_sent(user)
+ list = private_messages_for(user, :all)
+
+ list = list.where(<<~SQL, user.id)
+ EXISTS (
+ SELECT 1 FROM posts
+ WHERE posts.topic_id = topics.id AND posts.user_id = ?
+ )
+ SQL
+
+ list = filter_archived(list, user, archived: false)
+ create_list(:private_messages, {}, list)
+ end
+
+ def list_private_messages_all_archive(user)
+ list = private_messages_for(user, :all)
+ list = filter_archived(list, user, archived: true)
+ create_list(:private_messages, {}, list)
+ end
+
+ def list_private_messages_all_new(user)
+ list_private_messages_new(user, :all)
+ end
+
+ def list_private_messages_all_unread(user)
+ list_private_messages_unread(user, :all)
+ end
+
+ def list_private_messages(user)
+ list = private_messages_for(user, :user)
+ list = not_archived(list, user)
+ create_list(:private_messages, {}, list)
+ end
+
+ def list_private_messages_archive(user)
+ list = private_messages_for(user, :user)
+ list = list.joins(:user_archived_messages).where('user_archived_messages.user_id = ?', user.id)
+ create_list(:private_messages, {}, list)
+ end
+
+ def list_private_messages_sent(user)
+ list = private_messages_for(user, :user)
+
+ list = list.where(<<~SQL, user.id)
+ EXISTS (
+ SELECT 1 FROM posts
+ WHERE posts.topic_id = topics.id AND posts.user_id = ?
+ )
+ SQL
+
+ list = not_archived(list, user)
+ create_list(:private_messages, {}, list)
+ end
+
+ def list_private_messages_new(user, type = :user)
+ list = TopicQuery.new_filter(
+ private_messages_for(user, type),
+ treat_as_new_topic_start_date: user.user_option.treat_as_new_topic_start_date
+ )
+
+ list = remove_muted_tags(list, user)
+
+ create_list(:private_messages, {}, list)
+ end
+
+ def list_private_messages_unread(user, type = :user)
+ list = TopicQuery.unread_filter(
+ private_messages_for(user, type),
+ staff: user.staff?
+ )
+
+ first_unread_pm_at = UserStat
+ .where(user_id: user.id)
+ .pluck_first(:first_unread_pm_at)
+
+ if first_unread_pm_at
+ list = list.where("topics.updated_at >= ?", first_unread_pm_at)
+ end
+
+ create_list(:private_messages, {}, list)
+ end
+
+ def list_private_messages_group(user)
+ list = private_messages_for(user, :group)
+
+ list = list.joins(<<~SQL)
+ LEFT JOIN group_archived_messages gm
+ ON gm.topic_id = topics.id AND gm.group_id = #{group.id.to_i}
+ SQL
+
+ list = list.where("gm.id IS NULL")
+ publish_read_state = !!group.publish_read_state
+ list = append_read_state(list, group) if publish_read_state
+ create_list(:private_messages, { publish_read_state: publish_read_state }, list)
+ end
+
+ def list_private_messages_group_archive(user)
+ list = private_messages_for(user, :group)
+
+ list = list.joins(<<~SQL)
+ INNER JOIN group_archived_messages gm
+ ON gm.topic_id = topics.id AND gm.group_id = #{group.id.to_i}
+ SQL
+
+ publish_read_state = !!group.publish_read_state
+ list = append_read_state(list, group) if publish_read_state
+ create_list(:private_messages, { publish_read_state: publish_read_state }, list)
+ end
+
+ def list_private_messages_group_new(user)
+ list = TopicQuery.new_filter(
+ private_messages_for(user, :group),
+ treat_as_new_topic_start_date: user.user_option.treat_as_new_topic_start_date
+ )
+
+ publish_read_state = !!group.publish_read_state
+ list = append_read_state(list, group) if publish_read_state
+ create_list(:private_messages, { publish_read_state: publish_read_state }, list)
+ end
+
+ def list_private_messages_group_unread(user)
+ list = TopicQuery.unread_filter(
+ private_messages_for(user, :group),
+ staff: user.staff?
+ )
+
+ first_unread_pm_at = UserStat
+ .where(user_id: user.id)
+ .pluck_first(:first_unread_pm_at)
+
+ if first_unread_pm_at
+ list = list.where("topics.updated_at >= ?", first_unread_pm_at)
+ end
+
+ publish_read_state = !!group.publish_read_state
+ list = append_read_state(list, group) if publish_read_state
+ create_list(:private_messages, { publish_read_state: publish_read_state }, list)
+ end
+
+ def list_private_messages_warnings(user)
+ list = private_messages_for(user, :user)
+ list = list.where('topics.subtype = ?', TopicSubtype.moderator_warning)
+ # Exclude official warnings that the user created, instead of received
+ list = list.where('topics.user_id <> ?', user.id)
+ create_list(:private_messages, {}, list)
+ end
+
+ def private_messages_for(user, type)
+ options = @options
+ options.reverse_merge!(per_page: per_page_setting)
+
+ result = Topic.includes(:allowed_users)
+ result = result.includes(:tags) if SiteSetting.tagging_enabled
+
+ if type == :group
+ result = result.joins(
+ "INNER JOIN topic_allowed_groups tag ON tag.topic_id = topics.id AND tag.group_id IN (SELECT id FROM groups WHERE LOWER(name) = '#{PG::Connection.escape_string(@options[:group_name].downcase)}')"
+ )
+
+ unless user.admin?
+ result = result.joins("INNER JOIN group_users gu ON gu.group_id = tag.group_id AND gu.user_id = #{user.id.to_i}")
+ end
+ elsif type == :user
+ result = result.where("topics.id IN (SELECT topic_id FROM topic_allowed_users WHERE user_id = #{user.id.to_i})")
+ elsif type == :all
+ result = result.where("topics.id IN (
+ SELECT topic_id
+ FROM topic_allowed_users
+ WHERE user_id = #{user.id.to_i}
+ UNION ALL
+ SELECT topic_id FROM topic_allowed_groups
+ WHERE group_id IN (
+ SELECT group_id FROM group_users WHERE user_id = #{user.id.to_i}
+ )
+ )")
+ end
+
+ result = result.joins("LEFT OUTER JOIN topic_users AS tu ON (topics.id = tu.topic_id AND tu.user_id = #{user.id.to_i})")
+ .order("topics.bumped_at DESC")
+ .private_messages
+
+ result = result.limit(options[:per_page]) unless options[:limit] == false
+ result = result.visible if options[:visible] || @user.nil? || @user.regular?
+
+ if options[:page]
+ offset = options[:page].to_i * options[:per_page]
+ result = result.offset(offset) if offset > 0
+ end
+ result
+ end
+
+ def list_private_messages_tag(user)
+ list = private_messages_for(user, :all)
+ list = list.joins("JOIN topic_tags tt ON tt.topic_id = topics.id
+ JOIN tags t ON t.id = tt.tag_id AND t.name = '#{@options[:tags][0]}'")
+ create_list(:private_messages, {}, list)
+ end
+
+ private
+
+ def append_read_state(list, group)
+ group_id = group.id
+ return list if group_id.nil?
+
+ selected_values = list.select_values.empty? ? ['topics.*'] : list.select_values
+ selected_values << "COALESCE(tg.last_read_post_number, 0) AS last_read_post_number"
+
+ list
+ .joins("LEFT OUTER JOIN topic_groups tg ON topics.id = tg.topic_id AND tg.group_id = #{group_id}")
+ .select(*selected_values)
+ end
+
+ def filter_archived(list, user, archived: true)
+ list = list.joins(<<~SQL)
+ LEFT JOIN group_archived_messages gm ON gm.topic_id = topics.id
+ LEFT JOIN user_archived_messages um
+ ON um.user_id = #{user.id.to_i}
+ AND um.topic_id = topics.id
+ SQL
+
+ list =
+ if archived
+ list.where("um.user_id IS NOT NULL OR gm.topic_id IS NOT NULL")
+ else
+ list.where("um.user_id IS NULL AND gm.topic_id IS NULL")
+ end
+
+ list
+ end
+
+ def not_archived(list, user)
+ list.joins("LEFT JOIN user_archived_messages um
+ ON um.user_id = #{user.id.to_i} AND um.topic_id = topics.id")
+ .where('um.user_id IS NULL')
+ end
+
+ def group
+ @group ||= begin
+ Group
+ .where('name ilike ?', @options[:group_name])
+ .select(:id, :publish_read_state)
+ .first
+ end
+ end
+ end
+end
diff --git a/spec/components/topic_query_spec.rb b/spec/components/topic_query_spec.rb
index b4cc1efa27fd4..744ccd62a7d5b 100644
--- a/spec/components/topic_query_spec.rb
+++ b/spec/components/topic_query_spec.rb
@@ -1067,7 +1067,6 @@ def suggested_for(topic)
expect(TopicQuery.new(user, tags: [tag.name]).list_private_messages_tag(user).topics).to eq([private_message])
end
-
end
end
@@ -1193,75 +1192,6 @@ def suggested_for(topic)
end
end
- describe '#list_private_messages_group' do
- fab!(:group) { Fabricate(:group) }
-
- let!(:group_message) do
- Fabricate(:private_message_topic,
- allowed_groups: [group],
- topic_allowed_users: [
- Fabricate.build(:topic_allowed_user, user: Fabricate(:user)),
- ]
- )
- end
-
- before do
- group.add(creator)
- end
-
- it 'should return the right list for a group user' do
- topics = TopicQuery.new(nil, group_name: group.name)
- .list_private_messages_group(creator)
- .topics
-
- expect(topics).to contain_exactly(group_message)
- end
-
- it 'should return the right list for an admin not part of the group' do
- group.update!(name: group.name.capitalize)
-
- topics = TopicQuery.new(nil, group_name: group.name.upcase)
- .list_private_messages_group(Fabricate(:admin))
- .topics
-
- expect(topics).to contain_exactly(group_message)
- end
-
- it "should not allow a moderator not part of the group to view the group's messages" do
- topics = TopicQuery.new(nil, group_name: group.name)
- .list_private_messages_group(Fabricate(:moderator))
- .topics
-
- expect(topics).to eq([])
- end
-
- it "should not allow a user not part of the group to view the group's messages" do
- topics = TopicQuery.new(nil, group_name: group.name)
- .list_private_messages_group(Fabricate(:user))
- .topics
-
- expect(topics).to eq([])
- end
-
- context "Calculating minimum unread count for a topic" do
- before { group.update!(publish_read_state: true) }
-
- let(:listed_message) do
- TopicQuery.new(nil, group_name: group.name)
- .list_private_messages_group(creator)
- .topics.first
- end
-
- it 'returns the last read post number' do
- topic_group = TopicGroup.create!(
- topic: group_message, group: group, last_read_post_number: 10
- )
-
- expect(listed_message.last_read_post_number).to eq(topic_group.last_read_post_number)
- end
- end
- end
-
context "shared drafts" do
fab!(:category) { Fabricate(:category_with_definition) }
fab!(:shared_drafts_category) { Fabricate(:category_with_definition) }
@@ -1349,16 +1279,4 @@ def suggested_for(topic)
end
end
end
-
- describe '#list_private_messages' do
- it "includes topics with moderator posts" do
- private_message_topic = Fabricate(:private_message_post, user: user).topic
-
- expect(TopicQuery.new(user).list_private_messages(user).topics).to be_empty
-
- private_message_topic.add_moderator_post(admin, "Thank you for your flag")
-
- expect(TopicQuery.new(user).list_private_messages(user).topics).to eq([private_message_topic])
- end
- end
end
diff --git a/spec/lib/topic_query/private_message_lists_spec.rb b/spec/lib/topic_query/private_message_lists_spec.rb
new file mode 100644
index 0000000000000..8d37b1d17ddca
--- /dev/null
+++ b/spec/lib/topic_query/private_message_lists_spec.rb
@@ -0,0 +1,305 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+describe TopicQuery::PrivateMessageLists do
+ fab!(:user) { Fabricate(:user) }
+ fab!(:user_2) { Fabricate(:user) }
+
+ fab!(:group) do
+ Fabricate(:group, messageable_level: Group::ALIAS_LEVELS[:everyone]).tap do |g|
+ g.add(user_2)
+ end
+ end
+
+ fab!(:group_message) do
+ create_post(
+ user: user,
+ target_group_names: [group.name],
+ archetype: Archetype.private_message
+ ).topic
+ end
+
+ fab!(:private_message) do
+ create_post(
+ user: user,
+ target_usernames: [user_2.username],
+ archetype: Archetype.private_message
+ ).topic
+ end
+
+ describe '#list_private_messages_all' do
+ it 'returns a list of all private messages that a user has access to' do
+ topics = TopicQuery.new(nil).list_private_messages_all(user).topics
+
+ expect(topics).to contain_exactly(group_message, private_message)
+ end
+
+ it 'does not include user or group archived messages' do
+ UserArchivedMessage.archive!(user.id, group_message)
+ UserArchivedMessage.archive!(user.id, private_message)
+
+ topics = TopicQuery.new(nil).list_private_messages_all(user).topics
+
+ expect(topics).to eq([])
+
+ GroupArchivedMessage.archive!(user_2.id, group_message)
+
+ topics = TopicQuery.new(nil).list_private_messages_all(user_2).topics
+
+ expect(topics).to contain_exactly(private_message)
+ end
+ end
+
+ describe '#list_private_messages_all_sent' do
+ it 'returns a list of all private messages that a user has sent' do
+ topics = TopicQuery.new(nil).list_private_messages_all_sent(user_2).topics
+
+ expect(topics).to eq([])
+
+ create_post(user: user_2, topic: private_message)
+
+ topics = TopicQuery.new(nil).list_private_messages_all_sent(user_2).topics
+
+ expect(topics).to contain_exactly(private_message)
+
+ create_post(user: user_2, topic: group_message)
+
+ topics = TopicQuery.new(nil).list_private_messages_all_sent(user_2).topics
+
+ expect(topics).to contain_exactly(private_message, group_message)
+ end
+
+ it 'does not include user or group archived messages' do
+ create_post(user: user_2, topic: private_message)
+ create_post(user: user_2, topic: group_message)
+
+ UserArchivedMessage.archive!(user_2.id, private_message)
+ GroupArchivedMessage.archive!(user_2.id, group_message)
+
+ topics = TopicQuery.new(nil).list_private_messages_all_sent(user_2).topics
+
+ expect(topics).to eq([])
+ end
+ end
+
+ describe '#list_private_messages_all_archive' do
+ it 'returns a list of all private messages that has been archived' do
+ UserArchivedMessage.archive!(user_2.id, private_message)
+ GroupArchivedMessage.archive!(user_2.id, group_message)
+
+ topics = TopicQuery.new(nil).list_private_messages_all_archive(user_2).topics
+
+ expect(topics).to contain_exactly(private_message, group_message)
+ end
+ end
+
+ describe '#list_private_messages_all_new' do
+ it 'returns a list of new private messages' do
+ topics = TopicQuery.new(nil).list_private_messages_all_new(user_2).topics
+
+ expect(topics).to contain_exactly(private_message, group_message)
+
+ TopicUser.find_by(user: user_2, topic: group_message).update!(
+ last_read_post_number: 1
+ )
+
+ topics = TopicQuery.new(nil).list_private_messages_all_new(user_2).topics
+
+ expect(topics).to contain_exactly(private_message)
+ end
+ end
+
+ describe '#list_private_messages_all_unread' do
+ it 'returns a list of unread private messages' do
+ topics = TopicQuery.new(nil).list_private_messages_all_unread(user_2).topics
+
+ expect(topics).to eq([])
+
+ TopicUser.find_by(user: user_2, topic: group_message).update!(
+ last_read_post_number: 1
+ )
+
+ create_post(user: user, topic: group_message)
+
+ topics = TopicQuery.new(nil).list_private_messages_all_unread(user_2).topics
+
+ expect(topics).to contain_exactly(group_message)
+ end
+ end
+
+ describe '#list_private_messages' do
+ it 'returns a list of all private messages that a user has access to' do
+ topics = TopicQuery.new(nil).list_private_messages(user_2).topics
+
+ expect(topics).to contain_exactly(private_message)
+ end
+ end
+
+ describe '#list_private_messages_group' do
+ it 'should return the right list for a group user' do
+ group.add(user_2)
+
+ topics = TopicQuery.new(nil, group_name: group.name)
+ .list_private_messages_group(user_2)
+ .topics
+
+ expect(topics).to contain_exactly(group_message)
+ end
+
+ it 'should return the right list for an admin not part of the group' do
+ group.update!(name: group.name.capitalize)
+
+ topics = TopicQuery.new(nil, group_name: group.name.upcase)
+ .list_private_messages_group(Fabricate(:admin))
+ .topics
+
+ expect(topics).to contain_exactly(group_message)
+ end
+
+ it "should not allow a moderator not part of the group to view the group's messages" do
+ topics = TopicQuery.new(nil, group_name: group.name)
+ .list_private_messages_group(Fabricate(:moderator))
+ .topics
+
+ expect(topics).to eq([])
+ end
+
+ it "should not allow a user not part of the group to view the group's messages" do
+ topics = TopicQuery.new(nil, group_name: group.name)
+ .list_private_messages_group(Fabricate(:user))
+ .topics
+
+ expect(topics).to eq([])
+ end
+
+ context "Calculating minimum unread count for a topic" do
+ before do
+ group.update!(publish_read_state: true)
+ group.add(user)
+ end
+
+ let(:listed_message) do
+ TopicQuery.new(nil, group_name: group.name)
+ .list_private_messages_group(user)
+ .topics.first
+ end
+
+ it 'returns the last read post number' do
+ topic_group = TopicGroup.create!(
+ topic: group_message, group: group, last_read_post_number: 10
+ )
+
+ expect(listed_message.last_read_post_number).to eq(topic_group.last_read_post_number)
+ end
+ end
+ end
+
+ describe '#list_private_messages_group_new' do
+ it 'returns a list of new private messages for a group that user is a part of' do
+ topics = TopicQuery.new(nil, group_name: group.name)
+ .list_private_messages_group_new(user_2)
+ .topics
+
+ expect(topics).to contain_exactly(group_message)
+ end
+ end
+
+ describe '#list_private_messages_group_unread' do
+ it 'returns a list of unread private messages for a group that user is a part of' do
+ topics = TopicQuery.new(nil, group_name: group.name)
+ .list_private_messages_group_unread(user_2)
+ .topics
+
+ expect(topics).to eq([])
+
+ TopicUser.find_by(user: user_2, topic: group_message).update!(
+ last_read_post_number: 1
+ )
+
+ create_post(user: user, topic: group_message)
+
+ topics = TopicQuery.new(nil, group_name: group.name)
+ .list_private_messages_group_unread(user_2)
+ .topics
+
+ expect(topics).to contain_exactly(group_message)
+ end
+ end
+
+ describe '#list_private_messages_unread' do
+ fab!(:user) { Fabricate(:user) }
+ fab!(:user_2) { Fabricate(:user) }
+
+ fab!(:pm) do
+ create_post(
+ user: user,
+ target_usernames: [user_2.username],
+ archetype: Archetype.private_message
+ ).topic
+ end
+
+ fab!(:pm_2) do
+ create_post(
+ user: user,
+ target_usernames: [user_2.username],
+ archetype: Archetype.private_message
+ ).topic
+ end
+
+ fab!(:pm_3) do
+ create_post(
+ user: user,
+ target_usernames: [user_2.username],
+ archetype: Archetype.private_message
+ ).topic
+ end
+
+ it 'returns a list of private messages with unread posts that user is at least tracking' do
+ freeze_time 1.minute.from_now do
+ create_post(user: user_2, topic_id: pm.id)
+ create_post(user: user_2, topic_id: pm_3.id)
+ end
+
+ TopicUser.find_by(user: user, topic: pm_3).update!(
+ notification_level: TopicUser.notification_levels[:regular]
+ )
+
+ expect(TopicQuery.new(user).list_private_messages_unread(user).topics)
+ .to contain_exactly(pm)
+ end
+ end
+
+ describe '#list_private_messages_new' do
+ fab!(:user) { Fabricate(:user) }
+ fab!(:user_2) { Fabricate(:user) }
+
+ fab!(:pm) do
+ create_post(
+ user: user,
+ target_usernames: [user_2.username],
+ archetype: Archetype.private_message
+ ).topic
+ end
+
+ it 'returns a list of new private messages' do
+ expect(TopicQuery.new(user_2).list_private_messages_new(user_2).topics)
+ .to contain_exactly(pm)
+ end
+
+ it 'returns a list of new private messages accounting for muted tags' do
+ tag = Fabricate(:tag)
+
+ pm.tags << tag
+
+ TagUser.create!(
+ tag: tag,
+ user: user_2,
+ notification_level: TopicUser.notification_levels[:muted]
+ )
+
+ expect(TopicQuery.new(user_2).list_private_messages_new(user_2).topics)
+ .to eq([])
+ end
+ end
+end
diff --git a/spec/models/topic_tracking_state_spec.rb b/spec/models/topic_tracking_state_spec.rb
index 26e3d2c26641d..85133188c3713 100644
--- a/spec/models/topic_tracking_state_spec.rb
+++ b/spec/models/topic_tracking_state_spec.rb
@@ -205,8 +205,8 @@
expect(messages.map(&:channel)).to contain_exactly(
'/private-messages/inbox',
- "/private-messages/group/#{group1.name}",
- "/private-messages/group/#{group2.name}"
+ "/private-messages/group/#{group1.name}/inbox",
+ "/private-messages/group/#{group2.name}/inbox"
)
message = messages.find do |m|
@@ -218,7 +218,7 @@
[group1, group2].each do |group|
message = messages.find do |m|
- m.channel == "/private-messages/group/#{group.name}"
+ m.channel == "/private-messages/group/#{group.name}/inbox"
end
expect(message.data["topic_id"]).to eq(private_message_topic.id)
@@ -237,9 +237,9 @@
expect(messages.map(&:channel)).to contain_exactly(
'/private-messages/inbox',
- "/private-messages/group/#{group1.name}",
+ "/private-messages/group/#{group1.name}/inbox",
"/private-messages/group/#{group1.name}/archive",
- "/private-messages/group/#{group2.name}",
+ "/private-messages/group/#{group2.name}/inbox",
"/private-messages/group/#{group2.name}/archive",
)
@@ -249,11 +249,9 @@
expect(message.user_ids).to eq(private_message_topic.allowed_users.map(&:id))
[group1, group2].each do |group|
- group_channel = "/private-messages/group/#{group.name}"
-
[
- group_channel,
- "#{group_channel}/archive"
+ "/private-messages/group/#{group.name}/inbox",
+ "/private-messages/group/#{group.name}/archive"
].each do |channel|
message = messages.find { |m| m.channel == channel }
expect(message.data["topic_id"]).to eq(private_message_topic.id)
@@ -291,7 +289,7 @@
expected_channels = [
'/private-messages/inbox',
'/private-messages/sent',
- "/private-messages/group/#{group.name}"
+ "/private-messages/group/#{group.name}/inbox"
]
expect(messages.map(&:channel)).to contain_exactly(*expected_channels)
diff --git a/spec/requests/tags_controller_spec.rb b/spec/requests/tags_controller_spec.rb
index fecf09f2ebdb9..782c5f2e943ba 100644
--- a/spec/requests/tags_controller_spec.rb
+++ b/spec/requests/tags_controller_spec.rb
@@ -473,7 +473,7 @@
it "can't see pm tags" do
get "/tags/personal_messages/#{regular_user.username}.json"
- expect(response).not_to be_successful
+ expect(response.status).to eq(403)
end
end
@@ -485,7 +485,7 @@
it "can't see pm tags for regular user" do
get "/tags/personal_messages/#{regular_user.username}.json"
- expect(response).not_to be_successful
+ expect(response.status).to eq(404)
end
it "can see their own pm tags" do