From a8b7599d4a4647b24b375ab1c42f0885379cd731 Mon Sep 17 00:00:00 2001 From: Guo Xiang Tan Date: Tue, 8 Nov 2016 16:12:40 +0800 Subject: [PATCH] FEATURE: Add a radial ping when user's first notification has not been read. --- .../subscribe-user-notifications.js.es6 | 1 + .../discourse/widgets/header.js.es6 | 2 + .../stylesheets/common/base/discourse.scss | 37 +++++++++++++++++++ .../stylesheets/common/base/header.scss | 4 +- app/models/user.rb | 5 +++ app/serializers/current_user_serializer.rb | 1 + spec/models/user_spec.rb | 23 ++++++++++++ 7 files changed, 71 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/discourse/initializers/subscribe-user-notifications.js.es6 b/app/assets/javascripts/discourse/initializers/subscribe-user-notifications.js.es6 index 7583982d28b..8061b579b91 100644 --- a/app/assets/javascripts/discourse/initializers/subscribe-user-notifications.js.es6 +++ b/app/assets/javascripts/discourse/initializers/subscribe-user-notifications.js.es6 @@ -41,6 +41,7 @@ export default { user.set('unread_notifications', data.unread_notifications); user.set('unread_private_messages', data.unread_private_messages); + user.set('read_first_notification', data.read_first_notification); if (oldUnread !== data.unread_notifications || oldPM !== data.unread_private_messages) { appEvents.trigger('notifications:changed'); diff --git a/app/assets/javascripts/discourse/widgets/header.js.es6 b/app/assets/javascripts/discourse/widgets/header.js.es6 index 5f1f1e21337..1e01d0263de 100644 --- a/app/assets/javascripts/discourse/widgets/header.js.es6 +++ b/app/assets/javascripts/discourse/widgets/header.js.es6 @@ -42,6 +42,8 @@ createWidget('header-notifications', { const unreadPMs = currentUser.get('unread_private_messages'); if (!!unreadPMs) { + if (!currentUser.get('read_first_notification')) contents.push(h('span.ring')); + contents.push(this.attach('link', { action: attrs.action, className: 'badge-notification unread-private-messages', rawLabel: unreadPMs })); diff --git a/app/assets/stylesheets/common/base/discourse.scss b/app/assets/stylesheets/common/base/discourse.scss index 0d68b07b9ac..772ad99e89e 100644 --- a/app/assets/stylesheets/common/base/discourse.scss +++ b/app/assets/stylesheets/common/base/discourse.scss @@ -166,6 +166,43 @@ body { &.badge-notification[href] {color: $secondary;} } +.ring { + top: -13.4px !important; + right: 22.4px !important; + border-radius: 15px; + width: 20px; + height: 20px; + border: solid #FF0000 1px; + -moz-animation-iteration-count: infinite; + -webkit-animation-iteration-count: infinite; + -webkit-transform-origin: center; + -moz-animation-duration: 3s; + -webkit-animation-duration: 3s; + -moz-animation-name: ping; + -webkit-animation-name: ping; +} + +@-webkit-keyframes ping { + from { + $scale: 0.25; + transform: scale($scale); + -ms-transform: scale($scale); + -webkit-transform: scale($scale); + -o-transform: scale($scale); + -moz-transform: scale($scale); + opacity: 1; + } + to { + $scale: 2.5; + transform: scale($scale); + -ms-transform: scale($scale); + -webkit-transform: scale($scale); + -o-transform: scale($scale); + -moz-transform: scale($scale); + opacity: 0; + } +} + .fade { opacity: 0; transition: opacity 0.15s linear; diff --git a/app/assets/stylesheets/common/base/header.scss b/app/assets/stylesheets/common/base/header.scss index e1f2408f452..19917997670 100644 --- a/app/assets/stylesheets/common/base/header.scss +++ b/app/assets/stylesheets/common/base/header.scss @@ -123,7 +123,7 @@ .notifications { position: relative; } - .badge-notification { + .badge-notification, .ring { position: absolute; top: -9px; z-index: 1; @@ -133,7 +133,7 @@ right: 0; background-color: scale-color($tertiary, $lightness: 50%); } - .unread-private-messages { + .unread-private-messages, .ring { right: 25px; } .flagged-posts { diff --git a/app/models/user.rb b/app/models/user.rb index aacae0c9701..062bc473635 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -343,6 +343,10 @@ class User < ActiveRecord::Base end end + def read_first_notification? + notifications.order(created_at: :asc).first.read + end + def publish_notifications_state # publish last notification json with the message so we # can apply an update @@ -384,6 +388,7 @@ class User < ActiveRecord::Base {unread_notifications: unread_notifications, unread_private_messages: unread_private_messages, total_unread_notifications: total_unread_notifications, + read_first_notification: read_first_notification?, last_notification: json, recent: recent, seen_notification_id: seen_notification_id diff --git a/app/serializers/current_user_serializer.rb b/app/serializers/current_user_serializer.rb index 498632e4a76..f574ef3ccbf 100644 --- a/app/serializers/current_user_serializer.rb +++ b/app/serializers/current_user_serializer.rb @@ -6,6 +6,7 @@ class CurrentUserSerializer < BasicUserSerializer :total_unread_notifications, :unread_notifications, :unread_private_messages, + :read_first_notification?, :admin?, :notification_channel_position, :site_flagged_posts_count, diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index f0bea747de3..8e8218110cc 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -1314,4 +1314,27 @@ describe User do end end + describe '#read_first_notification?' do + let(:user) { Fabricate(:user) } + let(:notification) { Fabricate(:private_message_notification, user: user) } + let(:other_notification) { Fabricate(:private_message_notification, user: user) } + + describe 'when first notification has not been read' do + it 'should return the right value' do + notification.update_attributes!(read: false) + other_notification.update_attributes!(read: true) + + expect(user.read_first_notification?).to eq(false) + end + end + + describe 'when first notification has been read' do + it 'should return the right value' do + notification.update_attributes!(read: true) + other_notification.update_attributes!(read: false) + + expect(user.read_first_notification?).to eq(true) + end + end + end end