Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .rubocop_todo.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ Metrics/BlockNesting:
# Offense count: 23
# Configuration parameters: CountComments, CountAsOne.
Metrics/ClassLength:
Max: 342
Max: 343

# Offense count: 72
# Configuration parameters: AllowedMethods, AllowedPatterns.
Expand Down
1 change: 1 addition & 0 deletions app/abilities/ability.rb
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ def initialize(user)
can :update, :account_terms
can :create, :account_pd_declaration
can :read, :dashboard
can :index, :notification
can [:read, :update], [:preferences, :profile]
can [:create, :subscribe, :unsubscribe], DiaryEntry
can :update, DiaryEntry, :user => user
Expand Down
16 changes: 16 additions & 0 deletions app/controllers/notifications_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# frozen_string_literal: true

class NotificationsController < ApplicationController
layout :site_layout

before_action :authorize_web
before_action :set_locale

authorize_resource :class => false

before_action :check_database_readable

def index
@notifications = UserNotifications.new(current_user).visible
end
end
6 changes: 4 additions & 2 deletions app/helpers/user_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@ module UserHelper
def user_image(user, options = {})
options[:class] ||= "user_image border border-secondary-subtle bg-body"
options[:alt] ||= ""
width = options.fetch(:width, 100)
height = options.fetch(:height, 100)

if user.image_use_gravatar
user_gravatar_tag(user, options)
elsif user.avatar.attached?
user_avatar_variant_tag(user, { :resize_to_limit => [100, 100] }, options)
user_avatar_variant_tag(user, { :resize_to_limit => [width, height] }, options)
else
image_tag "avatar.svg", options.merge(:width => 100, :height => 100)
image_tag "avatar.svg", options.merge(:width => width, :height => height)
end
end

Expand Down
2 changes: 2 additions & 0 deletions app/models/user.rb
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,8 @@ class User < ApplicationRecord

has_many :reports

has_many :notifications, :as => :recipient, :class_name => "Noticed::Notification"

has_many :social_links
accepts_nested_attributes_for :social_links, :allow_destroy => true

Expand Down
127 changes: 127 additions & 0 deletions app/models/user_notifications.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
# frozen_string_literal: true

class UserNotifications
class Notification
def self.from(notification)
partial_classname = notification.class.name.sub("Notifier::Notification", "")
klass = "UserNotifications::#{partial_classname}Notification".constantize
klass.new(notification)
end

def initialize(notification)
@notification = notification
end

def visible?
true
end

def to_partial_path
partial_basename = @notification.class.name.sub("Notifier::Notification", "").underscore
"notifications/#{partial_basename}"
end

def timestamp
record.created_at
end

def record
@notification.record
end
end

class ChangesetCommentNotification < Notification
delegate :changeset, :to => :record
delegate :visible?, :to => :record

def commenter
record.author
end

def comment_id
record.id
end

def comment_body
record.body
end
end

class GpxImportFailureNotification < Notification
def trace_filename
@notification.params[:trace_name]
end

def trace_description
@notification.params[:trace_description]
end

def trace_tags
@notification.params[:trace_tags]
end

def trace_possible_points
nil
end

def trace_points
nil
end
end

class GpxImportSuccessNotification < Notification
def trace_filename
record.file.name
end

def trace_description
record.description
end

def trace_tags
record.tags.map(&:tag)
end

def trace_possible_points
@notification.params[:possible_points]
end

def trace_points
record.size
end

def user
record.user
end
end

class NewFollowerNotification < Notification
delegate :follower, :to => :record
end

include Enumerable

LISTABLE_NOTIFICATIONS = %w[
ChangesetCommentNotifier::Notification
GpxImportFailureNotifier::Notification
GpxImportSuccessNotifier::Notification
NewFollowerNotifier::Notification
].freeze

def initialize(user)
@user = user
end

def visible
select(&:visible?)
end

def each(&)
@user
.notifications
.where(:type => LISTABLE_NOTIFICATIONS)
.newest_first
.map { |instance| Notification.from(instance) }
.each(&)
end
end
1 change: 1 addition & 0 deletions app/views/layouts/_header.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
<span class="badge count-number"><%= number_with_delimiter(current_user.new_messages.size) %></span>
<% end %>
<%= link_to t("users.show.my profile"), current_user, :class => "dropdown-item" %>
<%= link_to t("users.show.my_notifications"), notifications_path, :class => "dropdown-item" %>
<%= link_to t("users.show.my_account"), account_path, :class => "dropdown-item" %>
<%= link_to t("users.show.my_preferences"), basic_preferences_path, :class => "dropdown-item" %>
<div class="dropdown-divider"></div>
Expand Down
47 changes: 47 additions & 0 deletions app/views/notifications/_changeset_comment.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<%# locals: (notification:) %>

