diff --git a/.discourse-compatibility b/.discourse-compatibility index 2f02d14..60c44c7 100644 --- a/.discourse-compatibility +++ b/.discourse-compatibility @@ -1,3 +1,4 @@ +< 3.5.0.beta8-dev: 52682ad08a831c7e1a1e67ce47e20796b3fa9df0 < 3.5.0.beta5-dev: be64a5ea30dcda658a74e22a9e7b5fd8cd7632c8 < 3.5.0.beta1-dev: b7181ad63238adf843d27b2d0db13cb6354df379 < 3.4.0.beta2-dev: ff810c65d88e3a208b1126e94ec9ba637d6e997e diff --git a/assets/javascripts/discourse/components/post-metadata-user-notes.gjs b/assets/javascripts/discourse/components/post-metadata-user-notes.gjs new file mode 100644 index 0000000..bb3a6a4 --- /dev/null +++ b/assets/javascripts/discourse/components/post-metadata-user-notes.gjs @@ -0,0 +1,35 @@ +import Component from "@glimmer/component"; +import { on } from "@ember/modifier"; +import { action } from "@ember/object"; +import { service } from "@ember/service"; +import icon from "discourse/helpers/d-icon"; +import emoji from "discourse/helpers/emoji"; +import { showUserNotes, updatePostUserNotesCount } from "../lib/user-notes"; + +export default class PostMetadataUserNotes extends Component { + @service siteSettings; + @service store; + + @action + showNotes() { + showUserNotes( + this.store, + this.args.post.user_id, + (count) => updatePostUserNotesCount(this.args.post, count), + { + postId: this.args.post.id, + } + ); + } + + +} diff --git a/assets/javascripts/discourse/initializers/enable-user-notes.gjs b/assets/javascripts/discourse/initializers/enable-user-notes.gjs new file mode 100644 index 0000000..94ee7d3 --- /dev/null +++ b/assets/javascripts/discourse/initializers/enable-user-notes.gjs @@ -0,0 +1,194 @@ +import Component from "@glimmer/component"; +import { withSilencedDeprecations } from "discourse/lib/deprecated"; +import { iconNode } from "discourse/lib/icon-library"; +import { withPluginApi } from "discourse/lib/plugin-api"; +import { applyValueTransformer } from "discourse/lib/transformer"; +import PostMetadataUserNotes from "../components/post-metadata-user-notes"; +import { showUserNotes, updatePostUserNotesCount } from "../lib/user-notes"; + +/** + * Plugin initializer for enabling user notes functionality + */ +export default { + name: "enable-user-notes", + initialize(container) { + const siteSettings = container.lookup("service:site-settings"); + const currentUser = container.lookup("service:current-user"); + + if (!siteSettings.user_notes_enabled || !currentUser?.staff) { + return; + } + + withPluginApi((api) => { + customizePost(api, container); + customizePostMenu(api, container); + }); + }, +}; + +/** + * Customizes how user notes are displayed in posts + * + * @param {Object} api - Plugin API instance + * @param {Object} container - Container instance + */ +function customizePost(api, container) { + const siteSettings = container.lookup("service:site-settings"); + + const placement = applyValueTransformer( + "user-notes-icon-placement", + siteSettings.user_notes_icon_placement + ); + + // Component to display user notes flair icon + class UserNotesPostMetadataFlairIcon extends Component { + static shouldRender(args) { + return args.post?.user_custom_fields?.user_notes_count > 0; + } + + + } + + // Handle placement next to avatar + if (placement === "avatar") { + api.renderAfterWrapperOutlet( + "poster-avatar", + UserNotesPostMetadataFlairIcon + ); + } + // Handle placement next to username + else if (placement === "name") { + // Mobile-specific version + class MobileUserNotesIcon extends UserNotesPostMetadataFlairIcon { + static shouldRender(args, context) { + return context.site.mobileView && super.shouldRender(args); + } + } + + // Desktop-specific version + class DesktopUserNotesIcon extends UserNotesPostMetadataFlairIcon { + static shouldRender(args, context) { + return !context.site.mobileView && super.shouldRender(args); + } + } + + api.renderBeforeWrapperOutlet( + "post-meta-data-poster-name", + MobileUserNotesIcon + ); + api.renderAfterWrapperOutlet( + "post-meta-data-poster-name", + DesktopUserNotesIcon + ); + } + + withSilencedDeprecations("discourse.post-stream-widget-overrides", () => + customizeWidgetPost(api) + ); +} + +/** + * Customizes the post widget to display user notes + * + * @param {Object} api - Plugin API instance + */ +function customizeWidgetPost(api) { + // Handler for showing user notes modal + function widgetShowUserNotes() { + showUserNotes( + this.store, + this.attrs.user_id, + (count) => { + this.sendWidgetAction("refreshUserNotes", count); + }, + { + postId: this.attrs.id, + } + ); + } + + // Update post when notes are changed + api.attachWidgetAction("post", "refreshUserNotes", function (count) { + updatePostUserNotesCount(this.model, count); + }); + + const mobileView = api.container.lookup("service:site").mobileView; + const loc = mobileView ? "before" : "after"; + + // Helper to attach notes icon if user has notes + const attachUserNotesIconIfPresent = (dec) => { + const post = dec.getModel(); + if (post?.user_custom_fields?.user_notes_count > 0) { + return dec.attach("user-notes-icon"); + } + }; + + // Add notes icon to poster name + api.decorateWidget(`poster-name:${loc}`, (dec) => { + if (dec.widget.settings.hideNotes) { + return; + } + + return attachUserNotesIconIfPresent(dec); + }); + + // Add notes icon after avatar + api.decorateWidget(`post-avatar:after`, (dec) => { + if (!dec.widget.settings.showNotes) { + return; + } + + return attachUserNotesIconIfPresent(dec); + }); + + api.attachWidgetAction("post", "showUserNotes", widgetShowUserNotes); + + // Create the user notes icon widget + api.createWidget("user-notes-icon", { + services: ["site-settings"], + + tagName: "span.user-notes-icon", + click: widgetShowUserNotes, + + html() { + if (this.siteSettings.enable_emoji) { + return this.attach("emoji", { name: "memo" }); + } else { + return iconNode("pen-to-square"); + } + }, + }); +} + +/** + * Adds user notes button to post admin menu + * + * @param {Object} api - Plugin API instance + * @param {Object} container - Container instance + */ +function customizePostMenu(api, container) { + const appEvents = container.lookup("service:app-events"); + const store = container.lookup("service:store"); + + api.addPostAdminMenuButton((attrs) => { + return { + icon: "pen-to-square", + label: "user_notes.attach", + action: (post) => { + showUserNotes( + store, + attrs.user_id, + (count) => { + updatePostUserNotesCount(post, count); + appEvents.trigger("post-stream:refresh", { + id: post.id, + }); + }, + { postId: attrs.id } + ); + }, + secondaryAction: "closeAdminMenu", + className: "add-user-note", + }; + }); +} diff --git a/assets/javascripts/discourse/initializers/enable-user-notes.js b/assets/javascripts/discourse/initializers/enable-user-notes.js deleted file mode 100644 index b23ad64..0000000 --- a/assets/javascripts/discourse/initializers/enable-user-notes.js +++ /dev/null @@ -1,114 +0,0 @@ -import { iconNode } from "discourse/lib/icon-library"; -import { withPluginApi } from "discourse/lib/plugin-api"; -import { showUserNotes } from "../lib/user-notes"; - -export default { - name: "enable-user-notes", - initialize(container) { - const siteSettings = container.lookup("service:site-settings"); - const currentUser = container.lookup("service:current-user"); - const appEvents = container.lookup("service:app-events"); - - if (!siteSettings.user_notes_enabled || !currentUser?.staff) { - return; - } - - const store = container.lookup("service:store"); - - withPluginApi("0.8.15", (api) => { - function widgetShowUserNotes() { - showUserNotes( - this.store, - this.attrs.user_id, - (count) => { - this.sendWidgetAction("refreshUserNotes", count); - }, - { - postId: this.attrs.id, - } - ); - } - - api.attachWidgetAction("post", "refreshUserNotes", function (count) { - const cfs = this.model.user_custom_fields || {}; - cfs.user_notes_count = count; - this.model.set("user_custom_fields", cfs); - }); - - const mobileView = api.container.lookup("service:site").mobileView; - const loc = mobileView ? "before" : "after"; - api.decorateWidget(`poster-name:${loc}`, (dec) => { - if (dec.widget.settings.hideNotes) { - return; - } - - const post = dec.getModel(); - if (!post) { - return; - } - - const ucf = post.user_custom_fields || {}; - if (ucf.user_notes_count > 0) { - return dec.attach("user-notes-icon"); - } - }); - - api.decorateWidget(`post-avatar:after`, (dec) => { - if (!dec.widget.settings.showNotes) { - return; - } - - const post = dec.getModel(); - if (!post) { - return; - } - - const ucf = post.user_custom_fields || {}; - if (ucf.user_notes_count > 0) { - return dec.attach("user-notes-icon"); - } - }); - api.addPostAdminMenuButton((attrs) => { - return { - icon: "pen-to-square", - label: "user_notes.attach", - action: (post) => { - showUserNotes( - store, - attrs.user_id, - (count) => { - const ucf = post.user_custom_fields || {}; - ucf.user_notes_count = count; - post.set("user_custom_fields", ucf); - - appEvents.trigger("post-stream:refresh", { - id: post.id, - }); - }, - { postId: attrs.id } - ); - }, - secondaryAction: "closeAdminMenu", - className: "add-user-note", - }; - }); - - api.attachWidgetAction("post", "showUserNotes", widgetShowUserNotes); - - api.createWidget("user-notes-icon", { - services: ["site-settings"], - - tagName: "span.user-notes-icon", - click: widgetShowUserNotes, - - html() { - if (this.siteSettings.enable_emoji) { - return this.attach("emoji", { name: "memo" }); - } else { - return iconNode("pen-to-square"); - } - }, - }); - }); - }, -}; diff --git a/assets/javascripts/discourse/lib/user-notes.js b/assets/javascripts/discourse/lib/user-notes.js index 05eaf53..b20de37 100644 --- a/assets/javascripts/discourse/lib/user-notes.js +++ b/assets/javascripts/discourse/lib/user-notes.js @@ -16,3 +16,9 @@ export function showUserNotes(store, userId, callback, opts) { }); }); } + +export function updatePostUserNotesCount(post, count) { + const cfs = post.user_custom_fields || {}; + cfs.user_notes_count = count; + post.user_custom_fields = cfs; +} diff --git a/assets/javascripts/discourse/pre-initializers/user-notes-transformers.js b/assets/javascripts/discourse/pre-initializers/user-notes-transformers.js new file mode 100644 index 0000000..9bb6c72 --- /dev/null +++ b/assets/javascripts/discourse/pre-initializers/user-notes-transformers.js @@ -0,0 +1,11 @@ +import { withPluginApi } from "discourse/lib/plugin-api"; + +export default { + before: "freeze-valid-transformers", + + initialize() { + withPluginApi((api) => { + api.addValueTransformerName("user-notes-icon-placement"); + }); + }, +}; diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index 4aa464b..96d2187 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -1,6 +1,7 @@ en: site_settings: user_notes_enabled: "Allow staff users to attach notes to users" + user_notes_icon_placement: "Placement of the user notes indicative icon in the posts" user_notes_moderators_delete: "Allow moderators to delete user notes" user_notes: diff --git a/config/settings.yml b/config/settings.yml index 5f6a42a..d2beb64 100644 --- a/config/settings.yml +++ b/config/settings.yml @@ -2,6 +2,13 @@ plugins: user_notes_enabled: default: false client: true + user_notes_icon_placement: + client: true + type: enum + default: "name" + choices: + - name + - avatar user_notes_moderators_delete: default: true client: false