diff --git a/app/assets/javascripts/discourse/app/components/pending-post.hbs b/app/assets/javascripts/discourse/app/components/pending-post.hbs deleted file mode 100644 index a0f045cc386..00000000000 --- a/app/assets/javascripts/discourse/app/components/pending-post.hbs +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/app/assets/javascripts/discourse/app/components/pending-post.js b/app/assets/javascripts/discourse/app/components/pending-post.js deleted file mode 100644 index b0e09b0ddca..00000000000 --- a/app/assets/javascripts/discourse/app/components/pending-post.js +++ /dev/null @@ -1,30 +0,0 @@ -import Component from "@ember/component"; -import { resolveAllShortUrls } from "pretty-text/upload-short-url"; -import { ajax } from "discourse/lib/ajax"; -import { afterRender } from "discourse/lib/decorators"; -import { loadOneboxes } from "discourse/lib/load-oneboxes"; - -export default class PendingPost extends Component { - didRender() { - super.didRender(...arguments); - this._loadOneboxes(); - this._resolveUrls(); - } - - @afterRender - _loadOneboxes() { - loadOneboxes( - this.element, - ajax, - this.post.topic_id, - this.post.category_id, - this.siteSettings.max_oneboxes_per_post, - true - ); - } - - @afterRender - _resolveUrls() { - resolveAllShortUrls(ajax, this.siteSettings, this.element, this.opts); - } -} diff --git a/app/assets/javascripts/discourse/app/components/post-action-description.gjs b/app/assets/javascripts/discourse/app/components/post-action-description.gjs new file mode 100644 index 00000000000..a433a565c32 --- /dev/null +++ b/app/assets/javascripts/discourse/app/components/post-action-description.gjs @@ -0,0 +1,21 @@ +import Component from "@glimmer/component"; +import { actionDescriptionHtml } from "discourse/widgets/post-small-action"; + +export default class PostActionDescription extends Component { + get description() { + if (this.args.actionCode && this.args.createdAt) { + return actionDescriptionHtml( + this.args.actionCode, + this.args.createdAt, + this.args.username, + this.args.path + ); + } + } + + +} diff --git a/app/assets/javascripts/discourse/app/components/post-list/index.gjs b/app/assets/javascripts/discourse/app/components/post-list/index.gjs index 0735faf18aa..5a7e0891303 100644 --- a/app/assets/javascripts/discourse/app/components/post-list/index.gjs +++ b/app/assets/javascripts/discourse/app/components/post-list/index.gjs @@ -8,14 +8,21 @@ * @args {String} emptyText (optional) - Custom text to display when there are no posts * @args {String|Array} additionalItemClasses (optional) - Additional classes to add to each post list item * @args {String} titleAriaLabel (optional) - Custom Aria label for the post title + * @args {String} showUserInfo (optional) - Whether to show user info in the post list items + * @args {String} idPath (optional) - The path to the key in the post object that contains the post ID + * @args {String} urlPath (optional) - The path to the key in the post object that contains the post URL + * @args {String} titlePath (optional) - The path to the key in the post object that contains the post title + * @args {String} usernamePath (optional) - The path to the key in the post object that contains the post author's username * * @template Usage Example: * ``` * * ``` */ @@ -68,13 +75,31 @@ export default class PostList extends Component { {{/if}} -
+
{{#each @posts as |post|}} + @showUserInfo={{@showUserInfo}} + > + <:abovePostItemHeader> + {{yield post to="abovePostItemHeader"}} + + <:belowPostItemMetaData> + {{yield post to="belowPostItemMetaData"}} + + <:abovePostItemExcerpt> + {{yield post to="abovePostItemExcerpt"}} + + <:belowPostItem> + {{yield post to="belowPostItem"}} + + {{else}}
{{this.emptyText}}
{{/each}} diff --git a/app/assets/javascripts/discourse/app/components/post-list/item/details.gjs b/app/assets/javascripts/discourse/app/components/post-list/item/details.gjs index dbee29bf63e..9f5189d05ac 100644 --- a/app/assets/javascripts/discourse/app/components/post-list/item/details.gjs +++ b/app/assets/javascripts/discourse/app/components/post-list/item/details.gjs @@ -1,65 +1,93 @@ import Component from "@glimmer/component"; import { hash } from "@ember/helper"; -import { htmlSafe } from "@ember/template"; import PluginOutlet from "discourse/components/plugin-outlet"; +import TopicStatus from "discourse/components/topic-status"; import categoryLink from "discourse/helpers/category-link"; import getURL from "discourse/lib/get-url"; import { prioritizeNameInUx } from "discourse/lib/settings"; import { i18n } from "discourse-i18n"; export default class PostListItemDetails extends Component { + get url() { + return this.args.urlPath + ? this.args.post[this.args.urlPath] + : this.args.post.url; + } + + get showUserInfo() { + if (this.args.showUserInfo !== undefined) { + return this.args.showUserInfo && this.args.user; + } + + return this.args.user; + } + + get topicTitle() { + return this.args.titlePath + ? this.args.post[this.args.titlePath] + : this.args.post.title; + } + get titleAriaLabel() { - return ( - this.args.titleAriaLabel || - i18n("post_list.aria_post_number", { - title: this.args.post.title, + if (this.args.titleAriaLabel) { + return this.args.titleAriaLabel; + } + + if (this.args.post.post_number && this.topicTitle) { + return i18n("post_list.aria_post_number", { + title: this.topicTitle, postNumber: this.args.post.post_number, - }) - ); + }); + } } get posterName() { - if (prioritizeNameInUx(this.args.post.user.name)) { - return this.args.post.user.name; + if (prioritizeNameInUx(this.args.user.name)) { + return this.args.user.name; } - return this.args.post.user.username; + return this.args.user.username; } ); 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,