<article class="row">
<div class="col-9">
<p class="text-body-secondary mb-1">
<%= link_to(
notification.commenter.display_name,
notification.commenter
) %>
left a comment
<%= friendly_date_ago(notification.timestamp) %>
on a changeset you are watching, created by
<%= link_to(
notification.changeset.user.display_name,
notification.changeset.user
) %>
with comment
<q><%= notification.changeset.comment %></q>
</p>
<div class="row">
<div class="col-2 text-center">
<%= link_to(
user_image(notification.commenter, :width => 50, :height => 50, :class => "img-fluid"),
user_url(notification.commenter),
:target => "_blank", :rel => "noopener"
) %>
</div>
<blockquote class="col-10 fst-italic">
<%= notification.comment_body.to_html %>
</blockquote>
</div>
</div>

<p class="col-3 text-center">
<%=
link_to(
t(".view_changeset_comment"),
changeset_path(
notification.changeset,
:anchor => "c#{notification.comment_id}"
)
)
%>
</p>
</article>

<hr>
44 changes: 44 additions & 0 deletions app/views/notifications/_gpx_import_failure.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<%# locals: (notification:) %>

<article class="row">
<div class="col-9">
<p class="text-body-secondary mb-1">
It looks like your file failed to be imported as a GPS trace.
</p>
<div class="row">
<div class="col-2 text-center">
<%# This space intentionally left blank, as there is no other user %>
</div>
<div class="col-10">
<dl>
<dt>Filename</dt>
<dd><%= notification.trace_filename %></dd>

<dt>Description</dt>
<dd><%= notification.trace_description %></dd>

<% if notification.trace_tags.length.positive? %>
<dt>Tags</dt>
<dd><%= notification.trace_tags.join(", ") %></dd>
<% end %>

<% if notification.trace_possible_points %>
<dt>Possible points</dt>
<dd><%= notification.trace_possible_points %></dd>
<% end %>

<% if notification.trace_points %>
<dt>Imported points</dt>
<dd><%= notification.trace_points %></dd>
<% end %>
</dl>
</div>
</div>
</div>

<p class="col-3 text-center">
<%# This space intentionally left blank, as there is no obvious action %>
</p>
</article>

<hr>
44 changes: 44 additions & 0 deletions app/views/notifications/_gpx_import_success.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<%# locals: (notification:) %>

<article class="row">
<div class="col-9">
<p class="text-body-secondary mb-1">
It looks like your file was imported successfully as a GPS trace.
</p>
<div class="row">
<div class="col-2 text-center">
<%# This space intentionally left blank, as there is no other user %>
</div>
<div class="col-10">
<dl>
<dt>Filename</dt>
<dd><%= notification.trace_filename %></dd>

<dt>Description</dt>
<dd><%= notification.trace_description %></dd>

<% if notification.trace_tags.length.positive? %>
<dt>Tags</dt>
<dd><%= notification.trace_tags.join(", ") %></dd>
<% end %>

<% if notification.trace_possible_points %>
<dt>Possible points</dt>
<dd><%= notification.trace_possible_points %></dd>
<% end %>

<% if notification.trace_points %>
<dt>Imported points</dt>
<dd><%= notification.trace_points %></dd>
<% end %>
</dl>
</div>
</div>
</div>

<p class="col-3 text-center">
<%= link_to "View", show_trace_path(notification.user, notification.record) %>
</p>
</article>

<hr>
32 changes: 32 additions & 0 deletions app/views/notifications/_new_follower.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<%# locals: (notification:) %>

<article class="row">
<div class="col-9">
<p class="text-body-secondary mb-1">
<%= link_to(
notification.follower.display_name,
notification.follower
) %>
started following you
<%= friendly_date_ago(notification.timestamp) %>
</p>
<div class="row">
<div class="col-2 text-center">
<%= link_to(
user_image(notification.follower, :width => 50, :height => 50, :class => "img-fluid"),
user_url(notification.follower),
:target => "_blank", :rel => "noopener"
) %>
</div>
<div class="col-10">
<p>You can <%= link_to "follow them back", follow_url(notification.follower) %> if you wish.</p>
</div>
</div>
</div>

<p class="col-3 text-center">
<%# This space intentionally left blank, as there is no obvious action %>
</p>
</article>

<hr>
12 changes: 12 additions & 0 deletions app/views/notifications/index.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<% content_for :heading do %>
<h1><%= t ".title" %></h1>
<% end %>

<% if @notifications.empty? %>
<p>You have no notifications at the moment.</p>
<% end %>
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would be a good opportunity to link to the notification preferences in #7001, depending on which PR lands first.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. Yes, I think users are now used to "click here to change your notification preferences" at places like this.

<div class="container">
<% @notifications.each do |notification| %>
<%= render :partial => notification.to_partial_path, :object => notification, :as => :notification %>
<% end %>
</div>
Loading
Loading