+ {{yield to="abovePostItemHeader"}}
+
-
+ {{yield to="abovePostItemExcerpt"}}
+
+
{{#if @post.expandedExcerpt}}
- {{htmlSafe @post.expandedExcerpt}}
+ {{~htmlSafe @post.expandedExcerpt~}}
{{else}}
- {{htmlSafe @post.excerpt}}
+ {{~htmlSafe @post.excerpt~}}
{{/if}}
+
+ {{yield to="belowPostItem"}}
}
diff --git a/app/assets/javascripts/discourse/app/components/user-stream-item.js b/app/assets/javascripts/discourse/app/components/user-stream-item.js
index 537d9c1ec36..9cb77527ec3 100644
--- a/app/assets/javascripts/discourse/app/components/user-stream-item.js
+++ b/app/assets/javascripts/discourse/app/components/user-stream-item.js
@@ -3,6 +3,7 @@ import { computed } from "@ember/object";
import { classNameBindings, tagName } from "@ember-decorators/component";
import { propertyEqual } from "discourse/lib/computed";
import discourseComputed from "discourse/lib/decorators";
+import deprecated from "discourse/lib/deprecated";
import { userPath } from "discourse/lib/url";
import { actionDescription } from "discourse/widgets/post-small-action";
@@ -26,6 +27,18 @@ export default class UserStreamItem extends Component {
)
actionDescription;
+ constructor() {
+ super(...arguments);
+ deprecated(
+ `
component is deprecated. Use
or
component to render a post list instead.`,
+ {
+ since: "3.4.0.beta4",
+ dropFrom: "3.5.0.beta1",
+ id: "discourse.user-stream-item",
+ }
+ );
+ }
+
@computed("item.hidden")
get hidden() {
return (
diff --git a/app/assets/javascripts/discourse/app/components/user-stream.gjs b/app/assets/javascripts/discourse/app/components/user-stream.gjs
new file mode 100644
index 00000000000..8ea63cd723d
--- /dev/null
+++ b/app/assets/javascripts/discourse/app/components/user-stream.gjs
@@ -0,0 +1,248 @@
+import Component from "@glimmer/component";
+import { tracked } from "@glimmer/tracking";
+import { fn, hash } from "@ember/helper";
+import { action } from "@ember/object";
+import { getOwner } from "@ember/owner";
+import { later } from "@ember/runloop";
+import { service } from "@ember/service";
+import { modifier } from "ember-modifier";
+import $ from "jquery";
+import DButton from "discourse/components/d-button";
+import PluginOutlet from "discourse/components/plugin-outlet";
+import PostActionDescription from "discourse/components/post-action-description";
+import PostList from "discourse/components/post-list";
+import avatar from "discourse/helpers/avatar";
+import concatClass from "discourse/helpers/concat-class";
+import dIcon from "discourse/helpers/d-icon";
+import { popupAjaxError } from "discourse/lib/ajax-error";
+import ClickTrack from "discourse/lib/click-track";
+import DiscourseURL from "discourse/lib/url";
+import { NEW_TOPIC_KEY } from "discourse/models/composer";
+import Draft from "discourse/models/draft";
+import Post from "discourse/models/post";
+import { i18n } from "discourse-i18n";
+
+export default class UserStreamComponent extends Component {
+ @service dialog;
+ @service composer;
+ @service appEvents;
+ @service currentUser;
+ @service router;
+ @tracked lastDecoratedElement;
+
+ eventListeners = modifier((element) => {
+ $(element).on("click.details-disabled", "details.disabled", () => false);
+ $(element).on("click.discourse-redirect", ".excerpt a", (e) => {
+ return ClickTrack.trackClick(e, getOwner(this));
+ });
+ later(() => {
+ this.updateLastDecoratedElement();
+ this.appEvents.trigger("decorate-non-stream-cooked-element", element);
+ });
+
+ return () => {
+ $(element).off("click.details-disabled", "details.disabled");
+ // Unbind link tracking
+ $(element).off("click.discourse-redirect", ".excerpt a");
+ };
+ });
+
+ constructor() {
+ super(...arguments);
+ }
+
+ get filterClassName() {
+ const filter = this.args.stream?.filter;
+
+ if (filter) {
+ return `filter-${filter.toString().replace(",", "-")}`;
+ }
+ }
+
+ get usernamePath() {
+ // We want the draft_username for the drafts route,
+ // in-case you are editing a post that was created by another user
+ // the draft usernmae will show the post item to show the editing user
+ if (this.router.currentRouteName === "userActivity.drafts") {
+ return "draft_username";
+ }
+
+ return "username";
+ }
+
+ @action
+ updateLastDecoratedElement() {
+ const nodes = document.querySelectorAll(".user-stream-item");
+ if (!nodes || nodes.length === 0) {
+ return;
+ }
+
+ const lastElement = nodes[nodes.length - 1];
+ if (lastElement === this.lastDecoratedElement) {
+ return;
+ }
+ this.lastDecoratedElement = lastElement;
+ }
+
+ @action
+ async removeBookmark(userAction) {
+ try {
+ await Post.updateBookmark(userAction.get("post_id"), false);
+ this.args.stream.remove(userAction);
+ } catch (error) {
+ popupAjaxError(error);
+ }
+ }
+
+ @action
+ async resumeDraft(item) {
+ if (this.composer.get("model.viewOpen")) {
+ this.composer.close();
+ }
+ if (item.get("postUrl")) {
+ DiscourseURL.routeTo(item.get("postUrl"));
+ } else {
+ try {
+ const draftData = await Draft.get(item.draft_key);
+ const draft = draftData.draft || item.data;
+ if (!draft) {
+ return;
+ }
+ this.composer.open({
+ draft,
+ draftKey: item.draft_key,
+ draftSequence: draftData.draft_sequence,
+ });
+ } catch (error) {
+ popupAjaxError(error);
+ }
+ }
+ }
+
+ @action
+ removeDraft(draft) {
+ this.dialog.yesNoConfirm({
+ message: i18n("drafts.remove_confirmation"),
+ didConfirm: async () => {
+ try {
+ await Draft.clear(draft.draft_key, draft.sequence);
+ this.args.stream.remove(draft);
+
+ if (draft.draft_key === NEW_TOPIC_KEY) {
+ this.currentUser.has_topic_draft = false;
+ }
+ } catch (error) {
+ popupAjaxError(error);
+ }
+ },
+ });
+ }
+
+ @action
+ async loadMore() {
+ await this.args.stream.findItems();
+
+ if (this.args.stream.canLoadMore === false) {
+ return [];
+ }
+
+ later(() => {
+ let element = this.lastDecoratedElement?.nextElementSibling;
+ while (element) {
+ this.appEvents.trigger("user-stream:new-item-inserted", element);
+ this.appEvents.trigger("decorate-non-stream-cooked-element", element);
+ element = element.nextElementSibling;
+ }
+ this.updateLastDecoratedElement();
+ });
+
+ return this.args.stream.content;
+ }
+
+
+
+ <:abovePostItemHeader as |post|>
+
+
+ <:belowPostItemMetaData as |post|>
+
+
+
+
+ <:abovePostItemExcerpt as |post|>
+
+
+ {{#each post.children as |child|}}
+
+ {{/each}}
+
+ {{#if post.editableDraft}}
+
+
+
+
+ {{/if}}
+
+
+ <:belowPostItem as |post|>
+ {{yield post to="bottom"}}
+
+
+
+}
diff --git a/app/assets/javascripts/discourse/app/components/user-stream.hbs b/app/assets/javascripts/discourse/app/components/user-stream.hbs
deleted file mode 100644
index 75a83184af6..00000000000
--- a/app/assets/javascripts/discourse/app/components/user-stream.hbs
+++ /dev/null
@@ -1,8 +0,0 @@
-{{#each @stream.content as |item|}}
-
-{{/each}}
\ No newline at end of file
diff --git a/app/assets/javascripts/discourse/app/components/user-stream.js b/app/assets/javascripts/discourse/app/components/user-stream.js
deleted file mode 100644
index 478c882faf7..00000000000
--- a/app/assets/javascripts/discourse/app/components/user-stream.js
+++ /dev/null
@@ -1,156 +0,0 @@
-import Component from "@ember/component";
-import { action } from "@ember/object";
-import { getOwner } from "@ember/owner";
-import { later } from "@ember/runloop";
-import { service } from "@ember/service";
-import { classNames, tagName } from "@ember-decorators/component";
-import { on } from "@ember-decorators/object";
-import $ from "jquery";
-import { popupAjaxError } from "discourse/lib/ajax-error";
-import ClickTrack from "discourse/lib/click-track";
-import DiscourseURL from "discourse/lib/url";
-import LoadMore from "discourse/mixins/load-more";
-import { NEW_TOPIC_KEY } from "discourse/models/composer";
-import Draft from "discourse/models/draft";
-import Post from "discourse/models/post";
-import { i18n } from "discourse-i18n";
-
-@tagName("ul")
-@classNames("user-stream")
-export default class UserStream extends Component.extend(LoadMore) {
- @service dialog;
- @service composer;
-
- loading = false;
- eyelineSelector = ".user-stream .item";
- _lastDecoratedElement = null;
-
- @on("init")
- _initialize() {
- const filter = this.get("stream.filter");
- if (filter) {
- this.set("classNames", [
- "user-stream",
- "filter-" + filter.toString().replace(",", "-"),
- ]);
- }
- }
-
- @on("didInsertElement")
- _inserted() {
- $(this.element).on(
- "click.details-disabled",
- "details.disabled",
- () => false
- );
- $(this.element).on("click.discourse-redirect", ".excerpt a", (e) => {
- return ClickTrack.trackClick(e, getOwner(this));
- });
- this._updateLastDecoratedElement();
- this.appEvents.trigger("decorate-non-stream-cooked-element", this.element);
- }
-
- // This view is being removed. Shut down operations
- @on("willDestroyElement")
- _destroyed() {
- $(this.element).off("click.details-disabled", "details.disabled");
-
- // Unbind link tracking
- $(this.element).off("click.discourse-redirect", ".excerpt a");
- }
-
- _updateLastDecoratedElement() {
- const nodes = this.element.querySelectorAll(".user-stream-item");
- if (nodes.length === 0) {
- return;
- }
- const lastElement = nodes[nodes.length - 1];
- if (lastElement === this._lastDecoratedElement) {
- return;
- }
- this._lastDecoratedElement = lastElement;
- }
-
- @action
- removeBookmark(userAction) {
- const stream = this.stream;
- Post.updateBookmark(userAction.get("post_id"), false)
- .then(() => {
- stream.remove(userAction);
- })
- .catch(popupAjaxError);
- }
-
- @action
- resumeDraft(item) {
- if (this.composer.get("model.viewOpen")) {
- this.composer.close();
- }
- if (item.get("postUrl")) {
- DiscourseURL.routeTo(item.get("postUrl"));
- } else {
- Draft.get(item.draft_key)
- .then((d) => {
- const draft = d.draft || item.data;
- if (!draft) {
- return;
- }
-
- this.composer.open({
- draft,
- draftKey: item.draft_key,
- draftSequence: d.draft_sequence,
- });
- })
- .catch((error) => {
- popupAjaxError(error);
- });
- }
- }
-
- @action
- removeDraft(draft) {
- const stream = this.stream;
-
- this.dialog.yesNoConfirm({
- message: i18n("drafts.remove_confirmation"),
- didConfirm: () => {
- Draft.clear(draft.draft_key, draft.sequence)
- .then(() => {
- stream.remove(draft);
- if (draft.draft_key === NEW_TOPIC_KEY) {
- this.currentUser.set("has_topic_draft", false);
- }
- })
- .catch((error) => {
- popupAjaxError(error);
- });
- },
- });
- }
-
- @action
- loadMore() {
- if (this.loading) {
- return;
- }
-
- this.set("loading", true);
- const stream = this.stream;
- stream.findItems().then(() => {
- this.set("loading", false);
-
- // The next elements are not rendered on the page yet, we need to
- // wait for that before trying to decorate them.
- later(() => {
- let element = this._lastDecoratedElement?.nextElementSibling;
- while (element) {
- this.trigger("user-stream:new-item-inserted", element);
- this.appEvents.trigger("decorate-non-stream-cooked-element", element);
- element = element.nextElementSibling;
- }
- this._updateLastDecoratedElement();
- });
- });
- }
-}
diff --git a/app/assets/javascripts/discourse/app/models/composer.js b/app/assets/javascripts/discourse/app/models/composer.js
index db466d72a91..506a04c1179 100644
--- a/app/assets/javascripts/discourse/app/models/composer.js
+++ b/app/assets/javascripts/discourse/app/models/composer.js
@@ -4,6 +4,7 @@ import { dependentKeyCompat } from "@ember/object/compat";
import { and, equal, not, or, reads } from "@ember/object/computed";
import { next, throttle } from "@ember/runloop";
import { service } from "@ember/service";
+import { isHTMLSafe } from "@ember/template";
import { isEmpty } from "@ember/utils";
import { observes, on } from "@ember-decorators/object";
import { Promise } from "rsvp";
@@ -643,6 +644,9 @@ export default class Composer extends RestModel {
@discourseComputed("title")
titleLength(title) {
title = title || "";
+ if (isHTMLSafe(title)) {
+ return title.toString().length;
+ }
return title.replace(/\s+/gim, " ").trim().length;
}
diff --git a/app/assets/javascripts/discourse/app/models/posts.js b/app/assets/javascripts/discourse/app/models/posts.js
new file mode 100644
index 00000000000..825bcfa1b05
--- /dev/null
+++ b/app/assets/javascripts/discourse/app/models/posts.js
@@ -0,0 +1,26 @@
+import { htmlSafe } from "@ember/template";
+import { ajax } from "discourse/lib/ajax";
+import Category from "discourse/models/category";
+import Post from "discourse/models/post";
+import RestModel from "discourse/models/rest";
+
+export default class Posts extends RestModel {
+ static async find(opts = {}) {
+ const data = {};
+
+ if (opts.before) {
+ data.before = opts.before;
+ }
+
+ if (opts.id) {
+ data.id = opts.id;
+ }
+
+ const { latest_posts } = await ajax("/posts.json", { data });
+ return latest_posts.map((post) => {
+ post.category = Category.findById(post.category_id);
+ post.topic_html_title = htmlSafe(post.topic_html_title);
+ return Post.create(post);
+ });
+ }
+}
diff --git a/app/assets/javascripts/discourse/app/models/user-draft.js b/app/assets/javascripts/discourse/app/models/user-draft.js
index 476f93bc9fc..fc4be0d3487 100644
--- a/app/assets/javascripts/discourse/app/models/user-draft.js
+++ b/app/assets/javascripts/discourse/app/models/user-draft.js
@@ -1,4 +1,5 @@
import { service } from "@ember/service";
+import replaceEmoji from "discourse/helpers/replace-emoji";
import discourseComputed from "discourse/lib/decorators";
import { userPath } from "discourse/lib/url";
import { postUrl } from "discourse/lib/utilities";
@@ -12,6 +13,10 @@ import { i18n } from "discourse-i18n";
export default class UserDraft extends RestModel {
@service currentUser;
+ get titleHtml() {
+ return replaceEmoji(this.get("title"));
+ }
+
@discourseComputed("draft_username")
editableDraft(draftUsername) {
return draftUsername === this.currentUser?.get("username");
diff --git a/app/assets/javascripts/discourse/app/models/user-drafts-stream.js b/app/assets/javascripts/discourse/app/models/user-drafts-stream.js
index b126f5ef02c..a4488d86949 100644
--- a/app/assets/javascripts/discourse/app/models/user-drafts-stream.js
+++ b/app/assets/javascripts/discourse/app/models/user-drafts-stream.js
@@ -1,8 +1,7 @@
import { Promise } from "rsvp";
import { ajax } from "discourse/lib/ajax";
import discourseComputed from "discourse/lib/decorators";
-import { cook, emojiUnescape, excerpt } from "discourse/lib/text";
-import { escapeExpression } from "discourse/lib/utilities";
+import { cook, excerpt } from "discourse/lib/text";
import Category from "discourse/models/category";
import {
NEW_PRIVATE_MESSAGE_KEY,
@@ -82,7 +81,6 @@ export default class UserDraftsStream extends RestModel {
) {
draft.title = draft.data.title;
}
- draft.title = emojiUnescape(escapeExpression(draft.title));
if (draft.data.categoryId) {
draft.category = Category.findById(draft.data.categoryId) || null;
}
diff --git a/app/assets/javascripts/discourse/app/models/user-stream.js b/app/assets/javascripts/discourse/app/models/user-stream.js
index 6ed62548edb..f0f32b78e8d 100644
--- a/app/assets/javascripts/discourse/app/models/user-stream.js
+++ b/app/assets/javascripts/discourse/app/models/user-stream.js
@@ -1,10 +1,9 @@
import { A } from "@ember/array";
import { Promise } from "rsvp";
+import replaceEmoji from "discourse/helpers/replace-emoji";
import { ajax } from "discourse/lib/ajax";
import { url } from "discourse/lib/computed";
import discourseComputed from "discourse/lib/decorators";
-import { emojiUnescape } from "discourse/lib/text";
-import { escapeExpression } from "discourse/lib/utilities";
import RestModel from "discourse/models/rest";
import Site from "discourse/models/site";
import UserAction from "discourse/models/user-action";
@@ -116,8 +115,8 @@ export default class UserStream extends RestModel {
Site.current().updateCategory(category);
});
- result.user_actions.forEach((action) => {
- action.title = emojiUnescape(escapeExpression(action.title));
+ result.user_actions?.forEach((action) => {
+ action.titleHtml = replaceEmoji(action.title);
copy.pushObject(UserAction.create(action));
});
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 0a30c35f607..d83c3dcf779 100644
--- a/app/assets/javascripts/discourse/app/routes/app-route-map.js
+++ b/app/assets/javascripts/discourse/app/routes/app-route-map.js
@@ -9,6 +9,7 @@ export default function () {
this.route("about", { resetNamespace: true });
this.route("post", { path: "/p/:id" });
+ this.route("posts");
// Topic routes
this.route(
diff --git a/app/assets/javascripts/discourse/app/routes/posts.js b/app/assets/javascripts/discourse/app/routes/posts.js
new file mode 100644
index 00000000000..44fe05b83ab
--- /dev/null
+++ b/app/assets/javascripts/discourse/app/routes/posts.js
@@ -0,0 +1,11 @@
+import { service } from "@ember/service";
+import Posts from "discourse/models/posts";
+import DiscourseRoute from "discourse/routes/discourse";
+
+export default class PostsRoute extends DiscourseRoute {
+ @service router;
+
+ async model() {
+ return Posts.find();
+ }
+}
diff --git a/app/assets/javascripts/discourse/app/templates/group-activity-posts.hbs b/app/assets/javascripts/discourse/app/templates/group-activity-posts.hbs
index 886a0ac86a5..58609867019 100644
--- a/app/assets/javascripts/discourse/app/templates/group-activity-posts.hbs
+++ b/app/assets/javascripts/discourse/app/templates/group-activity-posts.hbs
@@ -1,5 +1,6 @@
\ No newline at end of file
diff --git a/app/assets/javascripts/discourse/app/templates/posts.gjs b/app/assets/javascripts/discourse/app/templates/posts.gjs
new file mode 100644
index 00000000000..879312a5c5b
--- /dev/null
+++ b/app/assets/javascripts/discourse/app/templates/posts.gjs
@@ -0,0 +1,25 @@
+import Component from "@glimmer/component";
+import { action } from "@ember/object";
+import RouteTemplate from "ember-route-template";
+import PostList from "discourse/components/post-list";
+import Posts from "discourse/models/posts";
+
+export default RouteTemplate(
+ class extends Component {
+ @action
+ async loadMorePosts() {
+ const posts = this.args.model;
+ const before = posts[posts.length - 1].created_at;
+
+ return Posts.find({ before });
+ }
+
+
+
+
+ }
+);
diff --git a/app/assets/javascripts/discourse/app/templates/user-activity-pending.hbs b/app/assets/javascripts/discourse/app/templates/user-activity-pending.hbs
index 3f3b047982d..8d59a74f391 100644
--- a/app/assets/javascripts/discourse/app/templates/user-activity-pending.hbs
+++ b/app/assets/javascripts/discourse/app/templates/user-activity-pending.hbs
@@ -1,5 +1,9 @@
- {{#each this.model as |pending_post|}}
-
- {{/each}}
+
\ No newline at end of file
diff --git a/app/assets/javascripts/discourse/app/templates/user/posts.hbs b/app/assets/javascripts/discourse/app/templates/user/posts.hbs
index 2459802c51e..5068aff9f17 100644
--- a/app/assets/javascripts/discourse/app/templates/user/posts.hbs
+++ b/app/assets/javascripts/discourse/app/templates/user/posts.hbs
@@ -1,5 +1 @@
-{{#if this.model.canLoadMore}}
- {{hide-application-footer}}
-{{/if}}
-
\ No newline at end of file
diff --git a/app/assets/javascripts/discourse/app/templates/user/stream.hbs b/app/assets/javascripts/discourse/app/templates/user/stream.hbs
index a86b9da1b85..c035790268d 100644
--- a/app/assets/javascripts/discourse/app/templates/user/stream.hbs
+++ b/app/assets/javascripts/discourse/app/templates/user/stream.hbs
@@ -1,11 +1,8 @@
-{{#if (or this.loading this.model.stream.canLoadMore)}}
- {{hide-application-footer}}
-{{/if}}
-
{{#if this.model.stream.noContent}}
{{/if}}
+
\ No newline at end of file
diff --git a/app/assets/javascripts/discourse/tests/acceptance/user-anonymous-test.js b/app/assets/javascripts/discourse/tests/acceptance/user-anonymous-test.js
index dc97f2cc9e4..b2256e243cd 100644
--- a/app/assets/javascripts/discourse/tests/acceptance/user-anonymous-test.js
+++ b/app/assets/javascripts/discourse/tests/acceptance/user-anonymous-test.js
@@ -22,15 +22,15 @@ acceptance("User Anonymous", function () {
.dom(document.body)
.hasClass("user-activity-page", "has the body class");
assert.dom(".user-main .about").exists("has the about section");
- assert.dom(".user-stream .item").exists("has stream items");
+ assert.dom(".user-stream-item").exists("has stream items");
await visit("/u/eviltrout/activity/topics");
- assert.dom(".user-stream .item").doesNotExist("has no stream displayed");
+ assert.dom(".user-stream-item").doesNotExist("has no stream displayed");
assert.dom(".topic-list tr").exists("has a topic list");
await visit("/u/eviltrout/activity/replies");
assert.dom(".user-main .about").exists("has the about section");
- assert.dom(".user-stream .item").exists("has stream items");
+ assert.dom(".user-stream-item").exists("has stream items");
assert.dom(".user-stream.filter-5").exists("stream has filter class");
});
diff --git a/app/assets/javascripts/discourse/tests/fixtures/group-fixtures.js b/app/assets/javascripts/discourse/tests/fixtures/group-fixtures.js
index 9b8b213e854..facad0a23b0 100644
--- a/app/assets/javascripts/discourse/tests/fixtures/group-fixtures.js
+++ b/app/assets/javascripts/discourse/tests/fixtures/group-fixtures.js
@@ -306,13 +306,10 @@ export default {
name_lower: "ux",
auto_close_based_on_last_post: false,
},
- user: {
- id: 2770,
- username: "awesomerobot",
- uploaded_avatar_id: 33872,
- avatar_template:
- "/user_avatar/meta.discourse.org/awesomerobot/{size}/33872.png",
- },
+ user_id: 2770,
+ username: "awesomerobot",
+ avatar_template:
+ "/user_avatar/meta.discourse.org/awesomerobot/{size}/33872.png",
},
{
id: 94603,
@@ -358,13 +355,10 @@ export default {
name_lower: "ux",
auto_close_based_on_last_post: false,
},
- user: {
- id: 2770,
- username: "awesomerobot",
- uploaded_avatar_id: 33872,
- avatar_template:
- "/user_avatar/meta.discourse.org/awesomerobot/{size}/33872.png",
- },
+ user_id: 2770,
+ username: "awesomerobot",
+ avatar_template:
+ "/user_avatar/meta.discourse.org/awesomerobot/{size}/33872.png",
},
{
id: 94601,
@@ -410,13 +404,11 @@ export default {
name_lower: "ux",
auto_close_based_on_last_post: false,
},
- user: {
- id: 2770,
- username: "awesomerobot",
- uploaded_avatar_id: 33872,
- avatar_template:
- "/user_avatar/meta.discourse.org/awesomerobot/{size}/33872.png",
- },
+ user_id: 2770,
+ username: "awesomerobot",
+ avatar_template:
+ "/user_avatar/meta.discourse.org/awesomerobot/{size}/33872.png",
+
},
{
id: 94577,
@@ -463,13 +455,10 @@ export default {
name_lower: "feature",
auto_close_based_on_last_post: false,
},
- user: {
- id: 1995,
- username: "zogstrip",
- uploaded_avatar_id: 8630,
- avatar_template:
- "/user_avatar/meta.discourse.org/zogstrip/{size}/8630.png",
- },
+ user_id: 1995,
+ username: "zogstrip",
+ avatar_template:
+ "/user_avatar/meta.discourse.org/zogstrip/{size}/8630.png",
},
{
id: 94574,
@@ -516,13 +505,11 @@ export default {
name_lower: "feature",
auto_close_based_on_last_post: false,
},
- user: {
- id: 1995,
- username: "zogstrip",
- uploaded_avatar_id: 8630,
- avatar_template:
- "/user_avatar/meta.discourse.org/zogstrip/{size}/8630.png",
- },
+ user_id: 1995,
+ username: "zogstrip",
+ avatar_template:
+ "/user_avatar/meta.discourse.org/zogstrip/{size}/8630.png",
+
},
{
id: 94572,
@@ -569,13 +556,11 @@ export default {
name_lower: "translations",
auto_close_based_on_last_post: false,
},
- user: {
- id: 1995,
- username: "zogstrip",
- uploaded_avatar_id: 8630,
- avatar_template:
- "/user_avatar/meta.discourse.org/zogstrip/{size}/8630.png",
- },
+ user_id: 1995,
+ username: "zogstrip",
+ avatar_template:
+ "/user_avatar/meta.discourse.org/zogstrip/{size}/8630.png",
+
},
{
id: 94555,
@@ -623,13 +608,11 @@ export default {
name_lower: "dev",
auto_close_based_on_last_post: false,
},
- user: {
- id: 1995,
- username: "zogstrip",
- uploaded_avatar_id: 8630,
- avatar_template:
- "/user_avatar/meta.discourse.org/zogstrip/{size}/8630.png",
- },
+ user_id: 1995,
+ username: "zogstrip",
+ avatar_template:
+ "/user_avatar/meta.discourse.org/zogstrip/{size}/8630.png",
+
},
{
id: 94544,
@@ -676,13 +659,9 @@ export default {
name_lower: "ux",
auto_close_based_on_last_post: false,
},
- user: {
- id: 32,
- username: "codinghorror",
- uploaded_avatar_id: 5297,
- avatar_template:
- "/user_avatar/meta.discourse.org/codinghorror/{size}/5297.png",
- },
+ user_id: 32,
+ username: "codinghorror",
+ avatar_template: "/user_avatar/meta.discourse.org/codinghorror/{size}/5297.png",
},
{
id: 94543,
@@ -729,13 +708,10 @@ export default {
name_lower: "feature",
auto_close_based_on_last_post: false,
},
- user: {
- id: 32,
- username: "codinghorror",
- uploaded_avatar_id: 5297,
- avatar_template:
- "/user_avatar/meta.discourse.org/codinghorror/{size}/5297.png",
- },
+ user_id: 32,
+ username: "codinghorror",
+ avatar_template: "/user_avatar/meta.discourse.org/codinghorror/{size}/5297.png",
+
},
{
id: 94542,
@@ -782,13 +758,9 @@ export default {
name_lower: "support",
auto_close_based_on_last_post: false,
},
- user: {
- id: 32,
- username: "codinghorror",
- uploaded_avatar_id: 5297,
- avatar_template:
- "/user_avatar/meta.discourse.org/codinghorror/{size}/5297.png",
- },
+ user_id: 32,
+ username: "codinghorror",
+ avatar_template: "/user_avatar/meta.discourse.org/codinghorror/{size}/5297.png",
},
{
id: 94522,
@@ -835,13 +807,9 @@ export default {
name_lower: "bug",
auto_close_based_on_last_post: false,
},
- user: {
- id: 32,
- username: "codinghorror",
- uploaded_avatar_id: 5297,
- avatar_template:
- "/user_avatar/meta.discourse.org/codinghorror/{size}/5297.png",
- },
+ user_id: 32,
+ username: "codinghorror",
+ avatar_template: "/user_avatar/meta.discourse.org/codinghorror/{size}/5297.png",
},
{
id: 94521,
@@ -888,13 +856,9 @@ export default {
name_lower: "ux",
auto_close_based_on_last_post: false,
},
- user: {
- id: 32,
- username: "codinghorror",
- uploaded_avatar_id: 5297,
- avatar_template:
- "/user_avatar/meta.discourse.org/codinghorror/{size}/5297.png",
- },
+ user_id: 32,
+ username: "codinghorror",
+ avatar_template: "/user_avatar/meta.discourse.org/codinghorror/{size}/5297.png",
},
{
id: 94519,
@@ -941,13 +905,9 @@ export default {
name_lower: "meta",
auto_close_based_on_last_post: false,
},
- user: {
- id: 32,
- username: "codinghorror",
- uploaded_avatar_id: 5297,
- avatar_template:
- "/user_avatar/meta.discourse.org/codinghorror/{size}/5297.png",
- },
+ user_id: 32,
+ username: "codinghorror",
+ avatar_template: "/user_avatar/meta.discourse.org/codinghorror/{size}/5297.png",
},
{
id: 94518,
@@ -994,13 +954,9 @@ export default {
name_lower: "support",
auto_close_based_on_last_post: false,
},
- user: {
- id: 32,
- username: "codinghorror",
- uploaded_avatar_id: 5297,
- avatar_template:
- "/user_avatar/meta.discourse.org/codinghorror/{size}/5297.png",
- },
+ user_id: 32,
+ username: "codinghorror",
+ avatar_template: "/user_avatar/meta.discourse.org/codinghorror/{size}/5297.png",
},
{
id: 94517,
@@ -1047,13 +1003,9 @@ export default {
name_lower: "bug",
auto_close_based_on_last_post: false,
},
- user: {
- id: 32,
- username: "codinghorror",
- uploaded_avatar_id: 5297,
- avatar_template:
- "/user_avatar/meta.discourse.org/codinghorror/{size}/5297.png",
- },
+ user_id: 32,
+ username: "codinghorror",
+ avatar_template: "/user_avatar/meta.discourse.org/codinghorror/{size}/5297.png",
},
{
id: 94516,
@@ -1100,13 +1052,9 @@ export default {
name_lower: "support",
auto_close_based_on_last_post: false,
},
- user: {
- id: 32,
- username: "codinghorror",
- uploaded_avatar_id: 5297,
- avatar_template:
- "/user_avatar/meta.discourse.org/codinghorror/{size}/5297.png",
- },
+ user_id: 32,
+ username: "codinghorror",
+ avatar_template: "/user_avatar/meta.discourse.org/codinghorror/{size}/5297.png",
},
{
id: 94515,
@@ -1154,13 +1102,9 @@ export default {
name_lower: "dev",
auto_close_based_on_last_post: false,
},
- user: {
- id: 32,
- username: "codinghorror",
- uploaded_avatar_id: 5297,
- avatar_template:
- "/user_avatar/meta.discourse.org/codinghorror/{size}/5297.png",
- },
+ user_id: 32,
+ username: "codinghorror",
+ avatar_template: "/user_avatar/meta.discourse.org/codinghorror/{size}/5297.png",
},
{
id: 94514,
@@ -1207,13 +1151,9 @@ export default {
name_lower: "extensibility",
auto_close_based_on_last_post: false,
},
- user: {
- id: 32,
- username: "codinghorror",
- uploaded_avatar_id: 5297,
- avatar_template:
- "/user_avatar/meta.discourse.org/codinghorror/{size}/5297.png",
- },
+ user_id: 32,
+ username: "codinghorror",
+ avatar_template: "/user_avatar/meta.discourse.org/codinghorror/{size}/5297.png",
},
{
id: 94512,
@@ -1260,13 +1200,9 @@ export default {
name_lower: "support",
auto_close_based_on_last_post: false,
},
- user: {
- id: 32,
- username: "codinghorror",
- uploaded_avatar_id: 5297,
- avatar_template:
- "/user_avatar/meta.discourse.org/codinghorror/{size}/5297.png",
- },
+ user_id: 32,
+ username: "codinghorror",
+ avatar_template: "/user_avatar/meta.discourse.org/codinghorror/{size}/5297.png",
},
{
id: 94511,
@@ -1313,13 +1249,9 @@ export default {
name_lower: "feature",
auto_close_based_on_last_post: false,
},
- user: {
- id: 32,
- username: "codinghorror",
- uploaded_avatar_id: 5297,
- avatar_template:
- "/user_avatar/meta.discourse.org/codinghorror/{size}/5297.png",
- },
+ user_id: 32,
+ username: "codinghorror",
+ avatar_template: "/user_avatar/meta.discourse.org/codinghorror/{size}/5297.png",
},
],
},
diff --git a/app/assets/javascripts/discourse/tests/fixtures/post-list.js b/app/assets/javascripts/discourse/tests/fixtures/post-list.js
index 5d4d8435528..1b05a73c85b 100644
--- a/app/assets/javascripts/discourse/tests/fixtures/post-list.js
+++ b/app/assets/javascripts/discourse/tests/fixtures/post-list.js
@@ -2,6 +2,8 @@ const postModel = [
{
id: 1,
title: "My dog is so cute",
+ url: "/t/my-dog-is-so-cute/1/1",
+ topic_id: 1,
created_at: "2024-03-15T18:45:38.720Z",
category: {
id: 1,
@@ -21,6 +23,8 @@ const postModel = [
{
id: 2,
title: "My cat is adorable",
+ url: "/t/my-cat-is-so-adorable/2/1",
+ topic_id: 2,
created_at: "2024-03-16T18:45:38.720Z",
category: {
id: 1,
diff --git a/app/assets/javascripts/discourse/tests/integration/components/pending-post-test.js b/app/assets/javascripts/discourse/tests/integration/components/pending-post-test.js
deleted file mode 100644
index 68c8713276c..00000000000
--- a/app/assets/javascripts/discourse/tests/integration/components/pending-post-test.js
+++ /dev/null
@@ -1,26 +0,0 @@
-import { getOwner } from "@ember/owner";
-import { render } from "@ember/test-helpers";
-import { hbs } from "ember-cli-htmlbars";
-import { module, test } from "qunit";
-import { setupRenderingTest } from "discourse/tests/helpers/component-test";
-
-module("Integration | Component | pending-post", function (hooks) {
- setupRenderingTest(hooks);
-
- test("it renders", async function (assert) {
- const store = getOwner(this).lookup("service:store");
- store.createRecord("category", { id: 2 });
- const post = store.createRecord("pending-post", {
- id: 1,
- topic_url: "topic-url",
- username: "USERNAME",
- category_id: 2,
- raw_text: "**bold text**",
- });
- this.set("post", post);
-
- await render(hbs`
`);
-
- assert.dom("p.excerpt").hasText("bold text", "renders the cooked text");
- });
-});
diff --git a/app/assets/javascripts/discourse/tests/integration/components/post-list-test.gjs b/app/assets/javascripts/discourse/tests/integration/components/post-list-test.gjs
index 0e29a89fd67..b867d6da082 100644
--- a/app/assets/javascripts/discourse/tests/integration/components/post-list-test.gjs
+++ b/app/assets/javascripts/discourse/tests/integration/components/post-list-test.gjs
@@ -43,4 +43,61 @@ module("Integration | Component | PostList | Index", function (hooks) {
);
assert.dom(".post-list__empty-text").hasText("My custom empty text");
});
+
+ test("@showUserInfo", async function (assert) {
+ const posts = postModel;
+ await render(
+
+ );
+ assert.dom(".post-list-item__details .post-member-info").doesNotExist();
+ });
+
+ test("@titlePath", async function (assert) {
+ const posts = postModel.map((post) => {
+ post.topic_html_title = `Fancy title`;
+ return post;
+ });
+ await render(
+
+ );
+ assert.dom(".post-list-item__details .title a").hasText("Fancy title");
+ });
+
+ test("@idPath", async function (assert) {
+ const posts = postModel.map((post) => {
+ post.post_id = post.id;
+ return post;
+ });
+ await render(
+
+ );
+ assert.dom(".post-list-item .excerpt").hasAttribute("data-post-id", "1");
+ });
+
+ test("@urlPath", async function (assert) {
+ const posts = postModel.map((post) => {
+ post.postUrl = `/t/${post.topic_id}/${post.id}`;
+ return post;
+ });
+ await render(
+
+ );
+ assert
+ .dom(".post-list-item__details .title a")
+ .hasAttribute("href", "/t/1/1");
+ });
+
+ test("@usernamePath", async function (assert) {
+ const posts = postModel.map((post) => {
+ post.draft_username = "john";
+ return post;
+ });
+
+ await render(
+
+ );
+ assert
+ .dom(".post-list-item__header .avatar-link")
+ .hasAttribute("data-user-card", "john");
+ });
});
diff --git a/app/assets/stylesheets/common/components/user-stream-item.scss b/app/assets/stylesheets/common/components/user-stream-item.scss
index 8ed3a54c323..a57e540dbb4 100644
--- a/app/assets/stylesheets/common/components/user-stream-item.scss
+++ b/app/assets/stylesheets/common/components/user-stream-item.scss
@@ -62,7 +62,8 @@
line-height: var(--line-height-small);
color: var(--primary-medium);
font-size: var(--font-down-2);
- padding-top: 5px;
+ padding-top: 6px;
+ margin-right: 0.5rem;
}
.delete-info {
diff --git a/app/controllers/posts_controller.rb b/app/controllers/posts_controller.rb
index 96c3621f8b8..9dc6c8434dc 100644
--- a/app/controllers/posts_controller.rb
+++ b/app/controllers/posts_controller.rb
@@ -25,7 +25,8 @@ class PostsController < ApplicationController
skip_before_action :preload_json,
:check_xhr,
- only: %i[markdown_id markdown_num short_link latest user_posts_feed]
+ only: %i[markdown_id markdown_num short_link user_posts_feed]
+ skip_before_action :preload_json, :check_xhr, if: -> { request.format.rss? }
MARKDOWN_TOPIC_PAGE_SIZE = 100
@@ -128,6 +129,7 @@ class PostsController < ApplicationController
scope: guardian,
root: params[:id],
add_raw: true,
+ add_excerpt: true,
add_title: true,
all_post_actions: counts,
),
diff --git a/app/serializers/group_post_serializer.rb b/app/serializers/group_post_serializer.rb
index 0eed59b7a63..2ff638e7a70 100644
--- a/app/serializers/group_post_serializer.rb
+++ b/app/serializers/group_post_serializer.rb
@@ -5,15 +5,43 @@ require_relative "post_item_excerpt"
class GroupPostSerializer < ApplicationSerializer
include PostItemExcerpt
- attributes :id, :created_at, :title, :url, :category_id, :post_number, :topic_id, :post_type
+ attributes :id,
+ :created_at,
+ :topic_id,
+ :topic_title,
+ :topic_slug,
+ :topic_html_title,
+ :url,
+ :category_id,
+ :post_number,
+ :posts_count,
+ :post_type,
+ :username,
+ :name,
+ :avatar_template,
+ :user_title,
+ :primary_group_name
+ # TODO(keegan): Remove `embed: :object` after updating references in discourse-reactions
has_one :user, serializer: GroupPostUserSerializer, embed: :object
has_one :topic, serializer: BasicTopicSerializer, embed: :object
- def title
+ def topic_title
object.topic.title
end
+ def topic_html_title
+ object.topic.fancy_title
+ end
+
+ def topic_slug
+ object.topic.slug
+ end
+
+ def posts_count
+ object.topic.posts_count
+ end
+
def include_user_long_name?
SiteSetting.enable_names?
end
@@ -21,4 +49,24 @@ class GroupPostSerializer < ApplicationSerializer
def category_id
object.topic.category_id
end
+
+ def username
+ object&.user&.username
+ end
+
+ def name
+ object&.user&.name
+ end
+
+ def avatar_template
+ object&.user&.avatar_template
+ end
+
+ def user_title
+ object&.user&.title
+ end
+
+ def primary_group_name
+ object&.user&.primary_group&.name
+ end
end
diff --git a/app/serializers/post_serializer.rb b/app/serializers/post_serializer.rb
index d98be8d6813..8ae1f9e5b0e 100644
--- a/app/serializers/post_serializer.rb
+++ b/app/serializers/post_serializer.rb
@@ -17,6 +17,7 @@ class PostSerializer < BasicPostSerializer
attributes :post_number,
:post_type,
+ :posts_count,
:updated_at,
:reply_count,
:reply_to_post_number,
@@ -84,12 +85,14 @@ class PostSerializer < BasicPostSerializer
:last_wiki_edit,
:locked,
:excerpt,
+ :truncated,
:reviewable_id,
:reviewable_score_count,
:reviewable_score_pending_count,
:user_suspended,
:user_status,
- :mentioned_users
+ :mentioned_users,
+ :post_url
def initialize(object, opts)
super(object, opts)
@@ -99,6 +102,10 @@ class PostSerializer < BasicPostSerializer
end
end
+ def post_url
+ object&.url
+ end
+
def topic_slug
topic&.slug
end
@@ -119,6 +126,14 @@ class PostSerializer < BasicPostSerializer
@add_excerpt
end
+ def include_truncated?
+ @add_excerpt
+ end
+
+ def truncated
+ true
+ end
+
def topic_title
topic&.title
end
@@ -127,6 +142,10 @@ class PostSerializer < BasicPostSerializer
topic&.fancy_title
end
+ def posts_count
+ topic&.posts_count
+ end
+
def category_id
topic&.category_id
end
diff --git a/spec/requests/api/schemas/json/post_replies_response.json b/spec/requests/api/schemas/json/post_replies_response.json
index 8c9ecb3e582..d93837743e6 100644
--- a/spec/requests/api/schemas/json/post_replies_response.json
+++ b/spec/requests/api/schemas/json/post_replies_response.json
@@ -32,6 +32,9 @@
"post_type": {
"type": "integer"
},
+ "posts_count": {
+ "type": "integer"
+ },
"updated_at": {
"type": "string"
},
@@ -155,23 +158,22 @@
},
"actions_summary": {
"type": "array",
- "items":
- {
- "type": "object",
- "additionalProperties": false,
- "properties": {
- "id": {
- "type": "integer"
- },
- "can_act": {
- "type": "boolean"
- }
+ "items": {
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "id": {
+ "type": "integer"
},
- "required": [
- "id",
- "can_act"
- ]
- }
+ "can_act": {
+ "type": "boolean"
+ }
+ },
+ "required": [
+ "id",
+ "can_act"
+ ]
+ }
},
"moderator": {
"type": "boolean"
@@ -223,6 +225,9 @@
},
"reviewable_score_pending_count": {
"type": "integer"
+ },
+ "post_url": {
+ "type": "string"
}
},
"required": [
@@ -234,6 +239,7 @@
"cooked",
"post_number",
"post_type",
+ "posts_count",
"updated_at",
"reply_count",
"reply_to_post_number",
@@ -274,7 +280,8 @@
"wiki",
"reviewable_id",
"reviewable_score_count",
- "reviewable_score_pending_count"
+ "reviewable_score_pending_count",
+ "post_url"
]
}
-}
+}
\ No newline at end of file
diff --git a/spec/requests/api/schemas/json/post_show_response.json b/spec/requests/api/schemas/json/post_show_response.json
index 2148cd0a0f9..85ba81115b4 100644
--- a/spec/requests/api/schemas/json/post_show_response.json
+++ b/spec/requests/api/schemas/json/post_show_response.json
@@ -22,6 +22,9 @@
"post_type": {
"type": "integer"
},
+ "posts_count": {
+ "type": "integer"
+ },
"updated_at": {
"type": "string"
},
@@ -203,6 +206,9 @@
"reviewable_score_pending_count": {
"type": "integer"
},
+ "post_url": {
+ "type": "string"
+ },
"mentioned_users": {
"type": "array",
"items": {}
@@ -228,6 +234,7 @@
"cooked",
"post_number",
"post_type",
+ "posts_count",
"updated_at",
"reply_count",
"reply_to_post_number",
@@ -266,6 +273,7 @@
"wiki",
"reviewable_id",
"reviewable_score_count",
- "reviewable_score_pending_count"
+ "reviewable_score_pending_count",
+ "post_url"
]
-}
+}
\ No newline at end of file
diff --git a/spec/requests/api/schemas/json/post_update_response.json b/spec/requests/api/schemas/json/post_update_response.json
index bb004f4ca06..27ee8bbf9ed 100644
--- a/spec/requests/api/schemas/json/post_update_response.json
+++ b/spec/requests/api/schemas/json/post_update_response.json
@@ -26,6 +26,9 @@
"post_type": {
"type": "integer"
},
+ "posts_count": {
+ "type": "integer"
+ },
"updated_at": {
"type": "string"
},
@@ -205,6 +208,9 @@
"reviewable_score_pending_count": {
"type": "integer"
},
+ "post_url": {
+ "type": "string"
+ },
"mentioned_users": {
"type": "array",
"items": {}
@@ -230,6 +236,7 @@
"cooked",
"post_number",
"post_type",
+ "posts_count",
"updated_at",
"reply_count",
"reply_to_post_number",
@@ -269,11 +276,12 @@
"wiki",
"reviewable_id",
"reviewable_score_count",
- "reviewable_score_pending_count"
+ "reviewable_score_pending_count",
+ "post_url"
]
}
},
"required": [
"post"
]
-}
+}
\ No newline at end of file
diff --git a/spec/requests/api/schemas/json/topic_create_response.json b/spec/requests/api/schemas/json/topic_create_response.json
index 5cb486c8508..9849ca27e64 100644
--- a/spec/requests/api/schemas/json/topic_create_response.json
+++ b/spec/requests/api/schemas/json/topic_create_response.json
@@ -31,6 +31,9 @@
"post_type": {
"type": "integer"
},
+ "posts_count": {
+ "type": "integer"
+ },
"updated_at": {
"type": "string"
},
@@ -142,23 +145,22 @@
},
"actions_summary": {
"type": "array",
- "items":
- {
- "type": "object",
- "additionalProperties": false,
- "properties": {
- "id": {
- "type": "integer"
- },
- "can_act": {
- "type": "boolean"
- }
+ "items": {
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "id": {
+ "type": "integer"
},
- "required": [
- "id",
- "can_act"
- ]
- }
+ "can_act": {
+ "type": "boolean"
+ }
+ },
+ "required": [
+ "id",
+ "can_act"
+ ]
+ }
},
"moderator": {
"type": "boolean"
@@ -214,10 +216,12 @@
"reviewable_score_pending_count": {
"type": "integer"
},
+ "post_url": {
+ "type": "string"
+ },
"mentioned_users": {
"type": "array",
- "items": {
- }
+ "items": {}
}
},
"required": [
@@ -229,6 +233,7 @@
"cooked",
"post_number",
"post_type",
+ "posts_count",
"updated_at",
"reply_count",
"reply_to_post_number",
@@ -268,6 +273,7 @@
"wiki",
"reviewable_id",
"reviewable_score_count",
- "reviewable_score_pending_count"
+ "reviewable_score_pending_count",
+ "post_url"
]
-}
+}
\ No newline at end of file
diff --git a/spec/serializers/web_hook_post_serializer_spec.rb b/spec/serializers/web_hook_post_serializer_spec.rb
index f4c364a542d..c3ceca71e6a 100644
--- a/spec/serializers/web_hook_post_serializer_spec.rb
+++ b/spec/serializers/web_hook_post_serializer_spec.rb
@@ -18,6 +18,7 @@ RSpec.describe WebHookPostSerializer do
:cooked,
:post_number,
:post_type,
+ :posts_count,
:updated_at,
:reply_count,
:reply_to_post_number,
@@ -50,6 +51,7 @@ RSpec.describe WebHookPostSerializer do
:reviewable_id,
:reviewable_score_count,
:reviewable_score_pending_count,
+ :post_url,
:topic_posts_count,
:topic_filtered_posts_count,
:topic_archetype,