Version bump

This commit is contained in:
Neil Lalonde 2015-08-25 15:06:14 -04:00
commit 7d9c21143f
560 changed files with 15567 additions and 64904 deletions

View File

@ -20,5 +20,5 @@ vendor/
test/javascripts/helpers/
test/javascripts/test_helper.js
test/javascripts/test_helper.js
test/javascripts/fixtures
app/assets/javascripts/ember-addons/

105
.eslintrc Normal file
View File

@ -0,0 +1,105 @@
{
"env": {
"jasmine": true,
"node": true,
"mocha": true,
"browser": true,
"builtin": true
},
ecmaVersion: 7,
"globals":
{"Ember":true,
"jQuery":true,
"$":true,
"RSVP":true,
"Discourse":true,
"Em":true,
"PreloadStore":true,
"Handlebars":true,
"I18n":true,
"bootbox":true,
"module":true,
"moduleFor":true,
"moduleForComponent":true,
"Pretender":true,
"sandbox":true,
"controllerFor":true,
"test":true,
"ok":true,
"not":true,
"expect":true,
"equal":true,
"visit":true,
"andThen":true,
"click":true,
"currentPath":true,
"currentRouteName":true,
"currentURL":true,
"fillIn":true,
"keyEvent":true,
"triggerEvent":true,
"count":true,
"exists":true,
"visible":true,
"invisible":true,
"asyncRender":true,
"selectDropdown":true,
"asyncTestDiscourse":true,
"fixture":true,
"find":true,
"sinon":true,
"moment":true,
"start":true,
"_":true,
"alert":true,
"containsInstance":true,
"deepEqual":true,
"notEqual":true,
"define":true,
"require":true,
"requirejs":true,
"hasModule":true,
"Blob":true,
"File":true},
"rules": {
"block-scoped-var": 2,
"dot-notation": 0,
"eqeqeq": [
2,
"allow-null"
],
"guard-for-in": 2,
"no-bitwise": 2,
"no-caller": 2,
"no-cond-assign": 0,
"no-debugger": 2,
"no-empty": 0,
"no-eval": 2,
"no-extend-native": 2,
"no-extra-parens": 0,
"no-irregular-whitespace": 2,
"no-iterator": 2,
"no-loop-func": 2,
"no-multi-str": 2,
"no-new": 2,
"no-plusplus": 0,
"no-proto": 2,
"no-script-url": 2,
"no-sequences": 2,
"no-shadow": 2,
"no-undef": 2,
"no-unused-vars": 2,
"no-with": 2,
"semi": [
0,
"never"
],
"strict": 0,
"valid-typeof": 2,
"wrap-iife": [
2,
"inside"
]
},
"parser": "babel-eslint"
}

View File

@ -1,86 +0,0 @@
{
"predef":["Ember",
"jQuery",
"$",
"RSVP",
"Discourse",
"Em",
"PreloadStore",
"Handlebars",
"I18n",
"bootbox",
"module",
"moduleFor",
"moduleForComponent",
"Pretender",
"sandbox",
"controllerFor",
"test",
"ok",
"not",
"expect",
"equal",
"blank",
"present",
"visit",
"andThen",
"click",
"currentPath",
"currentRouteName",
"currentURL",
"fillIn",
"keyEvent",
"triggerEvent",
"count",
"exists",
"visible",
"invisible",
"asyncRender",
"selectDropdown",
"asyncTestDiscourse",
"fixture",
"find",
"sinon",
"moment",
"start",
"_",
"alert",
"containsInstance",
"parseHTML",
"deepEqual",
"notEqual",
"define",
"require",
"requirejs",
"hasModule",
"Blob",
"File"],
"node" : false,
"browser" : true,
"boss" : true,
"curly": false,
"debug": false,
"devel": false,
"eqeqeq": true,
"evil": true,
"forin": false,
"immed": false,
"laxbreak": false,
"newcap": true,
"noarg": true,
"noempty": false,
"nonew": false,
"nomen": false,
"onevar": false,
"plusplus": false,
"regexp": false,
"undef": true,
"unused": true,
"sub": true,
"strict": false,
"white": false,
"eqnull": true,
"quotmark": false,
"lastsemic": true,
"esnext": true
}

View File

@ -11,6 +11,12 @@ env:
addons:
postgresql: 9.3
apt:
packages:
- gifsicle
- jpegoptim
- optipng
- jhead
matrix:
allow_failures:
@ -23,18 +29,23 @@ rvm:
- 2.0.0
- 2.1
- 2.2
- rbx-2
services:
- redis-server
sudo: false
cache: bundler
cache:
directories:
- vendor/bundle
before_install:
- npm i -g jshint
- jshint .
- gem install bundler
- npm i -g eslint babel-eslint
- eslint app/assets/javascripts
- eslint --ext .es6 app/assets/javascripts
- eslint --ext .es6 test/javascripts
- eslint test/javascripts
before_script:
- bundle exec rake db:create db:migrate

View File

@ -1,22 +1,19 @@
# Install development dependencies on Mac OS X using Homebrew (http://mxcl.github.com/homebrew)
# ensure that Homebrew's sources are up to date
update
# add this repo to Homebrew's sources
tap homebrew/dupes
tap 'homebrew/dupes'
# install the gcc compiler required for ruby
install apple-gcc42
brew 'apple-gcc42'
# you probably already have git installed; ensure that it is the latest version
install git
brew 'git'
# install the PostgreSQL database
install postgresql
brew 'postgresql'
# install the Redis datastore
install redis
brew 'redis'
# install headless Javascript testing library
install phantomjs
brew 'phantomjs'

View File

@ -40,7 +40,7 @@ gem 'active_model_serializers', '~> 0.8.3'
gem 'onebox'
gem 'ember-rails'
gem 'ember-source', '1.11.3.1'
gem 'ember-source', '1.12.1'
gem 'handlebars-source', '2.0.0'
gem 'barber'
gem 'babel-transpiler'
@ -131,6 +131,7 @@ group :test, :development do
gem 'rspec-given'
gem 'pry-nav'
gem 'spork-rails'
gem 'byebug'
end
group :development do

View File

@ -46,9 +46,9 @@ GEM
multi_json (~> 1.0)
aws-sdk-resources (2.0.45)
aws-sdk-core (= 2.0.45)
babel-source (4.6.6)
babel-transpiler (0.6.0)
babel-source (>= 4.0, < 5)
babel-source (5.8.19)
babel-transpiler (0.7.0)
babel-source (>= 4.0, < 6)
execjs (~> 2.0)
barber (0.9.0)
ember-source (>= 1.0, < 2)
@ -60,10 +60,13 @@ GEM
binding_of_caller (0.7.2)
debug_inspector (>= 0.0.1)
builder (3.2.2)
byebug (5.0.0)
columnize (= 0.9.0)
celluloid (0.16.0)
timers (~> 4.0.0)
certified (1.0.0)
coderay (1.1.0)
columnize (0.9.0)
connection_pool (2.2.0)
crass (1.0.1)
daemons (1.2.2)
@ -86,7 +89,7 @@ GEM
ember-source (>= 1.1.0)
jquery-rails (>= 1.0.17)
railties (>= 3.1)
ember-source (1.11.3.1)
ember-source (1.12.1)
erubis (2.7.0)
eventmachine (1.0.7)
excon (0.45.3)
@ -145,7 +148,7 @@ GEM
thor (~> 0.15)
libv8 (3.16.14.7)
listen (0.7.3)
logster (0.8.4.5.pre)
logster (1.0.0.3.pre)
lru_redux (1.1.0)
mail (2.5.4)
mime-types (~> 1.16)
@ -324,12 +327,12 @@ GEM
shoulda-context (1.2.1)
shoulda-matchers (2.7.0)
activesupport (>= 3.0.0)
sidekiq (3.3.4)
celluloid (>= 0.16.0)
connection_pool (>= 2.1.1)
json
redis (>= 3.0.6)
redis-namespace (>= 1.3.1)
sidekiq (3.4.2)
celluloid (~> 0.16.0)
connection_pool (~> 2.2, >= 2.2.0)
json (~> 1.0)
redis (~> 3.2, >= 3.2.1)
redis-namespace (~> 1.5, >= 1.5.2)
simple-rss (1.3.1)
simplecov (0.9.1)
docile (~> 1.1.0)
@ -397,11 +400,12 @@ DEPENDENCIES
barber
better_errors
binding_of_caller
byebug
certified
discourse-qunit-rails
email_reply_parser
ember-rails
ember-source (= 1.11.3.1)
ember-source (= 1.12.1)
excon
fabrication (= 2.9.8)
fakeweb (~> 1.3.0)
@ -481,6 +485,3 @@ DEPENDENCIES
uglifier
unf
unicorn
BUNDLED WITH
1.10.3

View File

@ -2,4 +2,12 @@
require_asset("main_include_admin.js")
DiscoursePluginRegistry.admin_javascripts.each { |js| require_asset(js) }
DiscoursePluginRegistry.each_globbed_asset(admin: true) do |f, ext|
if File.directory?(f)
depend_on(f)
elsif f.to_s.end_with?(".#{ext}")
require_asset(f)
end
end
%>

View File

@ -0,0 +1,7 @@
import RestAdapter from 'discourse/adapters/rest';
export default RestAdapter.extend({
pathFor() {
return "/admin/customize/embedding";
}
});

View File

@ -0,0 +1,63 @@
import { bufferedProperty } from 'discourse/mixins/buffered-content';
import computed from 'ember-addons/ember-computed-decorators';
import { on, observes } from 'ember-addons/ember-computed-decorators';
import { popupAjaxError } from 'discourse/lib/ajax-error';
export default Ember.Component.extend(bufferedProperty('host'), {
editToggled: false,
tagName: 'tr',
categoryId: null,
editing: Ember.computed.or('host.isNew', 'editToggled'),
@on('didInsertElement')
@observes('editing')
_focusOnInput() {
Ember.run.schedule('afterRender', () => { this.$('.host-name').focus(); });
},
@computed('buffered.host', 'host.isSaving')
cantSave(host, isSaving) {
return isSaving || Ember.isEmpty(host);
},
actions: {
edit() {
this.set('categoryId', this.get('host.category.id'));
this.set('editToggled', true);
},
save() {
if (this.get('cantSave')) { return; }
const props = this.get('buffered').getProperties('host');
props.category_id = this.get('categoryId');
const host = this.get('host');
host.save(props).then(() => {
host.set('category', Discourse.Category.findById(this.get('categoryId')));
this.set('editToggled', false);
}).catch(popupAjaxError);
},
delete() {
bootbox.confirm(I18n.t('admin.embedding.confirm_delete'), (result) => {
if (result) {
this.get('host').destroyRecord().then(() => {
this.sendAction('deleteHost', this.get('host'));
});
}
});
},
cancel() {
const host = this.get('host');
if (host.get('isNew')) {
this.sendAction('deleteHost', host);
} else {
this.rollbackBuffer();
this.set('editToggled', false);
}
}
}
});

View File

@ -0,0 +1,23 @@
import computed from 'ember-addons/ember-computed-decorators';
export default Ember.Component.extend({
classNames: ['embed-setting'],
@computed('field')
inputId(field) { return field.dasherize(); },
@computed('field')
translationKey(field) { return `admin.embedding.${field}`; },
@computed('type')
isCheckbox(type) { return type === "checkbox"; },
@computed('value')
checked: {
get(value) { return !!value; },
set(value) {
this.set('value', value);
return value;
}
}
});

View File

