Skip to content
This repository was archived by the owner on Jul 14, 2025. It is now read-only.

Commit a685386

Browse files
authored
DEV: Add compatibility with the Glimmer Post Stream (#127)
This commit updates the discourse-user-notes plugin to ensure compatibility with the new Glimmer-based Post Stream in Discourse. The changes include: - Refactoring user note components and interactions to work seamlessly with Glimmer rendering. - Resolving any issues arising from differences between legacy and Glimmer Post Stream APIs or lifecycle hooks. - Ensuring user notes display and functionality remain intact when the Glimmer Post Stream is active.
1 parent 52682ad commit a685386

File tree

8 files changed

+255
-114
lines changed

8 files changed

+255
-114
lines changed

.discourse-compatibility

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
< 3.5.0.beta8-dev: 52682ad08a831c7e1a1e67ce47e20796b3fa9df0
12
< 3.5.0.beta5-dev: be64a5ea30dcda658a74e22a9e7b5fd8cd7632c8
23
< 3.5.0.beta1-dev: b7181ad63238adf843d27b2d0db13cb6354df379
34
< 3.4.0.beta2-dev: ff810c65d88e3a208b1126e94ec9ba637d6e997e
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import Component from "@glimmer/component";
2+
import { on } from "@ember/modifier";
3+
import { action } from "@ember/object";
4+
import { service } from "@ember/service";
5+
import icon from "discourse/helpers/d-icon";
6+
import emoji from "discourse/helpers/emoji";
7+
import { showUserNotes, updatePostUserNotesCount } from "../lib/user-notes";
8+
9+
export default class PostMetadataUserNotes extends Component {
10+
@service siteSettings;
11+
@service store;
12+
13+
@action
14+
showNotes() {
15+
showUserNotes(
16+
this.store,
17+
this.args.post.user_id,
18+
(count) => updatePostUserNotesCount(this.args.post, count),
19+
{
20+
postId: this.args.post.id,
21+
}
22+
);
23+
}
24+
25+
<template>
26+
{{! template-lint-disable no-invalid-interactive }}
27+
<span class="user-notes-icon" {{on "click" this.showNotes}}>
28+
{{#if this.siteSettings.enable_emoji}}
29+
{{emoji "memo"}}
30+
{{else}}
31+
{{icon "pen-to-square"}}
32+
{{/if}}
33+
</span>
34+
</template>
35+
}
Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
import Component from "@glimmer/component";
2+
import { withSilencedDeprecations } from "discourse/lib/deprecated";
3+
import { iconNode } from "discourse/lib/icon-library";
4+
import { withPluginApi } from "discourse/lib/plugin-api";
5+
import { applyValueTransformer } from "discourse/lib/transformer";
6+
import PostMetadataUserNotes from "../components/post-metadata-user-notes";
7+
import { showUserNotes, updatePostUserNotesCount } from "../lib/user-notes";
8+
9+
/**
10+
* Plugin initializer for enabling user notes functionality
11+
*/
12+
export default {
13+
name: "enable-user-notes",
14+
initialize(container) {
15+
const siteSettings = container.lookup("service:site-settings");
16+
const currentUser = container.lookup("service:current-user");
17+
18+
if (!siteSettings.user_notes_enabled || !currentUser?.staff) {
19+
return;
20+
}
21+
22+
withPluginApi((api) => {
23+
customizePost(api, container);
24+
customizePostMenu(api, container);
25+
});
26+
},
27+
};
28+
29+
/**
30+
* Customizes how user notes are displayed in posts
31+
*
32+
* @param {Object} api - Plugin API instance
33+
* @param {Object} container - Container instance
34+
*/
35+
function customizePost(api, container) {
36+
const siteSettings = container.lookup("service:site-settings");
37+
38+
const placement = applyValueTransformer(
39+
"user-notes-icon-placement",
40+
siteSettings.user_notes_icon_placement
41+
);
42+
43+
// Component to display user notes flair icon
44+
class UserNotesPostMetadataFlairIcon extends Component {
45+
static shouldRender(args) {
46+
return args.post?.user_custom_fields?.user_notes_count > 0;
47+
}
48+
49+
<template><PostMetadataUserNotes @post={{@post}} /></template>
50+
}
51+
52+
// Handle placement next to avatar
53+
if (placement === "avatar") {
54+
api.renderAfterWrapperOutlet(
55+
"poster-avatar",
56+
UserNotesPostMetadataFlairIcon
57+
);
58+
}
59+
// Handle placement next to username
60+
else if (placement === "name") {
61+
// Mobile-specific version
62+
class MobileUserNotesIcon extends UserNotesPostMetadataFlairIcon {
63+
static shouldRender(args, context) {
64+
return context.site.mobileView && super.shouldRender(args);
65+
}
66+
}
67+
68+
// Desktop-specific version
69+
class DesktopUserNotesIcon extends UserNotesPostMetadataFlairIcon {
70+
static shouldRender(args, context) {
71+
return !context.site.mobileView && super.shouldRender(args);
72+
}
73+
}
74+
75+
api.renderBeforeWrapperOutlet(
76+
"post-meta-data-poster-name",
77+
MobileUserNotesIcon
78+
);
79+
api.renderAfterWrapperOutlet(
80+
"post-meta-data-poster-name",
81+
DesktopUserNotesIcon
82+
);
83+
}
84+
85+
withSilencedDeprecations("discourse.post-stream-widget-overrides", () =>
86+
customizeWidgetPost(api)
87+
);
88+
}
89+
90+
/**
91+
* Customizes the post widget to display user notes
92+
*
93+
* @param {Object} api - Plugin API instance
94+
*/
95+
function customizeWidgetPost(api) {
96+
// Handler for showing user notes modal
97+
function widgetShowUserNotes() {
98+
showUserNotes(
99+
this.store,
100+
this.attrs.user_id,
101+
(count) => {
102+
this.sendWidgetAction("refreshUserNotes", count);
103+
},
104+
{
105+
postId: this.attrs.id,
106+
}
107+
);
108+
}
109+
110+
// Update post when notes are changed
111+
api.attachWidgetAction("post", "refreshUserNotes", function (count) {
112+
updatePostUserNotesCount(this.model, count);
113+
});
114+
115+
const mobileView = api.container.lookup("service:site").mobileView;
116+
const loc = mobileView ? "before" : "after";
117+
118+
// Helper to attach notes icon if user has notes
119+
const attachUserNotesIconIfPresent = (dec) => {
120+
const post = dec.getModel();
121+
if (post?.user_custom_fields?.user_notes_count > 0) {
122+
return dec.attach("user-notes-icon");
123+
}
124+
};
125+
126+
// Add notes icon to poster name
127+
api.decorateWidget(`poster-name:${loc}`, (dec) => {
128+
if (dec.widget.settings.hideNotes) {
129+
return;
130+
}
131+
132+
return attachUserNotesIconIfPresent(dec);
133+
});
134+
135+
// Add notes icon after avatar
136+
api.decorateWidget(`post-avatar:after`, (dec) => {
137+
if (!dec.widget.settings.showNotes) {
138+
return;
139+
}
140+
141+
return attachUserNotesIconIfPresent(dec);
142+
});
143+
144+
api.attachWidgetAction("post", "showUserNotes", widgetShowUserNotes);
145+
146+
// Create the user notes icon widget
147+
api.createWidget("user-notes-icon", {
148+
services: ["site-settings"],
149+
150+
tagName: "span.user-notes-icon",
151+
click: widgetShowUserNotes,
152+
153+
html() {
154+
if (this.siteSettings.enable_emoji) {
155+
return this.attach("emoji", { name: "memo" });
156+
} else {
157+
return iconNode("pen-to-square");
158+
}
159+
},
160+
});
161+
}
162+
163+
/**
164+
* Adds user notes button to post admin menu
165+
*
166+
* @param {Object} api - Plugin API instance
167+
* @param {Object} container - Container instance
168+
*/
169+
function customizePostMenu(api, container) {
170+
const appEvents = container.lookup("service:app-events");
171+
const store = container.lookup("service:store");
172+
173+
api.addPostAdminMenuButton((attrs) => {
174+
return {
175+
icon: "pen-to-square",
176+
label: "user_notes.attach",
177+
action: (post) => {
178+
showUserNotes(
179+
store,
180+
attrs.user_id,
181+
(count) => {
182+
updatePostUserNotesCount(post, count);
183+
appEvents.trigger("post-stream:refresh", {
184+
id: post.id,
185+
});
186+
},
187+
{ postId: attrs.id }
188+
);
189+
},
190+
secondaryAction: "closeAdminMenu",
191+
className: "add-user-note",
192+
};
193+
});
194+
}

assets/javascripts/discourse/initializers/enable-user-notes.js

Lines changed: 0 additions & 114 deletions
This file was deleted.

assets/javascripts/discourse/lib/user-notes.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,9 @@ export function showUserNotes(store, userId, callback, opts) {
1616
});
1717
});
1818
}
19+
20+
export function updatePostUserNotesCount(post, count) {
21+
const cfs = post.user_custom_fields || {};
22+
cfs.user_notes_count = count;
23+
post.user_custom_fields = cfs;
24+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { withPluginApi } from "discourse/lib/plugin-api";
2+
3+
export default {
4+
before: "freeze-valid-transformers",
5+
6+
initialize() {
7+
withPluginApi((api) => {
8+
api.addValueTransformerName("user-notes-icon-placement");
9+
});
10+
},
11+
};

config/locales/server.en.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
en:
22
site_settings:
33
user_notes_enabled: "Allow staff users to attach notes to users"
4+
user_notes_icon_placement: "Placement of the user notes indicative icon in the posts"
45
user_notes_moderators_delete: "Allow moderators to delete user notes"
56

67
user_notes:

config/settings.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,13 @@ plugins:
22
user_notes_enabled:
33
default: false
44
client: true
5+
user_notes_icon_placement:
6+
client: true
7+
type: enum
8+
default: "name"
9+
choices:
10+
- name
11+
- avatar
512
user_notes_moderators_delete:
613
default: true
714
client: false

0 commit comments

Comments
 (0)