Version bump
This commit is contained in:
commit
63f0bd0495
22
.codeclimate.yml
Normal file
22
.codeclimate.yml
Normal file
@ -0,0 +1,22 @@
|
||||
languages:
|
||||
Ruby: true
|
||||
JavaScript: true
|
||||
Python: false
|
||||
PHP: false
|
||||
|
||||
exclude_paths:
|
||||
- "app/assets/javascripts/defer/*"
|
||||
- "app/assets/javascripts/discourse/lib/Markdown.Editor.js"
|
||||
- "app/assets/javascripts/ember-addons/*"
|
||||
- "lib/autospec/*"
|
||||
- "lib/es6_module_transpiler/*"
|
||||
- "lib/highlight_js/*"
|
||||
- "lib/import/*"
|
||||
- "lib/javascripts/*"
|
||||
- "lib/tasks/*"
|
||||
- "lib/*.js"
|
||||
- "public/*"
|
||||
- "script/*"
|
||||
- "spec/*"
|
||||
- "test/*"
|
||||
- "vendor/*"
|
||||
@ -35,6 +35,7 @@
|
||||
"exists",
|
||||
"visible",
|
||||
"invisible",
|
||||
"asyncRender",
|
||||
"selectDropdown",
|
||||
"asyncTestDiscourse",
|
||||
"fixture",
|
||||
|
||||
8
Gemfile
8
Gemfile
@ -51,14 +51,14 @@ gem 'message_bus'
|
||||
gem 'rails_multisite', path: 'vendor/gems/rails_multisite'
|
||||
|
||||
gem 'redcarpet', require: false
|
||||
gem 'eventmachine'
|
||||
gem 'fast_xs'
|
||||
|
||||
gem 'fast_xor'
|
||||
|
||||
# while we sort out https://github.com/sdsykes/fastimage/pull/46
|
||||
gem 'fastimage_discourse', require: 'fastimage'
|
||||
gem 'fog', '1.26.0', require: false
|
||||
gem 'aws-sdk', require: false
|
||||
gem 'excon', require: false
|
||||
gem 'unf', require: false
|
||||
|
||||
gem 'email_reply_parser'
|
||||
@ -96,7 +96,7 @@ gem 'sass'
|
||||
gem 'sidekiq'
|
||||
|
||||
# for sidekiq web
|
||||
gem 'sinatra', require: nil
|
||||
gem 'sinatra', require: false
|
||||
|
||||
gem 'therubyracer'
|
||||
gem 'thin', require: false
|
||||
@ -124,7 +124,7 @@ group :test, :development do
|
||||
gem 'certified', require: false
|
||||
# later appears to break Fabricate(:topic, category: category)
|
||||
gem 'fabrication', '2.9.8', require: false
|
||||
gem 'qunit-rails'
|
||||
gem 'discourse-qunit-rails', require: 'qunit-rails'
|
||||
gem 'mocha', require: false
|
||||
gem 'rb-fsevent', require: RUBY_PLATFORM =~ /darwin/i ? 'rb-fsevent' : false
|
||||
gem 'rb-inotify', '~> 0.9', require: RUBY_PLATFORM =~ /linux/i ? 'rb-inotify' : false
|
||||
|
||||
98
Gemfile.lock
98
Gemfile.lock
@ -6,7 +6,6 @@ PATH
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
CFPropertyList (2.2.8)
|
||||
actionmailer (4.1.10)
|
||||
actionpack (= 4.1.10)
|
||||
actionview (= 4.1.10)
|
||||
@ -41,6 +40,14 @@ GEM
|
||||
activerecord (>= 2.3.0)
|
||||
rake (~> 10.4.2, >= 10.4.2)
|
||||
arel (5.0.1.20140414130214)
|
||||
aws-sdk (2.0.45)
|
||||
aws-sdk-resources (= 2.0.45)
|
||||
aws-sdk-core (2.0.45)
|
||||
builder (~> 3.0)
|
||||
jmespath (~> 1.0)
|
||||
multi_json (~> 1.0)
|
||||
aws-sdk-resources (2.0.45)
|
||||
aws-sdk-core (= 2.0.45)
|
||||
babel-source (4.6.6)
|
||||
babel-transpiler (0.6.0)
|
||||
babel-source (>= 4.0, < 5)
|
||||
@ -64,6 +71,8 @@ GEM
|
||||
daemons (1.2.2)
|
||||
debug_inspector (0.0.2)
|
||||
diff-lcs (1.2.5)
|
||||
discourse-qunit-rails (0.0.8)
|
||||
railties
|
||||
docile (1.1.5)
|
||||
dotenv (1.0.2)
|
||||
email_reply_parser (0.5.8)
|
||||
@ -82,7 +91,7 @@ GEM
|
||||
ember-source (1.11.3.1)
|
||||
erubis (2.7.0)
|
||||
eventmachine (1.0.7)
|
||||
excon (0.44.4)
|
||||
excon (0.45.3)
|
||||
execjs (2.5.2)
|
||||
exifr (1.1.3)
|
||||
fabrication (2.9.8)
|
||||
@ -99,79 +108,11 @@ GEM
|
||||
fast_xs (0.8.0)
|
||||
fastimage_discourse (1.6.6)
|
||||
ffi (1.9.6)
|
||||
fission (0.5.0)
|
||||
CFPropertyList (~> 2.2)
|
||||
flamegraph (0.1.0)
|
||||
fast_stack
|
||||
fog (1.26.0)
|
||||
fog-atmos
|
||||
fog-brightbox (~> 0.4)
|
||||
fog-core (~> 1.27, >= 1.27.1)
|
||||
fog-ecloud
|
||||
fog-json
|
||||
fog-profitbricks
|
||||
fog-radosgw (>= 0.0.2)
|
||||
fog-sakuracloud (>= 0.0.4)
|
||||
fog-softlayer
|
||||
fog-storm_on_demand
|
||||
fog-terremark
|
||||
fog-vmfusion
|
||||
fog-voxel
|
||||
fog-xml (~> 0.1.1)
|
||||
ipaddress (~> 0.5)
|
||||
nokogiri (~> 1.5, >= 1.5.11)
|
||||
fog-atmos (0.1.0)
|
||||
fog-core
|
||||
fog-xml
|
||||
fog-brightbox (0.7.1)
|
||||
fog-core (~> 1.22)
|
||||
fog-json
|
||||
inflecto (~> 0.0.2)
|
||||
fog-core (1.27.2)
|
||||
builder
|
||||
excon (~> 0.38)
|
||||
formatador (~> 0.2)
|
||||
mime-types
|
||||
net-scp (~> 1.1)
|
||||
net-ssh (>= 2.1.3)
|
||||
fog-ecloud (0.0.2)
|
||||
fog-core
|
||||
fog-xml
|
||||
fog-json (1.0.0)
|
||||
multi_json (~> 1.0)
|
||||
fog-profitbricks (0.0.1)
|
||||
fog-core
|
||||
fog-xml
|
||||
nokogiri
|
||||
fog-radosgw (0.0.3)
|
||||
fog-core (>= 1.21.0)
|
||||
fog-json
|
||||
fog-xml (>= 0.0.1)
|
||||
fog-sakuracloud (0.1.1)
|
||||
fog-core
|
||||
fog-json
|
||||
fog-softlayer (0.3.26)
|
||||
fog-core
|
||||
fog-json
|
||||
fog-storm_on_demand (0.1.0)
|
||||
fog-core
|
||||
fog-json
|
||||
fog-terremark (0.0.3)
|
||||
fog-core
|
||||
fog-xml
|
||||
fog-vmfusion (0.0.1)
|
||||
fission
|
||||
fog-core
|
||||
fog-voxel (0.0.2)
|
||||
fog-core
|
||||
fog-xml
|
||||
fog-xml (0.1.1)
|
||||
fog-core
|
||||
nokogiri (~> 1.5, >= 1.5.11)
|
||||
foreman (0.77.0)
|
||||
dotenv (~> 1.0.2)
|
||||
thor (~> 0.19.1)
|
||||
formatador (0.2.5)
|
||||
fspath (2.0.6)
|
||||
gctools (0.2.3)
|
||||
given_core (3.5.4)
|
||||
@ -193,8 +134,8 @@ GEM
|
||||
progress (~> 3.0.0)
|
||||
image_size (1.1.5)
|
||||
in_threads (1.2.2)
|
||||
inflecto (0.0.2)
|
||||
ipaddress (0.8.0)
|
||||
jmespath (1.0.2)
|
||||
multi_json (~> 1.0)
|
||||
jquery-rails (3.1.2)
|
||||
railties (>= 3.0, < 5.0)
|
||||
thor (>= 0.14, < 2.0)
|
||||
@ -229,9 +170,6 @@ GEM
|
||||
multi_xml (0.5.5)
|
||||
multipart-post (2.0.0)
|
||||
mustache (0.99.8)
|
||||
net-scp (1.2.1)
|
||||
net-ssh (>= 2.6.5)
|
||||
net-ssh (2.9.2)
|
||||
netrc (0.10.3)
|
||||
nokogiri (1.6.6.2)
|
||||
mini_portile (~> 0.6.0)
|
||||
@ -270,7 +208,7 @@ GEM
|
||||
omniauth-twitter (1.0.1)
|
||||
multi_json (~> 1.3)
|
||||
omniauth-oauth (~> 1.0)
|
||||
onebox (1.5.18)
|
||||
onebox (1.5.19)
|
||||
moneta (~> 0.7)
|
||||
multi_json (~> 1.7)
|
||||
mustache (~> 0.99)
|
||||
@ -291,8 +229,6 @@ GEM
|
||||
pry (>= 0.9.10)
|
||||
puma (2.11.1)
|
||||
rack (>= 1.1, < 2.0)
|
||||
qunit-rails (0.0.7)
|
||||
railties
|
||||
r2 (0.2.5)
|
||||
rack (1.5.3)
|
||||
rack-mini-profiler (0.9.3)
|
||||
@ -459,15 +395,17 @@ DEPENDENCIES
|
||||
actionpack-action_caching
|
||||
active_model_serializers (~> 0.8.3)
|
||||
annotate
|
||||
aws-sdk
|
||||
babel-transpiler
|
||||
barber
|
||||
better_errors
|
||||
binding_of_caller
|
||||
certified
|
||||
discourse-qunit-rails
|
||||
email_reply_parser
|
||||
ember-rails
|
||||
ember-source (= 1.11.3.1)
|
||||
eventmachine
|
||||
excon
|
||||
fabrication (= 2.9.8)
|
||||
fakeweb (~> 1.3.0)
|
||||
fast_blank
|
||||
@ -475,7 +413,6 @@ DEPENDENCIES
|
||||
fast_xs
|
||||
fastimage_discourse
|
||||
flamegraph
|
||||
fog (= 1.26.0)
|
||||
foreman
|
||||
gctools
|
||||
handlebars-source (= 2.0.0)
|
||||
@ -510,7 +447,6 @@ DEPENDENCIES
|
||||
pry-nav
|
||||
pry-rails
|
||||
puma
|
||||
qunit-rails
|
||||
r2 (~> 0.2.5)
|
||||
rack-mini-profiler
|
||||
rack-protection
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
/*global ace:true */
|
||||
|
||||
/* global ace:true */
|
||||
import loadScript from 'discourse/lib/load-script';
|
||||
|
||||
export default Ember.Component.extend({
|
||||
@ -33,19 +32,21 @@ export default Ember.Component.extend({
|
||||
const self = this;
|
||||
|
||||
loadScript("/javascripts/ace/ace.js", { scriptTag: true }).then(function() {
|
||||
const editor = ace.edit(self.$('.ace')[0]);
|
||||
ace.require(['ace/ace'], function(loadedAce) {
|
||||
const editor = loadedAce.edit(self.$('.ace')[0]);
|
||||
|
||||
editor.setTheme("ace/theme/chrome");
|
||||
editor.setShowPrintMargin(false);
|
||||
editor.getSession().setMode("ace/mode/" + (self.get('mode')));
|
||||
editor.on('change', function() {
|
||||
self._skipContentChangeEvent = true;
|
||||
self.set('content', editor.getSession().getValue());
|
||||
self._skipContentChangeEvent = false;
|
||||
editor.setTheme("ace/theme/chrome");
|
||||
editor.setShowPrintMargin(false);
|
||||
editor.getSession().setMode("ace/mode/" + self.get('mode'));
|
||||
editor.on('change', function() {
|
||||
self._skipContentChangeEvent = true;
|
||||
self.set('content', editor.getSession().getValue());
|
||||
self._skipContentChangeEvent = false;
|
||||
});
|
||||
|
||||
self.$().data('editor', editor);
|
||||
self._editor = editor;
|
||||
});
|
||||
|
||||
self.$().data('editor', editor);
|
||||
self._editor = editor;
|
||||
});
|
||||
|
||||
}.on('didInsertElement')
|
||||
|
||||
@ -1,3 +1,3 @@
|
||||
export default Ember.Component.extend({
|
||||
tagName: 'tbody'
|
||||
tagName: 'tr'
|
||||
});
|
||||
|
||||
@ -0,0 +1,3 @@
|
||||
export default Ember.Component.extend({
|
||||
tagName: 'tr'
|
||||
});
|
||||
@ -0,0 +1,3 @@
|
||||
export default Ember.Component.extend({
|
||||
tagName: 'tr'
|
||||
});
|
||||
@ -1,9 +1,10 @@
|
||||
import BufferedContent from 'discourse/mixins/buffered-content';
|
||||
import ScrollTop from 'discourse/mixins/scroll-top';
|
||||
import SiteSetting from 'admin/models/site-setting';
|
||||
|
||||
const CustomTypes = ['bool', 'enum', 'list', 'url_list'];
|
||||
|
||||
export default Ember.Component.extend(BufferedContent, Discourse.ScrollTop, {
|
||||
export default Ember.Component.extend(BufferedContent, ScrollTop, {
|
||||
classNameBindings: [':row', ':setting', 'setting.overridden', 'typeClass'],
|
||||
content: Ember.computed.alias('setting'),
|
||||
dirty: Discourse.computed.propertyNotEqual('buffered.value', 'setting.value'),
|
||||
@ -65,8 +66,8 @@ export default Ember.Component.extend(BufferedContent, Discourse.ScrollTop, {
|
||||
self.set('validationMessage', null);
|
||||
self.commitBuffer();
|
||||
}).catch(function(e) {
|
||||
if (e.responseJSON && e.responseJSON.errors) {
|
||||
self.set('validationMessage', e.responseJSON.errors[0]);
|
||||
if (e.jqXHR.responseJSON && e.jqXHR.responseJSON.errors) {
|
||||
self.set('validationMessage', e.jqXHR.responseJSON.errors[0]);
|
||||
} else {
|
||||
self.set('validationMessage', I18n.t('generic_error'));
|
||||
}
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { popupAjaxError } from 'discourse/lib/ajax-error';
|
||||
import BufferedContent from 'discourse/mixins/buffered-content';
|
||||
|
||||
export default Ember.ObjectController.extend(BufferedContent, {
|
||||
@ -64,11 +65,9 @@ export default Ember.ObjectController.extend(BufferedContent, {
|
||||
self.set('savingStatus', I18n.t('saved'));
|
||||
}
|
||||
|
||||
}).catch(function(error) {
|
||||
self.set('savingStatus', I18n.t('failed'));
|
||||
self.send('saveError', error);
|
||||
}).finally(function() {
|
||||
}).catch(popupAjaxError).finally(function() {
|
||||
self.set('saving', false);
|
||||
self.set('savingStatus', '');
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
@ -2,19 +2,24 @@ export default Ember.ArrayController.extend({
|
||||
sortProperties: ["name"],
|
||||
|
||||
actions: {
|
||||
emojiUploaded: function (emoji) {
|
||||
this.pushObject(emoji);
|
||||
emojiUploaded(emoji) {
|
||||
this.pushObject(Em.Object.create(emoji));
|
||||
},
|
||||
|
||||
destroy: function(emoji) {
|
||||
var self = this;
|
||||
return bootbox.confirm(I18n.t("admin.emoji.delete_confirm", { name: emoji.name }), I18n.t("no_value"), I18n.t("yes_value"), function (destroy) {
|
||||
if (destroy) {
|
||||
return Discourse.ajax("/admin/customize/emojis/" + emoji.name, { type: "DELETE" }).then(function() {
|
||||
self.removeObject(emoji);
|
||||
});
|
||||
destroy(emoji) {
|
||||
const self = this;
|
||||
return bootbox.confirm(
|
||||
I18n.t("admin.emoji.delete_confirm", { name: emoji.get("name") }),
|
||||
I18n.t("no_value"),
|
||||
I18n.t("yes_value"),
|
||||
function(destroy) {
|
||||
if (destroy) {
|
||||
return Discourse.ajax("/admin/customize/emojis/" + emoji.get("name"), { type: "DELETE" }).then(function() {
|
||||
self.removeObject(emoji);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
import { popupAjaxError } from 'discourse/lib/ajax-error';
|
||||
|
||||
export default Em.ObjectController.extend({
|
||||
needs: ['adminGroupsType'],
|
||||
disableSave: false,
|
||||
@ -73,7 +75,7 @@ export default Em.ObjectController.extend({
|
||||
let promise = group.get("id") ? group.save() : group.create().then(() => groupsController.addObject(group));
|
||||
|
||||
promise.then(() => this.transitionToRoute("adminGroup", group))
|
||||
.catch(e => bootbox.alert($.parseJSON(e.responseText).errors))
|
||||
.catch(popupAjaxError)
|
||||
.finally(() => this.set('disableSave', false));
|
||||
},
|
||||
|
||||
|
||||
@ -2,7 +2,7 @@ import UserField from 'admin/models/user-field';
|
||||
|
||||
export default Ember.ArrayController.extend({
|
||||
fieldTypes: null,
|
||||
createDisabled: Em.computed.gte('model.length', 3),
|
||||
createDisabled: Em.computed.gte('model.length', 20),
|
||||
|
||||
userFieldsDescription: function() {
|
||||
return I18n.t('admin.user_fields.description');
|
||||
|
||||
@ -9,7 +9,7 @@ export default ObjectController.extend(CanCheckEmails, {
|
||||
showApproval: Discourse.computed.setting('must_approve_users'),
|
||||
showBadges: Discourse.computed.setting('enable_badges'),
|
||||
|
||||
primaryGroupDirty: Discourse.computed.propertyNotEqual('originalPrimaryGroupId', 'primary_group_id'),
|
||||
primaryGroupDirty: Discourse.computed.propertyNotEqual('originalPrimaryGroupId', 'model.primary_group_id'),
|
||||
|
||||
automaticGroups: function() {
|
||||
return this.get("model.automaticGroups").map((g) => g.name).join(", ");
|
||||
@ -17,7 +17,7 @@ export default ObjectController.extend(CanCheckEmails, {
|
||||
|
||||
userFields: function() {
|
||||
const siteUserFields = this.site.get('user_fields'),
|
||||
userFields = this.get('user_fields');
|
||||
userFields = this.get('model.user_fields');
|
||||
|
||||
if (!Ember.isEmpty(siteUserFields)) {
|
||||
return siteUserFields.map(function(uf) {
|
||||
@ -26,7 +26,7 @@ export default ObjectController.extend(CanCheckEmails, {
|
||||
});
|
||||
}
|
||||
return [];
|
||||
}.property('user_fields.@each'),
|
||||
}.property('model.user_fields.@each'),
|
||||
|
||||
actions: {
|
||||
toggleTitleEdit() {
|
||||
@ -67,16 +67,16 @@ export default ObjectController.extend(CanCheckEmails, {
|
||||
|
||||
return Discourse.ajax("/admin/users/" + this.get('id') + "/primary_group", {
|
||||
type: 'PUT',
|
||||
data: {primary_group_id: this.get('primary_group_id')}
|
||||
data: {primary_group_id: this.get('model.primary_group_id')}
|
||||
}).then(function () {
|
||||
self.set('originalPrimaryGroupId', self.get('primary_group_id'));
|
||||
self.set('originalPrimaryGroupId', self.get('model.primary_group_id'));
|
||||
}).catch(function() {
|
||||
bootbox.alert(I18n.t('generic_error'));
|
||||
});
|
||||
},
|
||||
|
||||
resetPrimaryGroup() {
|
||||
this.set('primary_group_id', this.get('originalPrimaryGroupId'));
|
||||
this.set('model.primary_group_id', this.get('originalPrimaryGroupId'));
|
||||
},
|
||||
|
||||
regenerateApiKey() {
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
import { popupAjaxError } from 'discourse/lib/ajax-error';
|
||||
|
||||
const AdminUser = Discourse.User.extend({
|
||||
|
||||
customGroups: Em.computed.filter("groups", (g) => !g.automatic && Discourse.Group.create(g)),
|
||||
@ -90,14 +92,7 @@ const AdminUser = Discourse.User.extend({
|
||||
can_grant_admin: false,
|
||||
can_revoke_admin: true
|
||||
});
|
||||
}).catch(function(e) {
|
||||
let error;
|
||||
if (e.responseJSON && e.responseJSON.error) {
|
||||
error = e.responseJSON.error;
|
||||
}
|
||||
error = error || I18n.t('admin.user.grant_admin_failed', { error: "http: " + e.status + " - " + e.body });
|
||||
bootbox.alert(error);
|
||||
});
|
||||
}).catch(popupAjaxError);
|
||||
},
|
||||
|
||||
revokeModeration() {
|
||||
@ -110,7 +105,7 @@ const AdminUser = Discourse.User.extend({
|
||||
can_grant_moderation: true,
|
||||
can_revoke_moderation: false
|
||||
});
|
||||
});
|
||||
}).catch(popupAjaxError);
|
||||
},
|
||||
|
||||
grantModeration() {
|
||||
@ -123,14 +118,7 @@ const AdminUser = Discourse.User.extend({
|
||||
can_grant_moderation: false,
|
||||
can_revoke_moderation: true
|
||||
});
|
||||
}).catch(function(e) {
|
||||
let error;
|
||||
if (e.responseJSON && e.responseJSON.error) {
|
||||
error = e.responseJSON.error;
|
||||
}
|
||||
error = error || I18n.t('admin.user.grant_moderation_failed', { error: "http: " + e.status + " - " + e.body });
|
||||
bootbox.alert(error);
|
||||
});
|
||||
}).catch(popupAjaxError);
|
||||
},
|
||||
|
||||
refreshBrowsers() {
|
||||
@ -156,10 +144,6 @@ const AdminUser = Discourse.User.extend({
|
||||
this.set('originalTrustLevel', this.get('trust_level'));
|
||||
},
|
||||
|
||||
trustLevels: function() {
|
||||
return Discourse.Site.currentProp('trustLevels');
|
||||
}.property(),
|
||||
|
||||
dirty: Discourse.computed.propertyNotEqual('originalTrustLevel', 'trustLevel.id'),
|
||||
|
||||
saveTrustLevel() {
|
||||
@ -243,7 +227,7 @@ const AdminUser = Discourse.User.extend({
|
||||
type: 'POST',
|
||||
data: { username_or_email: this.get('username') }
|
||||
}).then(function() {
|
||||
document.location = "/";
|
||||
document.location = Discourse.getURL("/");
|
||||
}).catch(function(e) {
|
||||
if (e.status === 404) {
|
||||
bootbox.alert(I18n.t('admin.impersonate.not_found'));
|
||||
@ -321,9 +305,9 @@ const AdminUser = Discourse.User.extend({
|
||||
}).then(function(data) {
|
||||
if (data.success) {
|
||||
if (data.username) {
|
||||
document.location = "/admin/users/" + data.username;
|
||||
document.location = Discourse.getURL("/admin/users/" + data.username);
|
||||
} else {
|
||||
document.location = "/admin/users/list/active";
|
||||
document.location = Discourse.getURL("/admin/users/list/active");
|
||||
}
|
||||
} else {
|
||||
bootbox.alert(I18n.t("admin.user.anonymize_failed"));
|
||||
@ -386,7 +370,7 @@ const AdminUser = Discourse.User.extend({
|
||||
if (/^\/admin\/users\/list\//.test(location)) {
|
||||
document.location = location;
|
||||
} else {
|
||||
document.location = "/admin/users/list/active";
|
||||
document.location = Discourse.getURL("/admin/users/list/active");
|
||||
}
|
||||
} else {
|
||||
bootbox.alert(I18n.t("admin.user.delete_failed"));
|
||||
|
||||
@ -1,13 +1,11 @@
|
||||
<tr>
|
||||
<td class="title">
|
||||
{{#if report.icon}}
|
||||
{{fa-icon report.icon}}
|
||||
{{/if}}
|
||||
<a {{bind-attr href="report.reportUrl"}}>{{report.title}}</a>
|
||||
</td>
|
||||
<td class="value">{{report.todayCount}}</td>
|
||||
<td {{bind-attr class=":value report.yesterdayTrend"}} {{bind-attr title="report.yesterdayCountTitle"}}>{{report.yesterdayCount}} {{fa-icon "caret-up" class="up"}} {{fa-icon "caret-down" class="down"}}</td>
|
||||
<td {{bind-attr class=":value report.sevenDayTrend"}} {{bind-attr title="report.sevenDayCountTitle"}}>{{report.lastSevenDaysCount}} {{fa-icon "caret-up" class="up"}} {{fa-icon "caret-down" class="down"}}</td>
|
||||
<td {{bind-attr class=":value report.thirtyDayTrend"}} {{bind-attr title="report.thirtyDayCountTitle"}}>{{report.lastThirtyDaysCount}} {{fa-icon "caret-up" class="up"}} {{fa-icon "caret-down" class="down"}}</td>
|
||||
<td class="value">{{report.total}}</td>
|
||||
</tr>
|
||||
<td class="title">
|
||||
{{#if report.icon}}
|
||||
{{fa-icon report.icon}}
|
||||
{{/if}}
|
||||
<a href="{{report.reportUrl}}">{{report.title}}</a>
|
||||
</td>
|
||||
<td class="value">{{report.todayCount}}</td>
|
||||
<td {{bind-attr class=":value report.yesterdayTrend"}} title="{{report.yesterdayCountTitle}}">{{report.yesterdayCount}} {{fa-icon "caret-up" class="up"}} {{fa-icon "caret-down" class="down"}}</td>
|
||||
<td {{bind-attr class=":value report.sevenDayTrend"}} title="{{report.sevenDayCountTitle}}">{{report.lastSevenDaysCount}} {{fa-icon "caret-up" class="up"}} {{fa-icon "caret-down" class="down"}}</td>
|
||||
<td {{bind-attr class=":value report.thirtyDayTrend"}} title="{{report.thirtyDayCountTitle}}">{{report.lastThirtyDaysCount}} {{fa-icon "caret-up" class="up"}} {{fa-icon "caret-down" class="down"}}</td>
|
||||
<td class="value">{{report.total}}</td>
|
||||
|
||||
@ -0,0 +1,6 @@
|
||||
<td class="title"><a href={{report.reportUrl}}>{{report.title}}</a></td>
|
||||
<td class="value">{{report.todayCount}}</td>
|
||||
<td class="value">{{report.yesterdayCount}}</td>
|
||||
<td class="value">{{report.sevenDaysAgoCount}}</td>
|
||||
<td class="value">{{report.thirtyDaysAgoCount}}</td>
|
||||
<td class="value"></td>
|
||||
@ -0,0 +1,6 @@
|
||||
<td class="title">{{report.title}}</td>
|
||||
<td class="value">{{#link-to 'adminUsersList.show' 'newuser'}}{{value-at-tl report.data level="0"}}{{/link-to}}</td>
|
||||
<td class="value">{{#link-to 'adminUsersList.show' 'basic'}}{{value-at-tl report.data level="1"}}{{/link-to}}</td>
|
||||
<td class="value">{{#link-to 'adminUsersList.show' 'regular'}}{{value-at-tl report.data level="2"}}{{/link-to}}</td>
|
||||
<td class="value">{{#link-to 'adminUsersList.show' 'leader'}}{{value-at-tl report.data level="3"}}{{/link-to}}</td>
|
||||
<td class="value">{{#link-to 'adminUsersList.show' 'elder'}}{{value-at-tl report.data level="4"}}{{/link-to}}</td>
|
||||
@ -15,9 +15,11 @@
|
||||
<th>4</th>
|
||||
</tr>
|
||||
</thead>
|
||||
{{#unless loading}}
|
||||
{{ render 'admin/templates/reports/trust_levels_report' users_by_trust_level tagName="tbody"}}
|
||||
{{/unless}}
|
||||
<tbody>
|
||||
{{#unless loading}}
|
||||
{{admin-report-trust-level-counts report=users_by_trust_level}}
|
||||
{{/unless}}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
@ -50,16 +52,18 @@
|
||||
<th>{{i18n 'admin.dashboard.reports.all'}}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
{{#unless loading}}
|
||||
{{ render 'admin/templates/reports/per_day_counts_report' visits tagName="tbody"}}
|
||||
{{admin-report-counts report=signups}}
|
||||
{{admin-report-counts report=topics}}
|
||||
{{admin-report-counts report=posts}}
|
||||
{{admin-report-counts report=likes}}
|
||||
{{admin-report-counts report=flags}}
|
||||
{{admin-report-counts report=bookmarks}}
|
||||
{{admin-report-counts report=emails}}
|
||||
{{/unless}}
|
||||
<tbody>
|
||||
{{#unless loading}}
|
||||
{{admin-report-per-day-counts report=visits}}
|
||||
{{admin-report-counts report=signups}}
|
||||
{{admin-report-counts report=topics}}
|
||||
{{admin-report-counts report=posts}}
|
||||
{{admin-report-counts report=likes}}
|
||||
{{admin-report-counts report=flags}}
|
||||
{{admin-report-counts report=bookmarks}}
|
||||
{{admin-report-counts report=emails}}
|
||||
{{/unless}}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
@ -75,12 +79,14 @@
|
||||
<th>{{i18n 'admin.dashboard.reports.all'}}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
{{#unless loading}}
|
||||
{{admin-report-counts report=page_view_anon_reqs}}
|
||||
{{admin-report-counts report=page_view_logged_in_reqs}}
|
||||
{{admin-report-counts report=page_view_crawler_reqs}}
|
||||
{{admin-report-counts report=page_view_total_reqs}}
|
||||
{{/unless}}
|
||||
<tbody>
|
||||
{{#unless loading}}
|
||||
{{admin-report-counts report=page_view_anon_reqs}}
|
||||
{{admin-report-counts report=page_view_logged_in_reqs}}
|
||||
{{admin-report-counts report=page_view_crawler_reqs}}
|
||||
{{admin-report-counts report=page_view_total_reqs}}
|
||||
{{/unless}}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
@ -97,13 +103,15 @@
|
||||
<th>{{i18n 'admin.dashboard.reports.all'}}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
{{#unless loading}}
|
||||
{{admin-report-counts report=user_to_user_private_messages}}
|
||||
{{admin-report-counts report=system_private_messages}}
|
||||
{{admin-report-counts report=notify_moderators_private_messages}}
|
||||
{{admin-report-counts report=notify_user_private_messages}}
|
||||
{{admin-report-counts report=moderator_warning_private_messages}}
|
||||
{{/unless}}
|
||||
<tbody>
|
||||
{{#unless loading}}
|
||||
{{admin-report-counts report=user_to_user_private_messages}}
|
||||
{{admin-report-counts report=system_private_messages}}
|
||||
{{admin-report-counts report=notify_moderators_private_messages}}
|
||||
{{admin-report-counts report=notify_user_private_messages}}
|
||||
{{admin-report-counts report=moderator_warning_private_messages}}
|
||||
{{/unless}}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
@ -118,14 +126,14 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{#unless loading}}
|
||||
{{#unless loading}}
|
||||
<tr>
|
||||
<td>{{i18n 'admin.dashboard.uploads'}}</td>
|
||||
<td>{{disk_space.uploads_used}} ({{i18n 'admin.dashboard.space_free' size=disk_space.uploads_free}})</td>
|
||||
<td><a href="/admin/backups">{{i18n 'admin.dashboard.backups'}}</a></td>
|
||||
<td>{{disk_space.backups_used}} ({{i18n 'admin.dashboard.space_free' size=disk_space.backups_free}})</td>
|
||||
</tr>
|
||||
{{/unless}}
|
||||
{{/unless}}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@ -144,20 +152,22 @@
|
||||
<th>{{i18n 'admin.dashboard.reports.all'}}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
{{#unless loading}}
|
||||
{{admin-report-counts report=http_2xx_reqs}}
|
||||
{{admin-report-counts report=http_3xx_reqs}}
|
||||
{{admin-report-counts report=http_4xx_reqs}}
|
||||
{{admin-report-counts report=http_5xx_reqs}}
|
||||
{{admin-report-counts report=http_background_reqs}}
|
||||
{{admin-report-counts report=http_total_reqs}}
|
||||
{{/unless}}
|
||||
<tbody>
|
||||
{{#unless loading}}
|
||||
{{admin-report-counts report=http_2xx_reqs}}
|
||||
{{admin-report-counts report=http_3xx_reqs}}
|
||||
{{admin-report-counts report=http_4xx_reqs}}
|
||||
{{admin-report-counts report=http_5xx_reqs}}
|
||||
{{admin-report-counts report=http_background_reqs}}
|
||||
{{admin-report-counts report=http_total_reqs}}
|
||||
{{/unless}}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{{else}}
|
||||
<div class="dashboard-stats">
|
||||
<a href {{action showTrafficReport}}>{{i18n 'admin.dashboard.show_traffic_report'}}</a>
|
||||
</div>
|
||||
<div class="dashboard-stats">
|
||||
<a href {{action 'showTrafficReport'}}>{{i18n 'admin.dashboard.show_traffic_report'}}</a>
|
||||
</div>
|
||||
{{/if}}
|
||||
{{/unless}}
|
||||
</div>
|
||||
|
||||
@ -10,13 +10,13 @@
|
||||
<br/>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{i18n "admin.plugins.name"}}</th>
|
||||
<th>{{i18n "admin.plugins.version"}}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{i18n "admin.plugins.name"}}</th>
|
||||
<th>{{i18n "admin.plugins.version"}}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
{{#each plugin in controller}}
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
@ -1,8 +0,0 @@
|
||||
<tr>
|
||||
<td class="title"><a {{bind-attr href="reportUrl"}}>{{title}}</a></td>
|
||||
<td class="value">{{todayCount}}</td>
|
||||
<td class="value">{{yesterdayCount}}</td>
|
||||
<td class="value">{{sevenDaysAgoCount}}</td>
|
||||
<td class="value">{{thirtyDaysAgoCount}}</td>
|
||||
<td class="value"></td>
|
||||
</tr>
|
||||
@ -1,8 +0,0 @@
|
||||
<tr>
|
||||
<td class="title">{{title}}</td>
|
||||
<td class="value">{{#link-to 'adminUsersList.show' 'newuser'}}{{value-at-tl data level="0"}}{{/link-to}}</td>
|
||||
<td class="value">{{#link-to 'adminUsersList.show' 'basic'}}{{value-at-tl data level="1"}}{{/link-to}}</td>
|
||||
<td class="value">{{#link-to 'adminUsersList.show' 'regular'}}{{value-at-tl data level="2"}}{{/link-to}}</td>
|
||||
<td class="value">{{#link-to 'adminUsersList.show' 'leader'}}{{value-at-tl data level="3"}}{{/link-to}}</td>
|
||||
<td class="value">{{#link-to 'adminUsersList.show' 'elder'}}{{value-at-tl data level="4"}}{{/link-to}}</td>
|
||||
</tr>
|
||||
@ -1,12 +1,12 @@
|
||||
<section {{bind-attr class=":details active::not-activated"}}>
|
||||
<section class="details {{unless model.active 'not-activated'}}">
|
||||
|
||||
<div class='user-controls'>
|
||||
{{#if active}}
|
||||
{{#if model.active}}
|
||||
{{#link-to 'user' model class="btn"}}
|
||||
{{fa-icon "user"}}
|
||||
{{i18n 'admin.user.show_public_profile'}}
|
||||
{{/link-to}}
|
||||
{{#if can_impersonate}}
|
||||
{{#if model.can_impersonate}}
|
||||
<button class='btn btn-danger' {{action "impersonate" target="content"}} title="{{i18n 'admin.impersonate.help'}}">
|
||||
{{fa-icon "crosshairs"}}
|
||||
{{i18n 'admin.impersonate.title'}}
|
||||
@ -23,7 +23,7 @@
|
||||
|
||||
<div class='display-row username'>
|
||||
<div class='field'>{{i18n 'user.username.title'}}</div>
|
||||
<div class='value'>{{username}}</div>
|
||||
<div class='value'>{{model.username}}</div>
|
||||
<div class='controls'>
|
||||
{{#link-to 'preferences.username' model class="btn"}}
|
||||
{{fa-icon "pencil"}}
|
||||
@ -36,11 +36,11 @@
|
||||
<div class='display-row email'>
|
||||
<div class='field'>{{i18n 'user.email.title'}}</div>
|
||||
<div class='value'>
|
||||
{{#unless active}}
|
||||
{{#unless model.active}}
|
||||
<div class='controls'>{{i18n 'admin.users.not_verified'}}</div>
|
||||
{{/unless}}
|
||||
{{#if email}}
|
||||
<a href="mailto:{{unbound email}}">{{email}}</a>
|
||||
{{#if model.email}}
|
||||
<a href="mailto:{{unbound model.email}}">{{model.email}}</a>
|
||||
{{else}}
|
||||
<button class="btn" title="{{i18n 'admin.users.check_email.title'}}" {{action "checkEmail" this}}>{{fa-icon "envelope-o"}} {{i18n 'admin.users.check_email.text'}}</button>
|
||||
{{/if}}
|
||||
@ -50,8 +50,8 @@
|
||||
<div class='display-row associations'>
|
||||
<div class='field'>{{i18n 'user.associated_accounts'}}</div>
|
||||
<div class='value'>
|
||||
{{#if associated_accounts}}
|
||||
{{associated_accounts}}
|
||||
{{#if model.associated_accounts}}
|
||||
{{model.associated_accounts}}
|
||||
{{else}}
|
||||
<button class="btn" title="{{i18n 'admin.users.check_email.title'}}" {{action "checkEmail" this}}>{{fa-icon "envelope-o"}} {{i18n 'admin.users.check_email.text'}}</button>
|
||||
{{/if}}
|
||||
@ -68,9 +68,9 @@
|
||||
<div class='field'>{{i18n 'user.title.title'}}</div>
|
||||
<div class='value'>
|
||||
{{#if editingTitle}}
|
||||
{{text-field value=title autofocus="autofocus"}}
|
||||
{{text-field value=model.title autofocus="autofocus"}}
|
||||
{{else}}
|
||||
<span {{action "toggleTitleEdit"}}>{{title}} </span>
|
||||
<span {{action "toggleTitleEdit"}}>{{model.title}} </span>
|
||||
{{/if}}
|
||||
</div>
|
||||
<div class='controls'>
|
||||
@ -85,23 +85,23 @@
|
||||
|
||||
<div class='display-row'>
|
||||
<div class='field'>{{i18n 'user.ip_address.title'}}</div>
|
||||
<div class='value'>{{ip_address}}</div>
|
||||
<div class='value'>{{model.ip_address}}</div>
|
||||
<div class='controls'>
|
||||
{{#if currentUser.staff}}
|
||||
<button class='btn' {{action "refreshBrowsers" target="content"}}>
|
||||
{{i18n 'admin.user.refresh_browsers'}}
|
||||
</button>
|
||||
{{ip-lookup ip=ip_address userId=id}}
|
||||
{{ip-lookup ip=model.ip_address userId=model.id}}
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class='display-row'>
|
||||
<div class='field'>{{i18n 'user.registration_ip_address.title'}}</div>
|
||||
<div class='value'>{{registration_ip_address}}</div>
|
||||
<div class='value'>{{model.registration_ip_address}}</div>
|
||||
<div class='controls'>
|
||||
{{#if currentUser.staff}}
|
||||
{{ip-lookup ip=registration_ip_address userId=id}}
|
||||
{{ip-lookup ip=model.registration_ip_address userId=model.id}}
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
@ -110,10 +110,10 @@
|
||||
<div class='display-row'>
|
||||
<div class='field'>{{i18n 'admin.badges.title'}}</div>
|
||||
<div class='value'>
|
||||
{{i18n 'badges.badge_count' count=badge_count}}
|
||||
{{i18n 'badges.badge_count' count=model.badge_count}}
|
||||
</div>
|
||||
<div class='controls'>
|
||||
{{#link-to 'adminUser.badges' this class="btn"}}{{fa-icon "certificate"}}{{i18n 'admin.badges.edit_badges'}}{{/link-to}}
|
||||
{{#link-to 'adminUser.badges' model class="btn"}}{{fa-icon "certificate"}}{{i18n 'admin.badges.edit_badges'}}{{/link-to}}
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
@ -169,26 +169,26 @@
|
||||
<div class='display-row'>
|
||||
<div class='field'>{{i18n 'admin.users.active'}}</div>
|
||||
<div class='value'>
|
||||
{{#if active}}
|
||||
{{#if model.active}}
|
||||
{{i18n 'yes_value'}}
|
||||
{{else}}
|
||||
{{i18n 'no_value'}}
|
||||
{{/if}}
|
||||
</div>
|
||||
<div class='controls'>
|
||||
{{#if active}}
|
||||
{{#if can_deactivate}}
|
||||
{{#if model.active}}
|
||||
{{#if model.can_deactivate}}
|
||||
<button class='btn' {{action "deactivate" target="content"}}>{{i18n 'admin.user.deactivate_account'}}</button>
|
||||
{{i18n 'admin.user.deactivate_explanation'}}
|
||||
{{/if}}
|
||||
{{else}}
|
||||
{{#if can_send_activation_email}}
|
||||
{{#if model.can_send_activation_email}}
|
||||
<button class='btn' {{action "sendActivationEmail" target="content"}}>
|
||||
{{fa-icon "envelope"}}
|
||||
{{i18n 'admin.user.send_activation_email'}}
|
||||
</button>
|
||||
{{/if}}
|
||||
{{#if can_activate}}
|
||||
{{#if model.can_activate}}
|
||||
<button class='btn' {{action "activate" target="content"}}>
|
||||
{{fa-icon "check"}}
|
||||
{{i18n 'admin.user.activate'}}
|
||||
@ -200,9 +200,9 @@
|
||||
|
||||
<div class='display-row'>
|
||||
<div class='field'>{{i18n 'admin.api.key'}}</div>
|
||||
{{#if api_key}}
|
||||
{{#if model.api_key}}
|
||||
<div class='long-value'>
|
||||
{{api_key.key}}
|
||||
{{model.api_key.key}}
|
||||
{{d-button action="regenerateApiKey" icon="undo" label="admin.api.regenerate"}}
|
||||
{{d-button action="revokeApiKey" icon="times" label="admin.api.revoke"}}
|
||||
</div>
|
||||
@ -218,15 +218,15 @@
|
||||
|
||||
<div class='display-row'>
|
||||
<div class='field'>{{i18n 'admin.user.admin'}}</div>
|
||||
<div class='value'>{{admin}}</div>
|
||||
<div class='value'>{{model.admin}}</div>
|
||||
<div class='controls'>
|
||||
{{#if can_revoke_admin}}
|
||||
{{#if model.can_revoke_admin}}
|
||||
<button class='btn' {{action "revokeAdmin" target="content"}}>
|
||||
{{fa-icon "shield"}}
|
||||
{{i18n 'admin.user.revoke_admin'}}
|
||||
</button>
|
||||
{{/if}}
|
||||
{{#if can_grant_admin}}
|
||||
{{#if model.can_grant_admin}}
|
||||
<button class='btn' {{action "grantAdmin" target="content"}}>
|
||||
{{fa-icon "shield"}}
|
||||
{{i18n 'admin.user.grant_admin'}}
|
||||
@ -237,15 +237,15 @@
|
||||
|
||||
<div class='display-row'>
|
||||
<div class='field'>{{i18n 'admin.user.moderator'}}</div>
|
||||
<div class='value'>{{moderator}}</div>
|
||||
<div class='value'>{{model.moderator}}</div>
|
||||
<div class='controls'>
|
||||
{{#if can_revoke_moderation}}
|
||||
{{#if model.can_revoke_moderation}}
|
||||
<button class='btn' {{action "revokeModeration" target="content"}}>
|
||||
{{fa-icon "shield"}}
|
||||
{{i18n 'admin.user.revoke_moderation'}}
|
||||
</button>
|
||||
{{/if}}
|
||||
{{#if can_grant_moderation}}
|
||||
{{#if model.can_grant_moderation}}
|
||||
<button class='btn' {{action "grantModeration" target="content"}}>
|
||||
{{fa-icon "shield"}}
|
||||
{{i18n 'admin.user.grant_moderation'}}
|
||||
@ -257,8 +257,8 @@
|
||||
<div class='display-row'>
|
||||
<div class='field'>{{i18n 'trust_level'}}</div>
|
||||
<div class="value">
|
||||
{{combo-box content=trustLevels value=trust_level nameProperty="detailedName"}}
|
||||
{{#if dirty}}
|
||||
{{combo-box content=site.trustLevels value=model.trust_level nameProperty="detailedName"}}
|
||||
{{#if model.dirty}}
|
||||
<div>
|
||||
<button class='btn ok no-text' {{action "saveTrustLevel" target="content"}}>{{fa-icon "check"}}</button>
|
||||
<button class='btn cancel no-text' {{action "restoreTrustLevel" target="content"}}>{{fa-icon "times"}}</button>
|
||||
@ -273,17 +273,17 @@
|
||||
<i title='{{i18n 'admin.user.trust_level_unlocked_tip'}}' class='fa fa-unlock'></i> <button class="btn" {{action "lockTrustLevel" true target="model"}}>{{i18n 'admin.user.lock_trust_level'}}</button>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
{{#if tl3Requirements}}
|
||||
{{#link-to 'adminUser.tl3Requirements' this class="btn"}}{{i18n 'admin.user.trust_level_3_requirements'}}{{/link-to}}
|
||||
{{#if model.tl3Requirements}}
|
||||
{{#link-to 'adminUser.tl3Requirements' model class="btn"}}{{i18n 'admin.user.trust_level_3_requirements'}}{{/link-to}}
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div {{bind-attr class=":display-row isSuspended:highlight-danger"}}>
|
||||
<div {{bind-attr class=":display-row model.isSuspended:highlight-danger"}}>
|
||||
<div class='field'>{{i18n 'admin.user.suspended'}}</div>
|
||||
<div class='value'>{{isSuspended}}</div>
|
||||
<div class='value'>{{model.isSuspended}}</div>
|
||||
<div class='controls'>
|
||||
{{#if isSuspended}}
|
||||
{{#if model.isSuspended}}
|
||||
<button class='btn btn-danger' {{action "unsuspend" target="content"}}>
|
||||
{{fa-icon "ban"}}
|
||||
{{i18n 'admin.user.unsuspend'}}
|
||||
@ -291,7 +291,7 @@
|
||||
{{suspendDuration}}
|
||||
{{i18n 'admin.user.suspended_explanation'}}
|
||||
{{else}}
|
||||
{{#if canSuspend}}
|
||||
{{#if model.canSuspend}}
|
||||
<button class='btn btn-danger' {{action "showSuspendModal" this}}>
|
||||
{{fa-icon "ban"}}
|
||||
{{i18n 'admin.user.suspend'}}
|
||||
@ -302,7 +302,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{#if isSuspended}}
|
||||
{{#if model.isSuspended}}
|
||||
<div class='display-row highlight-danger'>
|
||||
<div class='field'>{{i18n 'admin.user.suspended_by'}}</div>
|
||||
<div class='value'>
|
||||
@ -316,11 +316,11 @@
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
<div {{bind-attr class=":display-row blocked:highlight-danger"}}>
|
||||
<div {{bind-attr class=":display-row model.blocked:highlight-danger"}}>
|
||||
<div class='field'>{{i18n 'admin.user.blocked'}}</div>
|
||||
<div class='value'>{{blocked}}</div>
|
||||
<div class='value'>{{model.blocked}}</div>
|
||||
<div class='controls'>
|
||||
{{#if blocked}}
|
||||
{{#if model.blocked}}
|
||||
<button class='btn' {{action "unblock" target="content"}}>
|
||||
{{fa-icon "thumbs-o-up"}}
|
||||
{{i18n 'admin.user.unblock'}}
|
||||
@ -342,12 +342,12 @@
|
||||
<div class='display-row'>
|
||||
<div class='field'>{{i18n 'admin.groups.custom'}}</div>
|
||||
<div class='value'>
|
||||
{{admin-group-selector selected=customGroups available=availableGroups}}
|
||||
{{admin-group-selector selected=model.customGroups available=availableGroups}}
|
||||
</div>
|
||||
<div class='controls'>
|
||||
{{#if customGroups}}
|
||||
{{#if model.customGroups}}
|
||||
{{i18n 'admin.groups.primary'}}
|
||||
{{combo-box content=customGroups value=primary_group_id nameProperty="name" none="admin.groups.no_primary"}}
|
||||
{{combo-box content=model.customGroups value=model.primary_group_id nameProperty="name" none="admin.groups.no_primary"}}
|
||||
{{/if}}
|
||||
{{#if primaryGroupDirty}}
|
||||
{{d-button icon="check" class="ok no-text" action="savePrimaryGroup"}}
|
||||
@ -363,71 +363,71 @@
|
||||
|
||||
<div class='display-row'>
|
||||
<div class='field'>{{i18n 'created'}}</div>
|
||||
<div class='value'>{{{created_at_age}}}</div>
|
||||
<div class='value'>{{{model.created_at_age}}}</div>
|
||||
</div>
|
||||
<div class='display-row'>
|
||||
<div class='field'>{{i18n 'admin.users.last_emailed'}}</div>
|
||||
<div class='value'>{{{last_emailed_age}}}</div>
|
||||
<div class='value'>{{{model.last_emailed_age}}}</div>
|
||||
</div>
|
||||
<div class='display-row'>
|
||||
<div class='field'>{{i18n 'last_seen'}}</div>
|
||||
<div class='value'>{{{last_seen_age}}}</div>
|
||||
<div class='value'>{{{model.last_seen_age}}}</div>
|
||||
</div>
|
||||
<div class='display-row'>
|
||||
<div class='field'>{{i18n 'admin.user.like_count'}}</div>
|
||||
<div class='value'>{{like_given_count}} / {{like_count}}</div>
|
||||
<div class='value'>{{model.like_given_count}} / {{model.like_count}}</div>
|
||||
</div>
|
||||
<div class='display-row'>
|
||||
<div class='field'>{{i18n 'admin.user.topics_entered'}}</div>
|
||||
<div class='value'>{{topics_entered}}</div>
|
||||
<div class='value'>{{model.topics_entered}}</div>
|
||||
</div>
|
||||
<div class='display-row'>
|
||||
<div class='field'>{{i18n 'admin.user.post_count'}}</div>
|
||||
<div class='value'>{{post_count}}</div>
|
||||
<div class='value'>{{model.post_count}}</div>
|
||||
<div class='controls'>
|
||||
{{#if can_delete_all_posts}}
|
||||
{{#if post_count}}
|
||||
{{#if model.can_delete_all_posts}}
|
||||
{{#if model.post_count}}
|
||||
<button class='btn btn-danger' {{action "deleteAllPosts" target="content"}}>
|
||||
{{fa-icon "trash-o"}}
|
||||
{{i18n 'admin.user.delete_all_posts'}}
|
||||
</button>
|
||||
{{/if}}
|
||||
{{else}}
|
||||
{{deleteAllPostsExplanation}}
|
||||
{{model.deleteAllPostsExplanation}}
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
<div class='display-row'>
|
||||
<div class='field'>{{i18n 'admin.user.posts_read_count'}}</div>
|
||||
<div class='value'>{{posts_read_count}}</div>
|
||||
<div class='value'>{{model.posts_read_count}}</div>
|
||||
</div>
|
||||
<div class='display-row'>
|
||||
<div class='field'>{{i18n 'admin.user.warnings_received_count'}}</div>
|
||||
<div class='value'>{{warnings_received_count}}</div>
|
||||
<div class='value'>{{model.warnings_received_count}}</div>
|
||||
</div>
|
||||
<div class='display-row'>
|
||||
<div class='field'>{{i18n 'admin.user.flags_given_received_count'}}</div>
|
||||
<div class='value'>{{flags_given_count}} / {{flags_received_count}}</div>
|
||||
<div class='value'>{{model.flags_given_count}} / {{model.flags_received_count}}</div>
|
||||
</div>
|
||||
<div class='display-row'>
|
||||
<div class='field'>{{i18n 'admin.user.private_topics_count'}}</div>
|
||||
<div class='value'>{{private_topics_count}}</div>
|
||||
<div class='value'>{{model.private_topics_count}}</div>
|
||||
</div>
|
||||
<div class='display-row'>
|
||||
<div class='field'>{{i18n 'admin.user.time_read'}}</div>
|
||||
<div class='value'>{{{time_read}}}</div>
|
||||
<div class='value'>{{{model.time_read}}}</div>
|
||||
</div>
|
||||
<div class='display-row'>
|
||||
<div class='field'>{{i18n 'user.invited.days_visited'}}</div>
|
||||
<div class='value'>{{{days_visited}}}</div>
|
||||
<div class='value'>{{{model.days_visited}}}</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{{#if single_sign_on_record}}
|
||||
{{#if model.single_sign_on_record}}
|
||||
<section class='details'>
|
||||
<h1>{{i18n 'admin.user.sso.title'}}</h1>
|
||||
|
||||
{{#with single_sign_on_record}}
|
||||
{{#with model.single_sign_on_record}}
|
||||
<div class='display-row'>
|
||||
<div class='field'>{{i18n 'admin.user.sso.external_id'}}</div>
|
||||
<div class='value'>{{external_id}}</div>
|
||||
@ -455,28 +455,28 @@
|
||||
<section>
|
||||
<hr/>
|
||||
<div class="pull-right">
|
||||
{{#unless anonymizeForbidden}}
|
||||
{{#unless model.anonymizeForbidden}}
|
||||
{{d-button label="admin.user.anonymize"
|
||||
icon="exclamation-triangle"
|
||||
class="btn-danger"
|
||||
disabled=anonymizeForbidden
|
||||
disabled=model.anonymizeForbidden
|
||||
action="anonymize"}}
|
||||
{{/unless}}
|
||||
|
||||
{{#unless deleteForbidden}}
|
||||
{{#unless model.deleteForbidden}}
|
||||
{{d-button label="admin.user.delete"
|
||||
icon="exclamation-triangle"
|
||||
class="btn-danger"
|
||||
disabled=deleteForbidden
|
||||
disabled=model.deleteForbidden
|
||||
action="destroy"}}
|
||||
{{/unless}}
|
||||
</div>
|
||||
|
||||
{{#if deleteExplanation}}
|
||||
{{#if model.deleteExplanation}}
|
||||
<div class="clearfix"></div>
|
||||
<br/>
|
||||
<div class="pull-right">
|
||||
{{fa-icon "exclamation-triangle"}} {{deleteExplanation}}
|
||||
{{fa-icon "exclamation-triangle"}} {{model.deleteExplanation}}
|
||||
</div>
|
||||
{{/if}}
|
||||
</section>
|
||||
|
||||
@ -1 +1,3 @@
|
||||
export default Ember.View.extend(Discourse.ScrollTop);
|
||||
import ScrollTop from 'discourse/mixins/scroll-top';
|
||||
|
||||
export default Ember.View.extend(ScrollTop);
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
export default Ember.View.extend(Discourse.ScrollTop, {
|
||||
import ScrollTop from 'discourse/mixins/scroll-top';
|
||||
|
||||
export default Ember.View.extend(ScrollTop, {
|
||||
_scrollOnModelChange: function() {
|
||||
this._scrollTop();
|
||||
}.observes('controller.model.id')
|
||||
|
||||
@ -1,36 +1,21 @@
|
||||
import UploadMixin from 'discourse/mixins/upload';
|
||||
import UploadMixin from "discourse/mixins/upload";
|
||||
|
||||
export default Em.Component.extend(UploadMixin, {
|
||||
type: 'avatar',
|
||||
tagName: 'span',
|
||||
type: "avatar",
|
||||
tagName: "span",
|
||||
imageIsNotASquare: false,
|
||||
|
||||
uploadUrl: Discourse.computed.url('username', '/users/%@/preferences/user_image'),
|
||||
|
||||
uploadButtonText: function() {
|
||||
return this.get("uploading") ?
|
||||
I18n.t("uploading") :
|
||||
I18n.t("user.change_avatar.upload_picture");
|
||||
return this.get("uploading") ? I18n.t("uploading") : I18n.t("user.change_avatar.upload_picture");
|
||||
}.property("uploading"),
|
||||
|
||||
uploadDone(data) {
|
||||
// display a warning whenever the image is not a square
|
||||
this.set("imageIsNotASquare", data.result.width !== data.result.height);
|
||||
|
||||
// in order to be as much responsive as possible, we're cheating a bit here
|
||||
// indeed, the server gives us back the url to the file we've just uploaded
|
||||
// often, this file is not a square, so we need to crop it properly
|
||||
// this will also capture the first frame of animated avatars when they're not allowed
|
||||
Discourse.Utilities.cropAvatar(data.result.url, data.files[0].type).then(avatarTemplate => {
|
||||
this.set("uploadedAvatarTemplate", avatarTemplate);
|
||||
|
||||
// indicates the users is using an uploaded avatar (must happen after cropping, otherwise
|
||||
// we will attempt to load an invalid avatar and cache a redirect to old one, uploadedAvatarTemplate
|
||||
// trumps over custom avatar upload id)
|
||||
this.set("custom_avatar_upload_id", data.result.upload_id);
|
||||
uploadDone(upload) {
|
||||
this.setProperties({
|
||||
imageIsNotASquare: upload.width !== upload.height,
|
||||
uploadedAvatarTemplate: upload.url,
|
||||
custom_avatar_upload_id: upload.id,
|
||||
});
|
||||
|
||||
// the upload is now done
|
||||
this.sendAction("done");
|
||||
}
|
||||
});
|
||||
|
||||
@ -17,7 +17,7 @@ export default DiscourseContainerView.extend({
|
||||
tagName: 'button',
|
||||
attributeBindings: ['style', 'title'],
|
||||
classNames: ['colorpicker'].concat( isUsed ? ['used-color'] : ['unused-color'] ),
|
||||
style: 'background-color: #' + color + ';',
|
||||
style: ('background-color: #' + color + ';').htmlSafe(),
|
||||
title: isUsed ? I18n.t("category.already_used") : null,
|
||||
click: function() {
|
||||
self.set("value", color);
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import UploadMixin from 'discourse/mixins/upload';
|
||||
import UploadMixin from "discourse/mixins/upload";
|
||||
|
||||
export default Em.Component.extend(UploadMixin, {
|
||||
type: "emoji",
|
||||
@ -11,9 +11,9 @@ export default Em.Component.extend(UploadMixin, {
|
||||
return Ember.isBlank(this.get("name")) ? {} : { name: this.get("name") };
|
||||
}.property("name"),
|
||||
|
||||
uploadDone: function (data) {
|
||||
uploadDone(upload) {
|
||||
this.set("name", null);
|
||||
this.sendAction("done", data.result);
|
||||
this.sendAction("done", upload);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
@ -1,31 +1,21 @@
|
||||
import UploadMixin from 'discourse/mixins/upload';
|
||||
import UploadMixin from "discourse/mixins/upload";
|
||||
|
||||
export default Em.Component.extend(UploadMixin, {
|
||||
classNames: ["image-uploader"],
|
||||
|
||||
backgroundStyle: function() {
|
||||
var imageUrl = this.get('imageUrl');
|
||||
const imageUrl = this.get("imageUrl");
|
||||
if (Em.isNone(imageUrl)) { return; }
|
||||
return ("background-image: url(" + imageUrl + ")").htmlSafe();
|
||||
}.property("imageUrl"),
|
||||
|
||||
return "background-image: url(" + imageUrl + ")";
|
||||
}.property('imageUrl'),
|
||||
|
||||
uploadDone: function(data) {
|
||||
this.set('imageUrl', data.result.url);
|
||||
uploadDone(upload) {
|
||||
this.set("imageUrl", upload.url);
|
||||
},
|
||||
|
||||
actions: {
|
||||
trash: function() {
|
||||
this.set('imageUrl', null);
|
||||
|
||||
// Do we want to signal the delete to the server right away?
|
||||
if (this.get('instantDelete')) {
|
||||
Discourse.ajax(this.get('uploadUrl'), {
|
||||
type: 'DELETE',
|
||||
data: { image_type: this.get('type') }
|
||||
}).then(null, function() {
|
||||
bootbox.alert(I18n.t('generic_error'));
|
||||
});
|
||||
}
|
||||
trash() {
|
||||
this.set("imageUrl", null);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@ -62,7 +62,7 @@ export default Em.Component.extend(StringBuffer, {
|
||||
buffer.push('</ul>');
|
||||
}
|
||||
|
||||
if ((links.length <= MAX_SHOWN || !collapsed) && this.get('canReplyAsNewTopic')) {
|
||||
if (this.get('canReplyAsNewTopic')) {
|
||||
buffer.push("<a href class='reply-new'>" + iconHTML('plus') + I18n.t('post.reply_as_new_topic') + "</a>");
|
||||
}
|
||||
},
|
||||
|
||||
@ -1,10 +1,12 @@
|
||||
export default Ember.Component.extend({
|
||||
_parse: function() {
|
||||
this.$().find('hr').remove();
|
||||
this.$().ellipsis();
|
||||
Ember.run.next(null, () => {
|
||||
this.$().find('hr').remove();
|
||||
this.$().ellipsis();
|
||||
});
|
||||
}.on('didInsertElement'),
|
||||
|
||||
render: function(buffer) {
|
||||
render(buffer) {
|
||||
buffer.push(this.get('text'));
|
||||
}
|
||||
});
|
||||
|
||||
@ -191,8 +191,9 @@ export default Ember.ObjectController.extend(Presence, {
|
||||
// for now handle a very narrow use case
|
||||
// if we are replying to a topic AND not on the topic pop the window up
|
||||
if (!force && composer.get('replyingToTopic')) {
|
||||
const topic = this.get('model.topic');
|
||||
if (!topic || topic.get('id') !== composer.get('topic.id'))
|
||||
|
||||
const currentTopic = this.get('controllers.topic.model');
|
||||
if (!currentTopic || currentTopic.get('id') !== composer.get('topic.id'))
|
||||
{
|
||||
const message = I18n.t("composer.posting_not_on_topic");
|
||||
|
||||
@ -202,12 +203,12 @@ export default Ember.ObjectController.extend(Presence, {
|
||||
"link": true
|
||||
}];
|
||||
|
||||
if (topic) {
|
||||
if (currentTopic) {
|
||||
buttons.push({
|
||||
"label": I18n.t("composer.reply_here") + "<br/><div class='topic-title overflow-ellipsis'>" + Handlebars.Utils.escapeExpression(topic.get('title')) + "</div>",
|
||||
"label": I18n.t("composer.reply_here") + "<br/><div class='topic-title overflow-ellipsis'>" + Handlebars.Utils.escapeExpression(currentTopic.get('title')) + "</div>",
|
||||
"class": "btn btn-reply-here",
|
||||
"callback": function() {
|
||||
composer.set('topic', topic);
|
||||
composer.set('topic', currentTopic);
|
||||
composer.set('post', null);
|
||||
self.save(true);
|
||||
}
|
||||
|
||||
@ -5,7 +5,10 @@ import { categoryBadgeHTML } from 'discourse/helpers/category-link';
|
||||
// Modal for editing / creating a category
|
||||
export default ObjectController.extend(ModalFunctionality, {
|
||||
foregroundColors: ['FFFFFF', '000000'],
|
||||
categoryUploadUrl: '/category/uploads',
|
||||
editingPermissions: false,
|
||||
selectedTab: null,
|
||||
saving: false,
|
||||
deleting: false,
|
||||
|
||||
parentCategories: function() {
|
||||
return Discourse.Category.list().filter(function (c) {
|
||||
@ -15,31 +18,31 @@ export default ObjectController.extend(ModalFunctionality, {
|
||||
|
||||
// We can change the parent if there are no children
|
||||
subCategories: function() {
|
||||
if (Em.isEmpty(this.get('id'))) { return null; }
|
||||
return Discourse.Category.list().filterBy('parent_category_id', this.get('id'));
|
||||
if (Em.isEmpty(this.get('model.id'))) { return null; }
|
||||
return Discourse.Category.list().filterBy('parent_category_id', this.get('model.id'));
|
||||
}.property('model.id'),
|
||||
|
||||
canSelectParentCategory: Em.computed.not('isUncategorizedCategory'),
|
||||
canSelectParentCategory: Em.computed.not('model.isUncategorizedCategory'),
|
||||
|
||||
onShow: function() {
|
||||
onShow() {
|
||||
this.changeSize();
|
||||
this.titleChanged();
|
||||
},
|
||||
|
||||
changeSize: function() {
|
||||
if (this.present('description')) {
|
||||
if (this.present('model.description')) {
|
||||
this.set('controllers.modal.modalClass', 'edit-category-modal full');
|
||||
} else {
|
||||
this.set('controllers.modal.modalClass', 'edit-category-modal small');
|
||||
}
|
||||
}.observes('description'),
|
||||
}.observes('model.description'),
|
||||
|
||||
title: function() {
|
||||
if (this.get('id')) {
|
||||
if (this.get('model.id')) {
|
||||
return I18n.t("category.edit_long") + " : " + this.get('model.name');
|
||||
}
|
||||
return I18n.t("category.create") + (this.get('model.name') ? (" : " + this.get('model.name')) : '');
|
||||
}.property('id', 'model.name'),
|
||||
}.property('model.id', 'model.name'),
|
||||
|
||||
titleChanged: function() {
|
||||
this.set('controllers.modal.title', this.get('title'));
|
||||
@ -47,10 +50,10 @@ export default ObjectController.extend(ModalFunctionality, {
|
||||
|
||||
disabled: function() {
|
||||
if (this.get('saving') || this.get('deleting')) return true;
|
||||
if (!this.get('name')) return true;
|
||||
if (!this.get('color')) return true;
|
||||
if (!this.get('model.name')) return true;
|
||||
if (!this.get('model.color')) return true;
|
||||
return false;
|
||||
}.property('saving', 'name', 'color', 'deleting'),
|
||||
}.property('saving', 'model.name', 'model.color', 'deleting'),
|
||||
|
||||
emailInEnabled: Discourse.computed.setting('email_in'),
|
||||
|
||||
@ -59,80 +62,82 @@ export default ObjectController.extend(ModalFunctionality, {
|
||||
}.property('disabled', 'saving', 'deleting'),
|
||||
|
||||
colorStyle: function() {
|
||||
return "background-color: #" + (this.get('color')) + "; color: #" + (this.get('text_color')) + ";";
|
||||
}.property('color', 'text_color'),
|
||||
return "background-color: #" + this.get('model.color') + "; color: #" + this.get('model.text_color') + ";";
|
||||
}.property('model.color', 'model.text_color'),
|
||||
|
||||
categoryBadgePreview: function() {
|
||||
var c = Discourse.Category.create({
|
||||
name: this.get('categoryName'),
|
||||
color: this.get('color'),
|
||||
text_color: this.get('text_color'),
|
||||
parent_category_id: parseInt(this.get('parent_category_id'),10),
|
||||
read_restricted: this.get('model.read_restricted')
|
||||
const model = this.get('model');
|
||||
const c = Discourse.Category.create({
|
||||
name: model.get('categoryName'),
|
||||
color: model.get('color'),
|
||||
text_color: model.get('text_color'),
|
||||
parent_category_id: parseInt(model.get('parent_category_id'),10),
|
||||
read_restricted: model.get('read_restricted')
|
||||
});
|
||||
return categoryBadgeHTML(c, {link: false});
|
||||
}.property('parent_category_id', 'categoryName', 'color', 'text_color'),
|
||||
}.property('model.parent_category_id', 'model.categoryName', 'model.color', 'model.text_color'),
|
||||
|
||||
// background colors are available as a pipe-separated string
|
||||
backgroundColors: function() {
|
||||
var categories = Discourse.Category.list();
|
||||
const categories = Discourse.Category.list();
|
||||
return Discourse.SiteSettings.category_colors.split("|").map(function(i) { return i.toUpperCase(); }).concat(
|
||||
categories.map(function(c) { return c.color.toUpperCase(); }) ).uniq();
|
||||
}.property('Discourse.SiteSettings.category_colors'),
|
||||
|
||||
usedBackgroundColors: function() {
|
||||
var categories = Discourse.Category.list();
|
||||
const categories = Discourse.Category.list();
|
||||
|
||||
var currentCat = this.get('model');
|
||||
const currentCat = this.get('model');
|
||||
|
||||
return categories.map(function(c) {
|
||||
// If editing a category, don't include its color:
|
||||
return (currentCat.get('id') && currentCat.get('color').toUpperCase() === c.color.toUpperCase()) ? null : c.color.toUpperCase();
|
||||
}, this).compact();
|
||||
}.property('id', 'color'),
|
||||
}.property('model.id', 'model.color'),
|
||||
|
||||
categoryName: function() {
|
||||
var name = this.get('name') || "";
|
||||
const name = this.get('name') || "";
|
||||
return name.trim().length > 0 ? name : I18n.t("preview");
|
||||
}.property('name'),
|
||||
|
||||
buttonTitle: function() {
|
||||
if (this.get('saving')) return I18n.t("saving");
|
||||
if (this.get('isUncategorizedCategory')) return I18n.t("save");
|
||||
return (this.get('id') ? I18n.t("category.save") : I18n.t("category.create"));
|
||||
}.property('saving', 'id'),
|
||||
if (this.get('model.isUncategorizedCategory')) return I18n.t("save");
|
||||
return (this.get('model.id') ? I18n.t("category.save") : I18n.t("category.create"));
|
||||
}.property('saving', 'model.id'),
|
||||
|
||||
deleteButtonTitle: function() {
|
||||
return I18n.t('category.delete');
|
||||
}.property(),
|
||||
|
||||
showDescription: function() {
|
||||
return !this.get('isUncategorizedCategory') && this.get('id');
|
||||
}.property('isUncategorizedCategory', 'id'),
|
||||
return !this.get('model.isUncategorizedCategory') && this.get('model.id');
|
||||
}.property('model.isUncategorizedCategory', 'model.id'),
|
||||
|
||||
showPositionInput: Discourse.computed.setting('fixed_category_positions'),
|
||||
|
||||
actions: {
|
||||
showCategoryTopic: function() {
|
||||
showCategoryTopic() {
|
||||
this.send('closeModal');
|
||||
Discourse.URL.routeTo(this.get('topic_url'));
|
||||
Discourse.URL.routeTo(this.get('model.topic_url'));
|
||||
return false;
|
||||
},
|
||||
|
||||
editPermissions: function(){
|
||||
editPermissions() {
|
||||
this.set('editingPermissions', true);
|
||||
},
|
||||
|
||||
addPermission: function(group, permission_id){
|
||||
this.get('model').addPermission({group_name: group + "", permission: Discourse.PermissionType.create({id: permission_id})});
|
||||
addPermission(group, id) {
|
||||
this.get('model').addPermission({group_name: group + "",
|
||||
permission: Discourse.PermissionType.create({id})});
|
||||
},
|
||||
|
||||
removePermission: function(permission){
|
||||
removePermission(permission) {
|
||||
this.get('model').removePermission(permission);
|
||||
},
|
||||
|
||||
saveCategory: function() {
|
||||
var self = this,
|
||||
saveCategory() {
|
||||
const self = this,
|
||||
model = this.get('model'),
|
||||
parentCategory = Discourse.Category.list().findBy('id', parseInt(model.get('parent_category_id'), 10));
|
||||
|
||||
@ -155,8 +160,8 @@ export default ObjectController.extend(ModalFunctionality, {
|
||||
});
|
||||
},
|
||||
|
||||
deleteCategory: function() {
|
||||
var self = this;
|
||||
deleteCategory() {
|
||||
const self = this;
|
||||
this.set('deleting', true);
|
||||
|
||||
this.send('hideModal');
|
||||
|
||||
@ -3,6 +3,7 @@ import ObjectController from 'discourse/controllers/object';
|
||||
// The basic controller for a group
|
||||
export default ObjectController.extend({
|
||||
counts: null,
|
||||
showing: null,
|
||||
|
||||
// It would be nice if bootstrap marked action lists as selected when their links
|
||||
// were 'active' not the `li` tags.
|
||||
|
||||
@ -5,15 +5,13 @@ export default Ember.ObjectController.extend({
|
||||
loadMore() {
|
||||
if (this.get("loading")) { return; }
|
||||
// we've reached the end
|
||||
if (this.get("members.length") >= this.get("user_count")) { return; }
|
||||
if (this.get("model.members.length") >= this.get("user_count")) { return; }
|
||||
|
||||
this.set("loading", true);
|
||||
|
||||
const self = this;
|
||||
|
||||
Discourse.Group.loadMembers(this.get("name"), this.get("members.length"), this.get("limit")).then(function (result) {
|
||||
self.get("members").addObjects(result.members.map(member => Discourse.User.create(member)));
|
||||
self.setProperties({
|
||||
Discourse.Group.loadMembers(this.get("name"), this.get("model.members.length"), this.get("limit")).then(result => {
|
||||
this.get("model.members").addObjects(result.members.map(member => Discourse.User.create(member)));
|
||||
this.setProperties({
|
||||
loading: false,
|
||||
user_count: result.meta.total,
|
||||
limit: result.meta.limit,
|
||||
|
||||
@ -1,21 +0,0 @@
|
||||
export default Em.ObjectController.extend({
|
||||
|
||||
byName: function() {
|
||||
var result = "",
|
||||
longName = this.get('user_long_name'),
|
||||
title = this.get('user_title');
|
||||
|
||||
if (!Em.isEmpty(longName)) {
|
||||
result += longName;
|
||||
}
|
||||
if (!Em.isEmpty(title)) {
|
||||
if (result.length > 0) {
|
||||
result += ", ";
|
||||
}
|
||||
result += title;
|
||||
}
|
||||
return result;
|
||||
}.property()
|
||||
|
||||
});
|
||||
|
||||
@ -16,12 +16,13 @@ export default ObjectController.extend(Presence, ModalFunctionality, {
|
||||
disabled: function() {
|
||||
if (this.get('saving')) return true;
|
||||
if (this.blank('emailOrUsername')) return true;
|
||||
const emailOrUsername = this.get('emailOrUsername').trim();
|
||||
// when inviting to forum, email must be valid
|
||||
if (!this.get('invitingToTopic') && !Discourse.Utilities.emailValid(this.get('emailOrUsername'))) return true;
|
||||
if (!this.get('invitingToTopic') && !Discourse.Utilities.emailValid(emailOrUsername)) return true;
|
||||
// normal users (not admin) can't invite users to private topic via email
|
||||
if (!this.get('isAdmin') && this.get('isPrivateTopic') && Discourse.Utilities.emailValid(this.get('emailOrUsername'))) return true;
|
||||
if (!this.get('isAdmin') && this.get('isPrivateTopic') && Discourse.Utilities.emailValid(emailOrUsername)) return true;
|
||||
// when invting to private topic via email, group name must be specified
|
||||
if (this.get('isPrivateTopic') && this.blank('groupNames') && Discourse.Utilities.emailValid(this.get('emailOrUsername'))) return true;
|
||||
if (this.get('isPrivateTopic') && this.blank('groupNames') && Discourse.Utilities.emailValid(emailOrUsername)) return true;
|
||||
if (this.get('model.details.can_invite_to')) return false;
|
||||
return false;
|
||||
}.property('isAdmin', 'emailOrUsername', 'invitingToTopic', 'isPrivateTopic', 'groupNames', 'saving'),
|
||||
@ -135,7 +136,7 @@ export default ObjectController.extend(Presence, ModalFunctionality, {
|
||||
|
||||
this.setProperties({ saving: true, error: false });
|
||||
|
||||
return this.get('model').createInvite(this.get('emailOrUsername'), groupNames).then(result => {
|
||||
return this.get('model').createInvite(this.get('emailOrUsername').trim(), groupNames).then(result => {
|
||||
this.setProperties({ saving: false, finished: true });
|
||||
if (!this.get('invitingToTopic')) {
|
||||
Discourse.Invite.findInvitedBy(Discourse.User.current()).then(invite_model => {
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import ObjectController from 'discourse/controllers/object';
|
||||
import CanCheckEmails from 'discourse/mixins/can-check-emails';
|
||||
import { popupAjaxError } from 'discourse/lib/ajax-error';
|
||||
|
||||
export default ObjectController.extend(CanCheckEmails, {
|
||||
|
||||
@ -10,8 +11,10 @@ export default ObjectController.extend(CanCheckEmails, {
|
||||
editHistoryVisible: Discourse.computed.setting('edit_history_visible_to_public'),
|
||||
|
||||
selectedCategories: function(){
|
||||
return [].concat(this.get("watchedCategories"), this.get("trackedCategories"), this.get("mutedCategories"));
|
||||
}.property("watchedCategories", "trackedCategories", "mutedCategories"),
|
||||
return [].concat(this.get("model.watchedCategories"),
|
||||
this.get("model.trackedCategories"),
|
||||
this.get("model.mutedCategories"));
|
||||
}.property("model.watchedCategories", "model.trackedCategories", "model.mutedCategories"),
|
||||
|
||||
// By default we haven't saved anything
|
||||
saved: false,
|
||||
@ -21,7 +24,7 @@ export default ObjectController.extend(CanCheckEmails, {
|
||||
userFields: function() {
|
||||
let siteUserFields = this.site.get('user_fields');
|
||||
if (!Ember.isEmpty(siteUserFields)) {
|
||||
const userFields = this.get('user_fields');
|
||||
const userFields = this.get('model.user_fields');
|
||||
|
||||
// Staff can edit fields that are not `editable`
|
||||
if (!this.get('currentUser.staff')) {
|
||||
@ -32,7 +35,7 @@ export default ObjectController.extend(CanCheckEmails, {
|
||||
return Ember.Object.create({ value, field });
|
||||
});
|
||||
}
|
||||
}.property('user_fields.@each.value'),
|
||||
}.property('model.user_fields.@each.value'),
|
||||
|
||||
cannotDeleteAccount: Em.computed.not('can_delete_account'),
|
||||
deleteDisabled: Em.computed.or('saving', 'deleting', 'cannotDeleteAccount'),
|
||||
@ -84,19 +87,19 @@ export default ObjectController.extend(CanCheckEmails, {
|
||||
{ name: I18n.t('user.new_topic_duration.last_here'), value: -2 }],
|
||||
|
||||
saveButtonText: function() {
|
||||
return this.get('saving') ? I18n.t('saving') : I18n.t('save');
|
||||
}.property('saving'),
|
||||
return this.get('model.isSaving') ? I18n.t('saving') : I18n.t('save');
|
||||
}.property('model.isSaving'),
|
||||
|
||||
imageUploadUrl: Discourse.computed.url('username', '/users/%@/preferences/user_image'),
|
||||
passwordProgress: null,
|
||||
|
||||
actions: {
|
||||
|
||||
save() {
|
||||
const self = this;
|
||||
this.setProperties({ saving: true, saved: false });
|
||||
this.set('saved', false);
|
||||
|
||||
const model = this.get('model'),
|
||||
userFields = this.get('userFields');
|
||||
const model = this.get('model');
|
||||
const userFields = this.get('userFields');
|
||||
|
||||
// Update the user fields
|
||||
if (!Ember.isEmpty(userFields)) {
|
||||
@ -111,22 +114,12 @@ export default ObjectController.extend(CanCheckEmails, {
|
||||
// Cook the bio for preview
|
||||
model.set('name', this.get('newNameInput'));
|
||||
return model.save().then(function() {
|
||||
// model was saved
|
||||
self.set('saving', false);
|
||||
if (Discourse.User.currentProp('id') === model.get('id')) {
|
||||
Discourse.User.currentProp('name', model.get('name'));
|
||||
}
|
||||
self.set('bio_cooked', Discourse.Markdown.cook(Discourse.Markdown.sanitize(self.get('bio_raw'))));
|
||||
model.set('bio_cooked', Discourse.Markdown.cook(Discourse.Markdown.sanitize(model.get('bio_raw'))));
|
||||
self.set('saved', true);
|
||||
}, function(error) {
|
||||
// model failed to save
|
||||
self.set('saving', false);
|
||||
if (error && error.responseText) {
|
||||
alert($.parseJSON(error.responseText).errors[0]);
|
||||
} else {
|
||||
alert(I18n.t('generic_error'));
|
||||
}
|
||||
});
|
||||
}).catch(popupAjaxError);
|
||||
},
|
||||
|
||||
changePassword() {
|
||||
|
||||
@ -2,6 +2,7 @@ export default Ember.ObjectController.extend({
|
||||
needs: ['topic'],
|
||||
progressPosition: null,
|
||||
expanded: false,
|
||||
toPostIndex: null,
|
||||
|
||||
actions: {
|
||||
toggleExpansion: function(opts) {
|
||||
@ -50,11 +51,11 @@ export default Ember.ObjectController.extend({
|
||||
},
|
||||
|
||||
jumpTop: function() {
|
||||
this.jumpTo(this.get('firstPostUrl'));
|
||||
this.jumpTo(this.get('model.firstPostUrl'));
|
||||
},
|
||||
|
||||
jumpBottom: function() {
|
||||
this.jumpTo(this.get('lastPostUrl'));
|
||||
this.jumpTo(this.get('model.lastPostUrl'));
|
||||
}
|
||||
},
|
||||
|
||||
@ -83,8 +84,8 @@ export default Ember.ObjectController.extend({
|
||||
|
||||
jumpBottomDisabled: function() {
|
||||
return this.get('progressPosition') >= this.get('model.postStream.filteredPostsCount') ||
|
||||
this.get('progressPosition') >= this.get('highest_post_number');
|
||||
}.property('model.postStream.filteredPostsCount', 'highest_post_number', 'progressPosition'),
|
||||
this.get('progressPosition') >= this.get('model.highest_post_number');
|
||||
}.property('model.postStream.filteredPostsCount', 'model.highest_post_number', 'progressPosition'),
|
||||
|
||||
hideProgress: function() {
|
||||
if (!this.get('model.postStream.loaded')) return true;
|
||||
@ -95,14 +96,14 @@ export default Ember.ObjectController.extend({
|
||||
|
||||
hugeNumberOfPosts: function() {
|
||||
return (this.get('model.postStream.filteredPostsCount') >= Discourse.SiteSettings.short_progress_text_threshold);
|
||||
}.property('highest_post_number'),
|
||||
}.property('model.highest_post_number'),
|
||||
|
||||
jumpToBottomTitle: function() {
|
||||
if (this.get('hugeNumberOfPosts')) {
|
||||
return I18n.t('topic.progress.jump_bottom_with_number', {post_number: this.get('highest_post_number')});
|
||||
return I18n.t('topic.progress.jump_bottom_with_number', {post_number: this.get('model.highest_post_number')});
|
||||
} else {
|
||||
return I18n.t('topic.progress.jump_bottom');
|
||||
}
|
||||
}.property('hugeNumberOfPosts', 'highest_post_number')
|
||||
}.property('hugeNumberOfPosts', 'model.highest_post_number')
|
||||
|
||||
});
|
||||
|
||||
@ -16,6 +16,7 @@ export default ObjectController.extend(SelectedPostsCount, BufferedContent, {
|
||||
loadedAllPosts: false,
|
||||
enteredAt: null,
|
||||
firstPostExpanded: false,
|
||||
retrying: false,
|
||||
|
||||
maxTitleLength: Discourse.computed.setting('max_topic_title_length'),
|
||||
|
||||
|
||||
@ -35,57 +35,60 @@ export default Ember.Controller.extend({
|
||||
show(username, postId, target) {
|
||||
// XSS protection (should be encapsulated)
|
||||
username = username.toString().replace(/[^A-Za-z0-9_]/g, "");
|
||||
const url = "/users/" + username;
|
||||
|
||||
// Don't show on mobile
|
||||
if (Discourse.Mobile.mobileView) {
|
||||
const url = "/users/" + username;
|
||||
Discourse.URL.routeTo(url);
|
||||
return;
|
||||
}
|
||||
|
||||
const currentUsername = this.get('username'),
|
||||
wasVisible = this.get('visible'),
|
||||
post = this.get('viewingTopic') && postId ? this.get('postStream').findLoadedPost(postId) : null;
|
||||
|
||||
this.setProperties({ avatar: null, post: post, 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, cardTarget: null });
|
||||
return;
|
||||
}
|
||||
wasVisible = this.get('visible'),
|
||||
previousTarget = this.get('cardTarget'),
|
||||
post = this.get('viewingTopic') && postId ? this.get('postStream').findLoadedPost(postId) : null;
|
||||
|
||||
if (username === currentUsername && this.get('userLoading') === username) {
|
||||
// debounce
|
||||
return;
|
||||
}
|
||||
|
||||
this.set('topicPostCount', null);
|
||||
if (wasVisible) {
|
||||
this.close();
|
||||
if (target === previousTarget) {
|
||||
return; // Same target, close it without loading the new user card
|
||||
}
|
||||
}
|
||||
|
||||
this.setProperties({ user: null, userLoading: username, cardTarget: target });
|
||||
this.setProperties({ username, userLoading: username, cardTarget: target, post });
|
||||
|
||||
const args = { stats: false };
|
||||
args.include_post_count_for = this.get('controllers.topic.id');
|
||||
|
||||
const self = this;
|
||||
return Discourse.User.findByUsername(username, args).then(function(user) {
|
||||
args.include_post_count_for = this.get('controllers.topic.model.id');
|
||||
|
||||
return Discourse.User.findByUsername(username, args).then((user) => {
|
||||
if (user.topic_post_count) {
|
||||
self.set('topicPostCount', user.topic_post_count[args.include_post_count_for]);
|
||||
this.set('topicPostCount', user.topic_post_count[args.include_post_count_for]);
|
||||
}
|
||||
user = Discourse.User.create(user);
|
||||
self.setProperties({ user, avatar: user, visible: true});
|
||||
self.appEvents.trigger('usercard:shown');
|
||||
}).catch(function(error) {
|
||||
self.close();
|
||||
this.setProperties({ user, avatar: user, visible: true });
|
||||
}).catch((error) => {
|
||||
this.close();
|
||||
throw error;
|
||||
}).finally(function() {
|
||||
self.set('userLoading', null);
|
||||
}).finally(() => {
|
||||
this.set('userLoading', null);
|
||||
});
|
||||
},
|
||||
|
||||
close() {
|
||||
this.setProperties({ visible: false, cardTarget: null });
|
||||
this.setProperties({
|
||||
visible: false,
|
||||
user: null,
|
||||
username: null,
|
||||
avatar: null,
|
||||
userLoading: null,
|
||||
cardTarget: null,
|
||||
post: null,
|
||||
topicPostCount: null
|
||||
});
|
||||
},
|
||||
|
||||
actions: {
|
||||
|
||||
@ -2,12 +2,12 @@ import registerUnbound from 'discourse/helpers/register-unbound';
|
||||
|
||||
registerUnbound('link-domain', function(link) {
|
||||
if (link) {
|
||||
var internal = Em.get(link, 'internal'),
|
||||
hasTitle = (!Em.isEmpty(Em.get(link, 'title')));
|
||||
if (hasTitle && !internal) {
|
||||
var domain = Em.get(link, 'domain');
|
||||
if (!Em.isEmpty(domain)) {
|
||||
var s = domain.split('.');
|
||||
const hasTitle = (!Ember.isEmpty(Em.get(link, 'title')));
|
||||
|
||||
if (hasTitle) {
|
||||
let domain = Ember.get(link, 'domain');
|
||||
if (!Ember.isEmpty(domain)) {
|
||||
const s = domain.split('.');
|
||||
domain = s[s.length-2] + "." + s[s.length-1];
|
||||
return new Handlebars.SafeString("<span class='domain'>" + domain + "</span>");
|
||||
}
|
||||
|
||||
@ -45,6 +45,6 @@ export default {
|
||||
inject(app, 'currentUser', 'component', 'route', 'controller');
|
||||
|
||||
app.register('message-bus:main', window.MessageBus, { instantiate: false });
|
||||
inject(app, 'messageBus', 'route', 'controller', 'view');
|
||||
inject(app, 'messageBus', 'route', 'controller', 'view', 'component');
|
||||
}
|
||||
};
|
||||
|
||||
@ -29,6 +29,8 @@ function extractError(error) {
|
||||
if (parsedJSON) {
|
||||
if (parsedJSON.errors && parsedJSON.errors.length > 0) {
|
||||
parsedError = parsedJSON.errors[0];
|
||||
} else if (parsedJSON.error) {
|
||||
parsedError = parsedJSON.error;
|
||||
} else if (parsedJSON.failed) {
|
||||
parsedError = parsedJSON.message;
|
||||
}
|
||||
|
||||
@ -7,7 +7,6 @@ let lastAction = -1;
|
||||
|
||||
const focusTrackerKey = "focus-tracker";
|
||||
const idleThresholdTime = 1000 * 10; // 10 seconds
|
||||
let notificationTagName; // "discourse-notification-popup-" + Discourse.SiteSettings.title;
|
||||
|
||||
// Called from an initializer
|
||||
function init(messageBus) {
|
||||
@ -25,8 +24,6 @@ function init(messageBus) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (!("Notification" in window)) {
|
||||
Em.Logger.info('Discourse desktop notifications are disabled - not supported by browser');
|
||||
return;
|
||||
@ -55,8 +52,6 @@ function init(messageBus) {
|
||||
// This function is only called if permission was granted
|
||||
function setupNotifications() {
|
||||
|
||||
notificationTagName = "discourse-notification-popup-" + Discourse.SiteSettings.title;
|
||||
|
||||
window.addEventListener("storage", function(e) {
|
||||
// note: This event only fires when other tabs setItem()
|
||||
const key = e.key;
|
||||
@ -108,13 +103,14 @@ function onNotification(data) {
|
||||
|
||||
const notificationBody = data.excerpt;
|
||||
const notificationIcon = Discourse.SiteSettings.logo_small_url || Discourse.SiteSettings.logo_url;
|
||||
const notificationTag = "discourse-notification-" + Discourse.SiteSettings.title + "-" + data.topic_id;
|
||||
|
||||
requestPermission().then(function() {
|
||||
// This shows the notification!
|
||||
const notification = new Notification(notificationTitle, {
|
||||
body: notificationBody,
|
||||
icon: notificationIcon,
|
||||
tag: notificationTagName
|
||||
tag: notificationTag
|
||||
});
|
||||
|
||||
function clickEventHandler() {
|
||||
|
||||
@ -20,7 +20,7 @@ const popstateCallbacks = [];
|
||||
*/
|
||||
const DiscourseLocation = Ember.Object.extend({
|
||||
|
||||
init: function() {
|
||||
init() {
|
||||
set(this, 'location', get(this, 'location') || window.location);
|
||||
this.initState();
|
||||
},
|
||||
@ -32,11 +32,11 @@ const DiscourseLocation = Ember.Object.extend({
|
||||
|
||||
@method initState
|
||||
*/
|
||||
initState: function() {
|
||||
initState() {
|
||||
set(this, 'history', get(this, 'history') || window.history);
|
||||
|
||||
var url = this.formatURL(this.getURL()),
|
||||
loc = get(this, 'location');
|
||||
let url = this.formatURL(this.getURL());
|
||||
const loc = get(this, 'location');
|
||||
|
||||
if (loc && loc.hash) {
|
||||
url += loc.hash;
|
||||
@ -60,15 +60,15 @@ const DiscourseLocation = Ember.Object.extend({
|
||||
|
||||
@method getURL
|
||||
*/
|
||||
getURL: function() {
|
||||
var rootURL = (Discourse.BaseUri === undefined ? "/" : Discourse.BaseUri),
|
||||
location = get(this, 'location'),
|
||||
url = location.pathname;
|
||||
getURL() {
|
||||
const location = get(this, 'location');
|
||||
let rootURL = (Discourse.BaseUri === undefined ? "/" : Discourse.BaseUri);
|
||||
let url = location.pathname;
|
||||
|
||||
rootURL = rootURL.replace(/\/$/, '');
|
||||
url = url.replace(rootURL, '');
|
||||
|
||||
var search = location.search || '';
|
||||
const search = location.search || '';
|
||||
url += search;
|
||||
|
||||
return url;
|
||||
@ -82,8 +82,8 @@ const DiscourseLocation = Ember.Object.extend({
|
||||
@method setURL
|
||||
@param path {String}
|
||||
*/
|
||||
setURL: function(path) {
|
||||
var state = this.getState();
|
||||
setURL(path) {
|
||||
const state = this.getState();
|
||||
path = this.formatURL(path);
|
||||
|
||||
if (state && state.path !== path) {
|
||||
@ -100,8 +100,8 @@ const DiscourseLocation = Ember.Object.extend({
|
||||
@method replaceURL
|
||||
@param path {String}
|
||||
*/
|
||||
replaceURL: function(path) {
|
||||
var state = this.getState();
|
||||
replaceURL(path) {
|
||||
const state = this.getState();
|
||||
path = this.formatURL(path);
|
||||
|
||||
if (state && state.path !== path) {
|
||||
@ -114,11 +114,11 @@ const DiscourseLocation = Ember.Object.extend({
|
||||
|
||||
Get the current `history.state`
|
||||
Polyfill checks for native browser support and falls back to retrieving
|
||||
from a private _historyState variable
|
||||
from a private _historyState constiable
|
||||
|
||||
@method getState
|
||||
*/
|
||||
getState: function() {
|
||||
getState() {
|
||||
return supportsHistoryState ? get(this, 'history').state : this._historyState;
|
||||
},
|
||||
|
||||
@ -130,8 +130,8 @@ const DiscourseLocation = Ember.Object.extend({
|
||||
@method pushState
|
||||
@param path {String}
|
||||
*/
|
||||
pushState: function(path) {
|
||||
var state = { path: path };
|
||||
pushState(path) {
|
||||
const state = { path: path };
|
||||
|
||||
// store state if browser doesn't support `history.state`
|
||||
if (!supportsHistoryState) {
|
||||
@ -152,8 +152,8 @@ const DiscourseLocation = Ember.Object.extend({
|
||||
@method replaceState
|
||||
@param path {String}
|
||||
*/
|
||||
replaceState: function(path) {
|
||||
var state = { path: path };
|
||||
replaceState(path) {
|
||||
const state = { path: path };
|
||||
|
||||
// store state if browser doesn't support `history.state`
|
||||
if (!supportsHistoryState) {
|
||||
@ -175,8 +175,8 @@ const DiscourseLocation = Ember.Object.extend({
|
||||
@method onUpdateURL
|
||||
@param callback {Function}
|
||||
*/
|
||||
onUpdateURL: function(callback) {
|
||||
var guid = Ember.guidFor(this),
|
||||
onUpdateURL(callback) {
|
||||
const guid = Ember.guidFor(this),
|
||||
self = this;
|
||||
|
||||
Ember.$(window).on('popstate.ember-location-'+guid, function() {
|
||||
@ -185,7 +185,7 @@ const DiscourseLocation = Ember.Object.extend({
|
||||
popstateFired = true;
|
||||
if (self.getURL() === self._previousURL) { return; }
|
||||
}
|
||||
var url = self.getURL();
|
||||
const url = self.getURL();
|
||||
popstateCallbacks.forEach(function(cb) {
|
||||
cb(url);
|
||||
});
|
||||
@ -201,8 +201,8 @@ const DiscourseLocation = Ember.Object.extend({
|
||||
@method formatURL
|
||||
@param url {String}
|
||||
*/
|
||||
formatURL: function(url) {
|
||||
var rootURL = get(this, 'rootURL');
|
||||
formatURL(url) {
|
||||
let rootURL = get(this, 'rootURL');
|
||||
|
||||
if (url !== '') {
|
||||
rootURL = rootURL.replace(/\/$/, '');
|
||||
@ -215,8 +215,8 @@ const DiscourseLocation = Ember.Object.extend({
|
||||
return rootURL + url;
|
||||
},
|
||||
|
||||
willDestroy: function() {
|
||||
var guid = Ember.guidFor(this);
|
||||
willDestroy() {
|
||||
const guid = Ember.guidFor(this);
|
||||
|
||||
Ember.$(window).off('popstate.ember-location-'+guid);
|
||||
}
|
||||
@ -230,7 +230,7 @@ const DiscourseLocation = Ember.Object.extend({
|
||||
**/
|
||||
CloakedCollectionView.reopen({
|
||||
_watchForPopState: function() {
|
||||
var self = this,
|
||||
const self = this,
|
||||
cb = function() {
|
||||
// Sam: This is a hack, but a very important one
|
||||
// Due to the way we use replace state the back button works strangely
|
||||
|
||||
@ -4,7 +4,8 @@ var PATH_BINDINGS = {
|
||||
'g n': '/new',
|
||||
'g u': '/unread',
|
||||
'g c': '/categories',
|
||||
'g t': '/top'
|
||||
'g t': '/top',
|
||||
'g b': '/bookmarks'
|
||||
},
|
||||
|
||||
SELECTED_POST_BINDINGS = {
|
||||
|
||||
@ -1,18 +1,23 @@
|
||||
/* global assetPath */
|
||||
|
||||
const _loaded = {};
|
||||
const _loading = {};
|
||||
|
||||
function loadWithTag(path, cb) {
|
||||
const head = document.getElementsByTagName('head')[0];
|
||||
|
||||
let s = document.createElement('script');
|
||||
s.src = path;
|
||||
if (Ember.Test) { Ember.Test.pendingAjaxRequests++; }
|
||||
head.appendChild(s);
|
||||
|
||||
s.onload = s.onreadystatechange = function(_, abort) {
|
||||
if (Ember.Test) { Ember.Test.pendingAjaxRequests--; }
|
||||
if (abort || !s.readyState || s.readyState === "loaded" || s.readyState === "complete") {
|
||||
s = s.onload = s.onreadystatechange = null;
|
||||
if (!abort) { cb(); }
|
||||
if (!abort) {
|
||||
Ember.run(null, cb);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
@ -25,9 +30,20 @@ export default function loadScript(url, opts) {
|
||||
|
||||
// If we already loaded this url
|
||||
if (_loaded[url]) { return resolve(); }
|
||||
if (_loading[url]) { return _loading[url].then(resolve);}
|
||||
|
||||
var done;
|
||||
_loading[url] = new Ember.RSVP.Promise(function(_done){
|
||||
done = _done;
|
||||
});
|
||||
|
||||
_loading[url].then(function(){
|
||||
delete _loading[url];
|
||||
});
|
||||
|
||||
const cb = function() {
|
||||
_loaded[url] = true;
|
||||
done();
|
||||
resolve();
|
||||
};
|
||||
|
||||
|
||||
@ -89,7 +89,7 @@ Discourse.URL = Ember.Object.createWithMixins({
|
||||
Em.run.schedule('afterRender', function() {
|
||||
var $elem = $(id);
|
||||
if ($elem.length === 0) {
|
||||
$elem = $("[name=" + id.replace('#', ''));
|
||||
$elem = $("[name='" + id.replace('#', '') + "']");
|
||||
}
|
||||
if ($elem.length > 0) {
|
||||
$('html,body').scrollTop($elem.offset().top - $('header').height() - 15);
|
||||
@ -230,7 +230,7 @@ Discourse.URL = Ember.Object.createWithMixins({
|
||||
var self = this;
|
||||
postStream.refresh(opts).then(function() {
|
||||
topicController.setProperties({
|
||||
currentPost: closest,
|
||||
'model.currentPost': closest,
|
||||
enteredAt: new Date().getTime().toString()
|
||||
});
|
||||
var closestPost = postStream.closestPostForPostNumber(closest),
|
||||
|
||||
@ -1,10 +1,3 @@
|
||||
/**
|
||||
General utility functions
|
||||
|
||||
@class Utilities
|
||||
@namespace Discourse
|
||||
@module Discourse
|
||||
**/
|
||||
Discourse.Utilities = {
|
||||
|
||||
translateSize: function(size) {
|
||||
@ -195,22 +188,10 @@ Discourse.Utilities = {
|
||||
return true;
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
Determine whether all file extensions are authorized.
|
||||
|
||||
@method authorizesAllExtensions
|
||||
**/
|
||||
authorizesAllExtensions: function() {
|
||||
return Discourse.SiteSettings.authorized_extensions.indexOf("*") >= 0;
|
||||
},
|
||||
|
||||
/**
|
||||
Check the extension of the file against the list of authorized extensions
|
||||
|
||||
@method isAuthorizedUpload
|
||||
@param {File} file The file we want to upload
|
||||
**/
|
||||
isAuthorizedUpload: function(file) {
|
||||
if (file && file.name) {
|
||||
var extensions = _.chain(Discourse.SiteSettings.authorized_extensions.split("|"))
|
||||
@ -222,11 +203,6 @@ Discourse.Utilities = {
|
||||
return false;
|
||||
},
|
||||
|
||||
/**
|
||||
List the authorized extension for display
|
||||
|
||||
@method authorizedExtensions
|
||||
**/
|
||||
authorizedExtensions: function() {
|
||||
return _.chain(Discourse.SiteSettings.authorized_extensions.split("|"))
|
||||
.reject(function(extension) { return extension.indexOf("*") >= 0; })
|
||||
@ -235,12 +211,6 @@ Discourse.Utilities = {
|
||||
.join(", ");
|
||||
},
|
||||
|
||||
/**
|
||||
Get the markdown template for an upload (either an image or an attachment)
|
||||
|
||||
@method getUploadMarkdown
|
||||
@param {Upload} upload The upload we want the markdown from
|
||||
**/
|
||||
getUploadMarkdown: function(upload) {
|
||||
if (Discourse.Utilities.isAnImage(upload.original_filename)) {
|
||||
return '<img src="' + upload.url + '" width="' + upload.width + '" height="' + upload.height + '">';
|
||||
@ -249,12 +219,6 @@ Discourse.Utilities = {
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
Check whether the path is refering to an image
|
||||
|
||||
@method isAnImage
|
||||
@param {String} path The path
|
||||
**/
|
||||
isAnImage: function(path) {
|
||||
return (/\.(png|jpe?g|gif|bmp|tiff?|svg|webp)$/i).test(path);
|
||||
},
|
||||
@ -264,11 +228,6 @@ Discourse.Utilities = {
|
||||
(/(png|jpe?g|gif|bmp|tiff?|svg|webp)/i).test(Discourse.Utilities.authorizedExtensions());
|
||||
},
|
||||
|
||||
/**
|
||||
Determines whether we allow attachments or not
|
||||
|
||||
@method allowsAttachments
|
||||
**/
|
||||
allowsAttachments: function() {
|
||||
return Discourse.Utilities.authorizesAllExtensions() ||
|
||||
!(/((png|jpe?g|gif|bmp|tiff?|svg|webp)(,\s)?)+$/i).test(Discourse.Utilities.authorizedExtensions());
|
||||
@ -296,60 +255,14 @@ Discourse.Utilities = {
|
||||
}
|
||||
return;
|
||||
}
|
||||
} else if (data.errors && data.errors.length > 0) {
|
||||
bootbox.alert(data.errors.join("\n"));
|
||||
return;
|
||||
}
|
||||
// otherwise, display a generic error message
|
||||
bootbox.alert(I18n.t('post.errors.upload'));
|
||||
},
|
||||
|
||||
/**
|
||||
Crop an image to be used as avatar.
|
||||
Simulate the "centered square thumbnail" generation done server-side.
|
||||
Uses only the first frame of animated gifs when they are disabled.
|
||||
|
||||
@method cropAvatar
|
||||
@param {String} url The url of the avatar
|
||||
@param {String} fileType The file type of the uploaded file
|
||||
@returns {Promise} a promise that will eventually be the cropped avatar.
|
||||
**/
|
||||
cropAvatar: function(url, fileType) {
|
||||
if (Discourse.SiteSettings.allow_animated_avatars && fileType === "image/gif") {
|
||||
// can't crop animated gifs... let the browser stretch the gif
|
||||
return Ember.RSVP.resolve(url);
|
||||
} else {
|
||||
return new Ember.RSVP.Promise(function(resolve) {
|
||||
var image = document.createElement("img");
|
||||
image.crossOrigin = 'Anonymous';
|
||||
// this event will be fired as soon as the image is loaded
|
||||
image.onload = function(e) {
|
||||
var img = e.target;
|
||||
// computes the dimension & position (x, y) of the largest square we can fit in the image
|
||||
var width = img.width, height = img.height, dimension, center, x, y;
|
||||
if (width <= height) {
|
||||
dimension = width;
|
||||
center = height / 2;
|
||||
x = 0;
|
||||
y = center - (dimension / 2);
|
||||
} else {
|
||||
dimension = height;
|
||||
center = width / 2;
|
||||
x = center - (dimension / 2);
|
||||
y = 0;
|
||||
}
|
||||
// set the size of the canvas to the maximum available size for avatars (browser will take care of downsizing the image)
|
||||
var canvas = document.createElement("canvas");
|
||||
var size = Discourse.Utilities.getRawSize(Discourse.Utilities.translateSize("huge"));
|
||||
canvas.height = canvas.width = size;
|
||||
// draw the image into the canvas
|
||||
canvas.getContext("2d").drawImage(img, x, y, dimension, dimension, 0, 0, size, size);
|
||||
// retrieve the image from the canvas
|
||||
resolve(canvas.toDataURL(fileType));
|
||||
};
|
||||
// launch the onload event
|
||||
image.src = url;
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
defaultHomepage: function() {
|
||||
// the homepage is the first item of the 'top_menu' site setting
|
||||
return Discourse.SiteSettings.top_menu.split("|")[0].split(",")[0];
|
||||
|
||||
12
app/assets/javascripts/discourse/mixins/scroll-top.js.es6
Normal file
12
app/assets/javascripts/discourse/mixins/scroll-top.js.es6
Normal file
@ -0,0 +1,12 @@
|
||||
function scrollTop() {
|
||||
if (Discourse.URL.isJumpScheduled()) { return; }
|
||||
Ember.run.schedule('afterRender', function() {
|
||||
$(document).scrollTop(0);
|
||||
});
|
||||
}
|
||||
|
||||
export default Ember.Mixin.create({
|
||||
_scrollTop: scrollTop.on('didInsertElement')
|
||||
});
|
||||
|
||||
export { scrollTop };
|
||||
@ -1,8 +0,0 @@
|
||||
Discourse.ScrollTop = Em.Mixin.create({
|
||||
_scrollTop: function() {
|
||||
if (Discourse.URL.isJumpScheduled()) { return; }
|
||||
Em.run.schedule('afterRender', function() {
|
||||
$(document).scrollTop(0);
|
||||
});
|
||||
}.on('didInsertElement')
|
||||
});
|
||||
@ -2,27 +2,33 @@ export default Em.Mixin.create({
|
||||
uploading: false,
|
||||
uploadProgress: 0,
|
||||
|
||||
uploadDone: function() {
|
||||
uploadDone() {
|
||||
Em.warn("You should implement `uploadDone`");
|
||||
},
|
||||
|
||||
deleteDone: function() {
|
||||
Em.warn("You should implement `deleteDone`");
|
||||
},
|
||||
_initialize: function() {
|
||||
const $upload = this.$(),
|
||||
csrf = Discourse.Session.currentProp("csrfToken"),
|
||||
uploadUrl = this.getWithDefault("uploadUrl", "/uploads"),
|
||||
reset = () => this.setProperties({ uploading: false, uploadProgress: 0});
|
||||
|
||||
_initializeUploader: function() {
|
||||
var $upload = this.$(),
|
||||
self = this,
|
||||
csrf = Discourse.Session.currentProp("csrfToken");
|
||||
this.messageBus.subscribe("/uploads/" + this.get("type"), upload => {
|
||||
if (upload && upload.url) {
|
||||
this.uploadDone(upload);
|
||||
} else {
|
||||
Discourse.Utilities.displayErrorForUpload(upload);
|
||||
}
|
||||
reset();
|
||||
});
|
||||
|
||||
$upload.fileupload({
|
||||
url: this.get('uploadUrl') + ".json?authenticity_token=" + encodeURIComponent(csrf),
|
||||
url: uploadUrl + ".json?authenticity_token=" + encodeURIComponent(csrf),
|
||||
dataType: "json",
|
||||
dropZone: $upload,
|
||||
pasteZone: $upload
|
||||
});
|
||||
|
||||
$upload.on("fileuploaddrop", function (e, data) {
|
||||
$upload.on("fileuploaddrop", (e, data) => {
|
||||
if (data.files.length > 10) {
|
||||
bootbox.alert(I18n.t("post.errors.too_many_dragged_and_dropped_files"));
|
||||
return false;
|
||||
@ -31,51 +37,31 @@ export default Em.Mixin.create({
|
||||
}
|
||||
});
|
||||
|
||||
$upload.on('fileuploadsubmit', function (e, data) {
|
||||
var isValid = Discourse.Utilities.validateUploadedFiles(data.files, true);
|
||||
var form = { image_type: self.get('type') };
|
||||
if (self.get("data")) { form = $.extend(form, self.get("data")); }
|
||||
$upload.on("fileuploadsubmit", (e, data) => {
|
||||
const isValid = Discourse.Utilities.validateUploadedFiles(data.files, true);
|
||||
let form = { type: this.get("type") };
|
||||
if (this.get("data")) { form = $.extend(form, this.get("data")); }
|
||||
data.formData = form;
|
||||
self.setProperties({ uploadProgress: 0, uploading: isValid });
|
||||
this.setProperties({ uploadProgress: 0, uploading: isValid });
|
||||
return isValid;
|
||||
});
|
||||
|
||||
$upload.on("fileuploadprogressall", function(e, data) {
|
||||
var progress = parseInt(data.loaded / data.total * 100, 10);
|
||||
self.set("uploadProgress", progress);
|
||||
$upload.on("fileuploadprogressall", (e, data) => {
|
||||
const progress = parseInt(data.loaded / data.total * 100, 10);
|
||||
this.set("uploadProgress", progress);
|
||||
});
|
||||
|
||||
$upload.on("fileuploaddone", function(e, data) {
|
||||
if (data.result) {
|
||||
if (data.result.url) {
|
||||
self.uploadDone(data);
|
||||
} else {
|
||||
if (data.result.message) {
|
||||
bootbox.alert(data.result.message);
|
||||
} else if (data.result.length > 0) {
|
||||
bootbox.alert(data.result.join("\n"));
|
||||
} else {
|
||||
bootbox.alert(I18n.t('post.errors.upload'));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
bootbox.alert(I18n.t('post.errors.upload'));
|
||||
}
|
||||
});
|
||||
|
||||
$upload.on("fileuploadfail", function(e, data) {
|
||||
$upload.on("fileuploadfail", (e, data) => {
|
||||
Discourse.Utilities.displayErrorForUpload(data);
|
||||
reset();
|
||||
});
|
||||
}.on("didInsertElement"),
|
||||
|
||||
$upload.on("fileuploadalways", function() {
|
||||
self.setProperties({ uploading: false, uploadProgress: 0});
|
||||
});
|
||||
}.on('didInsertElement'),
|
||||
|
||||
_destroyUploader: function() {
|
||||
var $upload = this.$();
|
||||
try { $upload.fileupload('destroy'); }
|
||||
_destroy: function() {
|
||||
this.messageBus.unsubscribe("/uploads/" + this.get("type"));
|
||||
const $upload = this.$();
|
||||
try { $upload.fileupload("destroy"); }
|
||||
catch (e) { /* wasn't initialized yet */ }
|
||||
$upload.off();
|
||||
}.on('willDestroyElement')
|
||||
}.on("willDestroyElement")
|
||||
});
|
||||
|
||||
@ -1,4 +1,7 @@
|
||||
Discourse.ActionSummary = Discourse.Model.extend({
|
||||
import RestModel from 'discourse/models/rest';
|
||||
import { popupAjaxError } from 'discourse/lib/ajax-error';
|
||||
|
||||
export default RestModel.extend({
|
||||
|
||||
// Description for the action
|
||||
description: function() {
|
||||
@ -84,10 +87,9 @@ Discourse.ActionSummary = Discourse.Model.extend({
|
||||
if (!self.get('flagTopic')) {
|
||||
return post.updateActionsSummary(result);
|
||||
}
|
||||
}).catch(function (error) {
|
||||
}).catch(function(error) {
|
||||
popupAjaxError(error);
|
||||
self.removeAction();
|
||||
var message = $.parseJSON(error.responseText).errors;
|
||||
bootbox.alert(message);
|
||||
});
|
||||
},
|
||||
|
||||
@ -90,7 +90,7 @@ Discourse.Category = Discourse.Model.extend({
|
||||
}.property("permissions"),
|
||||
|
||||
destroy: function() {
|
||||
return Discourse.ajax("/categories/" + (this.get('slug') || this.get('id')), { type: 'DELETE' });
|
||||
return Discourse.ajax("/categories/" + (this.get('id') || this.get('slug')), { type: 'DELETE' });
|
||||
},
|
||||
|
||||
addPermission: function(permission){
|
||||
|
||||
@ -26,12 +26,7 @@ Discourse.LoginMethod.reopenClass({
|
||||
|
||||
var methods = this.methods = Em.A();
|
||||
|
||||
/*
|
||||
* enable_google_logins etc.
|
||||
* */
|
||||
|
||||
[ "google",
|
||||
"google_oauth2",
|
||||
[ "google_oauth2",
|
||||
"facebook",
|
||||
"cas",
|
||||
"twitter",
|
||||
@ -42,7 +37,7 @@ Discourse.LoginMethod.reopenClass({
|
||||
|
||||
var params = {name: name};
|
||||
|
||||
if (name === "google" || name === "google_oauth2") {
|
||||
if (name === "google_oauth2") {
|
||||
params.frameWidth = 850;
|
||||
params.frameHeight = 500;
|
||||
} else if (name === "facebook") {
|
||||
|
||||
@ -69,7 +69,7 @@ const User = RestModel.extend({
|
||||
profileBackground: function() {
|
||||
var url = this.get('profile_background');
|
||||
if (Em.isEmpty(url) || !Discourse.SiteSettings.allow_profile_backgrounds) { return; }
|
||||
return 'background-image: url(' + Discourse.getURLWithCDN(url) + ')';
|
||||
return ('background-image: url(' + Discourse.getURLWithCDN(url) + ')').htmlSafe();
|
||||
}.property('profile_background'),
|
||||
|
||||
/**
|
||||
@ -171,27 +171,31 @@ const User = RestModel.extend({
|
||||
@returns {Promise} the result of the operation
|
||||
**/
|
||||
save: function() {
|
||||
var self = this,
|
||||
data = this.getProperties('auto_track_topics_after_msecs',
|
||||
'bio_raw',
|
||||
'website',
|
||||
'location',
|
||||
'name',
|
||||
'locale',
|
||||
'email_digests',
|
||||
'email_direct',
|
||||
'email_always',
|
||||
'email_private_messages',
|
||||
'dynamic_favicon',
|
||||
'digest_after_days',
|
||||
'new_topic_duration_minutes',
|
||||
'external_links_in_new_tab',
|
||||
'mailing_list_mode',
|
||||
'enable_quoting',
|
||||
'disable_jump_reply',
|
||||
'custom_fields',
|
||||
'user_fields',
|
||||
'muted_usernames');
|
||||
const self = this,
|
||||
data = this.getProperties(
|
||||
'auto_track_topics_after_msecs',
|
||||
'bio_raw',
|
||||
'website',
|
||||
'location',
|
||||
'name',
|
||||
'locale',
|
||||
'email_digests',
|
||||
'email_direct',
|
||||
'email_always',
|
||||
'email_private_messages',
|
||||
'dynamic_favicon',
|
||||
'digest_after_days',
|
||||
'new_topic_duration_minutes',
|
||||
'external_links_in_new_tab',
|
||||
'mailing_list_mode',
|
||||
'enable_quoting',
|
||||
'disable_jump_reply',
|
||||
'custom_fields',
|
||||
'user_fields',
|
||||
'muted_usernames',
|
||||
'profile_background',
|
||||
'card_background'
|
||||
);
|
||||
|
||||
['muted','watched','tracked'].forEach(function(s){
|
||||
var cats = self.get(s + 'Categories').map(function(c){ return c.get('id')});
|
||||
@ -204,6 +208,8 @@ const User = RestModel.extend({
|
||||
data['edit_history_public'] = this.get('edit_history_public');
|
||||
}
|
||||
|
||||
// TODO: We can remove this when migrated fully to rest model.
|
||||
this.set('isSaving', true);
|
||||
return Discourse.ajax("/users/" + this.get('username_lower'), {
|
||||
data: data,
|
||||
type: 'PUT'
|
||||
@ -212,6 +218,8 @@ const User = RestModel.extend({
|
||||
|
||||
var userProps = self.getProperties('enable_quoting', 'external_links_in_new_tab', 'dynamic_favicon');
|
||||
Discourse.User.current().setProperties(userProps);
|
||||
}).finally(() => {
|
||||
this.set('isSaving', false);
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
@ -2,11 +2,11 @@
|
||||
The parent route for all discovery routes.
|
||||
Handles the logic for showing the loading spinners.
|
||||
**/
|
||||
|
||||
import ShowFooter from "discourse/mixins/show-footer";
|
||||
import OpenComposer from "discourse/mixins/open-composer";
|
||||
import { scrollTop } from 'discourse/mixins/scroll-top';
|
||||
|
||||
const DiscoveryRoute = Discourse.Route.extend(Discourse.ScrollTop, OpenComposer, ShowFooter, {
|
||||
const DiscoveryRoute = Discourse.Route.extend(OpenComposer, ShowFooter, {
|
||||
redirect: function() { return this.redirectIfLoginRequired(); },
|
||||
|
||||
beforeModel: function(transition) {
|
||||
@ -27,7 +27,7 @@ const DiscoveryRoute = Discourse.Route.extend(Discourse.ScrollTop, OpenComposer,
|
||||
loadingComplete: function() {
|
||||
this.controllerFor('discovery').set('loading', false);
|
||||
if (!this.session.get('topicListScrollPosition')) {
|
||||
this._scrollTop();
|
||||
scrollTop();
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
// A base route that allows us to redirect when access is restricted
|
||||
import DiscourseRoute from 'discourse/routes/discourse';
|
||||
|
||||
export default Discourse.Route.extend({
|
||||
// A base route that allows us to redirect when access is restricted
|
||||
export default DiscourseRoute.extend({
|
||||
|
||||
afterModel() {
|
||||
if (!this.modelFor('user').get('can_edit')) {
|
||||
|
||||
@ -155,10 +155,7 @@ const TopicRoute = Discourse.Route.extend(ShowFooter, {
|
||||
let topic = this.modelFor('topic');
|
||||
if (topic && (topic.get('id') === parseInt(params.id, 10))) {
|
||||
this.setupParams(topic, queryParams);
|
||||
// If we have the existing model, refresh it
|
||||
return topic.get('postStream').refresh().then(function() {
|
||||
return topic;
|
||||
});
|
||||
return topic;
|
||||
} else {
|
||||
topic = this.store.createRecord('topic', _.omit(params, 'username_filters', 'filter'));
|
||||
return this.setupParams(topic, queryParams);
|
||||
|
||||
@ -1,7 +1,14 @@
|
||||
export default Discourse.Route.extend({
|
||||
|
||||
beforeModel: function() {
|
||||
this.replaceWith('userActivity');
|
||||
// HACK: Something with the way the user card intercepts clicks seems to break how the
|
||||
// transition into a user's activity works. This makes the back button work on mobile
|
||||
// where there is no user card as well as desktop where there is.
|
||||
if (Discourse.Mobile.mobileView) {
|
||||
this.replaceWith('userActivity');
|
||||
} else {
|
||||
this.transitionTo('userActivity');
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
<div class="uploaded-image-preview" class="input-xxlarge" {{bind-attr style="backgroundStyle"}}>
|
||||
<div class="uploaded-image-preview input-xxlarge" style={{backgroundStyle}}>
|
||||
<div class="image-upload-controls">
|
||||
<label class="btn pad-left no-text" {{bind-attr disabled="uploading"}}>
|
||||
<label class="btn pad-left no-text {{if uploading 'disabled'}}">
|
||||
{{fa-icon "picture-o"}}
|
||||
<input {{bind-attr disabled="uploading"}} type="file" accept="image/*" style="visibility: hidden; position: absolute;" />
|
||||
</label>
|
||||
|
||||
@ -56,6 +56,7 @@
|
||||
|
||||
{{#unless mapCollapsed}}
|
||||
<section class='avatars clearfix'>
|
||||
<h3>{{i18n 'topic_map.participants_title'}}</h3>
|
||||
{{#each p in details.participants}}
|
||||
{{topic-participant participant=p}}
|
||||
{{/each}}
|
||||
@ -63,9 +64,9 @@
|
||||
|
||||
{{#if infoLinks}}
|
||||
<section class='links'>
|
||||
|
||||
<h3>{{i18n 'topic_map.links_title'}}</h3>
|
||||
<table class='topic-links'>
|
||||
{{#each link in infoLinks}}
|
||||
{{#each infoLinks as |link|}}
|
||||
<tr>
|
||||
<td>
|
||||
<span class='badge badge-notification clicks' title='{{i18n 'topic_map.clicks' count=clicks}}'>{{link.clicks}}</span>
|
||||
|
||||
@ -1,18 +1,6 @@
|
||||
{{#if visible}}
|
||||
|
||||
{{!--
|
||||
note this spinner is NEVER turned "off" when the composer is open
|
||||
so I'm going to stop rendering it until we figure out what's up
|
||||
|
||||
<div class='composer-loading'>
|
||||
{{loading-spinner}}
|
||||
</div>
|
||||
--}}
|
||||
|
||||
<div class='contents'>
|
||||
|
||||
{{render "composer-messages"}}
|
||||
|
||||
<div class='control'>
|
||||
<a href class='toggler' {{action "toggle" bubbles=false}} title='{{i18n 'composer.toggler'}}'></a>
|
||||
|
||||
|
||||
@ -19,7 +19,7 @@
|
||||
<section class='user-right groups'>
|
||||
<section class='about group'>
|
||||
<div class='details'>
|
||||
<h1>{{name}}</h1>
|
||||
<h1>{{model.name}}</h1>
|
||||
</div>
|
||||
</section>
|
||||
{{outlet}}
|
||||
|
||||
@ -1,18 +1,18 @@
|
||||
<div class='user-stream'>
|
||||
{{#each p in model itemController="group/post"}}
|
||||
{{#each p in controller}}
|
||||
<div class='item'>
|
||||
<div class='clearfix info'>
|
||||
{{#link-to 'user' p.user class="avatar-link"}}<div class='avatar-wrapper'>{{avatar p.user imageSize="large" extraClasses="actor" ignoreTitle="true"}}</div>{{/link-to}}
|
||||
<a href="{{unbound p.user.userUrl}}" data-user-card="{{unbound p.user.username}}" class='avatar-link'><div class='avatar-wrapper'>{{avatar p.user imageSize="large" extraClasses="actor" ignoreTitle="true"}}</div></a>
|
||||
<span class='time'>{{format-date p.created_at leaveAgo="true"}}</span>
|
||||
<span class="title">
|
||||
<a href="{{unbound p.url}}">{{unbound p.title}}</a>
|
||||
</span>
|
||||
<span class="category">{{category-link p.category}}</span>
|
||||
{{#if p.byName}}
|
||||
<span class="name">
|
||||
{{unbound p.byName}}
|
||||
</span>
|
||||
{{/if}}
|
||||
<div class="user-info">
|
||||
{{#if p.user_long_name}}
|
||||
{{p.user_long_name}}{{#if p.user_title}}, {{p.user_title}}{{/if}}
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
<p class='excerpt'>
|
||||
{{{unbound p.cooked}}}
|
||||
|
||||
@ -16,13 +16,13 @@
|
||||
{{topic-list
|
||||
showPosters=true
|
||||
currentUser=currentUser
|
||||
hideCategory=hideCategory
|
||||
topics=topics}}
|
||||
hideCategory=model.hideCategory
|
||||
topics=model.topics}}
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
<footer class='topic-list-bottom'>
|
||||
{{conditional-loading-spinner condition=loadingMore}}
|
||||
{{conditional-loading-spinner condition=model.loadingMore}}
|
||||
{{#if allLoaded}}
|
||||
{{#if showDismissRead}}
|
||||
<button title="{{i18n 'topics.bulk.dismiss_topics_tooltip'}}" id='dismiss-topics' class='btn dismiss-read' {{action "dismissRead" "topics"}}>{{i18n 'topics.bulk.dismiss_topics'}}</button>
|
||||
|
||||
@ -17,8 +17,9 @@
|
||||
{{#if uploadedAvatarTemplate}}
|
||||
{{bound-avatar-template uploadedAvatarTemplate "large"}}
|
||||
{{else}}
|
||||
{{bound-avatar controller "large" custom_avatar_upload_id}} {{i18n 'user.change_avatar.uploaded_avatar'}}
|
||||
{{bound-avatar controller "large" custom_avatar_upload_id}}
|
||||
{{/if}}
|
||||
{{i18n 'user.change_avatar.uploaded_avatar'}}
|
||||
{{else}}
|
||||
{{i18n 'user.change_avatar.uploaded_avatar_empty'}}
|
||||
{{/if}}
|
||||
|
||||
@ -2,11 +2,11 @@
|
||||
<section class='field'>
|
||||
<section class="field-item">
|
||||
<label>{{i18n 'category.name'}}</label>
|
||||
{{text-field value=name placeholderKey="category.name_placeholder" maxlength="50"}}
|
||||
{{text-field value=model.name placeholderKey="category.name_placeholder" maxlength="50"}}
|
||||
</section>
|
||||
<section class="field-item">
|
||||
<label>{{i18n 'category.slug'}}</label>
|
||||
{{text-field value=slug placeholderKey="category.slug_placeholder" maxlength="255"}}
|
||||
{{text-field value=model.slug placeholderKey="category.slug_placeholder" maxlength="255"}}
|
||||
</section>
|
||||
</section>
|
||||
|
||||
@ -19,7 +19,7 @@
|
||||
{{/each}}
|
||||
{{else}}
|
||||
<label>{{i18n 'category.parent'}}</label>
|
||||
{{category-chooser valueAttribute="id" value=parent_category_id categories=parentCategories rootNone=true}}
|
||||
{{category-chooser valueAttribute="id" value=model.parent_category_id categories=parentCategories rootNone=true}}
|
||||
{{/if}}
|
||||
</section>
|
||||
{{/if}}
|
||||
@ -27,12 +27,12 @@
|
||||
{{#if showDescription}}
|
||||
<section class='field'>
|
||||
<label>{{i18n 'category.description'}}</label>
|
||||
{{#if description}}
|
||||
{{{description}}}
|
||||
{{#if model.description}}
|
||||
{{{model.description}}}
|
||||
{{else}}
|
||||
{{i18n 'category.no_description'}}
|
||||
{{/if}}
|
||||
{{#if topic_url}}
|
||||
{{#if model.topic_url}}
|
||||
<br/>
|
||||
{{d-button class="btn-small" action="showCategoryTopic" icon="pencil" label="category.change_in_category_topic"}}
|
||||
{{/if}}
|
||||
@ -46,14 +46,14 @@
|
||||
|
||||
<div class='input-prepend input-append' style="margin-top: 10px;">
|
||||
<span class='color-title'>{{i18n 'category.background_color'}}:</span>
|
||||
<span class='add-on'>#</span>{{text-field value=color placeholderKey="category.color_placeholder" maxlength="6"}}
|
||||
{{color-picker colors=backgroundColors usedColors=usedBackgroundColors value=color}}
|
||||
<span class='add-on'>#</span>{{text-field value=model.color placeholderKey="category.color_placeholder" maxlength="6"}}
|
||||
{{color-picker colors=backgroundColors usedColors=usedBackgroundColors value=model.color}}
|
||||
</div>
|
||||
|
||||
<div class='input-prepend input-append'>
|
||||
<span class='color-title'>{{i18n 'category.foreground_color'}}:</span>
|
||||
<span class='add-on'>#</span>{{text-field value=text_color placeholderKey="category.color_placeholder" maxlength="6"}}
|
||||
{{color-picker colors=foregroundColors value=text_color}}
|
||||
<span class='add-on'>#</span>{{text-field value=model.text_color placeholderKey="category.color_placeholder" maxlength="6"}}
|
||||
{{color-picker colors=foregroundColors value=model.text_color}}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
<section class='field'>
|
||||
<label>{{i18n 'category.logo'}}</label>
|
||||
{{image-uploader uploadUrl=categoryUploadUrl imageUrl=logo_url type="logo"}}
|
||||
{{image-uploader imageUrl=model.logo_url type="category_logo"}}
|
||||
</section>
|
||||
|
||||
<section class='field'>
|
||||
<label>{{i18n 'category.background_image'}}</label>
|
||||
{{image-uploader uploadUrl=categoryUploadUrl imageUrl=background_url type="background"}}
|
||||
{{image-uploader imageUrl=model.background_url type="category_background"}}
|
||||
</section>
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
<section class='field'>
|
||||
<ul class='permission-list'>
|
||||
{{#each p in permissions}}
|
||||
{{#each model.permissions as |p|}}
|
||||
<li>
|
||||
<span class="name"><span class="badge-group">{{p.group_name}}</span></span>
|
||||
{{{i18n "category.can"}}}
|
||||
|
||||
@ -1,13 +1,13 @@
|
||||
<section class='field'>
|
||||
{{auto-close-form autoCloseTime=auto_close_hours
|
||||
autoCloseBasedOnLastPost=auto_close_based_on_last_post
|
||||
{{auto-close-form autoCloseTime=model.auto_close_hours
|
||||
autoCloseBasedOnLastPost=model.auto_close_based_on_last_post
|
||||
limited="true" }}
|
||||
</section>
|
||||
|
||||
<section class='field'>
|
||||
<div class="allow-badges">
|
||||
<div>
|
||||
{{input type="checkbox" checked=allow_badges}}
|
||||
{{input type="checkbox" checked=model.allow_badges}}
|
||||
{{i18n 'category.allow_badges_label'}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
<div {{bind-attr class="loading:invisible"}}>
|
||||
<div>
|
||||
<ul class="nav nav-pills">
|
||||
{{edit-category-tab selectedTab=selectedTab tab="general"}}
|
||||
{{#unless isUncategorizedCategory}}
|
||||
{{#unless model.isUncategorizedCategory}}
|
||||
{{edit-category-tab selectedTab=selectedTab tab="security"}}
|
||||
{{/unless}}
|
||||
{{edit-category-tab selectedTab=selectedTab tab="settings"}}
|
||||
@ -16,11 +16,11 @@
|
||||
|
||||
<div class="modal-footer">
|
||||
<button class='btn btn-primary' {{bind-attr disabled="disabled"}} {{action "saveCategory"}}>{{buttonTitle}}</button>
|
||||
{{#if can_delete}}
|
||||
{{#if model.can_delete}}
|
||||
<button class='btn btn-danger pull-right' {{bind-attr disabled="deleteDisabled"}} {{action "deleteCategory"}}><i class="fa fa-trash-o"></i>{{deleteButtonTitle}}</button>
|
||||
{{else}}
|
||||
<div class="cannot_delete_reason">
|
||||
{{{cannot_delete_reason}}}
|
||||
{{{model.cannot_delete_reason}}}
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
@ -9,6 +9,7 @@
|
||||
<li>{{{i18n 'keyboard_shortcuts_help.jump_to.unread'}}}</li>
|
||||
<li>{{{i18n 'keyboard_shortcuts_help.jump_to.categories'}}}</li>
|
||||
<li>{{{i18n 'keyboard_shortcuts_help.jump_to.top'}}}</li>
|
||||
<li>{{{i18n 'keyboard_shortcuts_help.jump_to.bookmarks'}}}</li>
|
||||
</ul>
|
||||
<h4>{{i18n 'keyboard_shortcuts_help.navigation.title'}}</h4>
|
||||
<ul>
|
||||
|
||||
@ -8,7 +8,7 @@
|
||||
<p>{{{i18n 'topic.merge_topic.instructions' count=selectedPostsCount}}}</p>
|
||||
|
||||
<form>
|
||||
{{choose-topic selectedTopicId=selectedTopicId}}
|
||||
{{view "choose-topic" selectedTopicId=selectedTopicId}}
|
||||
</form>
|
||||
</div>
|
||||
|
||||
|
||||
@ -19,7 +19,7 @@
|
||||
{{/if}}
|
||||
|
||||
{{#if canEditCategory}}
|
||||
{{d-button class="btn-default" action="editCategory" actionParam=category icon="wrench" label="category.edit_long"}}
|
||||
{{d-button class="btn-default edit-category" action="editCategory" actionParam=category icon="wrench" label="category.edit_long"}}
|
||||
{{/if}}
|
||||
|
||||
<section class='category-heading'>
|
||||
|
||||
@ -72,6 +72,9 @@
|
||||
<div {{bind-attr class="showUserReplyTab:avoid-tab view.repliesShown::contents :regular view.extraClass"}}>
|
||||
<div class='cooked'>
|
||||
{{{cooked}}}
|
||||
{{#if firstPost}}
|
||||
{{plugin-outlet "topic-after-cooked"}}
|
||||
{{/if}}
|
||||
</div>
|
||||
{{#if cooked_hidden}}
|
||||
<a href {{action "expandHidden" this}}>{{i18n 'post.show_hidden'}}</a>
|
||||
|
||||
@ -61,11 +61,13 @@
|
||||
label="queue.reject"
|
||||
icon="times"
|
||||
class="btn-danger reject"}}
|
||||
{{d-button action="deleteUser"
|
||||
disabled=ctrl.post.isSaving
|
||||
label="queue.delete_user"
|
||||
icon="trash"
|
||||
class="btn-danger delete-user"}}
|
||||
{{#if ctrl.post.can_delete_user}}
|
||||
{{d-button action="deleteUser"
|
||||
disabled=ctrl.post.isSaving
|
||||
label="queue.delete_user"
|
||||
icon="trash"
|
||||
class="btn-danger delete-user"}}
|
||||
{{/if}}
|
||||
{{d-button action="edit"
|
||||
disabled=ctrl.post.isSaving
|
||||
label="queue.edit"
|
||||
|
||||
@ -128,8 +128,8 @@
|
||||
{{{model.notFoundHtml}}}
|
||||
{{else}}
|
||||
<div class="topic-error">
|
||||
<div>{{message}}</div>
|
||||
{{#if noRetry}}
|
||||
<div>{{model.message}}</div>
|
||||
{{#if model.noRetry}}
|
||||
{{#unless currentUser}}
|
||||
{{d-button action="showLogin" class="btn-primary topic-retry" icon="user" label="log_in"}}
|
||||
{{/unless}}
|
||||
|
||||
@ -47,7 +47,7 @@
|
||||
<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 user.bio_cooked}}<div class='bio'>{{text-overflow class="overflow" text=user.bio_excerpt}}</div>{{/if}}
|
||||
{{/if}}
|
||||
|
||||
{{#if user.card_badge}}
|
||||
@ -58,7 +58,9 @@
|
||||
|
||||
{{#if user}}
|
||||
<div class="metadata">
|
||||
<h3><span class='desc'>{{i18n 'last_post'}}</span> {{format-date user.last_posted_at leaveAgo="true"}}</h3>
|
||||
{{#if user.last_posted_at}}
|
||||
<h3><span class='desc'>{{i18n 'last_post'}}</span> {{format-date user.last_posted_at leaveAgo="true"}}</h3>
|
||||
{{/if}}
|
||||
<h3><span class='desc'>{{i18n 'joined'}}</span> {{format-date user.created_at leaveAgo="true"}}</h3>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
@ -1,23 +1,23 @@
|
||||
<section class='user-content'>
|
||||
<section class='user-content user-preferences'>
|
||||
|
||||
<form class="form-horizontal">
|
||||
|
||||
<div class="control-group save-button" id='save-button-top'>
|
||||
<div class="controls">
|
||||
{{partial 'user/preferences/saveButton'}}
|
||||
{{partial 'user/preferences/save-button'}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="control-group pref-username">
|
||||
<label class="control-label">{{i18n 'user.username.title'}}</label>
|
||||
<div class="controls">
|
||||
<span class='static'>{{username}}</span>
|
||||
{{#if can_edit_username}}
|
||||
<span class='static'>{{model.username}}</span>
|
||||
{{#if model.can_edit_username}}
|
||||
{{#link-to "preferences.username" class="btn btn-small pad-left no-text"}}<i class="fa fa-pencil"></i>{{/link-to}}
|
||||
{{/if}}
|
||||
</div>
|
||||
<div class='instructions'>
|
||||
{{{i18n 'user.username.short_instructions' username=username}}}
|
||||
{{{i18n 'user.username.short_instructions' username=model.username}}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -25,7 +25,7 @@
|
||||
<div class="control-group pref-name">
|
||||
<label class="control-label">{{i18n 'user.name.title'}}</label>
|
||||
<div class="controls">
|
||||
{{#if can_edit_name}}
|
||||
{{#if model.can_edit_name}}
|
||||
{{text-field value=newNameInput classNames="input-xxlarge"}}
|
||||
{{else}}
|
||||
<span class='static'>{{name}}</span>
|
||||
@ -41,7 +41,7 @@
|
||||
<div class="control-group pref-title">
|
||||
<label class="control-label">{{i18n 'user.title.title'}}</label>
|
||||
<div class="controls">
|
||||
<span class="static">{{title}}</span>
|
||||
<span class="static">{{model.title}}</span>
|
||||
{{#link-to "preferences.badgeTitle" class="btn btn-small pad-left no-text"}}{{fa-icon "pencil"}}{{/link-to}}
|
||||
</div>
|
||||
</div>
|
||||
@ -53,7 +53,7 @@
|
||||
{{#if model.email}}
|
||||
<div class="controls">
|
||||
<span class='static'>{{model.email}}</span>
|
||||
{{#if can_edit_email}}
|
||||
{{#if model.can_edit_email}}
|
||||
{{#link-to "preferences.email" class="btn btn-small pad-left no-text"}}{{fa-icon "pencil"}}{{/link-to}}
|
||||
{{/if}}
|
||||
</div>
|
||||
@ -74,7 +74,7 @@
|
||||
<div class="controls">
|
||||
<a href="#" {{action "changePassword"}} class='btn'>
|
||||
{{fa-icon "envelope"}}
|
||||
{{#if no_password}}
|
||||
{{#if model.no_password}}
|
||||
{{i18n 'user.change_password.set_password'}}
|
||||
{{else}}
|
||||
{{i18n 'user.change_password.action'}}
|
||||
@ -104,10 +104,7 @@
|
||||
<div class="control-group pref-profile-bg">
|
||||
<label class="control-label">{{i18n 'user.change_profile_background.title'}}</label>
|
||||
<div class="controls">
|
||||
{{image-uploader uploadUrl=imageUploadUrl
|
||||
imageUrl=profile_background
|
||||
instantDelete="true"
|
||||
type="profile_background"}}
|
||||
{{image-uploader imageUrl=model.profile_background type="profile_background"}}
|
||||
</div>
|
||||
<div class='instructions'>
|
||||
{{i18n 'user.change_profile_background.instructions'}}
|
||||
@ -117,10 +114,7 @@
|
||||
<div class="control-group pref-profile-bg">
|
||||
<label class="control-label">{{i18n 'user.change_card_background.title'}}</label>
|
||||
<div class="controls">
|
||||
{{image-uploader uploadUrl=imageUploadUrl
|
||||
imageUrl=card_background
|
||||
instantDelete="true"
|
||||
type="card_background"}}
|
||||
{{image-uploader imageUrl=model.card_background type="card_background"}}
|
||||
</div>
|
||||
<div class='instructions'>
|
||||
{{i18n 'user.change_card_background.instructions'}}
|
||||
@ -132,7 +126,7 @@
|
||||
<div class="control-group pref-locale">
|
||||
<label class="control-label">{{i18n 'user.locale.title'}}</label>
|
||||
<div class="controls">
|
||||
{{combo-box valueAttribute="value" content=availableLocales value=locale none="user.locale.default"}}
|
||||
{{combo-box valueAttribute="value" content=availableLocales value=model.locale none="user.locale.default"}}
|
||||
</div>
|
||||
<div class='instructions'>
|
||||
{{i18n 'user.locale.instructions'}}
|
||||
@ -143,7 +137,7 @@
|
||||
<div class="control-group pref-bio">
|
||||
<label class="control-label">{{i18n 'user.bio'}}</label>
|
||||
<div class="controls bio-composer">
|
||||
{{pagedown-editor value=bio_raw}}
|
||||
{{pagedown-editor value=model.bio_raw}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -154,22 +148,22 @@
|
||||
<div class="control-group pref-location">
|
||||
<label class="control-label">{{i18n 'user.location'}}</label>
|
||||
<div class="controls">
|
||||
{{input type="text" value=location class="input-xxlarge"}}
|
||||
{{input type="text" value=model.location class="input-xxlarge" id='edit-location'}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="control-group pref-website">
|
||||
<label class="control-label">{{i18n 'user.website'}}</label>
|
||||
<div class="controls">
|
||||
{{input type="text" value=website class="input-xxlarge"}}
|
||||
{{input type="text" value=model.website class="input-xxlarge"}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="control-group pref-card-badge">
|
||||
<label class="control-label">{{i18n 'user.card_badge.title'}}</label>
|
||||
<div class="controls">
|
||||
{{#if card_image_badge}}
|
||||
{{icon-or-image card_image_badge}}
|
||||
{{#if model.card_image_badge}}
|
||||
{{icon-or-image model.card_image_badge}}
|
||||
{{/if}}
|
||||
{{#link-to "preferences.card-badge" class="btn btn-small pad-left no-text"}}{{fa-icon "pencil"}}{{/link-to}}
|
||||
</div>
|
||||
@ -178,17 +172,17 @@
|
||||
<div class="control-group pref-email-settings">
|
||||
<label class="control-label">{{i18n 'user.email_settings'}}</label>
|
||||
{{#if canReceiveDigest}}
|
||||
{{preference-checkbox labelKey="user.email_digests.title" checked=email_digests}}
|
||||
{{#if email_digests}}
|
||||
{{preference-checkbox labelKey="user.email_digests.title" checked=model.email_digests}}
|
||||
{{#if model.email_digests}}
|
||||
<div class='controls controls-dropdown'>
|
||||
{{combo-box valueAttribute="value" content=digestFrequencies value=digest_after_days}}
|
||||
{{combo-box valueAttribute="value" content=digestFrequencies value=model.digest_after_days}}
|
||||
</div>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
{{preference-checkbox labelKey="user.email_private_messages" checked=email_private_messages}}
|
||||
{{preference-checkbox labelKey="user.email_direct" checked=email_direct}}
|
||||
{{preference-checkbox labelKey="user.mailing_list_mode" checked=mailing_list_mode}}
|
||||
{{preference-checkbox labelKey="user.email_always" checked=email_always}}
|
||||
{{preference-checkbox labelKey="user.email_private_messages" checked=model.email_private_messages}}
|
||||
{{preference-checkbox labelKey="user.email_direct" checked=model.email_direct}}
|
||||
{{preference-checkbox labelKey="user.mailing_list_mode" checked=model.mailing_list_mode}}
|
||||
{{preference-checkbox labelKey="user.email_always" checked=model.email_always}}
|
||||
|
||||
<div class='instructions'>
|
||||
{{i18n 'user.email.frequency' count=siteSettings.email_time_window_mins}}
|
||||
@ -200,20 +194,20 @@
|
||||
|
||||
<div class="controls controls-dropdown">
|
||||
<label>{{i18n 'user.new_topic_duration.label'}}</label>
|
||||
{{combo-box valueAttribute="value" content=considerNewTopicOptions value=new_topic_duration_minutes}}
|
||||
{{combo-box valueAttribute="value" content=considerNewTopicOptions value=model.new_topic_duration_minutes}}
|
||||
</div>
|
||||
|
||||
<div class="controls controls-dropdown">
|
||||
<label>{{i18n 'user.auto_track_topics'}}</label>
|
||||
{{combo-box valueAttribute="value" content=autoTrackDurations value=auto_track_topics_after_msecs}}
|
||||
{{combo-box valueAttribute="value" content=autoTrackDurations value=model.auto_track_topics_after_msecs}}
|
||||
</div>
|
||||
|
||||
{{preference-checkbox labelKey="user.external_links_in_new_tab" checked=external_links_in_new_tab}}
|
||||
{{preference-checkbox labelKey="user.enable_quoting" checked=enable_quoting}}
|
||||
{{preference-checkbox labelKey="user.dynamic_favicon" checked=dynamic_favicon}}
|
||||
{{preference-checkbox labelKey="user.disable_jump_reply" checked=disable_jump_reply}}
|
||||
{{preference-checkbox labelKey="user.external_links_in_new_tab" checked=model.external_links_in_new_tab}}
|
||||
{{preference-checkbox labelKey="user.enable_quoting" checked=model.enable_quoting}}
|
||||
{{preference-checkbox labelKey="user.dynamic_favicon" checked=model.dynamic_favicon}}
|
||||
{{preference-checkbox labelKey="user.disable_jump_reply" checked=model.disable_jump_reply}}
|
||||
{{#unless editHistoryVisible}}
|
||||
{{preference-checkbox labelKey="user.edit_history_public" checked=edit_history_public}}
|
||||
{{preference-checkbox labelKey="user.edit_history_public" checked=model.edit_history_public}}
|
||||
{{/unless}}
|
||||
|
||||
{{plugin-outlet "user_custom_preferences"}}
|
||||
@ -223,17 +217,17 @@
|
||||
<label class="control-label">{{i18n 'user.categories_settings'}}</label>
|
||||
<div class="controls category-controls">
|
||||
<label>{{i18n 'user.watched_categories'}}</label>
|
||||
{{category-group categories=watchedCategories blacklist=selectedCategories}}
|
||||
{{category-group categories=model.watchedCategories blacklist=selectedCategories}}
|
||||
</div>
|
||||
<div class="instructions">{{i18n 'user.watched_categories_instructions'}}</div>
|
||||
<div class="controls category-controls">
|
||||
<label>{{i18n 'user.tracked_categories'}}</label>
|
||||
{{category-group categories=trackedCategories blacklist=selectedCategories}}
|
||||
{{category-group categories=model.trackedCategories blacklist=selectedCategories}}
|
||||
</div>
|
||||
<div class="instructions">{{i18n 'user.tracked_categories_instructions'}}</div>
|
||||
<div class="controls category-controls">
|
||||
<label>{{i18n 'user.muted_categories'}}</label>
|
||||
{{category-group categories=mutedCategories blacklist=selectedCategories}}
|
||||
{{category-group categories=model.mutedCategories blacklist=selectedCategories}}
|
||||
</div>
|
||||
<div class="instructions">{{i18n 'user.muted_categories_instructions'}}</div>
|
||||
</div>
|
||||
@ -242,24 +236,23 @@
|
||||
<label class="control-label">{{i18n 'user.users'}}</label>
|
||||
<div class="controls category-controls">
|
||||
<label>{{i18n 'user.muted_users'}}</label>
|
||||
{{user-selector excludeCurrentUser=true usernames=muted_usernames class="user-selector"}}
|
||||
{{user-selector excludeCurrentUser=true usernames=model.muted_usernames class="user-selector"}}
|
||||
</div>
|
||||
<div class="instructions">{{i18n 'user.muted_users_instructions'}}</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<div class="controls">
|
||||
{{partial 'user/preferences/saveButton'}}
|
||||
{{partial 'user/preferences/save-button'}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{#if canDeleteAccount}}
|
||||
{{#if model.canDeleteAccount}}
|
||||
<div class="control-group delete-account">
|
||||
<hr/>
|
||||
<div class="controls">
|
||||
{{d-button action="delete" disabled="deleteDisabled" class="btn-danger" icon="trash-o" label="user.delete_account"}}
|
||||
{{d-button action="delete" disabled=deleteDisabled class="btn-danger" icon="trash-o" label="user.delete_account"}}
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
</form>
|
||||
</section>
|
||||
|
||||
@ -0,0 +1,7 @@
|
||||
{{#d-button action="save" disabled=model.isSaving class="btn btn-primary save-user"}}
|
||||
{{saveButtonText}}
|
||||
{{/d-button}}
|
||||
|
||||
{{#if saved}}
|
||||
<span class='saved-user'>{{i18n 'saved'}}</span>
|
||||
{{/if}}
|
||||
@ -1,2 +0,0 @@
|
||||
<button {{action "save"}} {{bind-attr disabled="saving"}} class="btn btn-primary">{{saveButtonText}}</button>
|
||||
{{#if saved}}{{i18n 'saved'}}{{/if}}
|
||||
@ -67,7 +67,7 @@
|
||||
<h3>{{model.title}}</h3>
|
||||
{{/if}}
|
||||
<h3>
|
||||
{{#if model.location}}{{fa-icon "map-marker"}}{{model.location}}{{/if}}
|
||||
{{#if model.location}}{{fa-icon "map-marker"}} {{model.location}}{{/if}}
|
||||
{{#if websiteName}}
|
||||
{{fa-icon "globe"}}
|
||||
{{#if linkWebsite}}
|
||||
|
||||
@ -1,2 +1,3 @@
|
||||
export default Ember.View.extend(Discourse.ScrollTop);
|
||||
import ScrollTop from 'discourse/mixins/scroll-top';
|
||||
|
||||
export default Ember.View.extend(ScrollTop);
|
||||
|
||||
@ -306,78 +306,73 @@ const ComposerView = Discourse.View.extend(Ember.Evented, {
|
||||
// in case it's still bound somehow
|
||||
this._unbindUploadTarget();
|
||||
|
||||
const $uploadTarget = $('#reply-control'),
|
||||
csrf = Discourse.Session.currentProp('csrfToken');
|
||||
let cancelledByTheUser;
|
||||
const $uploadTarget = $("#reply-control"),
|
||||
csrf = Discourse.Session.currentProp("csrfToken"),
|
||||
reset = () => this.setProperties({ uploadProgress: 0, isUploading: false });
|
||||
|
||||
var cancelledByTheUser;
|
||||
|
||||
this.messageBus.subscribe("/uploads/composer", upload => {
|
||||
if (!cancelledByTheUser) {
|
||||
if (upload && upload.url) {
|
||||
const markdown = Discourse.Utilities.getUploadMarkdown(upload);
|
||||
this.addMarkdown(markdown + " ");
|
||||
} else {
|
||||
Discourse.Utilities.displayErrorForUpload(upload);
|
||||
}
|
||||
}
|
||||
// reset upload state
|
||||
reset();
|
||||
});
|
||||
|
||||
// NOTE: we need both the .json extension and the CSRF token as a query parameter for IE9
|
||||
$uploadTarget.fileupload({
|
||||
url: Discourse.getURL('/uploads.json?authenticity_token=' + encodeURIComponent(csrf)),
|
||||
dataType: 'json',
|
||||
pasteZone: $uploadTarget
|
||||
url: Discourse.getURL("/uploads.json?authenticity_token=" + encodeURIComponent(csrf)),
|
||||
dataType: "json",
|
||||
pasteZone: $uploadTarget,
|
||||
});
|
||||
|
||||
// submit - this event is triggered for each upload
|
||||
$uploadTarget.on('fileuploadsubmit', function (e, data) {
|
||||
const result = Discourse.Utilities.validateUploadedFiles(data.files);
|
||||
// reset upload status when everything is ok
|
||||
if (result) self.setProperties({ uploadProgress: 0, isUploading: true });
|
||||
return result;
|
||||
$uploadTarget.on("fileuploadsubmit", (e, data) => {
|
||||
const isValid = Discourse.Utilities.validateUploadedFiles(data.files);
|
||||
data.formData = { type: "composer" };
|
||||
this.setProperties({ uploadProgress: 0, isUploading: isValid });
|
||||
return isValid;
|
||||
});
|
||||
|
||||
// send - this event is triggered when the upload request is about to start
|
||||
$uploadTarget.on('fileuploadsend', function (e, data) {
|
||||
cancelledByTheUser = false;
|
||||
$uploadTarget.on("fileuploadsend", (e, data) => {
|
||||
// hide the "file selector" modal
|
||||
self.get('controller').send('closeModal');
|
||||
// NOTE: IE9 doesn't support XHR
|
||||
this.get("controller").send("closeModal");
|
||||
// deal with cancellation
|
||||
cancelledByTheUser = false;
|
||||
if (data["xhr"]) {
|
||||
const jqHXR = data.xhr();
|
||||
if (jqHXR) {
|
||||
// need to wait for the link to show up in the DOM
|
||||
Em.run.schedule('afterRender', function() {
|
||||
// bind on the click event on the cancel link
|
||||
$('#cancel-file-upload').on('click', function() {
|
||||
// cancel the upload
|
||||
self.set('isUploading', false);
|
||||
// NOTE: this might trigger a 'fileuploadfail' event with status = 0
|
||||
if (jqHXR) { cancelledByTheUser = true; jqHXR.abort(); }
|
||||
Em.run.schedule("afterRender", () => {
|
||||
const $cancel = $("#cancel-file-upload");
|
||||
$cancel.on("click", () => {
|
||||
if (jqHXR) {
|
||||
cancelledByTheUser = true;
|
||||
// might trigger a "fileuploadfail" event with status = 0
|
||||
jqHXR.abort();
|
||||
// make sure we always reset the uploading status
|
||||
reset();
|
||||
}
|
||||
// unbind
|
||||
$(this).off('click');
|
||||
$cancel.off("click");
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// progress all
|
||||
$uploadTarget.on('fileuploadprogressall', function (e, data) {
|
||||
$uploadTarget.on("fileuploadprogressall", (e, data) => {
|
||||
const progress = parseInt(data.loaded / data.total * 100, 10);
|
||||
self.set('uploadProgress', progress);
|
||||
this.set("uploadProgress", progress);
|
||||
});
|
||||
|
||||
// done
|
||||
$uploadTarget.on('fileuploaddone', function (e, data) {
|
||||
$uploadTarget.on("fileuploadfail", (e, data) => {
|
||||
reset();
|
||||
if (!cancelledByTheUser) {
|
||||
// make sure we have a url
|
||||
if (data.result.url) {
|
||||
const markdown = Discourse.Utilities.getUploadMarkdown(data.result);
|
||||
// appends a space at the end of the inserted markdown
|
||||
self.addMarkdown(markdown + " ");
|
||||
self.set('isUploading', false);
|
||||
} else {
|
||||
// display the error message sent by the server
|
||||
bootbox.alert(data.result.join("\n"));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// fail
|
||||
$uploadTarget.on('fileuploadfail', function (e, data) {
|
||||
// hide upload status
|
||||
self.set('isUploading', false);
|
||||
if (!cancelledByTheUser) {
|
||||
// display an error message
|
||||
Discourse.Utilities.displayErrorForUpload(data);
|
||||
}
|
||||
});
|
||||
@ -538,6 +533,14 @@ const ComposerView = Discourse.View.extend(Ember.Evented, {
|
||||
});
|
||||
},
|
||||
|
||||
_unbindUploadTarget() {
|
||||
this.messageBus.unsubscribe("/uploads/composer");
|
||||
const $uploadTarget = $("#reply-controler");
|
||||
try { $uploadTarget.fileupload("destroy"); }
|
||||
catch (e) { /* wasn't initialized yet */ }
|
||||
$uploadTarget.off();
|
||||
},
|
||||
|
||||
titleValidation: function() {
|
||||
const titleLength = this.get('model.titleLength'),
|
||||
missingChars = this.get('model.missingTitleCharacters');
|
||||
@ -580,13 +583,6 @@ const ComposerView = Discourse.View.extend(Ember.Evented, {
|
||||
return Discourse.InputValidation.create({ failed: true, reason });
|
||||
}
|
||||
}.property('model.reply', 'model.replyLength', 'model.missingReplyCharacters', 'model.minimumPostLength'),
|
||||
|
||||
_unbindUploadTarget() {
|
||||
const $uploadTarget = $('#reply-control');
|
||||
try { $uploadTarget.fileupload('destroy'); }
|
||||
catch (e) { /* wasn't initialized yet */ }
|
||||
$uploadTarget.off();
|
||||
}
|
||||
});
|
||||
|
||||
RSVP.EventTarget.mixin(ComposerView);
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import UrlRefresh from 'discourse/mixins/url-refresh';
|
||||
|
||||
export default Discourse.View.extend(UrlRefresh, Discourse.ScrollTop, {
|
||||
export default Discourse.View.extend(UrlRefresh, {
|
||||
_addBodyClass: function() {
|
||||
$('body').addClass('categories-list');
|
||||
}.on('didInsertElement'),
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import UrlRefresh from 'discourse/mixins/url-refresh';
|
||||
import ScrollTop from 'discourse/mixins/scroll-top';
|
||||
|
||||
export default Discourse.View.extend(Discourse.ScrollTop, UrlRefresh);
|
||||
export default Discourse.View.extend(ScrollTop, UrlRefresh);
|
||||
|
||||
@ -6,7 +6,7 @@ export default Discourse.View.extend(LoadMore, UrlRefresh, {
|
||||
|
||||
actions: {
|
||||
loadMore() {
|
||||
var self = this;
|
||||
const self = this;
|
||||
Discourse.notifyTitle(0);
|
||||
this.get('controller').loadMoreTopics().then(function (hasMoreResults) {
|
||||
Em.run.schedule('afterRender', function() {
|
||||
@ -20,7 +20,7 @@ export default Discourse.View.extend(LoadMore, UrlRefresh, {
|
||||
},
|
||||
|
||||
_readjustScrollPosition: function() {
|
||||
var scrollTo = this.session.get('topicListScrollPosition');
|
||||
const scrollTo = this.session.get('topicListScrollPosition');
|
||||
|
||||
if (typeof scrollTo !== "undefined") {
|
||||
Em.run.schedule('afterRender', function() {
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import ScrollTop from 'discourse/mixins/scroll-top';
|
||||
import LoadMore from "discourse/mixins/load-more";
|
||||
|
||||
export default Discourse.View.extend(Discourse.ScrollTop, LoadMore, {
|
||||
export default Discourse.View.extend(ScrollTop, LoadMore, {
|
||||
eyelineSelector: '.user-stream .item',
|
||||
});
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import ScrollTop from 'discourse/mixins/scroll-top';
|
||||
import LoadMore from "discourse/mixins/load-more";
|
||||
|
||||
export default Discourse.View.extend(Discourse.ScrollTop, LoadMore, {
|
||||
export default Discourse.View.extend(ScrollTop, LoadMore, {
|
||||
eyelineSelector: '.group-members tr',
|
||||
});
|
||||
|
||||
@ -44,7 +44,7 @@ Button.prototype.render = function(buffer) {
|
||||
|
||||
var hiddenButtons;
|
||||
|
||||
export default Discourse.View.extend(StringBuffer, {
|
||||
var PostMenuView = Discourse.View.extend(StringBuffer, {
|
||||
tagName: 'section',
|
||||
classNames: ['post-menu-area', 'clearfix'],
|
||||
|
||||
@ -141,6 +141,13 @@ export default Discourse.View.extend(StringBuffer, {
|
||||
visibleButtons.splice(visibleButtons.length - 1, 0, this.buttonForShowMoreActions(post));
|
||||
}
|
||||
|
||||
var callbacks = PostMenuView._registerButtonCallbacks;
|
||||
if (callbacks) {
|
||||
_.each(callbacks, function(callback) {
|
||||
callback.apply(self, [visibleButtons]);
|
||||
});
|
||||
}
|
||||
|
||||
buffer.push('<div class="actions">');
|
||||
visibleButtons.forEach(function (b) {
|
||||
b.render(buffer);
|
||||
@ -374,3 +381,12 @@ export default Discourse.View.extend(StringBuffer, {
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
PostMenuView.reopenClass({
|
||||
registerButton: function(callback){
|
||||
this._registerButtonCallbacks = this._registerButtonCallbacks || [];
|
||||
this._registerButtonCallbacks.push(callback);
|
||||
}
|
||||
});
|
||||
|
||||
export default PostMenuView;
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
var DAY = 60 * 50 * 1000;
|
||||
const DAY = 60 * 50 * 1000;
|
||||
|
||||
var PostView = Discourse.GroupedView.extend(Ember.Evented, {
|
||||
const PostView = Discourse.GroupedView.extend(Ember.Evented, {
|
||||
classNames: ['topic-post', 'clearfix'],
|
||||
templateName: 'post',
|
||||
classNameBindings: ['postTypeClass',
|
||||
classNameBindings: ['needsModeratorClass:moderator:regular',
|
||||
'selected',
|
||||
'post.hidden:post-hidden',
|
||||
'post.deleted:deleted',
|
||||
@ -14,11 +14,11 @@ var PostView = Discourse.GroupedView.extend(Ember.Evented, {
|
||||
post: Ember.computed.alias('content'),
|
||||
|
||||
historyHeat: function() {
|
||||
var updatedAt = this.get('post.updated_at');
|
||||
const updatedAt = this.get('post.updated_at');
|
||||
if (!updatedAt) { return; }
|
||||
|
||||
// Show heat on age
|
||||
var rightNow = new Date().getTime(),
|
||||
const rightNow = new Date().getTime(),
|
||||
updatedAtDate = new Date(updatedAt).getTime();
|
||||
|
||||
if (updatedAtDate > (rightNow - DAY * Discourse.SiteSettings.history_hours_low)) return 'heatmap-high';
|
||||
@ -26,12 +26,13 @@ var PostView = Discourse.GroupedView.extend(Ember.Evented, {
|
||||
if (updatedAtDate > (rightNow - DAY * Discourse.SiteSettings.history_hours_high)) return 'heatmap-low';
|
||||
}.property('post.updated_at'),
|
||||
|
||||
postTypeClass: function() {
|
||||
return this.get('post.post_type') === Discourse.Site.currentProp('post_types.moderator_action') ? 'moderator' : 'regular';
|
||||
needsModeratorClass: function() {
|
||||
return (this.get('post.post_type') === this.site.get('post_types.moderator_action')) ||
|
||||
(this.get('post.topic.is_warning') && this.get('post.firstPost'));
|
||||
}.property('post.post_type'),
|
||||
|
||||
groupNameClass: function() {
|
||||
var primaryGroupName = this.get('post.primary_group_name');
|
||||
const primaryGroupName = this.get('post.primary_group_name');
|
||||
if (primaryGroupName) {
|
||||
return "group-" + primaryGroupName;
|
||||
}
|
||||
@ -40,7 +41,7 @@ var PostView = Discourse.GroupedView.extend(Ember.Evented, {
|
||||
showExpandButton: function() {
|
||||
if (this.get('controller.firstPostExpanded')) { return false; }
|
||||
|
||||
var post = this.get('post');
|
||||
const post = this.get('post');
|
||||
return post.get('post_number') === 1 && post.get('topic.expandable_first_post');
|
||||
}.property('post.post_number', 'controller.firstPostExpanded'),
|
||||
|
||||
@ -76,14 +77,14 @@ var PostView = Discourse.GroupedView.extend(Ember.Evented, {
|
||||
repliesShown: Em.computed.gt('post.replies.length', 0),
|
||||
|
||||
_updateQuoteElements($aside, desc) {
|
||||
var navLink = "",
|
||||
quoteTitle = I18n.t("post.follow_quote"),
|
||||
postNumber = $aside.data('post');
|
||||
let navLink = "";
|
||||
const quoteTitle = I18n.t("post.follow_quote"),
|
||||
postNumber = $aside.data('post');
|
||||
|
||||
if (postNumber) {
|
||||
|
||||
// If we have a topic reference
|
||||
var topicId, topic;
|
||||
let topicId, topic;
|
||||
if (topicId = $aside.data('topic')) {
|
||||
topic = this.get('controller.content');
|
||||
|
||||
@ -101,7 +102,7 @@ var PostView = Discourse.GroupedView.extend(Ember.Evented, {
|
||||
}
|
||||
}
|
||||
// Only add the expand/contract control if it's not a full post
|
||||
var expandContract = "";
|
||||
let expandContract = "";
|
||||
if (!$aside.data('full')) {
|
||||
expandContract = "<i class='fa fa-" + desc + "' title='" + I18n.t("post.expand_collapse") + "'></i>";
|
||||
$('.title', $aside).css('cursor', 'pointer');
|
||||
@ -115,7 +116,7 @@ var PostView = Discourse.GroupedView.extend(Ember.Evented, {
|
||||
|
||||
$aside.data('expanded', !$aside.data('expanded'));
|
||||
|
||||
var self = this,
|
||||
const self = this,
|
||||
finished = function() {
|
||||
self.set('expanding', false);
|
||||
};
|
||||
@ -123,23 +124,23 @@ var PostView = Discourse.GroupedView.extend(Ember.Evented, {
|
||||
if ($aside.data('expanded')) {
|
||||
this._updateQuoteElements($aside, 'chevron-up');
|
||||
// Show expanded quote
|
||||
var $blockQuote = $('blockquote', $aside);
|
||||
const $blockQuote = $('blockquote', $aside);
|
||||
$aside.data('original-contents',$blockQuote.html());
|
||||
|
||||
var originalText = $blockQuote.text().trim();
|
||||
const originalText = $blockQuote.text().trim();
|
||||
$blockQuote.html(I18n.t("loading"));
|
||||
var topicId = this.get('post.topic_id');
|
||||
let topicId = this.get('post.topic_id');
|
||||
if ($aside.data('topic')) {
|
||||
topicId = $aside.data('topic');
|
||||
}
|
||||
|
||||
var postId = parseInt($aside.data('post'), 10);
|
||||
const postId = parseInt($aside.data('post'), 10);
|
||||
topicId = parseInt(topicId, 10);
|
||||
|
||||
Discourse.ajax("/posts/by_number/" + topicId + "/" + postId).then(function (result) {
|
||||
// slightly double escape the cooked html to prevent jQuery from unescaping it
|
||||
var escaped = result.cooked.replace("&", "&");
|
||||
var parsed = $(escaped);
|
||||
const escaped = result.cooked.replace("&", "&");
|
||||
const parsed = $(escaped);
|
||||
parsed.replaceText(originalText, "<span class='highlighted'>" + originalText + "</span>");
|
||||
$blockQuote.showHtml(parsed, 'fast', finished);
|
||||
});
|
||||
@ -185,7 +186,7 @@ var PostView = Discourse.GroupedView.extend(Ember.Evented, {
|
||||
// Toggle the replies this post is a reply to
|
||||
toggleReplyHistory(post) {
|
||||
|
||||
var replyHistory = post.get('replyHistory'),
|
||||
const replyHistory = post.get('replyHistory'),
|
||||
topicController = this.get('controller'),
|
||||
origScrollTop = $(window).scrollTop(),
|
||||
replyPostNumber = this.get('post.reply_to_post_number'),
|
||||
@ -197,8 +198,8 @@ var PostView = Discourse.GroupedView.extend(Ember.Evented, {
|
||||
return;
|
||||
}
|
||||
|
||||
var stream = topicController.get('postStream');
|
||||
var offsetFromTop = this.$().position().top - $(window).scrollTop();
|
||||
const stream = topicController.get('postStream');
|
||||
const offsetFromTop = this.$().position().top - $(window).scrollTop();
|
||||
|
||||
if(Discourse.SiteSettings.experimental_reply_expansion) {
|
||||
if(postNumber - replyPostNumber > 1) {
|
||||
@ -213,7 +214,7 @@ var PostView = Discourse.GroupedView.extend(Ember.Evented, {
|
||||
}
|
||||
|
||||
if (replyHistory.length > 0) {
|
||||
var origHeight = this.$('.embedded-posts.top').height();
|
||||
const origHeight = this.$('.embedded-posts.top').height();
|
||||
|
||||
replyHistory.clear();
|
||||
Em.run.next(function() {
|
||||
@ -235,17 +236,17 @@ var PostView = Discourse.GroupedView.extend(Ember.Evented, {
|
||||
|
||||
// Add the quote controls to a post
|
||||
_insertQuoteControls() {
|
||||
var self = this,
|
||||
const self = this,
|
||||
$quotes = this.$('aside.quote');
|
||||
|
||||
// Safety check - in some cases with cloackedView this seems to be `undefined`.
|
||||
if (Em.isEmpty($quotes)) { return; }
|
||||
|
||||
$quotes.each(function(i, e) {
|
||||
var $aside = $(e);
|
||||
const $aside = $(e);
|
||||
if ($aside.data('post')) {
|
||||
self._updateQuoteElements($aside, 'chevron-down');
|
||||
var $title = $('.title', $aside);
|
||||
const $title = $('.title', $aside);
|
||||
|
||||
// Unless it's a full quote, allow click to expand
|
||||
if (!($aside.data('full') || $title.data('has-quote-controls'))) {
|
||||
@ -280,8 +281,8 @@ var PostView = Discourse.GroupedView.extend(Ember.Evented, {
|
||||
}.on('didInsertElement'),
|
||||
|
||||
_applySearchHighlight: function() {
|
||||
var highlight = this.get('controller.searchHighlight');
|
||||
var cooked = this.$('.cooked');
|
||||
const highlight = this.get('controller.searchHighlight');
|
||||
const cooked = this.$('.cooked');
|
||||
|
||||
if(!cooked){ return; }
|
||||
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
import isElementInViewport from "discourse/lib/is-element-in-viewport";
|
||||
import ScrollTop from 'discourse/mixins/scroll-top';
|
||||
|
||||
var readFaq = false;
|
||||
|
||||
export default Ember.View.extend(Discourse.ScrollTop, {
|
||||
export default Ember.View.extend(ScrollTop, {
|
||||
|
||||
_checkRead: function() {
|
||||
const path = this.get('controller.model.path');
|
||||
|
||||
@ -1 +1,3 @@
|
||||
export default Ember.View.extend(Discourse.ScrollTop);
|
||||
import ScrollTop from 'discourse/mixins/scroll-top';
|
||||
|
||||
export default Ember.View.extend(ScrollTop);
|
||||
|
||||
@ -3,8 +3,8 @@ import CleansUp from 'discourse/mixins/cleans-up';
|
||||
import afterTransition from 'discourse/lib/after-transition';
|
||||
|
||||
const clickOutsideEventName = "mousedown.outside-user-card",
|
||||
clickDataExpand = "click.discourse-user-card",
|
||||
clickMention = "click.discourse-user-mention";
|
||||
clickDataExpand = "click.discourse-user-card",
|
||||
clickMention = "click.discourse-user-mention";
|
||||
|
||||
export default Discourse.View.extend(CleansUp, {
|
||||
elementId: 'user-card',
|
||||
@ -27,54 +27,50 @@ export default Discourse.View.extend(CleansUp, {
|
||||
}.observes('controller.user.card_background'),
|
||||
|
||||
_setup: function() {
|
||||
const self = this;
|
||||
|
||||
afterTransition(self.$(), this._hide.bind(this));
|
||||
afterTransition(this.$(), this._hide.bind(this));
|
||||
|
||||
$('html').off(clickOutsideEventName)
|
||||
.on(clickOutsideEventName, function(e) {
|
||||
if (self.get('controller.visible')) {
|
||||
const $target = $(e.target);
|
||||
if ($target.closest('[data-user-card]').data('userCard') ||
|
||||
.on(clickOutsideEventName, (e) => {
|
||||
if (this.get('controller.visible')) {
|
||||
const $target = $(e.target);
|
||||
if ($target.closest('[data-user-card]').data('userCard') ||
|
||||
$target.closest('a.mention').length > 0 ||
|
||||
$target.closest('#user-card').length > 0) {
|
||||
return;
|
||||
return;
|
||||
}
|
||||
|
||||
this.get('controller').close();
|
||||
}
|
||||
|
||||
self.get('controller').close();
|
||||
return true;
|
||||
});
|
||||
|
||||
const expand = (username, $target) => {
|
||||
const postId = $target.parents('article').data('post-id'),
|
||||
user = this.get('controller').show(username, postId, $target[0]);
|
||||
if (user !== undefined) {
|
||||
user.then( () => this._willShow($target) ).catch( () => this._hide() );
|
||||
} else {
|
||||
this._hide();
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
var expand = function(username, $target) {
|
||||
const postId = $target.parents('article').data('post-id');
|
||||
self.get('controller')
|
||||
.show(username, postId, $target[0])
|
||||
.then(function() {
|
||||
self._willShow($target);
|
||||
}).catch(function() {
|
||||
self._hide();
|
||||
});
|
||||
return false;
|
||||
};
|
||||
|
||||
$('#main-outlet').on(clickDataExpand, '[data-user-card]', function(e) {
|
||||
$('#main-outlet').on(clickDataExpand, '[data-user-card]', (e) => {
|
||||
if (e.ctrlKey || e.metaKey) { return; }
|
||||
|
||||
const $target = $(e.currentTarget),
|
||||
username = $target.data('user-card');
|
||||
username = $target.data('user-card');
|
||||
return expand(username, $target);
|
||||
});
|
||||
|
||||
$('#main-outlet').on(clickMention, 'a.mention', function(e) {
|
||||
$('#main-outlet').on(clickMention, 'a.mention', (e) => {
|
||||
if (e.ctrlKey || e.metaKey) { return; }
|
||||
|
||||
const $target = $(e.target),
|
||||
username = $target.text().replace(/^@/, '');
|
||||
username = $target.text().replace(/^@/, '');
|
||||
return expand(username, $target);
|
||||
});
|
||||
|
||||
this.appEvents.on('usercard:shown', this, '_shown');
|
||||
}.on('didInsertElement'),
|
||||
|
||||
@ -89,9 +85,8 @@ export default Discourse.View.extend(CleansUp, {
|
||||
|
||||
_willShow(target) {
|
||||
if (!target) { return; }
|
||||
const self = this,
|
||||
width = this.$().width();
|
||||
Em.run.schedule('afterRender', function() {
|
||||
const width = this.$().width();
|
||||
Ember.run.schedule('afterRender', () => {
|
||||
if (target) {
|
||||
let position = target.offset();
|
||||
if (position) {
|
||||
@ -100,19 +95,20 @@ export default Discourse.View.extend(CleansUp, {
|
||||
const overage = ($(window).width() - 50) - (position.left + width);
|
||||
if (overage < 0) {
|
||||
position.left += overage;
|
||||
position.top += target.height() + 8;
|
||||
position.top += target.height() + 48;
|
||||
}
|
||||
|
||||
position.top -= $('#main-outlet').offset().top;
|
||||
self.$().css(position);
|
||||
this.$().css(position);
|
||||
}
|
||||
this.appEvents.trigger('usercard:shown');
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
_hide() {
|
||||
if (!this.get('controller.visible')) {
|
||||
this.$().css({ left: -9999, top: -9999 });
|
||||
this.$().css({left: -9999, top: -9999});
|
||||
}
|
||||
},
|
||||
|
||||
@ -120,11 +116,19 @@ export default Discourse.View.extend(CleansUp, {
|
||||
this.get('controller').close();
|
||||
},
|
||||
|
||||
keyUp(e) {
|
||||
if (e.keyCode === 27) { // ESC
|
||||
const target = this.get('controller.cardTarget');
|
||||
this.cleanUp();
|
||||
target.focus();
|
||||
}
|
||||
},
|
||||
|
||||
_removeEvents: function() {
|
||||
$('html').off(clickOutsideEventName);
|
||||
|
||||
$('#main').off(clickDataExpand)
|
||||
.off(clickMention);
|
||||
.off(clickMention);
|
||||
|
||||
this.appEvents.off('usercard:shown', this, '_shown');
|
||||
}.on('willDestroyElement')
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
export default Ember.View.extend(Discourse.ScrollTop, {
|
||||
import ScrollTop from 'discourse/mixins/scroll-top';
|
||||
|
||||
export default Ember.View.extend(ScrollTop, {
|
||||
templateName: 'user/user',
|
||||
userBinding: 'controller.content'
|
||||
});
|
||||
|
||||
@ -3,6 +3,7 @@ if Rails.env.development? || Rails.env.test?
|
||||
require_asset ("ember-template-compiler.js")
|
||||
require_asset ("ember.custom.debug.js")
|
||||
else
|
||||
require_asset ("ember-template-compiler.js")
|
||||
require_asset ("ember.prod.js")
|
||||
end
|
||||
%>
|
||||
|
||||
3
app/assets/javascripts/locales/bs_BA.js.erb
Normal file
3
app/assets/javascripts/locales/bs_BA.js.erb
Normal file
@ -0,0 +1,3 @@
|
||||
//= depend_on 'client.bs_BA.yml'
|
||||
//= require locales/i18n
|
||||
<%= JsLocaleHelper.output_locale(:bs_BA) %>
|
||||
@ -107,7 +107,6 @@
|
||||
.category .badge-notification {
|
||||
background-color:transparent;
|
||||
color: scale-color($primary, $lightness: 50%);
|
||||
position: inherit;
|
||||
}
|
||||
|
||||
.subcategories .badge {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user