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}}
+
+ {{/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) {