diff --git a/app/assets/javascripts/admin/controllers/admin-group.js.es6 b/app/assets/javascripts/admin/controllers/admin-group.js.es6 index e477ba4b0a1..fc11fbef13b 100644 --- a/app/assets/javascripts/admin/controllers/admin-group.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-group.js.es6 @@ -1,5 +1,6 @@ import { popupAjaxError } from 'discourse/lib/ajax-error'; import { propertyEqual } from 'discourse/lib/computed'; +import { escapeExpression } from 'discourse/lib/utilities'; export default Ember.Controller.extend({ needs: ['adminGroupsType'], @@ -35,6 +36,23 @@ export default Ember.Controller.extend({ ]; }.property(), + flairPreviewStyle: function() { + var style = ''; + if (this.get('model.flair_url')) { + style += 'background-image: url(' + escapeExpression(this.get('model.flair_url')) + '); '; + } + if (this.get('model.flairBackgroundHexColor')) { + style += 'background-color: #' + this.get('model.flairBackgroundHexColor') + ';'; + } + return style; + }.property('model.flair_url', 'model.flairBackgroundHexColor'), + + flairPreviewClasses: function() { + if (this.get('model.flairBackgroundHexColor')) { + return 'rounded'; + } + }.property('model.flairBackgroundHexColor'), + actions: { next() { if (this.get("showingLast")) { return; } diff --git a/app/assets/javascripts/admin/templates/group.hbs b/app/assets/javascripts/admin/templates/group.hbs index 2c2a1929a34..6154adef9ab 100644 --- a/app/assets/javascripts/admin/templates/group.hbs +++ b/app/assets/javascripts/admin/templates/group.hbs @@ -101,6 +101,38 @@ {{/if}} {{/unless}} + {{#unless model.automatic}} +
+
+
+ + {{text-field name="flair_url" value=model.flair_url placeholderKey="admin.groups.flair_url_placeholder"}} +
+ +
+ + {{text-field name="flair_bg_color" class="flair_bg_color" value=model.flair_bg_color placeholderKey="admin.groups.flair_bg_color_placeholder"}} +
+
+ + {{#if flairPreviewStyle}} +
+
+ +
+
+ +
+
+
+
+
+ {{/if}} + +
+
+ {{/unless}} +
{{#unless model.automatic}} diff --git a/app/assets/javascripts/discourse/lib/transform-post.js.es6 b/app/assets/javascripts/discourse/lib/transform-post.js.es6 index 18da1e5993a..975a86a2a9e 100644 --- a/app/assets/javascripts/discourse/lib/transform-post.js.es6 +++ b/app/assets/javascripts/discourse/lib/transform-post.js.es6 @@ -28,7 +28,9 @@ export function transformBasicPost(post) { isDeleted: post.deleted_at || post.user_deleted, deletedByAvatarTemplate: null, deletedByUsername: null, - primary_group_name: post.primary_group_name, + primaryGroupName: post.primary_group_name, + primaryGroupFlairUrl: post.primary_group_flair_url, + primaryGroupFlairBgColor: post.primary_group_flair_bg_color, wiki: post.wiki, firstPost: post.post_number === 1, post_number: post.post_number, diff --git a/app/assets/javascripts/discourse/models/group.js.es6 b/app/assets/javascripts/discourse/models/group.js.es6 index b6890e82631..0a89e4e060a 100644 --- a/app/assets/javascripts/discourse/models/group.js.es6 +++ b/app/assets/javascripts/discourse/models/group.js.es6 @@ -90,6 +90,11 @@ const Group = Discourse.Model.extend({ }); }, + @computed('flair_bg_color') + flairBackgroundHexColor() { + return this.get('flair_bg_color') ? this.get('flair_bg_color').replace(new RegExp("[^0-9a-fA-F]", "g"), "") : null; + }, + asJSON() { return { name: this.get('name'), @@ -101,6 +106,8 @@ const Group = Discourse.Model.extend({ primary_group: !!this.get('primary_group'), grant_trust_level: this.get('grant_trust_level'), incoming_email: this.get("incoming_email"), + flair_url: this.get('flair_url'), + flair_bg_color: this.get('flairBackgroundHexColor'), }; }, diff --git a/app/assets/javascripts/discourse/widgets/post.js.es6 b/app/assets/javascripts/discourse/widgets/post.js.es6 index a1e86d3bf14..f8f3a3d57d4 100644 --- a/app/assets/javascripts/discourse/widgets/post.js.es6 +++ b/app/assets/javascripts/discourse/widgets/post.js.es6 @@ -77,6 +77,29 @@ createWidget('reply-to-tab', { } }); +createWidget('post-avatar-flair', { + tagName: 'div.avatar-flair', + + title(attrs) { + return attrs.primaryGroupName; + }, + + buildClasses(attrs) { + return 'avatar-flair-' + attrs.primaryGroupName + (attrs.primaryGroupFlairBgColor ? ' rounded' : ''); + }, + + buildAttributes(attrs) { + var style = ''; + if (attrs.primaryGroupFlairUrl) { + style += 'background-image: url(' + attrs.primaryGroupFlairUrl + '); '; + } + if (attrs.primaryGroupFlairBgColor) { + style += 'background-color: #' + attrs.primaryGroupFlairBgColor + '; '; + } + return {style: style}; + } +}); + createWidget('post-avatar', { tagName: 'div.topic-avatar', @@ -93,11 +116,21 @@ createWidget('post-avatar', { template: attrs.avatar_template, username: attrs.username, url: attrs.usernameUrl, - className: 'main-avatar' + className: 'main-avatar', + flairUrl: attrs.primaryGroupFlairUrl, + flairBgColor: attrs.primaryGroupFlairBgColor }); } - return [body, h('div.poster-avatar-extra')]; + const result = [body]; + + if (attrs.primaryGroupFlairUrl || attrs.primaryGroupFlairBgColor) { + result.push(this.attach('post-avatar-flair', attrs)); + } + + result.push(h('div.poster-avatar-extra')); + + return result; } }); @@ -406,7 +439,7 @@ export default createWidget('post', { if (attrs.topicOwner) { classNames.push('topic-owner'); } if (attrs.hidden) { classNames.push('post-hidden'); } if (attrs.deleted) { classNames.push('deleted'); } - if (attrs.primary_group_name) { classNames.push(`group-${attrs.primary_group_name}`); } + if (attrs.primaryGroupName) { classNames.push(`group-${attrs.primaryGroupName}`); } if (attrs.wiki) { classNames.push(`wiki`); } if (attrs.isWhisper) { classNames.push('whisper'); } if (attrs.isModeratorAction || (attrs.isWarning && attrs.firstPost)) { diff --git a/app/assets/javascripts/discourse/widgets/poster-name.js.es6 b/app/assets/javascripts/discourse/widgets/poster-name.js.es6 index 4590da3424d..a560f6e2b0b 100644 --- a/app/assets/javascripts/discourse/widgets/poster-name.js.es6 +++ b/app/assets/javascripts/discourse/widgets/poster-name.js.es6 @@ -35,7 +35,7 @@ export default createWidget('poster-name', { if (attrs.moderator) { classNames.push('moderator'); } if (attrs.new_user) { classNames.push('new-user'); } - const primaryGroupName = attrs.primary_group_name; + const primaryGroupName = attrs.primaryGroupName; if (primaryGroupName && primaryGroupName.length) { classNames.push(primaryGroupName); } diff --git a/app/assets/stylesheets/common/admin/admin_base.scss b/app/assets/stylesheets/common/admin/admin_base.scss index a96ae3e0847..2f40c3cea12 100644 --- a/app/assets/stylesheets/common/admin/admin_base.scss +++ b/app/assets/stylesheets/common/admin/admin_base.scss @@ -684,6 +684,39 @@ section.details { width: 100%; border-color: dark-light-choose(scale-color($primary, $lightness: 75%), scale-color($secondary, $lightness: 25%)); } + .avatar-flair-preview { + position: relative; + width: 45px; + + .avatar-flair.demo { + top: 25px; + &.rounded { + top: 23px; + } + } + } + .form-horizontal { + .flair_inputs { + margin-top: 30px; + margin-bottom: 30px; + + .flair_left { + float: left; + width: 60%; + input[name=flair_url] { + width: 90%; + } + } + + .flair_right { + float: left; + margin-left: 30px; + } + } + } +} +.row.groups input[type='text'].flair_bg_color { + width: 200px; } // Customise area diff --git a/app/assets/stylesheets/common/base/topic-post.scss b/app/assets/stylesheets/common/base/topic-post.scss index 6028d278ab7..eff86a31826 100644 --- a/app/assets/stylesheets/common/base/topic-post.scss +++ b/app/assets/stylesheets/common/base/topic-post.scss @@ -153,6 +153,25 @@ aside.quote { } } +.topic-avatar .avatar-flair, .avatar-flair-preview .avatar-flair { + display: block; + background-size: 20px 20px; + background-repeat: no-repeat; + background-position: center; + width: 20px; + height: 20px; + position: absolute; + top: 40px; + right: -6px; + &.rounded { + background-size: 18px 18px; + border-radius: 12px; + width: 24px; + height: 24px; + top: 38px; + right: -8px; + } +} .topic-avatar .poster-avatar-extra { display: none; } diff --git a/app/assets/stylesheets/desktop/topic-post.scss b/app/assets/stylesheets/desktop/topic-post.scss index 09785266b16..b6d8cc5d59c 100644 --- a/app/assets/stylesheets/desktop/topic-post.scss +++ b/app/assets/stylesheets/desktop/topic-post.scss @@ -637,7 +637,7 @@ $topic-avatar-width: 45px; width: $topic-avatar-width; float: left; position: relative; - z-index: 2; + z-index: 3; } .gap { diff --git a/app/controllers/admin/groups_controller.rb b/app/controllers/admin/groups_controller.rb index 4b062cfc25f..df24d21bc3c 100644 --- a/app/controllers/admin/groups_controller.rb +++ b/app/controllers/admin/groups_controller.rb @@ -65,6 +65,9 @@ class Admin::GroupsController < Admin::AdminController title = params[:title] if params[:title].present? group.title = group.automatic ? nil : title + group.flair_url = params[:flair_url].presence + group.flair_bg_color = params[:flair_bg_color].presence + if group.save Group.reset_counters(group.id, :group_users) render_serialized(group, BasicGroupSerializer) diff --git a/app/models/group.rb b/app/models/group.rb index 3e7adbc30a8..58388e88e75 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -28,6 +28,7 @@ class Group < ActiveRecord::Base validates_uniqueness_of :name, case_sensitive: false validate :automatic_membership_email_domains_format_validator validate :incoming_email_validator + validates :flair_url, url: true AUTO_GROUPS = { :everyone => 0, diff --git a/app/serializers/basic_group_serializer.rb b/app/serializers/basic_group_serializer.rb index 6f52b98d3d4..972c54698bf 100644 --- a/app/serializers/basic_group_serializer.rb +++ b/app/serializers/basic_group_serializer.rb @@ -14,7 +14,9 @@ class BasicGroupSerializer < ApplicationSerializer :notification_level, :has_messages, :is_member, - :mentionable + :mentionable, + :flair_url, + :flair_bg_color def include_incoming_email? scope.is_staff? diff --git a/app/serializers/post_serializer.rb b/app/serializers/post_serializer.rb index 7171a3e5fc2..8ab6b75a000 100644 --- a/app/serializers/post_serializer.rb +++ b/app/serializers/post_serializer.rb @@ -34,6 +34,8 @@ class PostSerializer < BasicPostSerializer :category_id, :display_username, :primary_group_name, + :primary_group_flair_url, + :primary_group_flair_bg_color, :version, :can_edit, :can_delete, @@ -150,6 +152,16 @@ class PostSerializer < BasicPostSerializer end end + def primary_group_flair_url + return nil unless object.user && object.user.primary_group_id + object.user.primary_group.try(:flair_url) + end + + def primary_group_flair_bg_color + return nil unless object.user && object.user.primary_group_id + object.user.primary_group.try(:flair_bg_color) + end + def link_counts return @single_post_link_counts if @single_post_link_counts.present? diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 012a54c5d8f..727f005546f 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -2402,6 +2402,11 @@ en: add_owners: Add owners incoming_email: "Custom incoming email address" incoming_email_placeholder: "enter email address" + flair_url: "Avatar Flair URL" + flair_url_placeholder: "(Optional) Image URL" + flair_bg_color: "Avatar Flair Background Color" + flair_bg_color_placeholder: "(Optional) Hex color value" + flair_preview: "Preview" api: diff --git a/db/migrate/20160815210156_add_flair_url_to_groups.rb b/db/migrate/20160815210156_add_flair_url_to_groups.rb new file mode 100644 index 00000000000..19cd4c1d2d2 --- /dev/null +++ b/db/migrate/20160815210156_add_flair_url_to_groups.rb @@ -0,0 +1,6 @@ +class AddFlairUrlToGroups < ActiveRecord::Migration + def change + add_column :groups, :flair_url, :string + add_column :groups, :flair_bg_color, :string + end +end diff --git a/lib/validators/url_validator.rb b/lib/validators/url_validator.rb new file mode 100644 index 00000000000..c3295e63684 --- /dev/null +++ b/lib/validators/url_validator.rb @@ -0,0 +1,11 @@ +class UrlValidator < ActiveModel::EachValidator + def validate_each(record, attribute, value) + if value.present? + uri = URI.parse(value) rescue nil + + unless uri + record.errors[attribute] << (options[:message] || I18n.t('errors.messages.invalid')) + end + end + end +end diff --git a/spec/controllers/admin/groups_controller_spec.rb b/spec/controllers/admin/groups_controller_spec.rb index 1cb18f64e57..ce58ec0c35b 100644 --- a/spec/controllers/admin/groups_controller_spec.rb +++ b/spec/controllers/admin/groups_controller_spec.rb @@ -35,7 +35,9 @@ describe Admin::GroupsController do "notification_level"=>2, "has_messages"=>false, "is_member"=>true, - "mentionable"=>false + "mentionable"=>false, + "flair_url"=>nil, + "flair_bg_color"=>nil }]) end diff --git a/test/javascripts/widgets/poster-name-test.js.es6 b/test/javascripts/widgets/poster-name-test.js.es6 index 9c1bde62f46..d11678ec5c5 100644 --- a/test/javascripts/widgets/poster-name-test.js.es6 +++ b/test/javascripts/widgets/poster-name-test.js.es6 @@ -32,7 +32,7 @@ widgetTest('extra classes and glyphs', { admin: true, moderator: true, new_user: true, - primary_group_name: 'fish' + primaryGroupName: 'fish' }); }, test(assert) {