show card on preview panel.
refactor user and group cards refactor userLoading to loading add logic to display cards above mentions for fixed placements
This commit is contained in:
parent
efd744b049
commit
252dd32895
|
@ -64,6 +64,7 @@
|
||||||
//= require ./discourse/models/draft
|
//= require ./discourse/models/draft
|
||||||
//= require ./discourse/models/composer
|
//= require ./discourse/models/composer
|
||||||
//= require ./discourse/models/user-badge
|
//= require ./discourse/models/user-badge
|
||||||
|
//= require_tree ./discourse/lib
|
||||||
//= require_tree ./discourse/mixins
|
//= require_tree ./discourse/mixins
|
||||||
//= require ./discourse/models/invite
|
//= require ./discourse/models/invite
|
||||||
//= require ./discourse/controllers/discovery-sortable
|
//= require ./discourse/controllers/discovery-sortable
|
||||||
|
@ -87,7 +88,6 @@
|
||||||
//= require ./discourse/helpers/loading-spinner
|
//= require ./discourse/helpers/loading-spinner
|
||||||
//= require ./discourse/helpers/category-link
|
//= require ./discourse/helpers/category-link
|
||||||
//= require ./discourse/lib/export-result
|
//= require ./discourse/lib/export-result
|
||||||
//= require_tree ./discourse/lib
|
|
||||||
//= require ./discourse/mapping-router
|
//= require ./discourse/mapping-router
|
||||||
|
|
||||||
//= require_tree ./discourse/controllers
|
//= require_tree ./discourse/controllers
|
||||||
|
|
|
@ -260,7 +260,14 @@ export default Ember.Component.extend({
|
||||||
// disable clicking on links in the preview
|
// disable clicking on links in the preview
|
||||||
this.$('.d-editor-preview').on('click.preview', e => {
|
this.$('.d-editor-preview').on('click.preview', e => {
|
||||||
if (wantsNewWindow(e)) { return; }
|
if (wantsNewWindow(e)) { return; }
|
||||||
if ($(e.target).is("a")) {
|
const $target = $(e.target);
|
||||||
|
if ($target.is("a.mention")) {
|
||||||
|
this.appEvents.trigger('click.discourse-preview-user-card-mention', $target);
|
||||||
|
}
|
||||||
|
if ($target.is("a.mention-group")) {
|
||||||
|
this.appEvents.trigger('click.discourse-preview-group-card-mention-group', $target);
|
||||||
|
}
|
||||||
|
if ($target.is("a")) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,83 @@
|
||||||
|
import { setting } from 'discourse/lib/computed';
|
||||||
|
import { default as computed } from 'ember-addons/ember-computed-decorators';
|
||||||
|
import CardContentsBase from 'discourse/mixins/card-contents-base';
|
||||||
|
import CleansUp from 'discourse/mixins/cleans-up';
|
||||||
|
|
||||||
|
const maxMembersToDisplay = 10;
|
||||||
|
|
||||||
|
export default Ember.Component.extend(CardContentsBase, CleansUp, {
|
||||||
|
elementId: 'group-card',
|
||||||
|
triggeringLinkClass: 'mention-group',
|
||||||
|
classNames: ['no-bg'],
|
||||||
|
classNameBindings: ['visible:show', 'showBadges', 'hasCardBadgeImage', 'isFixed:fixed'],
|
||||||
|
allowBackgrounds: setting('allow_profile_backgrounds'),
|
||||||
|
showBadges: setting('enable_badges'),
|
||||||
|
|
||||||
|
postStream: Ember.computed.alias('topic.postStream'),
|
||||||
|
viewingTopic: Ember.computed.match('currentPath', /^topic\./),
|
||||||
|
|
||||||
|
showMoreMembers: Ember.computed.gt('moreMembersCount', 0),
|
||||||
|
|
||||||
|
group: null,
|
||||||
|
|
||||||
|
@computed('group.user_count', 'group.members.length')
|
||||||
|
moreMembersCount: (memberCount, maxMemberDisplay) => memberCount - maxMemberDisplay,
|
||||||
|
|
||||||
|
@computed('group')
|
||||||
|
groupPath(group) {
|
||||||
|
return `${Discourse.BaseUri}/groups/${group.name}`;
|
||||||
|
},
|
||||||
|
|
||||||
|
_showCallback(username, $target) {
|
||||||
|
this.store.find("group", username).then(group => {
|
||||||
|
this.setProperties({ group, visible: true });
|
||||||
|
this._positionCard($target);
|
||||||
|
if(!group.flair_url && !group.flair_bg_color) {
|
||||||
|
group.set('flair_url', 'fa-users');
|
||||||
|
}
|
||||||
|
group.set('limit', maxMembersToDisplay);
|
||||||
|
return group.findMembers();
|
||||||
|
}).catch(() => this._close()).finally(() => this.set('loading', null));
|
||||||
|
},
|
||||||
|
|
||||||
|
didInsertElement() {
|
||||||
|
this._super();
|
||||||
|
},
|
||||||
|
|
||||||
|
_close() {
|
||||||
|
this._super();
|
||||||
|
this.setProperties({
|
||||||
|
group: null,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
cleanUp() {
|
||||||
|
this._close();
|
||||||
|
},
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
close() {
|
||||||
|
this._close();
|
||||||
|
},
|
||||||
|
|
||||||
|
cancelFilter() {
|
||||||
|
const postStream = this.get('postStream');
|
||||||
|
postStream.cancelFilter();
|
||||||
|
postStream.refresh();
|
||||||
|
this._close();
|
||||||
|
},
|
||||||
|
|
||||||
|
composePrivateMessage(...args) {
|
||||||
|
this.sendAction('composePrivateMessage', ...args);
|
||||||
|
},
|
||||||
|
|
||||||
|
messageGroup() {
|
||||||
|
this.sendAction('createNewMessageViaParams', this.get('group.name'));
|
||||||
|
},
|
||||||
|
|
||||||
|
showGroup() {
|
||||||
|
this.sendAction('showGroup', this.get('group'));
|
||||||
|
this._close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
|
@ -1,50 +1,83 @@
|
||||||
import { wantsNewWindow } from 'discourse/lib/intercept-click';
|
|
||||||
import CleansUp from 'discourse/mixins/cleans-up';
|
|
||||||
import afterTransition from 'discourse/lib/after-transition';
|
|
||||||
import { setting } from 'discourse/lib/computed';
|
|
||||||
import { default as computed, observes } from 'ember-addons/ember-computed-decorators';
|
import { default as computed, observes } from 'ember-addons/ember-computed-decorators';
|
||||||
import DiscourseURL from 'discourse/lib/url';
|
|
||||||
import User from 'discourse/models/user';
|
import User from 'discourse/models/user';
|
||||||
import { userPath } from 'discourse/lib/url';
|
import { propertyNotEqual, setting } from 'discourse/lib/computed';
|
||||||
|
import { durationTiny } from 'discourse/lib/formatter';
|
||||||
|
import CanCheckEmails from 'discourse/mixins/can-check-emails';
|
||||||
|
import CardContentsBase from 'discourse/mixins/card-contents-base';
|
||||||
|
import CleansUp from 'discourse/mixins/cleans-up';
|
||||||
|
|
||||||
const clickOutsideEventName = "mousedown.outside-user-card";
|
export default Ember.Component.extend(CardContentsBase, CanCheckEmails, CleansUp, {
|
||||||
const clickDataExpand = "click.discourse-user-card";
|
|
||||||
const clickMention = "click.discourse-user-mention";
|
|
||||||
const groupClickMention = "click.discourse-group-mention";
|
|
||||||
const groupClickDataExpand = "click.discourse-group-card";
|
|
||||||
|
|
||||||
const maxMembersToDisplay = 10;
|
|
||||||
|
|
||||||
export default Ember.Component.extend(CleansUp, {
|
|
||||||
elementId: 'user-card',
|
elementId: 'user-card',
|
||||||
classNameBindings: ['visible:show', 'showBadges', 'hasCardBadgeImage', 'user.card_background::no-bg'],
|
triggeringLinkClass: 'mention',
|
||||||
|
classNameBindings: ['visible:show', 'showBadges', 'hasCardBadgeImage', 'user.card_background::no-bg', 'isFixed:fixed'],
|
||||||
allowBackgrounds: setting('allow_profile_backgrounds'),
|
allowBackgrounds: setting('allow_profile_backgrounds'),
|
||||||
showBadges: setting('enable_badges'),
|
showBadges: setting('enable_badges'),
|
||||||
|
|
||||||
postStream: Ember.computed.alias('topic.postStream'),
|
postStream: Ember.computed.alias('topic.postStream'),
|
||||||
viewingTopic: Ember.computed.match('currentPath', /^topic\./),
|
enoughPostsForFiltering: Ember.computed.gte('topicPostCount', 2),
|
||||||
|
showFilter: Ember.computed.and('viewingTopic', 'postStream.hasNoFilters', 'enoughPostsForFiltering'),
|
||||||
|
showName: propertyNotEqual('user.name', 'user.username'),
|
||||||
|
hasUserFilters: Ember.computed.gt('postStream.userFilters.length', 0),
|
||||||
|
isSuspended: Ember.computed.notEmpty('user.suspend_reason'),
|
||||||
|
showMoreBadges: Ember.computed.gt('moreBadgesCount', 0),
|
||||||
|
showDelete: Ember.computed.and("viewingAdmin", "showName", "user.canBeDeleted"),
|
||||||
|
linkWebsite: Ember.computed.not('user.isBasic'),
|
||||||
|
hasLocationOrWebsite: Ember.computed.or('user.location', 'user.website_name'),
|
||||||
|
showCheckEmail: Ember.computed.and('user.staged', 'canCheckEmails'),
|
||||||
|
|
||||||
visible: false,
|
|
||||||
user: null,
|
user: null,
|
||||||
group: null,
|
|
||||||
username: null,
|
|
||||||
avatar: null,
|
|
||||||
userLoading: null,
|
|
||||||
cardTarget: null,
|
|
||||||
post: null,
|
|
||||||
cardType: null,
|
|
||||||
|
|
||||||
// If inside a topic
|
// If inside a topic
|
||||||
topicPostCount: null,
|
topicPostCount: null,
|
||||||
|
|
||||||
@computed('cardType')
|
@computed('user.name')
|
||||||
isUserShown(cardType) {
|
nameFirst(name) {
|
||||||
return cardType === 'user';
|
return !this.siteSettings.prioritize_username_in_ux && name && name.trim().length > 0;
|
||||||
},
|
},
|
||||||
|
|
||||||
@computed('cardType')
|
@computed('username', 'topicPostCount')
|
||||||
isGroupShown(cardType) {
|
togglePostsLabel(username, count) {
|
||||||
return cardType === 'group';
|
return I18n.t("topic.filter_to", { username, count });
|
||||||
|
},
|
||||||
|
|
||||||
|
@computed('user.user_fields.@each.value')
|
||||||
|
publicUserFields() {
|
||||||
|
const siteUserFields = this.site.get('user_fields');
|
||||||
|
if (!Ember.isEmpty(siteUserFields)) {
|
||||||
|
const userFields = this.get('user.user_fields');
|
||||||
|
return siteUserFields.filterBy('show_on_user_card', true).sortBy('position').map(field => {
|
||||||
|
Ember.set(field, 'dasherized_name', field.get('name').dasherize());
|
||||||
|
const value = userFields ? userFields[field.get('id')] : null;
|
||||||
|
return Ember.isEmpty(value) ? null : Ember.Object.create({ value, field });
|
||||||
|
}).compact();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
@computed("user.trust_level")
|
||||||
|
removeNoFollow(trustLevel) {
|
||||||
|
return trustLevel > 2 && !this.siteSettings.tl3_links_no_follow;
|
||||||
|
},
|
||||||
|
|
||||||
|
@computed('user.badge_count', 'user.featured_user_badges.length')
|
||||||
|
moreBadgesCount: (badgeCount, badgeLength) => badgeCount - badgeLength,
|
||||||
|
|
||||||
|
@computed('user.time_read', 'user.recent_time_read')
|
||||||
|
showRecentTimeRead(timeRead, recentTimeRead) {
|
||||||
|
return timeRead !== recentTimeRead && recentTimeRead !== 0;
|
||||||
|
},
|
||||||
|
|
||||||
|
@computed('user.recent_time_read')
|
||||||
|
recentTimeRead(recentTimeReadSeconds) {
|
||||||
|
return durationTiny(recentTimeReadSeconds);
|
||||||
|
},
|
||||||
|
|
||||||
|
@computed('showRecentTimeRead', 'user.time_read', 'recentTimeRead')
|
||||||
|
timeReadTooltip(showRecent, timeRead, recentTimeRead) {
|
||||||
|
if (showRecent) {
|
||||||
|
return I18n.t('time_read_recently_tooltip', {time_read: durationTiny(timeRead), recent_time_read: recentTimeRead});
|
||||||
|
} else {
|
||||||
|
return I18n.t('time_read_tooltip', {time_read: durationTiny(timeRead)});
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@observes('user.card_background')
|
@observes('user.card_background')
|
||||||
|
@ -62,180 +95,28 @@ export default Ember.Component.extend(CleansUp, {
|
||||||
@computed('user.card_badge.image')
|
@computed('user.card_badge.image')
|
||||||
hasCardBadgeImage: image => image && image.indexOf('fa-') !== 0,
|
hasCardBadgeImage: image => image && image.indexOf('fa-') !== 0,
|
||||||
|
|
||||||
_showUser(username, $target) {
|
_showCallback(username, $target) {
|
||||||
const args = { stats: false };
|
const args = { stats: false };
|
||||||
args.include_post_count_for = this.get('topic.id');
|
args.include_post_count_for = this.get('topic.id');
|
||||||
|
|
||||||
User.findByUsername(username, args).then(user => {
|
User.findByUsername(username, args).then(user => {
|
||||||
if (user.topic_post_count) {
|
if (user.topic_post_count) {
|
||||||
this.set('topicPostCount', user.topic_post_count[args.include_post_count_for]);
|
this.set('topicPostCount', user.topic_post_count[args.include_post_count_for]);
|
||||||
}
|
}
|
||||||
this.setProperties({ user, avatar: user, visible: true, cardType: 'user' });
|
|
||||||
|
|
||||||
this._positionCard($target);
|
this._positionCard($target);
|
||||||
}).catch(() => this._close()).finally(() => this.set('userLoading', null));
|
this.setProperties({ user, visible: true });
|
||||||
},
|
|
||||||
|
|
||||||
_showGroup(groupname, $target) {
|
}).catch(() => this._close()).finally(() => this.set('loading', null));
|
||||||
this.store.find("group", groupname).then(group => {
|
|
||||||
this.setProperties({ group, avatar: group, visible: true, cardType: 'group' });
|
|
||||||
this._positionCard($target);
|
|
||||||
if(!group.flair_url && !group.flair_bg_color) {
|
|
||||||
group.set('flair_url', 'fa-users');
|
|
||||||
}
|
|
||||||
group.set('limit', maxMembersToDisplay);
|
|
||||||
return group.findMembers();
|
|
||||||
}).catch(() => this._close()).finally(() => this.set('userLoading', null));
|
|
||||||
},
|
|
||||||
|
|
||||||
_show(username, $target, userCardType) {
|
|
||||||
// No user card for anon
|
|
||||||
if (this.siteSettings.hide_user_profiles_from_public && !this.currentUser) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
username = Ember.Handlebars.Utils.escapeExpression(username.toString());
|
|
||||||
|
|
||||||
// Don't show on mobile
|
|
||||||
if (this.site.mobileView) {
|
|
||||||
DiscourseURL.routeTo(userPath(username));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const currentUsername = this.get('username');
|
|
||||||
if (username === currentUsername && this.get('userLoading') === username) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const postId = $target.parents('article').data('post-id');
|
|
||||||
|
|
||||||
const wasVisible = this.get('visible');
|
|
||||||
const previousTarget = this.get('cardTarget');
|
|
||||||
const target = $target[0];
|
|
||||||
if (wasVisible) {
|
|
||||||
this._close();
|
|
||||||
if (target === previousTarget) { return; }
|
|
||||||
}
|
|
||||||
|
|
||||||
const post = this.get('viewingTopic') && postId ? this.get('postStream').findLoadedPost(postId) : null;
|
|
||||||
this.setProperties({ username, userLoading: username, cardTarget: target, post });
|
|
||||||
|
|
||||||
if(userCardType === 'group') {
|
|
||||||
this._showGroup(username, $target);
|
|
||||||
}
|
|
||||||
else if(userCardType === 'user') {
|
|
||||||
this._showUser(username, $target);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
return false;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
didInsertElement() {
|
didInsertElement() {
|
||||||
this._super();
|
this._super();
|
||||||
afterTransition(this.$(), this._hide.bind(this));
|
|
||||||
|
|
||||||
$('html').off(clickOutsideEventName)
|
|
||||||
.on(clickOutsideEventName, (e) => {
|
|
||||||
if (this.get('visible')) {
|
|
||||||
const $target = $(e.target);
|
|
||||||
if ($target.closest('[data-user-card]').data('userCard') ||
|
|
||||||
$target.closest('a.mention').length > 0 ||
|
|
||||||
$target.closest('#user-card').length > 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this._close();
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
|
|
||||||
$('#main-outlet').on(clickDataExpand, '[data-user-card]', (e) => {
|
|
||||||
if (wantsNewWindow(e)) { return; }
|
|
||||||
const $target = $(e.currentTarget);
|
|
||||||
return this._show($target.data('user-card'), $target, 'user');
|
|
||||||
});
|
|
||||||
|
|
||||||
$('#main-outlet').on(clickMention, 'a.mention', (e) => {
|
|
||||||
if (wantsNewWindow(e)) { return; }
|
|
||||||
const $target = $(e.target);
|
|
||||||
return this._show($target.text().replace(/^@/, ''), $target, 'user');
|
|
||||||
});
|
|
||||||
|
|
||||||
$('#main-outlet').on(groupClickDataExpand, '[data-group-card]', (e) => {
|
|
||||||
if (wantsNewWindow(e)) { return; }
|
|
||||||
const $target = $(e.currentTarget);
|
|
||||||
return this._show($target.data('group-card'), $target, 'group');
|
|
||||||
});
|
|
||||||
|
|
||||||
$('#main-outlet').on(groupClickMention, 'a.mention-group', (e) => {
|
|
||||||
if (wantsNewWindow(e)) { return; }
|
|
||||||
const $target = $(e.target);
|
|
||||||
return this._show($target.text().replace(/^@/, ''), $target, 'group');
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
_positionCard(target) {
|
|
||||||
const rtl = ($('html').css('direction')) === 'rtl';
|
|
||||||
if (!target) { return; }
|
|
||||||
const width = this.$().width();
|
|
||||||
|
|
||||||
Ember.run.schedule('afterRender', () => {
|
|
||||||
if (target) {
|
|
||||||
let position = target.offset();
|
|
||||||
if (position) {
|
|
||||||
|
|
||||||
if (rtl) { // The site direction is rtl
|
|
||||||
position.right = $(window).width() - position.left + 10;
|
|
||||||
position.left = 'auto';
|
|
||||||
let overage = ($(window).width() - 50) - (position.right + width);
|
|
||||||
if (overage < 0) {
|
|
||||||
position.right += overage;
|
|
||||||
position.top += target.height() + 48;
|
|
||||||
}
|
|
||||||
} else { // The site direction is ltr
|
|
||||||
position.left += target.width() + 10;
|
|
||||||
|
|
||||||
let overage = ($(window).width() - 50) - (position.left + width);
|
|
||||||
if (overage < 0) {
|
|
||||||
position.left += overage;
|
|
||||||
position.top += target.height() + 48;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
position.top -= $('#main-outlet').offset().top;
|
|
||||||
this.$().css(position);
|
|
||||||
}
|
|
||||||
|
|
||||||
// After the card is shown, focus on the first link
|
|
||||||
//
|
|
||||||
// note: we DO NOT use afterRender here cause _positionCard may
|
|
||||||
// run afterwards, if we allowed this to happen the usercard
|
|
||||||
// may be offscreen and we may scroll all the way to it on focus
|
|
||||||
Ember.run.next(null, () => this.$('a:first').focus() );
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
_hide() {
|
|
||||||
if (!this.get('visible')) {
|
|
||||||
this.$().css({left: -9999, top: -9999});
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
_close() {
|
_close() {
|
||||||
|
this._super();
|
||||||
this.setProperties({
|
this.setProperties({
|
||||||
visible: false,
|
|
||||||
user: null,
|
user: null,
|
||||||
group: null,
|
|
||||||
username: null,
|
|
||||||
avatar: null,
|
|
||||||
userLoading: null,
|
|
||||||
cardTarget: null,
|
|
||||||
post: null,
|
|
||||||
topicPostCount: null,
|
topicPostCount: null,
|
||||||
cardType: null
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -243,20 +124,6 @@ export default Ember.Component.extend(CleansUp, {
|
||||||
this._close();
|
this._close();
|
||||||
},
|
},
|
||||||
|
|
||||||
keyUp(e) {
|
|
||||||
if (e.keyCode === 27) { // ESC
|
|
||||||
const target = this.get('cardTarget');
|
|
||||||
this._close();
|
|
||||||
target.focus();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
willDestroyElement() {
|
|
||||||
this._super();
|
|
||||||
$('html').off(clickOutsideEventName);
|
|
||||||
$('#main').off(clickDataExpand).off(clickMention).off(groupClickMention).off(groupClickDataExpand);
|
|
||||||
},
|
|
||||||
|
|
||||||
actions: {
|
actions: {
|
||||||
close() {
|
close() {
|
||||||
this._close();
|
this._close();
|
||||||
|
@ -273,10 +140,6 @@ export default Ember.Component.extend(CleansUp, {
|
||||||
this.sendAction('composePrivateMessage', ...args);
|
this.sendAction('composePrivateMessage', ...args);
|
||||||
},
|
},
|
||||||
|
|
||||||
messageGroup() {
|
|
||||||
this.sendAction('createNewMessageViaParams', this.get('group.name'));
|
|
||||||
},
|
|
||||||
|
|
||||||
togglePosts() {
|
togglePosts() {
|
||||||
this.sendAction('togglePosts', this.get('user'));
|
this.sendAction('togglePosts', this.get('user'));
|
||||||
this._close();
|
this._close();
|
||||||
|
@ -291,11 +154,6 @@ export default Ember.Component.extend(CleansUp, {
|
||||||
this._close();
|
this._close();
|
||||||
},
|
},
|
||||||
|
|
||||||
showGroup() {
|
|
||||||
this.sendAction('showGroup', this.get('group'));
|
|
||||||
this._close();
|
|
||||||
},
|
|
||||||
|
|
||||||
checkEmail(user) {
|
checkEmail(user) {
|
||||||
user.checkEmail();
|
user.checkEmail();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,32 +0,0 @@
|
||||||
import { default as computed } from 'ember-addons/ember-computed-decorators';
|
|
||||||
|
|
||||||
export default Ember.Component.extend({
|
|
||||||
group: null,
|
|
||||||
|
|
||||||
showMoreMembers: Ember.computed.gt('moreMembersCount', 0),
|
|
||||||
|
|
||||||
@computed('group.user_count', 'group.members.length')
|
|
||||||
moreMembersCount: (memberCount, maxMemberDisplay) => memberCount - maxMemberDisplay,
|
|
||||||
|
|
||||||
@computed('group')
|
|
||||||
groupPath(group) {
|
|
||||||
return `${Discourse.BaseUri}/groups/${group.name}`;
|
|
||||||
},
|
|
||||||
|
|
||||||
actions: {
|
|
||||||
close() {
|
|
||||||
this.sendAction('close');
|
|
||||||
},
|
|
||||||
|
|
||||||
messageGroup() {
|
|
||||||
this.sendAction('messageGroup');
|
|
||||||
},
|
|
||||||
|
|
||||||
showGroup() {
|
|
||||||
this.sendAction('showGroup');
|
|
||||||
},
|
|
||||||
showUser(user) {
|
|
||||||
this.sendAction('showUser', user);
|
|
||||||
},
|
|
||||||
}
|
|
||||||
});
|
|
|
@ -1,101 +0,0 @@
|
||||||
import { default as computed } from 'ember-addons/ember-computed-decorators';
|
|
||||||
import { propertyNotEqual, setting } from 'discourse/lib/computed';
|
|
||||||
import { durationTiny } from 'discourse/lib/formatter';
|
|
||||||
import CanCheckEmails from 'discourse/mixins/can-check-emails';
|
|
||||||
|
|
||||||
export default Ember.Component.extend(CanCheckEmails, {
|
|
||||||
|
|
||||||
allowBackgrounds: setting('allow_profile_backgrounds'),
|
|
||||||
showBadges: setting('enable_badges'),
|
|
||||||
|
|
||||||
enoughPostsForFiltering: Ember.computed.gte('topicPostCount', 2),
|
|
||||||
showFilter: Ember.computed.and('viewingTopic', 'postStream.hasNoFilters', 'enoughPostsForFiltering'),
|
|
||||||
showName: propertyNotEqual('user.name', 'user.username'),
|
|
||||||
hasUserFilters: Ember.computed.gt('postStream.userFilters.length', 0),
|
|
||||||
isSuspended: Ember.computed.notEmpty('user.suspend_reason'),
|
|
||||||
showMoreBadges: Ember.computed.gt('moreBadgesCount', 0),
|
|
||||||
showDelete: Ember.computed.and("viewingAdmin", "showName", "user.canBeDeleted"),
|
|
||||||
linkWebsite: Ember.computed.not('user.isBasic'),
|
|
||||||
hasLocationOrWebsite: Ember.computed.or('user.location', 'user.website_name'),
|
|
||||||
showCheckEmail: Ember.computed.and('user.staged', 'canCheckEmails'),
|
|
||||||
|
|
||||||
@computed('user.name')
|
|
||||||
nameFirst(name) {
|
|
||||||
return !this.siteSettings.prioritize_username_in_ux && name && name.trim().length > 0;
|
|
||||||
},
|
|
||||||
|
|
||||||
@computed('username', 'topicPostCount')
|
|
||||||
togglePostsLabel(username, count) {
|
|
||||||
return I18n.t("topic.filter_to", { username, count });
|
|
||||||
},
|
|
||||||
|
|
||||||
@computed('user.user_fields.@each.value')
|
|
||||||
publicUserFields() {
|
|
||||||
const siteUserFields = this.site.get('user_fields');
|
|
||||||
if (!Ember.isEmpty(siteUserFields)) {
|
|
||||||
const userFields = this.get('user.user_fields');
|
|
||||||
return siteUserFields.filterBy('show_on_user_card', true).sortBy('position').map(field => {
|
|
||||||
Ember.set(field, 'dasherized_name', field.get('name').dasherize());
|
|
||||||
const value = userFields ? userFields[field.get('id')] : null;
|
|
||||||
return Ember.isEmpty(value) ? null : Ember.Object.create({ value, field });
|
|
||||||
}).compact();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
@computed("user.trust_level")
|
|
||||||
removeNoFollow(trustLevel) {
|
|
||||||
return trustLevel > 2 && !this.siteSettings.tl3_links_no_follow;
|
|
||||||
},
|
|
||||||
|
|
||||||
@computed('user.badge_count', 'user.featured_user_badges.length')
|
|
||||||
moreBadgesCount: (badgeCount, badgeLength) => badgeCount - badgeLength,
|
|
||||||
|
|
||||||
@computed('user.time_read', 'user.recent_time_read')
|
|
||||||
showRecentTimeRead(timeRead, recentTimeRead) {
|
|
||||||
return timeRead !== recentTimeRead && recentTimeRead !== 0;
|
|
||||||
},
|
|
||||||
|
|
||||||
@computed('user.recent_time_read')
|
|
||||||
recentTimeRead(recentTimeReadSeconds) {
|
|
||||||
return durationTiny(recentTimeReadSeconds);
|
|
||||||
},
|
|
||||||
|
|
||||||
@computed('showRecentTimeRead', 'user.time_read', 'recentTimeRead')
|
|
||||||
timeReadTooltip(showRecent, timeRead, recentTimeRead) {
|
|
||||||
if (showRecent) {
|
|
||||||
return I18n.t('time_read_recently_tooltip', {time_read: durationTiny(timeRead), recent_time_read: recentTimeRead});
|
|
||||||
} else {
|
|
||||||
return I18n.t('time_read_tooltip', {time_read: durationTiny(timeRead)});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
actions: {
|
|
||||||
close() {
|
|
||||||
this.sendAction('close');
|
|
||||||
},
|
|
||||||
|
|
||||||
cancelFilter() {
|
|
||||||
this.sendAction('cancelFilter');
|
|
||||||
},
|
|
||||||
|
|
||||||
composePrivateMessage(...args) {
|
|
||||||
this.sendAction('composePrivateMessage', ...args);
|
|
||||||
},
|
|
||||||
|
|
||||||
togglePosts() {
|
|
||||||
this.sendAction('togglePosts');
|
|
||||||
},
|
|
||||||
|
|
||||||
deleteUser() {
|
|
||||||
this.sendAction('deleteUser');
|
|
||||||
},
|
|
||||||
|
|
||||||
showUser() {
|
|
||||||
this.sendAction('showUser');
|
|
||||||
},
|
|
||||||
|
|
||||||
checkEmail(user) {
|
|
||||||
this.sendAction('showUser', user);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
|
@ -0,0 +1,200 @@
|
||||||
|
import { wantsNewWindow } from 'discourse/lib/intercept-click';
|
||||||
|
import afterTransition from 'discourse/lib/after-transition';
|
||||||
|
import DiscourseURL from 'discourse/lib/url';
|
||||||
|
import { userPath } from 'discourse/lib/url';
|
||||||
|
|
||||||
|
export default Ember.Mixin.create({
|
||||||
|
elementId: null, //click detection added for data-{elementId}
|
||||||
|
triggeringLinkClass: null, //the <a> classname where this card should appear
|
||||||
|
_showCallback: null, //username, $target - load up data for when show is called, should call this._positionCard($target) when it's done.
|
||||||
|
|
||||||
|
postStream: Ember.computed.alias('topic.postStream'),
|
||||||
|
viewingTopic: Ember.computed.match('currentPath', /^topic\./),
|
||||||
|
|
||||||
|
visible: false,
|
||||||
|
username: null,
|
||||||
|
loading: null,
|
||||||
|
cardTarget: null,
|
||||||
|
post: null,
|
||||||
|
isFixed: false,
|
||||||
|
|
||||||
|
_show(username, $target) {
|
||||||
|
// No user card for anon
|
||||||
|
if (this.siteSettings.hide_user_profiles_from_public && !this.currentUser) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
username = Ember.Handlebars.Utils.escapeExpression(username.toString());
|
||||||
|
|
||||||
|
// Don't show on mobile
|
||||||
|
if (this.site.mobileView) {
|
||||||
|
DiscourseURL.routeTo(userPath(username));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentUsername = this.get('username');
|
||||||
|
if (username === currentUsername && this.get('loading') === username) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const postId = $target.parents('article').data('post-id');
|
||||||
|
|
||||||
|
const wasVisible = this.get('visible');
|
||||||
|
const previousTarget = this.get('cardTarget');
|
||||||
|
const target = $target[0];
|
||||||
|
if (wasVisible) {
|
||||||
|
this._close();
|
||||||
|
if (target === previousTarget) { return; }
|
||||||
|
}
|
||||||
|
|
||||||
|
const post = this.get('viewingTopic') && postId ? this.get('postStream').findLoadedPost(postId) : null;
|
||||||
|
this.setProperties({ username, loading: username, cardTarget: target, post });
|
||||||
|
|
||||||
|
this._showCallback(username, $target);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
|
||||||
|
didInsertElement() {
|
||||||
|
this._super();
|
||||||
|
afterTransition(this.$(), this._hide.bind(this));
|
||||||
|
const id = this.get('elementId');
|
||||||
|
const triggeringLinkClass = this.get('triggeringLinkClass');
|
||||||
|
const clickOutsideEventName = `mousedown.outside-${id}`;
|
||||||
|
const clickDataExpand = `click.discourse-${id}`;
|
||||||
|
const clickMention = `click.discourse-${id}-${triggeringLinkClass}`;
|
||||||
|
const previewClickEvent = `click.discourse-preview-${id}-${triggeringLinkClass}`;
|
||||||
|
|
||||||
|
this.setProperties({ clickOutsideEventName, clickDataExpand, clickMention, previewClickEvent });
|
||||||
|
|
||||||
|
$('html').off(clickOutsideEventName)
|
||||||
|
.on(clickOutsideEventName, (e) => {
|
||||||
|
if (this.get('visible')) {
|
||||||
|
const $target = $(e.target);
|
||||||
|
if ($target.closest(`[data-${id}]`).data(id) ||
|
||||||
|
$target.closest(`a.${triggeringLinkClass}`).length > 0 ||
|
||||||
|
$target.closest(`#${id}`).length > 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._close();
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#main-outlet').on(clickDataExpand, `[data-${id}]`, (e) => {
|
||||||
|
if (wantsNewWindow(e)) { return; }
|
||||||
|
const $target = $(e.currentTarget);
|
||||||
|
return this._show($target.data(id), $target);
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#main-outlet').on(clickMention, `a.${triggeringLinkClass}`, (e) => {
|
||||||
|
if (wantsNewWindow(e)) { return; }
|
||||||
|
const $target = $(e.target);
|
||||||
|
return this._show($target.text().replace(/^@/, ''), $target);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.appEvents.on(previewClickEvent, $target => {
|
||||||
|
this.set('isFixed', true);
|
||||||
|
return this._show($target.text().replace(/^@/, ''), $target);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
_positionCard(target) {
|
||||||
|
const rtl = ($('html').css('direction')) === 'rtl';
|
||||||
|
if (!target) { return; }
|
||||||
|
const width = this.$().width();
|
||||||
|
const height = 175;
|
||||||
|
const isFixed = this.get('isFixed');
|
||||||
|
|
||||||
|
let verticalAdjustments = 0;
|
||||||
|
|
||||||
|
Ember.run.schedule('afterRender', () => {
|
||||||
|
if (target) {
|
||||||
|
let position = target.offset();
|
||||||
|
if (position) {
|
||||||
|
position.bottom = 'unset';
|
||||||
|
|
||||||
|
if (rtl) { // The site direction is rtl
|
||||||
|
position.right = $(window).width() - position.left + 10;
|
||||||
|
position.left = 'auto';
|
||||||
|
let overage = ($(window).width() - 50) - (position.right + width);
|
||||||
|
if (overage < 0) {
|
||||||
|
position.right += overage;
|
||||||
|
position.top += target.height() + 48;
|
||||||
|
verticalAdjustments += target.height() + 48;
|
||||||
|
}
|
||||||
|
} else { // The site direction is ltr
|
||||||
|
position.left += target.width() + 10;
|
||||||
|
|
||||||
|
let overage = ($(window).width() - 50) - (position.left + width);
|
||||||
|
if (overage < 0) {
|
||||||
|
position.left += overage;
|
||||||
|
position.top += target.height() + 48;
|
||||||
|
verticalAdjustments += target.height() + 48;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
position.top -= $('#main-outlet').offset().top;
|
||||||
|
if(isFixed) {
|
||||||
|
position.top -= $('html').scrollTop();
|
||||||
|
//if content is fixed and will be cut off on the bottom, display it above...
|
||||||
|
if(position.top + height + verticalAdjustments > $(window).height() - 50) {
|
||||||
|
console.log("adjusting... by " + (height + verticalAdjustments + 48));
|
||||||
|
position.bottom = $(window).height() - (target.offset().top - $('html').scrollTop());
|
||||||
|
if(verticalAdjustments > 0) {
|
||||||
|
position.bottom += 48;
|
||||||
|
}
|
||||||
|
position.top = 'unset';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.$().css(position);
|
||||||
|
}
|
||||||
|
|
||||||
|
// After the card is shown, focus on the first link
|
||||||
|
//
|
||||||
|
// note: we DO NOT use afterRender here cause _positionCard may
|
||||||
|
// run afterwards, if we allowed this to happen the usercard
|
||||||
|
// may be offscreen and we may scroll all the way to it on focus
|
||||||
|
Ember.run.next(null, () => this.$('a:first').focus() );
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
_hide() {
|
||||||
|
if (!this.get('visible')) {
|
||||||
|
this.$().css({left: -9999, top: -9999});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_close() {
|
||||||
|
this.setProperties({
|
||||||
|
visible: false,
|
||||||
|
username: null,
|
||||||
|
loading: null,
|
||||||
|
cardTarget: null,
|
||||||
|
post: null,
|
||||||
|
isFixed: false
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
willDestroyElement() {
|
||||||
|
this._super();
|
||||||
|
const clickOutsideEventName = this.get('clickOutsideEventName');
|
||||||
|
const clickDataExpand = this.get('clickDataExpand');
|
||||||
|
const clickMention = this.get('clickMention');
|
||||||
|
const previewClickEvent = this.get('previewClickEvent');
|
||||||
|
$('html').off(clickOutsideEventName);
|
||||||
|
$('#main').off(clickDataExpand).off(clickMention);
|
||||||
|
this.appEvents.off(previewClickEvent);
|
||||||
|
},
|
||||||
|
|
||||||
|
keyUp(e) {
|
||||||
|
if (e.keyCode === 27) { // ESC
|
||||||
|
const target = this.get('cardTarget');
|
||||||
|
this._close();
|
||||||
|
target.focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
|
@ -0,0 +1,54 @@
|
||||||
|
{{#if visible}}
|
||||||
|
<div class="card-content">
|
||||||
|
<div class="group-card-avatar">
|
||||||
|
<a href={{groupPath}} {{action "showGroup"}} class="card-huge-avatar">
|
||||||
|
{{avatar-flair
|
||||||
|
flairURL=group.flair_url
|
||||||
|
flairBgColor=group.flair_bg_color
|
||||||
|
flairColor=group.flair_color
|
||||||
|
groupName=group.name}}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="names">
|
||||||
|
<span>
|
||||||
|
<h1 class="{{ group.name }}">
|
||||||
|
<a href={{groupPath}} {{action "showGroup"}}>{{ group.name }}</a>
|
||||||
|
</h1>
|
||||||
|
{{#if group.full_name}}
|
||||||
|
<h2 class='full-name'>{{group.full_name}}</h2>
|
||||||
|
{{else}}
|
||||||
|
<h2 class='username'>{{group.name}}</h2>
|
||||||
|
{{/if}}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="usercard-controls group-details-button">
|
||||||
|
{{group-membership-button
|
||||||
|
model=group
|
||||||
|
showLogin='showLogin'}}
|
||||||
|
|
||||||
|
{{#if group.messageable}}
|
||||||
|
{{d-button
|
||||||
|
action="messageGroup"
|
||||||
|
class="btn-primary group-message-button inline"
|
||||||
|
icon="envelope"
|
||||||
|
label="groups.message"}}
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="metadata">
|
||||||
|
<h3><a href={{groupPath}} {{action "showGroup"}}>{{ group.user_count }} {{i18n 'groups.user_count'}}</a></h3>
|
||||||
|
</div>
|
||||||
|
<div class="members metadata">
|
||||||
|
<span>
|
||||||
|
{{#each group.members as |user|}}
|
||||||
|
<a href={{user.path}} {{action "showUser" user}} class="card-tiny-avatar">{{bound-avatar user "tiny"}}</a>
|
||||||
|
{{/each}}
|
||||||
|
{{#if showMoreMembers}}
|
||||||
|
<a href={{groupPath}} {{action "showGroup"}}>+{{ moreMembersCount }} {{i18n "more"}}</a>
|
||||||
|
{{/if}}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
|
@ -1,33 +1,176 @@
|
||||||
{{#if visible}}
|
{{#if visible}}
|
||||||
<div class="card-content">
|
<div class="card-content">
|
||||||
{{#if isUserShown}}
|
<div class="user-card-avatar">
|
||||||
{{user-card-user-contents
|
<a href={{user.path}} {{action "showUser"}} class="card-huge-avatar">{{bound-avatar user "huge"}}</a>
|
||||||
model=model
|
{{#if user.primary_group_name}}
|
||||||
viewingTopic=viewingTopic
|
{{avatar-flair
|
||||||
topicPostCount=topicPostCount
|
flairURL=user.primary_group_flair_url
|
||||||
user=user
|
flairBgColor=user.primary_group_flair_bg_color
|
||||||
username=username
|
flairColor=user.primary_group_flair_color
|
||||||
userLoading=userLoading
|
groupName=user.primary_group_name}}
|
||||||
cardTarget=cardTarget
|
{{/if}}
|
||||||
avatar=avatar
|
{{plugin-outlet name="user-card-avatar-flair" args=(hash user=user) tagName='div'}}
|
||||||
postStream=postStream
|
</div>
|
||||||
close="close"
|
|
||||||
cancelFilter="cancelFilter"
|
<div class="names">
|
||||||
composePrivateMessage="composePrivateMessage"
|
<span>
|
||||||
togglePosts="togglePosts"
|
<h1 class="{{staff}} {{new_user}} {{if nameFirst "full-name" "username"}}">
|
||||||
deleteUser="deleteUser"
|
<a href={{user.path}} {{action "showUser"}}>{{if nameFirst user.name (format-username username)}} {{user-status user currentUser=currentUser}}</a>
|
||||||
showUser="showUser"
|
</h1>
|
||||||
checkEmail="checkEmail"
|
{{plugin-outlet name="user-card-after-username" args=(hash user=user) tagName=''}}
|
||||||
}}
|
|
||||||
|
{{#unless nameFirst}}
|
||||||
|
{{#if user.name}}
|
||||||
|
<h2 class='full-name'>{{user.name}}</h2>
|
||||||
|
{{/if}}
|
||||||
|
{{else}}
|
||||||
|
<h2 class='username'>{{username}}</h2>
|
||||||
|
{{/unless}}
|
||||||
|
|
||||||
|
{{#if user.title}}
|
||||||
|
<h2>{{user.title}}</h2>
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
{{#if user.staged}}
|
||||||
|
<h2 class="staged">{{i18n 'user.staged'}}</h2>
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
{{plugin-outlet name="user-card-post-names" args=(hash user=user) tagName='div'}}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ul class="usercard-controls">
|
||||||
|
{{#if user.can_send_private_message_to_user}}
|
||||||
|
<li class='compose-pm'>
|
||||||
|
{{d-button
|
||||||
|
class="btn-primary"
|
||||||
|
action=(action "composePrivateMessage" user post)
|
||||||
|
icon="envelope"
|
||||||
|
label="user.private_message"}}
|
||||||
|
</li>
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
{{#if showFilter}}
|
||||||
|
<li>
|
||||||
|
{{d-button
|
||||||
|
action=(action "togglePosts" user)
|
||||||
|
icon="filter"
|
||||||
|
translatedLabel=togglePostsLabel}}
|
||||||
|
</li>
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
{{#if hasUserFilters}}
|
||||||
|
<li>
|
||||||
|
{{d-button
|
||||||
|
action="cancelFilter"
|
||||||
|
icon="times"
|
||||||
|
label="topic.filters.cancel"}}
|
||||||
|
</li>
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
{{#if showDelete}}
|
||||||
|
<li>
|
||||||
|
{{d-button
|
||||||
|
class="btn-danger"
|
||||||
|
action=(action "deleteUser" user)
|
||||||
|
icon="exclamation-triangle"
|
||||||
|
label="admin.user.delete"}}
|
||||||
|
</li>
|
||||||
|
{{/if}}
|
||||||
|
</ul>
|
||||||
|
{{plugin-outlet
|
||||||
|
name="user-card-additional-controls"
|
||||||
|
args=(hash user=user close=(action "close"))
|
||||||
|
tagName=""}}
|
||||||
|
|
||||||
|
{{#if isSuspended}}
|
||||||
|
<div class='suspended'>
|
||||||
|
{{d-icon "ban"}}
|
||||||
|
<b>{{i18n 'user.suspended_notice' date=user.suspendedTillDate}}</b><br>
|
||||||
|
<b>{{i18n 'user.suspended_reason'}}</b> {{user.suspend_reason}}
|
||||||
|
</div>
|
||||||
|
{{else}}
|
||||||
|
{{#if user.bio_cooked}}<div class='bio'>{{text-overflow class="overflow" text=user.bio_excerpt}}</div>{{/if}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{#if isGroupShown}}
|
|
||||||
{{user-card-group-contents
|
{{#if user.card_badge}}
|
||||||
group=group
|
{{#link-to 'badges.show' user.card_badge class="card-badge" title=user.card_badge.name}}
|
||||||
close="close"
|
{{icon-or-image user.card_badge.image title=user.card_badge.name}}
|
||||||
messageGroup="messageGroup"
|
{{/link-to}}
|
||||||
showGroup="showGroup"
|
{{/if}}
|
||||||
showUser="showUser"
|
|
||||||
}}
|
{{#if hasLocationOrWebsite}}
|
||||||
|
<div class="location-and-website">
|
||||||
|
{{#if user.location}}
|
||||||
|
<span class='location'>{{d-icon "map-marker"}} <span>{{user.location}}</span></span>
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
{{#if user.website_name}}
|
||||||
|
<span class='website-name'>
|
||||||
|
{{d-icon "globe"}}
|
||||||
|
{{#if linkWebsite}}
|
||||||
|
<a href={{user.website}} rel={{unless removeNoFollow 'nofollow noopener'}} target="_blank">{{user.website_name}}</a>
|
||||||
|
{{else}}
|
||||||
|
<span title={{user.website}}>{{user.website_name}}</span>
|
||||||
|
{{/if}}
|
||||||
|
</span>
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
{{plugin-outlet name="user-card-location-and-website" args=(hash user=user)}}
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
{{#if user}}
|
||||||
|
<div class="metadata">
|
||||||
|
{{#if user.last_posted_at}}
|
||||||
|
<h3><span class='desc'>{{i18n 'last_post'}}</span> {{format-date user.last_posted_at leaveAgo="true"}}</h3>
|
||||||
|
{{/if}}
|
||||||
|
<h3><span class='desc'>{{i18n 'joined'}}</span> {{format-date user.created_at leaveAgo="true"}}</h3>
|
||||||
|
<h3 title="{{timeReadTooltip}}">
|
||||||
|
<span class='desc'>{{i18n 'time_read'}}</span>
|
||||||
|
{{format-duration user.time_read}}
|
||||||
|
{{#if showRecentTimeRead}}
|
||||||
|
<span>({{i18n 'time_read_recently' time_read=recentTimeRead}})</span>
|
||||||
|
{{/if}}
|
||||||
|
</h3>
|
||||||
|
{{#if showCheckEmail}}
|
||||||
|
<h3 class="email">
|
||||||
|
{{d-icon "envelope-o" title="user.email.title"}}
|
||||||
|
{{#if user.email}}
|
||||||
|
{{user.email}}
|
||||||
|
{{else}}
|
||||||
|
{{d-button action="checkEmail" actionParam=user icon="envelope-o" label="admin.users.check_email.text" class="btn-primary"}}
|
||||||
|
{{/if}}
|
||||||
|
</h3>
|
||||||
|
{{/if}}
|
||||||
|
{{plugin-outlet name="user-card-metadata" args=(hash user=user)}}
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
{{#if publicUserFields}}
|
||||||
|
<div class="public-user-fields">
|
||||||
|
{{#each publicUserFields as |uf|}}
|
||||||
|
{{#if uf.value}}
|
||||||
|
<div class="public-user-field {{uf.field.dasherized_name}}">
|
||||||
|
<span class="user-field-name">{{uf.field.name}}:</span>
|
||||||
|
<span class="user-field-value">{{uf.value}}</span>
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
{{/each}}
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
{{#if showBadges}}
|
||||||
|
<div class="badge-section">
|
||||||
|
{{#each user.featured_user_badges as |ub|}}
|
||||||
|
{{user-badge badge=ub.badge user=user}}
|
||||||
|
{{/each}}
|
||||||
|
{{#if showMoreBadges}}
|
||||||
|
{{#link-to 'user.badges' user class="btn more-user-badges"}}
|
||||||
|
{{i18n 'badges.more_badges' count=moreBadgesCount}}
|
||||||
|
{{/link-to}}
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
|
@ -1,50 +0,0 @@
|
||||||
<div class="group-card-avatar">
|
|
||||||
<a href={{groupPath}} {{action "showGroup"}} class="card-huge-avatar">
|
|
||||||
{{avatar-flair
|
|
||||||
flairURL=group.flair_url
|
|
||||||
flairBgColor=group.flair_bg_color
|
|
||||||
flairColor=group.flair_color
|
|
||||||
groupName=group.name}}
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="names">
|
|
||||||
<span>
|
|
||||||
<h1 class="{{ group.name }}">
|
|
||||||
<a href={{groupPath}} {{action "showGroup"}}>{{ group.name }}</a>
|
|
||||||
</h1>
|
|
||||||
{{#if group.full_name}}
|
|
||||||
<h2 class='full-name'>{{group.full_name}}</h2>
|
|
||||||
{{else}}
|
|
||||||
<h2 class='username'>{{group.name}}</h2>
|
|
||||||
{{/if}}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="usercard-controls group-details-button">
|
|
||||||
{{group-membership-button
|
|
||||||
model=group
|
|
||||||
showLogin='showLogin'}}
|
|
||||||
|
|
||||||
{{#if group.messageable}}
|
|
||||||
{{d-button
|
|
||||||
action="messageGroup"
|
|
||||||
class="btn-primary group-message-button inline"
|
|
||||||
icon="envelope"
|
|
||||||
label="groups.message"}}
|
|
||||||
{{/if}}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="metadata">
|
|
||||||
<h3><a href={{groupPath}} {{action "showGroup"}}>{{ group.user_count }} {{i18n 'groups.user_count'}}</a></h3>
|
|
||||||
</div>
|
|
||||||
<div class="members metadata">
|
|
||||||
<span>
|
|
||||||
{{#each group.members as |user|}}
|
|
||||||
<a href={{user.path}} {{action "showUser" user}} class="card-tiny-avatar">{{bound-avatar user "tiny"}}</a>
|
|
||||||
{{/each}}
|
|
||||||
{{#if showMoreMembers}}
|
|
||||||
<a href={{groupPath}} {{action "showGroup"}}>+{{ moreMembersCount }} {{i18n "more"}}</a>
|
|
||||||
{{/if}}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
|
@ -1,172 +0,0 @@
|
||||||
<div class="user-card-avatar">
|
|
||||||
<a href={{user.path}} {{action "showUser"}} class="card-huge-avatar">{{bound-avatar avatar "huge"}}</a>
|
|
||||||
{{#if user.primary_group_name}}
|
|
||||||
{{avatar-flair
|
|
||||||
flairURL=user.primary_group_flair_url
|
|
||||||
flairBgColor=user.primary_group_flair_bg_color
|
|
||||||
flairColor=user.primary_group_flair_color
|
|
||||||
groupName=user.primary_group_name}}
|
|
||||||
{{/if}}
|
|
||||||
{{plugin-outlet name="user-card-avatar-flair" args=(hash user=user) tagName='div'}}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="names">
|
|
||||||
<span>
|
|
||||||
<h1 class="{{staff}} {{new_user}} {{if nameFirst "full-name" "username"}}">
|
|
||||||
<a href={{user.path}} {{action "showUser"}}>{{if nameFirst user.name (format-username username)}} {{user-status user currentUser=currentUser}}</a>
|
|
||||||
</h1>
|
|
||||||
{{plugin-outlet name="user-card-after-username" args=(hash user=user) tagName=''}}
|
|
||||||
|
|
||||||
{{#unless nameFirst}}
|
|
||||||
{{#if user.name}}
|
|
||||||
<h2 class='full-name'>{{user.name}}</h2>
|
|
||||||
{{/if}}
|
|
||||||
{{else}}
|
|
||||||
<h2 class='username'>{{username}}</h2>
|
|
||||||
{{/unless}}
|
|
||||||
|
|
||||||
{{#if user.title}}
|
|
||||||
<h2>{{user.title}}</h2>
|
|
||||||
{{/if}}
|
|
||||||
|
|
||||||
{{#if user.staged}}
|
|
||||||
<h2 class="staged">{{i18n 'user.staged'}}</h2>
|
|
||||||
{{/if}}
|
|
||||||
|
|
||||||
{{plugin-outlet name="user-card-post-names" args=(hash user=user) tagName='div'}}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<ul class="usercard-controls">
|
|
||||||
{{#if user.can_send_private_message_to_user}}
|
|
||||||
<li class='compose-pm'>
|
|
||||||
{{d-button
|
|
||||||
class="btn-primary"
|
|
||||||
action=(action "composePrivateMessage" user post)
|
|
||||||
icon="envelope"
|
|
||||||
label="user.private_message"}}
|
|
||||||
</li>
|
|
||||||
{{/if}}
|
|
||||||
|
|
||||||
{{#if showFilter}}
|
|
||||||
<li>
|
|
||||||
{{d-button
|
|
||||||
action=(action "togglePosts" user)
|
|
||||||
icon="filter"
|
|
||||||
translatedLabel=togglePostsLabel}}
|
|
||||||
</li>
|
|
||||||
{{/if}}
|
|
||||||
|
|
||||||
{{#if hasUserFilters}}
|
|
||||||
<li>
|
|
||||||
{{d-button
|
|
||||||
action="cancelFilter"
|
|
||||||
icon="times"
|
|
||||||
label="topic.filters.cancel"}}
|
|
||||||
</li>
|
|
||||||
{{/if}}
|
|
||||||
|
|
||||||
{{#if showDelete}}
|
|
||||||
<li>
|
|
||||||
{{d-button
|
|
||||||
class="btn-danger"
|
|
||||||
action=(action "deleteUser" user)
|
|
||||||
icon="exclamation-triangle"
|
|
||||||
label="admin.user.delete"}}
|
|
||||||
</li>
|
|
||||||
{{/if}}
|
|
||||||
</ul>
|
|
||||||
{{plugin-outlet
|
|
||||||
name="user-card-additional-controls"
|
|
||||||
args=(hash user=user close=(action "close"))
|
|
||||||
tagName=""}}
|
|
||||||
|
|
||||||
{{#if isSuspended}}
|
|
||||||
<div class='suspended'>
|
|
||||||
{{d-icon "ban"}}
|
|
||||||
<b>{{i18n 'user.suspended_notice' date=user.suspendedTillDate}}</b><br>
|
|
||||||
<b>{{i18n 'user.suspended_reason'}}</b> {{user.suspend_reason}}
|
|
||||||
</div>
|
|
||||||
{{else}}
|
|
||||||
{{#if user.bio_cooked}}<div class='bio'>{{text-overflow class="overflow" text=user.bio_excerpt}}</div>{{/if}}
|
|
||||||
{{/if}}
|
|
||||||
|
|
||||||
{{#if user.card_badge}}
|
|
||||||
{{#link-to 'badges.show' user.card_badge class="card-badge" title=user.card_badge.name}}
|
|
||||||
{{icon-or-image user.card_badge.image title=user.card_badge.name}}
|
|
||||||
{{/link-to}}
|
|
||||||
{{/if}}
|
|
||||||
|
|
||||||
{{#if hasLocationOrWebsite}}
|
|
||||||
<div class="location-and-website">
|
|
||||||
{{#if user.location}}
|
|
||||||
<span class='location'>{{d-icon "map-marker"}} <span>{{user.location}}</span></span>
|
|
||||||
{{/if}}
|
|
||||||
|
|
||||||
{{#if user.website_name}}
|
|
||||||
<span class='website-name'>
|
|
||||||
{{d-icon "globe"}}
|
|
||||||
{{#if linkWebsite}}
|
|
||||||
<a href={{user.website}} rel={{unless removeNoFollow 'nofollow noopener'}} target="_blank">{{user.website_name}}</a>
|
|
||||||
{{else}}
|
|
||||||
<span title={{user.website}}>{{user.website_name}}</span>
|
|
||||||
{{/if}}
|
|
||||||
</span>
|
|
||||||
{{/if}}
|
|
||||||
|
|
||||||
{{plugin-outlet name="user-card-location-and-website" args=(hash user=user)}}
|
|
||||||
</div>
|
|
||||||
{{/if}}
|
|
||||||
|
|
||||||
{{#if user}}
|
|
||||||
<div class="metadata">
|
|
||||||
{{#if user.last_posted_at}}
|
|
||||||
<h3><span class='desc'>{{i18n 'last_post'}}</span> {{format-date user.last_posted_at leaveAgo="true"}}</h3>
|
|
||||||
{{/if}}
|
|
||||||
<h3><span class='desc'>{{i18n 'joined'}}</span> {{format-date user.created_at leaveAgo="true"}}</h3>
|
|
||||||
<h3 title="{{timeReadTooltip}}">
|
|
||||||
<span class='desc'>{{i18n 'time_read'}}</span>
|
|
||||||
{{format-duration user.time_read}}
|
|
||||||
{{#if showRecentTimeRead}}
|
|
||||||
<span>({{i18n 'time_read_recently' time_read=recentTimeRead}})</span>
|
|
||||||
{{/if}}
|
|
||||||
</h3>
|
|
||||||
{{#if showCheckEmail}}
|
|
||||||
<h3 class="email">
|
|
||||||
{{d-icon "envelope-o" title="user.email.title"}}
|
|
||||||
{{#if user.email}}
|
|
||||||
{{user.email}}
|
|
||||||
{{else}}
|
|
||||||
{{d-button action="checkEmail" actionParam=user icon="envelope-o" label="admin.users.check_email.text" class="btn-primary"}}
|
|
||||||
{{/if}}
|
|
||||||
</h3>
|
|
||||||
{{/if}}
|
|
||||||
{{plugin-outlet name="user-card-metadata" args=(hash user=user)}}
|
|
||||||
</div>
|
|
||||||
{{/if}}
|
|
||||||
|
|
||||||
{{#if publicUserFields}}
|
|
||||||
<div class="public-user-fields">
|
|
||||||
{{#each publicUserFields as |uf|}}
|
|
||||||
{{#if uf.value}}
|
|
||||||
<div class="public-user-field {{uf.field.dasherized_name}}">
|
|
||||||
<span class="user-field-name">{{uf.field.name}}:</span>
|
|
||||||
<span class="user-field-value">{{uf.value}}</span>
|
|
||||||
</div>
|
|
||||||
{{/if}}
|
|
||||||
{{/each}}
|
|
||||||
</div>
|
|
||||||
{{/if}}
|
|
||||||
|
|
||||||
{{#if showBadges}}
|
|
||||||
<div class="badge-section">
|
|
||||||
{{#each user.featured_user_badges as |ub|}}
|
|
||||||
{{user-badge badge=ub.badge user=user}}
|
|
||||||
{{/each}}
|
|
||||||
{{#if showMoreBadges}}
|
|
||||||
{{#link-to 'user.badges' user class="btn more-user-badges"}}
|
|
||||||
{{i18n 'badges.more_badges' count=moreBadgesCount}}
|
|
||||||
{{/link-to}}
|
|
||||||
{{/if}}
|
|
||||||
</div>
|
|
||||||
{{/if}}
|
|
|
@ -6,3 +6,12 @@
|
||||||
composePrivateMessage="composePrivateMessage"
|
composePrivateMessage="composePrivateMessage"
|
||||||
createNewMessageViaParams="createNewMessageViaParams"
|
createNewMessageViaParams="createNewMessageViaParams"
|
||||||
deleteUser="deleteUser"}}
|
deleteUser="deleteUser"}}
|
||||||
|
|
||||||
|
{{group-card-contents
|
||||||
|
currentPath=application.currentPath
|
||||||
|
topic=topic.model
|
||||||
|
showUser="showUser"
|
||||||
|
togglePosts="togglePosts"
|
||||||
|
composePrivateMessage="composePrivateMessage"
|
||||||
|
createNewMessageViaParams="createNewMessageViaParams"
|
||||||
|
deleteUser="deleteUser"}}
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
$user_card_primary: $primary;
|
$user_card_primary: $primary;
|
||||||
$user_card_background: $secondary;
|
$user_card_background: $secondary;
|
||||||
|
|
||||||
#user-card {
|
#user-card, #group-card {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: 500px;
|
width: 500px;
|
||||||
left: -9999px;
|
left: -9999px;
|
||||||
|
@ -27,6 +27,11 @@ $user_card_background: $secondary;
|
||||||
@include transform(scale(1));
|
@include transform(scale(1));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.fixed {
|
||||||
|
position: fixed;
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
|
||||||
.card-content {
|
.card-content {
|
||||||
padding: 12px 12px 0 12px;
|
padding: 12px 12px 0 12px;
|
||||||
background: rgba($user_card_background, .85);
|
background: rgba($user_card_background, .85);
|
||||||
|
|
|
@ -11,4 +11,17 @@ QUnit.test("card", assert => {
|
||||||
assert.ok(visible('#user-card'), 'card should appear');
|
assert.ok(visible('#user-card'), 'card should appear');
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
QUnit.test("group card", assert => {
|
||||||
|
visit('/t/301/1');
|
||||||
|
|
||||||
|
assert.ok(invisible('#group-card'), 'user card is invisible by default');
|
||||||
|
click('a.mention-group:first');
|
||||||
|
|
||||||
|
andThen(() => {
|
||||||
|
assert.ok(visible('#group-card'), 'card should appear');
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue