Merge branch 'master' into beta
This commit is contained in:
commit
c87673c651
52
Gemfile.lock
52
Gemfile.lock
@ -6,31 +6,31 @@ PATH
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
actionmailer (4.1.6)
|
||||
actionpack (= 4.1.6)
|
||||
actionview (= 4.1.6)
|
||||
actionmailer (4.1.7)
|
||||
actionpack (= 4.1.7)
|
||||
actionview (= 4.1.7)
|
||||
mail (~> 2.5, >= 2.5.4)
|
||||
actionpack (4.1.6)
|
||||
actionview (= 4.1.6)
|
||||
activesupport (= 4.1.6)
|
||||
actionpack (4.1.7)
|
||||
actionview (= 4.1.7)
|
||||
activesupport (= 4.1.7)
|
||||
rack (~> 1.5.2)
|
||||
rack-test (~> 0.6.2)
|
||||
actionpack-action_caching (1.1.1)
|
||||
actionpack (>= 4.0.0, < 5.0)
|
||||
actionview (4.1.6)
|
||||
activesupport (= 4.1.6)
|
||||
actionview (4.1.7)
|
||||
activesupport (= 4.1.7)
|
||||
builder (~> 3.1)
|
||||
erubis (~> 2.7.0)
|
||||
active_model_serializers (0.8.2)
|
||||
activemodel (>= 3.0)
|
||||
activemodel (4.1.6)
|
||||
activesupport (= 4.1.6)
|
||||
activemodel (4.1.7)
|
||||
activesupport (= 4.1.7)
|
||||
builder (~> 3.1)
|
||||
activerecord (4.1.6)
|
||||
activemodel (= 4.1.6)
|
||||
activesupport (= 4.1.6)
|
||||
activerecord (4.1.7)
|
||||
activemodel (= 4.1.7)
|
||||
activesupport (= 4.1.7)
|
||||
arel (~> 5.0.0)
|
||||
activesupport (4.1.6)
|
||||
activesupport (4.1.7)
|
||||
i18n (~> 0.6, >= 0.6.9)
|
||||
json (~> 1.7, >= 1.7.7)
|
||||
minitest (~> 5.1)
|
||||
@ -171,7 +171,7 @@ GEM
|
||||
method_source (0.8.2)
|
||||
mime-types (1.25.1)
|
||||
mini_portile (0.6.0)
|
||||
minitest (5.4.1)
|
||||
minitest (5.4.2)
|
||||
mocha (1.1.0)
|
||||
metaclass (~> 0.0.1)
|
||||
mock_redis (0.13.2)
|
||||
@ -255,21 +255,21 @@ GEM
|
||||
rack
|
||||
rack-test (0.6.2)
|
||||
rack (>= 1.0)
|
||||
rails (4.1.6)
|
||||
actionmailer (= 4.1.6)
|
||||
actionpack (= 4.1.6)
|
||||
actionview (= 4.1.6)
|
||||
activemodel (= 4.1.6)
|
||||
activerecord (= 4.1.6)
|
||||
activesupport (= 4.1.6)
|
||||
rails (4.1.7)
|
||||
actionmailer (= 4.1.7)
|
||||
actionpack (= 4.1.7)
|
||||
actionview (= 4.1.7)
|
||||
activemodel (= 4.1.7)
|
||||
activerecord (= 4.1.7)
|
||||
activesupport (= 4.1.7)
|
||||
bundler (>= 1.3.0, < 2.0)
|
||||
railties (= 4.1.6)
|
||||
railties (= 4.1.7)
|
||||
sprockets-rails (~> 2.0)
|
||||
rails-observers (0.1.2)
|
||||
activemodel (~> 4.0)
|
||||
railties (4.1.6)
|
||||
actionpack (= 4.1.6)
|
||||
activesupport (= 4.1.6)
|
||||
railties (4.1.7)
|
||||
actionpack (= 4.1.7)
|
||||
activesupport (= 4.1.7)
|
||||
rake (>= 0.8.7)
|
||||
thor (>= 0.18.1, < 2.0)
|
||||
raindrops (0.13.0)
|
||||
|
||||
@ -2,17 +2,27 @@ export default Ember.ObjectController.extend({
|
||||
viewMode: 'table',
|
||||
viewingTable: Em.computed.equal('viewMode', 'table'),
|
||||
viewingBarChart: Em.computed.equal('viewMode', 'barChart'),
|
||||
startDate: null,
|
||||
endDate: null,
|
||||
refreshing: false,
|
||||
|
||||
actions: {
|
||||
// Changes the current view mode to 'table'
|
||||
refreshReport: function() {
|
||||
var self = this;
|
||||
this.set('refreshing', true);
|
||||
Discourse.Report.find(this.get('type'), this.get('startDate'), this.get('endDate')).then(function(r) {
|
||||
self.set('model', r);
|
||||
}).finally(function() {
|
||||
self.set('refreshing', false);
|
||||
});
|
||||
},
|
||||
|
||||
viewAsTable: function() {
|
||||
this.set('viewMode', 'table');
|
||||
},
|
||||
|
||||
// Changes the current view mode to 'barChart'
|
||||
viewAsBarChart: function() {
|
||||
this.set('viewMode', 'barChart');
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
@ -90,11 +90,11 @@ export default Ember.ArrayController.extend(Discourse.Presence, {
|
||||
|
||||
@method refreshUsers
|
||||
**/
|
||||
refreshUsers: function() {
|
||||
refreshUsers: function(showEmails) {
|
||||
var adminUsersListController = this;
|
||||
adminUsersListController.set('loading', true);
|
||||
|
||||
Discourse.AdminUser.findAll(this.get('query'), { filter: this.get('username') }).then(function (result) {
|
||||
Discourse.AdminUser.findAll(this.get('query'), { filter: this.get('username'), show_emails: showEmails }).then(function (result) {
|
||||
adminUsersListController.set('content', result);
|
||||
adminUsersListController.set('loading', false);
|
||||
});
|
||||
@ -140,6 +140,10 @@ export default Ember.ArrayController.extend(Discourse.Presence, {
|
||||
bootbox.alert(message);
|
||||
controller.refreshUsers();
|
||||
});
|
||||
},
|
||||
|
||||
showEmails: function() {
|
||||
this.refreshUsers(true);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
@ -140,9 +140,12 @@ Discourse.Report = Discourse.Model.extend({
|
||||
});
|
||||
|
||||
Discourse.Report.reopenClass({
|
||||
find: function(type) {
|
||||
var model = Discourse.Report.create({type: type});
|
||||
Discourse.ajax("/admin/reports/" + type).then(function (json) {
|
||||
find: function(type, startDate, endDate) {
|
||||
|
||||
return Discourse.ajax("/admin/reports/" + type, {data: {
|
||||
start_date: startDate,
|
||||
end_date: endDate
|
||||
}}).then(function (json) {
|
||||
// Add a percent field to each tuple
|
||||
var maxY = 0;
|
||||
json.report.data.forEach(function (row) {
|
||||
@ -153,9 +156,9 @@ Discourse.Report.reopenClass({
|
||||
row.percentage = Math.round((row.y / maxY) * 100);
|
||||
});
|
||||
}
|
||||
var model = Discourse.Report.create({type: type});
|
||||
model.setProperties(json.report);
|
||||
model.set('loaded', true);
|
||||
return model;
|
||||
});
|
||||
return(model);
|
||||
}
|
||||
});
|
||||
|
||||
@ -9,5 +9,13 @@
|
||||
Discourse.AdminReportsRoute = Discourse.Route.extend({
|
||||
model: function(params) {
|
||||
return Discourse.Report.find(params.type);
|
||||
},
|
||||
|
||||
setupController: function(controller, model) {
|
||||
controller.setProperties({
|
||||
model: model,
|
||||
startDate: moment(model.get('start_date')).format('YYYY-MM-DD'),
|
||||
endDate: moment(model.get('end_date')).format('YYYY-MM-DD')
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@ -19,7 +19,7 @@
|
||||
</div>
|
||||
|
||||
{{#if loading}}
|
||||
<div class='spinner'></div>
|
||||
{{loading-spinner}}
|
||||
{{else}}
|
||||
{{#if showHtml}}
|
||||
{{{html_content}}}
|
||||
|
||||
@ -9,7 +9,7 @@
|
||||
|
||||
<div class="admin-container">
|
||||
{{#if loading}}
|
||||
<div class='spinner'></div>
|
||||
{{loading-spinner}}
|
||||
{{else}}
|
||||
{{#if length}}
|
||||
<table class='admin-flags'>
|
||||
@ -160,7 +160,7 @@
|
||||
</tbody>
|
||||
</table>
|
||||
{{#if view.loading}}
|
||||
<div class='spinner'></div>
|
||||
{{loading-spinner}}
|
||||
{{/if}}
|
||||
|
||||
{{else}}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
<p>{{i18n admin.logs.screened_emails.description}}</p>
|
||||
|
||||
{{#if loading}}
|
||||
<div class='spinner'></div>
|
||||
{{loading-spinner}}
|
||||
{{else}}
|
||||
{{#if model.length}}
|
||||
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
<br/>
|
||||
|
||||
{{#if loading}}
|
||||
<div class='spinner'></div>
|
||||
{{loading-spinner}}
|
||||
{{else}}
|
||||
{{#if model.length}}
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
<p>{{i18n admin.logs.screened_urls.description}}</p>
|
||||
|
||||
{{#if loading}}
|
||||
<div class='spinner'></div>
|
||||
{{loading-spinner}}
|
||||
{{else}}
|
||||
{{#if model.length}}
|
||||
|
||||
|
||||
@ -45,7 +45,7 @@
|
||||
|
||||
{{#if loading}}
|
||||
<br/>
|
||||
<div class='spinner'></div>
|
||||
{{loading-spinner}}
|
||||
{{else}}
|
||||
{{#if model.length}}
|
||||
{{view "staff-action-logs-list" content=controller}}
|
||||
|
||||
@ -1,14 +1,28 @@
|
||||
{{#if loaded}}
|
||||
<h3>{{title}}</h3>
|
||||
<h3>{{title}}</h3>
|
||||
|
||||
<button class='btn'
|
||||
{{action "viewAsTable"}}
|
||||
{{bind-attr disabled="viewingTable"}}>{{i18n admin.dashboard.reports.view_table}}</button>
|
||||
<div>
|
||||
{{i18n admin.dashboard.reports.start_date}} {{input type="date" value=startDate}}
|
||||
{{i18n admin.dashboard.reports.end_date}} {{input type="date" value=endDate}}
|
||||
<button {{action refreshReport}} class='btn btn-primary'>{{i18n admin.dashboard.reports.refresh_report}}</button>
|
||||
</div>
|
||||
|
||||
<button class='btn'
|
||||
{{action "viewAsBarChart"}}
|
||||
{{bind-attr disabled="viewingBarChart"}}>{{i18n admin.dashboard.reports.view_chart}}</button>
|
||||
<div class='view-options'>
|
||||
{{#if viewingTable}}
|
||||
{{i18n admin.dashboard.reports.view_table}}
|
||||
{{else}}
|
||||
<a href {{action "viewAsTable"}}>{{i18n admin.dashboard.reports.view_table}}</a>
|
||||
{{/if}}
|
||||
|
|
||||
{{#if viewingBarChart}}
|
||||
{{i18n admin.dashboard.reports.view_chart}}
|
||||
{{else}}
|
||||
<a href {{action "viewAsBarChart"}}>{{i18n admin.dashboard.reports.view_chart}}</a>
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
{{#if refreshing}}
|
||||
{{loading-spinner}}
|
||||
{{else}}
|
||||
<table class='table report'>
|
||||
<tr>
|
||||
<th>{{xaxis}}</th>
|
||||
@ -31,7 +45,4 @@
|
||||
</tr>
|
||||
{{/each}}
|
||||
</table>
|
||||
|
||||
{{else}}
|
||||
{{i18n loading}}
|
||||
{{/if}}
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
</div>
|
||||
|
||||
{{#if loading}}
|
||||
<div class='spinner'></div>
|
||||
{{loading-spinner}}
|
||||
{{else}}
|
||||
<div class='admin-container user-badges'>
|
||||
<h2>{{i18n admin.badges.grant_badge}}</h2>
|
||||
|
||||
@ -28,12 +28,17 @@
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
<h2>{{title}}</h2>
|
||||
|
||||
<br/>
|
||||
<div class="admin-title">
|
||||
<div class="pull-left">
|
||||
<h2>{{title}}</h2>
|
||||
</div>
|
||||
<div class="pull-right">
|
||||
<button {{action "showEmails"}} class="btn">{{i18n admin.users.show_emails}}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{#if loading}}
|
||||
<div class='spinner'></div>
|
||||
{{loading-spinner}}
|
||||
{{else}}
|
||||
{{#if model.length}}
|
||||
<table class='table'>
|
||||
@ -43,6 +48,7 @@
|
||||
{{/if}}
|
||||
<th> </th>
|
||||
<th>{{i18n username}}</th>
|
||||
<th>{{i18n email}}</th>
|
||||
<th>{{i18n admin.users.last_emailed}}</th>
|
||||
<th>{{i18n last_seen}}</th>
|
||||
<th>{{i18n admin.user.topics_entered}}</th>
|
||||
@ -53,7 +59,6 @@
|
||||
<th>{{i18n admin.users.approved}}</th>
|
||||
{{/if}}
|
||||
<th> </th>
|
||||
|
||||
</tr>
|
||||
|
||||
{{#each model}}
|
||||
@ -67,6 +72,7 @@
|
||||
{{/if}}
|
||||
<td>{{#link-to 'adminUser' this}}{{avatar this imageSize="small"}}{{/link-to}}</td>
|
||||
<td>{{#link-to 'adminUser' this}}{{unbound username}}{{/link-to}}</td>
|
||||
<td>{{{unbound email}}}</td>
|
||||
<td>{{{unbound last_emailed_age}}}</td>
|
||||
<td>{{{unbound last_seen_age}}}</td>
|
||||
<td>{{{unbound topics_entered}}}</td>
|
||||
|
||||
@ -1,12 +0,0 @@
|
||||
/**
|
||||
Displays a list of groups that a user belongs to.
|
||||
|
||||
@class Discourse.GroupsListComponent
|
||||
@extends Ember.Component
|
||||
@namespace Discourse
|
||||
@module Discourse
|
||||
**/
|
||||
export default Em.Component.extend({
|
||||
classNames: ['groups']
|
||||
});
|
||||
|
||||
@ -1,14 +1,9 @@
|
||||
export default Ember.Component.extend({
|
||||
tagName: 'a',
|
||||
attributeBindings: ['href'],
|
||||
attributeBindings: ['href','data-user-card'],
|
||||
classNames: ['trigger-user-card'],
|
||||
href: Em.computed.alias('post.usernameUrl'),
|
||||
|
||||
click: function(e) {
|
||||
this.appEvents.trigger('poster:expand', $(e.target));
|
||||
this.sendAction('action', this.get('post'));
|
||||
return false;
|
||||
},
|
||||
href: Em.computed.oneWay('post.usernameUrl'),
|
||||
"data-user-card": Em.computed.oneWay('post.username'),
|
||||
|
||||
render: function(buffer) {
|
||||
var avatar = Handlebars.helpers.avatar(this.get('post'), {hash: {imageSize: 'large'}});
|
||||
|
||||
@ -26,7 +26,7 @@ var PosterNameComponent = Em.Component.extend({
|
||||
linkClass += ' ' + primaryGroupName;
|
||||
}
|
||||
// Main link
|
||||
buffer.push("<span class='" + linkClass + "'><a href='" + url + "' data-auto-route='true'>" + username + "</a>");
|
||||
buffer.push("<span class='" + linkClass + "'><a href='" + url + "' data-auto-route='true' data-user-card='" + username + "'>" + username + "</a>");
|
||||
|
||||
// Add a glyph if we have one
|
||||
var glyph = this.posterGlyph(post);
|
||||
@ -38,7 +38,7 @@ var PosterNameComponent = Em.Component.extend({
|
||||
// Are we showing full names?
|
||||
if (name && this.get('displayNameOnPosts') && (this.sanitizeName(name) !== this.sanitizeName(username))) {
|
||||
name = Handlebars.Utils.escapeExpression(name);
|
||||
buffer.push("<span class='full-name'><a href='" + url + "' data-auto-route='true'>" + name + "</a></span>");
|
||||
buffer.push("<span class='full-name'><a href='" + url + "' data-auto-route='true' data-user-card='" + username + "'>" + name + "</a></span>");
|
||||
}
|
||||
|
||||
// User titles
|
||||
@ -59,20 +59,6 @@ var PosterNameComponent = Em.Component.extend({
|
||||
}
|
||||
},
|
||||
|
||||
click: function(e) {
|
||||
var $target = $(e.target),
|
||||
href = $target.attr('href'),
|
||||
url = this.get('post.usernameUrl');
|
||||
|
||||
if (!Em.isEmpty(href) && href !== url) {
|
||||
return true;
|
||||
} else {
|
||||
this.appEvents.trigger('poster:expand', $target);
|
||||
this.sendAction('expandAction', this.get('post'));
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
/**
|
||||
Overwrite this to give a user a custom font awesome glyph.
|
||||
|
||||
|
||||
@ -0,0 +1,9 @@
|
||||
export default Ember.Component.extend({
|
||||
_parse: function() {
|
||||
this.$().ellipsis();
|
||||
}.on('didInsertElement'),
|
||||
|
||||
render: function(buffer) {
|
||||
buffer.push(this.get('text'));
|
||||
}
|
||||
});
|
||||
@ -40,13 +40,11 @@ export default Ember.Component.extend({
|
||||
|
||||
var renderIconIf = function(conditionProp, name, key, actionable) {
|
||||
if (!self.get(conditionProp)) { return; }
|
||||
var title = I18n.t("topic_statuses." + key + ".help");
|
||||
|
||||
var title = Handlebars.Utils.escapeExpression(I18n.t("topic_statuses." + key + ".help"));
|
||||
var startTag = actionable ? "a href='#'" : "span";
|
||||
var endTag = actionable ? "a" : "span";
|
||||
|
||||
buffer.push("<" + startTag +
|
||||
" title='" + title +"' class='topic-status'><i class='fa fa-" + name + "'></i></" + endTag + ">");
|
||||
buffer.push("<" + startTag + " title='" + title + "' class='topic-status'><i class='fa fa-" + name + "'></i></" + endTag + ">");
|
||||
};
|
||||
|
||||
// Allow a plugin to add a custom icon to a topic
|
||||
|
||||
@ -85,7 +85,7 @@ var controllerOpts = {
|
||||
if (selected.length > 0) {
|
||||
promise = Discourse.Topic.bulkOperation(selected, operation);
|
||||
} else {
|
||||
promise = Discourse.Topic.bulkOperationByFilter(this.get('filter'), operation);
|
||||
promise = Discourse.Topic.bulkOperationByFilter('unread', operation, this.get('category.id'));
|
||||
}
|
||||
promise.then(function(result) {
|
||||
if (result && result.topic_ids) {
|
||||
@ -105,8 +105,12 @@ var controllerOpts = {
|
||||
return Discourse.TopicTrackingState.current();
|
||||
}.property(),
|
||||
|
||||
isFilterPage: function(filter, filterType) {
|
||||
return filter.match(new RegExp(filterType + '$', 'gi')) ? true : false;
|
||||
},
|
||||
|
||||
showDismissRead: function() {
|
||||
return this.get('filter') === 'unread' && this.get('topics.length') > 0;
|
||||
return this.isFilterPage(this.get('filter'), 'unread') && this.get('topics.length') > 0;
|
||||
}.property('filter', 'topics.length'),
|
||||
|
||||
showResetNew: function() {
|
||||
@ -114,8 +118,8 @@ var controllerOpts = {
|
||||
}.property('filter', 'topics.length'),
|
||||
|
||||
showDismissAtTop: function() {
|
||||
return (this.get('filter') === 'new' ||
|
||||
this.get('filter') === 'unread') &&
|
||||
return (this.isFilterPage(this.get('filter'), 'new') ||
|
||||
this.isFilterPage(this.get('filter'), 'unread')) &&
|
||||
this.get('topics.length') >= 30;
|
||||
}.property('filter', 'topics.length'),
|
||||
|
||||
|
||||
@ -18,8 +18,12 @@ export default ObjectController.extend({
|
||||
}.property('name_key'),
|
||||
|
||||
formattedName: function(){
|
||||
return this.get('name').replace("{{username}}", this.get('controllers.flag.username'));
|
||||
}.property('name'),
|
||||
if (this.get("is_custom_flag")) {
|
||||
return this.get('name').replace("{{username}}", this.get('controllers.flag.username'));
|
||||
} else {
|
||||
return I18n.t("flagging.formatted_name." + this.get('name_key'));
|
||||
}
|
||||
}.property('name', 'name_key', 'is_custom_flag'),
|
||||
|
||||
selected: function() {
|
||||
return this.get('model') === this.get('controllers.flag.selected');
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import ObjectController from 'discourse/controllers/object';
|
||||
import { spinnerHTML } from 'discourse/helpers/loading-spinner';
|
||||
|
||||
export default ObjectController.extend(Discourse.SelectedPostsCount, {
|
||||
multiSelect: false,
|
||||
@ -8,6 +9,7 @@ export default ObjectController.extend(Discourse.SelectedPostsCount, {
|
||||
selectedPosts: null,
|
||||
selectedReplies: null,
|
||||
queryParams: ['filter', 'username_filters', 'show_deleted'],
|
||||
searchHighlight: null,
|
||||
|
||||
maxTitleLength: Discourse.computed.setting('max_topic_title_length'),
|
||||
|
||||
@ -509,7 +511,7 @@ export default ObjectController.extend(Discourse.SelectedPostsCount, {
|
||||
}.property('isPrivateMessage'),
|
||||
|
||||
loadingHTML: function() {
|
||||
return "<div class='spinner'></div>";
|
||||
return spinnerHTML;
|
||||
}.property(),
|
||||
|
||||
recoverTopic: function() {
|
||||
|
||||
@ -7,6 +7,8 @@ export default ObjectController.extend({
|
||||
username: null,
|
||||
participant: null,
|
||||
avatar: null,
|
||||
userLoading: null,
|
||||
cardTarget: null,
|
||||
|
||||
postStream: Em.computed.alias('controllers.topic.postStream'),
|
||||
enoughPostsForFiltering: Em.computed.gte('participant.post_count', 2),
|
||||
@ -28,7 +30,12 @@ export default ObjectController.extend({
|
||||
|
||||
showMoreBadges: Em.computed.gt('moreBadgesCount', 0),
|
||||
|
||||
show: function(username, uploadedAvatarId) {
|
||||
hasCardBadgeImage: function() {
|
||||
var img = this.get('user.card_badge.image');
|
||||
return img && img.indexOf('fa-') !== 0;
|
||||
}.property('user.card_badge.image'),
|
||||
|
||||
show: function(username, target) {
|
||||
// XSS protection (should be encapsulated)
|
||||
username = username.replace(/[^A-Za-z0-9_]/g, "");
|
||||
var url = "/users/" + username;
|
||||
@ -42,17 +49,17 @@ export default ObjectController.extend({
|
||||
var currentUsername = this.get('username'),
|
||||
wasVisible = this.get('visible');
|
||||
|
||||
if (uploadedAvatarId) {
|
||||
this.set('avatar', {username: username, uploaded_avatar_id: uploadedAvatarId});
|
||||
} else {
|
||||
this.set('avatar', null);
|
||||
this.set('avatar', null);
|
||||
this.set('username', username);
|
||||
|
||||
// If we click the avatar again, close it (unless its diff element on the screen).
|
||||
if (target === this.get('cardTarget') && wasVisible) {
|
||||
this.setProperties({ visible: false, username: null, avatar: null, cardTarget: null });
|
||||
return;
|
||||
}
|
||||
|
||||
this.setProperties({visible: true, username: username});
|
||||
|
||||
// If we click the avatar again, close it.
|
||||
if (username === currentUsername && wasVisible) {
|
||||
this.setProperties({ visible: false, username: null, avatar: null });
|
||||
if (username === currentUsername && this.get('userLoading') === username) {
|
||||
// debounce
|
||||
return;
|
||||
}
|
||||
|
||||
@ -66,14 +73,19 @@ export default ObjectController.extend({
|
||||
|
||||
var self = this;
|
||||
self.set('user', null);
|
||||
self.set('userLoading', username);
|
||||
self.set('cardTarget', target);
|
||||
|
||||
Discourse.User.findByUsername(username).then(function (user) {
|
||||
self.set('user', user);
|
||||
self.set('avatar', user);
|
||||
self.setProperties({ user: user, avatar: user, visible: true});
|
||||
}).finally(function(){
|
||||
self.set('userLoading', null);
|
||||
});
|
||||
},
|
||||
|
||||
close: function() {
|
||||
this.set('visible', false);
|
||||
this.set('cardTarget', null);
|
||||
},
|
||||
|
||||
actions: {
|
||||
|
||||
@ -0,0 +1,7 @@
|
||||
var spinnerHTML = "<div class='spinner'><div class='spinner-container container1'><div class='circle1'></div><div class='circle2'></div><div class='circle3'></div><div class='circle4'></div></div><div class='spinner-container container2'><div class='circle1'></div><div class='circle2'></div><div class='circle3'></div><div class='circle4'></div></div><div class='spinner-container container3'><div class='circle1'></div><div class='circle2'></div><div class='circle3'></div><div class='circle4'></div></div></div>";
|
||||
|
||||
Handlebars.registerHelper('loading-spinner', function() {
|
||||
return new Handlebars.SafeString(spinnerHTML);
|
||||
});
|
||||
|
||||
export { spinnerHTML };
|
||||
@ -331,7 +331,7 @@ export default function(options) {
|
||||
}
|
||||
|
||||
// ESC
|
||||
if (e.which === keys.escape) {
|
||||
if (e.which === keys.esc) {
|
||||
if (completeStart !== null) {
|
||||
closeAutocomplete();
|
||||
return false;
|
||||
|
||||
@ -19,7 +19,13 @@ Discourse.PageTracker = Ember.Object.extend(Ember.Evented, {
|
||||
|
||||
router.on('didTransition', function() {
|
||||
this.send('refreshTitle');
|
||||
self.trigger('change', this.get('url'), Discourse.get('_docTitle'));
|
||||
var url = this.get('url');
|
||||
|
||||
// Refreshing the title is debounced, so we need to trigger this in the
|
||||
// next runloop to have the correct title.
|
||||
Em.run.next(function() {
|
||||
self.trigger('change', url, Discourse.get('_docTitle'));
|
||||
});
|
||||
});
|
||||
this.set('started', true);
|
||||
}
|
||||
|
||||
@ -269,7 +269,7 @@ Discourse.Utilities = {
|
||||
@param {String} path The path
|
||||
**/
|
||||
isAnImage: function(path) {
|
||||
return (/\.(png|jpg|jpeg|gif|bmp|tif|tiff)$/i).test(path);
|
||||
return (/\.(png|jpg|jpeg|gif|bmp|tif|tiff|svg|webp)$/i).test(path);
|
||||
},
|
||||
|
||||
/**
|
||||
@ -279,7 +279,7 @@ Discourse.Utilities = {
|
||||
**/
|
||||
allowsAttachments: function() {
|
||||
return Discourse.Utilities.authorizesAllExtensions() ||
|
||||
!(/((png|jpg|jpeg|gif|bmp|tif|tiff)(,\s)?)+$/i).test(Discourse.Utilities.authorizedExtensions());
|
||||
!(/((png|jpg|jpeg|gif|bmp|tif|tiff|svg|webp)(,\s)?)+$/i).test(Discourse.Utilities.authorizedExtensions());
|
||||
},
|
||||
|
||||
displayErrorForUpload: function(data) {
|
||||
|
||||
@ -130,7 +130,7 @@ Discourse.PostStream = Em.Object.extend({
|
||||
|
||||
hasNoFilters: function() {
|
||||
var streamFilters = this.get('streamFilters');
|
||||
return !(streamFilters && ((streamFilters.filter === 'summary') || streamFilters.userFilters));
|
||||
return !(streamFilters && ((streamFilters.filter === 'summary') || streamFilters.username_filters));
|
||||
}.property('streamFilters.[]', 'topic.posts_count', 'posts.length'),
|
||||
|
||||
/**
|
||||
|
||||
@ -471,10 +471,12 @@ Discourse.Topic.reopenClass({
|
||||
});
|
||||
},
|
||||
|
||||
bulkOperationByFilter: function(filter, operation) {
|
||||
bulkOperationByFilter: function(filter, operation, categoryId) {
|
||||
var data = { filter: filter, operation: operation };
|
||||
if (categoryId) data['category_id'] = categoryId;
|
||||
return Discourse.ajax("/topics/bulk", {
|
||||
type: 'PUT',
|
||||
data: { filter: filter, operation: operation }
|
||||
data: data
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
@ -19,11 +19,6 @@ var ApplicationRoute = Discourse.Route.extend({
|
||||
});
|
||||
},
|
||||
|
||||
expandUser: function(user) {
|
||||
this.controllerFor('user-card').show(user.get('username'), user.get('uploaded_avatar_id'));
|
||||
return true;
|
||||
},
|
||||
|
||||
error: function(err, transition) {
|
||||
if (err.status === 404) {
|
||||
// 404
|
||||
|
||||
@ -38,18 +38,6 @@ Discourse.TopicRoute = Discourse.Route.extend({
|
||||
this.controllerFor("topic-admin-menu").send("show");
|
||||
},
|
||||
|
||||
// Modals that can pop up within a topic
|
||||
expandPostUser: function(post) {
|
||||
this.controllerFor('user-card').show(post.get('username'), post.get('uploaded_avatar_id'));
|
||||
},
|
||||
|
||||
expandPostUsername: function(username) {
|
||||
username = username.replace(/^@/, '');
|
||||
if (!Em.isEmpty(username)) {
|
||||
this.controllerFor('user-card').show(username);
|
||||
}
|
||||
},
|
||||
|
||||
showFlags: function(post) {
|
||||
Discourse.Route.showModal(this, 'flag', post);
|
||||
this.controllerFor('flag').setProperties({ selected: null });
|
||||
|
||||
@ -36,11 +36,11 @@
|
||||
{{/each}}
|
||||
</div>
|
||||
{{#if canLoadMore}}
|
||||
<div class='spinner'></div>
|
||||
{{loading-spinner}}
|
||||
{{/if}}
|
||||
{{else}}
|
||||
{{#unless userBadgesLoaded}}
|
||||
<div class='spinner'></div>
|
||||
{{loading-spinner}}
|
||||
{{/unless}}
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
@ -56,5 +56,5 @@
|
||||
</div>
|
||||
{{/if}}
|
||||
{{else}}
|
||||
<div class='spinner'></div>
|
||||
{{loading-spinner}}
|
||||
{{/if}}
|
||||
|
||||
@ -1,6 +0,0 @@
|
||||
{{#if groups}}
|
||||
{{i18n groups.title count=groups.length}}:
|
||||
{{#each groups}}
|
||||
{{#link-to 'group' this class="group-link"}}{{name}}{{/link-to}}
|
||||
{{/each}}
|
||||
{{/if}}
|
||||
@ -1,4 +1,4 @@
|
||||
<div class='spinner'></div>
|
||||
{{loading-spinner}}
|
||||
|
||||
<div class='contents'>
|
||||
|
||||
|
||||
@ -11,7 +11,7 @@
|
||||
</div>
|
||||
|
||||
<div {{bind-attr class="loadingSpinner::hidden"}}>
|
||||
<div class='spinner'></div>
|
||||
{{loading-spinner}}
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
@ -72,7 +72,7 @@
|
||||
|
||||
<footer class='topic-list-bottom'>
|
||||
{{#if loadingMore}}
|
||||
<div class='topics-loading'></div>
|
||||
{{loading-spinner}}
|
||||
{{/if}}
|
||||
{{#if allLoaded}}
|
||||
{{#if showDismissRead}}
|
||||
|
||||
@ -2,13 +2,13 @@
|
||||
<div class='topic-avatar'>
|
||||
<div class='contents'>
|
||||
<div>
|
||||
{{poster-avatar action="expandPostUser" post=this classNames="main-avatar"}}
|
||||
{{poster-avatar post=this classNames="main-avatar"}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class='topic-body'>
|
||||
<div class="topic-meta-data">
|
||||
{{poster-name post=this expandAction="expandPostUser"}}
|
||||
{{poster-name post=this}}
|
||||
{{#if view.parentView.previousPost}}<a href='{{unbound url}}' class="post-info arrow" title="{{i18n topic.jump_reply_up}}"><i class='fa fa-arrow-up'></i></a>{{/if}}
|
||||
<div class='post-info post-date'>{{age-with-tooltip created_at}}</div>
|
||||
</div>
|
||||
|
||||
@ -53,5 +53,5 @@
|
||||
</div>
|
||||
{{/if}}
|
||||
{{else}}
|
||||
<div class='spinner'></div>
|
||||
{{loading-spinner}}
|
||||
{{/if}}
|
||||
|
||||
@ -25,7 +25,7 @@
|
||||
|
||||
<footer class='topic-list-bottom'>
|
||||
{{#if loadingMore}}
|
||||
<div class='topics-loading'></div>
|
||||
{{loading-spinner}}
|
||||
{{/if}}
|
||||
{{#if allLoaded}}
|
||||
{{#if showDismissRead}}
|
||||
|
||||
@ -20,7 +20,7 @@
|
||||
<div class='topic-avatar'>
|
||||
<div class="contents">
|
||||
{{#unless userDeleted}}
|
||||
{{poster-avatar action="expandPostUser" post=this classNames="main-avatar"}}
|
||||
{{poster-avatar post=this classNames="main-avatar"}}
|
||||
{{else}}
|
||||
<i class="fa fa-trash-o deleted-user-avatar"></i>
|
||||
{{/unless}}
|
||||
@ -30,7 +30,7 @@
|
||||
|
||||
<div class='topic-body'>
|
||||
<div class='topic-meta-data'>
|
||||
{{poster-name post=this expandAction="expandPostUser"}}
|
||||
{{poster-name post=this}}
|
||||
<div class='post-info'>
|
||||
<a class='post-date' {{bind-attr href="shareUrl" data-share-url="shareUrl" data-post-number="post_number"}}>{{age-with-tooltip created_at}}</a>
|
||||
</div>
|
||||
|
||||
@ -8,7 +8,7 @@
|
||||
<a href="#" class="show-help" {{action "showSearchHelp" bubbles=false}}>{{i18n show_help}}</a>
|
||||
</div>
|
||||
{{#if loading}}
|
||||
<div class='searching'></div>
|
||||
<div class='searching'>{{loading-spinner}}</div>
|
||||
{{else}}
|
||||
<div class="results">
|
||||
{{#unless noResults}}
|
||||
|
||||
@ -73,7 +73,7 @@
|
||||
{{render 'topic-progress'}}
|
||||
|
||||
{{#if postStream.loadingAbove}}
|
||||
<div class='spinner'></div>
|
||||
{{loading-spinner}}
|
||||
{{/if}}
|
||||
|
||||
{{#unless postStream.loadingFilter}}
|
||||
@ -90,7 +90,7 @@
|
||||
{{/unless}}
|
||||
|
||||
{{#if postStream.loadingBelow}}
|
||||
<div class='spinner'></div>
|
||||
{{loading-spinner}}
|
||||
{{/if}}
|
||||
</div>
|
||||
<div id='topic-bottom'></div>
|
||||
@ -137,13 +137,13 @@
|
||||
{{/if}}
|
||||
</div>
|
||||
{{#if retrying}}
|
||||
<div class='spinner'></div>
|
||||
{{loading-spinner}}
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</div>
|
||||
{{else}}
|
||||
<div class='container'>
|
||||
<div class='spinner'></div>
|
||||
{{loading-spinner}}
|
||||
</div>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
|
||||
@ -1,72 +1,73 @@
|
||||
{{#if username}}
|
||||
|
||||
<div class="card-content">
|
||||
|
||||
<div class="card-content">
|
||||
{{#link-to 'user' user}}{{bound-avatar avatar "huge"}}{{/link-to}}
|
||||
|
||||
<div class="names">
|
||||
<span>
|
||||
<h1 {{bind-attr class="staff new_user"}}>
|
||||
{{#link-to 'user' user}}{{username}}{{/link-to}}
|
||||
</h1>
|
||||
{{#if user.title}}
|
||||
<h2>{{user.title}}</h2>
|
||||
{{/if}}
|
||||
{{#if showName}}
|
||||
<h2>{{#link-to 'user' user}}{{name}}{{/link-to}}</h2>
|
||||
{{/if}}
|
||||
</span>
|
||||
<h1 {{bind-attr class="staff new_user"}}>
|
||||
{{#link-to 'user' user}}{{username}}{{{user.statusIcon}}}{{/link-to}}
|
||||
</h1>
|
||||
|
||||
{{#if user.name}}
|
||||
<h2>{{user.name}}</h2>
|
||||
{{/if}}
|
||||
|
||||
{{#if user.title}}
|
||||
<h2>{{user.title}}</h2>
|
||||
{{/if}}
|
||||
|
||||
{{#if showName}}
|
||||
<h2>{{#link-to 'user' user}}{{name}}{{/link-to}}</h2>
|
||||
{{/if}}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<ul class="usercard-controls">
|
||||
{{#if user.can_send_private_message_to_user}}
|
||||
<li><a class='btn btn-primary' {{action "composePrivateMessage" user}}>{{fa-icon "envelope"}}{{i18n user.private_message}}</a></li>
|
||||
{{/if}}
|
||||
|
||||
{{#if showFilter}}
|
||||
<li><a class='btn' {{action "togglePosts" user}}>{{fa-icon "filter"}}{{i18n topic.filter_to username="username" post_count="participant.post_count"}}</a></li>
|
||||
{{/if}}
|
||||
|
||||
{{#if hasUserFilters}}
|
||||
<li><a class='btn' {{action "cancelFilter"}}>{{fa-icon "times"}}{{i18n topic.filters.cancel}}</a></li>
|
||||
{{/if}}
|
||||
</ul>
|
||||
|
||||
{{#if isSuspended}}
|
||||
<div class='suspended'>
|
||||
{{fa-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_cooked}}</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 user}}
|
||||
|
||||
{{#if isSuspended}}
|
||||
<div class='suspended'>
|
||||
{{fa-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'>{{{user.bio_cooked}}}</div>{{/if}}
|
||||
{{/if}}
|
||||
|
||||
{{#if showBadges}}
|
||||
<div class="badge-section">
|
||||
{{#each user.featured_user_badges}}
|
||||
{{user-badge badge=badge}}
|
||||
{{/each}}
|
||||
{{#if showMoreBadges}}
|
||||
{{#link-to 'user.badges' user class="btn more-user-badges"}}
|
||||
{{i18n badges.more_badges count=moreBadgesCount}}
|
||||
{{/link-to}}
|
||||
{{/if}}
|
||||
<div class="metadata">
|
||||
<h3><span class='desc'>{{i18n last_post}}</span> {{format-date path="user.last_posted_at" leaveAgo="true"}} </h3>
|
||||
<h3><span class='desc'>{{i18n joined}}</span> {{format-date path="user.created_at" leaveAgo="true"}}</h3>
|
||||
</div>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
|
||||
<ul class="usercard-controls">
|
||||
{{#if user.can_send_private_message_to_user}}
|
||||
<li><a class='btn btn-primary' {{action "composePrivateMessage" user}}>{{fa-icon "envelope"}}{{i18n user.private_message}}</a></li>
|
||||
{{/if}}
|
||||
|
||||
<li>{{#link-to 'user' user class="btn"}}{{fa-icon "user"}}{{i18n user.profile}}{{/link-to}}</li>
|
||||
|
||||
{{#if showFilter}}
|
||||
<li><a class='btn' {{action "togglePosts" user}}>{{fa-icon "filter"}}{{i18n topic.filter_to username="username" post_count="participant.post_count"}}</a></li>
|
||||
{{/if}}
|
||||
|
||||
{{#if hasUserFilters}}
|
||||
<li><a class='btn' {{action "cancelFilter"}}>{{fa-icon "times"}}{{i18n topic.filters.cancel}}</a></li>
|
||||
{{/if}}
|
||||
</ul>
|
||||
|
||||
{{#if user.card_badge}}
|
||||
{{#link-to 'badges.show' user.card_badge class="card-badge"}}
|
||||
{{icon-or-image user.card_badge.image}}
|
||||
{{#if showBadges}}
|
||||
<div class="badge-section">
|
||||
{{#each user.featured_user_badges}}
|
||||
{{user-badge badge=badge}}
|
||||
{{/each}}
|
||||
{{#if showMoreBadges}}
|
||||
{{#link-to 'user.badges' user class="btn more-user-badges"}}
|
||||
{{i18n badges.more_badges count=moreBadgesCount}}
|
||||
{{/link-to}}
|
||||
{{/if}}
|
||||
|
||||
</div>
|
||||
{{else}}
|
||||
<p class='loading'>{{i18n loading}}</p>
|
||||
</div>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
@ -70,7 +70,7 @@
|
||||
{{/each}}
|
||||
</table>
|
||||
{{#if invitesLoading}}
|
||||
<div class='spinner'></div>
|
||||
{{loading-spinner}}
|
||||
{{/if}}
|
||||
|
||||
{{else}}
|
||||
|
||||
@ -23,7 +23,7 @@
|
||||
{{/each}}
|
||||
|
||||
{{#if loading}}
|
||||
<div class='spinner'></div>
|
||||
{{loading-spinner}}
|
||||
{{/if}}
|
||||
{{#unless canLoadMore}}
|
||||
<div class='end-of-stream'></div>
|
||||
|
||||
@ -27,5 +27,5 @@
|
||||
</div>
|
||||
{{/each}}
|
||||
{{#if loading}}
|
||||
<div class='spinner'></div>
|
||||
{{loading-spinner}}
|
||||
{{/if}}
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
<div {{bind-attr class=":item hidden deleted moderator_action"}}>
|
||||
<div class='clearfix info'>
|
||||
<a href="{{unbound userUrl}}" data-user-card="{{unbound username}}" class='avatar-link'><div class='avatar-wrapper'>{{avatar this imageSize="large" extraClasses="actor" ignoreTitle="true"}}</div></a>
|
||||
<span class='time'>{{format-date path="created_at" leaveAgo="true"}}</span>
|
||||
<span class='time'>{{format-date path="created_at"}}</span>
|
||||
<span class="title">
|
||||
<a href="{{unbound postUrl}}">{{unbound title}}</a>
|
||||
</span>
|
||||
@ -26,5 +26,5 @@
|
||||
</div>
|
||||
{{/grouped-each}}
|
||||
{{#if loading}}
|
||||
<div class='spinner'></div>
|
||||
{{loading-spinner}}
|
||||
{{/if}}
|
||||
|
||||
@ -94,11 +94,11 @@
|
||||
|
||||
{{#if websiteName}}
|
||||
<i class="fa fa-globe"></i>
|
||||
{{#if linkWebsite}}
|
||||
<a {{bind-attr href="website"}} rel="nofollow" target="_blank">{{websiteName}}</a>
|
||||
{{else}}
|
||||
<span {{bind-attr title="website"}}>{{websiteName}}</span>
|
||||
{{/if}}
|
||||
{{#if linkWebsite}}
|
||||
<a {{bind-attr href="website"}} rel="nofollow" target="_blank">{{websiteName}}</a>
|
||||
{{else}}
|
||||
<span {{bind-attr title="website"}}>{{websiteName}}</span>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
|
||||
</h3>
|
||||
@ -150,6 +150,14 @@
|
||||
{{/if}}
|
||||
</dd>
|
||||
{{/if}}
|
||||
{{#if custom_groups}}
|
||||
<dt>{{i18n groups.title count=custom_groups.length}}</dt>
|
||||
<dd class='groups'>
|
||||
{{#each custom_groups}}
|
||||
<span>{{#link-to 'group' this class="group-link"}}{{name}}{{/link-to}}</span>
|
||||
{{/each}}
|
||||
</dd>
|
||||
{{/if}}
|
||||
</dl>
|
||||
{{plugin-outlet "user-profile-secondary"}}
|
||||
</div>
|
||||
|
||||
@ -70,35 +70,6 @@ export default Discourse.View.extend(AddCategoryClass, Discourse.Scrolling, {
|
||||
|
||||
}.on('willDestroyElement'),
|
||||
|
||||
debounceLoadSuggested: Discourse.debounce(function(){
|
||||
if (this.get('isDestroyed') || this.get('isDestroying')) { return; }
|
||||
|
||||
var incoming = this.get('topicTrackingState.newIncoming'),
|
||||
suggested = this.get('topic.details.suggested_topics'),
|
||||
topicId = this.get('topic.id');
|
||||
|
||||
if(suggested) {
|
||||
var existing = _.invoke(suggested, 'get', 'id'),
|
||||
lookup = _.chain(incoming)
|
||||
.last(Discourse.SiteSettings.suggested_topics)
|
||||
.reverse()
|
||||
.union(existing)
|
||||
.uniq()
|
||||
.without(topicId)
|
||||
.first(Discourse.SiteSettings.suggested_topics)
|
||||
.value();
|
||||
|
||||
Discourse.TopicList.loadTopics(lookup, "").then(function(topics){
|
||||
suggested.clear();
|
||||
suggested.pushObjects(topics);
|
||||
});
|
||||
}
|
||||
}, 1000),
|
||||
|
||||
hasNewSuggested: function(){
|
||||
this.debounceLoadSuggested();
|
||||
}.observes('topicTrackingState.incomingCount'),
|
||||
|
||||
gotFocus: function(){
|
||||
if (Discourse.get('hasFocus')){
|
||||
this.scrolled();
|
||||
|
||||
@ -6,7 +6,7 @@ var clickOutsideEventName = "mousedown.outside-user-card",
|
||||
|
||||
export default Discourse.View.extend(CleansUp, {
|
||||
elementId: 'user-card',
|
||||
classNameBindings: ['controller.visible::hidden', 'controller.showBadges'],
|
||||
classNameBindings: ['controller.visible::hidden', 'controller.showBadges', 'controller.hasCardBadgeImage'],
|
||||
allowBackgrounds: Discourse.computed.setting('allow_profile_backgrounds'),
|
||||
|
||||
addBackground: function() {
|
||||
@ -30,8 +30,11 @@ export default Discourse.View.extend(CleansUp, {
|
||||
$('html').off(clickOutsideEventName).on(clickOutsideEventName, function(e) {
|
||||
if (self.get('controller.visible')) {
|
||||
var $target = $(e.target);
|
||||
if ($target.closest('.trigger-user-card').length > 0) { return; }
|
||||
if (self.$().has(e.target).length !== 0) { return; }
|
||||
if ($target.closest('[data-user-card]').data('userCard') ||
|
||||
$target.closest('a.mention').length > 0 ||
|
||||
$target.closest('#user-card').length > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
self.get('controller').close();
|
||||
}
|
||||
@ -39,19 +42,22 @@ export default Discourse.View.extend(CleansUp, {
|
||||
return true;
|
||||
});
|
||||
|
||||
var expand = function(username, $target){
|
||||
self._posterExpand($target);
|
||||
self.get('controller').show(username, $target[0]);
|
||||
return false;
|
||||
};
|
||||
|
||||
$('#main-outlet').on(clickDataExpand, '[data-user-card]', function(e) {
|
||||
var $target = $(e.currentTarget);
|
||||
self._posterExpand($target);
|
||||
self.get('controller').show($target.data('user-card'));
|
||||
return false;
|
||||
var username = $target.data('user-card');
|
||||
return expand(username, $target);
|
||||
});
|
||||
|
||||
$('#main-outlet').on(clickMention, 'a.mention', function(e) {
|
||||
var $target = $(e.target);
|
||||
self._posterExpand($target);
|
||||
var username = $target.text().replace(/^@/, '');
|
||||
self.get('controller').show(username);
|
||||
return false;
|
||||
return expand(username, $target);
|
||||
});
|
||||
}.on('didInsertElement'),
|
||||
|
||||
@ -67,9 +73,11 @@ export default Discourse.View.extend(CleansUp, {
|
||||
|
||||
var overage = ($(window).width() - 50) - (position.left + width);
|
||||
if (overage < 0) {
|
||||
position.left += overage;
|
||||
position.top += target.height() + 5;
|
||||
position.left -= (width/2) - 10;
|
||||
position.top += target.height() + 8;
|
||||
}
|
||||
|
||||
position.top -= $('#main-outlet').offset().top;
|
||||
self.$().css(position);
|
||||
}
|
||||
}
|
||||
|
||||
@ -45,6 +45,7 @@
|
||||
//= require ./discourse/components/visible
|
||||
//= require ./discourse/helpers/user-avatar
|
||||
//= require ./discourse/helpers/cold-age-class
|
||||
//= require ./discourse/helpers/loading-spinner
|
||||
|
||||
//= require ./discourse/dialects/dialect
|
||||
//= require_tree ./discourse/dialects
|
||||
|
||||
@ -40,4 +40,5 @@
|
||||
//= require ember-cloaking
|
||||
//= require break_string
|
||||
//= require buffered-proxy
|
||||
//= require jquery.autoellipsis-1.0.10.min.js
|
||||
//= require_tree ./discourse/ember
|
||||
|
||||
@ -39,6 +39,9 @@ td.flaggers td {
|
||||
}
|
||||
}
|
||||
|
||||
.view-options {
|
||||
float: right;
|
||||
}
|
||||
table.report {
|
||||
margin-top: 20px;
|
||||
tr {
|
||||
@ -89,6 +92,10 @@ td.flaggers td {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.admin-title {
|
||||
height: 45px;
|
||||
}
|
||||
|
||||
.admin-controls {
|
||||
background-color: dark-light-diff($primary, $secondary, 90%, -75%);
|
||||
padding: 10px 10px 3px 0;
|
||||
|
||||
@ -149,17 +149,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
.topics-loading {
|
||||
width: 20px;
|
||||
margin: 0 auto;
|
||||
padding: 25px 15px;
|
||||
background: {
|
||||
image: image-url("spinner_96.gif");
|
||||
repeat: no-repeat;
|
||||
size: 50px;
|
||||
};
|
||||
}
|
||||
|
||||
.list-controls {
|
||||
.home {
|
||||
background-color: scale-color-diff();
|
||||
|
||||
@ -15,6 +15,10 @@ big {
|
||||
font-size: 28px;
|
||||
}
|
||||
|
||||
small {
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
background-color: scale-color-diff();
|
||||
border-left: 5px solid darken(scale-color-diff(), 10%);
|
||||
@ -143,28 +147,6 @@ body {
|
||||
}
|
||||
}
|
||||
|
||||
.spinner {
|
||||
width: 20px;
|
||||
margin: 0 auto;
|
||||
padding: 25px 15px;
|
||||
background: {
|
||||
image: image-url("spinner_96.gif");
|
||||
repeat: no-repeat;
|
||||
size: 50px;
|
||||
};
|
||||
-webkit-animation: spinner .25s;
|
||||
animation: spinner .25s;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
//loading spinner fade-in
|
||||
@-webkit-keyframes spinner {
|
||||
from {opacity: 0}
|
||||
to {opacity: 1}
|
||||
}@keyframes fade {
|
||||
from {opacity: 0}
|
||||
to {opacity: 1}
|
||||
}
|
||||
|
||||
.avatar-wrapper {
|
||||
background-color: $secondary;
|
||||
@ -193,3 +175,115 @@ body {
|
||||
.fade.in {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.spinner {
|
||||
margin: 20px auto 0 auto;
|
||||
width: 35px;
|
||||
height: 35px;
|
||||
position: relative;
|
||||
|
||||
.container1 > div, .container2 > div, .container3 > div {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
background-color: $primary;
|
||||
|
||||
border-radius: 100%;
|
||||
position: absolute;
|
||||
-webkit-animation: bouncedelay 1.2s infinite ease-in-out;
|
||||
animation: bouncedelay 1.2s infinite ease-in-out;
|
||||
-webkit-animation-fill-mode: both;
|
||||
animation-fill-mode: both;
|
||||
}
|
||||
|
||||
.spinner-container {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.container2 {
|
||||
-webkit-transform: rotateZ(45deg);
|
||||
transform: rotateZ(45deg);
|
||||
}
|
||||
|
||||
.container3 {
|
||||
-webkit-transform: rotateZ(90deg);
|
||||
transform: rotateZ(90deg);
|
||||
}
|
||||
|
||||
.circle1 { top: 0; left: 0; }
|
||||
.circle2 { top: 0; right: 0; }
|
||||
.circle3 { right: 0; bottom: 0; }
|
||||
.circle4 { left: 0; bottom: 0; }
|
||||
|
||||
.container2 .circle1 {
|
||||
-webkit-animation-delay: -1.1s;
|
||||
animation-delay: -1.1s;
|
||||
}
|
||||
|
||||
.container3 .circle1 {
|
||||
-webkit-animation-delay: -1.0s;
|
||||
animation-delay: -1.0s;
|
||||
}
|
||||
|
||||
.container1 .circle2 {
|
||||
-webkit-animation-delay: -0.9s;
|
||||
animation-delay: -0.9s;
|
||||
}
|
||||
|
||||
.container2 .circle2 {
|
||||
-webkit-animation-delay: -0.8s;
|
||||
animation-delay: -0.8s;
|
||||
}
|
||||
|
||||
.container3 .circle2 {
|
||||
-webkit-animation-delay: -0.7s;
|
||||
animation-delay: -0.7s;
|
||||
}
|
||||
|
||||
.container1 .circle3 {
|
||||
-webkit-animation-delay: -0.6s;
|
||||
animation-delay: -0.6s;
|
||||
}
|
||||
|
||||
.container2 .circle3 {
|
||||
-webkit-animation-delay: -0.5s;
|
||||
animation-delay: -0.5s;
|
||||
}
|
||||
|
||||
.container3 .circle3 {
|
||||
-webkit-animation-delay: -0.4s;
|
||||
animation-delay: -0.4s;
|
||||
}
|
||||
|
||||
.container1 .circle4 {
|
||||
-webkit-animation-delay: -0.3s;
|
||||
animation-delay: -0.3s;
|
||||
}
|
||||
|
||||
.container2 .circle4 {
|
||||
-webkit-animation-delay: -0.2s;
|
||||
animation-delay: -0.2s;
|
||||
}
|
||||
|
||||
.container3 .circle4 {
|
||||
-webkit-animation-delay: -0.1s;
|
||||
animation-delay: -0.1s;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@-webkit-keyframes bouncedelay {
|
||||
0%, 80%, 100% { -webkit-transform: scale(0.0) }
|
||||
40% { -webkit-transform: scale(1.0) }
|
||||
}
|
||||
|
||||
@keyframes bouncedelay {
|
||||
0%, 80%, 100% {
|
||||
transform: scale(0.0);
|
||||
-webkit-transform: scale(0.0);
|
||||
} 40% {
|
||||
transform: scale(1.0);
|
||||
-webkit-transform: scale(1.0);
|
||||
}
|
||||
}
|
||||
|
||||
@ -270,18 +270,17 @@
|
||||
}
|
||||
|
||||
.searching {
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 17px;
|
||||
right: 15px;
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
background: {
|
||||
image: image-url("spinner_96.gif");
|
||||
repeat: no-repeat;
|
||||
position: 0px 50%;
|
||||
size: 18px;
|
||||
};
|
||||
top: 0;
|
||||
right: 0;
|
||||
.spinner {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
.container1 > div, .container2 > div, .container3 > div {
|
||||
width: 4px;
|
||||
height: 4px;
|
||||
}
|
||||
}
|
||||
// I am ghetto using this to display "Show More".. be warned
|
||||
.no-results {
|
||||
@ -366,5 +365,3 @@
|
||||
#search-help table td {
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -33,7 +33,6 @@ body {
|
||||
|
||||
.boxed {
|
||||
height: 100%;
|
||||
@include border-radius-all(5px);
|
||||
&.white {
|
||||
background-color: $secondary;
|
||||
}
|
||||
|
||||
@ -68,7 +68,7 @@
|
||||
background-color: $secondary;
|
||||
}
|
||||
&.highlighted {
|
||||
background-color: scale-color($tertiary, $lightness: 85%);
|
||||
background-color: dark-light-diff($tertiary, $secondary, 90%, -41%);
|
||||
}
|
||||
}
|
||||
button.bulk-select {
|
||||
|
||||
@ -242,7 +242,7 @@ nav.post-controls {
|
||||
|
||||
.topic-body {
|
||||
background: dark-light-diff($primary, $secondary, 90%, -65%) !important;
|
||||
width: 86%;
|
||||
width: 87%;
|
||||
padding-left: 1%;
|
||||
padding-right: 1%;
|
||||
border-top: none;
|
||||
@ -253,7 +253,7 @@ nav.post-controls {
|
||||
|
||||
.topic-avatar {
|
||||
width: 45px;
|
||||
margin: 0 10px 0 15px;
|
||||
margin: 0 5px 0 15px;
|
||||
border: none;
|
||||
}
|
||||
|
||||
@ -614,11 +614,11 @@ iframe {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
a.mention {
|
||||
padding: 2px 4px;
|
||||
color: $primary;
|
||||
background: scale-color-diff();
|
||||
}
|
||||
a.mention {
|
||||
padding: 2px 4px;
|
||||
color: $primary;
|
||||
background: scale-color-diff();
|
||||
}
|
||||
|
||||
|
||||
.modal-body {
|
||||
|
||||
@ -11,11 +11,13 @@
|
||||
color: $secondary;
|
||||
background-size: cover;
|
||||
background-position: center center;
|
||||
min-height: 175px;
|
||||
|
||||
.card-content {
|
||||
padding: 12px;
|
||||
padding: 12px 12px 0 12px;
|
||||
background: rgba($primary, .85);
|
||||
margin-top: 100px;
|
||||
margin-top: 80px;
|
||||
|
||||
&:after {
|
||||
content: '';
|
||||
display: block;
|
||||
@ -24,6 +26,8 @@
|
||||
}
|
||||
|
||||
&.no-bg {
|
||||
min-height: 50px;
|
||||
|
||||
.card-content {
|
||||
margin-top: 0;
|
||||
}
|
||||
@ -49,6 +53,10 @@
|
||||
a {
|
||||
color: $secondary;
|
||||
}
|
||||
i {
|
||||
font-size: 18px;
|
||||
color: $secondary;
|
||||
}
|
||||
}
|
||||
|
||||
h2 {
|
||||
@ -69,11 +77,11 @@
|
||||
font-size: 13px;
|
||||
font-weight: normal;
|
||||
margin-top: 0;
|
||||
color: scale-color($primary, $lightness: 50%);
|
||||
color: dark-light-diff($secondary, $primary, 25%, -25%);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
a {
|
||||
color: $secondary;
|
||||
color: dark-light-diff($primary, $secondary, 50%, -50%);
|
||||
}
|
||||
}
|
||||
.groups {
|
||||
@ -88,11 +96,19 @@
|
||||
}
|
||||
|
||||
.metadata {
|
||||
position: absolute;
|
||||
right: 20px;
|
||||
top: 10px;
|
||||
max-width: 180px;
|
||||
text-align: right;
|
||||
width: 100%;
|
||||
clear: both;
|
||||
padding-top: 5px;
|
||||
h3 {
|
||||
display: inline;
|
||||
margin-right: 5px;
|
||||
.desc, a {
|
||||
color: dark-light-diff($secondary, $primary, 50%, -50%);
|
||||
}
|
||||
}
|
||||
div {display: inline; color: scale-color($primary, $lightness: 50%);
|
||||
.group-link {color: scale-color($primary, $lightness: 50%);}
|
||||
}
|
||||
}
|
||||
|
||||
.bottom {
|
||||
@ -100,12 +116,14 @@
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
&.has-card-badge-image .bio {
|
||||
width: 75%;
|
||||
}
|
||||
|
||||
.bio {
|
||||
max-height: 55px;
|
||||
overflow: auto;
|
||||
float: left;
|
||||
margin: 10px 0;
|
||||
width: 70%;
|
||||
padding: 15px 0 0 0;
|
||||
clear: left;
|
||||
|
||||
a {
|
||||
color: $secondary;
|
||||
text-decoration: underline;
|
||||
@ -113,23 +131,26 @@
|
||||
img {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
a.mention {
|
||||
background-color: dark-light-diff($secondary, $primary, 50%, -60%);
|
||||
}
|
||||
.overflow {
|
||||
max-height: 60px;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
img.avatar {
|
||||
float: left;
|
||||
padding-right: 10px;
|
||||
margin-top: -60px;
|
||||
margin-top: -53px;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0 0 5px 0;
|
||||
}
|
||||
|
||||
p.loading {
|
||||
margin-top: 45px;
|
||||
color: $primary;
|
||||
}
|
||||
|
||||
.btn {
|
||||
margin: 0 0 7px 0;
|
||||
box-sizing: border-box;
|
||||
@ -137,9 +158,8 @@
|
||||
|
||||
.usercard-controls {
|
||||
list-style-type: none;
|
||||
float: right;
|
||||
margin: 0;
|
||||
position: absolute;
|
||||
right: 12px;
|
||||
|
||||
a {width: 100%; min-width: 150px;}
|
||||
}
|
||||
@ -156,24 +176,27 @@
|
||||
height: 60px;
|
||||
position: relative;
|
||||
width: 45%;
|
||||
margin-top: 11px;
|
||||
|
||||
span {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
display: block;
|
||||
width: 250px;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.badge-section {
|
||||
margin-top: 10px;
|
||||
float: left;
|
||||
width: 390px;
|
||||
width: 500px;
|
||||
padding-bottom: 10px;
|
||||
margin-top: 5px;
|
||||
|
||||
.user-badge {
|
||||
background: transparent;
|
||||
color: $secondary;
|
||||
|
||||
color: dark-light-diff($primary, $secondary, 50%, -50%);
|
||||
border-color: dark-light-diff($primary, $secondary, 50%, -50%);
|
||||
}
|
||||
|
||||
h3 {
|
||||
@ -186,20 +209,24 @@
|
||||
.more-user-badges {
|
||||
@extend .user-badge;
|
||||
padding: 3px 8px;
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
|
||||
.suspended {
|
||||
color: $danger;
|
||||
margin-bottom: 10px;
|
||||
margin-bottom: 5px;
|
||||
clear: left;
|
||||
padding-top: 15px;
|
||||
}
|
||||
|
||||
.card-badge {
|
||||
img {
|
||||
max-width: 100px;
|
||||
max-width: 120px;
|
||||
}
|
||||
float: right;
|
||||
margin-right: 5px;
|
||||
position: absolute;
|
||||
right: 12px;
|
||||
bottom: 12px;
|
||||
font-size: 30px;
|
||||
i {color: $secondary;}
|
||||
}
|
||||
}
|
||||
|
||||
@ -198,17 +198,19 @@
|
||||
|
||||
dl dd {
|
||||
display: inline;
|
||||
margin: 0 15px 0 5px;
|
||||
margin: 0 10px 0 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
dl dt {
|
||||
display: inline-block;
|
||||
margin: 0 5px 0 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
dl {
|
||||
padding: 10px 15px;
|
||||
margin: 0;
|
||||
padding: 8px 10px;
|
||||
}
|
||||
|
||||
dd {
|
||||
@ -217,6 +219,15 @@
|
||||
color: $primary;
|
||||
}
|
||||
|
||||
dd.groups {
|
||||
span:after {
|
||||
content: ','
|
||||
}
|
||||
span:last-of-type:after {
|
||||
content:''
|
||||
}
|
||||
}
|
||||
|
||||
dt {
|
||||
color: dark-light-diff($secondary, $primary, 50%, -40%);
|
||||
margin: 0;
|
||||
@ -271,24 +282,28 @@
|
||||
width: 100%;
|
||||
position: relative;
|
||||
float: left;
|
||||
color: dark-light-diff($secondary, $primary, 75%, 0%);
|
||||
color: $secondary;
|
||||
|
||||
h1 {font-weight: bold;}
|
||||
|
||||
.primary-textual {
|
||||
padding: 3px;
|
||||
a[href] {
|
||||
color: dark-light-diff($secondary, $primary, 75%, -10%);
|
||||
color: $secondary;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
.bio {
|
||||
color: dark-light-diff($secondary, $primary, 75%, 0%);
|
||||
color: $secondary;
|
||||
max-height: 300px;
|
||||
overflow: auto;
|
||||
max-width: 750px;
|
||||
|
||||
a.mention {
|
||||
background-color: dark-light-diff($secondary, $primary, 20%, -50%);
|
||||
}
|
||||
|
||||
a[href] {
|
||||
color: dark-light-diff($secondary, $primary, 75%, 0%);
|
||||
text-decoration: underline;
|
||||
@ -304,6 +319,7 @@
|
||||
.controls {
|
||||
padding: 0 0 12px 0;
|
||||
float: right;
|
||||
width: 180px;
|
||||
ul {list-style-type: none;}
|
||||
a {
|
||||
padding: 5px 10px;
|
||||
@ -321,6 +337,7 @@
|
||||
.about.collapsed-info {
|
||||
.controls {
|
||||
margin-top: 0;
|
||||
width: auto;
|
||||
ul {
|
||||
li {display: inline;}
|
||||
a {
|
||||
|
||||
@ -10,9 +10,8 @@ body {
|
||||
|
||||
.boxed {
|
||||
height: 100%;
|
||||
@include border-radius-all(5px);
|
||||
.contents {
|
||||
padding: 10px 10px 10px 10px;
|
||||
padding: 10px;
|
||||
}
|
||||
&.white {
|
||||
background-color: $secondary;
|
||||
|
||||
@ -50,6 +50,12 @@
|
||||
|
||||
// Search
|
||||
|
||||
.searching {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 25px;
|
||||
}
|
||||
|
||||
input[type='text'] {
|
||||
width: 265px;
|
||||
font-size: 16px;
|
||||
@ -75,7 +81,6 @@
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.d-dropdown .no-results .show-help {
|
||||
display: none;
|
||||
.d-dropdown#search-dropdown .heading {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
|
||||
@ -163,3 +163,16 @@
|
||||
float: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
#search-help {
|
||||
max-width: 300px;
|
||||
}
|
||||
|
||||
#search-help h2 {
|
||||
margin: 0;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
#search-help p {
|
||||
margin: 5px;
|
||||
}
|
||||
@ -364,10 +364,6 @@ iframe {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.topic-meta-data {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.open>.dropdown-menu {
|
||||
display: block;
|
||||
}
|
||||
@ -470,12 +466,13 @@ span.highlighted {
|
||||
}
|
||||
|
||||
.topic-meta-data {
|
||||
margin-left: 60px;
|
||||
white-space: nowrap;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
width: 80%;
|
||||
left: 0px;
|
||||
.names {
|
||||
margin: 5px 0 0 65px;
|
||||
margin: 5px 0 0 5px;
|
||||
line-height: 17px;
|
||||
span {
|
||||
display: block;
|
||||
|
||||
@ -151,12 +151,11 @@
|
||||
|
||||
.user-navigation {
|
||||
width: 100%;
|
||||
margin-right: 1.8018%;
|
||||
margin-top: 20px;
|
||||
|
||||
h3 {
|
||||
color: $primary;
|
||||
margin: 20px 0 10px 0;
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
}
|
||||
@ -241,7 +240,6 @@
|
||||
.btn { padding: 3px 12px; }
|
||||
|
||||
dl dd {
|
||||
display: inline;
|
||||
margin: 0 15px 0 5px;
|
||||
padding: 0;
|
||||
}
|
||||
@ -344,18 +342,16 @@
|
||||
}
|
||||
|
||||
.controls {
|
||||
padding: 0 0 12px 0;
|
||||
float: right;
|
||||
ul {list-style-type: none;}
|
||||
width: 55%;
|
||||
float:right;
|
||||
ul {
|
||||
list-style-type: none;
|
||||
margin: 0;
|
||||
}
|
||||
a {
|
||||
padding: 5px 10px;
|
||||
width: 140px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.right {
|
||||
float: right;
|
||||
margin-left: 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -411,6 +407,7 @@
|
||||
|
||||
|
||||
.user-stream {
|
||||
padding: 0 10px;
|
||||
.end-of-stream {
|
||||
width: auto;
|
||||
}
|
||||
@ -445,8 +442,7 @@
|
||||
.time, .delete-info {
|
||||
display: block;
|
||||
float: right;
|
||||
color: $primary;
|
||||
margin-right: 8px;
|
||||
color: lighten($primary, 40%);
|
||||
font-size: 11px;
|
||||
}
|
||||
.delete-info i {
|
||||
|
||||
@ -4,11 +4,13 @@ class Admin::ImpersonateController < Admin::AdminController
|
||||
params.require(:username_or_email)
|
||||
|
||||
user = User.find_by_username_or_email(params[:username_or_email])
|
||||
|
||||
raise Discourse::NotFound if user.blank?
|
||||
|
||||
guardian.ensure_can_impersonate!(user)
|
||||
|
||||
# log impersonate
|
||||
StaffActionLogger.new(current_user).log_impersonate(user)
|
||||
|
||||
# Log on as the user
|
||||
log_on_user(user)
|
||||
|
||||
|
||||
@ -3,12 +3,17 @@ require_dependency 'report'
|
||||
class Admin::ReportsController < Admin::AdminController
|
||||
|
||||
def show
|
||||
|
||||
report_type = params[:type]
|
||||
|
||||
raise Discourse::NotFound.new unless report_type =~ /^[a-z0-9\_]+$/
|
||||
|
||||
report = Report.find(report_type)
|
||||
start_date = 1.month.ago
|
||||
start_date = Time.parse(params[:start_date]) if params[:start_date].present?
|
||||
|
||||
end_date = start_date + 1.month
|
||||
end_date = Time.parse(params[:end_date]) if params[:end_date].present?
|
||||
|
||||
report = Report.find(report_type, {start_date: start_date, end_date: end_date})
|
||||
raise Discourse::NotFound.new if report.blank?
|
||||
|
||||
render_json_dump(report: report)
|
||||
|
||||
@ -25,8 +25,14 @@ class Admin::UsersController < Admin::AdminController
|
||||
:revoke_api_key]
|
||||
|
||||
def index
|
||||
query = ::AdminUserIndexQuery.new(params)
|
||||
render_serialized(query.find_users, AdminUserSerializer)
|
||||
users = ::AdminUserIndexQuery.new(params).find_users
|
||||
|
||||
if params[:show_emails] == "true"
|
||||
guardian.can_see_emails = true
|
||||
StaffActionLogger.new(current_user).log_show_emails(users)
|
||||
end
|
||||
|
||||
render_serialized(users, AdminUserSerializer)
|
||||
end
|
||||
|
||||
def show
|
||||
|
||||
@ -99,7 +99,7 @@ class CategoriesController < ApplicationController
|
||||
category_id = params[:category_id].to_i
|
||||
notification_level = params[:notification_level].to_i
|
||||
|
||||
CategoryUser.set_notification_level_for_category(current_user, notification_level , category_id)
|
||||
CategoryUser.set_notification_level_for_category(current_user, notification_level, category_id)
|
||||
render json: success_json
|
||||
end
|
||||
|
||||
|
||||
@ -364,7 +364,9 @@ class TopicsController < ApplicationController
|
||||
topic_ids = params[:topic_ids].map {|t| t.to_i}
|
||||
elsif params[:filter] == 'unread'
|
||||
tq = TopicQuery.new(current_user)
|
||||
topic_ids = TopicQuery.unread_filter(tq.joined_topic_user).listable_topics.pluck(:id)
|
||||
topics = TopicQuery.unread_filter(tq.joined_topic_user).listable_topics
|
||||
topics = topics.where('category_id = ?', params[:category_id]) if params[:category_id]
|
||||
topic_ids = topics.pluck(:id)
|
||||
else
|
||||
raise ActionController::ParameterMissing.new(:topic_ids)
|
||||
end
|
||||
@ -439,7 +441,7 @@ class TopicsController < ApplicationController
|
||||
end
|
||||
|
||||
def perform_show_response
|
||||
topic_view_serializer = TopicViewSerializer.new(@topic_view, scope: guardian, root: false)
|
||||
topic_view_serializer = TopicViewSerializer.new(@topic_view, scope: guardian, root: false, include_raw: !!params[:include_raw])
|
||||
|
||||
respond_to do |format|
|
||||
format.html do
|
||||
|
||||
@ -245,7 +245,7 @@ class UsersController < ApplicationController
|
||||
activation.finish
|
||||
|
||||
# save user email in session, to show on account-created page
|
||||
session["user_created_email"] = user.email
|
||||
session["user_created_message"] = activation.message
|
||||
|
||||
render json: {
|
||||
success: true,
|
||||
@ -364,6 +364,7 @@ class UsersController < ApplicationController
|
||||
end
|
||||
|
||||
def account_created
|
||||
@message = session['user_created_message']
|
||||
expires_now
|
||||
render layout: 'no_js'
|
||||
end
|
||||
|
||||
@ -1,13 +1,5 @@
|
||||
module UserNotificationsHelper
|
||||
|
||||
def self.sanitize_options
|
||||
return @sanitize_options if @sanitize_options
|
||||
@sanitize_options = Sanitize::Config::RELAXED.deep_dup
|
||||
@sanitize_options[:elements] << 'aside' << 'div'
|
||||
@sanitize_options[:attributes][:all] << 'class'
|
||||
@sanitize_options
|
||||
end
|
||||
|
||||
def indent(text, by=2)
|
||||
spacer = " " * by
|
||||
result = ""
|
||||
@ -57,21 +49,15 @@ module UserNotificationsHelper
|
||||
end
|
||||
|
||||
def email_excerpt(html, posts_count)
|
||||
# If there's only one post, include the whole thing.
|
||||
if posts_count == 1
|
||||
raw Sanitize.clean(html, UserNotificationsHelper.sanitize_options)
|
||||
else
|
||||
# Otherwise, try just the first paragraph.
|
||||
para = first_paragraph_from(html)
|
||||
raw Sanitize.clean(para.to_s, UserNotificationsHelper.sanitize_options)
|
||||
end
|
||||
# only include 1st paragraph when more than 1 posts
|
||||
html = first_paragraph_from(html).to_s if posts_count > 1
|
||||
raw format_for_email(html)
|
||||
end
|
||||
|
||||
def cooked_post_for_email(post)
|
||||
PrettyText.format_for_email(post.cooked).html_safe
|
||||
def format_for_email(html)
|
||||
PrettyText.format_for_email(html).html_safe
|
||||
end
|
||||
|
||||
|
||||
def email_category(category, opts=nil)
|
||||
opts = opts || {}
|
||||
|
||||
|
||||
@ -26,7 +26,7 @@ module Jobs
|
||||
user_data.each do |user|
|
||||
user_array = Array.new
|
||||
group_names = get_group_names(user).join(';')
|
||||
user_array.push(user['id']).push(user['name']).push(user['username']).push(user['email'])
|
||||
user_array = get_user_fields(user)
|
||||
user_array.push(group_names) if group_names != ''
|
||||
data.push(user_array)
|
||||
end
|
||||
@ -51,6 +51,22 @@ module Jobs
|
||||
return group_names
|
||||
end
|
||||
|
||||
def get_user_fields(user)
|
||||
csv_user_attrs = ['id','name','username','email','created_at']
|
||||
csv_user_stats_attr = ['topics_entered','posts_read_count','time_read','topic_count','post_count','likes_given','likes_received']
|
||||
user_array = []
|
||||
|
||||
csv_user_attrs.each do |user_attr|
|
||||
user_array.push(user.attributes[user_attr])
|
||||
end
|
||||
|
||||
csv_user_stats_attr.each do |user_stat_attr|
|
||||
user_array.push(user.user_stat.attributes[user_stat_attr])
|
||||
end
|
||||
|
||||
return user_array
|
||||
end
|
||||
|
||||
def set_file_path
|
||||
@file_name = "export_#{SecureRandom.hex(4)}.csv"
|
||||
# ensure directory exists
|
||||
|
||||
@ -250,9 +250,7 @@ class UserNotifications < ActionMailer::Base
|
||||
)
|
||||
|
||||
template = "user_notifications.user_#{notification_type}"
|
||||
if post.topic.private_message?
|
||||
template << "_pm"
|
||||
end
|
||||
template << "_pm" if post.topic.private_message?
|
||||
|
||||
email_opts = {
|
||||
topic_title: title,
|
||||
|
||||
@ -16,19 +16,17 @@ class CategoryUser < ActiveRecord::Base
|
||||
end
|
||||
|
||||
def self.auto_track_new_topic(topic)
|
||||
apply_default_to_topic(
|
||||
topic,
|
||||
TopicUser.notification_levels[:tracking],
|
||||
TopicUser.notification_reasons[:auto_track_category]
|
||||
)
|
||||
apply_default_to_topic(topic,
|
||||
TopicUser.notification_levels[:tracking],
|
||||
TopicUser.notification_reasons[:auto_track_category]
|
||||
)
|
||||
end
|
||||
|
||||
def self.auto_watch_new_topic(topic)
|
||||
apply_default_to_topic(
|
||||
topic,
|
||||
TopicUser.notification_levels[:watching],
|
||||
TopicUser.notification_reasons[:auto_watch_category]
|
||||
)
|
||||
apply_default_to_topic(topic,
|
||||
TopicUser.notification_levels[:watching],
|
||||
TopicUser.notification_reasons[:auto_watch_category]
|
||||
)
|
||||
end
|
||||
|
||||
def self.batch_set(user, level, category_ids)
|
||||
@ -49,8 +47,6 @@ class CategoryUser < ActiveRecord::Base
|
||||
|
||||
def self.set_notification_level_for_category(user, level, category_id)
|
||||
record = CategoryUser.where(user: user, category_id: category_id).first
|
||||
# oder CategoryUser.where(user: user, category_id: category_id).destroy_all
|
||||
# und danach mir create anlegen.
|
||||
|
||||
if record.present?
|
||||
record.notification_level = level
|
||||
@ -62,23 +58,21 @@ class CategoryUser < ActiveRecord::Base
|
||||
|
||||
def self.apply_default_to_topic(topic, level, reason)
|
||||
# Can not afford to slow down creation of topics when a pile of users are watching new topics, reverting to SQL for max perf here
|
||||
sql = <<SQL
|
||||
INSERT INTO topic_users(user_id, topic_id, notification_level, notifications_reason_id)
|
||||
SELECT user_id, :topic_id, :level, :reason
|
||||
FROM category_users
|
||||
WHERE notification_level = :level AND
|
||||
category_id = :category_id AND
|
||||
NOT EXISTS(SELECT 1 FROM topic_users WHERE topic_id = :topic_id AND user_id = category_users.user_id)
|
||||
SQL
|
||||
sql = <<-SQL
|
||||
INSERT INTO topic_users(user_id, topic_id, notification_level, notifications_reason_id)
|
||||
SELECT user_id, :topic_id, :level, :reason
|
||||
FROM category_users
|
||||
WHERE notification_level = :level
|
||||
AND category_id = :category_id
|
||||
AND NOT EXISTS(SELECT 1 FROM topic_users WHERE topic_id = :topic_id AND user_id = category_users.user_id)
|
||||
SQL
|
||||
|
||||
exec_sql(
|
||||
sql,
|
||||
topic_id: topic.id,
|
||||
category_id: topic.category_id,
|
||||
level: level,
|
||||
reason: reason
|
||||
|
||||
)
|
||||
exec_sql(sql,
|
||||
topic_id: topic.id,
|
||||
category_id: topic.category_id,
|
||||
level: level,
|
||||
reason: reason
|
||||
)
|
||||
end
|
||||
|
||||
private_class_method :apply_default_to_topic
|
||||
|
||||
@ -13,8 +13,8 @@ class EmailLog < ActiveRecord::Base
|
||||
User.where(id: user_id).update_all("last_emailed_at = CURRENT_TIMESTAMP") if user_id.present? and !skipped
|
||||
end
|
||||
|
||||
def self.count_per_day(sinceDaysAgo = 30)
|
||||
where('created_at > ? and skipped = false', sinceDaysAgo.days.ago).group('date(created_at)').order('date(created_at)').count
|
||||
def self.count_per_day(start_date, end_date)
|
||||
where('created_at >= ? and created_at < ? AND skipped = false', start_date, end_date).group('date(created_at)').order('date(created_at)').count
|
||||
end
|
||||
|
||||
def self.for(reply_key)
|
||||
@ -22,8 +22,7 @@ class EmailLog < ActiveRecord::Base
|
||||
end
|
||||
|
||||
def self.last_sent_email_address
|
||||
where(email_type: 'signup').order('created_at DESC')
|
||||
.first.try(:to_address)
|
||||
where(email_type: 'signup').order('created_at DESC').first.try(:to_address)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@ -31,9 +31,15 @@ class OptimizedImage < ActiveRecord::Base
|
||||
extension = File.extname(original_path)
|
||||
temp_file = Tempfile.new(["discourse-thumbnail", extension])
|
||||
temp_path = temp_file.path
|
||||
original_path += "[0]" unless opts[:allow_animation]
|
||||
|
||||
if resize(original_path, temp_path, width, height)
|
||||
if extension =~ /\.svg$/i
|
||||
FileUtils.cp(original_path, temp_path)
|
||||
resized = true
|
||||
else
|
||||
resized = resize(original_path, temp_path, width, height, opts[:allow_animation])
|
||||
end
|
||||
|
||||
if resized
|
||||
thumbnail = OptimizedImage.create!(
|
||||
upload_id: upload.id,
|
||||
sha1: Digest::SHA1.file(temp_path).hexdigest,
|
||||
@ -72,7 +78,8 @@ class OptimizedImage < ActiveRecord::Base
|
||||
end
|
||||
end
|
||||
|
||||
def self.resize(from, to, width, height)
|
||||
def self.resize(from, to, width, height, allow_animation=false)
|
||||
from << "[0]" unless allow_animation
|
||||
# NOTE: ORDER is important!
|
||||
instructions = %W{
|
||||
#{from}
|
||||
|
||||
@ -460,8 +460,8 @@ class Post < ActiveRecord::Base
|
||||
Jobs.enqueue(:process_post, args)
|
||||
end
|
||||
|
||||
def self.public_posts_count_per_day(since_days_ago=30)
|
||||
public_posts.where('posts.created_at > ?', since_days_ago.days.ago).group('date(posts.created_at)').order('date(posts.created_at)').count
|
||||
def self.public_posts_count_per_day(start_date, end_date)
|
||||
public_posts.where('posts.created_at >= ? AND posts.created_at <= ?', start_date, end_date).group('date(posts.created_at)').order('date(posts.created_at)').count
|
||||
end
|
||||
|
||||
def self.private_messages_count_per_day(since_days_ago, topic_subtype)
|
||||
|
||||
@ -2,39 +2,51 @@ require_dependency 'topic_subtype'
|
||||
|
||||
class Report
|
||||
|
||||
attr_accessor :type, :data, :total, :prev30Days
|
||||
attr_accessor :type, :data, :total, :prev30Days, :start_date, :end_date
|
||||
|
||||
def self.default_days
|
||||
30
|
||||
end
|
||||
|
||||
def initialize(type)
|
||||
@type = type
|
||||
@data = nil
|
||||
@total = nil
|
||||
@prev30Days = nil
|
||||
@start_date ||= 1.month.ago
|
||||
@end_date ||= Time.now
|
||||
end
|
||||
|
||||
def as_json(options = nil)
|
||||
{
|
||||
type: self.type,
|
||||
title: I18n.t("reports.#{self.type}.title"),
|
||||
xaxis: I18n.t("reports.#{self.type}.xaxis"),
|
||||
yaxis: I18n.t("reports.#{self.type}.yaxis"),
|
||||
data: self.data,
|
||||
total: self.total,
|
||||
type: type,
|
||||
title: I18n.t("reports.#{type}.title"),
|
||||
xaxis: I18n.t("reports.#{type}.xaxis"),
|
||||
yaxis: I18n.t("reports.#{type}.yaxis"),
|
||||
data: data,
|
||||
total: total,
|
||||
start_date: start_date,
|
||||
end_date: end_date,
|
||||
prev30Days: self.prev30Days
|
||||
}
|
||||
end
|
||||
|
||||
def self.find(type, opts={})
|
||||
def self.find(type, opts=nil)
|
||||
opts ||= {}
|
||||
report_method = :"report_#{type}"
|
||||
return nil unless respond_to?(report_method)
|
||||
|
||||
# Load the report
|
||||
report = Report.new(type)
|
||||
|
||||
report.start_date = opts[:start_date] if opts[:start_date]
|
||||
report.end_date = opts[:end_date] if opts[:end_date]
|
||||
send(report_method, report)
|
||||
report
|
||||
end
|
||||
|
||||
def self.report_visits(report)
|
||||
basic_report_about report, UserVisit, :by_day
|
||||
basic_report_about report, UserVisit, :by_day, report.start_date, report.end_date
|
||||
end
|
||||
|
||||
def self.report_signups(report)
|
||||
@ -42,15 +54,15 @@ class Report
|
||||
end
|
||||
|
||||
def self.report_topics(report)
|
||||
basic_report_about report, Topic, :listable_count_per_day
|
||||
basic_report_about report, Topic, :listable_count_per_day, report.start_date, report.end_date
|
||||
report.total = Topic.listable_topics.count
|
||||
report.prev30Days = Topic.listable_topics.where('created_at > ? and created_at < ?', 60.days.ago, 30.days.ago).count
|
||||
report.prev30Days = Topic.listable_topics.where('created_at > ? and created_at < ?', report.start_date - 30.days, report.start_date).count
|
||||
end
|
||||
|
||||
def self.report_posts(report)
|
||||
basic_report_about report, Post, :public_posts_count_per_day
|
||||
basic_report_about report, Post, :public_posts_count_per_day, report.start_date, report.end_date
|
||||
report.total = Post.public_posts.count
|
||||
report.prev30Days = Post.public_posts.where('posts.created_at > ? and posts.created_at < ?', 60.days.ago, 30.days.ago).count
|
||||
report.prev30Days = Post.public_posts.where('posts.created_at > ? and posts.created_at < ?', report.start_date - 30.days, report.start_date).count
|
||||
end
|
||||
|
||||
def self.report_emails(report)
|
||||
@ -58,20 +70,20 @@ class Report
|
||||
end
|
||||
|
||||
def self.report_about(report, subject_class, report_method = :count_per_day)
|
||||
basic_report_about report, subject_class, report_method
|
||||
basic_report_about report, subject_class, report_method, report.start_date, report.end_date
|
||||
add_counts(report, subject_class)
|
||||
end
|
||||
|
||||
def self.basic_report_about(report, subject_class, report_method, *args)
|
||||
report.data = []
|
||||
subject_class.send(report_method, 30, *args).each do |date, count|
|
||||
subject_class.send(report_method, *args).each do |date, count|
|
||||
report.data << {x: date, y: count}
|
||||
end
|
||||
end
|
||||
|
||||
def self.add_counts(report, subject_class)
|
||||
report.total = subject_class.count
|
||||
report.prev30Days = subject_class.where('created_at > ? and created_at < ?', 60.days.ago, 30.days.ago).count
|
||||
report.prev30Days = subject_class.where('created_at > ? and created_at < ?', report.start_date - 30.days, report.start_date).count
|
||||
end
|
||||
|
||||
def self.report_users_by_trust_level(report)
|
||||
@ -82,10 +94,10 @@ class Report
|
||||
end
|
||||
|
||||
def self.report_starred(report)
|
||||
basic_report_about report, Topic, :starred_counts_per_day
|
||||
basic_report_about report, Topic, :starred_counts_per_day, default_days
|
||||
query = TopicUser.where(starred: true)
|
||||
report.total = query.count
|
||||
report.prev30Days = query.where('starred_at > ? and starred_at < ?', 60.days.ago, 30.days.ago).count
|
||||
report.prev30Days = query.where('starred_at > ? and starred_at < ?', report.start_date - 30.days, report.start_date).count
|
||||
end
|
||||
|
||||
# Post action counts:
|
||||
@ -102,7 +114,7 @@ class Report
|
||||
end
|
||||
flagsQuery = PostAction.where(post_action_type_id: PostActionType.flag_types.values)
|
||||
report.total = flagsQuery.count
|
||||
report.prev30Days = flagsQuery.where('created_at > ? and created_at < ?', 60.days.ago, 30.days.ago).count
|
||||
report.prev30Days = flagsQuery.where('created_at > ? and created_at < ?', report.start_date - 30.days, report.start_date).count
|
||||
end
|
||||
|
||||
def self.report_likes(report)
|
||||
@ -120,15 +132,15 @@ class Report
|
||||
end
|
||||
query = PostAction.unscoped.where(post_action_type_id: post_action_type)
|
||||
report.total = query.count
|
||||
report.prev30Days = query.where('created_at > ? and created_at < ?', 60.days.ago, 30.days.ago).count
|
||||
report.prev30Days = query.where('created_at > ? and created_at < ?', report.start_date - 30.days, report.start_date).count
|
||||
end
|
||||
|
||||
# Private messages counts:
|
||||
|
||||
def self.private_messages_report(report, topic_subtype)
|
||||
basic_report_about report, Post, :private_messages_count_per_day, topic_subtype
|
||||
basic_report_about report, Post, :private_messages_count_per_day, default_days, topic_subtype
|
||||
report.total = Post.private_posts.with_topic_subtype(topic_subtype).count
|
||||
report.prev30Days = Post.private_posts.with_topic_subtype(topic_subtype).where('posts.created_at > ? and posts.created_at < ?', 60.days.ago, 30.days.ago).count
|
||||
report.prev30Days = Post.private_posts.with_topic_subtype(topic_subtype).where('posts.created_at > ? and posts.created_at < ?', report.start_date - 30.days, report.start_date).count
|
||||
end
|
||||
|
||||
def self.report_user_to_user_private_messages(report)
|
||||
|
||||
@ -345,8 +345,8 @@ class Topic < ActiveRecord::Base
|
||||
custom_fields[key.to_s]
|
||||
end
|
||||
|
||||
def self.listable_count_per_day(sinceDaysAgo=30)
|
||||
listable_topics.where('created_at > ?', sinceDaysAgo.days.ago).group('date(created_at)').order('date(created_at)').count
|
||||
def self.listable_count_per_day(start_date, end_date)
|
||||
listable_topics.where('created_at >= ? and created_at < ?', start_date, end_date).group('date(created_at)').order('date(created_at)').count
|
||||
end
|
||||
|
||||
def private_message?
|
||||
@ -421,9 +421,9 @@ class Topic < ActiveRecord::Base
|
||||
WHEN last_read_post_number > :highest THEN :highest
|
||||
ELSE last_read_post_number
|
||||
END,
|
||||
seen_post_count = CASE
|
||||
WHEN seen_post_count > :highest THEN :highest
|
||||
ELSE seen_post_count
|
||||
highest_seen_post_number = CASE
|
||||
WHEN highest_seen_post_number > :highest THEN :highest
|
||||
ELSE highest_seen_post_number
|
||||
END
|
||||
WHERE topic_id = :topic_id",
|
||||
highest: highest_post_number,
|
||||
|
||||
@ -152,8 +152,8 @@ class TopicUser < ActiveRecord::Base
|
||||
threshold: SiteSetting.auto_track_topics_after
|
||||
}
|
||||
|
||||
# In case anyone seens "seen_post_count" and gets confused, like I do.
|
||||
# seen_post_count represents the highest_post_number of the topic when
|
||||
# In case anyone seens "highest_seen_post_number" and gets confused, like I do.
|
||||
# highest_seen_post_number represents the highest_post_number of the topic when
|
||||
# the user visited it. It may be out of alignment with last_read, meaning
|
||||
# ... user visited the topic but did not read the posts
|
||||
#
|
||||
@ -161,7 +161,7 @@ class TopicUser < ActiveRecord::Base
|
||||
rows = exec_sql("UPDATE topic_users
|
||||
SET
|
||||
last_read_post_number = GREATEST(:post_number, tu.last_read_post_number),
|
||||
seen_post_count = t.highest_post_number,
|
||||
highest_seen_post_number = t.highest_post_number,
|
||||
total_msecs_viewed = LEAST(tu.total_msecs_viewed + :msecs,86400000),
|
||||
notification_level =
|
||||
case when tu.notifications_reason_id is null and (tu.total_msecs_viewed + :msecs) >
|
||||
@ -210,7 +210,7 @@ class TopicUser < ActiveRecord::Base
|
||||
TopicTrackingState.publish_read(topic_id, post_number, user.id, args[:new_status])
|
||||
user.update_posts_read!(post_number)
|
||||
|
||||
exec_sql("INSERT INTO topic_users (user_id, topic_id, last_read_post_number, seen_post_count, last_visited_at, first_visited_at, notification_level)
|
||||
exec_sql("INSERT INTO topic_users (user_id, topic_id, last_read_post_number, highest_seen_post_number, last_visited_at, first_visited_at, notification_level)
|
||||
SELECT :user_id, :topic_id, :post_number, ft.highest_post_number, :now, :now, :new_status
|
||||
FROM topics AS ft
|
||||
JOIN users u on u.id = :user_id
|
||||
@ -241,7 +241,7 @@ class TopicUser < ActiveRecord::Base
|
||||
UPDATE topic_users t
|
||||
SET
|
||||
last_read_post_number = LEAST(GREATEST(last_read, last_read_post_number), max_post_number),
|
||||
seen_post_count = LEAST(max_post_number,GREATEST(t.seen_post_count, last_read))
|
||||
highest_seen_post_number = LEAST(max_post_number,GREATEST(t.highest_seen_post_number, last_read))
|
||||
FROM (
|
||||
SELECT topic_id, user_id, MAX(post_number) last_read
|
||||
FROM post_timings
|
||||
@ -259,7 +259,7 @@ X.topic_id = t.topic_id AND
|
||||
X.user_id = t.user_id AND
|
||||
(
|
||||
last_read_post_number <> LEAST(GREATEST(last_read, last_read_post_number), max_post_number) OR
|
||||
seen_post_count <> LEAST(max_post_number,GREATEST(t.seen_post_count, last_read))
|
||||
highest_seen_post_number <> LEAST(max_post_number,GREATEST(t.highest_seen_post_number, last_read))
|
||||
)
|
||||
SQL
|
||||
|
||||
@ -281,7 +281,7 @@ end
|
||||
# starred :boolean default(FALSE), not null
|
||||
# posted :boolean default(FALSE), not null
|
||||
# last_read_post_number :integer
|
||||
# seen_post_count :integer
|
||||
# highest_seen_post_number :integer
|
||||
# starred_at :datetime
|
||||
# last_visited_at :datetime
|
||||
# first_visited_at :datetime
|
||||
|
||||
@ -75,13 +75,24 @@ class Upload < ActiveRecord::Base
|
||||
# deal with width & height for images
|
||||
if FileHelper.is_image?(filename)
|
||||
begin
|
||||
# fix orientation first
|
||||
Upload.fix_image_orientation(file.path)
|
||||
# retrieve image info
|
||||
image_info = FastImage.new(file, raise_on_failure: true)
|
||||
# compute image aspect ratio
|
||||
upload.width, upload.height = ImageSizer.resize(*image_info.size)
|
||||
# make sure we're at the beginning of the file (FastImage moves the pointer)
|
||||
if filename =~ /\.svg$/i
|
||||
svg = Nokogiri::XML(file).at_css("svg")
|
||||
width, height = svg["width"].to_i, svg["height"].to_i
|
||||
if width == 0 || height == 0
|
||||
upload.errors.add(:base, I18n.t("upload.images.size_not_found"))
|
||||
else
|
||||
upload.width, upload.height = ImageSizer.resize(width, height)
|
||||
end
|
||||
else
|
||||
# fix orientation first
|
||||
Upload.fix_image_orientation(file.path)
|
||||
# retrieve image info
|
||||
image_info = FastImage.new(file, raise_on_failure: true)
|
||||
# compute image aspect ratio
|
||||
upload.width, upload.height = ImageSizer.resize(*image_info.size)
|
||||
end
|
||||
# make sure we're at the beginning of the file
|
||||
# (FastImage and Nokogiri move the pointer)
|
||||
file.rewind
|
||||
rescue FastImage::ImageFetchFailure
|
||||
upload.errors.add(:base, I18n.t("upload.images.fetch_failure"))
|
||||
|
||||
@ -531,8 +531,8 @@ class User < ActiveRecord::Base
|
||||
.limit(3)
|
||||
end
|
||||
|
||||
def self.count_by_signup_date(sinceDaysAgo=30)
|
||||
where('created_at > ?', sinceDaysAgo.days.ago).group('date(created_at)').order('date(created_at)').count
|
||||
def self.count_by_signup_date(start_date, end_date)
|
||||
where('created_at >= ? and created_at < ?', start_date, end_date).group('date(created_at)').order('date(created_at)').count
|
||||
end
|
||||
|
||||
|
||||
|
||||
@ -32,7 +32,8 @@ class UserHistory < ActiveRecord::Base
|
||||
:auto_trust_level_change,
|
||||
:check_email,
|
||||
:delete_post,
|
||||
:delete_topic)
|
||||
:delete_topic,
|
||||
:impersonate)
|
||||
end
|
||||
|
||||
# Staff actions is a subset of all actions, used to audit actions taken by staff users.
|
||||
@ -48,7 +49,8 @@ class UserHistory < ActiveRecord::Base
|
||||
:revoke_badge,
|
||||
:check_email,
|
||||
:delete_post,
|
||||
:delete_topic]
|
||||
:delete_topic,
|
||||
:impersonate]
|
||||
end
|
||||
|
||||
def self.staff_action_ids
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
class UserVisit < ActiveRecord::Base
|
||||
|
||||
# A count of visits in the last month by day
|
||||
def self.by_day(sinceDaysAgo=30)
|
||||
where("visited_at >= ?", sinceDaysAgo.days.ago).group(:visited_at).order(:visited_at).count
|
||||
def self.by_day(start_date, end_date)
|
||||
where("visited_at >= ? and visited_at < ?", start_date, end_date).group(:visited_at).order(:visited_at).count
|
||||
end
|
||||
|
||||
def self.ensure_consistency!
|
||||
|
||||
@ -39,7 +39,7 @@ class AdminUserSerializer < BasicUserSerializer
|
||||
|
||||
def include_email?
|
||||
# staff members can always see their email
|
||||
scope.is_staff? && object.id == scope.user.id
|
||||
(scope.is_staff? && object.id == scope.user.id) || scope.can_see_emails?
|
||||
end
|
||||
|
||||
alias_method :include_associated_accounts?, :include_email?
|
||||
|
||||
@ -21,6 +21,7 @@ module PostStreamSerializerMixin
|
||||
object.posts.each_with_index do |p, idx|
|
||||
highest_number_in_posts = p.post_number if p.post_number > highest_number_in_posts
|
||||
ps = PostSerializer.new(p, scope: scope, root: false)
|
||||
ps.add_raw = true if @options[:include_raw]
|
||||
ps.topic_view = object
|
||||
p.topic = object.topic
|
||||
|
||||
|
||||
@ -142,10 +142,32 @@ class StaffActionLogger
|
||||
}))
|
||||
end
|
||||
|
||||
def log_show_emails(users)
|
||||
values = []
|
||||
|
||||
users.each do |user|
|
||||
values << "(#{@admin.id}, #{UserHistory.actions[:check_email]}, #{user.id}, current_timestamp, current_timestamp)"
|
||||
end
|
||||
|
||||
# bulk insert
|
||||
UserHistory.exec_sql <<-SQL
|
||||
INSERT INTO user_histories (acting_user_id, action, target_user_id, created_at, updated_at)
|
||||
VALUES #{values.join(",")}
|
||||
SQL
|
||||
end
|
||||
|
||||
def log_impersonate(user, opts={})
|
||||
raise Discourse::InvalidParameters.new("user is nil") unless user
|
||||
UserHistory.create(params(opts).merge({
|
||||
action: UserHistory.actions[:impersonate],
|
||||
target_user_id: user.id
|
||||
}))
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def params(opts)
|
||||
{acting_user_id: @admin.id, context: opts[:context]}
|
||||
{ acting_user_id: @admin.id, context: opts[:context] }
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@ -10,7 +10,7 @@
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class='body'><%= cooked_post_for_email(post) %></td>
|
||||
<td class='body'><%= format_for_email(post.cooked) %></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@ -10,10 +10,7 @@
|
||||
<%= raw(@markdown_linker.create(t.title, t.relative_url)) %>
|
||||
|
||||
<%- if t.best_post.present? %>
|
||||
<%= raw(t.best_post.excerpt(1000,
|
||||
strip_links: true,
|
||||
text_entities: true,
|
||||
markdown_images: true)) %>
|
||||
<%= raw(t.best_post.excerpt(1000, strip_links: true, text_entities: true, markdown_images: true)) %>
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
<%- end %>
|
||||
|
||||
@ -2,6 +2,6 @@
|
||||
(function() {
|
||||
setTimeout(function() {
|
||||
window.location.href = '/';
|
||||
}, 5000);
|
||||
}, 2000);
|
||||
})();
|
||||
</script>
|
||||
@ -1,5 +1,3 @@
|
||||
<div id='simple-container'>
|
||||
<% if session["user_created_email"] %>
|
||||
<span style="font-size: 16px; line-height: 24px;"><%= t('login.activate_email', email: session["user_created_email"]).html_safe %></span>
|
||||
<% end %>
|
||||
<span style="font-size: 16px; line-height: 24px;"><%= @message.html_safe %></span>
|
||||
</div>
|
||||
|
||||
@ -359,7 +359,6 @@ cs:
|
||||
frequency: "Budeme vás informovat emailem pouze pokud jste se na našem webu dlouho neukázali a pokud jste obsah, o kterém vás chceme informovat, doposud neviděli."
|
||||
name:
|
||||
title: "Jméno"
|
||||
instructions: "Vaše celé jméno."
|
||||
too_short: "Vaše jméno je příliš krátké."
|
||||
ok: "Vaše jméno vypadá dobře"
|
||||
username:
|
||||
@ -387,6 +386,8 @@ cs:
|
||||
created: "Účet vytvořen"
|
||||
log_out: "Odhlásit se"
|
||||
location: "Lokace"
|
||||
card_badge:
|
||||
title: "User Card Badge"
|
||||
website: "Webová stránka"
|
||||
email_settings: "Emailová upozornění"
|
||||
email_digests:
|
||||
@ -589,6 +590,7 @@ cs:
|
||||
drafts_offline: "koncepty offline"
|
||||
min_length:
|
||||
need_more_for_title: "ještě {{n}} znaků nadpisu tématu"
|
||||
need_more_for_reply: "ještě {{n}} znaků"
|
||||
error:
|
||||
title_missing: "Název musí být vyplněn"
|
||||
title_too_short: "Název musí být dlouhý alespoň {{min}} znaků"
|
||||
@ -612,6 +614,7 @@ cs:
|
||||
view_new_post: "Zobrazit váš nový příspěvek."
|
||||
saving: "Ukládám..."
|
||||
saved: "Uloženo!"
|
||||
saved_draft: "Máte rozepsaný příspěvek. Klikněte pro obnovení."
|
||||
uploading: "Nahrávám..."
|
||||
show_preview: 'zobrazit náhled »'
|
||||
hide_preview: '« skrýt náhled'
|
||||
@ -906,7 +909,6 @@ cs:
|
||||
one: "Je zobrazen pouze 1 příspěvek"
|
||||
few: "Jsou zobrazeny pouze {{count}} příspěvky"
|
||||
other: "Je zobrazeno pouze {{count}} příspěvků"
|
||||
cancel: "Zobrazí znovu všechny příspěvky v tomto tématu."
|
||||
split_topic:
|
||||
title: "Rozdělit téma"
|
||||
action: "do nového téma"
|
||||
@ -1375,8 +1377,6 @@ cs:
|
||||
7_days_ago: "Týden"
|
||||
30_days_ago: "Měsíc"
|
||||
all: "Celkem"
|
||||
view_table: "Zobrazit jako tabulku"
|
||||
view_chart: "Zobrazit jako sloupcový graf"
|
||||
commits:
|
||||
latest_changes: "Poslední změny:"
|
||||
by: "od"
|
||||
@ -1883,6 +1883,7 @@ cs:
|
||||
star: '<b>f</b> Star topic'
|
||||
share_topic: '<b>shift s</b> Sdílet téma'
|
||||
share_post: '<b>s</b> Share post'
|
||||
reply_as_new_topic: '<b>t</b> Odpovědět v propojeném tématu'
|
||||
reply_topic: '<b>shift r</b> Odpovědět na téma'
|
||||
reply_post: '<b>r</b> Odpovědět na příspěvek'
|
||||
quote_post: '<b>q</b> Citovat příspěvek'
|
||||
@ -1910,6 +1911,7 @@ cs:
|
||||
few: "1 udělen"
|
||||
other: "%{count} uděleno"
|
||||
select_badge_for_title: Vyberte odznak, který chcete použít jako svůj titul
|
||||
none: "<none>"
|
||||
badge_grouping:
|
||||
other:
|
||||
name: Ostatní
|
||||
|
||||
@ -824,7 +824,6 @@ da:
|
||||
n_posts:
|
||||
one: "1 indlæg"
|
||||
other: "{{count}} indlæg"
|
||||
cancel: "Se alle indlæg i emnet."
|
||||
split_topic:
|
||||
title: "Flyt til nyt emne"
|
||||
action: "flyt til nyt emne"
|
||||
@ -1263,8 +1262,6 @@ da:
|
||||
7_days_ago: "7 dage siden"
|
||||
30_days_ago: "30 dage siden"
|
||||
all: "Alle"
|
||||
view_table: "Vis som tabel"
|
||||
view_chart: "Vis som søjlediagram"
|
||||
commits:
|
||||
latest_changes: "Seneste ændringer: opdatér ofte!"
|
||||
by: "af"
|
||||
|
||||
@ -277,6 +277,7 @@ de:
|
||||
admin_tooltip: "Dieser Benutzer ist ein Administrator"
|
||||
suspended_notice: "Dieser Benutzer ist bis zum {{date}} gesperrt."
|
||||
suspended_reason: "Grund: "
|
||||
github_profile: "Github"
|
||||
mailing_list_mode: "Erhalte bei jedem neuen Beitrag eine E-Mail (sofern du das Thema oder die Kategorie nicht stummgeschaltet hast)"
|
||||
watched_categories: "Beobachtet"
|
||||
watched_categories_instructions: "Du wirst in diesen Kategorien automatisch alle neuen Themen beobachten. Du wirst über alle neuen Beiträge und Themen benachrichtigt werden. Außerdem wird die Anzahl der ungelesenen und neuen Beiträge neben dem Eintrag in der Themenliste angezeigt."
|
||||
@ -343,7 +344,7 @@ de:
|
||||
frequency: "Wir werden dir nur E-Mails senden, wenn wir dich kürzlich nicht gesehen haben und wenn du die betroffenen Inhalte noch nicht gesehen hast."
|
||||
name:
|
||||
title: "Name"
|
||||
instructions: "Dein vollständiger Name."
|
||||
instructions: "Ihr vollständiger Name (optional)."
|
||||
too_short: "Dein Name ist zu kurz."
|
||||
ok: "Dein Name schaut gut aus."
|
||||
username:
|
||||
@ -511,8 +512,7 @@ de:
|
||||
created: 'Erstellt'
|
||||
created_lowercase: 'erstellt'
|
||||
trust_level: 'Vertrauensstufe'
|
||||
search_hint: 'Benutzername oder IP-Adresse'
|
||||
search_hint_admin: 'Benutzername, E-Mail- oder IP-Adresse'
|
||||
search_hint: 'Benutzername, E-Mail- oder IP-Adresse'
|
||||
create_account:
|
||||
title: "Neues Benutzerkonto erstellen"
|
||||
failed: "Etwas ist fehlgeschlagen. Vielleicht ist diese E-Mail-Adresse bereits registriert. Versuche den 'Passwort vergessen'-Link."
|
||||
@ -712,6 +712,8 @@ de:
|
||||
hot: "Es gibt keine beliebten Themen."
|
||||
category: "Es gibt keine Themen in {{category}}."
|
||||
top: "Es gibt keine Top-Themen."
|
||||
educate:
|
||||
new: '<p>Hier werden neue Themen angezeigt.</p><p>Standardmäßig werden jene Themen als neu angesehen und mit dem <span class="badge new-topic badge-notification" style="vertical-align:middle;line-height:inherit;">neu</span> Indikator versehen, die in den letzten 2 Tagen erstellt wurden.</p><p>Du kannst das in deinen <a href="%{userPrefsUrl}">Einstellungen</a> ändern.</p>'
|
||||
bottom:
|
||||
latest: "Das waren die aktuellen Themen."
|
||||
hot: "Das waren alle beliebten Themen."
|
||||
@ -881,7 +883,7 @@ de:
|
||||
n_posts:
|
||||
one: "1 Beitrag"
|
||||
other: "{{count}} Beiträge"
|
||||
cancel: "Zeige in diesem Thema wieder alle Beiträge an."
|
||||
cancel: "Filter entfernen"
|
||||
split_topic:
|
||||
title: "In neues Thema verschieben"
|
||||
action: "in ein neues Thema verschieben"
|
||||
@ -1181,6 +1183,9 @@ de:
|
||||
submit_tooltip: "Private Meldung abschicken"
|
||||
take_action_tooltip: "Den Meldungsschwellenwert sofort erreichen, anstatt auf weitere Meldungen aus der Community zu warten."
|
||||
cant: "Entschuldige, Du kannst diesen Beitrag augenblicklich nicht melden."
|
||||
formatted_name:
|
||||
inappropriate: "Es ist unangemessen"
|
||||
spam: "Es ist Spam"
|
||||
custom_placeholder_notify_user: "Weshalb erfordert der Beitrag, dass du den Benutzer direkt und privat kontaktieren möchtest? Sei spezifisch, konstruktiv und immer freundlich."
|
||||
custom_placeholder_notify_moderators: "Warum soll ein Moderator sich diesen Beitrag ansehen? Bitte lass uns wissen, was genau Dich beunruhigt, und wenn möglich dafür relevante Links."
|
||||
custom_message:
|
||||
@ -1239,6 +1244,7 @@ de:
|
||||
history: "Verlauf"
|
||||
changed_by: "von {{author}}"
|
||||
raw_email:
|
||||
title: "Unverarbeitete E-Mail"
|
||||
not_available: "Nicht verfügbar!"
|
||||
categories_list: "Liste der Kategorien"
|
||||
filters:
|
||||
@ -1346,8 +1352,11 @@ de:
|
||||
7_days_ago: "vor 7 Tagen"
|
||||
30_days_ago: "vor 30 Tagen"
|
||||
all: "alle"
|
||||
view_table: "Als Tabelle anzeigen"
|
||||
view_chart: "Als Balkendiagramm anzeigen"
|
||||
view_table: "Tabelle"
|
||||
view_chart: "Säulendiagramm"
|
||||
refresh_report: "Bericht aktualisieren"
|
||||
start_date: "Startdatum"
|
||||
end_date: "Enddatum"
|
||||
commits:
|
||||
latest_changes: "Letzte Änderungen: bitte häufig updaten!"
|
||||
by: "von"
|
||||
@ -1684,6 +1693,7 @@ de:
|
||||
last_emailed: "Letzte E-Mail"
|
||||
not_found: "Entschuldige, dieser Benutzername existiert im System nicht."
|
||||
active: "Aktiv"
|
||||
show_emails: "E-Mails anzeigen"
|
||||
nav:
|
||||
new: "Neu"
|
||||
active: "Aktiv"
|
||||
|
||||
@ -310,6 +310,7 @@ en:
|
||||
admin_tooltip: "This user is an admin"
|
||||
suspended_notice: "This user is suspended until {{date}}."
|
||||
suspended_reason: "Reason: "
|
||||
github_profile: "Github"
|
||||
mailing_list_mode: "Receive an email for every new post (unless you mute the topic or category)"
|
||||
watched_categories: "Watched"
|
||||
watched_categories_instructions: "You will automatically watch all new topics in these categories. You will be notified of all new posts and topics, plus the count of unread and new posts will also appear next to the topic's listing."
|
||||
@ -380,7 +381,7 @@ en:
|
||||
email:
|
||||
title: "Email"
|
||||
instructions: "Never shown to the public."
|
||||
ok: "Looks good. We will email you to confirm."
|
||||
ok: "We will email you to confirm."
|
||||
invalid: "Please enter a valid email address."
|
||||
authenticated: "Your email has been authenticated by {{provider}}."
|
||||
frequency: "We'll only email you if we haven't seen you recently and you haven't already seen the thing we're emailing you about."
|
||||
@ -1003,7 +1004,7 @@ en:
|
||||
n_posts:
|
||||
one: "1 post"
|
||||
other: "{{count}} posts"
|
||||
cancel: "Show all posts in this topic again."
|
||||
cancel: "Remove filter"
|
||||
|
||||
split_topic:
|
||||
title: "Move to New Topic"
|
||||
@ -1323,6 +1324,10 @@ en:
|
||||
submit_tooltip: "Submit the private flag"
|
||||
take_action_tooltip: "Reach the flag threshold immediately, rather than waiting for more community flags"
|
||||
cant: "Sorry, you can't flag this post at this time."
|
||||
formatted_name:
|
||||
off_topic: "It's Off-Topic"
|
||||
inappropriate: "It's Inappropriate"
|
||||
spam: "It's Spam"
|
||||
custom_placeholder_notify_user: "Why does this post require you to speak to this user directly and privately? Be specific, be constructive, and always be kind."
|
||||
custom_placeholder_notify_moderators: "Why does this post require moderator attention? Let us know specifically what you are concerned about, and provide relevant links where possible."
|
||||
custom_message:
|
||||
@ -1507,8 +1512,11 @@ en:
|
||||
7_days_ago: "7 Days Ago"
|
||||
30_days_ago: "30 Days Ago"
|
||||
all: "All"
|
||||
view_table: "View as Table"
|
||||
view_chart: "View as Bar Chart"
|
||||
view_table: "table"
|
||||
view_chart: "bar chart"
|
||||
refresh_report: "Refresh Report"
|
||||
start_date: "Start Date"
|
||||
end_date: "End Date"
|
||||
|
||||
commits:
|
||||
latest_changes: "Latest changes: please update often!"
|
||||
@ -1825,6 +1833,7 @@ en:
|
||||
check_email: "check email"
|
||||
delete_topic: "delete topic"
|
||||
delete_post: "delete post"
|
||||
impersonate: "impersonate"
|
||||
screened_emails:
|
||||
title: "Screened Emails"
|
||||
description: "When someone tries to create a new account, the following email addresses will be checked and the registration will be blocked, or some other action performed."
|
||||
@ -1861,6 +1870,7 @@ en:
|
||||
last_emailed: "Last Emailed"
|
||||
not_found: "Sorry, that username doesn't exist in our system."
|
||||
active: "Active"
|
||||
show_emails: "Show Emails"
|
||||
nav:
|
||||
new: "New"
|
||||
active: "Active"
|
||||
|
||||
@ -343,7 +343,6 @@ es:
|
||||
frequency: "Sólo te enviaremos e-mails si no te hemos visto recientemente y todavía no has visto lo que te estamos enviando."
|
||||
name:
|
||||
title: "Nombre"
|
||||
instructions: "Tu nombre completo."
|
||||
too_short: "Tu nombre es demasiado corto."
|
||||
ok: "Tu nombre es válido."
|
||||
username:
|
||||
@ -511,8 +510,6 @@ es:
|
||||
created: 'Creado'
|
||||
created_lowercase: 'creado'
|
||||
trust_level: 'Nivel de Confianza'
|
||||
search_hint: 'nombre de usuario o dirección IP'
|
||||
search_hint_admin: 'nombre de usuario, e-mail o dirección IP'
|
||||
create_account:
|
||||
title: "Crear nueva cuenta"
|
||||
failed: "Algo ha salido mal, tal vez este e-mail ya fue registrado, intenta con el enlace 'olvidé la contraseña'"
|
||||
@ -887,7 +884,6 @@ es:
|
||||
n_posts:
|
||||
one: "1 post"
|
||||
other: "{{count}} posts"
|
||||
cancel: "Mostrar de nuevo todos los posts de este tema."
|
||||
split_topic:
|
||||
title: "Mover a un tema nuevo"
|
||||
action: "mover a un tema nuevo"
|
||||
@ -1353,8 +1349,6 @@ es:
|
||||
7_days_ago: "Hace 7 días"
|
||||
30_days_ago: "Hace 30 días"
|
||||
all: "Todo"
|
||||
view_table: "Ver como tabla"
|
||||
view_chart: "Ver como gráfico de tablas"
|
||||
commits:
|
||||
latest_changes: "Cambios recientes: ¡actualiza a menudo!"
|
||||
by: "por"
|
||||
|
||||
@ -104,6 +104,7 @@ fi:
|
||||
admin_title: "Ylläpito"
|
||||
flags_title: "Liput"
|
||||
show_more: "näytä lisää"
|
||||
show_help: "ohje"
|
||||
links: "Linkit"
|
||||
links_lowercase: "linkit"
|
||||
faq: "UKK"
|
||||
@ -138,6 +139,10 @@ fi:
|
||||
stat:
|
||||
all_time: "Kaikki"
|
||||
last_7_days: "Viimeiset 7 päivää"
|
||||
like_count: "Tykkäyksiä"
|
||||
topic_count: "Ketjuja"
|
||||
post_count: "Viestejä"
|
||||
user_count: "Käyttäjiä"
|
||||
bookmarks:
|
||||
not_logged_in: "pahoittelut, sinun täytyy kirjautua sisään voidaksesi lisätä viestin kirjanmerkin"
|
||||
created: "olet lisännyt tämän viestin kirjainmerkkeihisi"
|
||||
@ -325,6 +330,10 @@ fi:
|
||||
image_is_not_a_square: "Varoitus: olemme rajanneet kuvaasti; se ei ole neliön muotoinen."
|
||||
change_profile_background:
|
||||
title: "Profiilin taustakuva"
|
||||
instructions: "Profiilin taustakuvan leveys on 850 pikseliä."
|
||||
change_card_background:
|
||||
title: "Käyttäjäkortin taustakuva"
|
||||
instructions: "Taustakuvan leveys on 590 pikseliä."
|
||||
email:
|
||||
title: "Sähköposti"
|
||||
instructions: "Ei tule julkiseksi."
|
||||
@ -334,7 +343,7 @@ fi:
|
||||
frequency: "Lähetämme sähköpostia vain aiheista, joita et ole nähnyt, ja kun emme ole nähneet sinua viime aikoina."
|
||||
name:
|
||||
title: "Nimi"
|
||||
instructions: "Koko nimesi."
|
||||
instructions: "Koko nimesi (valinnainen)"
|
||||
too_short: "Nimesi on liian lyhyt."
|
||||
ok: "Nimesi vaikuttaa hyvältä."
|
||||
username:
|
||||
@ -362,6 +371,8 @@ fi:
|
||||
created: "Liittynyt"
|
||||
log_out: "Kirjaudu ulos"
|
||||
location: "Sijainti"
|
||||
card_badge:
|
||||
title: "Käyttäjäkortin tunnus"
|
||||
website: "Nettisivu"
|
||||
email_settings: "Sähköposti"
|
||||
email_digests:
|
||||
@ -500,6 +511,7 @@ fi:
|
||||
created: 'Luotu'
|
||||
created_lowercase: 'luotu'
|
||||
trust_level: 'Luottamustaso'
|
||||
search_hint: 'käyttäjätunnus, sähköposti tai IP-osoite'
|
||||
create_account:
|
||||
title: "Luo uusi tunnus"
|
||||
failed: "Jotain meni pieleen. Ehkäpä tämä sähköpostiosoite on jo rekisteröity, kokeile salasana unohtui -linkkiä."
|
||||
@ -585,6 +597,7 @@ fi:
|
||||
view_new_post: "Katsele uutta viestiäsi."
|
||||
saving: "Tallennetaan..."
|
||||
saved: "Tallennettu!"
|
||||
saved_draft: "Viestiluonnos kesken. Klikkaa tähän jatkaaksesi."
|
||||
uploading: "Lähettää..."
|
||||
show_preview: 'näytä esikatselu »'
|
||||
hide_preview: '« piilota esikatselu'
|
||||
@ -873,7 +886,6 @@ fi:
|
||||
n_posts:
|
||||
one: "1 viesti"
|
||||
other: "{{count}} viestiä"
|
||||
cancel: "Näytä tämän ketjun kaikki viestit."
|
||||
split_topic:
|
||||
title: "Siirrä uuteen ketjuun"
|
||||
action: "siirrä uuteen ketjuun"
|
||||
@ -1231,6 +1243,7 @@ fi:
|
||||
history: "Historia"
|
||||
changed_by: "käyttäjältä {{author}}"
|
||||
raw_email:
|
||||
title: "Alkuperäinen sähköposti"
|
||||
not_available: "Ei käytettävissä!"
|
||||
categories_list: "Lista alueista"
|
||||
filters:
|
||||
@ -1338,8 +1351,6 @@ fi:
|
||||
7_days_ago: "7 päivää sitten"
|
||||
30_days_ago: "30 päivää sitten"
|
||||
all: "Kaikki"
|
||||
view_table: "Näytä taulukkona"
|
||||
view_chart: "Katsele pylväsdiagrammina"
|
||||
commits:
|
||||
latest_changes: "Viimeisimmät muutokset: päivitä usein!"
|
||||
by: "käyttäjältä"
|
||||
@ -1858,7 +1869,7 @@ fi:
|
||||
confirm: 'Vahvistus'
|
||||
site_text:
|
||||
none: "Valitse sisällön tyyppi aloittaaksesi muokkaamisen."
|
||||
title: 'Tekstin sisältö'
|
||||
title: 'Tekstit'
|
||||
site_settings:
|
||||
show_overriden: 'Näytä vain muokatut'
|
||||
title: 'Asetukset'
|
||||
@ -1911,11 +1922,14 @@ fi:
|
||||
grant: Myönnä
|
||||
no_user_badges: "%{name} ei ole saanut yhtään arvomerkkiä."
|
||||
no_badges: Myönnettäviä arvomerkkejä ei ole.
|
||||
none_selected: "Valitse arvomerkki aloittaaksesi"
|
||||
allow_title: Salli arvomerkin käyttäminen tittelinä
|
||||
multiple_grant: Voidaan myöntää useita kertoja
|
||||
listable: Näytä arvomerkki julkisella arvomerkkisivulla
|
||||
enabled: Ota arvomerkki käyttöön
|
||||
icon: Ikoni
|
||||
image: Kuva
|
||||
icon_help: "Käytä joko Font Awesome -luokkaa tai kuvan URL-osoitetta"
|
||||
query: Arvomerkkien haku tietokannasta (SQL)
|
||||
target_posts: Tietokantakyselyn kohdeviestit
|
||||
auto_revoke: Aja kumoamis-ajo päivittäin
|
||||
@ -1948,6 +1962,8 @@ fi:
|
||||
with_time: <span class="username">%{username}</span> <span class="time">%{time}</span>
|
||||
lightbox:
|
||||
download: "lataa"
|
||||
search_help:
|
||||
title: 'Etsi ohjetta'
|
||||
keyboard_shortcuts_help:
|
||||
title: 'Näppäinoikotiet'
|
||||
jump_to:
|
||||
@ -2009,6 +2025,7 @@ fi:
|
||||
one: "1 myönnetty"
|
||||
other: "%{count} myönnettyä"
|
||||
select_badge_for_title: Valitse tittelisi arvomerkeistä
|
||||
none: "<ei mitään>"
|
||||
badge_grouping:
|
||||
getting_started:
|
||||
name: Ensiaskeleet
|
||||
@ -2090,3 +2107,12 @@ fi:
|
||||
reader:
|
||||
name: Lukija
|
||||
description: Luki kaikki viestit ketjusta, jossa on yli 100 viestiä
|
||||
google_search: |
|
||||
<h2>Etsi Googlella</h2>
|
||||
<p>
|
||||
<form action='//google.com/search' id='google-search' onsubmit="document.getElementById('google-query').value = 'site:' + window.location.host + ' ' + document.getElementById('user-query').value; return true;">
|
||||
<input type="text" id='user-query' value="">
|
||||
<input type='hidden' id='google-query' name="q">
|
||||
<button class="btn btn-primary">Google</button>
|
||||
</form>
|
||||
</p>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user