Version bump
This commit is contained in:
commit
557dde29c7
@ -1,7 +1,5 @@
|
||||
app/assets/javascripts/env.js
|
||||
app/assets/javascripts/main_include.js
|
||||
app/assets/javascripts/main_include_admin.js
|
||||
app/assets/javascripts/pagedown_custom.js
|
||||
app/assets/javascripts/vendor.js
|
||||
app/assets/javascripts/locales/i18n.js
|
||||
app/assets/javascripts/ember-addons/
|
||||
@ -11,11 +9,9 @@ lib/javascripts/messageformat.js
|
||||
lib/javascripts/moment.js
|
||||
lib/javascripts/moment_locale/
|
||||
lib/highlight_js/
|
||||
plugins/**/lib/javascripts/locale
|
||||
public/javascripts/
|
||||
spec/phantom_js/smoke_test.js
|
||||
vendor/
|
||||
test/javascripts/test_helper.js
|
||||
test/javascripts/test_helper.js
|
||||
test/javascripts/fixtures
|
||||
test/javascripts/helpers/assertions.js
|
||||
app/assets/javascripts/ember-addons/
|
||||
|
||||
5
.gitignore
vendored
5
.gitignore
vendored
@ -117,9 +117,12 @@ bundler_stubs/*
|
||||
vendor/bundle/*
|
||||
*.db
|
||||
|
||||
#ignore jetbrains ide file
|
||||
# ignore jetbrains ide file
|
||||
*.iml
|
||||
|
||||
# vim swap
|
||||
*.swn
|
||||
|
||||
# ignore nodejs files
|
||||
/node_modules
|
||||
/package-lock.json
|
||||
|
||||
@ -21,8 +21,6 @@ addons:
|
||||
|
||||
matrix:
|
||||
fast_finish: true
|
||||
allow_failures:
|
||||
- rvm: 2.5.0
|
||||
|
||||
rvm:
|
||||
- 2.5.0
|
||||
|
||||
11
Gemfile
11
Gemfile
@ -36,12 +36,12 @@ gem 'redis-namespace'
|
||||
|
||||
gem 'active_model_serializers', '~> 0.8.3'
|
||||
|
||||
gem 'onebox', '1.8.33'
|
||||
gem 'onebox', '1.8.36'
|
||||
|
||||
gem 'http_accept_language', '~>2.0.5', require: false
|
||||
|
||||
gem 'ember-rails', '0.18.5'
|
||||
gem 'ember-source'
|
||||
gem 'ember-source', '2.13.3'
|
||||
gem 'ember-handlebars-template', '0.7.5'
|
||||
gem 'barber'
|
||||
|
||||
@ -53,14 +53,13 @@ gem 'fast_xs'
|
||||
|
||||
gem 'fast_xor'
|
||||
|
||||
# Forked until https://github.com/sdsykes/fastimage/pull/93 is merged
|
||||
gem 'discourse_fastimage', require: 'fastimage'
|
||||
gem 'fastimage'
|
||||
|
||||
gem 'aws-sdk-s3', require: false
|
||||
gem 'excon', require: false
|
||||
gem 'unf', require: false
|
||||
|
||||
gem 'email_reply_trimmer', '0.1.8'
|
||||
gem 'email_reply_trimmer', '0.1.9'
|
||||
|
||||
# Forked until https://github.com/toy/image_optim/pull/149 is merged
|
||||
gem 'discourse_image_optim', require: 'image_optim'
|
||||
@ -83,7 +82,7 @@ gem 'omniauth-oauth2', require: false
|
||||
|
||||
gem 'omniauth-google-oauth2'
|
||||
gem 'oj'
|
||||
gem 'pg'
|
||||
gem 'pg', '~> 0.21.0'
|
||||
gem 'pry-rails', require: false
|
||||
gem 'r2', '~> 0.2.5', require: false
|
||||
gem 'rake'
|
||||
|
||||
38
Gemfile.lock
38
Gemfile.lock
@ -74,7 +74,7 @@ GEM
|
||||
uniform_notifier (~> 1.10.0)
|
||||
byebug (9.0.6)
|
||||
certified (1.0.0)
|
||||
coderay (1.1.1)
|
||||
coderay (1.1.2)
|
||||
concurrent-ruby (1.0.5)
|
||||
connection_pool (2.2.1)
|
||||
cppjieba_rb (0.3.0)
|
||||
@ -85,14 +85,13 @@ GEM
|
||||
diff-lcs (1.3)
|
||||
discourse-qunit-rails (0.0.11)
|
||||
railties
|
||||
discourse_fastimage (2.1.0)
|
||||
discourse_image_optim (0.24.5)
|
||||
exifr (~> 1.2, >= 1.2.2)
|
||||
fspath (~> 3.0)
|
||||
image_size (~> 1.5)
|
||||
in_threads (~> 1.3)
|
||||
progress (~> 3.0, >= 3.0.1)
|
||||
email_reply_trimmer (0.1.8)
|
||||
email_reply_trimmer (0.1.9)
|
||||
ember-data-source (2.2.1)
|
||||
ember-source (>= 1.8, < 3.0)
|
||||
ember-handlebars-template (0.7.5)
|
||||
@ -120,6 +119,7 @@ GEM
|
||||
rake
|
||||
rake-compiler
|
||||
fast_xs (0.8.0)
|
||||
fastimage (2.1.1)
|
||||
ffi (1.9.18)
|
||||
flamegraph (0.9.5)
|
||||
foreman (0.84.0)
|
||||
@ -144,8 +144,8 @@ GEM
|
||||
railties (>= 4.2.0)
|
||||
thor (>= 0.14, < 2.0)
|
||||
jwt (1.5.6)
|
||||
kgio (2.11.0)
|
||||
libv8 (5.9.211.38.1)
|
||||
kgio (2.11.1)
|
||||
libv8 (6.3.292.48.1)
|
||||
listen (3.1.5)
|
||||
rb-fsevent (~> 0.9, >= 0.9.4)
|
||||
rb-inotify (~> 0.9, >= 0.9.7)
|
||||
@ -166,7 +166,7 @@ GEM
|
||||
mail (2.6.6)
|
||||
mime-types (>= 1.16, < 4)
|
||||
memory_profiler (0.9.8)
|
||||
message_bus (2.1.1)
|
||||
message_bus (2.1.2)
|
||||
rack (>= 1.1.3)
|
||||
metaclass (0.0.4)
|
||||
method_source (0.8.2)
|
||||
@ -175,8 +175,8 @@ GEM
|
||||
mime-types-data (3.2016.0521)
|
||||
mini_mime (0.1.3)
|
||||
mini_portile2 (2.3.0)
|
||||
mini_racer (0.1.11)
|
||||
libv8 (~> 5.7)
|
||||
mini_racer (0.1.15)
|
||||
libv8 (~> 6.3)
|
||||
mini_suffix (0.3.0)
|
||||
ffi (~> 1.9)
|
||||
minitest (5.10.3)
|
||||
@ -232,7 +232,7 @@ GEM
|
||||
omniauth-twitter (1.3.0)
|
||||
omniauth-oauth (~> 1.1)
|
||||
rack
|
||||
onebox (1.8.33)
|
||||
onebox (1.8.36)
|
||||
fast_blank (>= 1.0.0)
|
||||
htmlentities (~> 4.3)
|
||||
moneta (~> 1.0)
|
||||
@ -246,7 +246,7 @@ GEM
|
||||
parallel (1.12.0)
|
||||
parser (2.4.0.0)
|
||||
ast (~> 2.2)
|
||||
pg (0.20.0)
|
||||
pg (0.21.0)
|
||||
powerpack (0.1.1)
|
||||
progress (3.3.1)
|
||||
pry (0.10.4)
|
||||
@ -275,7 +275,7 @@ GEM
|
||||
nokogiri (>= 1.6)
|
||||
rails-html-sanitizer (1.0.3)
|
||||
loofah (~> 2.0)
|
||||
rails_multisite (1.1.2)
|
||||
rails_multisite (2.0.2)
|
||||
activerecord (> 4.2, < 6)
|
||||
railties (> 4.2, < 6)
|
||||
railties (5.1.4)
|
||||
@ -293,7 +293,7 @@ GEM
|
||||
rb-fsevent (0.9.8)
|
||||
rb-inotify (0.9.8)
|
||||
ffi (>= 0.5.0)
|
||||
rbtrace (0.4.8)
|
||||
rbtrace (0.4.10)
|
||||
ffi (>= 1.0.6)
|
||||
msgpack (>= 0.4.3)
|
||||
trollop (>= 1.16.2)
|
||||
@ -389,7 +389,7 @@ GEM
|
||||
unf_ext
|
||||
unf_ext (0.0.7.4)
|
||||
unicode-display_width (1.3.0)
|
||||
unicorn (5.3.1)
|
||||
unicorn (5.4.0)
|
||||
kgio (~> 2.6)
|
||||
raindrops (~> 0.7)
|
||||
uniform_notifier (1.10.0)
|
||||
@ -420,12 +420,11 @@ DEPENDENCIES
|
||||
certified
|
||||
cppjieba_rb
|
||||
discourse-qunit-rails
|
||||
discourse_fastimage
|
||||
discourse_image_optim
|
||||
email_reply_trimmer (= 0.1.8)
|
||||
email_reply_trimmer (= 0.1.9)
|
||||
ember-handlebars-template (= 0.7.5)
|
||||
ember-rails (= 0.18.5)
|
||||
ember-source
|
||||
ember-source (= 2.13.3)
|
||||
excon
|
||||
execjs
|
||||
fabrication (= 2.9.8)
|
||||
@ -433,6 +432,7 @@ DEPENDENCIES
|
||||
fast_blank
|
||||
fast_xor
|
||||
fast_xs
|
||||
fastimage
|
||||
flamegraph
|
||||
foreman
|
||||
gc_tracer
|
||||
@ -469,9 +469,9 @@ DEPENDENCIES
|
||||
omniauth-oauth2
|
||||
omniauth-openid
|
||||
omniauth-twitter
|
||||
onebox (= 1.8.33)
|
||||
onebox (= 1.8.36)
|
||||
openid-redis-store
|
||||
pg
|
||||
pg (~> 0.21.0)
|
||||
pry-nav
|
||||
pry-rails
|
||||
puma
|
||||
@ -509,4 +509,4 @@ DEPENDENCIES
|
||||
webmock
|
||||
|
||||
BUNDLED WITH
|
||||
1.16.0
|
||||
1.16.1
|
||||
|
||||
@ -0,0 +1,3 @@
|
||||
export default Ember.Component.extend({
|
||||
classNames: ['flag-user-lists']
|
||||
});
|
||||
@ -0,0 +1,3 @@
|
||||
export default Ember.Component.extend({
|
||||
tagName: 'h3'
|
||||
});
|
||||
@ -13,15 +13,18 @@ export default Ember.Component.extend({
|
||||
'flaggedPost.deleted'
|
||||
],
|
||||
|
||||
canAct: Ember.computed.alias('actableFilter'),
|
||||
|
||||
@computed('filter')
|
||||
canAct(filter) {
|
||||
actableFilter(filter) {
|
||||
return filter === 'active';
|
||||
},
|
||||
|
||||
removeAfter(promise) {
|
||||
return promise.then(() => {
|
||||
this.attrs.removePost();
|
||||
}).catch(() => {
|
||||
}).catch(error => {
|
||||
if (error._discourse_displayed) { return; }
|
||||
bootbox.alert(I18n.t("admin.flags.error"));
|
||||
});
|
||||
},
|
||||
|
||||
@ -0,0 +1,3 @@
|
||||
export default Ember.Component.extend({
|
||||
tagName: ''
|
||||
});
|
||||
@ -0,0 +1,3 @@
|
||||
export default Ember.Component.extend({
|
||||
tagName: ''
|
||||
});
|
||||
@ -1,7 +1,7 @@
|
||||
export default Ember.Controller.extend({
|
||||
loading: false,
|
||||
term: null,
|
||||
period: "yearly",
|
||||
period: "quarterly",
|
||||
searchType: "all",
|
||||
|
||||
searchTypeOptions: [
|
||||
|
||||
@ -1,8 +1,10 @@
|
||||
import UserBadge from 'discourse/models/user-badge';
|
||||
import GrantBadgeController from "discourse/mixins/grant-badge-controller";
|
||||
|
||||
export default Ember.Controller.extend({
|
||||
export default Ember.Controller.extend(GrantBadgeController, {
|
||||
adminUser: Ember.inject.controller(),
|
||||
user: Ember.computed.alias('adminUser.model'),
|
||||
userBadges: Ember.computed.alias('model'),
|
||||
allBadges: Ember.computed.alias('badges'),
|
||||
|
||||
sortedBadges: Ember.computed.sort('model', 'badgeSortOrder'),
|
||||
badgeSortOrder: ['granted_at:desc'],
|
||||
@ -41,36 +43,6 @@ export default Ember.Controller.extend({
|
||||
return _(expanded).sortBy(group => group.granted_at).reverse().value();
|
||||
}.property('model', 'model.[]', 'model.expandedBadges.[]'),
|
||||
|
||||
/**
|
||||
Array of badges that have not been granted to this user.
|
||||
|
||||
@property grantableBadges
|
||||
@type {Boolean}
|
||||
**/
|
||||
grantableBadges: function() {
|
||||
var granted = {};
|
||||
this.get('model').forEach(function(userBadge) {
|
||||
granted[userBadge.get('badge_id')] = true;
|
||||
});
|
||||
|
||||
var badges = [];
|
||||
this.get('badges').forEach(function(badge) {
|
||||
if (badge.get('enabled') && (badge.get('multiple_grant') || !granted[badge.get('id')])) {
|
||||
badges.push(badge);
|
||||
}
|
||||
});
|
||||
|
||||
return _.sortBy(badges, badge => badge.get('name'));
|
||||
}.property('badges.[]', 'model.[]'),
|
||||
|
||||
/**
|
||||
Whether there are any badges that can be granted.
|
||||
|
||||
@property noBadges
|
||||
@type {Boolean}
|
||||
**/
|
||||
noBadges: Em.computed.empty('grantableBadges'),
|
||||
|
||||
actions: {
|
||||
|
||||
expandGroup: function(userBadge){
|
||||
@ -79,21 +51,21 @@ export default Ember.Controller.extend({
|
||||
model.get('expandedBadges').pushObject(userBadge.badge.id);
|
||||
},
|
||||
|
||||
grantBadge(badgeId) {
|
||||
UserBadge.grant(badgeId, this.get('user.username'), this.get('badgeReason')).then(userBadge => {
|
||||
this.set('badgeReason', '');
|
||||
this.get('model').pushObject(userBadge);
|
||||
Ember.run.next(() => {
|
||||
// Update the selected badge ID after the combobox has re-rendered.
|
||||
const newSelectedBadge = this.get('grantableBadges')[0];
|
||||
if (newSelectedBadge) {
|
||||
this.set('selectedBadgeId', newSelectedBadge.get('id'));
|
||||
}
|
||||
grantBadge() {
|
||||
this.grantBadge(this.get('selectedBadgeId'), this.get('user.username'), this.get('badgeReason'))
|
||||
.then(() => {
|
||||
this.set('badgeReason', '');
|
||||
Ember.run.next(() => {
|
||||
// Update the selected badge ID after the combobox has re-rendered.
|
||||
const newSelectedBadge = this.get('grantableBadges')[0];
|
||||
if (newSelectedBadge) {
|
||||
this.set('selectedBadgeId', newSelectedBadge.get('id'));
|
||||
}
|
||||
});
|
||||
}, function() {
|
||||
// Failure
|
||||
bootbox.alert(I18n.t('generic_error'));
|
||||
});
|
||||
}, function() {
|
||||
// Failure
|
||||
bootbox.alert(I18n.t('generic_error'));
|
||||
});
|
||||
},
|
||||
|
||||
revokeBadge(userBadge) {
|
||||
|
||||
@ -64,6 +64,15 @@ export default Ember.Controller.extend(CanCheckEmails, {
|
||||
anonymize() { return this.get('model').anonymize(); },
|
||||
destroy() { return this.get('model').destroy(); },
|
||||
|
||||
viewActionLogs() {
|
||||
this.get('adminTools').showActionLogs(this, {
|
||||
target_user: this.get('model.username'),
|
||||
});
|
||||
},
|
||||
|
||||
showFlagsReceived() {
|
||||
this.get('adminTools').showFlagsReceived(this.get('model'));
|
||||
},
|
||||
showSuspendModal() {
|
||||
this.get('adminTools').showSuspendModal(this.get('model'));
|
||||
},
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
import { ajax } from 'discourse/lib/ajax';
|
||||
export default Ember.Controller.extend({
|
||||
import ModalFunctionality from 'discourse/mixins/modal-functionality';
|
||||
|
||||
export default Ember.Controller.extend(ModalFunctionality, {
|
||||
|
||||
modelChanged: function(){
|
||||
const model = this.get('model');
|
||||
|
||||
@ -0,0 +1,17 @@
|
||||
export default Ember.Controller.extend({
|
||||
loadingFlags: null,
|
||||
user: null,
|
||||
|
||||
onShow() {
|
||||
this.set('loadingFlags', true);
|
||||
this.store.findAll('flagged-post', {
|
||||
filter: 'without_custom',
|
||||
user_id: this.get('model.id')
|
||||
}).then(result => {
|
||||
this.set('loadingFlags', false);
|
||||
console.log(result);
|
||||
this.set('flaggedPosts', result);
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
@ -22,6 +22,11 @@ export default Post.extend({
|
||||
});
|
||||
},
|
||||
|
||||
@computed('post_actions')
|
||||
hasDisposedBy() {
|
||||
return this.get('post_actions').some(action => action.disposed_by);
|
||||
},
|
||||
|
||||
@computed('post_actions.@each.name_key')
|
||||
flaggedForSpam() {
|
||||
return this.get('post_actions').every(action => action.name_key === 'spam');
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { ajax } from 'discourse/lib/ajax';
|
||||
import round from "discourse/lib/round";
|
||||
import { fmt } from 'discourse/lib/computed';
|
||||
import { fillMissingDates } from 'discourse/lib/utilities';
|
||||
|
||||
const Report = Discourse.Model.extend({
|
||||
reportUrl: fmt("type", "/admin/reports/%@"),
|
||||
@ -142,14 +143,13 @@ Report.reopenClass({
|
||||
group_id: groupId
|
||||
}
|
||||
}).then(json => {
|
||||
// Add a percent field to each tuple
|
||||
let maxY = 0;
|
||||
json.report.data.forEach(row => {
|
||||
if (row.y > maxY) maxY = row.y;
|
||||
});
|
||||
if (maxY > 0) {
|
||||
json.report.data.forEach(row => row.percentage = Math.round((row.y / maxY) * 100));
|
||||
// Add zero values for missing dates
|
||||
if (json.report.data.length > 0) {
|
||||
const startDateFormatted = moment(json.report.start_date).format('YYYY-MM-DD');
|
||||
const endDateFormatted = moment(json.report.end_date).format('YYYY-MM-DD');
|
||||
json.report.data = fillMissingDates(json.report.data, startDateFormatted, endDateFormatted);
|
||||
}
|
||||
|
||||
const model = Report.create({ type: type });
|
||||
model.setProperties(json.report);
|
||||
return model;
|
||||
|
||||
@ -1,7 +1,15 @@
|
||||
import Backup from 'admin/models/backup';
|
||||
|
||||
export default Ember.Route.extend({
|
||||
activate() {
|
||||
this.messageBus.subscribe("/admin/backups", backups => this.controller.set("model", backups));
|
||||
},
|
||||
|
||||
model() {
|
||||
return Backup.find();
|
||||
},
|
||||
|
||||
deactivate() {
|
||||
this.messageBus.unsubscribe("/admin/backups");
|
||||
}
|
||||
});
|
||||
|
||||
@ -9,26 +9,24 @@ const LOG_CHANNEL = "/admin/backups/logs";
|
||||
export default Discourse.Route.extend({
|
||||
|
||||
activate() {
|
||||
this.messageBus.subscribe(LOG_CHANNEL, this._processLogMessage.bind(this));
|
||||
},
|
||||
|
||||
_processLogMessage(log) {
|
||||
if (log.message === "[STARTED]") {
|
||||
this.controllerFor("adminBackups").set("model.isOperationRunning", true);
|
||||
this.controllerFor("adminBackupsLogs").get('logs').clear();
|
||||
} else if (log.message === "[FAILED]") {
|
||||
this.controllerFor("adminBackups").set("model.isOperationRunning", false);
|
||||
bootbox.alert(I18n.t("admin.backups.operations.failed", { operation: log.operation }));
|
||||
} else if (log.message === "[SUCCESS]") {
|
||||
Discourse.User.currentProp("hideReadOnlyAlert", false);
|
||||
this.controllerFor("adminBackups").set("model.isOperationRunning", false);
|
||||
if (log.operation === "restore") {
|
||||
// redirect to homepage when the restore is done (session might be lost)
|
||||
window.location.pathname = Discourse.getURL("/");
|
||||
this.messageBus.subscribe(LOG_CHANNEL, (log) => {
|
||||
if (log.message === "[STARTED]") {
|
||||
this.controllerFor("adminBackups").set("model.isOperationRunning", true);
|
||||
this.controllerFor("adminBackupsLogs").get('logs').clear();
|
||||
} else if (log.message === "[FAILED]") {
|
||||
this.controllerFor("adminBackups").set("model.isOperationRunning", false);
|
||||
bootbox.alert(I18n.t("admin.backups.operations.failed", { operation: log.operation }));
|
||||
} else if (log.message === "[SUCCESS]") {
|
||||
Discourse.User.currentProp("hideReadOnlyAlert", false);
|
||||
this.controllerFor("adminBackups").set("model.isOperationRunning", false);
|
||||
if (log.operation === "restore") {
|
||||
// redirect to homepage when the restore is done (session might be lost)
|
||||
window.location.pathname = Discourse.getURL("/");
|
||||
}
|
||||
} else {
|
||||
this.controllerFor("adminBackupsLogs").get('logs').pushObject(Em.Object.create(log));
|
||||
}
|
||||
} else {
|
||||
this.controllerFor("adminBackupsLogs").get('logs').pushObject(Em.Object.create(log));
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
model() {
|
||||
@ -122,12 +120,7 @@ export default Discourse.Route.extend({
|
||||
},
|
||||
|
||||
uploadSuccess(filename) {
|
||||
const self = this;
|
||||
bootbox.alert(I18n.t("admin.backups.upload.success", { filename: filename }), function() {
|
||||
Backup.find().then(function (backups) {
|
||||
self.controllerFor("adminBackupsIndex").set("model", backups);
|
||||
});
|
||||
});
|
||||
bootbox.alert(I18n.t("admin.backups.upload.success", { filename: filename }));
|
||||
},
|
||||
|
||||
uploadError(filename, message) {
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
import { ajax } from 'discourse/lib/ajax';
|
||||
import { fillMissingDates } from 'discourse/lib/utilities';
|
||||
import { translateResults } from "discourse/lib/search";
|
||||
|
||||
export default Discourse.Route.extend({
|
||||
queryParams: {
|
||||
@ -15,6 +17,16 @@ export default Discourse.Route.extend({
|
||||
search_type: params.searchType
|
||||
}
|
||||
}).then(json => {
|
||||
// Add zero values for missing dates
|
||||
if (json.term.data.length > 0) {
|
||||
const startDate = (json.term.period === "all") ? moment(json.term.data[0].x).format('YYYY-MM-DD') : moment(json.term.start_date).format('YYYY-MM-DD');
|
||||
const endDate = moment(json.term.end_date).format('YYYY-MM-DD');
|
||||
json.term.data = fillMissingDates(json.term.data, startDate, endDate);
|
||||
}
|
||||
if (json.term.search_result) {
|
||||
json.term.search_result = translateResults(json.term.search_result);
|
||||
}
|
||||
|
||||
const model = Ember.Object.create({ type: "search_log_term" });
|
||||
model.setProperties(json.term);
|
||||
return model;
|
||||
|
||||
@ -21,15 +21,5 @@ export default Discourse.Route.extend({
|
||||
availableGroups: this._availableGroups,
|
||||
model
|
||||
});
|
||||
},
|
||||
|
||||
actions: {
|
||||
viewActionLogs(username) {
|
||||
const controller = this.controllerFor('adminLogs.staffActionLogs');
|
||||
this.transitionTo('adminLogs.staffActionLogs').then(() => {
|
||||
controller.set('filters', Ember.Object.create());
|
||||
controller._changeFilters({ target_user: username });
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@ -17,10 +17,26 @@ export default Ember.Service.extend({
|
||||
this.siteSettings = getOwner(this).lookup('site-settings:main');
|
||||
},
|
||||
|
||||
showActionLogs(target, filters) {
|
||||
const controller = getOwner(target).lookup('controller:adminLogs.staffActionLogs');
|
||||
target.transitionToRoute('adminLogs.staffActionLogs').then(() => {
|
||||
controller.set('filters', Ember.Object.create());
|
||||
controller._changeFilters(filters);
|
||||
});
|
||||
},
|
||||
|
||||
showFlagsReceived(user) {
|
||||
showModal(`admin-flags-received`, { admin: true, model: user });
|
||||
},
|
||||
|
||||
checkSpammer(userId) {
|
||||
return AdminUser.find(userId).then(au => this.spammerDetails(au));
|
||||
},
|
||||
|
||||
deleteUser(id) {
|
||||
AdminUser.find(id).then(user => user.destroy({ deletePosts: true }));
|
||||
},
|
||||
|
||||
spammerDetails(adminUser) {
|
||||
return {
|
||||
deleteUser: () => this._deleteSpammer(adminUser),
|
||||
@ -39,11 +55,10 @@ export default Ember.Service.extend({
|
||||
controller.set('post', opts.post);
|
||||
}
|
||||
|
||||
let promise = user.adminUserView ?
|
||||
return (user.adminUserView ?
|
||||
Ember.RSVP.resolve(user) :
|
||||
AdminUser.find(user.get('id'));
|
||||
|
||||
promise.then(loadedUser => {
|
||||
AdminUser.find(user.get('id'))
|
||||
).then(loadedUser => {
|
||||
controller.setProperties({
|
||||
user: loadedUser,
|
||||
loadingUser: false,
|
||||
|
||||
@ -4,11 +4,11 @@
|
||||
{{/admin-form-row}}
|
||||
|
||||
{{#admin-form-row label="admin.user_fields.name"}}
|
||||
{{input value=buffered.name class="user-field-name"}}
|
||||
{{input value=buffered.name class="user-field-name" maxlength="255"}}
|
||||
{{/admin-form-row}}
|
||||
|
||||
{{#admin-form-row label="admin.user_fields.description"}}
|
||||
{{input value=buffered.description class="user-field-desc"}}
|
||||
{{input value=buffered.description class="user-field-desc" maxlength="255"}}
|
||||
{{/admin-form-row}}
|
||||
|
||||
{{#if bufferedFieldType.hasOptions}}
|
||||
|
||||
@ -0,0 +1,32 @@
|
||||
<div class='flagged-by'>
|
||||
<div class='user-list-title'>
|
||||
{{i18n "admin.flags.flagged_by"}}
|
||||
</div>
|
||||
<div class='flag-users'>
|
||||
{{#each flaggedPost.post_actions as |postAction|}}
|
||||
{{#flag-user user=postAction.user date=postAction.created_at}}
|
||||
<div class='flagger-flag-type'>
|
||||
{{post-action-title postAction.post_action_type_id postAction.name_key}}
|
||||
</div>
|
||||
{{/flag-user}}
|
||||
{{/each}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{#if showResolvedBy}}
|
||||
<div class='flagged-post-resolved-by'>
|
||||
<div class='user-list-title'>
|
||||
{{i18n "admin.flags.resolved_by"}}
|
||||
</div>
|
||||
<div class='flag-users'>
|
||||
{{#each flaggedPost.post_actions as |postAction|}}
|
||||
{{#flag-user user=postAction.disposed_by date=postAction.disposed_at}}
|
||||
{{disposition-icon postAction.disposition}}
|
||||
{{#if postAction.staff_took_action}}
|
||||
{{d-icon "gavel" title="admin.flags.took_action"}}
|
||||
{{/if}}
|
||||
{{/flag-user}}
|
||||
{{/each}}
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
@ -0,0 +1,5 @@
|
||||
{{#if flaggedPost.topic.isPrivateMessage}}
|
||||
<span class="private-message-glyph">{{d-icon "envelope"}}</span>
|
||||
{{/if}}
|
||||
{{topic-status topic=flaggedPost.topic}}
|
||||
<a href='{{unbound flaggedPost.url}}'>{{{unbound flaggedPost.topic.fancyTitle}}}</a>
|
||||
@ -22,17 +22,15 @@
|
||||
<div class="flagged-post-contents">
|
||||
<div class='flagged-post-user-details'>
|
||||
<a class='username' href={{user.path}} data-user-card={{flaggedPost.user.username}}>{{format-username flaggedPost.user.username}}</a>
|
||||
{{plugin-outlet
|
||||
name="flagged-post-controls"
|
||||
tagName=""
|
||||
args=(hash flaggedPost=flaggedPost actableFilter=actableFilter topic=topic)}}
|
||||
</div>
|
||||
|
||||
<div class='flagged-post-excerpt'>
|
||||
{{#unless hideTitle}}
|
||||
<h3>
|
||||
{{#if flaggedPost.topic.isPrivateMessage}}
|
||||
<span class="private-message-glyph">{{d-icon "envelope"}}</span>
|
||||
{{/if}}
|
||||
{{topic-status topic=flaggedPost.topic}}
|
||||
<a href='{{unbound flaggedPost.url}}'>{{{unbound flaggedPost.topic.fancyTitle}}}</a>
|
||||
</h3>
|
||||
{{flagged-post-title flaggedPost=flaggedPost}}
|
||||
{{/unless}}
|
||||
{{#if flaggedPost.postAuthorFlagged}}
|
||||
{{#if expanded}}
|
||||
@ -68,44 +66,11 @@
|
||||
</div>
|
||||
{{/each}}
|
||||
|
||||
<div class='flag-user-lists'>
|
||||
<div class='flagged-by'>
|
||||
<div class='user-list-title'>
|
||||
{{i18n "admin.flags.flagged_by"}}
|
||||
</div>
|
||||
<div class='flag-users'>
|
||||
{{#each flaggedPost.post_actions as |postAction|}}
|
||||
{{#flag-user user=postAction.user date=postAction.created_at}}
|
||||
<div class='flagger-flag-type'>
|
||||
{{post-action-title postAction.post_action_type_id postAction.name_key}}
|
||||
</div>
|
||||
{{/flag-user}}
|
||||
{{/each}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{#if showResolvedBy}}
|
||||
<div class='flagged-post-resolved-by'>
|
||||
<div class='user-list-title'>
|
||||
{{i18n "admin.flags.resolved_by"}}
|
||||
</div>
|
||||
<div class='flag-users'>
|
||||
{{#each flaggedPost.post_actions as |postAction|}}
|
||||
{{#flag-user user=postAction.disposed_by date=postAction.disposed_at}}
|
||||
{{disposition-icon postAction.disposition}}
|
||||
{{#if postAction.staff_took_action}}
|
||||
{{d-icon "gavel" title="admin.flags.took_action"}}
|
||||
{{/if}}
|
||||
{{/flag-user}}
|
||||
{{/each}}
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
{{flag-user-lists flaggedPost=flaggedPost showResolvedBy=showResolvedBy}}
|
||||
|
||||
{{#if suspended}}
|
||||
<div class='suspended-message'>
|
||||
The user was suspended for this post.
|
||||
{{i18n "admin.flags.suspended_for_post"}}
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
@ -133,12 +98,14 @@
|
||||
|
||||
{{d-button
|
||||
class="defer-flag"
|
||||
title="admin.flags.defer_flag_title"
|
||||
title="admin.flags.ignore_flag_title"
|
||||
action="defer"
|
||||
icon="external-link"
|
||||
label="admin.flags.defer_flag"}}
|
||||
label="admin.flags.ignore_flag"}}
|
||||
|
||||
{{admin-delete-flag-dropdown post=flaggedPost removeAfter=(action "removeAfter")}}
|
||||
{{admin-delete-flag-dropdown
|
||||
post=flaggedPost
|
||||
removeAfter=(action "removeAfter")}}
|
||||
|
||||
{{#unless suspended}}
|
||||
{{d-button
|
||||
@ -158,6 +125,6 @@
|
||||
{{plugin-outlet
|
||||
name="flagged-post-below-controls"
|
||||
tagName=""
|
||||
args=(hash flaggedPost=flaggedPost canAct=canAct)}}
|
||||
args=(hash flaggedPost=flaggedPost canAct=canAct actableFilter=actableFilter)}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -5,6 +5,7 @@
|
||||
{{flagged-post
|
||||
flaggedPost=flaggedPost
|
||||
filter=filter
|
||||
topic=topic
|
||||
showResolvedBy=showResolvedBy
|
||||
removePost=(action "removePost" flaggedPost)
|
||||
hideTitle=topic}}
|
||||
|
||||
@ -0,0 +1,21 @@
|
||||
<div class='reason-controls'>
|
||||
<label>
|
||||
<div class='silence-reason-label'>
|
||||
{{{i18n 'admin.user.silence_reason_label'}}}
|
||||
</div>
|
||||
</label>
|
||||
{{text-field
|
||||
value=reason
|
||||
class="silence-reason"
|
||||
placeholderKey="admin.user.silence_reason_placeholder"}}
|
||||
</div>
|
||||
|
||||
<label>
|
||||
<div class='silence-message-label'>
|
||||
{{i18n "admin.user.silence_message"}}
|
||||
</div>
|
||||
</label>
|
||||
{{textarea
|
||||
value=message
|
||||
class="silence-message"
|
||||
placeholder=(i18n "admin.user.silence_message_placeholder")}}
|
||||
@ -0,0 +1,25 @@
|
||||
<div class='reason-controls'>
|
||||
<label>
|
||||
<div class='suspend-reason-label'>
|
||||
{{#if siteSettings.hide_suspension_reasons}}
|
||||
{{{i18n 'admin.user.suspend_reason_hidden_label'}}}
|
||||
{{else}}
|
||||
{{{i18n 'admin.user.suspend_reason_label'}}}
|
||||
{{/if}}
|
||||
</div>
|
||||
</label>
|
||||
{{text-field
|
||||
value=reason
|
||||
class="suspend-reason"
|
||||
placeholderKey="admin.user.suspend_reason_placeholder"}}
|
||||
</div>
|
||||
|
||||
<label>
|
||||
<div class='suspend-message-label'>
|
||||
{{i18n "admin.user.suspend_message"}}
|
||||
</div>
|
||||
</label>
|
||||
{{textarea
|
||||
value=message
|
||||
class="suspend-message"
|
||||
placeholder=(i18n "admin.user.suspend_message_placeholder")}}
|
||||
@ -224,7 +224,7 @@
|
||||
<td class="title">
|
||||
<div class="referred-topic-title">
|
||||
<div class="overflow-ellipsis">
|
||||
<a href="/t/{{unbound data.topic_slug}}/{{unbound data.topic_id}}">{{data.topic_title}}</a>
|
||||
<a href="{{unbound data.topic_url}}">{{data.topic_title}}</a>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
@ -0,0 +1,14 @@
|
||||
{{#d-modal-body rawTitle=(i18n "admin.user.flags_received_by" username=model.username)}}
|
||||
{{#conditional-loading-spinner condition=loadingFlags}}
|
||||
{{#each flaggedPosts as |flaggedPost|}}
|
||||
<div class='received-flag flagged-post'>
|
||||
<div class='flagged-post-excerpt'>
|
||||
{{flagged-post-title flaggedPost=flaggedPost}}
|
||||
</div>
|
||||
{{flag-user-lists flaggedPost=flaggedPost showResolvedBy=flaggedPost.hasDisposedBy}}
|
||||
</div>
|
||||
{{else}}
|
||||
{{i18n "admin.user.flags_received_none"}}
|
||||
{{/each}}
|
||||
{{/conditional-loading-spinner}}
|
||||
{{/d-modal-body}}
|
||||
@ -11,28 +11,7 @@
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class='reason-controls'>
|
||||
<label>
|
||||
<div class='silence-reason-label'>
|
||||
{{{i18n 'admin.user.silence_reason_label'}}}
|
||||
</div>
|
||||
</label>
|
||||
{{text-field
|
||||
value=reason
|
||||
class="silence-reason"
|
||||
placeholderKey="admin.user.silence_reason_placeholder"}}
|
||||
</div>
|
||||
|
||||
<label>
|
||||
<div class='silence-message-label'>
|
||||
{{i18n "admin.user.silence_message"}}
|
||||
</div>
|
||||
</label>
|
||||
{{textarea
|
||||
value=message
|
||||
class="silence-message"
|
||||
placeholder=(i18n "admin.user.silence_message_placeholder")}}
|
||||
|
||||
{{silence-details reason=reason message=message}}
|
||||
|
||||
{{/conditional-loading-spinner}}
|
||||
|
||||
@ -47,4 +26,4 @@
|
||||
label="admin.user.silence"}}
|
||||
{{d-modal-cancel close=(action "closeModal")}}
|
||||
{{conditional-loading-spinner condition=loading size="small"}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -11,32 +11,8 @@
|
||||
input=suspendUntil}}
|
||||
</label>
|
||||
</div>
|
||||
{{suspension-details reason=reason message=message}}
|
||||
|
||||
<div class='reason-controls'>
|
||||
<label>
|
||||
<div class='suspend-reason-label'>
|
||||
{{#if siteSettings.hide_suspension_reasons}}
|
||||
{{{i18n 'admin.user.suspend_reason_hidden_label'}}}
|
||||
{{else}}
|
||||
{{{i18n 'admin.user.suspend_reason_label'}}}
|
||||
{{/if}}
|
||||
</div>
|
||||
</label>
|
||||
{{text-field
|
||||
value=reason
|
||||
class="suspend-reason"
|
||||
placeholderKey="admin.user.suspend_reason_placeholder"}}
|
||||
</div>
|
||||
|
||||
<label>
|
||||
<div class='suspend-message-label'>
|
||||
{{i18n "admin.user.suspend_message"}}
|
||||
</div>
|
||||
</label>
|
||||
{{textarea
|
||||
value=message
|
||||
class="suspend-message"
|
||||
placeholder=(i18n "admin.user.suspend_message_placeholder")}}
|
||||
{{else}}
|
||||
<div class='cant-suspend'>
|
||||
{{i18n "admin.user.cant_suspend"}}
|
||||
|
||||
@ -10,4 +10,53 @@
|
||||
|
||||
{{#conditional-loading-spinner condition=refreshing}}
|
||||
{{admin-graph model=model}}
|
||||
|
||||
<br><br>
|
||||
<h2> {{i18n "admin.logs.search_logs.header_search_results"}} </h2>
|
||||
<br>
|
||||
|
||||
<div class='header-search-results'>
|
||||
{{#each model.search_result.posts as |result|}}
|
||||
<div class='fps-result'>
|
||||
<div class='author'>
|
||||
<a href={{result.userPath}} data-user-card="{{unbound result.username}}">
|
||||
{{avatar result imageSize="large"}}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class='fps-topic'>
|
||||
<div class='topic'>
|
||||
<a class='search-link' href='{{unbound result.url}}'>
|
||||
{{topic-status topic=result.topic disableActions=true}}<span class='topic-title'>{{#highlight-text highlight=term}}{{{unbound result.topic.fancyTitle}}}{{/highlight-text}}</span>
|
||||
</a>
|
||||
|
||||
<div class='search-category'>
|
||||
{{#if result.topic.category.parentCategory}}
|
||||
{{category-link result.topic.category.parentCategory}}
|
||||
{{/if}}
|
||||
{{category-link result.topic.category hideParent=true}}
|
||||
{{#each result.topic.tags as |tag|}}
|
||||
{{discourse-tag tag}}
|
||||
{{/each}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class='blurb container'>
|
||||
<span class='date'>
|
||||
{{format-age result.created_at}}
|
||||
{{#if result.blurb}}
|
||||
-
|
||||
{{/if}}
|
||||
</span>
|
||||
|
||||
{{#if result.blurb}}
|
||||
{{#highlight-text highlight=term}}
|
||||
{{{unbound result.blurb}}}
|
||||
{{/highlight-text}}
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{/each}}
|
||||
</div>
|
||||
{{/conditional-loading-spinner}}
|
||||
|
||||
@ -10,7 +10,7 @@
|
||||
<div class='admin-container user-badges'>
|
||||
<h2>{{i18n 'admin.badges.grant_badge'}}</h2>
|
||||
<br>
|
||||
{{#if noBadges}}
|
||||
{{#if noGrantableBadges}}
|
||||
<p>{{i18n 'admin.badges.no_badges'}}</p>
|
||||
{{else}}
|
||||
<form class="form-horizontal">
|
||||
@ -22,7 +22,7 @@
|
||||
<label>{{i18n 'admin.badges.reason'}}</label>
|
||||
{{input type="text" value=badgeReason}}<br><small>{{i18n 'admin.badges.reason_help'}}</small>
|
||||
</label>
|
||||
<button class='btn btn-primary' {{action "grantBadge" selectedBadgeId}}>{{i18n 'admin.badges.grant'}}</button>
|
||||
<button class='btn btn-primary' {{action "grantBadge"}}>{{i18n 'admin.badges.grant'}}</button>
|
||||
</form>
|
||||
{{/if}}
|
||||
|
||||
|
||||
@ -6,6 +6,7 @@
|
||||
{{i18n 'admin.user.show_public_profile'}}
|
||||
{{/link-to}}
|
||||
{{/if}}
|
||||
|
||||
{{#if model.can_view_action_logs}}
|
||||
{{d-button action="viewActionLogs" actionParam=model.username icon="list-alt" label="admin.user.action_logs"}}
|
||||
{{/if}}
|
||||
@ -466,7 +467,18 @@
|
||||
</div>
|
||||
<div class='display-row'>
|
||||
<div class='field'>{{i18n 'admin.user.flags_given_received_count'}}</div>
|
||||
<div class='value'>{{model.flags_given_count}} / {{model.flags_received_count}}</div>
|
||||
<div class='value'>
|
||||
{{model.flags_given_count}} / {{model.flags_received_count}}
|
||||
</div>
|
||||
<div class='controls'>
|
||||
{{#if model.flags_received_count}}
|
||||
{{d-button
|
||||
action=(action "showFlagsReceived")
|
||||
label="admin.user.show_flags_received"
|
||||
icon="flag"
|
||||
}}
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
<div class='display-row'>
|
||||
<div class='field'>{{i18n 'admin.user.private_topics_count'}}</div>
|
||||
|
||||
@ -60,9 +60,9 @@
|
||||
//= require ./discourse/models/user-action
|
||||
//= require ./discourse/models/draft
|
||||
//= require ./discourse/models/composer
|
||||
//= require ./discourse/models/user-badge
|
||||
//= require_tree ./discourse/mixins
|
||||
//= require ./discourse/models/invite
|
||||
//= require ./discourse/models/user-badge
|
||||
//= require ./discourse/controllers/discovery-sortable
|
||||
//= require ./discourse/controllers/navigation/default
|
||||
//= require ./discourse/components/edit-category-panel
|
||||
|
||||
@ -195,7 +195,8 @@ export function buildResolver(baseName) {
|
||||
findAdminTemplate(parsedName) {
|
||||
var decamelized = parsedName.fullNameWithoutType.decamelize();
|
||||
if (decamelized.indexOf('components') === 0) {
|
||||
const compTemplate = Ember.TEMPLATES['admin/templates/' + decamelized];
|
||||
let comPath = `admin/templates/${decamelized}`;
|
||||
const compTemplate = Ember.TEMPLATES[`javascripts/${comPath}`] || Ember.TEMPLATES[comPath];
|
||||
if (compTemplate) { return compTemplate; }
|
||||
}
|
||||
|
||||
|
||||
@ -1,152 +0,0 @@
|
||||
import { setting } from 'discourse/lib/computed';
|
||||
import computed from 'ember-addons/ember-computed-decorators';
|
||||
|
||||
var get = Ember.get;
|
||||
|
||||
export default Ember.Component.extend({
|
||||
classNameBindings: ['category::no-category', 'categories:has-drop', 'categoryStyle'],
|
||||
categoryStyle: setting('category_style'),
|
||||
expanded: false,
|
||||
|
||||
tagName: 'li',
|
||||
|
||||
@computed('expanded')
|
||||
expandIcon(expanded) {
|
||||
return expanded ? 'd-drop-expanded' : 'd-drop-collapsed';
|
||||
},
|
||||
|
||||
allCategoriesUrl: function() {
|
||||
if (this.get('subCategory')) {
|
||||
return this.get('parentCategory.url') || "/";
|
||||
} else {
|
||||
return "/";
|
||||
}
|
||||
}.property('parentCategory.url', 'subCategory'),
|
||||
|
||||
noCategoriesUrl: function() {
|
||||
return this.get('parentCategory.url') + "/none";
|
||||
}.property('parentCategory.url'),
|
||||
|
||||
allCategoriesLabel: function() {
|
||||
if (this.get('subCategory')) {
|
||||
return I18n.t('categories.all_subcategories', {categoryName: this.get('parentCategory.name')});
|
||||
}
|
||||
return I18n.t('categories.all');
|
||||
}.property('category'),
|
||||
|
||||
dropdownButtonClass: function() {
|
||||
let result = 'dropdown-header category-dropdown-button';
|
||||
if (Em.isNone(this.get('category'))) {
|
||||
result += ' home';
|
||||
}
|
||||
return result;
|
||||
}.property('category'),
|
||||
|
||||
categoryColor: function() {
|
||||
var category = this.get('category');
|
||||
|
||||
if (category) {
|
||||
var color = get(category, 'color');
|
||||
|
||||
if (color) {
|
||||
var style = "";
|
||||
if (color) { style += "background-color: #" + color + ";"; }
|
||||
return style.htmlSafe();
|
||||
}
|
||||
}
|
||||
|
||||
return "background-color: #eee;".htmlSafe();
|
||||
}.property('category'),
|
||||
|
||||
badgeStyle: function() {
|
||||
let category = this.get('category');
|
||||
|
||||
const categoryStyle = this.siteSettings.category_style;
|
||||
if (categoryStyle === 'bullet') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (category) {
|
||||
let color = get(category, 'color');
|
||||
let textColor = get(category, 'text_color');
|
||||
|
||||
if (color || textColor) {
|
||||
let style = "";
|
||||
if (color) {
|
||||
if (categoryStyle === "bar") {
|
||||
style += `border-color: #${color};`;
|
||||
} else if (categoryStyle === "box") {
|
||||
style += `background-color: #${color};`;
|
||||
if (textColor) { style += "color: #" + textColor + "; "; }
|
||||
}
|
||||
}
|
||||
|
||||
return style.htmlSafe();
|
||||
}
|
||||
}
|
||||
|
||||
if (categoryStyle === 'box') {
|
||||
return "background-color: #eee; color: #333".htmlSafe();
|
||||
}
|
||||
}.property('category'),
|
||||
|
||||
clickEventName: function() {
|
||||
return "click.category-drop-" + (this.get('category.id') || "all");
|
||||
}.property('category.id'),
|
||||
|
||||
actions: {
|
||||
expand: function() {
|
||||
var self = this;
|
||||
|
||||
if(!this.get('renderCategories')){
|
||||
this.set('renderCategories',true);
|
||||
Em.run.next(function(){
|
||||
self.send('expand');
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.get('expanded')) {
|
||||
this.close();
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.get('categories')) {
|
||||
this.set('expanded', true);
|
||||
}
|
||||
var $dropdown = this.$()[0];
|
||||
|
||||
this.$('a[data-drop-close]').on('click.category-drop', function() {
|
||||
self.close();
|
||||
});
|
||||
|
||||
Em.run.next(function(){
|
||||
self.$('.cat a').add('html').on(self.get('clickEventName'), function(e) {
|
||||
var $target = $(e.target),
|
||||
closest = $target.closest($dropdown);
|
||||
|
||||
if ($(e.currentTarget).hasClass('badge-wrapper')){
|
||||
self.close();
|
||||
}
|
||||
|
||||
return ($(e.currentTarget).hasClass('badge-category') || (closest.length && closest[0] === $dropdown)) ? true : self.close();
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
removeEvents: function(){
|
||||
$('html').off(this.get('clickEventName'));
|
||||
this.$('a[data-drop-close]').off('click.category-drop');
|
||||
},
|
||||
|
||||
close: function() {
|
||||
this.removeEvents();
|
||||
this.set('expanded', false);
|
||||
},
|
||||
|
||||
willDestroyElement: function() {
|
||||
this.removeEvents();
|
||||
}
|
||||
|
||||
});
|
||||
@ -13,6 +13,7 @@ import { tinyAvatar,
|
||||
displayErrorForUpload,
|
||||
getUploadMarkdown,
|
||||
validateUploadedFiles,
|
||||
authorizesOneOrMoreImageExtensions,
|
||||
formatUsername,
|
||||
clipboardData
|
||||
} from 'discourse/lib/utilities';
|
||||
@ -36,6 +37,12 @@ export default Ember.Component.extend({
|
||||
return `[${I18n.t('uploading')}]() `;
|
||||
},
|
||||
|
||||
@computed()
|
||||
replyPlaceholder() {
|
||||
const key = authorizesOneOrMoreImageExtensions() ? "reply_placeholder" : "reply_placeholder_no_images";
|
||||
return `composer.${key}`;
|
||||
},
|
||||
|
||||
@observes('composer.uploadCancelled')
|
||||
_cancelUpload() {
|
||||
if (!this.get('composer.uploadCancelled')) { return; }
|
||||
@ -662,13 +669,15 @@ export default Ember.Component.extend({
|
||||
unshift: true
|
||||
});
|
||||
|
||||
toolbar.addButton({
|
||||
id: 'upload',
|
||||
group: 'insertions',
|
||||
icon: 'upload',
|
||||
title: 'upload',
|
||||
sendAction: 'showUploadModal'
|
||||
});
|
||||
if (this.get('allowUpload')) {
|
||||
toolbar.addButton({
|
||||
id: 'upload',
|
||||
group: 'insertions',
|
||||
icon: 'upload',
|
||||
title: 'upload',
|
||||
sendAction: 'showUploadModal'
|
||||
});
|
||||
}
|
||||
|
||||
toolbar.addButton({
|
||||
id: 'options',
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
import LinkLookup from 'discourse/lib/link-lookup';
|
||||
|
||||
let _messagesCache = {};
|
||||
|
||||
export default Ember.Component.extend({
|
||||
classNameBindings: [':composer-popup-container', 'hidden'],
|
||||
checkedMessages: false,
|
||||
@ -165,7 +167,9 @@ export default Ember.Component.extend({
|
||||
if (topicId) { args.topic_id = topicId; }
|
||||
if (postId) { args.post_id = postId; }
|
||||
|
||||
composer.store.find('composer-message', args).then(messages => {
|
||||
const cacheKey = `${args.composer_action}${args.topic_id}${args.post_id}`;
|
||||
|
||||
const processMessages = messages => {
|
||||
if (this.isDestroying || this.isDestroyed) { return; }
|
||||
|
||||
// Checking composer messages on replies can give us a list of links to check for
|
||||
@ -177,6 +181,15 @@ export default Ember.Component.extend({
|
||||
this.set('checkedMessages', true);
|
||||
const queuedForTyping = this.get('queuedForTyping');
|
||||
messages.forEach(msg => msg.wait_for_typing ? queuedForTyping.addObject(msg) : this.send('popup', msg));
|
||||
});
|
||||
};
|
||||
|
||||
if (_messagesCache.cacheKey === cacheKey) {
|
||||
processMessages(_messagesCache.messages);
|
||||
} else {
|
||||
composer.store.find('composer-message', args).then(messages => {
|
||||
_messagesCache = {messages, cacheKey};
|
||||
processMessages(messages);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@ -2,6 +2,8 @@ import { getCustomHTML } from 'discourse/helpers/custom-html';
|
||||
import { getOwner } from 'discourse-common/lib/get-owner';
|
||||
|
||||
export default Ember.Component.extend({
|
||||
triggerAppEvent: null,
|
||||
|
||||
init() {
|
||||
this._super();
|
||||
const name = this.get('name');
|
||||
@ -16,5 +18,19 @@ export default Ember.Component.extend({
|
||||
this.set('layoutName', name);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
didInsertElement() {
|
||||
this._super();
|
||||
if (this.get('triggerAppEvent') === 'true') {
|
||||
this.appEvents.trigger(`inserted-custom-html:${this.get('name')}`);
|
||||
}
|
||||
},
|
||||
|
||||
willDestroyElement() {
|
||||
this._super();
|
||||
if (this.get('triggerAppEvent') === 'true') {
|
||||
this.appEvents.trigger(`destroyed-custom-html:${this.get('name')}`);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@ -254,6 +254,7 @@ export default Ember.Component.extend({
|
||||
@on('willDestroyElement')
|
||||
_shutDown() {
|
||||
if (this.get('composerEvents')) {
|
||||
this.appEvents.off('composer:insert-block');
|
||||
this.appEvents.off('composer:insert-text');
|
||||
this.appEvents.off('composer:replace-text');
|
||||
}
|
||||
@ -734,6 +735,11 @@ export default Ember.Component.extend({
|
||||
|
||||
showLinkModal() {
|
||||
this._lastSel = this._getSelected();
|
||||
|
||||
if (this._lastSel) {
|
||||
this.set("linkText", this._lastSel.value.trim());
|
||||
}
|
||||
|
||||
this.set('insertLinkHidden', false);
|
||||
},
|
||||
|
||||
|
||||
@ -4,6 +4,7 @@ import { emojiUrlFor } from "discourse/lib/text";
|
||||
import KeyValueStore from "discourse/lib/key-value-store";
|
||||
import { emojis } from "pretty-text/emoji/data";
|
||||
import { extendedEmojiList, isSkinTonableEmoji } from "pretty-text/emoji";
|
||||
const { run } = Ember;
|
||||
|
||||
const keyValueStore = new KeyValueStore("discourse_emojis_");
|
||||
const EMOJI_USAGE = "emojiUsage";
|
||||
@ -44,7 +45,7 @@ export default Ember.Component.extend({
|
||||
this.set("selectedDiversity", keyValueStore.getObject(EMOJI_SELECTED_DIVERSITY) || 1);
|
||||
this.set("recentEmojis", keyValueStore.getObject(EMOJI_USAGE) || []);
|
||||
|
||||
Ember.run.scheduleOnce("afterRender", this, function() {
|
||||
run.scheduleOnce("afterRender", this, function() {
|
||||
this._bindEvents();
|
||||
this._sectionLoadingCheck();
|
||||
this._loadCategoriesEmojis();
|
||||
@ -83,13 +84,14 @@ export default Ember.Component.extend({
|
||||
|
||||
@on("didUpdateAttrs")
|
||||
_setState() {
|
||||
this.get("active") === true ? this.show() : this.close();
|
||||
this.get("active") ? this.show() : this.close();
|
||||
},
|
||||
|
||||
@observes("filter")
|
||||
filterChanged() {
|
||||
this.$filter.find(".clear-filter").toggle(!_.isEmpty(this.get("filter")));
|
||||
Ember.run.debounce(this, this._filterEmojisList, 250);
|
||||
const filterDelay = this.site.isMobileDevice ? 400 : 250;
|
||||
run.debounce(this, this._filterEmojisList, filterDelay);
|
||||
},
|
||||
|
||||
@observes("selectedDiversity")
|
||||
@ -148,7 +150,7 @@ export default Ember.Component.extend({
|
||||
|
||||
_sectionLoadingCheck() {
|
||||
this._checkTimeout = setTimeout(() => { this._sectionLoadingCheck(); }, 500);
|
||||
Ember.run.throttle(this, this._checkVisibleSection, 100);
|
||||
run.throttle(this, this._checkVisibleSection, 100);
|
||||
},
|
||||
|
||||
_loadCategoriesEmojis() {
|
||||
@ -200,7 +202,7 @@ export default Ember.Component.extend({
|
||||
if (this.get("filter") === "") {
|
||||
this.$filter.find("input[name='filter']").val("");
|
||||
this.$results.empty().hide();
|
||||
this.$list.show();
|
||||
this.$list.css("visibility", "visible");
|
||||
} else {
|
||||
const lowerCaseFilter = this.get("filter").toLowerCase();
|
||||
const filterableEmojis = emojis.concat(_.keys(extendedEmojiList()));
|
||||
@ -217,7 +219,7 @@ export default Ember.Component.extend({
|
||||
).show();
|
||||
this._bindHover(this.$results);
|
||||
this._bindEmojiClick(this.$results);
|
||||
this.$list.hide();
|
||||
this.$list.css("visibility", "hidden");
|
||||
}
|
||||
},
|
||||
|
||||
@ -239,7 +241,7 @@ export default Ember.Component.extend({
|
||||
this.$picker.find(".category-icon").on("click", "button.emoji", (event) => {
|
||||
this.set("filter", "");
|
||||
this.$results.empty();
|
||||
this.$list.show();
|
||||
this.$list.css("visibility", "visible");
|
||||
|
||||
const section = $(event.currentTarget).data("section");
|
||||
const $section = this.$list.find(`.section[data-section="${section}"]`);
|
||||
@ -264,11 +266,11 @@ export default Ember.Component.extend({
|
||||
|
||||
_bindResizing() {
|
||||
this.$(window).on("resize", () => {
|
||||
Ember.run.throttle(this, this._positionPicker, 16);
|
||||
run.throttle(this, this._positionPicker, 16);
|
||||
});
|
||||
|
||||
$("#reply-control").on("div-resizing", () => {
|
||||
Ember.run.throttle(this, this._positionPicker, 16);
|
||||
run.throttle(this, this._positionPicker, 16);
|
||||
});
|
||||
},
|
||||
|
||||
@ -320,7 +322,7 @@ export default Ember.Component.extend({
|
||||
_bindSectionsScroll() {
|
||||
this.$list.on("scroll", () => {
|
||||
this.scrollPosition = this.$list.scrollTop();
|
||||
Ember.run.throttle(this, this._checkVisibleSection, 150);
|
||||
run.throttle(this, this._checkVisibleSection, 150);
|
||||
});
|
||||
},
|
||||
|
||||
@ -415,12 +417,12 @@ export default Ember.Component.extend({
|
||||
|
||||
const mobilePositioning = options => {
|
||||
let attributes = {
|
||||
width: windowWidth - 12,
|
||||
marginLeft: 5,
|
||||
marginTop: -130,
|
||||
width: windowWidth,
|
||||
marginLeft: 0,
|
||||
marginTop: "auto",
|
||||
left: 0,
|
||||
bottom: "",
|
||||
top: "50%",
|
||||
top: 0,
|
||||
display: "flex"
|
||||
};
|
||||
|
||||
@ -445,7 +447,7 @@ export default Ember.Component.extend({
|
||||
this.$picker.css(_.merge(attributes, options));
|
||||
};
|
||||
|
||||
if(Ember.testing || this.get("automaticPositioning") === false) {
|
||||
if(Ember.testing || !this.get("automaticPositioning")) {
|
||||
desktopPositioning();
|
||||
return;
|
||||
}
|
||||
@ -510,7 +512,7 @@ export default Ember.Component.extend({
|
||||
this.$list.scrollTop(yPosition);
|
||||
|
||||
// if we don’t actually scroll we need to force it
|
||||
if(yPosition === 0) {
|
||||
if (yPosition === 0) {
|
||||
this.$list.scroll();
|
||||
}
|
||||
},
|
||||
|
||||
@ -7,11 +7,13 @@ export default Ember.Component.extend(bufferedRender({
|
||||
rerenderTriggers: ['order', 'desc'],
|
||||
|
||||
buildBuffer(buffer) {
|
||||
buffer.push("<span class='header-contents'>");
|
||||
buffer.push(I18n.t(this.get('i18nKey')));
|
||||
|
||||
if (this.get('field') === this.get('order')) {
|
||||
buffer.push(iconHTML(this.get('desc') ? 'chevron-down' : 'chevron-up'));
|
||||
}
|
||||
buffer.push("</span>");
|
||||
},
|
||||
|
||||
click() {
|
||||
|
||||
@ -1,46 +0,0 @@
|
||||
import CleansUp from 'discourse/mixins/cleans-up';
|
||||
|
||||
export default Ember.Component.extend(CleansUp, {
|
||||
classNames: 'period-chooser',
|
||||
showPeriods: false,
|
||||
|
||||
cleanUp: function() {
|
||||
this.set('showPeriods', false);
|
||||
$('html').off('mousedown.top-period');
|
||||
},
|
||||
|
||||
_clickToClose: function() {
|
||||
const self = this;
|
||||
$('html').off('mousedown.top-period').on('mousedown.top-period', function(e) {
|
||||
const $target = $(e.target);
|
||||
if (($target.prop('id') === 'topic-entrance') || (self.$().has($target).length !== 0)) {
|
||||
return;
|
||||
}
|
||||
self.cleanUp();
|
||||
});
|
||||
},
|
||||
|
||||
click(e) {
|
||||
if ($(e.target).closest('.period-popup').length) { return; }
|
||||
|
||||
if (!this.get('showPeriods')) {
|
||||
if (!this.site.mobileView) {
|
||||
const $chevron = this.$('.d-icon-caret-down');
|
||||
this.$('#period-popup').css($chevron.position());
|
||||
} else {
|
||||
this.$('#period-popup').css({top: this.$().height()});
|
||||
}
|
||||
this.set('showPeriods', true);
|
||||
this._clickToClose();
|
||||
}
|
||||
},
|
||||
|
||||
actions: {
|
||||
changePeriod(p) {
|
||||
this.cleanUp();
|
||||
this.set('period', p);
|
||||
this.sendAction('action', p);
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
@ -139,7 +139,7 @@ const SiteHeaderComponent = MountWidget.extend(Docking, {
|
||||
if ($panelBody.height() !== contentHeight) {
|
||||
$panelBody.height(contentHeight);
|
||||
}
|
||||
$('body').addClass('drop-down-visible');
|
||||
$('body').addClass('drop-down-mode');
|
||||
} else {
|
||||
const menuTop = headerHeight();
|
||||
|
||||
@ -157,7 +157,7 @@ const SiteHeaderComponent = MountWidget.extend(Docking, {
|
||||
if (style.top !== menuTop + "px" || style.height !== height) {
|
||||
$panel.css({ top: menuTop + "px", height });
|
||||
}
|
||||
$('body').removeClass('drop-down-visible');
|
||||
$('body').removeClass('drop-down-mode');
|
||||
}
|
||||
|
||||
$panel.width(width);
|
||||
|
||||
@ -1,139 +0,0 @@
|
||||
import { setting } from 'discourse/lib/computed';
|
||||
import computed from 'ember-addons/ember-computed-decorators';
|
||||
|
||||
export default Ember.Component.extend({
|
||||
classNameBindings: [':tag-drop', 'tag::no-category', 'tags:has-drop','categoryStyle','tagClass'],
|
||||
categoryStyle: setting('category_style'), // match the category-drop style
|
||||
currentCategory: Em.computed.or('secondCategory', 'firstCategory'),
|
||||
showFilterByTag: setting('show_filter_by_tag'),
|
||||
showTagDropdown: Em.computed.and('showFilterByTag', 'tags'),
|
||||
tagId: null,
|
||||
|
||||
tagName: 'li',
|
||||
|
||||
@computed('site.top_tags')
|
||||
tags(topTags) {
|
||||
if (this.siteSettings.tags_sort_alphabetically && topTags) {
|
||||
return topTags.sort();
|
||||
} else {
|
||||
return topTags;
|
||||
}
|
||||
},
|
||||
|
||||
@computed('expanded')
|
||||
expandedIcon(expanded) {
|
||||
return expanded ? 'd-drop-expanded' : 'd-drop-collapsed';
|
||||
},
|
||||
|
||||
@computed('tagId')
|
||||
tagClass() {
|
||||
if (this.get('tagId')) {
|
||||
return "tag-" + this.get('tagId');
|
||||
} else {
|
||||
return "tag_all";
|
||||
}
|
||||
},
|
||||
|
||||
@computed('firstCategory', 'secondCategory')
|
||||
allTagsUrl() {
|
||||
if (this.get('currentCategory')) {
|
||||
return this.get('currentCategory.url') + "?allTags=1";
|
||||
} else {
|
||||
return "/";
|
||||
}
|
||||
},
|
||||
|
||||
@computed('tag')
|
||||
allTagsLabel() {
|
||||
return I18n.t("tagging.selector_all_tags");
|
||||
},
|
||||
|
||||
@computed('tagId')
|
||||
noTagsSelected() {
|
||||
return this.get('tagId') === 'none';
|
||||
},
|
||||
|
||||
@computed('firstCategory', 'secondCategory')
|
||||
noTagsUrl() {
|
||||
var url = '/tags';
|
||||
if (this.get('currentCategory')) {
|
||||
url += this.get('currentCategory.url');
|
||||
}
|
||||
return url + '/none';
|
||||
},
|
||||
|
||||
@computed('tag')
|
||||
noTagsLabel() {
|
||||
return I18n.t("tagging.selector_no_tags");
|
||||
},
|
||||
|
||||
@computed('tag')
|
||||
dropdownButtonClass() {
|
||||
let result = 'dropdown-header category-dropdown-button';
|
||||
if (Em.isNone(this.get('tag'))) {
|
||||
result += ' home';
|
||||
}
|
||||
return result;
|
||||
},
|
||||
|
||||
@computed('tag')
|
||||
clickEventName() {
|
||||
return "click.tag-drop-" + (this.get('tag') || "all");
|
||||
},
|
||||
|
||||
actions: {
|
||||
expand: function() {
|
||||
var self = this;
|
||||
|
||||
if(!this.get('renderTags')){
|
||||
this.set('renderTags',true);
|
||||
Em.run.next(function(){
|
||||
self.send('expand');
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.get('expanded')) {
|
||||
this.close();
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.get('tags')) {
|
||||
this.set('expanded', true);
|
||||
}
|
||||
var $dropdown = this.$()[0];
|
||||
|
||||
this.$('a[data-drop-close]').on('click.tag-drop', function() {
|
||||
self.close();
|
||||
});
|
||||
|
||||
Em.run.next(function(){
|
||||
self.$('.cat a').add('html').on(self.get('clickEventName'), function(e) {
|
||||
var $target = $(e.target),
|
||||
closest = $target.closest($dropdown);
|
||||
|
||||
if ($(e.currentTarget).hasClass('badge-wrapper')){
|
||||
self.close();
|
||||
}
|
||||
|
||||
return ($(e.currentTarget).hasClass('badge-category') || (closest.length && closest[0] === $dropdown)) ? true : self.close();
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
removeEvents: function(){
|
||||
$('html').off(this.get('clickEventName'));
|
||||
this.$('a[data-drop-close]').off('click.tag-drop');
|
||||
},
|
||||
|
||||
close: function() {
|
||||
this.removeEvents();
|
||||
this.set('expanded', false);
|
||||
},
|
||||
|
||||
willDestroyElement: function() {
|
||||
this.removeEvents();
|
||||
}
|
||||
|
||||
});
|
||||
@ -6,6 +6,16 @@ export default Ember.Component.extend({
|
||||
// Allow us to extend it
|
||||
layoutName: 'components/topic-footer-buttons',
|
||||
|
||||
@computed('topic.isPrivateMessage')
|
||||
canArchive(isPM) {
|
||||
return this.siteSettings.enable_private_messages && isPM;
|
||||
},
|
||||
|
||||
@computed('topic.isPrivateMessage')
|
||||
showNotificationsButton(isPM) {
|
||||
return (!isPM) || this.siteSettings.enable_private_messages;
|
||||
},
|
||||
|
||||
@computed('topic.details.can_invite_to')
|
||||
canInviteTo(result) {
|
||||
return !this.site.mobileView && result;
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
export default Ember.Component.extend({
|
||||
classNames: ['user-stat'],
|
||||
type: 'number',
|
||||
isNumber: Ember.computed.equal('type', 'number')
|
||||
isNumber: Ember.computed.equal('type', 'number'),
|
||||
isDuration: Ember.computed.equal('type', 'duration')
|
||||
});
|
||||
|
||||
@ -5,7 +5,7 @@ import Composer from 'discourse/models/composer';
|
||||
import { default as computed, observes, on } from 'ember-addons/ember-computed-decorators';
|
||||
import InputValidation from 'discourse/models/input-validation';
|
||||
import { getOwner } from 'discourse-common/lib/get-owner';
|
||||
import { escapeExpression } from 'discourse/lib/utilities';
|
||||
import { escapeExpression, authorizesOneOrMoreExtensions } from 'discourse/lib/utilities';
|
||||
import { emojiUnescape } from 'discourse/lib/text';
|
||||
import { shortDate } from 'discourse/lib/formatter';
|
||||
|
||||
@ -70,6 +70,7 @@ export default Ember.Controller.extend({
|
||||
scopedCategoryId: null,
|
||||
lastValidatedAt: null,
|
||||
isUploading: false,
|
||||
allowUpload: false,
|
||||
topic: null,
|
||||
linkLookup: null,
|
||||
showPreview: true,
|
||||
@ -223,6 +224,11 @@ export default Ember.Controller.extend({
|
||||
return emojiUnescape(escapeExpression(topic.get('title')));
|
||||
},
|
||||
|
||||
@computed
|
||||
allowUpload() {
|
||||
return authorizesOneOrMoreExtensions();
|
||||
},
|
||||
|
||||
actions: {
|
||||
cancelUpload() {
|
||||
this.set('model.uploadCancelled', true);
|
||||
|
||||
@ -169,7 +169,7 @@ export default Ember.Controller.extend({
|
||||
|
||||
@computed('expanded', 'model.grouped_search_result.can_create_topic')
|
||||
canCreateTopic(expanded, userCanCreateTopic) {
|
||||
return this.currentUser && userCanCreateTopic && !this.site.mobileView && !expanded;
|
||||
return this.currentUser && userCanCreateTopic && !expanded;
|
||||
},
|
||||
|
||||
@computed('expanded')
|
||||
@ -231,6 +231,7 @@ export default Ember.Controller.extend({
|
||||
}
|
||||
}else{
|
||||
setTransient('lastSearch', { searchKey, model }, 5);
|
||||
model.grouped_search_result = results.grouped_search_result;
|
||||
this.set("model", model);
|
||||
}
|
||||
}).finally(() => {
|
||||
|
||||
@ -0,0 +1,63 @@
|
||||
import computed from "ember-addons/ember-computed-decorators";
|
||||
import { extractError } from 'discourse/lib/ajax-error';
|
||||
import ModalFunctionality from "discourse/mixins/modal-functionality";
|
||||
import GrantBadgeController from "discourse/mixins/grant-badge-controller";
|
||||
import Badge from 'discourse/models/badge';
|
||||
import UserBadge from 'discourse/models/user-badge';
|
||||
|
||||
export default Ember.Controller.extend(ModalFunctionality, GrantBadgeController, {
|
||||
topicController: Ember.inject.controller("topic"),
|
||||
loading: true,
|
||||
saving: false,
|
||||
selectedBadgeId: null,
|
||||
allBadges: [],
|
||||
userBadges: [],
|
||||
|
||||
@computed('topicController.selectedPosts')
|
||||
post() {
|
||||
return this.get('topicController.selectedPosts')[0];
|
||||
},
|
||||
|
||||
@computed('post')
|
||||
badgeReason(post) {
|
||||
const url = post.get('url');
|
||||
const protocolAndHost = window.location.protocol + '//' + window.location.host;
|
||||
|
||||
return url.indexOf('/') === 0 ? protocolAndHost + url : url;
|
||||
},
|
||||
|
||||
@computed("saving", "selectedBadgeGrantable")
|
||||
buttonDisabled(saving, selectedBadgeGrantable) {
|
||||
return saving || !selectedBadgeGrantable;
|
||||
},
|
||||
|
||||
onShow() {
|
||||
this.set('loading', true);
|
||||
|
||||
Ember.RSVP.all([Badge.findAll(), UserBadge.findByUsername(this.get('post.username'))])
|
||||
.then(([allBadges, userBadges]) => {
|
||||
this.setProperties({
|
||||
'allBadges': allBadges,
|
||||
'userBadges': userBadges,
|
||||
'loading': false,
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
actions: {
|
||||
grantBadge() {
|
||||
this.set('saving', true);
|
||||
|
||||
this.grantBadge(this.get('selectedBadgeId'), this.get('post.username'), this.get('badgeReason'))
|
||||
.then(newBadge => {
|
||||
this.set('selectedBadgeId', null);
|
||||
this.flash(I18n.t(
|
||||
'badges.successfully_granted', { username: this.get('post.username'), badge: newBadge.get('badge.name') }
|
||||
), 'success');
|
||||
}, error => {
|
||||
this.flash(extractError(error), 'error');
|
||||
})
|
||||
.finally(() => this.set('saving', false));
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -24,7 +24,7 @@ export default Ember.Controller.extend({
|
||||
|
||||
group.findPosts(opts).then(newPosts => {
|
||||
posts.addObjects(newPosts);
|
||||
if(newPosts.length === 0) {
|
||||
if (newPosts.length === 0) {
|
||||
this.set('canLoadMore', false);
|
||||
}
|
||||
}).finally(() => {
|
||||
|
||||
@ -217,7 +217,7 @@ export default Ember.Controller.extend(ModalFunctionality, {
|
||||
|
||||
// Reload the page if we're authenticated
|
||||
if (options.authenticated) {
|
||||
const destinationUrl = $.cookie('destination_url');
|
||||
const destinationUrl = $.cookie('destination_url') || options.destination_url;
|
||||
if (destinationUrl) {
|
||||
// redirect client to the original URL
|
||||
$.cookie('destination_url', null);
|
||||
|
||||
@ -1,7 +1,20 @@
|
||||
import computed from 'ember-addons/ember-computed-decorators';
|
||||
|
||||
export default Ember.Controller.extend({
|
||||
sortProperties: ['count:desc', 'id'],
|
||||
|
||||
canAdminTags: Ember.computed.alias("currentUser.staff"),
|
||||
groupedByCategory: Ember.computed.notEmpty('model.extras.categories'),
|
||||
groupedByTagGroup: Ember.computed.notEmpty('model.extras.tag_groups'),
|
||||
|
||||
@computed('groupedByCategory', 'groupedByTagGroup')
|
||||
otherTagsTitleKey(groupedByCategory, groupedByTagGroup) {
|
||||
if (!groupedByCategory && !groupedByTagGroup) {
|
||||
return 'tagging.all_tags';
|
||||
} else {
|
||||
return 'tagging.other_tags';
|
||||
}
|
||||
},
|
||||
|
||||
actions: {
|
||||
sortByCount() {
|
||||
|
||||
@ -119,8 +119,8 @@ export default Ember.Controller.extend(BulkTopicSelection, {
|
||||
|
||||
deleteTag() {
|
||||
const self = this;
|
||||
const topicsLength = this.get('list.topic_list.topics.length');
|
||||
const confirmText = topicsLength === 0 ? I18n.t("tagging.delete_confirm_no_topics") : I18n.t("tagging.delete_confirm", {count: topicsLength});
|
||||
const numTopics = this.get('list.topic_list.tags.firstObject.topic_count') || 0;
|
||||
const confirmText = numTopics === 0 ? I18n.t("tagging.delete_confirm_no_topics") : I18n.t("tagging.delete_confirm", {count: numTopics});
|
||||
bootbox.confirm(confirmText, function(result) {
|
||||
if (!result) { return; }
|
||||
|
||||
|
||||
@ -518,6 +518,19 @@ export default Ember.Controller.extend(BufferedContent, {
|
||||
this.send('changeOwner');
|
||||
},
|
||||
|
||||
lockPost(post) {
|
||||
return post.updatePostField('locked', true);
|
||||
},
|
||||
|
||||
unlockPost(post) {
|
||||
return post.updatePostField('locked', false);
|
||||
},
|
||||
|
||||
grantBadge(post) {
|
||||
this.set("selectedPostIds", [post.id]);
|
||||
this.send('showGrantBadgeModal');
|
||||
},
|
||||
|
||||
toggleParticipant(user) {
|
||||
this.get("model.postStream")
|
||||
.toggleParticipant(user.get("username"))
|
||||
@ -767,9 +780,12 @@ export default Ember.Controller.extend(BufferedContent, {
|
||||
return selectedPostsCount > 0 && (selectedAllPosts || selectedPosts.every(p => p.can_delete));
|
||||
},
|
||||
|
||||
@computed('canMergeTopic', 'selectedAllPosts')
|
||||
canSplitTopic(canMergeTopic, selectedAllPosts) {
|
||||
return canMergeTopic && !selectedAllPosts;
|
||||
@computed('canMergeTopic', 'selectedAllPosts', 'selectedPosts', 'selectedPosts.[]')
|
||||
canSplitTopic(canMergeTopic, selectedAllPosts, selectedPosts) {
|
||||
return canMergeTopic &&
|
||||
!selectedAllPosts &&
|
||||
selectedPosts.length > 0 &&
|
||||
selectedPosts.sort((a, b) => a.post_number - b.post_number)[0].post_type === 1;
|
||||
},
|
||||
|
||||
@computed('model.details.can_move_posts', 'selectedPostsCount')
|
||||
|
||||
@ -1,12 +1,14 @@
|
||||
import CanCheckEmails from 'discourse/mixins/can-check-emails';
|
||||
import computed from 'ember-addons/ember-computed-decorators';
|
||||
import User from 'discourse/models/user';
|
||||
import optionalService from 'discourse/lib/optional-service';
|
||||
|
||||
export default Ember.Controller.extend(CanCheckEmails, {
|
||||
indexStream: false,
|
||||
application: Ember.inject.controller(),
|
||||
userNotifications: Ember.inject.controller('user-notifications'),
|
||||
currentPath: Ember.computed.alias('application.currentPath'),
|
||||
adminTools: optionalService(),
|
||||
|
||||
@computed("content.username")
|
||||
viewingSelf(username) {
|
||||
@ -93,11 +95,15 @@ export default Ember.Controller.extend(CanCheckEmails, {
|
||||
this.set('forceExpand', true);
|
||||
},
|
||||
|
||||
adminDelete() {
|
||||
// I really want this deferred, don't want to bring in all this code till used
|
||||
const AdminUser = requirejs('admin/models/admin-user').default;
|
||||
AdminUser.find(this.get('model.id')).then(user => user.destroy({deletePosts: true}));
|
||||
showSuspensions() {
|
||||
this.get('adminTools').showActionLogs(this, {
|
||||
target_user: this.get('model.username'),
|
||||
action_name: 'suspend_user'
|
||||
});
|
||||
},
|
||||
|
||||
adminDelete() {
|
||||
this.get('adminTools').deleteUser(this.get('model.id'));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@ -67,9 +67,9 @@ export function categoryBadgeHTML(category, opts) {
|
||||
let categoryName = escapeExpression(get(category, 'name'));
|
||||
|
||||
if (restricted) {
|
||||
html += iconHTML('lock') + " " + categoryName;
|
||||
html += `${iconHTML('lock')}<span>${categoryName}</span>`;
|
||||
} else {
|
||||
html += categoryName;
|
||||
html += `<span>${categoryName}</span>`;
|
||||
}
|
||||
html += "</span>";
|
||||
|
||||
|
||||
@ -4,31 +4,21 @@ export default {
|
||||
name: 'localization',
|
||||
after: 'inject-objects',
|
||||
|
||||
enableVerboseLocalization() {
|
||||
let counter = 0;
|
||||
let keys = {};
|
||||
let t = I18n.t;
|
||||
|
||||
I18n.noFallbacks = true;
|
||||
isVerboseLocalizationEnabled(container) {
|
||||
const siteSettings = container.lookup('site-settings:main');
|
||||
if (siteSettings.verbose_localization) return true;
|
||||
|
||||
I18n.t = I18n.translate = function(scope, value){
|
||||
let current = keys[scope];
|
||||
if (!current) {
|
||||
current = keys[scope] = ++counter;
|
||||
let message = "Translation #" + current + ": " + scope;
|
||||
if (!_.isEmpty(value)) {
|
||||
message += ", parameters: " + JSON.stringify(value);
|
||||
}
|
||||
Em.Logger.info(message);
|
||||
}
|
||||
return t.apply(I18n, [scope, value]) + " (#" + current + ")";
|
||||
};
|
||||
try {
|
||||
return sessionStorage && sessionStorage.getItem("verbose_localization");
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
initialize(container) {
|
||||
const siteSettings = container.lookup('site-settings:main');
|
||||
if (siteSettings.verbose_localization) {
|
||||
this.enableVerboseLocalization();
|
||||
if (this.isVerboseLocalizationEnabled(container)) {
|
||||
I18n.enableVerboseLocalization();
|
||||
}
|
||||
|
||||
// Merge any overrides into our object
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { cleanDOM } from 'discourse/lib/clean-dom';
|
||||
import { startPageTracking } from 'discourse/lib/page-tracker';
|
||||
import { startPageTracking, googleTagManagerPageChanged } from 'discourse/lib/page-tracker';
|
||||
import { viewTrackingRequired } from 'discourse/lib/ajax';
|
||||
|
||||
export default {
|
||||
@ -35,15 +35,7 @@ export default {
|
||||
|
||||
// And Google Tag Manager too
|
||||
if (typeof window.dataLayer !== 'undefined') {
|
||||
appEvents.on('page:changed', data => {
|
||||
window.dataLayer.push({
|
||||
'event': 'virtualPageView',
|
||||
'page': {
|
||||
'title': data.title,
|
||||
'url': data.url
|
||||
}
|
||||
});
|
||||
});
|
||||
appEvents.on('page:changed', googleTagManagerPageChanged);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@ -55,4 +55,8 @@ export function throwAjaxError(undoCallback) {
|
||||
|
||||
export function popupAjaxError(error) {
|
||||
bootbox.alert(extractError(error));
|
||||
|
||||
error._discourse_displayed = true;
|
||||
// We re-throw in a catch to not swallow the exception
|
||||
throw error;
|
||||
}
|
||||
|
||||
@ -276,7 +276,6 @@ export default function(options) {
|
||||
}
|
||||
|
||||
if (Discourse.Site.currentProp('mobileView')) {
|
||||
div.css('width', 'auto');
|
||||
|
||||
if ((me.height() / 2) >= pos.top) { vOffset = -23; }
|
||||
if ((me.width() / 2) <= pos.left) { hOffset = -div.width(); }
|
||||
|
||||
@ -129,7 +129,8 @@ export default {
|
||||
}
|
||||
|
||||
// Otherwise, use a custom URL with a redirect
|
||||
if (Discourse.User.currentProp('external_links_in_new_tab')) {
|
||||
// consider CTRL+mouse-left-click / CMD+mouse-left-click or mouse-middle-click as well
|
||||
if (Discourse.User.currentProp('external_links_in_new_tab') || ((e.ctrlKey || e.metaKey) && (e.which === 1)) || (e.which === 2)) {
|
||||
window.open(destUrl, '_blank').focus();
|
||||
} else {
|
||||
DiscourseURL.redirectTo(destUrl);
|
||||
|
||||
@ -12,6 +12,7 @@ const bindings = {
|
||||
'.': {click: '.alert.alert-info.clickable', anonymous: true}, // show incoming/updated topics
|
||||
'b': {handler: 'toggleBookmark'},
|
||||
'c': {handler: 'createTopic'},
|
||||
'C': {handler: 'focusComposer'},
|
||||
'ctrl+f': {handler: 'showPageSearch', anonymous: true},
|
||||
'command+f': {handler: 'showPageSearch', anonymous: true},
|
||||
'ctrl+p': {handler: 'printTopic', anonymous: true},
|
||||
@ -67,6 +68,12 @@ export default {
|
||||
this.searchService = this.container.lookup('search-service:main');
|
||||
this.appEvents = this.container.lookup('app-events:main');
|
||||
this.currentUser = this.container.lookup('current-user:main');
|
||||
let siteSettings = this.container.lookup('site-settings:main');
|
||||
|
||||
// Disable the shortcut if private messages are disabled
|
||||
if (!siteSettings.enable_private_messages) {
|
||||
delete bindings['g m'];
|
||||
}
|
||||
|
||||
Object.keys(bindings).forEach(key => {
|
||||
const binding = bindings[key];
|
||||
@ -174,6 +181,15 @@ export default {
|
||||
}
|
||||
},
|
||||
|
||||
focusComposer() {
|
||||
const composer = this.container.lookup('controller:composer');
|
||||
if (composer.get('model.viewOpen')) {
|
||||
setTimeout(() => $('textarea.d-editor-input').focus(), 0);
|
||||
} else {
|
||||
composer.send('openIfDraft');
|
||||
}
|
||||
},
|
||||
|
||||
pinUnpinTopic() {
|
||||
this.container.lookup('controller:topic').togglePinnedState();
|
||||
},
|
||||
|
||||
@ -38,3 +38,23 @@ export function startPageTracking(router, appEvents) {
|
||||
});
|
||||
_started = true;
|
||||
}
|
||||
|
||||
const _gtmPageChangedCallbacks = [];
|
||||
|
||||
export function addGTMPageChangedCallback(callback) {
|
||||
_gtmPageChangedCallbacks.push(callback);
|
||||
}
|
||||
|
||||
export function googleTagManagerPageChanged(data) {
|
||||
let gtmData = {
|
||||
'event': 'virtualPageView',
|
||||
'page': {
|
||||
'title': data.title,
|
||||
'url': data.url
|
||||
}
|
||||
};
|
||||
|
||||
_.each(_gtmPageChangedCallbacks, callback => callback(gtmData));
|
||||
|
||||
window.dataLayer.push(gtmData);
|
||||
}
|
||||
|
||||
@ -22,9 +22,10 @@ import { registerIconRenderer, replaceIcon } from 'discourse-common/lib/icon-lib
|
||||
import { addNavItem } from 'discourse/models/nav-item';
|
||||
import { replaceFormatter } from 'discourse/lib/utilities';
|
||||
import { modifySelectKit } from "select-kit/mixins/plugin-api";
|
||||
import { addGTMPageChangedCallback } from 'discourse/lib/page-tracker';
|
||||
|
||||
// If you add any methods to the API ensure you bump up this number
|
||||
const PLUGIN_API_VERSION = '0.8.13';
|
||||
const PLUGIN_API_VERSION = '0.8.17';
|
||||
|
||||
class PluginApi {
|
||||
constructor(version, container) {
|
||||
@ -350,10 +351,24 @@ class PluginApi {
|
||||
```
|
||||
**/
|
||||
onPageChange(fn) {
|
||||
let appEvents = this.container.lookup('app-events:main');
|
||||
appEvents.on('page:changed', data => fn(data.url, data.title));
|
||||
this.onAppEvent('page:changed', data => fn(data.url, data.title));
|
||||
}
|
||||
|
||||
/**
|
||||
Listen for a triggered `AppEvent` from Discourse.
|
||||
|
||||
```javascript
|
||||
api.onAppEvent('inserted-custom-html', () => {
|
||||
console.log('a custom footer was rendered');
|
||||
});
|
||||
```
|
||||
**/
|
||||
onAppEvent(name, fn) {
|
||||
let appEvents = this.container.lookup('app-events:main');
|
||||
appEvents.on(name, fn);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Changes a setting associated with a widget. For example, if
|
||||
* you wanted small avatars in the post stream:
|
||||
@ -605,6 +620,20 @@ class PluginApi {
|
||||
modifySelectKit(pluginApiKey) {
|
||||
return modifySelectKit(pluginApiKey);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Registers a function that can inspect and modify the data that
|
||||
* will be sent to Google Tag Manager when a page changed event is triggered.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* addGTMPageChangedCallback( gtmData => gtmData.locale = I18n.currentLocale() )
|
||||
*
|
||||
*/
|
||||
addGTMPageChangedCallback(fn) {
|
||||
addGTMPageChangedCallback(fn);
|
||||
}
|
||||
}
|
||||
|
||||
let _pluginv01;
|
||||
|
||||
@ -66,6 +66,7 @@ export default class {
|
||||
this._totalTimings = {};
|
||||
this._topicTime = 0;
|
||||
this._onscreen = [];
|
||||
this._inProgress = false;
|
||||
}
|
||||
|
||||
scrolled() {
|
||||
@ -108,6 +109,7 @@ export default class {
|
||||
|
||||
if (!$.isEmptyObject(newTimings)) {
|
||||
if (this.currentUser) {
|
||||
this._inProgress = true;
|
||||
ajax('/topics/timings', {
|
||||
data: {
|
||||
timings: newTimings,
|
||||
@ -128,6 +130,9 @@ export default class {
|
||||
}).catch(e => {
|
||||
const error = e.jqXHR;
|
||||
if (error.status === 405 && error.responseJSON.error_type === "read_only") return;
|
||||
}).finally(() => {
|
||||
this._inProgress = false;
|
||||
this._lastFlush = 0;
|
||||
});
|
||||
} else if (this._anonCallback) {
|
||||
// Anonymous viewer - save to localStorage
|
||||
@ -182,7 +187,7 @@ export default class {
|
||||
return timings[postNumber] > 0 && !totalTimings[postNumber];
|
||||
});
|
||||
|
||||
if (this._lastFlush > nextFlush || rush) {
|
||||
if (!this._inProgress && (this._lastFlush > nextFlush || rush)) {
|
||||
this.flush();
|
||||
}
|
||||
|
||||
|
||||
@ -2,7 +2,7 @@ import parseHTML from 'discourse/helpers/parse-html';
|
||||
|
||||
const trimLeft = text => text.replace(/^\s+/,"");
|
||||
const trimRight = text => text.replace(/\s+$/,"");
|
||||
const countPipes = text => text.replace(/\\\|/,"").match(/\|/g).length;
|
||||
const countPipes = text => (text.replace(/\\\|/,"").match(/\|/g) || []).length;
|
||||
|
||||
class Tag {
|
||||
constructor(name, prefix = "", suffix = "", inline = false) {
|
||||
@ -189,8 +189,14 @@ class Tag {
|
||||
toMarkdown() {
|
||||
const text = this.element.innerMarkdown().trim();
|
||||
|
||||
if (text.includes("\n")) {
|
||||
throw "Unsupported format inside Markdown table cells";
|
||||
if(text.includes("\n")) { // Unsupported format inside Markdown table cells
|
||||
let e = this.element;
|
||||
while(e = e.parent) {
|
||||
if (e.name === "table") {
|
||||
e.tag().invalid();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return this.decorate(text);
|
||||
@ -242,21 +248,38 @@ class Tag {
|
||||
|
||||
static table() {
|
||||
return class extends Tag.block("table") {
|
||||
constructor() {
|
||||
super();
|
||||
this.isValid = true;
|
||||
}
|
||||
|
||||
invalid() {
|
||||
this.isValid = false;
|
||||
if (this.element.parentNames.includes("table")) {
|
||||
let e = this.element;
|
||||
while(e = e.parent) {
|
||||
if (e.name === "table") {
|
||||
e.tag().invalid();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
decorate(text) {
|
||||
text = super.decorate(text).replace(/\|\n{2,}\|/g, "|\n|");
|
||||
const rows = text.trim().split("\n");
|
||||
const pipeCount = countPipes(rows[0]);
|
||||
const isValid = rows.length > 1 &&
|
||||
pipeCount > 2 &&
|
||||
rows.reduce((a, c) => a && countPipes(c) <= pipeCount);
|
||||
this.isValid = this.isValid && rows.length > 1 && pipeCount > 2 && rows.reduce((a, c) => a && countPipes(c) <= pipeCount); // Unsupported table format for Markdown conversion
|
||||
|
||||
if (!isValid) {
|
||||
throw "Unsupported table format for Markdown conversion";
|
||||
if (this.isValid) {
|
||||
const splitterRow = [...Array(pipeCount-1)].map(() => "| --- ").join("") + "|\n";
|
||||
text = text.replace("|\n", "|\n" + splitterRow);
|
||||
} else {
|
||||
text = text.replace(/\|/g, " ");
|
||||
this.invalid();
|
||||
}
|
||||
|
||||
const splitterRow = [...Array(pipeCount-1)].map(() => "| --- ").join("") + "|\n";
|
||||
text = text.replace("|\n", "|\n" + splitterRow);
|
||||
|
||||
return text;
|
||||
}
|
||||
};
|
||||
|
||||
@ -77,6 +77,7 @@ export function transformBasicPost(post) {
|
||||
cooked_hidden: !!post.cooked_hidden,
|
||||
expandablePost: false,
|
||||
replyCount: post.reply_count,
|
||||
locked: post.locked
|
||||
};
|
||||
|
||||
_additionalAttributes.forEach(a => postAtts[a] = post[a]);
|
||||
|
||||
@ -193,6 +193,8 @@ export function validateUploadedFiles(files, opts) {
|
||||
}
|
||||
|
||||
export function validateUploadedFile(file, opts) {
|
||||
if (!authorizesOneOrMoreExtensions()) return false;
|
||||
|
||||
opts = opts || {};
|
||||
|
||||
const name = file && file.name;
|
||||
@ -277,6 +279,21 @@ export function authorizesAllExtensions() {
|
||||
return Discourse.SiteSettings.authorized_extensions.indexOf("*") >= 0;
|
||||
}
|
||||
|
||||
export function authorizesOneOrMoreExtensions() {
|
||||
if (authorizesAllExtensions()) return true;
|
||||
|
||||
return Discourse.SiteSettings.authorized_extensions
|
||||
.split("|")
|
||||
.filter(ext => ext)
|
||||
.length > 0;
|
||||
}
|
||||
|
||||
export function authorizesOneOrMoreImageExtensions() {
|
||||
if (authorizesAllExtensions()) return true;
|
||||
|
||||
return imagesExtensions().length > 0;
|
||||
}
|
||||
|
||||
export function isAnImage(path) {
|
||||
return (/\.(png|jpe?g|gif|bmp|tiff?|svg|webp|ico)$/i).test(path);
|
||||
}
|
||||
@ -291,13 +308,13 @@ function isGUID(value) {
|
||||
|
||||
function imageNameFromFileName(fileName) {
|
||||
const split = fileName.split('.');
|
||||
const name = split[split.length-2];
|
||||
let name = split[split.length - 2];
|
||||
|
||||
if (exports.isAppleDevice() && isGUID(name)) {
|
||||
return I18n.t('upload_selector.default_image_alt_text');
|
||||
name = I18n.t('upload_selector.default_image_alt_text');
|
||||
}
|
||||
|
||||
return name;
|
||||
return encodeURIComponent(name);
|
||||
}
|
||||
|
||||
export function allowsImages() {
|
||||
@ -450,5 +467,25 @@ export function clipboardData(e, canUpload) {
|
||||
return { clipboard, types, canUpload, canPasteHtml };
|
||||
}
|
||||
|
||||
export function fillMissingDates(data, startDate, endDate) {
|
||||
const startMoment = moment(startDate, "YYYY-MM-DD");
|
||||
const endMoment = moment(endDate, "YYYY-MM-DD");
|
||||
const countDays = endMoment.diff(startMoment, 'days');
|
||||
let currentMoment = startMoment;
|
||||
|
||||
for (let i = 0; i <= countDays; i++) {
|
||||
let date = (data[i]) ? moment(data[i].x, "YYYY-MM-DD") : null;
|
||||
if (i === 0 && date.isAfter(startMoment)) {
|
||||
data.splice(i, 0, { "x" : startMoment.format("YYYY-MM-DD"), 'y': 0 });
|
||||
} else {
|
||||
if (!date || date.isAfter(moment(currentMoment))) {
|
||||
data.splice(i, 0, { "x" : currentMoment, 'y': 0 });
|
||||
}
|
||||
}
|
||||
currentMoment = moment(currentMoment).add(1, "day").format("YYYY-MM-DD");
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
// This prevents a mini racer crash
|
||||
export default {};
|
||||
|
||||
@ -0,0 +1,38 @@
|
||||
import computed from "ember-addons/ember-computed-decorators";
|
||||
import UserBadge from 'discourse/models/user-badge';
|
||||
|
||||
export default Ember.Mixin.create({
|
||||
@computed('allBadges.[]', 'userBadges.[]')
|
||||
grantableBadges(allBadges, userBadges) {
|
||||
const granted = userBadges.reduce((map, badge) => {
|
||||
map[badge.get('badge_id')] = true;
|
||||
return map;
|
||||
}, {});
|
||||
|
||||
return allBadges
|
||||
.filter(badge => {
|
||||
return badge.get('enabled')
|
||||
&& badge.get('manually_grantable')
|
||||
&& (!granted[badge.get('id')] || badge.get('multiple_grant'));
|
||||
})
|
||||
.sort((a, b) => a.get('name').localeCompare(b.get('name')));
|
||||
},
|
||||
|
||||
noGrantableBadges: Ember.computed.empty('grantableBadges'),
|
||||
|
||||
@computed('selectedBadgeId', 'grantableBadges')
|
||||
selectedBadgeGrantable(selectedBadgeId, grantableBadges) {
|
||||
return grantableBadges && grantableBadges.find(badge => badge.get('id') === selectedBadgeId);
|
||||
},
|
||||
|
||||
grantBadge(selectedBadgeId, username, badgeReason) {
|
||||
return UserBadge.grant(selectedBadgeId, username, badgeReason)
|
||||
.then(newBadge => {
|
||||
this.get('userBadges').pushObject(newBadge);
|
||||
return newBadge;
|
||||
}, error => {
|
||||
throw error;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@ -53,7 +53,7 @@ export default Ember.Object.extend({
|
||||
|
||||
results.forEach(result => {
|
||||
result.title = I18n.t(`topic_statuses.${result.key}.help`);
|
||||
if (!self.disableActions && (result.key === "pinned" ||result.key === "unpinned")) {
|
||||
if (this.currentUser && (result.key === "pinned" || result.key === "unpinned")) {
|
||||
result.openTag = 'a href';
|
||||
result.closeTag = 'a';
|
||||
} else {
|
||||
|
||||
@ -30,20 +30,9 @@ export default Discourse.Route.extend({
|
||||
afterModel(model, transition) {
|
||||
const username = transition.queryParams && transition.queryParams.username;
|
||||
|
||||
const userBadgesGrant = UserBadge.findByBadgeId(model.get("id"), {username}).then(userBadges => {
|
||||
return UserBadge.findByBadgeId(model.get("id"), {username}).then(userBadges => {
|
||||
this.userBadgesGrant = userBadges;
|
||||
});
|
||||
|
||||
const userBadgesAll = UserBadge.findByUsername(username).then(userBadges => {
|
||||
this.userBadgesAll = userBadges;
|
||||
});
|
||||
|
||||
const promises = {
|
||||
userBadgesGrant,
|
||||
userBadgesAll,
|
||||
};
|
||||
|
||||
return Ember.RSVP.hash(promises);
|
||||
},
|
||||
|
||||
titleToken() {
|
||||
|
||||
@ -54,8 +54,8 @@ function findTopicList(store, tracking, filter, filterParams, extras) {
|
||||
tracking.trackIncoming(list.filter);
|
||||
}
|
||||
Discourse.Session.currentProp('topicList', list);
|
||||
if (list.topic_list && list.topic_list.tags) {
|
||||
Discourse.Site.currentProp('top_tags', list.topic_list.tags);
|
||||
if (list.topic_list && list.topic_list.top_tags) {
|
||||
Discourse.Site.currentProp('top_tags', list.topic_list.top_tags);
|
||||
}
|
||||
return list;
|
||||
});
|
||||
|
||||
@ -36,7 +36,8 @@ export default Discourse.Route.extend({
|
||||
return null;
|
||||
}
|
||||
}).then(results => {
|
||||
const model = (results && translateResults(results)) || {};
|
||||
const grouped_search_result = results ? results.grouped_search_result : {};
|
||||
const model = (results && translateResults(results)) || { grouped_search_result };
|
||||
setTransient('lastSearch', { searchKey, model }, 5);
|
||||
return model;
|
||||
});
|
||||
|
||||
@ -12,8 +12,13 @@ export function buildGroupPage(type) {
|
||||
},
|
||||
|
||||
setupController(controller, model) {
|
||||
this.controllerFor('group-activity-posts').setProperties({ model, type, canLoadMore: true });
|
||||
this.controllerFor("group").set("showing", type);
|
||||
let loadedAll = model.length < 20;
|
||||
this.controllerFor('group-activity-posts').setProperties({
|
||||
model,
|
||||
type,
|
||||
canLoadMore: !loadedAll,
|
||||
});
|
||||
this.controllerFor('application').set('showFooter', loadedAll);
|
||||
},
|
||||
|
||||
renderTemplate() {
|
||||
|
||||
@ -4,10 +4,11 @@ import Group from 'discourse/models/group';
|
||||
export default Discourse.Route.extend({
|
||||
|
||||
beforeModel(transition) {
|
||||
const self = this;
|
||||
const params = transition.queryParams;
|
||||
|
||||
if (this.currentUser) {
|
||||
this.replaceWith("discovery.latest").then(e => {
|
||||
if (self.currentUser) {
|
||||
self.replaceWith("discovery.latest").then(e => {
|
||||
if (params.username) {
|
||||
// send a message to a user
|
||||
User.findByUsername(params.username).then(user => {
|
||||
|
||||
@ -85,6 +85,10 @@ const TopicRoute = Discourse.Route.extend({
|
||||
this.controllerFor('modal').set('modalClass', 'history-modal');
|
||||
},
|
||||
|
||||
showGrantBadgeModal() {
|
||||
showModal('grant-badge', { model: this.modelFor('topic'), title: 'admin.badges.grant_badge' });
|
||||
},
|
||||
|
||||
showRawEmail(model) {
|
||||
showModal('raw-email', { model });
|
||||
this.controllerFor('raw_email').loadRawEmail(model.get("id"));
|
||||
|
||||
@ -23,7 +23,7 @@
|
||||
|
||||
{{plugin-outlet name="above-footer" args=(hash showFooter=showFooter)}}
|
||||
{{#if showFooter}}
|
||||
{{custom-html name="footer"}}
|
||||
{{custom-html name="footer" triggerAppEvent="true"}}
|
||||
{{/if}}
|
||||
{{plugin-outlet name="below-footer" args=(hash showFooter=showFooter)}}
|
||||
|
||||
|
||||
@ -10,7 +10,10 @@
|
||||
<div class="control-group">
|
||||
<label class="control-label"></label>
|
||||
<div class="controls">
|
||||
{{combo-box value=selectedUserBadgeId nameProperty="badge.name" content=selectableUserBadges}}
|
||||
{{combo-box
|
||||
value=selectedUserBadgeId
|
||||
nameProperty="badge.name"
|
||||
content=selectableUserBadges}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@ -1,7 +1,14 @@
|
||||
{{category-drop category=firstCategory categories=parentCategories}}
|
||||
{{category-drop
|
||||
category=firstCategory
|
||||
categories=parentCategories}}
|
||||
|
||||
{{#if childCategories}}
|
||||
{{category-drop category=secondCategory parentCategory=firstCategory categories=childCategories subCategory="true" noSubcategories=noSubcategories}}
|
||||
{{category-drop
|
||||
category=secondCategory
|
||||
parentCategory=firstCategory
|
||||
categories=childCategories
|
||||
subCategory="true"
|
||||
noSubcategories=noSubcategories}}
|
||||
{{/if}}
|
||||
|
||||
{{#if siteSettings.tagging_enabled}}
|
||||
|
||||
@ -2,14 +2,14 @@
|
||||
<table class="category-list {{if showTopics 'with-topics'}}">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="category">{{i18n 'categories.category'}}</th>
|
||||
<th class="category"><span aria-role="heading" aria-level="2" id="categories-only-category">{{i18n 'categories.category'}}</span></th>
|
||||
<th class="topics">{{i18n 'categories.topics'}}</th>
|
||||
{{#if showTopics}}
|
||||
<th class="latest">{{i18n 'categories.latest'}}</th>
|
||||
{{/if}}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tbody aria-labelledby="categories-only-category">
|
||||
{{#each categories as |c|}}
|
||||
<tr data-category-id={{c.id}} class="{{if c.description_excerpt 'has-description' 'no-description'}} {{if c.uploaded_logo.url 'has-logo' 'no-logo'}}">
|
||||
<td class="category" style={{border-color c.color}}>
|
||||
|
||||
@ -1,30 +0,0 @@
|
||||
{{#if category}}
|
||||
<a href {{action "expand"}} class="dropdown-header" style={{badgeStyle}} aria-label={{i18n 'categories.category_list'}} aria-expanded={{expanded}}>
|
||||
<span class="badge-category-bg" style={{categoryColor}}></span>
|
||||
{{#if category.read_restricted}}
|
||||
{{d-icon "lock"}}
|
||||
{{/if}}
|
||||
<span class='d-label'>{{category.name}}</span>
|
||||
</a>
|
||||
{{else}}
|
||||
{{#if noSubcategories}}
|
||||
<a href {{action "expand"}} class='dropdown-header home' style={{badgeStyle}} aria-label={{i18n 'categories.category_list'}} aria-expanded={{expanded}}>{{i18n 'categories.no_subcategory'}}</a>
|
||||
{{else}}
|
||||
<a href {{action "expand"}} class='dropdown-header home' style={{badgeStyle}} aria-label={{i18n 'categories.category_list'}} aria-expanded={{expanded}}>{{allCategoriesLabel}}</a>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
|
||||
{{#if categories}}
|
||||
<a href {{action "expand"}} class={{dropdownButtonClass}} style={{badgeStyle}}>
|
||||
{{d-icon expandIcon}}
|
||||
</a>
|
||||
<section class="{{unless expanded 'hidden'}} category-dropdown-menu chooser">
|
||||
<div class='cat'><a href={{allCategoriesUrl}} data-drop-close="true" class='badge-category home'>{{allCategoriesLabel}}</a></div>
|
||||
{{#if subCategory}}
|
||||
<div class='cat'><a href={{noCategoriesUrl}} data-drop-close="true" class='badge-category home'>{{i18n 'categories.no_subcategory'}}</a></div>
|
||||
{{/if}}
|
||||
{{#if renderCategories}}
|
||||
{{#each categories as |c|}}<div class='cat'>{{category-link c allowUncategorized=true hideParent=subCategory}}</div>{{/each}}
|
||||
{{/if}}
|
||||
</section>
|
||||
{{/if}}
|
||||
@ -0,0 +1,10 @@
|
||||
<section class='field'>
|
||||
<section class="field-item">
|
||||
<label>{{i18n 'category.name'}}</label>
|
||||
{{text-field value=category.name placeholderKey="category.name_placeholder" maxlength="50"}}
|
||||
</section>
|
||||
<section class="field-item">
|
||||
<label>{{i18n 'category.slug'}}</label>
|
||||
{{text-field value=category.slug placeholderKey="category.slug_placeholder" maxlength="255"}}
|
||||
</section>
|
||||
</section>
|
||||
@ -1,20 +1,22 @@
|
||||
{{d-editor tabindex="4"
|
||||
value=composer.reply
|
||||
placeholder="composer.reply_placeholder"
|
||||
previewUpdated="previewUpdated"
|
||||
markdownOptions=markdownOptions
|
||||
extraButtons="extraButtons"
|
||||
importQuote="importQuote"
|
||||
showUploadModal="showUploadModal"
|
||||
togglePreview="togglePreview"
|
||||
validation=validation
|
||||
loading=composer.loading
|
||||
forcePreview=forcePreview
|
||||
composerEvents=true
|
||||
onExpandPopupMenuOptions="onExpandPopupMenuOptions"
|
||||
onPopupMenuAction=onPopupMenuAction
|
||||
popupMenuOptions=popupMenuOptions}}
|
||||
{{d-editor
|
||||
tabindex="4"
|
||||
value=composer.reply
|
||||
placeholder=replyPlaceholder
|
||||
previewUpdated="previewUpdated"
|
||||
markdownOptions=markdownOptions
|
||||
extraButtons="extraButtons"
|
||||
importQuote="importQuote"
|
||||
showUploadModal="showUploadModal"
|
||||
togglePreview="togglePreview"
|
||||
validation=validation
|
||||
loading=composer.loading
|
||||
forcePreview=forcePreview
|
||||
composerEvents=true
|
||||
onExpandPopupMenuOptions="onExpandPopupMenuOptions"
|
||||
onPopupMenuAction=onPopupMenuAction
|
||||
popupMenuOptions=popupMenuOptions
|
||||
outletArgs=(hash composer=composer editorType="composer")}}
|
||||
|
||||
{{#if site.mobileView}}
|
||||
<input type="file" id="mobile-uploader" multiple />
|
||||
<input type="file" id="mobile-uploader" multiple>
|
||||
{{/if}}
|
||||
|
||||
@ -41,13 +41,14 @@
|
||||
{{conditional-loading-spinner condition=loading}}
|
||||
{{textarea tabindex=tabindex value=value class="d-editor-input" placeholder=placeholderTranslated}}
|
||||
{{popup-input-tip validation=validation}}
|
||||
{{plugin-outlet name="after-d-editor" tagName="" args=outletArgs}}
|
||||
</div>
|
||||
|
||||
<div class="d-editor-preview-wrapper {{if forcePreview 'force-preview'}}">
|
||||
<div class="d-editor-preview">
|
||||
{{{preview}}}
|
||||
</div>
|
||||
{{plugin-outlet name="editor-preview" classNames="d-editor-plugin"}}
|
||||
{{plugin-outlet name="editor-preview" classNames="d-editor-plugin"}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@ -1,14 +1,5 @@
|
||||
<form>
|
||||
<section class='field'>
|
||||
<section class="field-item">
|
||||
<label>{{i18n 'category.name'}}</label>
|
||||
{{text-field value=category.name placeholderKey="category.name_placeholder" maxlength="50"}}
|
||||
</section>
|
||||
<section class="field-item">
|
||||
<label>{{i18n 'category.slug'}}</label>
|
||||
{{text-field value=category.slug placeholderKey="category.slug_placeholder" maxlength="255"}}
|
||||
</section>
|
||||
</section>
|
||||
{{category-name-fields category=category tagName=""}}
|
||||
|
||||
{{#if canSelectParentCategory}}
|
||||
<section class='field'>
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
<div class='table-heading'>
|
||||
<div class='table-heading' aria-role="heading" aria-level="2">
|
||||
{{i18n "filters.latest.title"}}
|
||||
</div>
|
||||
|
||||
|
||||
@ -1,11 +0,0 @@
|
||||
<h2>{{period-title period showDateRange=true}}</h2>
|
||||
<button>{{d-icon "caret-down"}}</button>
|
||||
|
||||
<div id='period-popup' class="{{unless showPeriods 'hidden'}} period-popup">
|
||||
<ul>
|
||||
{{#each site.periods as |p|}}
|
||||
<li><a href {{action "changePeriod" p}}>{{period-title p showDateRange=true}}</a></li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
</div>
|
||||
<div class='clearfix'></div>
|
||||
@ -1,28 +0,0 @@
|
||||
{{#if showTagDropdown}}
|
||||
{{#if tagId}}
|
||||
{{#if noTagsSelected}}
|
||||
<a href {{action "expand"}} class="dropdown-header {{tagClass}} home">{{noTagsLabel}}</a>
|
||||
{{else}}
|
||||
<a href {{action "expand"}} class="dropdown-header {{tagClass}}">{{tagId}}</a>
|
||||
{{/if}}
|
||||
{{else}}
|
||||
<a href {{action "expand"}} class="dropdown-header {{tagClass}} home">{{allTagsLabel}}</a>
|
||||
{{/if}}
|
||||
|
||||
{{#if tags}}
|
||||
<a href {{action "expand"}} class={{dropdownButtonClass}}>
|
||||
{{d-icon expandedIcon}}
|
||||
</a>
|
||||
<section class="{{unless expanded 'hidden'}} category-dropdown-menu chooser">
|
||||
<div class='cat'><a href={{allTagsUrl}} data-drop-close="true" class='dropdown-header home'>{{allTagsLabel}}</a></div>
|
||||
<div class='cat'><a href={{noTagsUrl}} data-drop-close="true" class='dropdown-header home'>{{noTagsLabel}}</a></div>
|
||||
{{#if renderTags}}
|
||||
{{#each tags as |t|}}
|
||||
<div class='cat'>
|
||||
{{tag-drop-link tagId=t category=currentCategory}}
|
||||
</div>
|
||||
{{/each}}
|
||||
{{/if}}
|
||||
</section>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
@ -4,6 +4,9 @@
|
||||
{{#if category}}
|
||||
{{category-title-link category=category}}
|
||||
{{/if}}
|
||||
{{#if tagGroupName}}
|
||||
<h3>{{tagGroupName}}</h3>
|
||||
{{/if}}
|
||||
{{#each sortedTags as |tag|}}
|
||||
<div class='tag-box'>
|
||||
{{#if tag.count}}
|
||||
|
||||
@ -50,8 +50,8 @@
|
||||
disabled=inviteDisabled}}
|
||||
{{/if}}
|
||||
|
||||
{{#if topic.isPrivateMessage}}
|
||||
{{d-button class="standard"
|
||||
{{#if canArchive}}
|
||||
{{d-button class="standard archive-topic"
|
||||
title=archiveTitle
|
||||
label=archiveLabel
|
||||
icon=archiveIcon
|
||||
@ -79,7 +79,9 @@
|
||||
{{pinned-button pinned=topic.pinned topic=topic}}
|
||||
</div>
|
||||
|
||||
{{topic-notifications-button notificationLevel=topic.details.notification_level topic=topic}}
|
||||
{{#if showNotificationsButton}}
|
||||
{{topic-notifications-button notificationLevel=topic.details.notification_level topic=topic}}
|
||||
{{/if}}
|
||||
|
||||
{{plugin-outlet name="after-topic-footer-buttons"
|
||||
args=(hash topic=topic)
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
<label class="control-label">{{{field.name}}}</label>
|
||||
<div class='controls'>
|
||||
<label class="control-label checkbox-label">{{input checked=value type="checkbox"}} {{{field.description}}}</label>
|
||||
</div>
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
<span class='value'>
|
||||
{{#if isNumber}}
|
||||
{{number value}}
|
||||
{{else if isDuration}}
|
||||
{{format-duration value}}
|
||||
{{else}}
|
||||
{{value}}
|
||||
{{/if}}
|
||||
|
||||
@ -85,6 +85,7 @@
|
||||
popupMenuOptions=popupMenuOptions
|
||||
draftStatus=model.draftStatus
|
||||
isUploading=isUploading
|
||||
allowUpload=allowUpload
|
||||
isCancellable=isCancellable
|
||||
uploadProgress=uploadProgress
|
||||
groupsMentioned="groupsMentioned"
|
||||
@ -131,7 +132,9 @@
|
||||
|
||||
<div class="composer-bottom-right">
|
||||
{{#if site.mobileView}}
|
||||
<a class="mobile-file-upload {{if isUploading 'hidden'}}">{{i18n 'upload'}}</a>
|
||||
{{#if allowUpload}}
|
||||
<a class="mobile-file-upload {{if isUploading 'hidden'}}">{{i18n 'upload'}}</a>
|
||||
{{/if}}
|
||||
|
||||
{{#if showPreview}}
|
||||
{{d-button action='togglePreview' class='hide-preview' label='composer.hide_preview'}}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
{{#if bulkSelectEnabled}}
|
||||
<td class='star'>
|
||||
<td class="bulk-select">
|
||||
<input type="checkbox" class="bulk-select">
|
||||
</td>
|
||||
{{/if}}
|
||||
|
||||
@ -7,5 +7,5 @@
|
||||
{{user-stat value=item.posts_read label="directory.posts_read"}}
|
||||
{{user-stat value=item.days_visited label="directory.days_visited"}}
|
||||
{{#if showTimeRead}}
|
||||
<div class='time-read'>{{unbound item.time_read}}</div>
|
||||
{{user-stat value=item.time_read label="directory.time_read" type="duration"}}
|
||||
{{/if}}
|
||||
|
||||
@ -0,0 +1,15 @@
|
||||
{{#d-modal-body class='grant-badge'}}
|
||||
{{#conditional-loading-spinner condition=loading}}
|
||||
{{#if noGrantableBadges}}
|
||||
<p>{{i18n 'admin.badges.no_badges'}}</p>
|
||||
{{else}}
|
||||
<p>{{combo-box filterable=true value=selectedBadgeId content=grantableBadges none="badges.none"}}</p>
|
||||
{{/if}}
|
||||
{{/conditional-loading-spinner}}
|
||||
{{/d-modal-body}}
|
||||
|
||||
<div class="modal-footer">
|
||||
<button class='btn btn-primary' disabled={{buttonDisabled}} {{action "grantBadge"}}>
|
||||
{{i18n 'admin.badges.grant'}}
|
||||
</button>
|
||||
</div>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user