diff --git a/app/assets/fonts/icons.eot b/app/assets/fonts/icons.eot
index 3046c50cc..ced98a9b4 100644
Binary files a/app/assets/fonts/icons.eot and b/app/assets/fonts/icons.eot differ
diff --git a/app/assets/fonts/icons.svg b/app/assets/fonts/icons.svg
index c107e740d..d92b9a44a 100644
--- a/app/assets/fonts/icons.svg
+++ b/app/assets/fonts/icons.svg
@@ -19,7 +19,6 @@
-
@@ -30,12 +29,10 @@
-
-
@@ -45,4 +42,8 @@
+
+
+
+
diff --git a/app/assets/fonts/icons.ttf b/app/assets/fonts/icons.ttf
index b933f1c3f..f3486cf96 100644
Binary files a/app/assets/fonts/icons.ttf and b/app/assets/fonts/icons.ttf differ
diff --git a/app/assets/fonts/icons.woff b/app/assets/fonts/icons.woff
index feb047bd0..9045aa563 100644
Binary files a/app/assets/fonts/icons.woff and b/app/assets/fonts/icons.woff differ
diff --git a/app/assets/stylesheets/icons.scss b/app/assets/stylesheets/icons.scss
index eda8b979d..46e24e4cf 100644
--- a/app/assets/stylesheets/icons.scss
+++ b/app/assets/stylesheets/icons.scss
@@ -38,116 +38,119 @@
}
.icon-angle-down:before {
- content: "a";
+ content: "\61";
}
.icon-angle-left:before {
- content: "b";
+ content: "\62";
}
.icon-angle-right:before {
- content: "c";
+ content: "\63";
}
.icon-angle-up:before {
- content: "d";
+ content: "\64";
}
.icon-comments:before {
- content: "e";
+ content: "\65";
}
.icon-twitter:before {
- content: "f";
+ content: "\66";
}
.icon-calendar:before {
- content: "g";
+ content: "\67";
}
.icon-debates:before {
- content: "i";
+ content: "\69";
}
.icon-unlike:before {
- content: "j";
+ content: "\6a";
}
.icon-like:before {
- content: "k";
+ content: "\6b";
}
.icon-check:before {
- content: "l";
+ content: "\6c";
}
.icon-edit:before {
- content: "m";
-}
-.icon-star:before {
- content: "n";
+ content: "\6d";
}
.icon-user:before {
- content: "o";
+ content: "\6f";
}
.icon-settings:before {
- content: "q";
+ content: "\71";
}
.icon-stats:before {
- content: "r";
+ content: "\72";
}
.icon-proposals:before {
- content: "h";
+ content: "\68";
}
.icon-organizations:before {
- content: "s";
+ content: "\73";
}
.icon-deleted:before {
- content: "t";
+ content: "\74";
}
.icon-tag:before {
- content: "u";
+ content: "\75";
}
.icon-eye:before {
- content: "p";
+ content: "\70";
}
.icon-x:before {
- content: "v";
+ content: "\76";
}
.icon-flag:before {
- content: "w";
-}
-.icon-notification:before {
- content: "x";
+ content: "\77";
}
.icon-comment:before {
- content: "y";
+ content: "\79";
}
.icon-reply:before {
- content: "z";
+ content: "\7a";
}
.icon-facebook:before {
- content: "A";
+ content: "\41";
}
.icon-google-plus:before {
- content: "B";
-}
-.icon-language:before {
- content: "C";
+ content: "\42";
}
.icon-search:before {
- content: "E";
+ content: "\45";
}
.icon-external:before {
- content: "F";
+ content: "\46";
}
.icon-video:before {
- content: "D";
+ content: "\44";
}
.icon-document:before {
- content: "G";
+ content: "\47";
}
.icon-print:before {
- content: "H";
+ content: "\48";
}
.icon-blog:before {
- content: "J";
+ content: "\4a";
}
.icon-box:before {
- content: "I";
+ content: "\49";
}
.icon-youtube:before {
- content: "K";
+ content: "\4b";
}
.icon-letter:before {
- content: "L";
-}
\ No newline at end of file
+ content: "\4c";
+}
+.icon-no-notification:before {
+ content: "\78";
+}
+.icon-notification:before {
+ content: "\6e";
+}
+.icon-circle:before {
+ content: "\43";
+}
+.icon-circle-o:before {
+ content: "\4d";
+}
diff --git a/app/assets/stylesheets/layout.scss b/app/assets/stylesheets/layout.scss
index 3685e0b6f..650621624 100644
--- a/app/assets/stylesheets/layout.scss
+++ b/app/assets/stylesheets/layout.scss
@@ -396,7 +396,6 @@ header {
&:hover {
background: none;
color: white;
- text-decoration: underline;
transition: text-decoration 275ms;
}
@@ -420,6 +419,7 @@ header {
&:hover, &:focus {
background-color: #007095 !important;
+ text-decoration: underline;
}
}
@@ -947,6 +947,77 @@ img.avatar, img.admin-avatar, img.moderator-avatar, img.initialjs-avatar {
}
}
+.notifications {
+ position: relative;
+
+ &:hover {
+ text-decoration: none;
+ }
+
+ [class^="icon-"] {
+ font-size: $h4-font-size;
+ vertical-align: middle;
+ }
+
+ .icon-circle {
+ color: #ecf00b;
+ font-size: $tiny-font-size;
+ position: absolute;
+ right: 4px;
+ top: -6px;
+ }
+}
+
+.notifications-list:before {
+ background: $border;
+ content: '';
+ height: 100%;
+ left: 28px;
+ position: absolute;
+ top: 0;
+ width: 2px;
+}
+
+.notification {
+ display: block;
+ padding: $line-height/2 0 $line-height/2 $line-height*1.5;
+ position: relative;
+
+ &:hover {
+
+ a {
+ text-decoration: none;
+ }
+
+ p:not(.time) {
+ color: $link;
+ }
+
+ &:before {
+ content: "\43";
+ }
+ }
+
+ &:before {
+ background: white;
+ color: $brand;
+ content: "\4d";
+ font-family: "icons" !important;
+ left: 6px;
+ position: absolute;
+ }
+
+ p {
+ color: $text;
+ margin-bottom: 0;
+ }
+
+ .time {
+ font-size: $small-font-size;
+ color: $text-medium;
+ }
+}
+
// 09. Filters & search
// - - - - - - - - - - - - - - - - - - - - - - - - -
diff --git a/app/assets/stylesheets/variables.scss b/app/assets/stylesheets/variables.scss
index d9da9c580..6a61ec72c 100644
--- a/app/assets/stylesheets/variables.scss
+++ b/app/assets/stylesheets/variables.scss
@@ -39,6 +39,7 @@ $h6-font-size: rem-calc(13);
$h6-line-height: rem-calc(17);
$small-font-size: rem-calc(14);
+$tiny-font-size: rem-calc(10);
$line-height: rem-calc(24);
// 02. Colors
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 841b32498..5150f97ec 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -108,4 +108,5 @@ class ApplicationController < ActionController::Base
store_location_for(:user, request.path)
end
end
+
end
diff --git a/app/controllers/comments_controller.rb b/app/controllers/comments_controller.rb
index 050962414..df928aa02 100644
--- a/app/controllers/comments_controller.rb
+++ b/app/controllers/comments_controller.rb
@@ -9,6 +9,7 @@ class CommentsController < ApplicationController
def create
if @comment.save
CommentNotifier.new(comment: @comment).process
+ add_notification @comment
else
render :new
end
@@ -67,4 +68,13 @@ class CommentsController < ApplicationController
["1", true].include?(comment_params[:as_moderator]) && can?(:comment_as_moderator, @commentable)
end
+ def add_notification(comment)
+ if comment.reply?
+ notifiable = comment.parent
+ else
+ notifiable = comment.commentable
+ end
+ Notification.add(notifiable.author_id, notifiable) unless comment.author_id == notifiable.author_id
+ end
+
end
diff --git a/app/controllers/notifications_controller.rb b/app/controllers/notifications_controller.rb
new file mode 100644
index 000000000..a4ec31b50
--- /dev/null
+++ b/app/controllers/notifications_controller.rb
@@ -0,0 +1,26 @@
+class NotificationsController < ApplicationController
+ before_action :authenticate_user!
+ after_action :mark_as_read, only: :show
+ skip_authorization_check
+
+ def index
+ @notifications = current_user.notifications.unread.recent.for_render
+ end
+
+ def show
+ @notification = current_user.notifications.find(params[:id])
+ redirect_to url_for(@notification.notifiable)
+ end
+
+ def mark_all_as_read
+ current_user.notifications.each { |notification| notification.mark_as_read }
+ redirect_to notifications_path
+ end
+
+ private
+
+ def mark_as_read
+ @notification.mark_as_read
+ end
+
+end
diff --git a/app/helpers/notifications_helper.rb b/app/helpers/notifications_helper.rb
new file mode 100644
index 000000000..281163380
--- /dev/null
+++ b/app/helpers/notifications_helper.rb
@@ -0,0 +1,6 @@
+module NotificationsHelper
+
+ def notification_action(notification)
+ notification.notifiable_type == "Comment" ? "replies_to" : "comments_on"
+ end
+end
diff --git a/app/models/activity.rb b/app/models/activity.rb
index 977204669..047ccb7dd 100644
--- a/app/models/activity.rb
+++ b/app/models/activity.rb
@@ -1,5 +1,4 @@
class Activity < ActiveRecord::Base
-
belongs_to :actionable, -> { with_hidden }, polymorphic: true
belongs_to :user, -> { with_hidden }
@@ -24,5 +23,4 @@ class Activity < ActiveRecord::Base
def self.by(user)
where(user: user)
end
-
end
diff --git a/app/models/notification.rb b/app/models/notification.rb
new file mode 100644
index 000000000..1cb500ccf
--- /dev/null
+++ b/app/models/notification.rb
@@ -0,0 +1,24 @@
+class Notification < ActiveRecord::Base
+ belongs_to :user, counter_cache: true
+ belongs_to :notifiable, polymorphic: true
+
+ scope :unread, -> { all }
+ scope :recent, -> { order(id: :desc) }
+ scope :for_render, -> { includes(:notifiable) }
+
+ def timestamp
+ notifiable.created_at
+ end
+
+ def mark_as_read
+ self.destroy
+ end
+
+ def self.add(user_id, notifiable)
+ if notification = Notification.find_by(user_id: user_id, notifiable: notifiable)
+ Notification.increment_counter(:counter, notification.id)
+ else
+ Notification.create!(user_id: user_id, notifiable: notifiable)
+ end
+ end
+end
\ No newline at end of file
diff --git a/app/models/user.rb b/app/models/user.rb
index dd2bc5cf0..276598bf2 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -22,6 +22,7 @@ class User < ActiveRecord::Base
has_many :proposals, -> { with_hidden }, foreign_key: :author_id
has_many :comments, -> { with_hidden }
has_many :failed_census_calls
+ has_many :notifications
validates :username, presence: true, if: :username_required?
validates :username, uniqueness: true, if: :username_required?
@@ -199,7 +200,7 @@ class User < ActiveRecord::Base
def email_required?
!erased?
end
-
+
def has_official_email?
domain = Setting.value_for 'email_domain_for_officials'
!email.blank? && ( (email.end_with? "@#{domain}") || (email.end_with? ".#{domain}") )
diff --git a/app/views/comments/_comment.html.erb b/app/views/comments/_comment.html.erb
index c001da74e..e8c49636b 100644
--- a/app/views/comments/_comment.html.erb
+++ b/app/views/comments/_comment.html.erb
@@ -67,7 +67,7 @@
<%= simple_format text_with_links comment.body %>
-
+