DEV: Add compatibility with the Glimmer Post Stream

This commit is contained in:
Sérgio Saquetim 2025-03-28 19:31:33 -03:00
parent bf52519dc7
commit 1b8b2a3a32
No known key found for this signature in database
GPG Key ID: B4E3D7F11E793062
5 changed files with 345 additions and 64 deletions

View File

@ -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
);
}
<template>
{{#if this.isAssigned}}
<p class="assigned-to">
{{icon this.icon}}
{{#if this.assignedToUser}}
<span class="assignee">
<span class="assigned-to--user">
{{i18n
"discourse_assign.assigned_topic_to"
username=(userPrioritizedName this.assignedToUser)
path=(assignedToUserPath this.assignedToUser)
}}
</span>
</span>
{{/if}}
{{#if this.assignedToGroup}}
<span class="assignee">
<span class="assigned-to--group">
{{i18n
"discourse_assign.assigned_topic_to"
username=this.assignedToGroup.name
path=(assignedToGroupPath this.assignedToGroup)
}}
</span>
</span>
{{/if}}
{{#each this.indirectAssignments key="postId" as |indirectAssignment|}}
<span class="assign-text">
{{i18n "discourse_assign.assigned"}}
</span>
<span class="assignee">
<a href={{indirectAssignment.url}} class="assigned-indirectly">
{{i18n
"discourse_assign.assign_post_to_multiple"
post_number=indirectAssignment.postNumber
username=(userPrioritizedName indirectAssignment.assignee)
}}
</a>
</span>
{{/each}}
</p>
{{/if}}
</template>
}

View File

@ -4,6 +4,7 @@ import { service } from "@ember/service";
import DButton from "discourse/components/d-button"; import DButton from "discourse/components/d-button";
import DropdownMenu from "discourse/components/dropdown-menu"; import DropdownMenu from "discourse/components/dropdown-menu";
import icon from "discourse/helpers/d-icon"; import icon from "discourse/helpers/d-icon";
import userPrioritizedName from "discourse/helpers/user-prioritized-name";
import { i18n } from "discourse-i18n"; import { i18n } from "discourse-i18n";
import DMenu from "float-kit/components/d-menu"; import DMenu from "float-kit/components/d-menu";
@ -11,14 +12,6 @@ export default class AssignedToPost extends Component {
@service taskActions; @service taskActions;
@service siteSettings; @service siteSettings;
get nameOrUsername() {
if (this.siteSettings.prioritize_full_name_in_ux) {
return this.args.assignedToUser.name || this.args.assignedToUser.username;
} else {
return this.args.assignedToUser.username;
}
}
@action @action
unassign() { unassign() {
this.taskActions.unassignPost(this.args.post); this.taskActions.unassignPost(this.args.post);
@ -42,7 +35,7 @@ export default class AssignedToPost extends Component {
<a href={{@href}} class="assigned-to-username"> <a href={{@href}} class="assigned-to-username">
{{#if @assignedToUser}} {{#if @assignedToUser}}
{{this.nameOrUsername}} {{userPrioritizedName @assignedToUser}}
{{else}} {{else}}
{{@assignedToGroup.name}} {{@assignedToGroup.name}}
{{/if}} {{/if}}

View File

@ -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);
}
<template>
{{#if this.post.firstPost}}
<AssignedFirstPost @post={{this.post}} />
{{else if this.assignedTo}}
<p class="assigned-to">
<AssignedToPost
@assignedToUser={{this.assignedToUser}}
@assignedToGroup={{this.assignedToGroup}}
@href={{this.assignedHref}}
@post={{this.post}}
/>
</p>
{{/if}}
</template>
}

View File

@ -6,6 +6,7 @@ import { hbs } from "ember-cli-htmlbars";
import { h } from "virtual-dom"; import { h } from "virtual-dom";
import { renderAvatar } from "discourse/helpers/user-avatar"; import { renderAvatar } from "discourse/helpers/user-avatar";
import discourseComputed from "discourse/lib/decorators"; import discourseComputed from "discourse/lib/decorators";
import { withSilencedDeprecations } from "discourse/lib/deprecated";
import getURL from "discourse/lib/get-url"; import getURL from "discourse/lib/get-url";
import { iconHTML, iconNode } from "discourse/lib/icon-library"; import { iconHTML, iconNode } from "discourse/lib/icon-library";
import { withPluginApi } from "discourse/lib/plugin-api"; import { withPluginApi } from "discourse/lib/plugin-api";
@ -18,7 +19,9 @@ import { i18n } from "discourse-i18n";
import AssignButton from "../components/assign-button"; import AssignButton from "../components/assign-button";
import BulkActionsAssignUser from "../components/bulk-actions/bulk-assign-user"; import BulkActionsAssignUser from "../components/bulk-actions/bulk-assign-user";
import EditTopicAssignments from "../components/modal/edit-topic-assignments"; import EditTopicAssignments from "../components/modal/edit-topic-assignments";
import PostAssignmentsDisplay from "../components/post-assignments-display";
import TopicLevelAssignMenu from "../components/topic-level-assign-menu"; import TopicLevelAssignMenu from "../components/topic-level-assign-menu";
import { assignedToGroupPath, assignedToUserPath } from "../lib/url";
import { extendTopicModel } from "../models/topic"; import { extendTopicModel } from "../models/topic";
const DEPENDENT_KEYS = [ const DEPENDENT_KEYS = [
@ -322,7 +325,10 @@ function initialize(api) {
} }
api.addPostSmallActionClassesCallback((post) => { 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"]; return ["private-assign"];
} }
}); });
@ -344,19 +350,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( api.modifyClass(
"model:bookmark", "model:bookmark",
(Superclass) => (Superclass) =>
@ -403,29 +396,6 @@ function initialize(api) {
api.addPostSmallActionIcon("reassigned", "user-plus"); api.addPostSmallActionIcon("reassigned", "user-plus");
api.addPostSmallActionIcon("reassigned_group", "group-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.addDiscoveryQueryParam("assigned", { replace: true, refreshModel: true });
api.addTagsHtmlCallback((topic, params = {}) => { api.addTagsHtmlCallback((topic, params = {}) => {
@ -717,6 +687,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) => { api.decorateWidget("post-contents:after-cooked", (dec) => {
const postModel = dec.getModel(); const postModel = dec.getModel();
if (postModel) { if (postModel) {
@ -752,26 +758,145 @@ function initialize(api) {
} }
}); });
api.replaceIcon("notification.assigned", "user-plus"); api.createWidget("assigned-to-post", {
html(attrs) {
api.replaceIcon( return new RenderGlimmer(
"notification.discourse_assign.assign_group_notification", this,
"group-plus" "p.assigned-to",
); hbs`
<AssignedToPost @assignedToUser={{@data.assignedToUser}} @assignedToGroup={{@data.assignedToGroup}}
api.modifyClass( @href={{@data.href}} @post={{@data.post}} />`,
"controller:preferences/notifications", {
(Superclass) => assignedToUser: attrs.post.assigned_to_user,
class extends Superclass { assignedToGroup: attrs.post.assigned_to_group,
@action href: attrs.href,
save() { post: attrs.post,
this.saveAttrNames.push("custom_fields");
super.save(...arguments);
} }
} );
); },
});
api.addKeyboardShortcut("g a", "", { path: "/my/activity/assigned" }); 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 `<span class="assigned-to--${type}">${htmlSafe(
i18n("discourse_assign.assigned_topic_to", {
username,
path,
})
)}</span>`;
};
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,
]);
}
},
});
// 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) { function customizePostMenu(api) {

View File

@ -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`);
}