diff --git a/assets/javascripts/discourse/components/assigned-to-first-post.gjs b/assets/javascripts/discourse/components/assigned-to-first-post.gjs new file mode 100644 index 00000000..d1a292d7 --- /dev/null +++ b/assets/javascripts/discourse/components/assigned-to-first-post.gjs @@ -0,0 +1,95 @@ +import Component from "@glimmer/component"; +import { concat } from "@ember/helper"; +import icon from "discourse/helpers/d-icon"; +import userPrioritizedName from "discourse/helpers/user-prioritized-name"; +import { i18n } from "discourse-i18n"; +import { assignedToGroupPath, assignedToUserPath } from "../lib/url"; + +export default class AssignedToFirstPost extends Component { + get assignedToUser() { + return this.args.post?.topic?.assigned_to_user; + } + + get assignedToGroup() { + return this.args.post?.topic?.assigned_to_group; + } + + get icon() { + return this.assignedToUser ? "user-plus" : "group-plus"; + } + + get indirectlyAssignedTo() { + return this.args.post?.topic?.indirectly_assigned_to; + } + + get indirectAssignments() { + if (!this.indirectlyAssignedTo) { + return null; + } + + return Object.keys(this.indirectlyAssignedTo).map((postId) => { + const postNumber = this.indirectlyAssignedTo[postId].post_number; + + return { + postId, + assignee: this.indirectlyAssignedTo[postId].assigned_to, + postNumber, + url: `${this.args.post.topic.url}/${postNumber}`, + }; + }); + } + + get isAssigned() { + return !!( + this.assignedToUser || + this.assignedToGroup || + this.args.post?.topic?.indirectly_assigned_to + ); + } + + +} diff --git a/assets/javascripts/discourse/components/assigned-to-post.gjs b/assets/javascripts/discourse/components/assigned-to-post.gjs index 65d143b6..80800a6b 100644 --- a/assets/javascripts/discourse/components/assigned-to-post.gjs +++ b/assets/javascripts/discourse/components/assigned-to-post.gjs @@ -4,6 +4,7 @@ import { service } from "@ember/service"; import DButton from "discourse/components/d-button"; import DropdownMenu from "discourse/components/dropdown-menu"; import icon from "discourse/helpers/d-icon"; +import userPrioritizedName from "discourse/helpers/user-prioritized-name"; import { i18n } from "discourse-i18n"; import DMenu from "float-kit/components/d-menu"; @@ -11,14 +12,6 @@ export default class AssignedToPost extends Component { @service taskActions; @service siteSettings; - get nameOrUsername() { - if (!this.siteSettings.prioritize_username_in_ux) { - return this.args.assignedToUser.name || this.args.assignedToUser.username; - } else { - return this.args.assignedToUser.username; - } - } - @action unassign() { this.taskActions.unassignPost(this.args.post); @@ -42,7 +35,7 @@ export default class AssignedToPost extends Component { {{#if @assignedToUser}} - {{this.nameOrUsername}} + {{userPrioritizedName @assignedToUser}} {{else}} {{@assignedToGroup.name}} {{/if}} diff --git a/assets/javascripts/discourse/components/post-assignments-display.gjs b/assets/javascripts/discourse/components/post-assignments-display.gjs new file mode 100644 index 00000000..c078c890 --- /dev/null +++ b/assets/javascripts/discourse/components/post-assignments-display.gjs @@ -0,0 +1,49 @@ +import Component from "@glimmer/component"; +import { assignedToGroupPath, assignedToUserPath } from "../lib/url"; +import AssignedFirstPost from "./assigned-to-first-post"; +import AssignedToPost from "./assigned-to-post"; + +export default class PostAssignmentsDisplay extends Component { + static shouldRender(args) { + return args.post; + } + + get post() { + return this.args.outletArgs.post; + } + + get assignedTo() { + return this.post.topic?.indirectly_assigned_to?.[this.post.id]?.assigned_to; + } + + get assignedToUser() { + return this.assignedTo.username ? this.assignedTo : null; + } + + get assignedToGroup() { + return !this.assignedToUser && this.assignedTo.name + ? this.assignedTo + : null; + } + + get assignedHref() { + return this.assignedToUser + ? assignedToUserPath(this.assignedToUser) + : assignedToGroupPath(this.assignedToGroup); + } + + +} diff --git a/assets/javascripts/discourse/initializers/extend-for-assigns.js b/assets/javascripts/discourse/initializers/extend-for-assigns.js index 50306542..8ba6b038 100644 --- a/assets/javascripts/discourse/initializers/extend-for-assigns.js +++ b/assets/javascripts/discourse/initializers/extend-for-assigns.js @@ -22,7 +22,9 @@ import AssignButton, { } from "../components/assign-button"; import BulkActionsAssignUser from "../components/bulk-actions/bulk-assign-user"; import EditTopicAssignments from "../components/modal/edit-topic-assignments"; +import PostAssignmentsDisplay from "../components/post-assignments-display"; import TopicLevelAssignMenu from "../components/topic-level-assign-menu"; +import { assignedToGroupPath, assignedToUserPath } from "../lib/url"; import { extendTopicModel } from "../models/topic"; const DEPENDENT_KEYS = [ @@ -326,7 +328,10 @@ function initialize(api) { } api.addPostSmallActionClassesCallback((post) => { - if (post.actionCode.includes("assigned") && !siteSettings.assigns_public) { + // TODO (glimmer-post-stream): only check for .action_code once the widget code is removed + const actionCode = post.action_code || post.actionCode; + + if (actionCode.includes("assigned") && !siteSettings.assigns_public) { return ["private-assign"]; } }); @@ -348,19 +353,6 @@ function initialize(api) { : {} ); - function assignedToUserPath(assignedToUser) { - return getURL( - siteSettings.assigns_user_url_path.replace( - "{username}", - assignedToUser.username - ) - ); - } - - function assignedToGroupPath(assignedToGroup) { - return getURL(`/g/${assignedToGroup.name}/assigned/everyone`); - } - api.modifyClass( "model:bookmark", (Superclass) => @@ -407,29 +399,6 @@ function initialize(api) { api.addPostSmallActionIcon("reassigned", "user-plus"); api.addPostSmallActionIcon("reassigned_group", "group-plus"); - api.addPostTransformCallback((transformed) => { - if ( - [ - "assigned", - "unassigned", - "reassigned", - "assigned_group", - "unassigned_group", - "reassigned_group", - "assigned_to_post", - "assigned_group_to_post", - "unassigned_from_post", - "unassigned_group_from_post", - "details_change", - "note_change", - "status_change", - ].includes(transformed.actionCode) - ) { - transformed.isSmallAction = true; - transformed.canEdit = true; - } - }); - api.addDiscoveryQueryParam("assigned", { replace: true, refreshModel: true }); api.addTagsHtmlCallback((topic, params = {}) => { @@ -521,119 +490,6 @@ function initialize(api) { return result; }); - api.createWidget("assigned-to-post", { - html(attrs) { - return new RenderGlimmer( - this, - "p.assigned-to", - hbs` - `, - { - assignedToUser: attrs.post.assigned_to_user, - assignedToGroup: attrs.post.assigned_to_group, - href: attrs.href, - post: attrs.post, - } - ); - }, - }); - - api.createWidget("assigned-to-first-post", { - html(attrs) { - const topic = attrs.topic; - const [assignedToUser, assignedToGroup, indirectlyAssignedTo] = [ - topic.assigned_to_user, - topic.assigned_to_group, - topic.indirectly_assigned_to, - ]; - const assigneeElements = []; - - const assignedHtml = (username, path, type) => { - return `${htmlSafe( - i18n("discourse_assign.assigned_topic_to", { - username, - path, - }) - )}`; - }; - - let displayedName = ""; - if (assignedToUser) { - displayedName = !this.siteSettings.prioritize_username_in_ux - ? assignedToUser.name || assignedToUser.username - : assignedToUser.username; - - assigneeElements.push( - h( - "span.assignee", - new RawHtml({ - html: assignedHtml( - displayedName, - assignedToUserPath(assignedToUser), - "user" - ), - }) - ) - ); - } - if (assignedToGroup) { - assigneeElements.push( - h( - "span.assignee", - new RawHtml({ - html: assignedHtml( - assignedToGroup.name, - assignedToGroupPath(assignedToGroup), - "group" - ), - }) - ) - ); - } - - if (indirectlyAssignedTo) { - Object.keys(indirectlyAssignedTo).map((postId) => { - const assignee = indirectlyAssignedTo[postId].assigned_to; - const postNumber = indirectlyAssignedTo[postId].post_number; - - displayedName = - !this.siteSettings.prioritize_username_in_ux || !assignee.username - ? assignee.name || assignee.username - : assignee.username; - - assigneeElements.push( - h("span.assignee", [ - h( - "a", - { - attributes: { - class: "assigned-indirectly", - href: `${topic.url}/${postNumber}`, - }, - }, - i18n("discourse_assign.assign_post_to_multiple", { - post_number: postNumber, - username: displayedName, - }) - ), - ]) - ); - }); - } - - if (!isEmpty(assigneeElements)) { - return h("p.assigned-to", [ - assignedToUser ? iconNode("user-plus") : iconNode("group-plus"), - assignedToUser || assignedToGroup - ? "" - : h("span.assign-text", i18n("discourse_assign.assigned")), - assigneeElements, - ]); - } - }, - }); - api.modifyClass( "model:group", (Superclass) => @@ -720,6 +576,42 @@ function initialize(api) { } ); + customizePost(api); + + api.replaceIcon("notification.assigned", "user-plus"); + + api.replaceIcon( + "notification.discourse_assign.assign_group_notification", + "group-plus" + ); + + api.modifyClass( + "controller:preferences/notifications", + (Superclass) => + class extends Superclass { + @action + save() { + this.saveAttrNames.push("custom_fields"); + super.save(...arguments); + } + } + ); + + api.addKeyboardShortcut("g a", "", { path: "/my/activity/assigned" }); +} + +function customizePost(api) { + api.renderAfterWrapperOutlet( + "post-content-cooked-html", + PostAssignmentsDisplay + ); + + withSilencedDeprecations("discourse.post-stream-widget-overrides", () => + customizeWidgetPost(api) + ); +} + +function customizeWidgetPost(api) { api.decorateWidget("post-contents:after-cooked", (dec) => { const postModel = dec.getModel(); if (postModel) { @@ -755,26 +647,145 @@ function initialize(api) { } }); - api.replaceIcon("notification.assigned", "user-plus"); + api.createWidget("assigned-to-post", { + html(attrs) { + return new RenderGlimmer( + this, + "p.assigned-to", + hbs` + `, + { + assignedToUser: attrs.post.assigned_to_user, + assignedToGroup: attrs.post.assigned_to_group, + href: attrs.href, + post: attrs.post, + } + ); + }, + }); - api.replaceIcon( - "notification.discourse_assign.assign_group_notification", - "group-plus" - ); + api.createWidget("assigned-to-first-post", { + html(attrs) { + const topic = attrs.topic; + const [assignedToUser, assignedToGroup, indirectlyAssignedTo] = [ + topic.assigned_to_user, + topic.assigned_to_group, + topic.indirectly_assigned_to, + ]; + const assigneeElements = []; - api.modifyClass( - "controller:preferences/notifications", - (Superclass) => - class extends Superclass { - @action - save() { - this.saveAttrNames.push("custom_fields"); - super.save(...arguments); - } + const assignedHtml = (username, path, type) => { + return `${htmlSafe( + i18n("discourse_assign.assigned_topic_to", { + username, + path, + }) + )}`; + }; + + let displayedName = ""; + if (assignedToUser) { + displayedName = !this.siteSettings.prioritize_username_in_ux + ? assignedToUser.name || assignedToUser.username + : assignedToUser.username; + + assigneeElements.push( + h( + "span.assignee", + new RawHtml({ + html: assignedHtml( + displayedName, + assignedToUserPath(assignedToUser), + "user" + ), + }) + ) + ); } - ); - api.addKeyboardShortcut("g a", "", { path: "/my/activity/assigned" }); + if (assignedToGroup) { + assigneeElements.push( + h( + "span.assignee", + new RawHtml({ + html: assignedHtml( + assignedToGroup.name, + assignedToGroupPath(assignedToGroup), + "group" + ), + }) + ) + ); + } + + if (indirectlyAssignedTo) { + Object.keys(indirectlyAssignedTo).map((postId) => { + const assignee = indirectlyAssignedTo[postId].assigned_to; + const postNumber = indirectlyAssignedTo[postId].post_number; + + displayedName = + !this.siteSettings.prioritize_username_in_ux || !assignee.username + ? assignee.name || assignee.username + : assignee.username; + + assigneeElements.push( + h("span.assignee", [ + h( + "a", + { + attributes: { + class: "assigned-indirectly", + href: `${topic.url}/${postNumber}`, + }, + }, + i18n("discourse_assign.assign_post_to_multiple", { + post_number: postNumber, + username: displayedName, + }) + ), + ]) + ); + }); + } + + if (!isEmpty(assigneeElements)) { + return h("p.assigned-to", [ + assignedToUser ? iconNode("user-plus") : iconNode("group-plus"), + assignedToUser || assignedToGroup + ? "" + : h("span.assign-text", i18n("discourse_assign.assigned")), + assigneeElements, + ]); + } + }, + }); + + // This won't have a direct translation in the Glimmer API as it uses can_edit from the model + // TODO (glimmer-post-stream): check the post small action component and introduce a transformer to override the + // canEdit behavior there + api.addPostTransformCallback((transformed) => { + if ( + [ + "assigned", + "unassigned", + "reassigned", + "assigned_group", + "unassigned_group", + "reassigned_group", + "assigned_to_post", + "assigned_group_to_post", + "unassigned_from_post", + "unassigned_group_from_post", + "details_change", + "note_change", + "status_change", + ].includes(transformed.actionCode) + ) { + transformed.isSmallAction = true; + transformed.canEdit = true; + } + }); } function customizePostMenu(api) { @@ -808,7 +819,10 @@ function customizePostMenu(api) { const silencedKey = transformerRegistered && "discourse.post-menu-widget-overrides"; - withSilencedDeprecations(silencedKey, () => customizeWidgetPostMenu(api)); + withSilencedDeprecations( + ["discourse.post-stream-widget-overrides", silencedKey], + () => customizeWidgetPostMenu(api) + ); } function customizeWidgetPostMenu(api) { diff --git a/assets/javascripts/discourse/lib/url.js b/assets/javascripts/discourse/lib/url.js new file mode 100644 index 00000000..208cecf0 --- /dev/null +++ b/assets/javascripts/discourse/lib/url.js @@ -0,0 +1,19 @@ +import { getOwnerWithFallback } from "discourse/lib/get-owner"; +import getURL from "discourse/lib/get-url"; + +export function assignedToUserPath(assignedToUser) { + const siteSettings = getOwnerWithFallback(this).lookup( + "service:site-settings" + ); + + return getURL( + siteSettings.assigns_user_url_path.replace( + "{username}", + assignedToUser.username + ) + ); +} + +export function assignedToGroupPath(assignedToGroup) { + return getURL(`/g/${assignedToGroup.name}/assigned/everyone`); +}