-
Notifications
You must be signed in to change notification settings - Fork 0
FEATURE: per-topic unsubscribe option in emails #2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: email-notifications-enhancement
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| import ObjectController from "discourse/controllers/object"; | ||
|
|
||
| export default ObjectController.extend({ | ||
|
|
||
| stopNotificiationsText: function() { | ||
| return I18n.t("topic.unsubscribe.stop_notifications", { title: this.get("model.fancyTitle") }); | ||
| }.property("model.fancyTitle"), | ||
|
|
||
| }) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| import PostStream from "discourse/models/post-stream"; | ||
|
|
||
| export default Discourse.Route.extend({ | ||
| model(params) { | ||
| const topic = this.store.createRecord("topic", { id: params.id }); | ||
| return PostStream.loadTopicView(params.id).then(json => { | ||
| topic.updateFromJson(json); | ||
| return topic; | ||
| }); | ||
| }, | ||
|
Comment on lines
+4
to
+10
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: # Find and read the file
cat -n app/assets/javascripts/discourse/routes/topic-unsubscribe.js.es6Repository: AI-Code-Review-Evals/coderabbit-discourse Length of output: 817 🏁 Script executed: # Search for other routes using loadTopicView to see error handling patterns
rg "loadTopicView" app/assets/javascripts/discourse/routes/ -A 5 -B 2Repository: AI-Code-Review-Evals/coderabbit-discourse Length of output: 823 🏁 Script executed: # Look at the PostStream model to understand loadTopicView
fd "post-stream" app/assets/javascripts/discourse/models/ --type fRepository: AI-Code-Review-Evals/coderabbit-discourse Length of output: 143 🏁 Script executed: # Find all route files and check error handling patterns
fd "\.es6$" app/assets/javascripts/discourse/routes/ --type f | head -10Repository: AI-Code-Review-Evals/coderabbit-discourse Length of output: 717 🏁 Script executed: # Look at topic route specifically as it's likely related
cat -n app/assets/javascripts/discourse/routes/topic.js.es6 2>/dev/null || echo "File not found"Repository: AI-Code-Review-Evals/coderabbit-discourse Length of output: 8952 🏁 Script executed: # Search for .catch patterns in routes to see error handling conventions
rg "\.catch|error:" app/assets/javascripts/discourse/routes/ -lRepository: AI-Code-Review-Evals/coderabbit-discourse Length of output: 201 🏁 Script executed: # Check the PostStream.loadTopicView implementation
head -100 app/assets/javascripts/discourse/models/post-stream.js.es6Repository: AI-Code-Review-Evals/coderabbit-discourse Length of output: 3272 🏁 Script executed: # Check topic-from-params.js.es6 for error handling patterns
cat -n app/assets/javascripts/discourse/routes/topic-from-params.js.es6Repository: AI-Code-Review-Evals/coderabbit-discourse Length of output: 2634 🏁 Script executed: # Check user.js.es6 for error handling patterns
cat -n app/assets/javascripts/discourse/routes/user.js.es6 | head -150Repository: AI-Code-Review-Evals/coderabbit-discourse Length of output: 3230 🏁 Script executed: # Search for any routes that use PostStream.loadTopicView and check their error handling
rg "PostStream\.loadTopicView" app/assets/javascripts/discourse/ -B 2 -A 10Repository: AI-Code-Review-Evals/coderabbit-discourse Length of output: 2465 Add error handling for The promise returned by Other Discourse routes consistently handle such async operations with Proposed fix model(params) {
const topic = this.store.createRecord("topic", { id: params.id });
return PostStream.loadTopicView(params.id).then(json => {
topic.updateFromJson(json);
return topic;
+ }).catch(() => {
+ this.transitionTo("exception-unknown");
});
},🤖 Prompt for AI Agents |
||
|
|
||
| afterModel(topic) { | ||
| // hide the notification reason text | ||
| topic.set("details.notificationReasonText", null); | ||
| }, | ||
|
|
||
| actions: { | ||
| didTransition() { | ||
| this.controllerFor("application").set("showFooter", true); | ||
| return true; | ||
| } | ||
| } | ||
| }); | ||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,8 @@ | ||||||
| <div class="container"> | ||||||
| <p> | ||||||
| {{{stopNotificiationsText}}} | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Typo in property name and potential XSS concern with triple-brace rendering.
🔒 Recommended fixIf HTML formatting is needed, ensure - {{{stopNotificiationsText}}}
+ {{stopNotificationsText}}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||
| </p> | ||||||
| <p> | ||||||
| {{i18n "topic.unsubscribe.change_notification_state"}} {{topic-notifications-button topic=model}} | ||||||
| </p> | ||||||
| </div> | ||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| export default Discourse.View.extend({ | ||
| classNames: ["topic-unsubscribe"] | ||
| }); | ||
|
Comment on lines
+1
to
+3
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: # Check prevalence of Discourse.View.extend in the codebase
rg -n "Discourse\.View\.extend" --type js | wc -lRepository: AI-Code-Review-Evals/coderabbit-discourse Length of output: 86 🏁 Script executed: # Get a sample of Discourse.View usage with context
rg -n "Discourse\.View\.extend" --type js -A 1 | head -50Repository: AI-Code-Review-Evals/coderabbit-discourse Length of output: 520 🏁 Script executed: # Check if there are any Ember components or other view patterns used
rg -n "Ember\.Component|\.extend\({" --type js | head -30Repository: AI-Code-Review-Evals/coderabbit-discourse Length of output: 204 🏁 Script executed: # Search for Ember component patterns
rg -n "Ember\.Component\.extend" --type js | head -20Repository: AI-Code-Review-Evals/coderabbit-discourse Length of output: 836 🏁 Script executed: # Check the structure of the views directory
find app/assets/javascripts -type d -name "views" 2>/dev/nullRepository: AI-Code-Review-Evals/coderabbit-discourse Length of output: 158 🏁 Script executed: # List files in the discourse views directory
ls -la app/assets/javascripts/discourse/views/ 2>/dev/null || echo "Directory may not exist yet"Repository: AI-Code-Review-Evals/coderabbit-discourse Length of output: 5967 🏁 Script executed: # Check how views are typically structured - look at one of the existing admin views
cat app/assets/javascripts/admin/views/admin_user_view.jsRepository: AI-Code-Review-Evals/coderabbit-discourse Length of output: 291 🏁 Script executed: # Check what pattern other small discourse views use
head -10 app/assets/javascripts/discourse/views/badges-index.js.es6
head -10 app/assets/javascripts/discourse/views/badges-show.js.es6
head -10 app/assets/javascripts/discourse/views/unknown.js.es6
head -10 app/assets/javascripts/discourse/views/button.js.es6Repository: AI-Code-Review-Evals/coderabbit-discourse Length of output: 1173 🏁 Script executed: # Check the file size and pattern of topic-unsubscribe.js.es6
cat app/assets/javascripts/discourse/views/topic-unsubscribe.js.es6Repository: AI-Code-Review-Evals/coderabbit-discourse Length of output: 163 Reconsider if a View class is necessary for only setting classNames. This view extends 🤖 Prompt for AI Agents |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -24,11 +24,12 @@ class TopicsController < ApplicationController | |
| :bulk, | ||
| :reset_new, | ||
| :change_post_owners, | ||
| :bookmark] | ||
| :bookmark, | ||
| :unsubscribe] | ||
|
|
||
| before_filter :consider_user_for_promotion, only: :show | ||
|
|
||
| skip_before_filter :check_xhr, only: [:show, :feed] | ||
| skip_before_filter :check_xhr, only: [:show, :unsubscribe, :feed] | ||
|
|
||
| def id_for_slug | ||
| topic = Topic.find_by(slug: params[:slug].downcase) | ||
|
|
@@ -94,6 +95,26 @@ def show | |
| raise ex | ||
| end | ||
|
|
||
| def unsubscribe | ||
| @topic_view = TopicView.new(params[:topic_id], current_user) | ||
|
|
||
| if slugs_do_not_match || (!request.format.json? && params[:slug].blank?) | ||
| return redirect_to @topic_view.topic.unsubscribe_url, status: 301 | ||
| end | ||
|
|
||
| tu = TopicUser.find_by(user_id: current_user.id, topic_id: params[:topic_id]) | ||
|
|
||
| if tu.notification_level > TopicUser.notification_levels[:regular] | ||
| tu.notification_level = TopicUser.notification_levels[:regular] | ||
| else | ||
| tu.notification_level = TopicUser.notification_levels[:muted] | ||
| end | ||
|
|
||
| tu.save! | ||
|
|
||
| perform_show_response | ||
| end | ||
|
Comment on lines
+98
to
+116
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Potential
Consider using Proposed fix using find_or_initialize_by- tu = TopicUser.find_by(user_id: current_user.id, topic_id: params[:topic_id])
+ tu = TopicUser.find_or_initialize_by(user_id: current_user.id, topic_id: params[:topic_id])
+
+ # For new records, default to regular notification level
+ tu.notification_level ||= TopicUser.notification_levels[:regular]
if tu.notification_level > TopicUser.notification_levels[:regular]
tu.notification_level = TopicUser.notification_levels[:regular]
else
tu.notification_level = TopicUser.notification_levels[:muted]
end
tu.save!🧰 Tools🪛 RuboCop (1.82.1)[convention] 98-116: Assignment Branch Condition size for (Metrics/AbcSize) 🤖 Prompt for AI Agents |
||
|
|
||
| def wordpress | ||
| params.require(:best) | ||
| params.require(:topic_id) | ||
|
|
@@ -476,6 +497,7 @@ def perform_show_response | |
| format.html do | ||
| @description_meta = @topic_view.topic.excerpt | ||
| store_preloaded("topic_#{@topic_view.topic.id}", MultiJson.dump(topic_view_serializer)) | ||
| render :show | ||
| end | ||
|
|
||
| format.json do | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -292,6 +292,7 @@ def send_notification_email(opts) | |||||
| context: context, | ||||||
| username: username, | ||||||
| add_unsubscribe_link: true, | ||||||
| unsubscribe_url: post.topic.unsubscribe_url, | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Potential If the topic has been deleted between the notification being created and the email being sent, Consider adding nil-safe navigation or a fallback: Proposed fix- unsubscribe_url: post.topic.unsubscribe_url,
+ unsubscribe_url: post.topic&.unsubscribe_url,📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||
| allow_reply_by_email: allow_reply_by_email, | ||||||
| use_site_subject: use_site_subject, | ||||||
| add_re_to_subject: add_re_to_subject, | ||||||
|
|
@@ -306,9 +307,7 @@ def send_notification_email(opts) | |||||
| } | ||||||
|
|
||||||
| # If we have a display name, change the from address | ||||||
| if from_alias.present? | ||||||
| email_opts[:from_alias] = from_alias | ||||||
| end | ||||||
| email_opts[:from_alias] = from_alias if from_alias.present? | ||||||
|
|
||||||
| TopicUser.change(user.id, post.topic_id, last_emailed_post_number: post.post_number) | ||||||
|
|
||||||
|
|
||||||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -1,31 +1,29 @@ | ||||||
| <div id='main' class=<%= classes %>> | ||||||
|
|
||||||
| <%= render :partial => 'email/post', :locals => {:post => post} %> | ||||||
| <%= render partial: 'email/post', locals: { post: post } %> | ||||||
|
|
||||||
| <% if context_posts.present? %> | ||||||
| <div class='footer'> | ||||||
| %{respond_instructions} | ||||||
| </div> | ||||||
| <hr> | ||||||
| <h4 class='.previous-discussion'><%= t "user_notifications.previous_discussion" %></h4> | ||||||
| <% if context_posts.present? %> | ||||||
| <div class='footer'>%{respond_instructions}</div> | ||||||
|
|
||||||
| <hr> | ||||||
|
|
||||||
| <% context_posts.each do |p| %> | ||||||
| <%= render :partial => 'email/post', :locals => {:post => p} %> | ||||||
| <h4 class='.previous-discussion'><%= t "user_notifications.previous_discussion" %></h4> | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Typo in class attribute: remove leading dot. Class names in HTML attributes should not include the leading dot ( Proposed fix- <h4 class='.previous-discussion'><%= t "user_notifications.previous_discussion" %></h4>
+ <h4 class='previous-discussion'><%= t "user_notifications.previous_discussion" %></h4>📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||
|
|
||||||
| <% context_posts.each do |p| %> | ||||||
| <%= render partial: 'email/post', locals: { post: p } %> | ||||||
| <% end %> | ||||||
| <% end %> | ||||||
| <% end %> | ||||||
|
|
||||||
| <hr> | ||||||
| <hr> | ||||||
|
|
||||||
| <div class='footer'>%{respond_instructions}</div> | ||||||
| <div class='footer'>%{unsubscribe_link}</div> | ||||||
|
|
||||||
| <div class='footer'> | ||||||
| %{respond_instructions} | ||||||
| </div> | ||||||
| <div class='footer'> | ||||||
| %{unsubscribe_link} | ||||||
| </div> | ||||||
| </div> | ||||||
|
|
||||||
| <div itemscope itemtype="http://schema.org/EmailMessage" style="display:none"> | ||||||
| <div itemprop="action" itemscope itemtype="http://schema.org/ViewAction"> | ||||||
| <link itemprop="url" href="<%= Discourse.base_url %><%= post.url %>" /> | ||||||
| <meta itemprop="name" content="<%= t 'read_full_topic' %>"/> | ||||||
| </div> | ||||||
| <div itemprop="action" itemscope itemtype="http://schema.org/ViewAction"> | ||||||
| <link itemprop="url" href="<%= Discourse.base_url %><%= post.url %>" /> | ||||||
| <meta itemprop="name" content="<%= t 'read_full_topic' %>"/> | ||||||
| </div> | ||||||
| </div> | ||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -981,6 +981,9 @@ en: | |
| search: "There are no more search results." | ||
|
|
||
| topic: | ||
| unsubscribe: | ||
| stop_notifications: "You will stop receiving notifications for <strong>{{title}}</strong>." | ||
| change_notification_state: "You can change your notification state" | ||
| filter_to: "{{post_count}} posts in topic" | ||
|
Comment on lines
983
to
987
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Search for usage of these translation keys
rg -n "topic\.unsubscribe\.stop_notifications|topic\.unsubscribe\.change_notification_state" --type js --type ts --type handlebars -B 2 -A 2Repository: AI-Code-Review-Evals/coderabbit-discourse Length of output: 226 🏁 Script executed: #!/bin/bash
# Search for "stop_notifications" in the entire codebase to find rendering context
rg -i "stop_notifications" --type js --type ts --type html --type handlebars -B 3 -A 3Repository: AI-Code-Review-Evals/coderabbit-discourse Length of output: 119 🏁 Script executed: #!/bin/bash
# Check for i18n/translation rendering patterns that might indicate html-safe behavior
rg -n "html-safe|htmlSafe|\.html\(|triple.*mustache|{{{" app/assets -A 2 -B 2 | head -50Repository: AI-Code-Review-Evals/coderabbit-discourse Length of output: 253 🏁 Script executed: #!/bin/bash
# Look for the topic unsubscribe notification logic
fd -e js -e ts -e hbs | xargs grep -l "unsubscribe" | head -10Repository: AI-Code-Review-Evals/coderabbit-discourse Length of output: 232 🏁 Script executed: cat -n app/assets/javascripts/discourse/templates/topic/unsubscribe.hbsRepository: AI-Code-Review-Evals/coderabbit-discourse Length of output: 332 🏁 Script executed: rg -n "stop_notifications" app/assets/javascripts -A 5 -B 5Repository: AI-Code-Review-Evals/coderabbit-discourse Length of output: 1013 🏁 Script executed: # Search for i18n helper usage and escaping behavior
rg -n "i18n|t\(" app/assets/javascripts/discourse/templates/topic/unsubscribe.hbs -A 2 -B 2Repository: AI-Code-Review-Evals/coderabbit-discourse Length of output: 223 🏁 Script executed: # Check for any js that handles the unsubscribe view/controller
fd -e js -e ts app/assets/javascripts | xargs grep -l "unsubscribe" 2>/dev/null | head -5Repository: AI-Code-Review-Evals/coderabbit-discourse Length of output: 478 🏁 Script executed: # Look for i18n/translation rendering patterns - check how handlebars renders translations
rg -n "interpolate|htmlSafe|\.html" app/assets/javascripts/discourse -A 1 -B 1 | grep -i "i18n\|t\(" | head -20Repository: AI-Code-Review-Evals/coderabbit-discourse Length of output: 108 🏁 Script executed: # Find the I18n library implementation
fd -e js "i18n" app/ vendor/ public/ | grep -i i18n | head -10Repository: AI-Code-Review-Evals/coderabbit-discourse Length of output: 165 🏁 Script executed: # Search for I18n.t implementation to see if it escapes interpolated values
rg -n "I18n\s*=|\.t\s*=|function t\(" -A 10 vendor/assets/javascripts/ --type js | head -50Repository: AI-Code-Review-Evals/coderabbit-discourse Length of output: 67 🏁 Script executed: # Look for discourse's i18n helper or how interpolation works
rg -n "interpolate" app/assets/javascripts -A 5 | head -30Repository: AI-Code-Review-Evals/coderabbit-discourse Length of output: 1369 🏁 Script executed: # Check if there's any escaping of interpolated values in i18n
rg -n "escape|htmlEscape" vendor/assets/javascripts/ --type js -B 2 -A 2 | head -30Repository: AI-Code-Review-Evals/coderabbit-discourse Length of output: 2416 🏁 Script executed: cat -n app/assets/javascripts/locales/i18n.js | sed -n '167,200p'Repository: AI-Code-Review-Evals/coderabbit-discourse Length of output: 1297 🏁 Script executed: # Get more of the interpolate function
sed -n '167,190p' app/assets/javascripts/locales/i18n.jsRepository: AI-Code-Review-Evals/coderabbit-discourse Length of output: 688 🏁 Script executed: # Look at the full interpolate function
awk '/I18n\.interpolate = function/,/^I18n\./ {print NR": "$0}' app/assets/javascripts/locales/i18n.js | head -40Repository: AI-Code-Review-Evals/coderabbit-discourse Length of output: 137 🏁 Script executed: # Check if there are any utility functions for escaping in Discourse
rg -n "Handlebars\.Utils\.escapeExpression|escape|htmlEscape" app/assets/javascripts -A 2 -B 1 | head -30Repository: AI-Code-Review-Evals/coderabbit-discourse Length of output: 2621 🏁 Script executed: # Verify what fancyTitle contains and where it comes from
rg -n "fancyTitle" app/assets/javascripts -B 2 -A 2 | head -20Repository: AI-Code-Review-Evals/coderabbit-discourse Length of output: 1781 HTML injection vulnerability: The translation string contains Escaping must occur before interpolation or the rendered output will be unsafe. 🤖 Prompt for AI Agents |
||
| create: 'New Topic' | ||
| create_long: 'Create a new Topic' | ||
|
|
@@ -1014,7 +1017,6 @@ en: | |
| new_posts: | ||
| one: "there is 1 new post in this topic since you last read it" | ||
| other: "there are {{count}} new posts in this topic since you last read it" | ||
|
|
||
| likes: | ||
| one: "there is 1 like in this topic" | ||
| other: "there are {{count}} likes in this topic" | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
Repository: AI-Code-Review-Evals/coderabbit-discourse
Length of output: 1261
🏁 Script executed:
Repository: AI-Code-Review-Evals/coderabbit-discourse
Length of output: 332
🏁 Script executed:
Repository: AI-Code-Review-Evals/coderabbit-discourse
Length of output: 295
Fix misspelled method name in controller and template:
stopNotificiationsTextshould bestopNotificationsText.The method name is missing a 'c' in "Notifications". Both the controller method and the template reference must be updated together to fix this consistently.
Proposed changes
Controller (app/assets/javascripts/discourse/controllers/topic-unsubscribe.js.es6):
Template (app/assets/javascripts/discourse/templates/topic/unsubscribe.hbs):
📝 Committable suggestion
🤖 Prompt for AI Agents