From a8b5192efd43bcbf64ffe7ae44e5c5b7f3be0b51 Mon Sep 17 00:00:00 2001 From: Sam <sam.saffron@gmail.com> Date: Thu, 17 Dec 2015 18:06:04 +1100 Subject: [PATCH] FEATURE: User page refactor Re-organise user page so it is easier to find interesting info split it into tabs - Introduce notifications and messages tabs - Stop couting stuff for the user page to speed up rendering - Suppress more information when viewing your own profile --- .../controllers/user-private-messages.js.es6 | 25 +++++ .../discourse/controllers/user.js.es6 | 31 ++--- .../mixins/viewing-action-type.js.es6 | 1 + .../javascripts/discourse/models/user.js.es6 | 13 ++- .../discourse/routes/app-route-map.js.es6 | 16 ++- ...s6 => build-private-messages-route.js.es6} | 2 +- .../routes/user-activity-replies.js.es6 | 2 +- .../discourse/routes/user-activity.js.es6 | 18 --- ...js.es6 => user-notifications-edits.js.es6} | 0 .../routes/user-notifications-index.js.es6 | 5 + ... user-notifications-likes-received.js.es6} | 2 +- ...es6 => user-notifications-mentions.js.es6} | 0 ...s6 => user-notifications-responses.js.es6} | 2 +- .../routes/user-notifications.js.es6 | 5 + .../routes/user-private-messages-group.js.es6 | 6 +- .../routes/user-private-messages-index.js.es6 | 2 +- .../routes/user-private-messages-mine.js.es6 | 2 +- .../user-private-messages-unread.js.es6 | 2 +- .../routes/user-private-messages.js.es6 | 33 +++++- .../discourse/routes/user-statistics.js.es6 | 2 + .../discourse/templates/user-topics-list.hbs | 8 -- .../discourse/templates/user/activity.hbs | 41 +++++++ .../discourse/templates/user/messages.hbs | 43 +++++++ .../templates/user/notifications-index.hbs | 34 ++++++ .../templates/user/notifications.hbs | 55 ++++----- .../discourse/templates/user/preferences.hbs | 18 +-- .../discourse/templates/user/user.hbs | 106 +++++------------- app/assets/stylesheets/common/base/user.scss | 31 +++++ .../common/components/navs.css.scss | 4 +- app/assets/stylesheets/desktop/user.scss | 13 ++- app/controllers/users_controller.rb | 7 +- app/models/user.rb | 4 - app/serializers/basic_group_serializer.rb | 3 +- app/serializers/user_serializer.rb | 10 +- config/locales/client.en.yml | 2 +- config/routes.rb | 1 + ...151219045559_add_has_messages_to_groups.rb | 15 +++ lib/topic_creator.rb | 1 + 38 files changed, 371 insertions(+), 194 deletions(-) create mode 100644 app/assets/javascripts/discourse/controllers/user-private-messages.js.es6 rename app/assets/javascripts/discourse/routes/{build-user-topic-list-route.js.es6 => build-private-messages-route.js.es6} (92%) rename app/assets/javascripts/discourse/routes/{user-activity-edits.js.es6 => user-notifications-edits.js.es6} (100%) create mode 100644 app/assets/javascripts/discourse/routes/user-notifications-index.js.es6 rename app/assets/javascripts/discourse/routes/{user-activity-likes-received.js.es6 => user-notifications-likes-received.js.es6} (77%) rename app/assets/javascripts/discourse/routes/{user-activity-mentions.js.es6 => user-notifications-mentions.js.es6} (100%) rename app/assets/javascripts/discourse/routes/{user-activity-posts.js.es6 => user-notifications-responses.js.es6} (80%) create mode 100644 app/assets/javascripts/discourse/routes/user-statistics.js.es6 create mode 100644 app/assets/javascripts/discourse/templates/user/messages.hbs create mode 100644 app/assets/javascripts/discourse/templates/user/notifications-index.hbs create mode 100644 db/migrate/20151219045559_add_has_messages_to_groups.rb diff --git a/app/assets/javascripts/discourse/controllers/user-private-messages.js.es6 b/app/assets/javascripts/discourse/controllers/user-private-messages.js.es6 new file mode 100644 index 000000000..7b7664e75 --- /dev/null +++ b/app/assets/javascripts/discourse/controllers/user-private-messages.js.es6 @@ -0,0 +1,25 @@ +import computed from 'ember-addons/ember-computed-decorators'; + +export default Ember.Controller.extend({ + + pmView: false, + + privateMessagesActive: Em.computed.equal('pmView', 'index'), + privateMessagesMineActive: Em.computed.equal('pmView', 'mine'), + privateMessagesUnreadActive: Em.computed.equal('pmView', 'unread'), + isGroup: Em.computed.equal('pmView', 'groups'), + + + @computed('model.groups', 'groupFilter', 'pmView') + groupPMStats(groups, filter, pmView) { + if (groups) { + return groups.filter(group => group.has_messages) + .map(g => { + return { + name: g.name, + active: (g.name === filter && pmView === "groups") + }; + }); + } + } +}); diff --git a/app/assets/javascripts/discourse/controllers/user.js.es6 b/app/assets/javascripts/discourse/controllers/user.js.es6 index 49889e5c0..298592061 100644 --- a/app/assets/javascripts/discourse/controllers/user.js.es6 +++ b/app/assets/javascripts/discourse/controllers/user.js.es6 @@ -6,7 +6,6 @@ import User from 'discourse/models/user'; export default Ember.Controller.extend(CanCheckEmails, { indexStream: false, - pmView: false, userActionType: null, needs: ['user-notifications', 'user-topics-list'], @@ -28,11 +27,14 @@ export default Ember.Controller.extend(CanCheckEmails, { }, @computed('viewingSelf', 'currentUser.admin') - canSeePrivateMessages(viewingSelf, isAdmin) { + showPrivateMessages(viewingSelf, isAdmin) { return this.siteSettings.enable_private_messages && (viewingSelf || isAdmin); }, - canSeeNotificationHistory: Em.computed.alias('canSeePrivateMessages'), + @computed('viewingSelf', 'currentUser.admin') + canSeeNotificationHistory(viewingSelf, isAdmin) { + return viewingSelf || isAdmin; + }, @computed("content.badge_count") showBadges(badgeCount) { @@ -45,6 +47,12 @@ export default Ember.Controller.extend(CanCheckEmails, { (userActionType === UserAction.TYPES.messages_received); }, + @computed("indexStream", "userActionType") + showActionTypeSummary(indexStream,userActionType, showPMs) { + return (indexStream || userActionType) && !showPMs; + }, + + @computed() canInviteToForum() { return User.currentProp('can_invite_to_forum'); @@ -64,23 +72,6 @@ export default Ember.Controller.extend(CanCheckEmails, { } }, - privateMessagesActive: Em.computed.equal('pmView', 'index'), - privateMessagesMineActive: Em.computed.equal('pmView', 'mine'), - privateMessagesUnreadActive: Em.computed.equal('pmView', 'unread'), - - @computed('model.private_messages_stats.groups', 'groupFilter', 'pmView') - groupPMStats(stats,filter,pmView) { - if (stats) { - return stats.map(g => { - return { - name: g.name, - count: g.count, - active: (g.name === filter && pmView === 'groups') - }; - }); - } - }, - actions: { expandProfile() { this.set('forceExpand', true); diff --git a/app/assets/javascripts/discourse/mixins/viewing-action-type.js.es6 b/app/assets/javascripts/discourse/mixins/viewing-action-type.js.es6 index 35084dcf1..1da5529cd 100644 --- a/app/assets/javascripts/discourse/mixins/viewing-action-type.js.es6 +++ b/app/assets/javascripts/discourse/mixins/viewing-action-type.js.es6 @@ -3,4 +3,5 @@ export default { this.controllerFor('user').set('userActionType', userActionType); this.controllerFor('user-activity').set('userActionType', userActionType); } + }; diff --git a/app/assets/javascripts/discourse/models/user.js.es6 b/app/assets/javascripts/discourse/models/user.js.es6 index 97c611ab8..2dbeea576 100644 --- a/app/assets/javascripts/discourse/models/user.js.es6 +++ b/app/assets/javascripts/discourse/models/user.js.es6 @@ -199,6 +199,15 @@ const User = RestModel.extend({ ua.action_type === UserAction.TYPES.topics; }, + @computed("groups.@each") + displayGroups() { + const groups = this.get('groups'); + const filtered = groups.filter(group => { + return !group.automatic || group.name === "moderators"; + }); + return filtered.length === 0 ? null : filtered; + }, + // The user's stat count, excluding PMs. @computed("statsExcludingPms.@each.count") statsCountNonPM() { @@ -233,8 +242,8 @@ const User = RestModel.extend({ })); } - if (!Em.isEmpty(json.user.custom_groups)) { - json.user.custom_groups = json.user.custom_groups.map(g => Group.create(g)); + if (!Em.isEmpty(json.user.groups)) { + json.user.groups = json.user.groups.map(g => Group.create(g)); } if (json.user.invited_by) { diff --git a/app/assets/javascripts/discourse/routes/app-route-map.js.es6 b/app/assets/javascripts/discourse/routes/app-route-map.js.es6 index d67e0a786..39bee4912 100644 --- a/app/assets/javascripts/discourse/routes/app-route-map.js.es6 +++ b/app/assets/javascripts/discourse/routes/app-route-map.js.es6 @@ -58,13 +58,20 @@ export default function() { this.resource('users'); this.resource('user', { path: '/users/:username' }, function() { this.resource('userActivity', { path: '/activity' }, function() { - _.map(Discourse.UserAction.TYPES, (id, userAction) => { - this.route(userAction, { path: userAction.replace('_', '-') }); - }); + this.route('topics'); + this.route('replies'); + this.route('likesGiven', {path: 'likes-given'}); + this.route('bookmarks'); + }); + + this.resource('userNotifications', {path: '/notifications'}, function(){ + this.route('responses'); + this.route('likesReceived', { path: 'likes-received'}); + this.route('mentions'); + this.route('edits'); }); this.route('badges'); - this.route('notifications'); this.route('flaggedPosts', { path: '/flagged-posts' }); this.route('deletedPosts', { path: '/deleted-posts' }); @@ -85,6 +92,7 @@ export default function() { this.resource('userInvited', { path: '/invited' }, function() { this.route('show', { path: '/:filter' }); }); + }); this.route('signup', {path: '/signup'}); diff --git a/app/assets/javascripts/discourse/routes/build-user-topic-list-route.js.es6 b/app/assets/javascripts/discourse/routes/build-private-messages-route.js.es6 similarity index 92% rename from app/assets/javascripts/discourse/routes/build-user-topic-list-route.js.es6 rename to app/assets/javascripts/discourse/routes/build-private-messages-route.js.es6 index 5704cd96d..cef72ed15 100644 --- a/app/assets/javascripts/discourse/routes/build-user-topic-list-route.js.es6 +++ b/app/assets/javascripts/discourse/routes/build-private-messages-route.js.es6 @@ -24,7 +24,7 @@ export default (viewName, path) => { showParticipants: true }); - this.controllerFor("user").set("pmView", viewName); + this.controllerFor("userPrivateMessages").set("pmView", viewName); this.searchService.set('contextType', 'private_messages'); }, diff --git a/app/assets/javascripts/discourse/routes/user-activity-replies.js.es6 b/app/assets/javascripts/discourse/routes/user-activity-replies.js.es6 index 28e11587f..62607c9a1 100644 --- a/app/assets/javascripts/discourse/routes/user-activity-replies.js.es6 +++ b/app/assets/javascripts/discourse/routes/user-activity-replies.js.es6 @@ -2,5 +2,5 @@ import UserActivityStreamRoute from "discourse/routes/user-activity-stream"; import UserAction from "discourse/models/user-action"; export default UserActivityStreamRoute.extend({ - userActionType: UserAction.TYPES["replies"] + userActionType: UserAction.TYPES["posts"] }); diff --git a/app/assets/javascripts/discourse/routes/user-activity.js.es6 b/app/assets/javascripts/discourse/routes/user-activity.js.es6 index 5f2173032..546e135cb 100644 --- a/app/assets/javascripts/discourse/routes/user-activity.js.es6 +++ b/app/assets/javascripts/discourse/routes/user-activity.js.es6 @@ -1,5 +1,3 @@ -import Draft from 'discourse/models/draft'; - export default Discourse.Route.extend({ model() { return this.modelFor("user"); @@ -7,21 +5,5 @@ export default Discourse.Route.extend({ setupController(controller, user) { this.controllerFor("user-activity").set("model", user); - - // Bring up a draft - const composerController = this.controllerFor("composer"); - controller.set("model", user); - if (this.currentUser) { - Draft.get("new_private_message").then(function(data) { - if (data.draft) { - composerController.open({ - draft: data.draft, - draftKey: "new_private_message", - ignoreIfChanged: true, - draftSequence: data.draft_sequence - }); - } - }); - } } }); diff --git a/app/assets/javascripts/discourse/routes/user-activity-edits.js.es6 b/app/assets/javascripts/discourse/routes/user-notifications-edits.js.es6 similarity index 100% rename from app/assets/javascripts/discourse/routes/user-activity-edits.js.es6 rename to app/assets/javascripts/discourse/routes/user-notifications-edits.js.es6 diff --git a/app/assets/javascripts/discourse/routes/user-notifications-index.js.es6 b/app/assets/javascripts/discourse/routes/user-notifications-index.js.es6 new file mode 100644 index 000000000..7fb891d6b --- /dev/null +++ b/app/assets/javascripts/discourse/routes/user-notifications-index.js.es6 @@ -0,0 +1,5 @@ +export default Discourse.Route.extend({ + renderTemplate() { + this.render("user/notifications-index"); + } +}); diff --git a/app/assets/javascripts/discourse/routes/user-activity-likes-received.js.es6 b/app/assets/javascripts/discourse/routes/user-notifications-likes-received.js.es6 similarity index 77% rename from app/assets/javascripts/discourse/routes/user-activity-likes-received.js.es6 rename to app/assets/javascripts/discourse/routes/user-notifications-likes-received.js.es6 index a53300564..7206e7705 100644 --- a/app/assets/javascripts/discourse/routes/user-activity-likes-received.js.es6 +++ b/app/assets/javascripts/discourse/routes/user-notifications-likes-received.js.es6 @@ -2,5 +2,5 @@ import UserActivityStreamRoute from "discourse/routes/user-activity-stream"; import UserAction from "discourse/models/user-action"; export default UserActivityStreamRoute.extend({ - userActionType: UserAction.TYPES["likes_received"] + userActionType: UserAction.TYPES["likes_received"], }); diff --git a/app/assets/javascripts/discourse/routes/user-activity-mentions.js.es6 b/app/assets/javascripts/discourse/routes/user-notifications-mentions.js.es6 similarity index 100% rename from app/assets/javascripts/discourse/routes/user-activity-mentions.js.es6 rename to app/assets/javascripts/discourse/routes/user-notifications-mentions.js.es6 diff --git a/app/assets/javascripts/discourse/routes/user-activity-posts.js.es6 b/app/assets/javascripts/discourse/routes/user-notifications-responses.js.es6 similarity index 80% rename from app/assets/javascripts/discourse/routes/user-activity-posts.js.es6 rename to app/assets/javascripts/discourse/routes/user-notifications-responses.js.es6 index 62607c9a1..28e11587f 100644 --- a/app/assets/javascripts/discourse/routes/user-activity-posts.js.es6 +++ b/app/assets/javascripts/discourse/routes/user-notifications-responses.js.es6 @@ -2,5 +2,5 @@ import UserActivityStreamRoute from "discourse/routes/user-activity-stream"; import UserAction from "discourse/models/user-action"; export default UserActivityStreamRoute.extend({ - userActionType: UserAction.TYPES["posts"] + userActionType: UserAction.TYPES["replies"] }); diff --git a/app/assets/javascripts/discourse/routes/user-notifications.js.es6 b/app/assets/javascripts/discourse/routes/user-notifications.js.es6 index 7c2384db0..cfb91acdb 100644 --- a/app/assets/javascripts/discourse/routes/user-notifications.js.es6 +++ b/app/assets/javascripts/discourse/routes/user-notifications.js.es6 @@ -1,6 +1,11 @@ import ViewingActionType from "discourse/mixins/viewing-action-type"; export default Discourse.Route.extend(ViewingActionType, { + + renderTemplate() { + this.render('user/notifications'); + }, + actions: { didTransition() { this.controllerFor("user-notifications")._showFooter(); diff --git a/app/assets/javascripts/discourse/routes/user-private-messages-group.js.es6 b/app/assets/javascripts/discourse/routes/user-private-messages-group.js.es6 index 173641b1d..7e962ee86 100644 --- a/app/assets/javascripts/discourse/routes/user-private-messages-group.js.es6 +++ b/app/assets/javascripts/discourse/routes/user-private-messages-group.js.es6 @@ -1,5 +1,5 @@ import Group from 'discourse/models/group'; -import createPMRoute from "discourse/routes/build-user-topic-list-route"; +import createPMRoute from "discourse/routes/build-private-messages-route"; export default createPMRoute('groups', 'private-messages-groups').extend({ model(params) { @@ -13,13 +13,13 @@ export default createPMRoute('groups', 'private-messages-groups').extend({ const groupName = _.last(model.get("filter").split('/')); Group.findAll().then(groups => { const group = _.first(groups.filterBy("name", groupName)); - this.controllerFor("user-topics-list").set("group", group); + this.controllerFor("user-private-messages").set("group", group); }); }, setupController(controller, model) { this._super.apply(this, arguments); const group = _.last(model.get("filter").split('/')); - this.controllerFor("user").set("groupFilter", group); + this.controllerFor("userPrivateMessages").set("groupFilter", group); } }); diff --git a/app/assets/javascripts/discourse/routes/user-private-messages-index.js.es6 b/app/assets/javascripts/discourse/routes/user-private-messages-index.js.es6 index 5265bfb67..d1e2bf3c2 100644 --- a/app/assets/javascripts/discourse/routes/user-private-messages-index.js.es6 +++ b/app/assets/javascripts/discourse/routes/user-private-messages-index.js.es6 @@ -1,3 +1,3 @@ -import createPMRoute from "discourse/routes/build-user-topic-list-route"; +import createPMRoute from "discourse/routes/build-private-messages-route"; export default createPMRoute('index', 'private-messages'); diff --git a/app/assets/javascripts/discourse/routes/user-private-messages-mine.js.es6 b/app/assets/javascripts/discourse/routes/user-private-messages-mine.js.es6 index 47db69992..b2cf029e5 100644 --- a/app/assets/javascripts/discourse/routes/user-private-messages-mine.js.es6 +++ b/app/assets/javascripts/discourse/routes/user-private-messages-mine.js.es6 @@ -1,3 +1,3 @@ -import createPMRoute from "discourse/routes/build-user-topic-list-route"; +import createPMRoute from "discourse/routes/build-private-messages-route"; export default createPMRoute('mine', 'private-messages-sent'); diff --git a/app/assets/javascripts/discourse/routes/user-private-messages-unread.js.es6 b/app/assets/javascripts/discourse/routes/user-private-messages-unread.js.es6 index 19e2a5167..062ef7328 100644 --- a/app/assets/javascripts/discourse/routes/user-private-messages-unread.js.es6 +++ b/app/assets/javascripts/discourse/routes/user-private-messages-unread.js.es6 @@ -1,3 +1,3 @@ -import createPMRoute from "discourse/routes/build-user-topic-list-route"; +import createPMRoute from "discourse/routes/build-private-messages-route"; export default createPMRoute('unread', 'private-messages-unread'); diff --git a/app/assets/javascripts/discourse/routes/user-private-messages.js.es6 b/app/assets/javascripts/discourse/routes/user-private-messages.js.es6 index 328a304ca..b1074f80a 100644 --- a/app/assets/javascripts/discourse/routes/user-private-messages.js.es6 +++ b/app/assets/javascripts/discourse/routes/user-private-messages.js.es6 @@ -1,6 +1,35 @@ -import UserActivityRoute from 'discourse/routes/user-activity'; +import Draft from 'discourse/models/draft'; + +export default Discourse.Route.extend({ + + renderTemplate() { + this.render('user/messages'); + }, + + + model() { + return this.modelFor("user"); + }, + + setupController(controller, user) { + this._super(); + // Bring up a draft + const composerController = this.controllerFor("composer"); + controller.set("model", user); + if (this.currentUser) { + Draft.get("new_private_message").then(function(data) { + if (data.draft) { + composerController.open({ + draft: data.draft, + draftKey: "new_private_message", + ignoreIfChanged: true, + draftSequence: data.draft_sequence + }); + } + }); + } + }, -export default UserActivityRoute.extend({ actions: { willTransition: function() { this._super(); diff --git a/app/assets/javascripts/discourse/routes/user-statistics.js.es6 b/app/assets/javascripts/discourse/routes/user-statistics.js.es6 new file mode 100644 index 000000000..610a10489 --- /dev/null +++ b/app/assets/javascripts/discourse/routes/user-statistics.js.es6 @@ -0,0 +1,2 @@ +export default Discourse.Route.extend({ +}); diff --git a/app/assets/javascripts/discourse/templates/user-topics-list.hbs b/app/assets/javascripts/discourse/templates/user-topics-list.hbs index a2aae2de1..c94625730 100644 --- a/app/assets/javascripts/discourse/templates/user-topics-list.hbs +++ b/app/assets/javascripts/discourse/templates/user-topics-list.hbs @@ -1,11 +1,3 @@ -<div class="clearfix"> - {{#if group}} - {{group-notifications-button group=group}} - {{/if}} - {{#if showNewPM}} - {{d-button class="btn-primary pull-right new-private-message" action="composePrivateMessage" icon="envelope" label="user.new_private_message"}} - {{/if}} -</div> {{basic-topic-list topicList=model hideCategory=hideCategory diff --git a/app/assets/javascripts/discourse/templates/user/activity.hbs b/app/assets/javascripts/discourse/templates/user/activity.hbs index c24cd6895..2c3d03ab5 100644 --- a/app/assets/javascripts/discourse/templates/user/activity.hbs +++ b/app/assets/javascripts/discourse/templates/user/activity.hbs @@ -1 +1,42 @@ +<section class='user-navigation'> + <ul class='action-list nav-stacked'> + + <li class='no-glyph'> + {{#link-to 'userActivity.index'}}{{i18n 'user.filters.all'}}{{/link-to}} + </li> + + <li class='no-glyph'> + {{#link-to 'userActivity.topics'}}{{i18n 'user_action_groups.4'}}{{/link-to}} + </li> + + <li> + {{#link-to 'userActivity.replies'}} + <i class="glyph fa fa-reply"></i>{{i18n 'user_action_groups.5'}} + {{/link-to}} + </li> + + <li> + {{#link-to 'userActivity.likesGiven'}} + <i class="glyph fa fa-heart"></i>{{i18n 'user_action_groups.1'}} + {{/link-to}} + </li> + + <li> + {{#link-to 'userActivity.bookmarks'}} + <i class="glyph fa fa-bookmark"></i>{{i18n 'user_action_groups.3'}} + {{/link-to}} + </li> + </ul> + + {{#if viewingSelf}} + <div class='user-archive'> + {{d-button action="exportUserArchive" label="user.download_archive" icon="download"}} + </div> + {{/if}} + +</section> + +<section class='user-right'> {{outlet}} +</section> + diff --git a/app/assets/javascripts/discourse/templates/user/messages.hbs b/app/assets/javascripts/discourse/templates/user/messages.hbs new file mode 100644 index 000000000..5e404cb17 --- /dev/null +++ b/app/assets/javascripts/discourse/templates/user/messages.hbs @@ -0,0 +1,43 @@ +<section class='user-navigation'> + <ul class='action-list nav-stacked'> + <li {{bind-attr class=":noGlyph privateMessagesActive:active"}}> + {{#link-to 'userPrivateMessages.index' model}} + {{i18n 'user.messages.all'}} + {{#if model.hasPMs}}<span class='count'>({{model.private_messages_stats.all}})</span>{{/if}} + {{/link-to}} + </li> + <li {{bind-attr class=":noGlyph privateMessagesMineActive:active"}}> + {{#link-to 'userPrivateMessages.mine' model}} + {{i18n 'user.messages.mine'}} + {{#if model.hasStartedPMs}}<span class='count'>({{model.private_messages_stats.mine}})</span>{{/if}} + {{/link-to}} + </li> + <li {{bind-attr class=":noGlyph privateMessagesUnreadActive:active"}}> + {{#link-to 'userPrivateMessages.unread' model}} + {{i18n 'user.messages.unread'}} + {{#if model.hasUnreadPMs}}<span class='badge-notification unread-private-messages'>{{model.private_messages_stats.unread}}</span>{{/if}} + {{/link-to}} + </li> + {{#each groupPMStats as |group|}} + <li class="{{if group.active "active"}}"> + {{#link-to 'userPrivateMessages.group' group.name}} + <i class='glyph fa fa-group'></i> + {{group.name}} + {{/link-to}} + </li> + {{/each}} + </ul> + + {{d-button class="btn-primary new-private-message" action="composePrivateMessage" icon="envelope" label="user.new_private_message"}} +</section> + +<section class='user-right messages'> + +{{#if isGroup}} +<div class="clearfix"> + {{group-notifications-button group=group}} +</div> +{{/if}} + +{{outlet}} +</section> diff --git a/app/assets/javascripts/discourse/templates/user/notifications-index.hbs b/app/assets/javascripts/discourse/templates/user/notifications-index.hbs new file mode 100644 index 000000000..9ca0b0b99 --- /dev/null +++ b/app/assets/javascripts/discourse/templates/user/notifications-index.hbs @@ -0,0 +1,34 @@ +{{#if model.error}} + <div class="item error"> + {{#if model.forbidden}} + {{i18n 'errors.reasons.forbidden'}} + {{else}} + {{i18n 'errors.desc.unknown'}} + {{/if}} + </div> +{{/if}} + +{{#if showDismissButton}} + <div class='notification-buttons'> + <button title="{{i18n 'user.dismiss_notifications_tooltip'}}" id='dismiss-notifications-top' class='btn notifications-read' {{action "resetNew"}}>{{i18n 'user.dismiss_notifications'}}</button> + </div> +{{/if}} + +{{#each n in model}} + <div {{bind-attr class=":item :notification n.read::unread"}}> + {{notification-item notification=n}} + <span class="time"> + {{format-date n.created_at leaveAgo="true"}} + </span> + </div> +{{/each}} + +{{#conditional-loading-spinner condition=loading}} + {{#unless model.canLoadMore}} + {{#if showDismissButton}} + <div class='notification-buttons'> + <button title="{{i18n 'user.dismiss_notifications_tooltip'}}" id='dismiss-notifications' class='btn notifications-read' {{action "resetNew"}}>{{i18n 'user.dismiss_notifications'}}</button> + </div> + {{/if}} + {{/unless}} +{{/conditional-loading-spinner}} diff --git a/app/assets/javascripts/discourse/templates/user/notifications.hbs b/app/assets/javascripts/discourse/templates/user/notifications.hbs index 9ca0b0b99..ac8c6353e 100644 --- a/app/assets/javascripts/discourse/templates/user/notifications.hbs +++ b/app/assets/javascripts/discourse/templates/user/notifications.hbs @@ -1,34 +1,25 @@ -{{#if model.error}} - <div class="item error"> - {{#if model.forbidden}} - {{i18n 'errors.reasons.forbidden'}} - {{else}} - {{i18n 'errors.desc.unknown'}} - {{/if}} - </div> -{{/if}} -{{#if showDismissButton}} - <div class='notification-buttons'> - <button title="{{i18n 'user.dismiss_notifications_tooltip'}}" id='dismiss-notifications-top' class='btn notifications-read' {{action "resetNew"}}>{{i18n 'user.dismiss_notifications'}}</button> - </div> -{{/if}} +<section class='user-navigation'> + <ul class='action-list nav-stacked'> + <li class='no-glyph'> + {{#link-to 'userNotifications.index'}}{{i18n 'user.filters.all'}}{{/link-to}} + </li> + <li> + {{#link-to 'userNotifications.responses'}} + <i class="glyph fa fa-reply"></i> + {{i18n 'user_action_groups.6'}} + {{/link-to}} + </li> + <li> + {{#link-to 'userNotifications.likesReceived'}} + <i class="glyph fa fa-heart"></i>{{i18n 'user_action_groups.2'}} + {{/link-to}} + </li> + <li>{{#link-to 'userNotifications.mentions'}}<i class="glyph fa fa-at"></i>{{i18n 'user_action_groups.7'}}{{/link-to}}</li> + <li>{{#link-to 'userNotifications.edits'}}<i class="glyph fa fa-pencil"></i>{{i18n 'user_action_groups.11'}}{{/link-to}}</li> + </ul> +</section> -{{#each n in model}} - <div {{bind-attr class=":item :notification n.read::unread"}}> - {{notification-item notification=n}} - <span class="time"> - {{format-date n.created_at leaveAgo="true"}} - </span> - </div> -{{/each}} - -{{#conditional-loading-spinner condition=loading}} - {{#unless model.canLoadMore}} - {{#if showDismissButton}} - <div class='notification-buttons'> - <button title="{{i18n 'user.dismiss_notifications_tooltip'}}" id='dismiss-notifications' class='btn notifications-read' {{action "resetNew"}}>{{i18n 'user.dismiss_notifications'}}</button> - </div> - {{/if}} - {{/unless}} -{{/conditional-loading-spinner}} +<section class='user-right'> + {{outlet}} +</section> diff --git a/app/assets/javascripts/discourse/templates/user/preferences.hbs b/app/assets/javascripts/discourse/templates/user/preferences.hbs index 643757fc7..c18a9afe5 100644 --- a/app/assets/javascripts/discourse/templates/user/preferences.hbs +++ b/app/assets/javascripts/discourse/templates/user/preferences.hbs @@ -237,15 +237,8 @@ {{category-group categories=model.mutedCategories blacklist=selectedCategories}} </div> <div class="instructions">{{i18n 'user.muted_categories_instructions'}}</div> - </div> - - <div class="control-group topics"> - <label class="control-label">{{i18n 'categories.topics'}}</label> - {{#if siteSettings.automatically_unpin_topics}} - {{preference-checkbox labelKey="user.automatically_unpin_topics" checked=model.automatically_unpin_topics}} - {{/if}} - <div class="controls topic-controls"> - <a href="{{unbound model.mutedTopicsPath}}">{{i18n 'user.muted_topics_link'}}</a> + <div class="controls category-controls"> + <a href="{{unbound model.mutedTopicsPath}}">{{i18n 'user.muted_topics_link'}}</a> </div> </div> @@ -258,6 +251,13 @@ <div class="instructions">{{i18n 'user.muted_users_instructions'}}</div> </div> + {{#if siteSettings.automatically_unpin_topics}} + <div class="control-group topics"> + <label class="control-label">{{i18n 'categories.topics'}}</label> + {{preference-checkbox labelKey="user.automatically_unpin_topics" checked=model.automatically_unpin_topics}} + </div> + {{/if}} + {{plugin-outlet "user-custom-controls"}} <div class="control-group"> diff --git a/app/assets/javascripts/discourse/templates/user/user.hbs b/app/assets/javascripts/discourse/templates/user/user.hbs index ae0d50b06..db3678945 100644 --- a/app/assets/javascripts/discourse/templates/user/user.hbs +++ b/app/assets/javascripts/discourse/templates/user/user.hbs @@ -1,6 +1,7 @@ <div class="container{{if viewingSelf ' viewing-self'}}"> <section class='user-main'> <section {{bind-attr class="collapsedInfo :about model.profileBackground:has-background:no-background"}} style={{model.profileBackground}}> + {{#unless collapsedInfo}} <div class='staff-counters'> {{#if model.number_of_flags_given}} <div><span class="helpful-flags">{{model.number_of_flags_given}}</span> {{i18n 'user.staff_counters.flags_given'}}</div> @@ -26,6 +27,8 @@ <div><span class="warnings-received">{{model.number_of_warnings}}</span> {{i18n 'user.staff_counters.warnings_received'}}</div> {{/if}} </div> + {{/unless}} + <div class='profile-image'></div> <div class='details'> <div class='primary'> @@ -40,18 +43,9 @@ </a> </li> {{/if}} - {{#if viewingSelf}} - <li><a {{action "logout"}} href class='btn btn-danger'>{{fa-icon "sign-out"}}{{i18n 'user.log_out'}}</a></li> - {{/if}} {{#if currentUser.staff}} <li><a href={{model.adminPath}} class="btn">{{fa-icon "wrench"}}{{i18n 'admin.user.show_admin_profile'}}</a></li> {{/if}} - {{#if model.can_edit}} - <li>{{#link-to 'preferences' class="btn"}}{{fa-icon "cog"}}{{i18n 'user.preferences'}}{{/link-to}}</li> - {{/if}} - {{#if canInviteToForum}} - <li>{{#link-to 'userInvited' class="btn"}}{{fa-icon "user-plus"}}{{i18n 'user.invited.title'}}{{/link-to}}</li> - {{/if}} {{#if collapsedInfo}} {{#if viewingSelf}} <li><a {{action "expandProfile"}} href class="btn">{{fa-icon "angle-double-down"}}{{i18n 'user.expand_profile'}}</a></li> @@ -109,6 +103,8 @@ <div style='clear: both'></div> </div> + + {{#unless collapsedInfo}} <div class='secondary'> <dl> {{#if model.created_at}} @@ -135,10 +131,10 @@ {{/if}} </dd> {{/if}} - {{#if model.custom_groups}} - <dt>{{i18n 'groups.title' count=model.custom_groups.length}}</dt> + {{#if model.displayGroups}} + <dt>{{i18n 'groups.title' count=model.displayGroups.length}}</dt> <dd class='groups'> - {{#each group in model.custom_groups}} + {{#each group in model.displayGroups}} <span>{{#link-to 'group' group class="group-link"}}{{group.name}}{{/link-to}}</span> {{/each}} </dd> @@ -149,76 +145,34 @@ </dl> {{plugin-outlet "user-profile-secondary"}} </div> + {{/unless}} </section> - <section class='user-navigation'> - <ul class='action-list nav-stacked'> - {{activity-filter count=model.statsCountNonPM user=model userActionType=userActionType indexStream=indexStream}} - {{#each stat in model.statsExcludingPms}} - {{activity-filter content=stat user=model userActionType=userActionType indexStream=indexStream}} - {{/each}} - {{#if showBadges}} - {{#link-to 'user.badges' tagName="li"}} - {{#link-to 'user.badges'}} - <i class='glyph fa fa-certificate'></i> - {{i18n 'badges.title'}} - <span class='count'>({{model.badge_count}})</span> - {{/link-to}} + <ul class="user-nav"> + <li class='selected'>{{#link-to 'userActivity'}}{{i18n 'user.activity_stream'}}{{/link-to}}</li> + {{#if canSeeNotificationHistory}} + <li> + {{#link-to 'userNotifications'}} + {{fa-icon "comment" class="glyph"}} + {{i18n 'user.notifications'}} {{/link-to}} - {{/if}} - {{#if canSeeNotificationHistory}} - {{#link-to 'user.notifications' tagName="li"}} - {{#link-to 'user.notifications'}} - {{fa-icon "comment" class="glyph"}} - {{i18n 'user.notifications'}} - {{/link-to}} - {{/link-to}} - {{/if}} - </ul> - - {{#if canSeePrivateMessages}} - <h3>{{fa-icon "envelope"}} {{i18n 'user.private_messages'}}</h3> - <ul class='action-list nav-stacked'> - <li {{bind-attr class=":noGlyph privateMessagesActive:active"}}> - {{#link-to 'userPrivateMessages.index' model}} - {{i18n 'user.messages.all'}} - {{#if model.hasPMs}}<span class='count'>({{model.private_messages_stats.all}})</span>{{/if}} - {{/link-to}} - </li> - <li {{bind-attr class=":noGlyph privateMessagesMineActive:active"}}> - {{#link-to 'userPrivateMessages.mine' model}} - {{i18n 'user.messages.mine'}} - {{#if model.hasStartedPMs}}<span class='count'>({{model.private_messages_stats.mine}})</span>{{/if}} - {{/link-to}} - </li> - <li {{bind-attr class=":noGlyph privateMessagesUnreadActive:active"}}> - {{#link-to 'userPrivateMessages.unread' model}} - {{i18n 'user.messages.unread'}} - {{#if model.hasUnreadPMs}}<span class='badge-notification unread-private-messages'>{{model.private_messages_stats.unread}}</span>{{/if}} - {{/link-to}} - </li> - {{#each groupPMStats as |group|}} - <li class="{{if group.active "active"}}"> - {{#link-to 'userPrivateMessages.group' group.name}} - <i class='glyph fa fa-group'></i> - {{group.name}} - <span class='count'>({{group.count}})</span> - {{/link-to}} - </li> - {{/each}} - </ul> + </li> {{/if}} - - {{#if viewingSelf}} - <div class='user-archive'> - {{d-button action="exportUserArchive" label="user.download_archive" icon="download"}} - </div> + {{#if showPrivateMessages}} + <li>{{#link-to 'userPrivateMessages'}}{{fa-icon "envelope-o"}}{{i18n 'user.private_messages'}}{{/link-to}}</li> {{/if}} - </section> + {{#if canInviteToForum}} + <li>{{#link-to 'userInvited'}}{{fa-icon "user-plus"}}{{i18n 'user.invited.title'}}{{/link-to}}</li> + {{/if}} + {{#if showBadges}} + <li>{{#link-to 'user.badges'}}{{fa-icon "certificate"}}{{i18n 'badges.title'}}{{/link-to}}</li> + {{/if}} + {{#if model.can_edit}} + <li>{{#link-to 'preferences'}}{{fa-icon "cog"}}{{i18n 'user.preferences'}}{{/link-to}}</li> + {{/if}} + </ul> - <section class='user-right'> - {{outlet}} - </section> + {{outlet}} </section> </div> diff --git a/app/assets/stylesheets/common/base/user.scss b/app/assets/stylesheets/common/base/user.scss index 3eeb787e9..d532078bf 100644 --- a/app/assets/stylesheets/common/base/user.scss +++ b/app/assets/stylesheets/common/base/user.scss @@ -131,3 +131,34 @@ } } + +.user-nav { + width: 100%; + padding: 0; + margin: 0; + li { + padding: 0; + a { + color: dark-light-choose(scale-color($primary, $lightness: 40%), scale-color($secondary, $lightness: 40%)); + padding: 5px; + min-width: 90px; + display: inline-block; + text-align: center; + border-bottom: 3px solid transparent; + } + a.active, a:hover { + color: $primary; + border-bottom: 3px solid dark-light-choose(scale-color($primary, $lightness: 20%), scale-color($secondary, $lightness: 20%)); + } + display: inline-block; + text-decoration: none; + margin: 5px 0px; + .fa { + margin-right: 5px; + } + .fa.fa-comment { + margin-right: 2px; + } + } +} + diff --git a/app/assets/stylesheets/common/components/navs.css.scss b/app/assets/stylesheets/common/components/navs.css.scss index d29d05a3d..c10d68430 100644 --- a/app/assets/stylesheets/common/components/navs.css.scss +++ b/app/assets/stylesheets/common/components/navs.css.scss @@ -65,13 +65,13 @@ color: $primary; } } - .active > a, + .active > a, & li > a.active { color: $secondary; background-color: $quaternary; } - .active > a::after, + .active > a::after, & li > a.active::after { left: 90%; top: 33%; diff --git a/app/assets/stylesheets/desktop/user.scss b/app/assets/stylesheets/desktop/user.scss index 1cfdfab7e..1ae8d69e6 100644 --- a/app/assets/stylesheets/desktop/user.scss +++ b/app/assets/stylesheets/desktop/user.scss @@ -231,7 +231,6 @@ background-position: center center; background-size: cover; width: 100%; - margin-bottom: 10px; overflow: hidden; &.group { @@ -642,3 +641,15 @@ margin-top: 0; } } + + +.user-right .group-notification-menu { + float: right; + margin-bottom: 5px; +} +.user-right.messages .topic-list { + thead, th.views, td.views { + display: none; + } +} + diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 213442d11..3e510bb25 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -33,9 +33,10 @@ class UsersController < ApplicationController @user = fetch_user_from_params(include_inactive: current_user.try(:staff?)) user_serializer = UserSerializer.new(@user, scope: guardian, root: 'user') - if params[:stats].to_s == "false" - user_serializer.omit_stats = true - end + + # TODO remove this options from serializer + user_serializer.omit_stats = true + topic_id = params[:include_post_count_for].to_i if topic_id != 0 user_serializer.topic_post_count = {topic_id => Post.where(topic_id: topic_id, user_id: @user.id).count } diff --git a/app/models/user.rb b/app/models/user.rb index 1a3dbd47e..a9784dd6f 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -141,10 +141,6 @@ class User < ActiveRecord::Base SiteSetting.min_username_length.to_i..SiteSetting.max_username_length.to_i end - def custom_groups - groups.where(automatic: false, visible: true) - end - def self.username_available?(username) lower = username.downcase User.where(username_lower: lower).blank? && !SiteSetting.reserved_usernames.split("|").include?(username) diff --git a/app/serializers/basic_group_serializer.rb b/app/serializers/basic_group_serializer.rb index 925ac2365..d9a50ef12 100644 --- a/app/serializers/basic_group_serializer.rb +++ b/app/serializers/basic_group_serializer.rb @@ -11,7 +11,8 @@ class BasicGroupSerializer < ApplicationSerializer :title, :grant_trust_level, :incoming_email, - :notification_level + :notification_level, + :has_messages def include_incoming_email? scope.is_staff? diff --git a/app/serializers/user_serializer.rb b/app/serializers/user_serializer.rb index 476601b0f..06abd889a 100644 --- a/app/serializers/user_serializer.rb +++ b/app/serializers/user_serializer.rb @@ -70,7 +70,7 @@ class UserSerializer < BasicUserSerializer :automatically_unpin_topics has_one :invited_by, embed: :object, serializer: BasicUserSerializer - has_many :custom_groups, embed: :object, serializer: BasicGroupSerializer + has_many :groups, embed: :object, serializer: BasicGroupSerializer has_many :featured_user_badges, embed: :ids, serializer: UserBadgeSerializer, root: :user_badges has_one :card_badge, embed: :object, serializer: BadgeSerializer @@ -118,6 +118,14 @@ class UserSerializer < BasicUserSerializer ### ATTRIBUTES ### + def groups + if scope.is_admin? || object.id == scope.user.try(:id) + object.groups + else + object.groups.where(visible: true) + end + end + def include_email? object.id && object.id == scope.user.try(:id) end diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index b44e809b7..0eb1e2fa7 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -378,7 +378,6 @@ en: "6": "Responses" "7": "Mentions" "9": "Quotes" - "10": "Starred" "11": "Edits" "12": "Sent Items" "13": "Inbox" @@ -448,6 +447,7 @@ en: invited_by: "Invited By" trust_level: "Trust Level" notifications: "Notifications" + statistics: "Stats" desktop_notifications: label: "Desktop Notifications" not_supported: "Notifications are not supported on this browser. Sorry." diff --git a/config/routes.rb b/config/routes.rb index c74d77404..3f7f8c8f2 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -305,6 +305,7 @@ Discourse::Application.routes.draw do get "users/:username/activity/:filter" => "users#show", constraints: {username: USERNAME_ROUTE_FORMAT} get "users/:username/badges" => "users#show", constraints: {username: USERNAME_ROUTE_FORMAT} get "users/:username/notifications" => "users#show", constraints: {username: USERNAME_ROUTE_FORMAT} + get "users/:username/notifications/:filter" => "users#show", constraints: {username: USERNAME_ROUTE_FORMAT} get "users/:username/pending" => "users#show", constraints: {username: USERNAME_ROUTE_FORMAT} delete "users/:username" => "users#destroy", constraints: {username: USERNAME_ROUTE_FORMAT} # The external_id constraint is to allow periods to be used in the value without becoming part of the format. ie: foo.bar.json diff --git a/db/migrate/20151219045559_add_has_messages_to_groups.rb b/db/migrate/20151219045559_add_has_messages_to_groups.rb new file mode 100644 index 000000000..e4f4fcbef --- /dev/null +++ b/db/migrate/20151219045559_add_has_messages_to_groups.rb @@ -0,0 +1,15 @@ +class AddHasMessagesToGroups < ActiveRecord::Migration + def up + add_column :groups, :has_messages, :boolean, default: false, null: false + + execute <<SQL + UPDATE groups g SET has_messages = true + WHERE exists(SELECT group_id FROM topic_allowed_groups WHERE group_id = g.id) +SQL + + end + + def down + remove_column :groups, :has_messages + end +end diff --git a/lib/topic_creator.rb b/lib/topic_creator.rb index 79a2a92ef..7c6a8854b 100644 --- a/lib/topic_creator.rb +++ b/lib/topic_creator.rb @@ -186,6 +186,7 @@ class TopicCreator check_can_send_permission!(topic, group) topic.topic_allowed_groups.build(group_id: group.id) len += 1 + group.update_columns(:has_messages, true) unless group.has_messages end rollback_with!(topic, :target_group_not_found) unless len == names.length