@ -0,0 +1,12 @@
import { on, observes } from 'ember-addons/ember-computed-decorators';
import highlightSyntax from 'discourse/lib/highlight-syntax';
export default Ember.Component.extend({
@on('didInsertElement')
@observes('code')
_refresh: function() {
highlightSyntax(this.$());
}
});

View File

@ -2,8 +2,9 @@ import BufferedContent from 'discourse/mixins/buffered-content';
import ScrollTop from 'discourse/mixins/scroll-top';
import SiteSetting from 'admin/models/site-setting';
import { propertyNotEqual } from 'discourse/lib/computed';
import computed from 'ember-addons/ember-computed-decorators';
const CustomTypes = ['bool', 'enum', 'list', 'url_list', 'host_list'];
const CustomTypes = ['bool', 'enum', 'list', 'url_list', 'host_list', 'category_list'];
export default Ember.Component.extend(BufferedContent, ScrollTop, {
classNameBindings: [':row', ':setting', 'setting.overridden', 'typeClass'],
@ -11,41 +12,32 @@ export default Ember.Component.extend(BufferedContent, ScrollTop, {
dirty: propertyNotEqual('buffered.value', 'setting.value'),
validationMessage: null,
preview: function() {
const preview = this.get('setting.preview');
@computed("setting.preview", "buffered.value")
preview(preview, value) {
if (preview) {
return new Handlebars.SafeString("<div class='preview'>" +
preview.replace(/\{\{value\}\}/g, this.get('buffered.value')) +
"</div>");
return new Handlebars.SafeString("<div class='preview'>" + preview.replace(/\{\{value\}\}/g, value) + "</div>");
}
}.property('buffered.value'),
},
typeClass: function() {
return this.get('partialType').replace("_", "-");
}.property('partialType'),
@computed('componentType')
typeClass(componentType) {
return componentType.replace("_", "-");
},
enabled: function(key, value) {
if (arguments.length > 1) {
this.set('buffered.value', value ? 'true' : 'false');
}
@computed("setting.setting")
settingName(setting) {
return setting.replace(/\_/g, ' ');
},
const bufferedValue = this.get('buffered.value');
if (Ember.isEmpty(bufferedValue)) { return false; }
return bufferedValue === 'true';
}.property('buffered.value'),
@computed("setting.type")
componentType(type) {
return CustomTypes.indexOf(type) !== -1 ? type : 'string';
},
settingName: function() {
return this.get('setting.setting').replace(/\_/g, ' ');
}.property('setting.setting'),
partialType: function() {
let type = this.get('setting.type');
return (CustomTypes.indexOf(type) !== -1) ? type : 'string';
}.property('setting.type'),
partialName: function() {
return 'admin/templates/site-settings/' + this.get('partialType');
}.property('partialType'),
@computed("typeClass")
componentName(typeClass) {
return "site-settings/" + typeClass;
},
_watchEnterKey: function() {
const self = this;
@ -61,8 +53,8 @@ export default Ember.Component.extend(BufferedContent, ScrollTop, {
}.on("willDestroyElement"),
_save() {
const setting = this.get('buffered');
const self = this;
const self = this,
setting = this.get('buffered');
SiteSetting.update(setting.get('setting'), setting.get('value')).then(function() {
self.set('validationMessage', null);
self.commitBuffer();

View File

@ -0,0 +1,17 @@
import computed from "ember-addons/ember-computed-decorators";
export default Ember.Component.extend({
@computed("value")
enabled: {
get(value) {
if (Ember.isEmpty(value)) { return false; }
return value === "true";
},
set(value) {
this.set("value", value ? "true" : "false");
return value;
}
},
});

View File

@ -0,0 +1,16 @@
import computed from "ember-addons/ember-computed-decorators";
export default Ember.Component.extend({
@computed("value")
selectedCategories: {
get(value) {
return Discourse.Category.findByIds(value.split("|"));
},
set(value) {
this.set("value", value.mapBy("id").join("|"));
return value;
}
}
});

View File

@ -1,8 +1,6 @@
export default Ember.ArrayController.extend({
needs: ["adminBackups"],
status: Em.computed.alias("controllers.adminBackups"),
isOperationRunning: Ember.computed.alias("status.model.isOperationRunning"),
restoreDisabled: Ember.computed.alias("status.model.restoreDisabled"),
status: Ember.computed.alias("controllers.adminBackups"),
uploadLabel: function() { return I18n.t("admin.backups.upload.label"); }.property(),

View File

@ -1,4 +1,4 @@
export default Ember.ObjectController.extend({
export default Ember.Controller.extend({
noOperationIsRunning: Ember.computed.not("model.isOperationRunning"),
rollbackEnabled: Ember.computed.and("model.canRollback", "model.restoreEnabled", "noOperationIsRunning"),
rollbackDisabled: Ember.computed.not("rollbackEnabled")

View File

@ -2,7 +2,7 @@ import { popupAjaxError } from 'discourse/lib/ajax-error';
import BufferedContent from 'discourse/mixins/buffered-content';
import { propertyNotEqual } from 'discourse/lib/computed';
export default Ember.ObjectController.extend(BufferedContent, {
export default Ember.Controller.extend(BufferedContent, {
needs: ['admin-badges'],
saving: false,
savingStatus: '',

View File

@ -1,6 +1,4 @@
import DiscourseController from 'discourse/controllers/controller';
export default DiscourseController.extend({
export default Ember.Controller.extend({
/**
Is the "send test email" button disabled?

View File

@ -1,20 +1,17 @@
import ObjectController from 'discourse/controllers/object';
export default ObjectController.extend({
export default Ember.Controller.extend({
actions: {
refresh: function() {
var model = this.get('model'),
self = this;
refresh() {
const model = this.get('model');
self.set('loading', true);
Discourse.EmailPreview.findDigest(this.get('lastSeen')).then(function (email) {
this.set('loading', true);
Discourse.EmailPreview.findDigest(this.get('lastSeen')).then(email => {
model.setProperties(email.getProperties('html_content', 'text_content'));
self.set('loading', false);
this.set('loading', false);
});
},
toggleShowHtml: function() {
toggleShowHtml() {
this.toggleProperty('showHtml');
}
}

View File

@ -1,8 +1,8 @@
import DiscourseController from 'discourse/controllers/controller';
import debounce from 'discourse/lib/debounce';
export default DiscourseController.extend({
export default Ember.Controller.extend({
filterEmailLogs: Discourse.debounce(function() {
filterEmailLogs: debounce(function() {
var self = this;
Discourse.EmailLog.findAll(this.get("filter")).then(function(logs) {
self.set("model", logs);

View File

@ -1,7 +1,7 @@
import DiscourseController from 'discourse/controllers/controller';
import debounce from 'discourse/lib/debounce';
export default DiscourseController.extend({
filterEmailLogs: Discourse.debounce(function() {
export default Ember.Controller.extend({
filterEmailLogs: debounce(function() {
var self = this;
Discourse.EmailLog.findAll(this.get("filter")).then(function(logs) {
self.set("model", logs);

View File

@ -0,0 +1,55 @@
import computed from 'ember-addons/ember-computed-decorators';
import { popupAjaxError } from 'discourse/lib/ajax-error';
export default Ember.Controller.extend({
saved: false,
embedding: null,
// show settings if we have at least one created host
@computed('embedding.embeddable_hosts.@each.isCreated')
showSecondary() {
const hosts = this.get('embedding.embeddable_hosts');
return hosts.length && hosts.findProperty('isCreated');
},
@computed('embedding.base_url')
embeddingCode(baseUrl) {
const html =
`<div id='discourse-comments'></div>
<script type="text/javascript">
DiscourseEmbed = { discourseUrl: '${baseUrl}/',
discourseEmbedUrl: 'REPLACE_ME' };
(function() {
var d = document.createElement('script'); d.type = 'text/javascript'; d.async = true;
d.src = DiscourseEmbed.discourseUrl + 'javascripts/embed.js';
(document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(d);
})();
</script>`;
return html;
},
actions: {
saveChanges() {
const embedding = this.get('embedding');
const updates = embedding.getProperties(embedding.get('fields'));
this.set('saved', false);
this.get('embedding').update(updates).then(() => {
this.set('saved', true);
}).catch(popupAjaxError);
},
addHost() {
const host = this.store.createRecord('embeddable-host');
this.get('embedding.embeddable_hosts').pushObject(host);
},
deleteHost(host) {
this.get('embedding.embeddable_hosts').removeObject(host);
}
}
});

View File

@ -1,7 +1,7 @@
import { popupAjaxError } from 'discourse/lib/ajax-error';
import { propertyEqual } from 'discourse/lib/computed';
export default Em.ObjectController.extend({
export default Ember.Controller.extend({
needs: ['adminGroupsType'],
disableSave: false,

View File

@ -1,4 +1,4 @@
export default Ember.ObjectController.extend({
export default Ember.Controller.extend({
editing: false,
savedIpAddress: null,

View File

@ -1,3 +1,4 @@
import debounce from 'discourse/lib/debounce';
import { outputExportResult } from 'discourse/lib/export-result';
import { exportEntity } from 'discourse/lib/export-csv';
@ -6,7 +7,7 @@ export default Ember.ArrayController.extend({
itemController: 'admin-log-screened-ip-address',
filter: null,
show: Discourse.debounce(function() {
show: debounce(function() {
var self = this;
self.set('loading', true);
Discourse.ScreenedIpAddress.findAll(this.get("filter")).then(function(result) {

View File

@ -1,8 +1,10 @@
import debounce from 'discourse/lib/debounce';
export default Ember.ArrayController.extend({
loading: false,
filter: null,
show: Discourse.debounce(function() {
show: debounce(function() {
var self = this;
self.set('loading', true);
Discourse.Permalink.findAll(self.get("filter")).then(function(result) {

View File

@ -1,4 +1,4 @@
export default Ember.ObjectController.extend({
export default Ember.Controller.extend({
viewMode: 'table',
viewingTable: Em.computed.equal('viewMode', 'table'),
viewingBarChart: Em.computed.equal('viewMode', 'barChart'),

View File

@ -1,4 +1,4 @@
export default Ember.ObjectController.extend({
export default Ember.Controller.extend({
categoryNameKey: null,
needs: ['adminSiteSettings'],

View File

@ -1,13 +1,13 @@
import Presence from 'discourse/mixins/presence';
import debounce from 'discourse/lib/debounce';
export default Ember.ArrayController.extend(Presence, {
export default Ember.ArrayController.extend({
filter: null,
onlyOverridden: false,
filtered: Ember.computed.notEmpty('filter'),
filterContentNow: function(category) {
// If we have no content, don't bother filtering anything
if (!this.present('allSiteSettings')) return;
if (!!Ember.isEmpty(this.get('allSiteSettings'))) return;
let filter;
if (this.get('filter')) {
@ -50,7 +50,7 @@ export default Ember.ArrayController.extend(Presence, {
this.transitionToRoute("adminSiteSettingsCategory", category || "all_results");
},
filterContent: Discourse.debounce(function() {
filterContent: debounce(function() {
if (this.get("_skipBounce")) {
this.set("_skipBounce", false);
} else {
@ -64,6 +64,10 @@ export default Ember.ArrayController.extend(Presence, {
filter: '',
onlyOverridden: false
});
},
toggleMenu() {
$('.admin-detail').toggleClass('mobile-closed mobile-open');
}
}

View File

@ -1,25 +1,18 @@
/**
This controller supports the interface for granting and revoking badges from
individual users.
import UserBadge from 'discourse/models/user-badge';
@class AdminUserBadgesController
@extends Ember.ArrayController
@namespace Discourse
@module Discourse
**/
export default Ember.ArrayController.extend({
needs: ["adminUser"],
user: Em.computed.alias('controllers.adminUser'),
user: Em.computed.alias('controllers.adminUser.model'),
sortProperties: ['granted_at'],
sortAscending: false,
groupedBadges: function(){
const badges = this.get('model');
const allBadges = this.get('model');
var grouped = _.groupBy(badges, badge => badge.badge_id);
var grouped = _.groupBy(allBadges, badge => badge.badge_id);
var expanded = [];
const expandedBadges = badges.get('expandedBadges');
const expandedBadges = allBadges.get('expandedBadges');
_(grouped).each(function(badges){
var lastGranted = badges[0].granted_at;
@ -95,7 +88,7 @@ export default Ember.ArrayController.extend({
**/
grantBadge: function(badgeId) {
var self = this;
Discourse.UserBadge.grant(badgeId, this.get('user.username'), this.get('badgeReason')).then(function(userBadge) {
UserBadge.grant(badgeId, this.get('user.username'), this.get('badgeReason')).then(function(userBadge) {
self.set('badgeReason', '');
self.pushObject(userBadge);
Ember.run.next(function() {
@ -111,12 +104,6 @@ export default Ember.ArrayController.extend({
});
},
/**
Revoke the selected userBadge.
@method revokeBadge
@param {Discourse.UserBadge} userBadge the `Discourse.UserBadge` instance that needs to be revoked.
**/
revokeBadge: function(userBadge) {
var self = this;
return bootbox.confirm(I18n.t("admin.badges.revoke_confirm"), I18n.t("no_value"), I18n.t("yes_value"), function(result) {

View File

@ -1,8 +1,7 @@
import ObjectController from 'discourse/controllers/object';
import CanCheckEmails from 'discourse/mixins/can-check-emails';
import { propertyNotEqual, setting } from 'discourse/lib/computed';
export default ObjectController.extend(CanCheckEmails, {
export default Ember.Controller.extend(CanCheckEmails, {
editingTitle: false,
originalPrimaryGroupId: null,
availableGroups: null,
@ -37,8 +36,8 @@ export default ObjectController.extend(CanCheckEmails, {
saveTitle() {
const self = this;
return Discourse.ajax("/users/" + this.get('username').toLowerCase(), {
data: {title: this.get('title')},
return Discourse.ajax("/users/" + this.get('model.username').toLowerCase(), {
data: {title: this.get('model.title')},
type: 'PUT'
}).catch(function(e) {
bootbox.alert(I18n.t("generic_error_with_reason", {error: "http: " + e.status + " - " + e.body}));
@ -66,7 +65,7 @@ export default ObjectController.extend(CanCheckEmails, {
savePrimaryGroup() {
const self = this;
return Discourse.ajax("/admin/users/" + this.get('id') + "/primary_group", {
return Discourse.ajax("/admin/users/" + this.get('model.id') + "/primary_group", {
type: 'PUT',
data: {primary_group_id: this.get('model.primary_group_id')}
}).then(function () {

View File

@ -1,3 +1 @@
import ObjectController from 'discourse/controllers/object';
export default ObjectController.extend();
export default Ember.Controller.extend();

View File

@ -1,3 +1,4 @@
import debounce from 'discourse/lib/debounce';
import { i18n } from 'discourse/lib/computed';
export default Ember.ArrayController.extend({
@ -33,7 +34,7 @@ export default Ember.ArrayController.extend({
return I18n.t('admin.users.titles.' + this.get('query'));
}.property('query'),
_filterUsers: Discourse.debounce(function() {
_filterUsers: debounce(function() {
this._refreshUsers();
}, 250).observes('listFilter'),

View File

@ -1,6 +1,4 @@
import DiscourseController from 'discourse/controllers/controller';
export default DiscourseController.extend({
export default Ember.Controller.extend({
showBadges: function() {
return this.get('currentUser.admin') && this.siteSettings.enable_badges;
}.property()

View File

@ -1,7 +1,6 @@
import ModalFunctionality from 'discourse/mixins/modal-functionality';
import ObjectController from 'discourse/controllers/object';
export default ObjectController.extend(ModalFunctionality, {
export default Ember.Controller.extend(ModalFunctionality, {
needs: ["admin-flags-list"],
_agreeFlag: function (actionOnPost) {

View File

@ -1,8 +1,6 @@
import ModalFunctionality from 'discourse/mixins/modal-functionality';
import ObjectController from 'discourse/controllers/object';
export default ObjectController.extend(ModalFunctionality, {
export default Ember.Controller.extend(ModalFunctionality, {
needs: ["admin-flags-list"],
actions: {

View File

@ -1,5 +1,3 @@
import ModalFunctionality from 'discourse/mixins/modal-functionality';
import ObjectController from 'discourse/controllers/object';
export default ObjectController.extend(ModalFunctionality);
export default Ember.Controller.extend(ModalFunctionality);

View File

@ -1,7 +1,6 @@
import ModalFunctionality from 'discourse/mixins/modal-functionality';
import Controller from 'discourse/controllers/controller';
export default Controller.extend(ModalFunctionality, {
export default Ember.Controller.extend(ModalFunctionality, {
needs: ["adminBackupsLogs"],
_startBackup: function (withUploads) {

View File

@ -1,7 +1,6 @@
import ModalFunctionality from 'discourse/mixins/modal-functionality';
import ObjectController from 'discourse/controllers/object';
export default ObjectController.extend(ModalFunctionality, {
export default Ember.Controller.extend(ModalFunctionality, {
submitDisabled: function() {
return (!this.get('reason') || this.get('reason').length < 1);

View File

@ -1,7 +1,6 @@
import ModalFunctionality from 'discourse/mixins/modal-functionality';
import ObjectController from 'discourse/controllers/object';
export default ObjectController.extend(ModalFunctionality, {
export default Ember.Controller.extend(ModalFunctionality, {
previousSelected: Ember.computed.equal('selectedTab', 'previous'),
newSelected: Ember.computed.equal('selectedTab', 'new'),

View File

@ -0,0 +1,12 @@
import computed from "ember-addons/ember-computed-decorators";
export default Discourse.Model.extend({
restoreDisabled: Em.computed.not("restoreEnabled"),
@computed("allowRestore", "isOperationRunning")
restoreEnabled(allowRestore, isOperationRunning) {
return allowRestore && !isOperationRunning;
}
});

View File

@ -1,9 +0,0 @@
Discourse.BackupStatus = Discourse.Model.extend({
restoreDisabled: Em.computed.not("restoreEnabled"),
restoreEnabled: function() {
return this.get('allowRestore') && !this.get("isOperationRunning");
}.property("isOperationRunning", "allowRestore")
});

View File

@ -47,7 +47,7 @@ Discourse.FlaggedPost = Discourse.Post.extend({
},
wasEdited: function () {
if (this.blank("last_revised_at")) { return false; }
if (Ember.isEmpty(this.get("last_revised_at"))) { return false; }
var lastRevisedAt = Date.parse(this.get("last_revised_at"));
return _.some(this.get("post_actions"), function (postAction) {
return Date.parse(postAction.created_at) < lastRevisedAt;

View File

@ -50,7 +50,7 @@ export default Discourse.Route.extend({
},
backupStarted() {
this.modelFor("adminBackups").set("isOperationRunning", true);
this.controllerFor("adminBackups").set("isOperationRunning", true);
this.transitionTo("admin.backups.logs");
this.send("closeModal");
},
@ -82,7 +82,7 @@ export default Discourse.Route.extend({
Discourse.User.currentProp("hideReadOnlyAlert", true);
backup.restore().then(function() {
self.controllerFor("adminBackupsLogs").clear();
self.modelFor("adminBackups").set("model.isOperationRunning", true);
self.controllerFor("adminBackups").set("model.isOperationRunning", true);
self.transitionTo("admin.backups.logs");
});
}

View File

@ -1,3 +1,4 @@
import Badge from 'discourse/models/badge';
import showModal from 'discourse/lib/show-modal';
export default Ember.Route.extend({
@ -7,7 +8,7 @@ export default Ember.Route.extend({
model(params) {
if (params.badge_id === "new") {
return Discourse.Badge.create({
return Badge.create({
name: I18n.t('admin.badges.new_badge')
});
}

View File

@ -1,3 +1,5 @@
import Badge from 'discourse/models/badge';
export default Discourse.Route.extend({
_json: null,
@ -5,7 +7,7 @@ export default Discourse.Route.extend({
var self = this;
return Discourse.ajax('/admin/badges.json').then(function(json) {
self._json = json;
return Discourse.Badge.createFromJson(json);
return Badge.createFromJson(json);
});
},

View File

@ -0,0 +1,16 @@
export default Discourse.Route.extend({
model() {
return Discourse.EmailPreview.findDigest();
},
afterModel(model) {
const controller = this.controllerFor('adminEmailPreviewDigest');
controller.setProperties({
model: model,
lastSeen: moment().subtract(7, 'days').format('YYYY-MM-DD'),
showHtml: true
});
}
});

View File

@ -0,0 +1,9 @@
export default Ember.Route.extend({
model() {
return this.store.find('embedding');
},
setupController(controller, model) {
controller.set('embedding', model);
}
});

View File

@ -27,6 +27,7 @@ export default {
this.resource('adminUserFields', { path: '/user_fields' });
this.resource('adminEmojis', { path: '/emojis' });
this.resource('adminPermalinks', { path: '/permalinks' });
this.resource('adminEmbedding', { path: '/embedding' });
});
this.route('api');

View File

@ -0,0 +1,26 @@
import UserBadge from 'discourse/models/user-badge';
import Badge from 'discourse/models/badge';
export default Discourse.Route.extend({
model() {
const username = this.modelFor('adminUser').get('username');
return UserBadge.findByUsername(username);
},
setupController(controller, model) {
// Find all badges.
controller.set('loading', true);
Badge.findAll().then(function(badges) {
controller.set('badges', badges);
if (badges.length > 0) {
var grantableBadges = controller.get('grantableBadges');
if (grantableBadges.length > 0) {
controller.set('selectedBadgeId', grantableBadges[0].get('id'));
}
}
controller.set('loading', false);
});
// Set the model.
controller.set('model', model);
}
});

View File

@ -1,25 +0,0 @@
/**
Previews the Email Digests
@class AdminEmailPreviewDigest
@extends Discourse.Route
@namespace Discourse
@module Discourse
**/
Discourse.AdminEmailPreviewDigestRoute = Discourse.Route.extend({
model: function() {
return Discourse.EmailPreview.findDigest();
},
afterModel: function(model) {
var controller = this.controllerFor('adminEmailPreviewDigest');
controller.setProperties({
model: model,
lastSeen: moment().subtract(7, 'days').format('YYYY-MM-DD'),
showHtml: true
});
}
});

View File

@ -1,32 +0,0 @@
/**
Shows all of the badges that have been granted to a user, and allow granting and
revoking badges.
@class AdminUserBadgesRoute
@extends Discourse.Route
@namespace Discourse
@module Discourse
**/
Discourse.AdminUserBadgesRoute = Discourse.Route.extend({
model: function() {
var username = this.modelFor('adminUser').get('username');
return Discourse.UserBadge.findByUsername(username);
},
setupController: function(controller, model) {
// Find all badges.
controller.set('loading', true);
Discourse.Badge.findAll().then(function(badges) {
controller.set('badges', badges);
if (badges.length > 0) {
var grantableBadges = controller.get('grantableBadges');
if (grantableBadges.length > 0) {
controller.set('selectedBadgeId', grantableBadges[0].get('id'));
}
}
controller.set('loading', false);
});
// Set the model.
controller.set('model', model);
}
});

View File

@ -6,9 +6,9 @@
<div class="pull-right">
{{resumable-upload target="/admin/backups/upload" success="uploadSuccess" error="uploadError" uploadText=uploadLabel title="admin.backups.upload.title"}}
{{#if site.isReadOnly}}
{{d-button icon="eye" action="toggleReadOnlyMode" disabled=model.isOperationRunning title="admin.backups.read_only.disable.title" label="admin.backups.read_only.disable.label"}}
{{d-button icon="eye" action="toggleReadOnlyMode" disabled=status.model.isOperationRunning title="admin.backups.read_only.disable.title" label="admin.backups.read_only.disable.label"}}
{{else}}
{{d-button icon="eye" action="toggleReadOnlyMode" disabled=model.isOperationRunning title="admin.backups.read_only.enable.title" label="admin.backups.read_only.enable.label"}}
{{d-button icon="eye" action="toggleReadOnlyMode" disabled=status.model.isOperationRunning title="admin.backups.read_only.enable.title" label="admin.backups.read_only.enable.label"}}
{{/if}}
</div>
</th>
@ -20,12 +20,12 @@
<td>
<div class="pull-right">
<a {{bind-attr href="backup.link"}} class="btn download" title="{{i18n 'admin.backups.operations.download.title'}}">{{fa-icon "download"}}{{i18n 'admin.backups.operations.download.label'}}</a>
{{#if model.isOperationRunning}}
{{#if status.model.isOperationRunning}}
{{d-button icon="trash-o" action="destroyBackup" actionParam=backup class="btn-danger" disabled="true" title="admin.backups.operations.is_running"}}
{{d-button icon="play" action="startRestore" actionParam=backup disabled=model.restoreDisabled title=restoreTitle label="admin.backups.operations.restore.label"}}
{{d-button icon="play" action="startRestore" actionParam=backup disabled=status.model.restoreDisabled title=restoreTitle label="admin.backups.operations.restore.label"}}
{{else}}
{{d-button icon="trash-o" action="destroyBackup" actionParam=backup class="btn-danger" title="admin.backups.operations.destroy.title"}}
{{d-button icon="play" action="startRestore" actionParam=backup disabled=model.restoreDisabled title=restoreTitle label="admin.backups.operations.restore.label"}}
{{d-button icon="play" action="startRestore" actionParam=backup disabled=status.model.restoreDisabled title=restoreTitle label="admin.backups.operations.restore.label"}}
{{/if}}
</div>
</td>

View File

@ -0,0 +1,19 @@
{{#if editing}}
<td>
{{input value=buffered.host placeholder="example.com" enter="save" class="host-name"}}
</td>
<td>
{{category-chooser value=categoryId}}
</td>
<td>
{{d-button icon="check" action="save" class="btn-primary" disabled=cantSave}}
{{d-button icon="times" action="cancel" class="btn-danger" disabled=host.isSaving}}
</td>
{{else}}
<td>{{host.host}}</td>
<td>{{category-badge host.category}}</td>
<td>
{{d-button icon="pencil" action="edit"}}
{{d-button icon="trash-o" action="delete" class='btn-danger'}}
</td>
{{/if}}

View File

@ -0,0 +1,11 @@
{{#if isCheckbox}}
<label for={{inputId}}>
{{input checked=checked id=inputId type="checkbox"}}
{{i18n translationKey}}
</label>
{{else}}
<label for={{inputId}}>{{i18n translationKey}}</label>
{{input value=value id=inputId}}
{{/if}}
<div class='clearfix'></div>

View File

@ -0,0 +1 @@
<pre><code class={{lang}}>{{code}}</code></pre>

View File

@ -2,7 +2,7 @@
<h3>{{unbound settingName}}</h3>
</div>
<div class="setting-value">
{{partial partialName}}
{{component componentName setting=setting value=buffered.value validationMessage=validationMessage}}
</div>
{{#if dirty}}
<div class='setting-controls'>

View File

@ -0,0 +1,3 @@
{{category-group categories=selectedCategories blacklist=selectedCategories}}
<div class='desc'>{{{unbound setting.description}}}</div>
{{setting-validation-message message=validationMessage}}

View File

@ -1,4 +1,4 @@
{{combo-box valueAttribute="value" content=setting.validValues value=buffered.value none=setting.allowsNone}}
{{combo-box valueAttribute="value" content=setting.validValues value=value none=setting.allowsNone}}
{{preview}}
{{setting-validation-message message=validationMessage}}
<div class='desc'>{{{unbound setting.description}}}</div>

View File

@ -1,3 +1,3 @@
{{value-list values=buffered.value addKey="admin.site_settings.add_url"}}
{{value-list values=value addKey="admin.site_settings.add_host"}}
{{setting-validation-message message=validationMessage}}
<div class='desc'>{{{unbound setting.description}}}</div>

View File

@ -1,3 +1,3 @@
{{list-setting settingValue=buffered.value choices=setting.choices settingName=setting.setting}}
{{list-setting settingValue=value choices=setting.choices settingName=setting.setting}}
{{setting-validation-message message=validationMessage}}
<div class='desc'>{{{unbound setting.description}}}</div>

View File

@ -1,3 +1,3 @@
{{text-field value=buffered.value classNames="input-setting-string"}}
{{text-field value=value classNames="input-setting-string"}}
{{setting-validation-message message=validationMessage}}
<div class='desc'>{{{unbound setting.description}}}</div>

View File

@ -1,3 +1,3 @@
{{value-list values=buffered.value addKey="admin.site_settings.add_host"}}
{{value-list values=value addKey="admin.site_settings.add_url"}}
{{setting-validation-message message=validationMessage}}
<div class='desc'>{{{unbound setting.description}}}</div>

View File

@ -5,6 +5,7 @@
{{nav-item route='adminUserFields' label='admin.user_fields.title'}}
{{nav-item route='adminEmojis' label='admin.emoji.title'}}
{{nav-item route='adminPermalinks' label='admin.permalink.title'}}
{{nav-item route='adminEmbedding' label='admin.embedding.title'}}
{{/admin-nav}}
<div class="admin-container">

View File

@ -40,6 +40,93 @@
</div>
</div>
<div class="greyexplain">
Various greys used throught the UI.
<div><div class="cboxcontainer primary">
<div class="cbox0"></div>
<div class="cbox5"></div>
<div class="cbox10"></div>
<div class="cbox15"></div>
<div class="cbox20"></div>
<div class="cbox25"></div>
<div class="cbox30"></div>
<div class="cbox40"></div>
<div class="cbox50"></div>
<div class="cbox60"></div>
<div class="cbox70"></div>
<div class="cbox75"></div>
<div class="cbox80"></div>
<div class="cbox85"></div>
<div class="cbox90"></div>
<div class="cbox95"></div>
<div class="cbox100"></div>
blend-primary-secondary()
</div></div>
<div><div class="cboxcontainer secondary">
<div class="cbox0"></div>
<div class="cbox5"></div>
<div class="cbox10"></div>
<div class="cbox15"></div>
<div class="cbox20"></div>
<div class="cbox25"></div>
<div class="cbox30"></div>
<div class="cbox40"></div>
<div class="cbox50"></div>
<div class="cbox60"></div>
<div class="cbox70"></div>
<div class="cbox75"></div>
<div class="cbox80"></div>
<div class="cbox85"></div>
<div class="cbox90"></div>
<div class="cbox95"></div>
<div class="cbox100"></div>
blend-primary-secondary()
</div></div>
<div><div class="cboxcontainer primary">
<div class="dbox100"></div>
<div class="dbox95"></div>
<div class="dbox90"></div>
<div class="dbox85"></div>
<div class="dbox80"></div>
<div class="dbox75"></div>
<div class="dbox70"></div>
<div class="dbox60"></div>
<div class="dbox50"></div>
<div class="dbox40"></div>
<div class="dbox30"></div>
<div class="dbox25"></div>
<div class="dbox20"></div>
<div class="dbox15"></div>
<div class="dbox10"></div>
<div class="dbox5"></div>
<div class="dbox0"></div>
dark-light-diff()
</div></div>
<div><div class="cboxcontainer secondary">
<div class="dbox100"></div>
<div class="dbox95"></div>
<div class="dbox90"></div>
<div class="dbox85"></div>
<div class="dbox80"></div>
<div class="dbox75"></div>
<div class="dbox70"></div>
<div class="dbox60"></div>
<div class="dbox50"></div>
<div class="dbox40"></div>
<div class="dbox30"></div>
<div class="dbox25"></div>
<div class="dbox20"></div>
<div class="dbox15"></div>
<div class="dbox10"></div>
<div class="dbox5"></div>
<div class="dbox0"></div>
dark-light-diff()
</div></div>
</div>
{{#if colors.length}}
<table class="table colors">
<thead>

View File

@ -4,24 +4,22 @@
<div class='span7 controls'>
<label for='last-seen'>{{i18n 'admin.email.last_seen_user'}}</label>
{{input type="date" value=lastSeen id="last-seen"}}
</div>
<div>
<button class='btn' {{action "refresh"}}>{{i18n 'admin.email.refresh'}}</button>
</div>
<div class="span7 toggle">
<label>{{i18n 'admin.email.format'}}</label>
{{#if showHtml}}
<span>{{i18n 'admin.email.html'}}</span> | <a href='#' {{action "toggleShowHtml"}}>{{i18n 'admin.email.text'}}</a>
{{else}}
<a href='#' {{action "toggleShowHtml"}}>{{i18n 'admin.email.html'}}</a> | <span>{{i18n 'admin.email.text'}}</span>
{{/if}}
<div class="toggle">
<label>{{i18n 'admin.email.format'}}</label>
{{#if showHtml}}
<span>{{i18n 'admin.email.html'}}</span> | <a href='#' {{action "toggleShowHtml"}}>{{i18n 'admin.email.text'}}</a>
{{else}}
<a href='#' {{action "toggleShowHtml"}}>{{i18n 'admin.email.html'}}</a> | <span>{{i18n 'admin.email.text'}}</span>
{{/if}}
</div>
</div>
</div>
{{#conditional-loading-spinner condition=loading}}
{{#if showHtml}}
{{{html_content}}}
{{{model.html_content}}}
{{else}}
<pre>{{{text_content}}}</pre>
<pre>{{{model.text_content}}}</pre>
{{/if}}
{{/conditional-loading-spinner}}

View File

@ -0,0 +1,61 @@
<div class='embeddable-hosts'>
{{#if embedding.embeddable_hosts}}
<table class='embedding'>
<tr>
<th style='width: 50%'>{{i18n "admin.embedding.host"}}</th>
<th style='width: 30%'>{{i18n "admin.embedding.category"}}</th>
<th style='width: 20%'>&nbsp;</th>
</tr>
{{#each embedding.embeddable_hosts as |host|}}
{{embeddable-host host=host deleteHost="deleteHost"}}
{{/each}}
</table>
{{else}}
<p>{{i18n "admin.embedding.get_started"}}</p>
{{/if}}
{{d-button label="admin.embedding.add_host" action="addHost" icon="plus" class="btn-primary add-host"}}
</div>
{{#if showSecondary}}
<div class='embedding-secondary'>
<p>{{{i18n "admin.embedding.sample"}}}</p>
{{highlighted-code code=embeddingCode lang="html"}}
</div>
<hr>
<div class='embedding-secondary'>
<h3>{{i18n "admin.embedding.settings"}}</h3>
{{embedding-setting field="embed_by_username" value=embedding.embed_by_username}}
{{embedding-setting field="embed_post_limit" value=embedding.embed_post_limit}}
{{embedding-setting field="embed_truncate" value=embedding.embed_truncate type="checkbox"}}
</div>
<div class='embedding-secondary'>
<h3>{{i18n "admin.embedding.feed_settings"}}</h3>
<p class="description">{{i18n "admin.embedding.feed_description"}}</p>
{{embedding-setting field="feed_polling_enabled" value=embedding.feed_polling_enabled type="checkbox"}}
{{embedding-setting field="feed_polling_url" value=embedding.feed_polling_url}}
{{embedding-setting field="embed_username_key_from_feed" value=embedding.embed_username_key_from_feed}}
</div>
<div class='embedding-secondary'>
<h3>{{i18n "admin.embedding.crawling_settings"}}</h3>
<p class="description">{{i18n "admin.embedding.crawling_description"}}</p>
{{embedding-setting field="embed_whitelist_selector" value=embedding.embed_whitelist_selector}}
{{embedding-setting field="embed_blacklist_selector" value=embedding.embed_blacklist_selector}}
</div>
<div class='embedding-secondary'>
{{d-button label="admin.embedding.save"
action="saveChanges"
class="btn-primary embed-save"
disabled=embedding.isSaving}}
{{#if saved}}{{i18n "saved"}}{{/if}}
</div>
{{/if}}

View File

@ -6,6 +6,7 @@
</label>
</div>
<div class='controls'>
<button {{action "toggleMenu"}} class="menu-toggle">{{fa-icon "bars"}}</button>
{{text-field value=filter placeholderKey="type_to_filter" class="no-blur"}}
<button {{action "clearFilter"}} class="btn">{{i18n 'admin.site_settings.clear_filter'}}</button>
</div>
@ -26,7 +27,7 @@
</ul>
</div>
<div class="admin-detail pull-left">
<div class="admin-detail pull-left mobile-closed">
{{outlet}}
</div>

View File

@ -143,19 +143,19 @@
<div class='display-row'>
<div class='field'>{{i18n 'admin.users.approved'}}</div>
<div class='value'>
{{#if approved}}
{{#if model.approved}}
{{i18n 'admin.user.approved_by'}}
{{#link-to 'adminUser' approvedBy}}{{avatar approvedBy imageSize="small"}}{{/link-to}}
{{#link-to 'adminUser' approvedBy}}{{approvedBy.username}}{{/link-to}}
{{#link-to 'adminUser' approvedBy}}{{avatar model.approvedBy imageSize="small"}}{{/link-to}}
{{#link-to 'adminUser' approvedBy}}{{model.approvedBy.username}}{{/link-to}}
{{else}}
{{i18n 'no_value'}}
{{/if}}
</div>
<div class='controls'>
{{#if approved}}
{{#if model.approved}}
{{i18n 'admin.user.approve_success'}}
{{else}}
{{#if can_approve}}
{{#if model.can_approve}}
<button class='btn' {{action "approve" target="content"}}>
{{fa-icon "check"}}
{{i18n 'admin.user.approve'}}
@ -306,12 +306,12 @@
<div class='display-row highlight-danger'>
<div class='field'>{{i18n 'admin.user.suspended_by'}}</div>
<div class='value'>
{{#link-to 'adminUser' suspendedBy}}{{avatar suspendedBy imageSize="tiny"}}{{/link-to}}
{{#link-to 'adminUser' suspendedBy}}{{suspendedBy.username}}{{/link-to}}
{{#link-to 'adminUser' suspendedBy}}{{avatar model.suspendedBy imageSize="tiny"}}{{/link-to}}
{{#link-to 'adminUser' suspendedBy}}{{model.suspendedBy.username}}{{/link-to}}
</div>
<div class='controls'>
<b>{{i18n 'admin.user.suspend_reason'}}</b>:
{{suspend_reason}}
{{model.suspend_reason}}
</div>
</div>
{{/if}}

View File

@ -0,0 +1,137 @@
<div class='admin-controls'>
<div class='span15'>
<ul class="nav nav-pills">
<li>{{#link-to 'adminUser' model}}<i class="fa fa-caret-left"></i> &nbsp;{{model.username}}{{/link-to}}</li>
<li>{{#link-to 'adminUsersList.show' 'regular'}}{{i18n 'admin.user.trust_level_2_users'}}{{/link-to}}</li>
</ul>
</div>
</div>
<div class="admin-container tl3-requirements">
<h2>{{model.username}} - {{i18n 'admin.user.tl3_requirements.title'}}</h2>
<br/>
<p>{{i18n 'admin.user.tl3_requirements.table_title'}}</p>
<table class="table" style="width: auto;">
<thead>
<tr>
<th></th>
<th></th>
<th>{{i18n 'admin.user.tl3_requirements.value_heading'}}</th>
<th>{{i18n 'admin.user.tl3_requirements.requirement_heading'}}</th>
</tr>
</thead>
<tbody>
<tr>
<th>{{i18n 'admin.user.tl3_requirements.visits'}}</th>
<td><i {{bind-attr class=":fa model.tl3Requirements.met.days_visited:fa-check:fa-times"}}></i></td>
<td>
{{model.tl3Requirements.days_visited_percent}}% ({{model.tl3Requirements.days_visited}} / {{model.tl3Requirements.time_period}} {{i18n 'admin.user.tl3_requirements.days'}})
</td>
<td>{{model.tl3Requirements.min_days_visited_percent}}%</td>
</tr>
<tr>
<th>{{i18n 'admin.user.tl3_requirements.topics_replied_to'}}</th>
<td><i {{bind-attr class=":fa model.tl3Requirements.met.topics_replied_to:fa-check:fa-times"}}></i></td>
<td>{{model.tl3Requirements.num_topics_replied_to}}</td>
<td>{{model.tl3Requirements.min_topics_replied_to}}</td>
</tr>
<tr>
<th>{{i18n 'admin.user.tl3_requirements.topics_viewed'}}</th>
<td><i {{bind-attr class=":fa model.tl3Requirements.met.topics_viewed:fa-check:fa-times"}}></i></td>
<td>{{model.tl3Requirements.topics_viewed}}</td>
<td>{{model.tl3Requirements.min_topics_viewed}}</td>
</tr>
<tr>
<th>{{i18n 'admin.user.tl3_requirements.topics_viewed_all_time'}}</th>
<td><i {{bind-attr class=":fa model.tl3Requirements.met.topics_viewed_all_time:fa-check:fa-times"}}></i></td>
<td>{{model.tl3Requirements.topics_viewed_all_time}}</td>
<td>{{model.tl3Requirements.min_topics_viewed_all_time}}</td>
</tr>
<tr>
<th>{{i18n 'admin.user.tl3_requirements.posts_read'}}</th>
<td><i {{bind-attr class=":fa model.tl3Requirements.met.posts_read:fa-check:fa-times"}}></i></td>
<td>{{model.tl3Requirements.posts_read}}</td>
<td>{{model.tl3Requirements.min_posts_read}}</td>
</tr>
<tr>
<th>{{i18n 'admin.user.tl3_requirements.posts_read_all_time'}}</th>
<td><i {{bind-attr class=":fa model.tl3Requirements.met.posts_read_all_time:fa-check:fa-times"}}></i></td>
<td>{{model.tl3Requirements.posts_read_all_time}}</td>
<td>{{model.tl3Requirements.min_posts_read_all_time}}</td>
</tr>
<tr>
<th>{{i18n 'admin.user.tl3_requirements.flagged_posts'}}</th>
<td><i {{bind-attr class=":fa model.tl3Requirements.met.flagged_posts:fa-check:fa-times"}}></i></td>
<td>{{model.tl3Requirements.num_flagged_posts}}</td>
<td>{{i18n 'max_of_count' count=model.tl3Requirements.max_flagged_posts}}</td>
</tr>
<tr>
<th>{{i18n 'admin.user.tl3_requirements.flagged_by_users'}}</th>
<td><i {{bind-attr class=":fa model.tl3Requirements.met.flagged_by_users:fa-check:fa-times"}}></i></td>
<td>{{model.tl3Requirements.num_flagged_by_users}}</td>
<td>{{i18n 'max_of_count' count=model.tl3Requirements.max_flagged_by_users}}</td>
</tr>
<tr>
<th>{{i18n 'admin.user.tl3_requirements.likes_given'}}</th>
<td><i {{bind-attr class=":fa model.tl3Requirements.met.likes_given:fa-check:fa-times"}}></i></td>
<td>{{model.tl3Requirements.num_likes_given}}</td>
<td>{{model.tl3Requirements.min_likes_given}}</td>
</tr>
<tr>
<th>{{i18n 'admin.user.tl3_requirements.likes_received'}}</th>
<td><i {{bind-attr class=":fa model.tl3Requirements.met.likes_received:fa-check:fa-times"}}></i></td>
<td>{{model.tl3Requirements.num_likes_received}}</td>
<td>{{model.tl3Requirements.min_likes_received}}</td>
</tr>
<tr>
<th>{{i18n 'admin.user.tl3_requirements.likes_received_days'}}</th>
<td><i {{bind-attr class=":fa model.tl3Requirements.met.likes_received_days:fa-check:fa-times"}}></i></td>
<td>{{model.tl3Requirements.num_likes_received_days}}</td>
<td>{{model.tl3Requirements.min_likes_received_days}}</td>
</tr>
<tr>
<th>{{i18n 'admin.user.tl3_requirements.likes_received_users'}}</th>
<td><i {{bind-attr class=":fa model.tl3Requirements.met.likes_received_users:fa-check:fa-times"}}></i></td>
<td>{{model.tl3Requirements.num_likes_received_users}}</td>
<td>{{model.tl3Requirements.min_likes_received_users}}</td>
</tr>
</tbody>
</table>
<br/>
<p>
{{#if model.istl3}}
{{#if model.tl3Requirements.requirements_lost}}
{{! tl implicitly not locked }}
{{#if model.tl3Requirements.on_grace_period}}
<i class="fa fa-times"></i> {{i18n 'admin.user.tl3_requirements.on_grace_period'}}
{{else}} {{! not on grace period }}
<i class="fa fa-times"></i> {{i18n 'admin.user.tl3_requirements.does_not_qualify'}}
{{i18n 'admin.user.tl3_requirements.will_be_demoted'}}
{{/if}}
{{else}} {{! requirements not lost - remains tl3 }}
{{#if model.tl3Requirements.trust_level_locked}}
<i class="fa fa-lock"></i> {{i18n 'admin.user.tl3_requirements.locked_will_not_be_demoted'}}
{{else}} {{! tl not locked }}
<i class="fa fa-check"></i> {{i18n 'admin.user.tl3_requirements.qualifies'}}
{{#if model.tl3Requirements.on_grace_period}}
{{i18n 'admin.user.tl3_requirements.on_grace_period'}}
{{/if}}
{{/if}}
{{/if}}
{{else}} {{! is not tl3 }}
{{#if model.tl3Requirements.requirements_met}}
{{! met & not tl3 - will be promoted}}
<i class="fa fa-check"></i> {{i18n 'admin.user.tl3_requirements.qualifies'}}
{{i18n 'admin.user.tl3_requirements.will_be_promoted'}}
{{else}} {{! requirements not met - remains regular }}
{{#if model.tl3Requirements.trust_level_locked}}
<i class="fa fa-lock"></i> {{i18n 'admin.user.tl3_requirements.locked_will_not_be_promoted'}}
{{else}}
<i class="fa fa-times"></i> {{i18n 'admin.user.tl3_requirements.does_not_qualify'}}
{{/if}}
{{/if}}
{{/if}}
</p>
</div>

View File

@ -1,137 +0,0 @@
<div class='admin-controls'>
<div class='span15'>
<ul class="nav nav-pills">
<li>{{#link-to 'adminUser' this}}<i class="fa fa-caret-left"></i> &nbsp;{{username}}{{/link-to}}</li>
<li>{{#link-to 'adminUsersList.show' 'regular'}}{{i18n 'admin.user.trust_level_2_users'}}{{/link-to}}</li>
</ul>
</div>
</div>
<div class="admin-container tl3-requirements">
<h2>{{username}} - {{i18n 'admin.user.tl3_requirements.title'}}</h2>
<br/>
<p>{{i18n 'admin.user.tl3_requirements.table_title'}}</p>
<table class="table" style="width: auto;">
<thead>
<tr>
<th></th>
<th></th>
<th>{{i18n 'admin.user.tl3_requirements.value_heading'}}</th>
<th>{{i18n 'admin.user.tl3_requirements.requirement_heading'}}</th>
</tr>
</thead>
<tbody>
<tr>
<th>{{i18n 'admin.user.tl3_requirements.visits'}}</th>
<td><i {{bind-attr class=":fa tl3Requirements.met.days_visited:fa-check:fa-times"}}></i></td>
<td>
{{tl3Requirements.days_visited_percent}}% ({{tl3Requirements.days_visited}} / {{tl3Requirements.time_period}} {{i18n 'admin.user.tl3_requirements.days'}})
</td>
<td>{{tl3Requirements.min_days_visited_percent}}%</td>
</tr>
<tr>
<th>{{i18n 'admin.user.tl3_requirements.topics_replied_to'}}</th>
<td><i {{bind-attr class=":fa tl3Requirements.met.topics_replied_to:fa-check:fa-times"}}></i></td>
<td>{{tl3Requirements.num_topics_replied_to}}</td>
<td>{{tl3Requirements.min_topics_replied_to}}</td>
</tr>
<tr>
<th>{{i18n 'admin.user.tl3_requirements.topics_viewed'}}</th>
<td><i {{bind-attr class=":fa tl3Requirements.met.topics_viewed:fa-check:fa-times"}}></i></td>
<td>{{tl3Requirements.topics_viewed}}</td>
<td>{{tl3Requirements.min_topics_viewed}}</td>
</tr>
<tr>
<th>{{i18n 'admin.user.tl3_requirements.topics_viewed_all_time'}}</th>
<td><i {{bind-attr class=":fa tl3Requirements.met.topics_viewed_all_time:fa-check:fa-times"}}></i></td>
<td>{{tl3Requirements.topics_viewed_all_time}}</td>
<td>{{tl3Requirements.min_topics_viewed_all_time}}</td>
</tr>
<tr>
<th>{{i18n 'admin.user.tl3_requirements.posts_read'}}</th>
<td><i {{bind-attr class=":fa tl3Requirements.met.posts_read:fa-check:fa-times"}}></i></td>
<td>{{tl3Requirements.posts_read}}</td>
<td>{{tl3Requirements.min_posts_read}}</td>
</tr>
<tr>
<th>{{i18n 'admin.user.tl3_requirements.posts_read_all_time'}}</th>
<td><i {{bind-attr class=":fa tl3Requirements.met.posts_read_all_time:fa-check:fa-times"}}></i></td>
<td>{{tl3Requirements.posts_read_all_time}}</td>
<td>{{tl3Requirements.min_posts_read_all_time}}</td>
</tr>
<tr>
<th>{{i18n 'admin.user.tl3_requirements.flagged_posts'}}</th>
<td><i {{bind-attr class=":fa tl3Requirements.met.flagged_posts:fa-check:fa-times"}}></i></td>
<td>{{tl3Requirements.num_flagged_posts}}</td>
<td>{{i18n 'max_of_count' count=tl3Requirements.max_flagged_posts}}</td>
</tr>
<tr>
<th>{{i18n 'admin.user.tl3_requirements.flagged_by_users'}}</th>
<td><i {{bind-attr class=":fa tl3Requirements.met.flagged_by_users:fa-check:fa-times"}}></i></td>
<td>{{tl3Requirements.num_flagged_by_users}}</td>
<td>{{i18n 'max_of_count' count=tl3Requirements.max_flagged_by_users}}</td>
</tr>
<tr>
<th>{{i18n 'admin.user.tl3_requirements.likes_given'}}</th>
<td><i {{bind-attr class=":fa tl3Requirements.met.likes_given:fa-check:fa-times"}}></i></td>
<td>{{tl3Requirements.num_likes_given}}</td>
<td>{{tl3Requirements.min_likes_given}}</td>
</tr>
<tr>
<th>{{i18n 'admin.user.tl3_requirements.likes_received'}}</th>
<td><i {{bind-attr class=":fa tl3Requirements.met.likes_received:fa-check:fa-times"}}></i></td>
<td>{{tl3Requirements.num_likes_received}}</td>
<td>{{tl3Requirements.min_likes_received}}</td>
</tr>
<tr>
<th>{{i18n 'admin.user.tl3_requirements.likes_received_days'}}</th>
<td><i {{bind-attr class=":fa tl3Requirements.met.likes_received_days:fa-check:fa-times"}}></i></td>
<td>{{tl3Requirements.num_likes_received_days}}</td>
<td>{{tl3Requirements.min_likes_received_days}}</td>
</tr>
<tr>
<th>{{i18n 'admin.user.tl3_requirements.likes_received_users'}}</th>
<td><i {{bind-attr class=":fa tl3Requirements.met.likes_received_users:fa-check:fa-times"}}></i></td>
<td>{{tl3Requirements.num_likes_received_users}}</td>
<td>{{tl3Requirements.min_likes_received_users}}</td>
</tr>
</tbody>
</table>
<br/>
<p>
{{#if istl3}}
{{#if tl3Requirements.requirements_lost}}
{{! tl implicitly not locked }}
{{#if tl3Requirements.on_grace_period}}
<i class="fa fa-times"></i> {{i18n 'admin.user.tl3_requirements.on_grace_period'}}
{{else}} {{! not on grace period }}
<i class="fa fa-times"></i> {{i18n 'admin.user.tl3_requirements.does_not_qualify'}}
{{i18n 'admin.user.tl3_requirements.will_be_demoted'}}
{{/if}}
{{else}} {{! requirements not lost - remains tl3 }}
{{#if tl3Requirements.trust_level_locked}}
<i class="fa fa-lock"></i> {{i18n 'admin.user.tl3_requirements.locked_will_not_be_demoted'}}
{{else}} {{! tl not locked }}
<i class="fa fa-check"></i> {{i18n 'admin.user.tl3_requirements.qualifies'}}
{{#if tl3Requirements.on_grace_period}}
{{i18n 'admin.user.tl3_requirements.on_grace_period'}}
{{/if}}
{{/if}}
{{/if}}
{{else}} {{! is not tl3 }}
{{#if tl3Requirements.requirements_met}}
{{! met & not tl3 - will be promoted}}
<i class="fa fa-check"></i> {{i18n 'admin.user.tl3_requirements.qualifies'}}
{{i18n 'admin.user.tl3_requirements.will_be_promoted'}}
{{else}} {{! requirements not met - remains regular }}
{{#if tl3Requirements.trust_level_locked}}
<i class="fa fa-lock"></i> {{i18n 'admin.user.tl3_requirements.locked_will_not_be_promoted'}}
{{else}}
<i class="fa fa-times"></i> {{i18n 'admin.user.tl3_requirements.does_not_qualify'}}
{{/if}}
{{/if}}
{{/if}}
</p>
</div>

View File

@ -1,6 +1,7 @@
import debounce from 'discourse/lib/debounce';
import { renderSpinner } from 'discourse/helpers/loading-spinner';
export default Discourse.View.extend({
export default Ember.View.extend({
classNames: ["admin-backups-logs"],
_initialize: function() { this._reset(); }.on("init"),
@ -9,7 +10,7 @@ export default Discourse.View.extend({
this.setProperties({ formattedLogs: "", index: 0 });
},
_updateFormattedLogs: Discourse.debounce(function() {
_updateFormattedLogs: debounce(function() {
const logs = this.get("controller.model");
if (logs.length === 0) {
this._reset(); // reset the cached logs whenever the model is reset

View File

@ -1,4 +1,6 @@
export default Discourse.View.extend({
import DiscourseURL from 'discourse/lib/url';
export default Ember.View.extend({
classNames: ["admin-backups"],
_hijackDownloads: function() {
@ -12,7 +14,7 @@ export default Discourse.View.extend({
$link.data("auto-route", true);
}
Discourse.URL.redirectTo($link.data("href"));
DiscourseURL.redirectTo($link.data("href"));
});
}.on("didInsertElement"),

View File

@ -1,6 +1,6 @@
import LoadMore from "discourse/mixins/load-more";
export default Discourse.View.extend(LoadMore, {
export default Ember.View.extend(LoadMore, {
loading: false,
eyelineSelector: '.admin-flags tbody tr',

View File

@ -1,4 +1,4 @@
export default Discourse.View.extend({
export default Ember.View.extend({
_disableCustomStylesheets: function() {
if (this.session.get("disableCustomCSS")) {
$("link.custom-css").attr("rel", "");

View File

@ -1,11 +1,3 @@
/**
A view to handle color selections within a site customization
@class AdminCustomizeColorsView
@extends Discourse.View
@namespace Discourse
@module Discourse
**/
Discourse.AdminCustomizeColorsView = Discourse.View.extend({
Discourse.AdminCustomizeColorsView = Ember.View.extend({
templateName: 'admin/templates/customize_colors'
});

View File

@ -1,10 +1 @@
/**
The view class for an Admin User
@class AdminUserView
@extends Discourse.View
@namespace Discourse
@module Discourse
**/
Discourse.AdminUserView = Discourse.View.extend(Discourse.ScrollTop);
Discourse.AdminUserView = Ember.View.extend(Discourse.ScrollTop);

View File

@ -6,15 +6,11 @@ require_asset ("./main_include.js")
DiscoursePluginRegistry.javascripts.each { |js| require_asset(js) }
DiscoursePluginRegistry.handlebars.each { |hb| require_asset(hb) }
# Load any glob dependencies
DiscoursePluginRegistry.asset_globs.each do |g|
root, extension = *g
Dir.glob("#{root}/**/*") do |f|
if File.directory?(f)
depend_on(f)
elsif f.to_s.end_with?(".#{extension}")
require_asset(f)
end
DiscoursePluginRegistry.each_globbed_asset do |f, ext|
if File.directory?(f)
depend_on(f)
elsif f.to_s.end_with?(".#{ext}")
require_asset(f)
end
end

View File

@ -3,7 +3,7 @@ var DiscourseResolver = require('discourse/ember/resolver').default;
// Allow us to import Ember
define('ember', ['exports'], function(__exports__) {
__exports__["default"] = Ember;
__exports__.default = Ember;
});
window.Discourse = Ember.Application.createWithMixins(Discourse.Ajax, {
@ -16,7 +16,7 @@ window.Discourse = Ember.Application.createWithMixins(Discourse.Ajax, {
// if it's a non relative URL, return it.
if (!/^\/[^\/]/.test(url)) return url;
var u = (Discourse.BaseUri === undefined ? "/" : Discourse.BaseUri);
var u = Discourse.BaseUri === undefined ? "/" : Discourse.BaseUri;
if (u[u.length-1] === '/') u = u.substring(0, u.length-1);
if (url.indexOf(u) !== -1) return url;
@ -57,7 +57,11 @@ window.Discourse = Ember.Application.createWithMixins(Discourse.Ajax, {
faviconChanged: function() {
if(Discourse.User.currentProp('dynamic_favicon')) {
new Favcount(Discourse.SiteSettings.favicon_url).set(
var url = Discourse.SiteSettings.favicon_url;
if (/^http/.test(url)) {
url = Discourse.getURL("/favicon/proxied?" + encodeURIComponent(url));
}
new Favcount(url).set(
this.get('notifyCount')
);
}
@ -66,7 +70,7 @@ window.Discourse = Ember.Application.createWithMixins(Discourse.Ajax, {
// The classes of buttons to show on a post
postButtons: function() {
return Discourse.SiteSettings.post_menu.split("|").map(function(i) {
return (i.replace(/\+/, '').capitalize());
return i.replace(/\+/, '').capitalize();
});
}.property(),
@ -109,12 +113,26 @@ window.Discourse = Ember.Application.createWithMixins(Discourse.Ajax, {
$('noscript').remove();
// Load any ES6 initializers
Ember.keys(requirejs._eak_seen).forEach(function(key) {
if (/\/pre\-initializers\//.test(key)) {
var module = require(key, null, null, true);
if (!module) { throw new Error(key + ' must export an initializer.'); }
Discourse.initializer(module.default);
}
});
Ember.keys(requirejs._eak_seen).forEach(function(key) {
if (/\/initializers\//.test(key)) {
var module = require(key, null, null, true);
if (!module) { throw new Error(key + ' must export an initializer.'); }
Discourse.initializer(module.default);
var init = module.default;
var oldInitialize = init.initialize;
init.initialize = function(app) {
oldInitialize.call(this, app.container, Discourse);
};
Discourse.instanceInitializer(init);
}
});
@ -125,22 +143,24 @@ window.Discourse = Ember.Application.createWithMixins(Discourse.Ajax, {
return desired && Discourse.get("currentAssetVersion") !== desired;
}.property("currentAssetVersion", "desiredAssetVersion"),
assetVersion: function(prop, val) {
if(val) {
if(this.get("currentAssetVersion")){
this.set("desiredAssetVersion", val);
} else {
this.set("currentAssetVersion", val);
assetVersion: Ember.computed({
get: function() {
return this.get("currentAssetVersion");
},
set: function(key, val) {
if(val) {
if (this.get("currentAssetVersion")) {
this.set("desiredAssetVersion", val);
} else {
this.set("currentAssetVersion", val);
}
}
return this.get("currentAssetVersion");
}
return this.get("currentAssetVersion");
}.property()
})
});
// TODO: Remove this, it is in for backwards compatibiltiy with plugins
Discourse.HasCurrentUser = {};
function proxyDep(propName, moduleFunc, msg) {
if (Discourse.hasOwnProperty(propName)) { return; }
Object.defineProperty(Discourse, propName, {
@ -155,3 +175,7 @@ function proxyDep(propName, moduleFunc, msg) {
proxyDep('computed', function() { return require('discourse/lib/computed') });
proxyDep('Formatter', function() { return require('discourse/lib/formatter') });
proxyDep('PageTracker', function() { return require('discourse/lib/page-tracker').default });
proxyDep('URL', function() { return require('discourse/lib/url').default });
proxyDep('Quote', function() { return require('discourse/lib/quote').default });
proxyDep('debounce', function() { return require('discourse/lib/debounce').default });
proxyDep('View', function() { return Ember.View }, "Use `Ember.View` instead");

View File

@ -1,4 +1,4 @@
const ADMIN_MODELS = ['plugin', 'site-customization'];
const ADMIN_MODELS = ['plugin', 'site-customization', 'embeddable-host'];
export function Result(payload, responseJson) {
this.payload = payload;
@ -19,7 +19,7 @@ function rethrow(error) {
export default Ember.Object.extend({
basePath(store, type) {
if (ADMIN_MODELS.indexOf(type) !== -1) { return "/admin/"; }
if (ADMIN_MODELS.indexOf(type.replace('_', '-')) !== -1) { return "/admin/"; }
return "/";
},

View File

@ -24,7 +24,7 @@ export default Ember.Component.extend({
const slug = link.match(regexp)[1];
return Discourse.Category.findSingleBySlug(slug);
});
self.set("categories", categories);
Em.run.next(() => self.set("categories", categories));
},
template,
transformComplete(category) {

View File

@ -1,4 +1,5 @@
import { iconHTML } from 'discourse/helpers/fa-icon';
import computed from 'ember-addons/ember-computed-decorators';
export default Ember.Component.extend({
tagName: 'button',
@ -7,17 +8,15 @@ export default Ember.Component.extend({
noText: Ember.computed.empty('translatedLabel'),
translatedTitle: function() {
const title = this.get('title');
return title ? I18n.t(title) : this.get('translatedLabel');
}.property('title', 'translatedLabel'),
@computed("title", "translatedLabel")
translatedTitle(title, translatedLabel) {
return title ? I18n.t(title) : translatedLabel;
},
translatedLabel: function() {
const label = this.get('label');
if (label) {
return I18n.t(this.get('label'));
}
}.property('label'),
@computed("label")
translatedLabel(label) {
if (label) return I18n.t(label);
},
render(buffer) {
const label = this.get('translatedLabel'),

View File

@ -1,23 +1,29 @@
import computed from 'ember-addons/ember-computed-decorators';
export default Ember.Component.extend({
classNames: ['controls'],
notificationsPermission: function() {
@computed
notificationsPermission() {
if (this.get('isNotSupported')) return '';
return Notification.permission;
}.property(),
},
notificationsDisabled: function(_, value) {
if (arguments.length > 1) {
@computed
notificationsDisabled: {
set(value) {
localStorage.setItem('notifications-disabled', value);
return localStorage.getItem('notifications-disabled');
},
get() {
return localStorage.getItem('notifications-disabled');
}
return localStorage.getItem('notifications-disabled');
}.property(),
},
isNotSupported: function() {
return !window['Notification'];
}.property(),
@computed
isNotSupported() {
return typeof window.Notification === "undefined";
},
isDefaultPermission: function() {
if (this.get('isNotSupported')) return false;

View File

@ -1,3 +1,4 @@
import DiscourseURL from 'discourse/lib/url';
import { buildCategoryPanel } from 'discourse/components/edit-category-panel';
import { categoryBadgeHTML } from 'discourse/helpers/category-link';
@ -53,7 +54,7 @@ export default buildCategoryPanel('general', {
actions: {
showCategoryTopic() {
Discourse.URL.routeTo(this.get('category.topic_url'));
DiscourseURL.routeTo(this.get('category.topic_url'));
return false;
}
}

View File

@ -1,3 +1,5 @@
import DiscourseURL from 'discourse/lib/url';
const TopicCategoryComponent = Ember.Component.extend({
needsSecondRow: Ember.computed.gt('secondRowItems.length', 0),
secondRowItems: function() { return []; }.property(),
@ -10,7 +12,7 @@ const TopicCategoryComponent = Ember.Component.extend({
jumpToTopPost() {
const topic = this.get('topic');
if (topic) {
Discourse.URL.routeTo(topic.get('firstPostUrl'));
DiscourseURL.routeTo(topic.get('firstPostUrl'));
}
}
}

View File

@ -1,3 +1,4 @@
import DiscourseURL from 'discourse/lib/url';
import { setting } from 'discourse/lib/computed';
export default Ember.Component.extend({
@ -26,7 +27,7 @@ export default Ember.Component.extend({
e.preventDefault();
Discourse.URL.routeTo('/');
DiscourseURL.routeTo('/');
return false;
}
});

View File

@ -1,3 +1,5 @@
import DiscourseURL from 'discourse/lib/url';
export default Ember.Component.extend({
tagName: 'ul',
classNameBindings: [':nav', ':nav-pills'],
@ -6,8 +8,8 @@ export default Ember.Component.extend({
const filterMode = this.get('filterMode'),
navItems = this.get('navItems');
var item = navItems.find(function(item){
return item.get('filterMode').indexOf(filterMode) === 0;
var item = navItems.find(function(i){
return i.get('filterMode').indexOf(filterMode) === 0;
});
return item || navItems[0];
@ -24,7 +26,7 @@ export default Ember.Component.extend({
this.set('expanded',false);
}
$(window).off('click.navigation-bar');
Discourse.URL.appEvents.off('dom:clean', this, this.ensureDropClosed);
DiscourseURL.appEvents.off('dom:clean', this, this.ensureDropClosed);
},
actions: {
@ -33,7 +35,7 @@ export default Ember.Component.extend({
var self = this;
if (this.get('expanded')) {
Discourse.URL.appEvents.on('dom:clean', this, this.ensureDropClosed);
DiscourseURL.appEvents.on('dom:clean', this, this.ensureDropClosed);
Em.run.next(function() {

View File

@ -45,8 +45,8 @@ export default Ember.Component.extend({
this.rerender();
});
on('th.sortable', function(e){
this.sendAction('changeSort', e.data('sort-order'));
on('th.sortable', function(e2){
this.sendAction('changeSort', e2.data('sort-order'));
this.rerender();
});
}

View File

@ -1,7 +1,6 @@
import ModalFunctionality from 'discourse/mixins/modal-functionality';
import DiscourseController from 'discourse/controllers/controller';
export default DiscourseController.extend(ModalFunctionality, {
export default Ember.Controller.extend(ModalFunctionality, {
uploadedAvatarTemplate: null,
saveDisabled: Em.computed.alias("uploading"),
hasUploadedAvatar: Em.computed.or('uploadedAvatarTemplate', 'custom_avatar_upload_id'),

View File

@ -1,6 +1,6 @@
import ObjectController from 'discourse/controllers/object';
import UserBadge from 'discourse/models/user-badge';
export default ObjectController.extend({
export default Ember.Controller.extend({
noMoreBadges: false,
userBadges: null,
needs: ["application"],
@ -10,7 +10,7 @@ export default ObjectController.extend({
const self = this;
const userBadges = this.get('userBadges');
Discourse.UserBadge.findByBadgeId(this.get('model.id'), {
UserBadge.findByBadgeId(this.get('model.id'), {
offset: userBadges.length
}).then(function(result) {
userBadges.pushObjects(result);

View File

@ -1,9 +1,9 @@
import Presence from 'discourse/mixins/presence';
import SelectedPostsCount from 'discourse/mixins/selected-posts-count';
import ModalFunctionality from 'discourse/mixins/modal-functionality';
import DiscourseURL from 'discourse/lib/url';
// Modal related to changing the ownership of posts
export default Ember.Controller.extend(Presence, SelectedPostsCount, ModalFunctionality, {
export default Ember.Controller.extend(SelectedPostsCount, ModalFunctionality, {
needs: ['topic'],
topicController: Em.computed.alias('controllers.topic'),
@ -13,7 +13,7 @@ export default Ember.Controller.extend(Presence, SelectedPostsCount, ModalFuncti
buttonDisabled: function() {
if (this.get('saving')) return true;
return this.blank('new_user');
return Ember.isEmpty(this.get('new_user'));
}.property('saving', 'new_user'),
buttonTitle: function() {
@ -43,7 +43,7 @@ export default Ember.Controller.extend(Presence, SelectedPostsCount, ModalFuncti
// success
self.send('closeModal');
self.get('topicController').send('toggleMultiSelect');
Em.run.next(function() { Discourse.URL.routeTo(result.url); });
Em.run.next(function() { DiscourseURL.routeTo(result.url); });
}, function() {
// failure
self.flash(I18n.t('topic.change_owner.error'), 'alert-error');

View File

@ -0,0 +1,58 @@
import ModalFunctionality from 'discourse/mixins/modal-functionality';
import computed from 'ember-addons/ember-computed-decorators';
// Modal related to changing the timestamp of posts
export default Ember.Controller.extend(ModalFunctionality, {
needs: ['topic'],
topicController: Em.computed.alias('controllers.topic'),
saving: false,
date: '',
time: '',
@computed('saving')
buttonTitle(saving) {
return saving ? I18n.t('saving') : I18n.t('topic.change_timestamp.action');
},
@computed('date', 'time')
createdAt(date, time) {
return moment(date + ' ' + time, 'YYYY-MM-DD HH:mm:ss');
},
@computed('createdAt')
validTimestamp(createdAt) {
return moment().diff(createdAt, 'minutes') < 0;
},
@computed('saving', 'date', 'validTimestamp')
buttonDisabled() {
if (this.get('saving') || this.get('validTimestamp')) return true;
return Ember.isEmpty(this.get('date'));
},
onShow: function() {
this.setProperties({
date: moment().format('YYYY-MM-DD')
});
},
actions: {
changeTimestamp: function() {
this.set('saving', true);
const self = this;
Discourse.Topic.changeTimestamp(
this.get('topicController.model.id'),
this.get('createdAt').unix()
).then(function() {
self.send('closeModal');
self.setProperties({ date: '', time: '', saving: false });
}).catch(function() {
self.flash(I18n.t('topic.change_timestamp.error'), 'alert-error');
self.set('saving', false);
});
return false;
}
}
});

View File

@ -1,7 +1,9 @@
import { setting } from 'discourse/lib/computed';
import Presence from 'discourse/mixins/presence';
import DiscourseURL from 'discourse/lib/url';
import Quote from 'discourse/lib/quote';
import Draft from 'discourse/models/draft';
export default Ember.ObjectController.extend(Presence, {
export default Ember.Controller.extend({
needs: ['modal', 'topic', 'composer-messages', 'application'],
replyAsNewTopicDraft: Em.computed.equal('model.draftKey', Discourse.Composer.REPLY_AS_NEW_TOPIC_KEY),
@ -72,7 +74,7 @@ export default Ember.ObjectController.extend(Presence, {
const composer = this;
return this.store.find('post', postId).then(function(post) {
const quote = Discourse.Quote.build(post, post.get("raw"), {raw: true, full: true});
const quote = Quote.build(post, post.get("raw"), {raw: true, full: true});
composer.appendBlockAtCursor(quote);
composer.set('model.loading', false);
});
@ -149,7 +151,7 @@ export default Ember.ObjectController.extend(Presence, {
this.closeAutocomplete();
switch (this.get('model.composeState')) {
case Discourse.Composer.OPEN:
if (this.blank('model.reply') && this.blank('model.title')) {
if (Ember.isEmpty(this.get('model.reply')) && Ember.isEmpty(this.get('model.title'))) {
this.close();
} else {
this.shrink();
@ -262,7 +264,7 @@ export default Ember.ObjectController.extend(Presence, {
if (!composer.get('replyingToTopic') || !disableJumpReply) {
const post = result.target;
if (post && !staged) {
Discourse.URL.routeTo(post.get('url'));
DiscourseURL.routeTo(post.get('url'));
}
}
}).catch(function(error) {
@ -278,7 +280,7 @@ export default Ember.ObjectController.extend(Presence, {
Em.run.schedule('afterRender', function() {
if (staged && !disableJumpReply) {
const postNumber = staged.get('post_number');
Discourse.URL.jumpToPost(postNumber, { skipIfOnScreen: true });
DiscourseURL.jumpToPost(postNumber, { skipIfOnScreen: true });
self.appEvents.trigger('post:highlight', postNumber);
}
});
@ -396,7 +398,8 @@ export default Ember.ObjectController.extend(Presence, {
// If we're already open, we don't have to do anything
if (composerModel.get('composeState') === Discourse.Composer.OPEN &&
composerModel.get('draftKey') === opts.draftKey) {
composerModel.get('draftKey') === opts.draftKey &&
composerModel.get('action') === opts.action ) {
return resolve();
}
@ -404,7 +407,7 @@ export default Ember.ObjectController.extend(Presence, {
if (composerModel.get('composeState') === Discourse.Composer.DRAFT &&
composerModel.get('draftKey') === opts.draftKey) {
composerModel.set('composeState', Discourse.Composer.OPEN);
return resolve();
if (composerModel.get('action') === opts.action) return resolve();
}
// If it's a different draft, cancel it and try opening again.
@ -415,7 +418,7 @@ export default Ember.ObjectController.extend(Presence, {
// we need a draft sequence for the composer to work
if (opts.draftSequence === undefined) {
return Discourse.Draft.get(opts.draftKey).then(function(data) {
return Draft.get(opts.draftKey).then(function(data) {
opts.draftSequence = data.draft_sequence;
opts.draft = data.draft;
self._setModel(composerModel, opts);
@ -477,7 +480,7 @@ export default Ember.ObjectController.extend(Presence, {
// View a new reply we've made
viewNewReply() {
Discourse.URL.routeTo(this.get('model.createdPost.url'));
DiscourseURL.routeTo(this.get('model.createdPost.url'));
this.close();
return false;
},
@ -485,7 +488,7 @@ export default Ember.ObjectController.extend(Presence, {
destroyDraft() {
const key = this.get('model.draftKey');
if (key) {
Discourse.Draft.clear(key, this.get('model.draftSequence'));
Draft.clear(key, this.get('model.draftSequence'));
}
},

View File

@ -1,3 +0,0 @@
import Presence from 'discourse/mixins/presence';
export default Ember.Controller.extend(Presence);

View File

@ -1,8 +1,8 @@
import debounce from 'discourse/lib/debounce';
import ModalFunctionality from 'discourse/mixins/modal-functionality';
import DiscourseController from 'discourse/controllers/controller';
import { setting } from 'discourse/lib/computed';
export default DiscourseController.extend(ModalFunctionality, {
export default Ember.Controller.extend(ModalFunctionality, {
needs: ['login'],
uniqueUsernameValidation: null,
@ -65,7 +65,7 @@ export default DiscourseController.extend(ModalFunctionality, {
usernameRequired: Ember.computed.not('authOptions.omit_username'),
passwordRequired: function() {
return this.blank('authOptions.auth_provider');
return Ember.isEmpty(this.get('authOptions.auth_provider'));
}.property('authOptions.auth_provider'),
passwordInstructions: function() {
@ -82,7 +82,7 @@ export default DiscourseController.extend(ModalFunctionality, {
this.fetchConfirmationValue();
}
if (Discourse.SiteSettings.full_name_required && this.blank('accountName')) {
if (Discourse.SiteSettings.full_name_required && Ember.isEmpty(this.get('accountName'))) {
return Discourse.InputValidation.create({ failed: true });
}
@ -93,7 +93,7 @@ export default DiscourseController.extend(ModalFunctionality, {
emailValidation: function() {
// If blank, fail without a reason
let email;
if (this.blank('accountEmail')) {
if (Ember.isEmpty(this.get('accountEmail'))) {
return Discourse.InputValidation.create({
failed: true
});
@ -143,7 +143,7 @@ export default DiscourseController.extend(ModalFunctionality, {
}
this.set('prefilledUsername', null);
}
if (this.get('emailValidation.ok') && (this.blank('accountUsername') || this.get('authOptions.email'))) {
if (this.get('emailValidation.ok') && (Ember.isEmpty(this.get('accountUsername')) || this.get('authOptions.email'))) {
// If email is valid and username has not been entered yet,
// or email and username were filled automatically by 3rd parth auth,
// then look for a registered username that matches the email.
@ -151,10 +151,10 @@ export default DiscourseController.extend(ModalFunctionality, {
}
}.observes('emailValidation', 'accountEmail'),
fetchExistingUsername: Discourse.debounce(function() {
fetchExistingUsername: debounce(function() {
const self = this;
Discourse.User.checkUsername(null, this.get('accountEmail')).then(function(result) {
if (result.suggestion && (self.blank('accountUsername') || self.get('accountUsername') === self.get('authOptions.username'))) {
if (result.suggestion && (Ember.isEmpty(self.get('accountUsername')) || self.get('accountUsername') === self.get('authOptions.username'))) {
self.set('accountUsername', result.suggestion);
self.set('prefilledUsername', result.suggestion);
}
@ -193,7 +193,7 @@ export default DiscourseController.extend(ModalFunctionality, {
}
// If blank, fail without a reason
if (this.blank('accountUsername')) {
if (Ember.isEmpty(this.get('accountUsername'))) {
return Discourse.InputValidation.create({
failed: true
});
@ -224,10 +224,10 @@ export default DiscourseController.extend(ModalFunctionality, {
}.property('accountUsername'),
shouldCheckUsernameMatch: function() {
return !this.blank('accountUsername') && this.get('accountUsername').length >= this.get('minUsernameLength');
return !Ember.isEmpty(this.get('accountUsername')) && this.get('accountUsername').length >= this.get('minUsernameLength');
},
checkUsernameAvailability: Discourse.debounce(function() {
checkUsernameAvailability: debounce(function() {
const _this = this;
if (this.shouldCheckUsernameMatch()) {
return Discourse.User.checkUsername(this.get('accountUsername'), this.get('accountEmail')).then(function(result) {
@ -295,7 +295,7 @@ export default DiscourseController.extend(ModalFunctionality, {
// If blank, fail without a reason
const password = this.get("accountPassword");
if (this.blank('accountPassword')) {
if (Ember.isEmpty(this.get('accountPassword'))) {
return Discourse.InputValidation.create({ failed: true });
}
@ -314,14 +314,14 @@ export default DiscourseController.extend(ModalFunctionality, {
});
}
if (!this.blank('accountUsername') && this.get('accountPassword') === this.get('accountUsername')) {
if (!Ember.isEmpty(this.get('accountUsername')) && this.get('accountPassword') === this.get('accountUsername')) {
return Discourse.InputValidation.create({
failed: true,
reason: I18n.t('user.password.same_as_username')
});
}
if (!this.blank('accountEmail') && this.get('accountPassword') === this.get('accountEmail')) {
if (!Ember.isEmpty(this.get('accountEmail')) && this.get('accountPassword') === this.get('accountEmail')) {
return Discourse.InputValidation.create({
failed: true,
reason: I18n.t('user.password.same_as_email')

View File

@ -1,5 +1,3 @@
import DiscourseController from 'discourse/controllers/controller';
// Just add query params here to have them automatically passed to topic list filters.
export var queryParams = {
order: { replace: true, refreshModel: true },
@ -22,4 +20,4 @@ controllerOpts.queryParams.forEach(function(p) {
controllerOpts[p] = Em.computed.alias('controllers.discovery/topics.' + p);
});
export default DiscourseController.extend(controllerOpts);
export default Ember.Controller.extend(controllerOpts);

View File

@ -1,4 +1,6 @@
export default Ember.ObjectController.extend({
import DiscourseURL from 'discourse/lib/url';
export default Ember.Controller.extend({
needs: ['navigation/category', 'discovery/topics', 'application'],
loading: false,
@ -22,7 +24,7 @@ export default Ember.ObjectController.extend({
actions: {
changePeriod(p) {
Discourse.URL.routeTo(this.showMoreUrl(p));
DiscourseURL.routeTo(this.showMoreUrl(p));
}
}

View File

@ -95,6 +95,10 @@ const controllerOpts = {
return this.get('model.filter') === 'new' && this.get('model.topics.length') > 0;
}.property('model.filter', 'model.topics.length'),
tooManyTracked: function(){
return Discourse.TopicTrackingState.current().tooManyTracked();
}.property(),
showDismissAtTop: function() {
return (this.isFilterPage(this.get('model.filter'), 'new') ||
this.isFilterPage(this.get('model.filter'), 'unread')) &&

Some files were not shown because too many files have changed in this diff Show More