Version bump
This commit is contained in:
commit
adc1a2e9a5
52
Gemfile.lock
52
Gemfile.lock
@ -46,18 +46,18 @@ GEM
|
||||
ember-source
|
||||
execjs
|
||||
handlebars-source (>= 1.0.0.rc.4)
|
||||
better_errors (2.1.0)
|
||||
better_errors (2.1.1)
|
||||
coderay (>= 1.0.0)
|
||||
erubis (>= 2.6.6)
|
||||
rack (>= 0.9.0)
|
||||
binding_of_caller (0.7.2)
|
||||
debug_inspector (>= 0.0.1)
|
||||
builder (3.2.2)
|
||||
celluloid (0.15.2)
|
||||
timers (~> 1.1.0)
|
||||
celluloid (0.16.0)
|
||||
timers (~> 4.0.0)
|
||||
certified (1.0.0)
|
||||
coderay (1.1.0)
|
||||
connection_pool (2.0.0)
|
||||
connection_pool (2.1.1)
|
||||
crass (0.2.1)
|
||||
daemons (1.1.9)
|
||||
debug_inspector (0.0.2)
|
||||
@ -81,7 +81,7 @@ GEM
|
||||
ember-source (1.9.0.beta.4)
|
||||
handlebars-source (~> 2.0)
|
||||
erubis (2.7.0)
|
||||
eventmachine (1.0.4)
|
||||
eventmachine (1.0.5)
|
||||
excon (0.42.1)
|
||||
execjs (2.2.2)
|
||||
exifr (1.1.3)
|
||||
@ -179,10 +179,11 @@ GEM
|
||||
sorcerer (>= 0.3.7)
|
||||
guess_html_encoding (0.0.9)
|
||||
handlebars-source (2.0.0)
|
||||
hashie (3.3.1)
|
||||
hashie (3.3.2)
|
||||
highline (1.6.21)
|
||||
hike (1.2.3)
|
||||
hiredis (0.5.2)
|
||||
hitimes (1.2.2)
|
||||
htmlentities (4.3.3)
|
||||
i18n (0.6.11)
|
||||
image_optim (0.9.1)
|
||||
@ -198,7 +199,7 @@ GEM
|
||||
jquery-rails (3.1.2)
|
||||
railties (>= 3.0, < 5.0)
|
||||
thor (>= 0.14, < 2.0)
|
||||
json (1.8.1)
|
||||
json (1.8.2)
|
||||
jwt (1.0.0)
|
||||
kgio (2.9.2)
|
||||
librarian (0.1.2)
|
||||
@ -212,20 +213,20 @@ GEM
|
||||
mime-types (~> 1.16)
|
||||
treetop (~> 1.4.8)
|
||||
memory_profiler (0.0.4)
|
||||
message_bus (1.0.5)
|
||||
message_bus (1.0.6)
|
||||
eventmachine
|
||||
rack (>= 1.1.3)
|
||||
redis
|
||||
metaclass (0.0.4)
|
||||
method_source (0.8.2)
|
||||
mime-types (1.25.1)
|
||||
mini_portile (0.6.1)
|
||||
mini_portile (0.6.2)
|
||||
minitest (5.4.2)
|
||||
mocha (1.1.0)
|
||||
metaclass (~> 0.0.1)
|
||||
mock_redis (0.13.2)
|
||||
moneta (0.8.0)
|
||||
msgpack (0.5.8)
|
||||
msgpack (0.5.10)
|
||||
multi_json (1.10.1)
|
||||
multi_xml (0.5.5)
|
||||
multipart-post (2.0.0)
|
||||
@ -234,7 +235,7 @@ GEM
|
||||
net-ssh (>= 2.6.5)
|
||||
net-ssh (2.9.1)
|
||||
netrc (0.7.7)
|
||||
nokogiri (1.6.5)
|
||||
nokogiri (1.6.6.2)
|
||||
mini_portile (~> 0.6.0)
|
||||
nokogumbo (1.1.12)
|
||||
nokogiri
|
||||
@ -245,7 +246,7 @@ GEM
|
||||
multi_json (~> 1.3)
|
||||
multi_xml (~> 0.5)
|
||||
rack (~> 1.2)
|
||||
oj (2.11.1)
|
||||
oj (2.11.4)
|
||||
omniauth (1.2.2)
|
||||
hashie (>= 1.2, < 4)
|
||||
rack (~> 1.0)
|
||||
@ -271,7 +272,7 @@ GEM
|
||||
omniauth-twitter (1.0.1)
|
||||
multi_json (~> 1.3)
|
||||
omniauth-oauth (~> 1.0)
|
||||
onebox (1.5.6)
|
||||
onebox (1.5.11)
|
||||
moneta (~> 0.7)
|
||||
multi_json (~> 1.7)
|
||||
mustache (~> 0.99)
|
||||
@ -279,7 +280,7 @@ GEM
|
||||
openid-redis-store (0.0.2)
|
||||
redis
|
||||
ruby-openid
|
||||
pg (0.18.0)
|
||||
pg (0.18.1)
|
||||
polyglot (0.3.5)
|
||||
progress (3.0.1)
|
||||
pry (0.10.1)
|
||||
@ -290,7 +291,7 @@ GEM
|
||||
pry (>= 0.9.10, < 0.11.0)
|
||||
pry-rails (0.3.2)
|
||||
pry (>= 0.9.10)
|
||||
puma (2.9.1)
|
||||
puma (2.11.0)
|
||||
rack (>= 1.1, < 2.0)
|
||||
qunit-rails (0.0.7)
|
||||
railties
|
||||
@ -328,7 +329,7 @@ GEM
|
||||
rb-fsevent (0.9.4)
|
||||
rb-inotify (0.9.5)
|
||||
ffi (>= 0.5.0)
|
||||
rbtrace (0.4.5)
|
||||
rbtrace (0.4.6)
|
||||
ffi (>= 1.0.6)
|
||||
msgpack (>= 0.4.3)
|
||||
trollop (>= 1.16.2)
|
||||
@ -388,9 +389,9 @@ GEM
|
||||
shoulda-context (1.2.1)
|
||||
shoulda-matchers (2.7.0)
|
||||
activesupport (>= 3.0.0)
|
||||
sidekiq (3.2.5)
|
||||
celluloid (= 0.15.2)
|
||||
connection_pool (>= 2.0.0)
|
||||
sidekiq (3.3.1)
|
||||
celluloid (>= 0.16.0)
|
||||
connection_pool (>= 2.1.1)
|
||||
json
|
||||
redis (>= 3.0.6)
|
||||
redis-namespace (>= 1.3.1)
|
||||
@ -424,19 +425,20 @@ GEM
|
||||
therubyracer (0.12.1)
|
||||
libv8 (~> 3.16.14.0)
|
||||
ref
|
||||
thin (1.6.2)
|
||||
daemons (>= 1.0.9)
|
||||
eventmachine (>= 1.0.0)
|
||||
rack (>= 1.0.0)
|
||||
thin (1.6.3)
|
||||
daemons (~> 1.0, >= 1.0.9)
|
||||
eventmachine (~> 1.0)
|
||||
rack (~> 1.0)
|
||||
thor (0.19.1)
|
||||
thread_safe (0.3.4)
|
||||
tilt (1.4.1)
|
||||
timecop (0.7.1)
|
||||
timers (1.1.0)
|
||||
timers (4.0.1)
|
||||
hitimes
|
||||
treetop (1.4.15)
|
||||
polyglot
|
||||
polyglot (>= 0.3.1)
|
||||
trollop (2.0)
|
||||
trollop (2.1.1)
|
||||
tzinfo (1.2.2)
|
||||
thread_safe (~> 0.1)
|
||||
uglifier (2.6.0)
|
||||
|
||||
4
Vagrantfile
vendored
4
Vagrantfile
vendored
@ -3,8 +3,8 @@
|
||||
# See https://github.com/discourse/discourse/blob/master/docs/VAGRANT.md
|
||||
#
|
||||
Vagrant.configure("2") do |config|
|
||||
config.vm.box = 'discourse-0.9.9.13'
|
||||
config.vm.box_url = "https://d3fvb7b7auiut8.cloudfront.net/discourse-0.9.9.13.box"
|
||||
config.vm.box= "discourse/discourse-0.9.9.15.box"
|
||||
config.vm.box_url = "https://vagrantcloud.com/discourse/discourse-0.9.9.15.box"
|
||||
|
||||
# Make this VM reachable on the host network as well, so that other
|
||||
# VM's running other browsers can access our dev server.
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
export default Em.ObjectController.extend({
|
||||
needs: ['adminGroups'],
|
||||
needs: ['adminGroupsType'],
|
||||
disableSave: false,
|
||||
usernames: null,
|
||||
|
||||
currentPage: function() {
|
||||
if (this.get("user_count") == 0) { return 0; }
|
||||
@ -59,28 +58,29 @@ export default Em.ObjectController.extend({
|
||||
},
|
||||
|
||||
addMembers: function() {
|
||||
// TODO: should clear the input
|
||||
if (Em.isEmpty(this.get("usernames"))) { return; }
|
||||
this.get("model").addMembers(this.get("usernames"));
|
||||
// clear the user selector
|
||||
this.set("usernames", null);
|
||||
},
|
||||
|
||||
save: function() {
|
||||
var self = this,
|
||||
group = this.get('model');
|
||||
group = this.get('model'),
|
||||
groupsController = this.get("controllers.adminGroupsType");
|
||||
|
||||
self.set('disableSave', true);
|
||||
this.set('disableSave', true);
|
||||
|
||||
var promise;
|
||||
if (group.get('id')) {
|
||||
if (group.get("id")) {
|
||||
promise = group.save();
|
||||
} else {
|
||||
promise = group.create().then(function() {
|
||||
var groupsController = self.get('controllers.adminGroups');
|
||||
groupsController.addObject(group);
|
||||
});
|
||||
}
|
||||
promise.then(function() {
|
||||
self.send('showGroup', group);
|
||||
self.transitionToRoute("adminGroup", group);
|
||||
}, function(e) {
|
||||
var message = $.parseJSON(e.responseText).errors;
|
||||
bootbox.alert(message);
|
||||
@ -91,12 +91,13 @@ export default Em.ObjectController.extend({
|
||||
|
||||
destroy: function() {
|
||||
var group = this.get('model'),
|
||||
groupsController = this.get('controllers.adminGroups'),
|
||||
groupsController = this.get('controllers.adminGroupsType'),
|
||||
self = this;
|
||||
|
||||
bootbox.confirm(I18n.t("admin.groups.delete_confirm"), I18n.t("no_value"), I18n.t("yes_value"), function(result) {
|
||||
if (result) {
|
||||
self.set('disableSave', true);
|
||||
this.set('disableSave', true);
|
||||
|
||||
bootbox.confirm(I18n.t("admin.groups.delete_confirm"), I18n.t("no_value"), I18n.t("yes_value"), function(confirmed) {
|
||||
if (confirmed) {
|
||||
group.destroy().then(function() {
|
||||
groupsController.get('model').removeObject(group);
|
||||
self.transitionToRoute('adminGroups.index');
|
||||
|
||||
@ -0,0 +1,16 @@
|
||||
export default Ember.ArrayController.extend({
|
||||
sortProperties: ['name'],
|
||||
refreshingAutoGroups: false,
|
||||
|
||||
actions: {
|
||||
refreshAutoGroups: function(){
|
||||
var self = this;
|
||||
this.set('refreshingAutoGroups', true);
|
||||
Discourse.ajax('/admin/groups/refresh_automatic_groups', {type: 'POST'}).then(function() {
|
||||
self.transitionToRoute("adminGroupsType", "automatic").then(function() {
|
||||
self.set('refreshingAutoGroups', false);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -1,24 +0,0 @@
|
||||
export default Ember.ArrayController.extend({
|
||||
sortProperties: ['name'],
|
||||
|
||||
refreshingAutoGroups: false,
|
||||
|
||||
actions: {
|
||||
refreshAutoGroups: function(){
|
||||
var self = this,
|
||||
groups = this.get('model');
|
||||
|
||||
self.set('refreshingAutoGroups', true);
|
||||
this.transitionToRoute('adminGroups.index').then(function() {
|
||||
Discourse.ajax('/admin/groups/refresh_automatic_groups', {type: 'POST'}).then(function() {
|
||||
return Discourse.Group.findAll().then(function(newGroups) {
|
||||
groups.clear();
|
||||
groups.addObjects(newGroups);
|
||||
}).finally(function() {
|
||||
self.set('refreshingAutoGroups', false);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -17,15 +17,18 @@ export default Ember.ObjectController.extend(BufferedContent, {
|
||||
if (this.get('required')) {
|
||||
ret.push(I18n.t('admin.user_fields.required.enabled'));
|
||||
}
|
||||
if (this.get('show_on_profile')) {
|
||||
ret.push(I18n.t('admin.user_fields.show_on_profile.enabled'));
|
||||
}
|
||||
|
||||
return ret.join(', ');
|
||||
}.property('editable', 'required'),
|
||||
}.property('editable', 'required', 'show_on_profile'),
|
||||
|
||||
actions: {
|
||||
save: function() {
|
||||
var self = this;
|
||||
|
||||
var attrs = this.get('buffered').getProperties('name', 'description', 'field_type', 'editable', 'required');
|
||||
var attrs = this.get('buffered').getProperties('name', 'description', 'field_type', 'editable', 'required', 'show_on_profile');
|
||||
|
||||
this.get('model').save(attrs).then(function(res) {
|
||||
self.set('model.id', res.user_field.id);
|
||||
|
||||
@ -1,17 +1,20 @@
|
||||
Discourse.AdminGroupRoute = Discourse.Route.extend({
|
||||
export default Discourse.Route.extend({
|
||||
|
||||
model: function(params) {
|
||||
var groups = this.modelFor('adminGroups'),
|
||||
var groups = this.modelFor('adminGroupsType'),
|
||||
group = groups.findProperty('name', params.name);
|
||||
|
||||
if (!group) { return this.transitionTo('adminGroups.index'); }
|
||||
|
||||
return group;
|
||||
},
|
||||
|
||||
setupController: function(controller, model) {
|
||||
controller.set("model", model);
|
||||
// clear the user selector
|
||||
controller.set("usernames", null);
|
||||
// load the members of the group
|
||||
model.findMembers();
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
@ -0,0 +1,5 @@
|
||||
export default Discourse.Route.extend({
|
||||
redirect: function() {
|
||||
this.transitionTo("adminGroupsType", "custom");
|
||||
}
|
||||
})
|
||||
17
app/assets/javascripts/admin/routes/admin-groups-type.js.es6
Normal file
17
app/assets/javascripts/admin/routes/admin-groups-type.js.es6
Normal file
@ -0,0 +1,17 @@
|
||||
export default Discourse.Route.extend({
|
||||
model: function(params) {
|
||||
return Discourse.Group.findAll().then(function(groups) {
|
||||
return groups.filterBy("type", params.type);
|
||||
});
|
||||
},
|
||||
|
||||
actions: {
|
||||
newGroup: function() {
|
||||
var self = this;
|
||||
this.transitionTo("adminGroupsType", "custom").then(function() {
|
||||
var group = Discourse.Group.create({ automatic: false, visible: true });
|
||||
self.transitionTo("adminGroup", group);
|
||||
})
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -1,5 +1,7 @@
|
||||
export default function() {
|
||||
this.resource('admin', function() {
|
||||
export default {
|
||||
resource: 'admin',
|
||||
|
||||
map: function() {
|
||||
this.route('dashboard', { path: '/' });
|
||||
this.resource('adminSiteSettings', { path: '/site_settings' }, function() {
|
||||
this.resource('adminSiteSettingsCategory', { path: 'category/:category_id'} );
|
||||
@ -40,8 +42,10 @@ export default function() {
|
||||
this.route('screenedUrls', { path: '/screened_urls' });
|
||||
});
|
||||
|
||||
this.resource('adminGroups', { path: '/groups'}, function() {
|
||||
this.resource('adminGroup', { path: '/:name' });
|
||||
this.resource('adminGroups', { path: '/groups' }, function() {
|
||||
this.resource('adminGroupsType', { path: '/:type' }, function() {
|
||||
this.resource('adminGroup', { path: '/:name' });
|
||||
});
|
||||
});
|
||||
|
||||
this.resource('adminUsers', { path: '/users' }, function() {
|
||||
@ -51,13 +55,12 @@ export default function() {
|
||||
});
|
||||
|
||||
this.resource('adminUsersList', { path: '/list' }, function() {
|
||||
this.route('show', {path: '/:filter'});
|
||||
this.route('show', { path: '/:filter' });
|
||||
});
|
||||
});
|
||||
|
||||
this.resource('adminBadges', { path: '/badges' }, function() {
|
||||
this.route('show', { path: '/:badge_id' });
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@ -1,31 +0,0 @@
|
||||
/**
|
||||
Handles routes for admin groups
|
||||
|
||||
@class AdminGroupsRoute
|
||||
@extends Discourse.Route
|
||||
@namespace Discourse
|
||||
@module Discourse
|
||||
**/
|
||||
Discourse.AdminGroupsRoute = Discourse.Route.extend({
|
||||
model: function() {
|
||||
return Discourse.Group.findAll();
|
||||
},
|
||||
|
||||
actions: {
|
||||
showGroup: function(g) {
|
||||
// This hack is needed because the autocomplete plugin does not
|
||||
// refresh properly when the underlying data changes. TODO should
|
||||
// be to update the plugin so it works properly and remove this hack.
|
||||
var self = this;
|
||||
this.transitionTo('adminGroups.index').then(function() {
|
||||
self.transitionTo('adminGroup', g);
|
||||
});
|
||||
},
|
||||
|
||||
newGroup: function(){
|
||||
var group = Discourse.Group.create({ visible: true });
|
||||
this.send('showGroup', group);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@ -13,7 +13,7 @@
|
||||
<li>{{#link-to 'adminBadges.index'}}{{i18n 'admin.badges.title'}}{{/link-to}}</li>
|
||||
{{/if}}
|
||||
{{#if currentUser.admin}}
|
||||
<li>{{#link-to 'adminGroups.index'}}{{i18n 'admin.groups.title'}}{{/link-to}}</li>
|
||||
<li>{{#link-to 'adminGroups'}}{{i18n 'admin.groups.title'}}{{/link-to}}</li>
|
||||
{{/if}}
|
||||
<li>{{#link-to 'adminEmail'}}{{i18n 'admin.email.title'}}{{/link-to}}</li>
|
||||
<li>{{#link-to 'adminFlags'}}{{i18n 'admin.flags.title'}}{{/link-to}}</li>
|
||||
@ -23,6 +23,7 @@
|
||||
<li>{{#link-to 'admin.api'}}{{i18n 'admin.api.title'}}{{/link-to}}</li>
|
||||
<li>{{#link-to 'admin.backups'}}{{i18n 'admin.backups.title'}}{{/link-to}}</li>
|
||||
{{/if}}
|
||||
{{plugin-outlet "admin-menu" tagName="li"}}
|
||||
</ul>
|
||||
|
||||
<div class='boxed white admin-content'>
|
||||
|
||||
@ -118,7 +118,7 @@
|
||||
</p>
|
||||
<p class="actions">
|
||||
<small>{{i18n 'admin.dashboard.last_checked'}}: {{problemsTimestamp}}</small>
|
||||
<button {{action "refreshProblems"}} class="btn btn-small"><i class="fa fa-refresh"></i>{{i18n 'admin.dashboard.refresh_problems'}}</button>
|
||||
{{d-button action="refreshProblems" class="btn-small" icon="refresh" label="admin.dashboard.refresh_problems"}}
|
||||
</p>
|
||||
</div>
|
||||
<div class="clearfix"></div>
|
||||
@ -130,7 +130,7 @@
|
||||
<div class="problem-messages">
|
||||
<p>
|
||||
{{i18n 'admin.dashboard.no_problems'}}
|
||||
<button {{action "refreshProblems"}} class="btn btn-small"><i class="fa fa-refresh"></i>{{i18n 'admin.dashboard.refresh_problems'}}</button>
|
||||
{{d-button action="refreshProblems" class="btn-small" icon="refresh" label="admin.dashboard.refresh_problems"}}
|
||||
</p>
|
||||
</div>
|
||||
<div class="clearfix"></div>
|
||||
|
||||
@ -43,6 +43,17 @@
|
||||
{{combo-box name="alias" valueAttribute="value" value=alias_level content=aliasLevelOptions}}
|
||||
</div>
|
||||
|
||||
{{#unless automatic}}
|
||||
<div>
|
||||
<label for="automatic_membership">{{i18n 'admin.groups.automatic_membership_email_domains'}}</label>
|
||||
{{list-setting name="automatic_membership" settingValue=emailDomains}}
|
||||
<label>
|
||||
{{input type="checkbox" checked=automatic_membership_retroactive}}
|
||||
{{i18n 'admin.groups.automatic_membership_retroactive'}}
|
||||
</label>
|
||||
</div>
|
||||
{{/unless}}
|
||||
|
||||
<div class='buttons'>
|
||||
<button {{action "save"}} {{bind-attr disabled="disableSave"}} class='btn btn-primary'>{{i18n 'admin.customize.save'}}</button>
|
||||
{{#unless automatic}}
|
||||
|
||||
@ -1,20 +1,11 @@
|
||||
<div class='row groups'>
|
||||
<div class='content-list span6'>
|
||||
<h3>{{i18n 'admin.groups.edit'}}</h3>
|
||||
<ul>
|
||||
{{#each group in arrangedContent}}
|
||||
<li>
|
||||
<a href='#' {{action "showGroup" group}}>{{group.name}} <span class="count">{{group.userCountDisplay}}</span></a>
|
||||
</li>
|
||||
{{/each}}
|
||||
<div class="admin-controls">
|
||||
<div class="span15">
|
||||
<ul class="nav nav-pills">
|
||||
<li>{{#link-to "adminGroupsType" "custom"}}{{i18n 'admin.groups.custom'}}{{/link-to}}</li>
|
||||
<li>{{#link-to "adminGroupsType" "automatic"}}{{i18n 'admin.groups.automatic'}}{{/link-to}}</li>
|
||||
</ul>
|
||||
<div class='controls'>
|
||||
<button class='btn' {{bind-attr disabled="refreshingAutoGroups"}} {{action "refreshAutoGroups"}}><i class="fa fa-refresh"></i>{{i18n 'admin.groups.refresh'}}</button>
|
||||
<button class='btn' {{action "newGroup"}}><i class="fa fa-plus"></i>{{i18n 'admin.groups.new'}}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class='content-editor'>
|
||||
{{outlet}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="admin-container">
|
||||
{{outlet}}
|
||||
</div>
|
||||
|
||||
@ -1 +0,0 @@
|
||||
{{i18n 'admin.groups.about'}}
|
||||
20
app/assets/javascripts/admin/templates/groups_type.hbs
Normal file
20
app/assets/javascripts/admin/templates/groups_type.hbs
Normal file
@ -0,0 +1,20 @@
|
||||
<div class='row groups'>
|
||||
<div class='content-list span6'>
|
||||
<h3>{{i18n 'admin.groups.edit'}}</h3>
|
||||
<ul>
|
||||
{{#each group in controller}}
|
||||
<li>
|
||||
{{#link-to "adminGroup" group.type group.name}}{{group.name}} <span class="count">{{group.userCountDisplay}}</span>{{/link-to}}
|
||||
</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
<div class='controls'>
|
||||
{{d-button action="newGroup" icon="plus" label="admin.groups.new"}}
|
||||
{{d-button action="refreshAutoGroups" icon="refresh" label="admin.groups.refresh" disabled=refreshingAutoGroups}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class='content-editor'>
|
||||
{{outlet}}
|
||||
</div>
|
||||
</div>
|
||||
@ -3,7 +3,7 @@
|
||||
<div>
|
||||
{{i18n 'admin.dashboard.reports.start_date'}} {{input type="date" value=startDate}}
|
||||
{{i18n 'admin.dashboard.reports.end_date'}} {{input type="date" value=endDate}}
|
||||
<button {{action "refreshReport"}} class='btn btn-primary'>{{i18n 'admin.dashboard.reports.refresh_report'}}</button>
|
||||
{{d-button action="refreshReport" class="btn-primary" label="admin.dashboard.reports.refresh_report" icon="refresh"}}
|
||||
</div>
|
||||
|
||||
<div class='view-options'>
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
</div>
|
||||
<div class="setting-value">
|
||||
{{combo-box valueAttribute="value" content=validValues value=value none=allowsNone}}
|
||||
{{view.preview}}
|
||||
<div class='desc'>{{unbound description}}</div>
|
||||
</div>
|
||||
{{#if dirty}}
|
||||
|
||||
@ -32,6 +32,11 @@
|
||||
{{input type="checkbox" checked=f.buffered.required}} {{i18n 'admin.user_fields.required.title'}}
|
||||
</label>
|
||||
</div>
|
||||
<div class='form-element'>
|
||||
<label>
|
||||
{{input type="checkbox" checked=f.buffered.show_on_profile}} {{i18n 'admin.user_fields.show_on_profile.title'}}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
{{else}}
|
||||
<div class="row">
|
||||
|
||||
@ -9,6 +9,16 @@
|
||||
Discourse.SiteSettingView = Discourse.View.extend(Discourse.ScrollTop, {
|
||||
classNameBindings: [':row', ':setting', 'content.overridden'],
|
||||
|
||||
preview: function() {
|
||||
var preview = this.get('content.preview');
|
||||
if(preview){
|
||||
return new Handlebars.SafeString("<div class='preview'>" +
|
||||
preview.replace("{{value}}",this.get('content.value')) +
|
||||
"</div>"
|
||||
);
|
||||
}
|
||||
}.property('content.value'),
|
||||
|
||||
templateName: function() {
|
||||
// If we're editing a boolean, show a checkbox
|
||||
if (this.get('content.type') === 'bool') return 'admin/templates/site_settings/setting_bool';
|
||||
|
||||
@ -24,6 +24,12 @@ window.Discourse = Ember.Application.createWithMixins(Discourse.Ajax, {
|
||||
return u + url;
|
||||
},
|
||||
|
||||
getURLWithCDN: function(url) {
|
||||
url = this.getURL(url);
|
||||
if (Discourse.CDN) { url = Discourse.CDN + url; }
|
||||
return url;
|
||||
},
|
||||
|
||||
Resolver: DiscourseResolver,
|
||||
|
||||
_titleChanged: function() {
|
||||
|
||||
@ -14,9 +14,6 @@ export default Em.Component.extend(UploadMixin, {
|
||||
uploadDone: function(data) {
|
||||
var self = this;
|
||||
|
||||
// indicates the users is using an uploaded avatar
|
||||
this.set("custom_avatar_upload_id", data.result.upload_id);
|
||||
|
||||
// display a warning whenever the image is not a square
|
||||
this.set("imageIsNotASquare", data.result.width !== data.result.height);
|
||||
|
||||
@ -26,6 +23,11 @@ export default Em.Component.extend(UploadMixin, {
|
||||
// this will also capture the first frame of animated avatars when they're not allowed
|
||||
Discourse.Utilities.cropAvatar(data.result.url, data.files[0].type).then(function(avatarTemplate) {
|
||||
self.set("uploadedAvatarTemplate", avatarTemplate);
|
||||
|
||||
// indicates the users is using an uploaded avatar (must happen after cropping, otherwise
|
||||
// we will attempt to load an invalid avatar and cache a redirect to old one, uploadedAvatarTemplate
|
||||
// trumps over custom avatar upload id)
|
||||
self.set("custom_avatar_upload_id", data.result.upload_id);
|
||||
});
|
||||
|
||||
// the upload is now done
|
||||
|
||||
@ -1,13 +1,12 @@
|
||||
/**
|
||||
Renders a drop down for selecting a category
|
||||
var get = Ember.get;
|
||||
|
||||
@class CategoryDropComponent
|
||||
@extends Ember.Component
|
||||
@namespace Discourse
|
||||
@module Discourse
|
||||
**/
|
||||
export default Ember.Component.extend({
|
||||
classNameBindings: ['category::no-category', 'categories:has-drop'],
|
||||
classNameBindings: ['category::no-category', 'categories:has-drop','categoryStyle'],
|
||||
|
||||
categoryStyle: function(){
|
||||
return Discourse.SiteSettings.category_style;
|
||||
}.property(),
|
||||
|
||||
tagName: 'li',
|
||||
|
||||
iconClass: function() {
|
||||
@ -44,11 +43,20 @@ export default Ember.Component.extend({
|
||||
|
||||
badgeStyle: function() {
|
||||
var category = this.get('category');
|
||||
|
||||
if (category) {
|
||||
return Discourse.HTML.categoryStyle(category);
|
||||
} else {
|
||||
return "background-color: #eee; color: #333";
|
||||
var color = get(category, 'color'),
|
||||
textColor = get(category, 'text_color');
|
||||
|
||||
if (color || textColor) {
|
||||
var style = "";
|
||||
if (color) { style += "background-color: #" + color + "; border-color: #" + color + ";"; }
|
||||
if (textColor) { style += "color: #" + textColor + "; "; }
|
||||
return style;
|
||||
}
|
||||
}
|
||||
|
||||
return "background-color: #eee; color: #333";
|
||||
}.property('category'),
|
||||
|
||||
clickEventName: function() {
|
||||
@ -81,24 +89,33 @@ export default Ember.Component.extend({
|
||||
self.close();
|
||||
});
|
||||
|
||||
$('html').on(this.get('clickEventName'), function(e) {
|
||||
var $target = $(e.target),
|
||||
closest = $target.closest($dropdown);
|
||||
Em.run.next(function(){
|
||||
self.$('.cat a').add('html').on(self.get('clickEventName'), function(e) {
|
||||
var $target = $(e.target),
|
||||
closest = $target.closest($dropdown);
|
||||
|
||||
return ($(e.currentTarget).hasClass('badge-category') || (closest.length && closest[0] === $dropdown)) ? true : self.close();
|
||||
if ($(e.currentTarget).hasClass('badge-wrapper')){
|
||||
self.close();
|
||||
}
|
||||
|
||||
return ($(e.currentTarget).hasClass('badge-category') || (closest.length && closest[0] === $dropdown)) ? true : self.close();
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
close: function() {
|
||||
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() {
|
||||
$('html').off(this.get('clickEventName'));
|
||||
this.$('a[data-drop-close]').off('click.category-drop');
|
||||
this.removeEvents();
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
import { categoryBadgeHTML } from 'discourse/helpers/category-link';
|
||||
|
||||
export default Ember.Component.extend({
|
||||
|
||||
_initializeAutocomplete: function(){
|
||||
@ -25,7 +27,7 @@ export default Ember.Component.extend({
|
||||
},
|
||||
template: template,
|
||||
transformComplete: function(category) {
|
||||
return Discourse.HTML.categoryBadge(category, {allowUncategorized: true});
|
||||
return categoryBadgeHTML(category, {allowUncategorized: true});
|
||||
}
|
||||
});
|
||||
}.on('didInsertElement')
|
||||
|
||||
@ -7,7 +7,7 @@ export default Em.Component.extend({
|
||||
categoryUrl = Discourse.getURL('/c/') + Discourse.Category.slugFor(category),
|
||||
categoryName = Handlebars.Utils.escapeExpression(category.get('name'));
|
||||
|
||||
if (category.get('read_restricted')) { buffer.push("<i class='fa fa-group'></i>"); }
|
||||
if (category.get('read_restricted')) { buffer.push("<i class='fa fa-lock'></i>"); }
|
||||
|
||||
buffer.push("<a href='" + categoryUrl + "'>");
|
||||
buffer.push("<span class='category-name'>" + categoryName + "</span>");
|
||||
|
||||
31
app/assets/javascripts/discourse/components/d-button.js.es6
Normal file
31
app/assets/javascripts/discourse/components/d-button.js.es6
Normal file
@ -0,0 +1,31 @@
|
||||
import { iconHTML } from 'discourse/helpers/fa-icon';
|
||||
|
||||
export default Ember.Component.extend({
|
||||
tagName: 'button',
|
||||
classNameBindings: [':btn'],
|
||||
attributeBindings: ['disabled', 'translatedTitle:title'],
|
||||
|
||||
translatedTitle: function() {
|
||||
var label = this.get('label');
|
||||
if (label) {
|
||||
return I18n.t(this.get('label'));
|
||||
}
|
||||
}.property('label'),
|
||||
|
||||
render: function(buffer) {
|
||||
var title = this.get('translatedTitle'),
|
||||
icon = this.get('icon');
|
||||
|
||||
if (title || icon) {
|
||||
if (icon) { buffer.push(iconHTML(icon) + ' '); }
|
||||
if (title) { buffer.push(title); }
|
||||
} else {
|
||||
// If no label or icon is present, yield
|
||||
return this._super();
|
||||
}
|
||||
},
|
||||
|
||||
click: function() {
|
||||
this.sendAction("action", this.get("actionParam"));
|
||||
}
|
||||
});
|
||||
@ -0,0 +1,4 @@
|
||||
export default Ember.Component.extend({
|
||||
tagName: 'span',
|
||||
classNameBindings: [':fa-stack'],
|
||||
});
|
||||
@ -10,6 +10,10 @@ export default Ember.Component.extend(StringBuffer, {
|
||||
notices.push(I18n.t("read_only_mode.enabled"));
|
||||
}
|
||||
|
||||
if (this.siteSettings.disable_emails) {
|
||||
notices.push(I18n.t("emails_are_disabled"));
|
||||
}
|
||||
|
||||
if (Discourse.User.currentProp('admin') && this.siteSettings.show_create_topics_notice) {
|
||||
var topic_count = 0,
|
||||
post_count = 0;
|
||||
|
||||
@ -18,6 +18,10 @@ export default Ember.Component.extend({
|
||||
return !!this.get('changeSort');
|
||||
}.property(),
|
||||
|
||||
skipHeader: function() {
|
||||
return Discourse.Mobile.mobileView;
|
||||
}.property(),
|
||||
|
||||
showLikes: function(){
|
||||
return this.get('order') === "likes";
|
||||
}.property('order'),
|
||||
|
||||
@ -16,18 +16,15 @@ export default TextField.extend({
|
||||
return selected;
|
||||
}
|
||||
|
||||
var template = this.container.lookup('template:user-selector-autocomplete.raw');
|
||||
$(this.get('element')).val(this.get('usernames')).autocomplete({
|
||||
template: template,
|
||||
|
||||
this.$().val(this.get('usernames')).autocomplete({
|
||||
template: this.container.lookup('template:user-selector-autocomplete.raw'),
|
||||
disabled: this.get('disabled'),
|
||||
single: this.get('single'),
|
||||
allowAny: this.get('allowAny'),
|
||||
|
||||
dataSource: function(term) {
|
||||
term = term.replace(/[^a-zA-Z0-9_]/, '');
|
||||
return userSearch({
|
||||
term: term,
|
||||
term: term.replace(/[^a-zA-Z0-9_]/, ''),
|
||||
topicId: self.get('topicId'),
|
||||
exclude: excludedUsernames(),
|
||||
includeGroups: includeGroups
|
||||
@ -58,6 +55,15 @@ export default TextField.extend({
|
||||
}
|
||||
|
||||
});
|
||||
}.on('didInsertElement')
|
||||
}.on('didInsertElement'),
|
||||
|
||||
// THIS IS A HUGE HACK TO SUPPORT CLEARING THE INPUT
|
||||
_clearInput: function() {
|
||||
if (arguments.length > 1) {
|
||||
if (Em.isEmpty(this.get("usernames"))) {
|
||||
this.$().parent().find("a").click();
|
||||
}
|
||||
}
|
||||
}.observes("usernames")
|
||||
|
||||
});
|
||||
|
||||
@ -39,7 +39,34 @@ export default DiscourseController.extend({
|
||||
|
||||
// Import a quote from the post
|
||||
importQuote: function() {
|
||||
this.get('model').importQuote();
|
||||
var postStream = this.get('topic.postStream'),
|
||||
postId = this.get('model.post.id');
|
||||
|
||||
// If there is no current post, use the first post id from the stream
|
||||
if (!postId && postStream) {
|
||||
postId = postStream.get('firstPostId');
|
||||
}
|
||||
|
||||
// If we're editing a post, fetch the reply when importing a quote
|
||||
if (this.get('model.editingPost')) {
|
||||
var replyToPostNumber = this.get('model.post.reply_to_post_number');
|
||||
if (replyToPostNumber) {
|
||||
var replyPost = postStream.get('posts').findBy('post_number', replyToPostNumber);
|
||||
if (replyPost) {
|
||||
postId = replyPost.get('id');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (postId) {
|
||||
this.set('model.loading', true);
|
||||
var composer = this;
|
||||
return Discourse.Post.load(postId).then(function(post) {
|
||||
var quote = Discourse.Quote.build(post, post.get("raw"))
|
||||
composer.appendBlockAtCursor(quote);
|
||||
composer.set('model.loading', false);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
cancel: function() {
|
||||
@ -212,7 +239,9 @@ export default DiscourseController.extend({
|
||||
}
|
||||
|
||||
if ((!composer.get('replyingToTopic')) || (!Discourse.User.currentProp('disable_jump_reply'))) {
|
||||
Discourse.URL.routeTo(opts.post.get('url'));
|
||||
if (opts.post) {
|
||||
Discourse.URL.routeTo(opts.post.get('url'));
|
||||
}
|
||||
}
|
||||
}, function(error) {
|
||||
composer.set('disableDrafts', false);
|
||||
|
||||
@ -8,7 +8,6 @@ export default DiscoveryController.extend({
|
||||
|
||||
actions: {
|
||||
refresh: function() {
|
||||
var self = this;
|
||||
|
||||
// Don't refresh if we're still loading
|
||||
if (this.get('controllers.discovery.loading')) { return; }
|
||||
@ -17,7 +16,17 @@ export default DiscoveryController.extend({
|
||||
// router and ember throws an error due to missing `handlerInfos`.
|
||||
// Lesson learned: Don't call `loading` yourself.
|
||||
this.set('controllers.discovery.loading', true);
|
||||
Discourse.CategoryList.list('categories').then(function(list) {
|
||||
|
||||
var parentCategory = this.get('model.parentCategory');
|
||||
var promise;
|
||||
if (parentCategory) {
|
||||
promise = Discourse.CategoryList.listForParent(parentCategory);
|
||||
} else {
|
||||
promise = Discourse.CategoryList.list();
|
||||
}
|
||||
|
||||
var self = this;
|
||||
promise.then(function(list) {
|
||||
self.set('model', list);
|
||||
self.send('loadingComplete');
|
||||
});
|
||||
|
||||
@ -50,6 +50,8 @@ var controllerOpts = {
|
||||
// Lesson learned: Don't call `loading` yourself.
|
||||
this.set('controllers.discovery.loading', true);
|
||||
Discourse.TopicList.find(filter).then(function(list) {
|
||||
Discourse.TopicList.hideUniformCategory(list, self.get('category'));
|
||||
|
||||
self.setProperties({ model: list, selected: [] });
|
||||
|
||||
var tracking = Discourse.TopicTrackingState.current();
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import ModalFunctionality from 'discourse/mixins/modal-functionality';
|
||||
import ObjectController from 'discourse/controllers/object';
|
||||
import { categoryBadgeHTML } from 'discourse/helpers/category-link';
|
||||
|
||||
// Modal for editing / creating a category
|
||||
export default ObjectController.extend(ModalFunctionality, {
|
||||
@ -69,7 +70,7 @@ export default ObjectController.extend(ModalFunctionality, {
|
||||
parent_category_id: parseInt(this.get('parent_category_id'),10),
|
||||
read_restricted: this.get('model.read_restricted')
|
||||
});
|
||||
return Discourse.HTML.categoryBadge(c, {showParent: true, link: false});
|
||||
return categoryBadgeHTML(c, {link: false});
|
||||
}.property('parent_category_id', 'categoryName', 'color', 'text_color'),
|
||||
|
||||
// background colors are available as a pipe-separated string
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import ModalFunctionality from 'discourse/mixins/modal-functionality';
|
||||
import ObjectController from 'discourse/controllers/object';
|
||||
import { categoryBadgeHTML } from 'discourse/helpers/category-link';
|
||||
|
||||
// This controller handles displaying of history
|
||||
export default ObjectController.extend(ModalFunctionality, {
|
||||
@ -22,14 +23,14 @@ export default ObjectController.extend(ModalFunctionality, {
|
||||
|
||||
hide: function(postId, postVersion) {
|
||||
var self = this;
|
||||
Discourse.Post.hideRevision(postId, postVersion).then(function (result) {
|
||||
Discourse.Post.hideRevision(postId, postVersion).then(function () {
|
||||
self.refresh(postId, postVersion);
|
||||
});
|
||||
},
|
||||
|
||||
show: function(postId, postVersion) {
|
||||
var self = this;
|
||||
Discourse.Post.showRevision(postId, postVersion).then(function (result) {
|
||||
Discourse.Post.showRevision(postId, postVersion).then(function () {
|
||||
self.refresh(postId, postVersion);
|
||||
});
|
||||
},
|
||||
@ -65,47 +66,38 @@ export default ObjectController.extend(ModalFunctionality, {
|
||||
displayingSideBySideMarkdown: Em.computed.equal("viewMode", "side_by_side_markdown"),
|
||||
|
||||
previousCategory: function() {
|
||||
var changes = this.get("category_changes");
|
||||
var changes = this.get("category_id_changes");
|
||||
if (changes) {
|
||||
var category = Discourse.Category.findById(changes["previous"]);
|
||||
return Discourse.HTML.categoryBadge(category, { allowUncategorized: true });
|
||||
return categoryBadgeHTML(category, { allowUncategorized: true });
|
||||
}
|
||||
}.property("category_changes"),
|
||||
}.property("category_id_changes"),
|
||||
|
||||
currentCategory: function() {
|
||||
var changes = this.get("category_changes");
|
||||
var changes = this.get("category_id_changes");
|
||||
if (changes) {
|
||||
var category = Discourse.Category.findById(changes["current"]);
|
||||
return Discourse.HTML.categoryBadge(category, { allowUncategorized: true });
|
||||
return categoryBadgeHTML(category, { allowUncategorized: true });
|
||||
}
|
||||
}.property("category_changes"),
|
||||
}.property("category_id_changes"),
|
||||
|
||||
wiki_diff: function() {
|
||||
var changes = this.get("wiki_changes")
|
||||
if (changes) {
|
||||
return changes["current"] ?
|
||||
'<span class="fa-stack"><i class="fa fa-pencil-square-o fa-stack-2x"></i></span>' :
|
||||
'<span class="fa-stack"><i class="fa fa-pencil-square-o fa-stack-2x"></i><i class="fa fa-ban fa-stack-2x"></i></span>';
|
||||
}
|
||||
}.property("wiki_changes"),
|
||||
wikiDisabled: function() {
|
||||
var changes = this.get("wiki_changes");
|
||||
return changes && !changes['current'];
|
||||
}.property('wiki_changes'),
|
||||
|
||||
post_type_diff: function () {
|
||||
var moderator = Discourse.Site.currentProp('post_types.moderator_action');
|
||||
postTypeDisabled: function () {
|
||||
var changes = this.get("post_type_changes");
|
||||
if (changes) {
|
||||
return changes["current"] == moderator ?
|
||||
'<span class="fa-stack"><i class="fa fa-shield fa-stack-2x"></i></span>' :
|
||||
'<span class="fa-stack"><i class="fa fa-shield fa-stack-2x"></i><i class="fa fa-ban fa-stack-2x"></i></span>';
|
||||
}
|
||||
return (changes && changes['current'] !== this.site.get('post_types.moderator_action'));
|
||||
}.property("post_type_changes"),
|
||||
|
||||
title_diff: function() {
|
||||
titleDiff: function() {
|
||||
var viewMode = this.get("viewMode");
|
||||
if (viewMode === "side_by_side_markdown") { viewMode = "side_by_side"; }
|
||||
return this.get("title_changes." + viewMode);
|
||||
}.property("viewMode", "title_changes"),
|
||||
|
||||
body_diff: function() {
|
||||
bodyDiff: function() {
|
||||
return this.get("body_changes." + this.get("viewMode"));
|
||||
}.property("viewMode", "body_changes"),
|
||||
|
||||
|
||||
@ -160,6 +160,12 @@ export default DiscourseController.extend(ModalFunctionality, {
|
||||
this.set('authenticate', null);
|
||||
return;
|
||||
}
|
||||
if (options.suspended) {
|
||||
this.send('showLogin');
|
||||
this.flash(options.suspended_message, 'error');
|
||||
this.set('authenticate', null);
|
||||
return;
|
||||
}
|
||||
// Reload the page if we're authenticated
|
||||
if (options.authenticated) {
|
||||
if (window.location.pathname === Discourse.getURL('/login')) {
|
||||
|
||||
@ -35,15 +35,19 @@ export default ObjectController.extend(CanCheckEmails, {
|
||||
canEditName: Discourse.computed.setting('enable_names'),
|
||||
|
||||
canSelectTitle: function() {
|
||||
return Discourse.SiteSettings.enable_badges && this.get('model.has_title_badges');
|
||||
return this.siteSettings.enable_badges && this.get('model.has_title_badges');
|
||||
}.property('model.badge_count'),
|
||||
|
||||
canChangePassword: function() {
|
||||
return !Discourse.SiteSettings.enable_sso && Discourse.SiteSettings.enable_local_logins;
|
||||
return !this.siteSettings.enable_sso && this.siteSettings.enable_local_logins;
|
||||
}.property(),
|
||||
|
||||
canReceiveDigest: function() {
|
||||
return !this.siteSettings.disable_digest_emails;
|
||||
}.property(),
|
||||
|
||||
availableLocales: function() {
|
||||
return Discourse.SiteSettings.available_locales.split('|').map( function(s) {
|
||||
return this.siteSettings.available_locales.split('|').map( function(s) {
|
||||
return {name: s, value: s};
|
||||
});
|
||||
}.property(),
|
||||
@ -166,5 +170,3 @@ export default ObjectController.extend(CanCheckEmails, {
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
||||
@ -3,7 +3,9 @@ export default Em.ObjectController.extend({
|
||||
|
||||
actions: {
|
||||
markFaqRead: function() {
|
||||
Discourse.ajax("/users/read-faq", { method: "POST" });
|
||||
if (Discourse.User.current()) {
|
||||
Discourse.ajax("/users/read-faq", { method: "POST" });
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@ -42,6 +42,21 @@ export default ObjectController.extend(CanCheckEmails, {
|
||||
return this.get('can_be_deleted') && this.get('can_delete_all_posts');
|
||||
}.property('can_be_deleted', 'can_delete_all_posts'),
|
||||
|
||||
publicUserFields: function() {
|
||||
var siteUserFields = this.site.get('user_fields');
|
||||
if (!Ember.isEmpty(siteUserFields)) {
|
||||
var userFields = this.get('user_fields');
|
||||
return siteUserFields.filterProperty('show_on_profile', true).sortBy('id').map(function(uf) {
|
||||
var val = userFields ? userFields[uf.get('id').toString()] : null;
|
||||
if (Ember.isEmpty(val)) {
|
||||
return null;
|
||||
} else {
|
||||
return Ember.Object.create({value: val, field: uf});
|
||||
}
|
||||
}).compact();
|
||||
}
|
||||
}.property('user_fields.@each.value'),
|
||||
|
||||
privateMessagesActive: Em.computed.equal('pmView', 'index'),
|
||||
privateMessagesMineActive: Em.computed.equal('pmView', 'mine'),
|
||||
privateMessagesUnreadActive: Em.computed.equal('pmView', 'unread'),
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
var esc = Handlebars.Utils.escapeExpression;
|
||||
|
||||
Discourse.BBCode.register('quote', {noWrap: true, singlePara: true}, function(contents, bbParams, options) {
|
||||
var params = {'class': 'quote'},
|
||||
username = null;
|
||||
|
||||
@ -1,4 +1,70 @@
|
||||
import registerUnbound from 'discourse/helpers/register-unbound';
|
||||
import { iconHTML } from 'discourse/helpers/fa-icon';
|
||||
|
||||
var get = Em.get,
|
||||
escapeExpression = Handlebars.Utils.escapeExpression;
|
||||
|
||||
function categoryStripe(color, classes) {
|
||||
var style = color ? "style='background-color: #" + color + ";'" : "";
|
||||
return "<span class='" + classes + "' " + style + "></span>";
|
||||
}
|
||||
|
||||
export function categoryBadgeHTML(category, opts) {
|
||||
opts = opts || {};
|
||||
|
||||
if ((!category) ||
|
||||
(!opts.allowUncategorized &&
|
||||
Em.get(category, 'id') === Discourse.Site.currentProp("uncategorized_category_id") &&
|
||||
Discourse.SiteSettings.suppress_uncategorized_badge
|
||||
)
|
||||
) return "";
|
||||
|
||||
var description = get(category, 'description_text'),
|
||||
restricted = get(category, 'read_restricted'),
|
||||
url = Discourse.getURL("/c/") + Discourse.Category.slugFor(category),
|
||||
href = (opts.link === false ? '' : url),
|
||||
tagName = (opts.link === false || opts.link === "false" ? 'span' : 'a'),
|
||||
extraClasses = (opts.extraClasses ? (' ' + opts.extraClasses) : ''),
|
||||
color = get(category, 'color'),
|
||||
html = "",
|
||||
parentCat = null;
|
||||
|
||||
if (!opts.hideParent) {
|
||||
parentCat = Discourse.Category.findById(get(category, 'parent_category_id'));
|
||||
}
|
||||
|
||||
if (parentCat && parentCat !== category) {
|
||||
html += categoryStripe(get(parentCat,'color'), "badge-category-parent-bg");
|
||||
}
|
||||
|
||||
html += categoryStripe(color, "badge-category-bg");
|
||||
|
||||
var classNames = "badge-category clear-badge";
|
||||
if (restricted) { classNames += " restricted"; }
|
||||
|
||||
var textColor = "#" + get(category, 'text_color');
|
||||
|
||||
html += "<span" + ' style="color: ' + textColor + ';" '+
|
||||
'data-drop-close="true" class="' + classNames + '"' +
|
||||
(description ? 'title="' + escapeExpression(description) + '" ' : '') +
|
||||
">";
|
||||
|
||||
var name = escapeExpression(get(category, 'name'));
|
||||
if (restricted) {
|
||||
html += iconHTML('lock') + " " + name;
|
||||
} else {
|
||||
html += name;
|
||||
}
|
||||
html += "</span>";
|
||||
|
||||
if(href){
|
||||
href = " href='" + href + "' ";
|
||||
}
|
||||
|
||||
extraClasses = Discourse.SiteSettings.category_style ? Discourse.SiteSettings.category_style + extraClasses : extraClasses;
|
||||
|
||||
return "<" + tagName + " class='badge-wrapper " + extraClasses + "' " + href + ">" + html + "</" + tagName + ">";
|
||||
}
|
||||
|
||||
export function categoryLinkHTML(category, options) {
|
||||
var categoryOptions = {};
|
||||
@ -9,12 +75,11 @@ export function categoryLinkHTML(category, options) {
|
||||
|
||||
if (options) {
|
||||
if (options.allowUncategorized) { categoryOptions.allowUncategorized = true; }
|
||||
if (options.showParent) { categoryOptions.showParent = true; }
|
||||
if (options.onlyStripe) { categoryOptions.onlyStripe = true; }
|
||||
if (options.link !== undefined) { categoryOptions.link = options.link; }
|
||||
if (options.extraClasses) { categoryOptions.extraClasses = options.extraClasses; }
|
||||
if (options.hideParent) { categoryOptions.hideParent = true; }
|
||||
}
|
||||
return new Handlebars.SafeString(Discourse.HTML.categoryBadge(category, categoryOptions));
|
||||
return new Handlebars.SafeString(categoryBadgeHTML(category, categoryOptions));
|
||||
}
|
||||
|
||||
registerUnbound('category-link', categoryLinkHTML);
|
||||
|
||||
@ -1,12 +1,22 @@
|
||||
Handlebars.registerHelper('fa-icon', function(icon, options) {
|
||||
var labelKey;
|
||||
if (options.hash) { labelKey = options.hash.label; }
|
||||
import registerUnbound from 'discourse/helpers/register-unbound';
|
||||
|
||||
var html = "<i class='fa fa-" + icon + "'";
|
||||
if (labelKey) { html += " aria-hidden='true'"; }
|
||||
export function iconClasses(icon, modifier) {
|
||||
var classes = "fa fa-" + icon;
|
||||
if (modifier) { classes += " fa-" + modifier; }
|
||||
return classes;
|
||||
}
|
||||
|
||||
export function iconHTML(icon, label, modifier) {
|
||||
var html = "<i class='" + iconClasses(icon, modifier) + "'";
|
||||
if (label) { html += " aria-hidden='true'"; }
|
||||
html += "></i>";
|
||||
if (labelKey) {
|
||||
html += "<span class='sr-only'>" + I18n.t(labelKey) + "</span>";
|
||||
if (label) {
|
||||
html += "<span class='sr-only'>" + I18n.t(label) + "</span>";
|
||||
}
|
||||
return new Handlebars.SafeString(html);
|
||||
return html;
|
||||
}
|
||||
|
||||
|
||||
registerUnbound('fa-icon', function(icon, params) {
|
||||
return new Handlebars.SafeString(iconHTML(icon, params.label, params.modifier));
|
||||
});
|
||||
|
||||
@ -88,20 +88,21 @@ export default function(connectionName, options) {
|
||||
if (!_connectorCache) { buildConnectorCache(); }
|
||||
|
||||
if (_connectorCache[connectionName]) {
|
||||
var view;
|
||||
var viewClass;
|
||||
var childViews = _connectorCache[connectionName];
|
||||
|
||||
// If there is more than one view, create a container. Otherwise
|
||||
// just shove it in.
|
||||
if (childViews.length > 1) {
|
||||
view = Ember.ContainerView.extend({
|
||||
viewClass = Ember.ContainerView.extend({
|
||||
childViews: childViews
|
||||
});
|
||||
} else {
|
||||
view = childViews[0];
|
||||
viewClass = childViews[0];
|
||||
}
|
||||
|
||||
delete options.fn; // we don't need the default template since we have a connector
|
||||
return Ember.Handlebars.helpers.view.call(this, view, options);
|
||||
return Ember.Handlebars.helpers.view.call(this, viewClass, options);
|
||||
} else if (options.fn) {
|
||||
// If a block is passed, render its content.
|
||||
return Ember.Handlebars.helpers.view.call(this,
|
||||
|
||||
10
app/assets/javascripts/discourse/initializers/ie9-hax.js.es6
Normal file
10
app/assets/javascripts/discourse/initializers/ie9-hax.js.es6
Normal file
@ -0,0 +1,10 @@
|
||||
export default {
|
||||
name: 'ie9-hacks',
|
||||
initialize: function() {
|
||||
if (!window) { return; }
|
||||
|
||||
// IE9 does not support a console object unless the developer tools are open
|
||||
if (!window.console) { window.console = {}; }
|
||||
if (!window.console.log) { window.console.log = Ember.K; }
|
||||
}
|
||||
};
|
||||
@ -1,11 +1,12 @@
|
||||
import Session from 'discourse/models/session';
|
||||
import AppEvents from 'discourse/lib/app-events';
|
||||
|
||||
export default {
|
||||
name: "inject-objects",
|
||||
initialize: function(container, application) {
|
||||
|
||||
// Inject appEvents everywhere
|
||||
var appEvents = Ember.Object.createWithMixins(Ember.Evented);
|
||||
var appEvents = AppEvents.create();
|
||||
application.register('app-events:main', appEvents, { instantiate: false });
|
||||
|
||||
application.inject('controller', 'appEvents', 'app-events:main');
|
||||
|
||||
@ -1676,7 +1676,7 @@
|
||||
return querypart.replace(/\+/g, " "); // in the query string, a plus and a space are identical
|
||||
});
|
||||
link = decodeURIComponent(link); // unencode first, to prevent double encoding
|
||||
link = encodeURI(link).replace(/'/g, '%27').replace(/\(/g, '%28').replace(/\)/g, '%29');
|
||||
link = encodeURI(link).replace(/#/g, '%23').replace(/'/g, '%27').replace(/\(/g, '%28').replace(/\)/g, '%29');
|
||||
link = link.replace(/\?.*$/, function (querypart) {
|
||||
return querypart.replace(/\+/g, "%2b"); // since we replaced plus with spaces in the query part, all pluses that now appear where originally encoded
|
||||
});
|
||||
|
||||
23
app/assets/javascripts/discourse/lib/app-events.js.es6
Normal file
23
app/assets/javascripts/discourse/lib/app-events.js.es6
Normal file
@ -0,0 +1,23 @@
|
||||
export default Ember.Object.extend(Ember.Evented);
|
||||
|
||||
var id = 1;
|
||||
function newKey() {
|
||||
return "_view_app_event_" + (id++);
|
||||
}
|
||||
|
||||
export function createViewListener(eventName, cb) {
|
||||
var extension = {};
|
||||
extension[newKey()] = function() {
|
||||
this.appEvents.on(eventName, this, cb);
|
||||
}.on('didInsertElement');
|
||||
|
||||
extension[newKey()] = function() {
|
||||
this.appEvents.off(eventName, this, cb);
|
||||
}.on('willDestroyElement');
|
||||
|
||||
return extension;
|
||||
}
|
||||
|
||||
export function listenForViewEvent(viewClass, eventName, cb) {
|
||||
viewClass.reopen(createViewListener(eventName, cb));
|
||||
}
|
||||
@ -40,34 +40,89 @@ var closeSelector = function(){
|
||||
$('body, textarea').off('keydown.emoji');
|
||||
};
|
||||
|
||||
var ungroupedIcons;
|
||||
var ungroupedIcons, recentlyUsedIcons;
|
||||
|
||||
var initializeUngroupedIcons = function(){
|
||||
ungroupedIcons = [];
|
||||
|
||||
var groupedIcons = {};
|
||||
_.each(groups, function(group){
|
||||
_.each(group.icons, function(icon){
|
||||
groupedIcons[icon] = true;
|
||||
});
|
||||
});
|
||||
|
||||
var emojis = Discourse.Emoji.list();
|
||||
_.each(emojis, function(emoji){
|
||||
if(groupedIcons[emoji] !== true){
|
||||
ungroupedIcons.push(emoji);
|
||||
}
|
||||
});
|
||||
|
||||
if(ungroupedIcons.length > 0){
|
||||
groups.push({name: 'ungrouped', icons: ungroupedIcons});
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
if (localStorage && !localStorage.emojiUsage) { localStorage.emojiUsage = "{}"; }
|
||||
} catch(e){
|
||||
/* localStorage can be disabled, or cookies disabled, do not crash script here
|
||||
* TODO introduce a global wrapper for dealing with local storage
|
||||
* */
|
||||
}
|
||||
|
||||
var trackEmojiUsage = function(title){
|
||||
var recent = JSON.parse(localStorage.emojiUsage);
|
||||
|
||||
if (!recent[title]) { recent[title] = { title: title, usage: 0 }; }
|
||||
recent[title]["usage"]++;
|
||||
|
||||
localStorage.emojiUsage = JSON.stringify(recent);
|
||||
|
||||
// clear the cache
|
||||
recentlyUsedIcons = null;
|
||||
};
|
||||
|
||||
var initializeRecentlyUsedIcons = function(){
|
||||
recentlyUsedIcons = [];
|
||||
|
||||
var usage = _.map(JSON.parse(localStorage.emojiUsage));
|
||||
usage.sort(function(a,b){
|
||||
if(a.usage > b.usage){
|
||||
return -1;
|
||||
}
|
||||
if(b.usage > a.usage){
|
||||
return 1;
|
||||
}
|
||||
return a.title.localeCompare(b.title);
|
||||
});
|
||||
|
||||
var recent = _.take(usage, PER_ROW);
|
||||
|
||||
if(recent.length > 0){
|
||||
_.each(recent, function(emoji){
|
||||
recentlyUsedIcons.push(emoji.title);
|
||||
});
|
||||
|
||||
var recentGroup = _.find(groups, {name: 'recent'});
|
||||
if(!recentGroup){
|
||||
recentGroup = {name: 'recent', icons: []};
|
||||
groups.push(recentGroup);
|
||||
}
|
||||
|
||||
recentGroup.icons = recentlyUsedIcons;
|
||||
}
|
||||
};
|
||||
|
||||
var toolbar = function(selected){
|
||||
if(!ungroupedIcons){
|
||||
ungroupedIcons = [];
|
||||
var groupedIcons = {};
|
||||
|
||||
_.each(groups, function(group){
|
||||
_.each(group.icons, function(icon){
|
||||
groupedIcons[icon] = true;
|
||||
});
|
||||
});
|
||||
|
||||
var emojis = Discourse.Emoji.list();
|
||||
_.each(emojis,function(emoji){
|
||||
if(groupedIcons[emoji] !== true){
|
||||
ungroupedIcons.push(emoji);
|
||||
}
|
||||
});
|
||||
|
||||
if(ungroupedIcons.length > 0){
|
||||
groups.push({name: 'ungrouped', icons: ungroupedIcons});
|
||||
}
|
||||
}
|
||||
if (!ungroupedIcons) { initializeUngroupedIcons(); }
|
||||
if (!recentlyUsedIcons) { initializeRecentlyUsedIcons(); }
|
||||
|
||||
return _.map(groups, function(g, i){
|
||||
var row = {src: Discourse.Emoji.urlFor(g.icons[0]), groupId: i};
|
||||
if(i===selected){
|
||||
var icon = g.name === "recent" ? "star2" : g.icons[0];
|
||||
var row = {src: Discourse.Emoji.urlFor(icon), groupId: i};
|
||||
if(i === selected){
|
||||
row.selected = true;
|
||||
}
|
||||
return row;
|
||||
@ -80,9 +135,11 @@ var bindEvents = function(page,offset){
|
||||
var composerController = Discourse.__container__.lookup('controller:composer');
|
||||
|
||||
$('.emoji-page a').click(function(){
|
||||
composerController.appendTextAtCursor(":" + $(this).attr('title') + ":", {space: true});
|
||||
closeSelector();
|
||||
return false;
|
||||
var title = $(this).attr('title');
|
||||
trackEmojiUsage(title)
|
||||
composerController.appendTextAtCursor(":" + title + ":", {space: true});
|
||||
closeSelector();
|
||||
return false;
|
||||
}).hover(function(){
|
||||
var title = $(this).attr('title');
|
||||
var html = "<img src='" + Discourse.Emoji.urlFor(title) + "' class='emoji'> <span>:" + title + ":<span>";
|
||||
|
||||
@ -7,6 +7,7 @@ var emoji = <%= Emoji.standard.map(&:name).flatten.inspect %>;
|
||||
|
||||
var extendedEmoji = {};
|
||||
Discourse.Dialect.registerEmoji = function(code, url) {
|
||||
code = code.toLowerCase();
|
||||
extendedEmoji[code] = url;
|
||||
};
|
||||
|
||||
@ -53,13 +54,13 @@ var search = function(term, options) {
|
||||
Discourse.Emoji.search = search;
|
||||
|
||||
var emojiHash = {};
|
||||
emoji.forEach(function(code){
|
||||
emojiHash[code] = true;
|
||||
});
|
||||
emoji.forEach(function(code){ emojiHash[code] = true; });
|
||||
|
||||
var urlFor = function(code) {
|
||||
var url, set = Discourse.SiteSettings.emoji_set;
|
||||
|
||||
code = code.toLowerCase();
|
||||
|
||||
if(extendedEmoji.hasOwnProperty(code)) {
|
||||
url = extendedEmoji[code];
|
||||
}
|
||||
@ -82,10 +83,12 @@ var urlFor = function(code) {
|
||||
Discourse.Emoji.urlFor = urlFor;
|
||||
|
||||
Discourse.Emoji.exists = function(code){
|
||||
return !!(extendedEmoji.hasOwnProperty(code) || emojiHash.hasOwnProperty(code));
|
||||
code = code.toLowerCase();
|
||||
return !!(extendedEmoji.hasOwnProperty(code) || emojiHash.hasOwnProperty(code));
|
||||
}
|
||||
|
||||
function imageFor(code) {
|
||||
code = code.toLowerCase();
|
||||
var url = urlFor(code);
|
||||
if (url) {
|
||||
return ['img', { href: url, title: ':' + code + ':', 'class': 'emoji', alt: code }];
|
||||
@ -145,7 +148,7 @@ Object.keys(translations).forEach(function (t) {
|
||||
});
|
||||
|
||||
function escapeRegExp(s) {
|
||||
return s.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&');
|
||||
return s.replace(/[-/\\^$*+?.()|[\]{}]/gi, '\\$&');
|
||||
}
|
||||
|
||||
var translationColonRegexp = new RegExp(Object.keys(translationsWithColon).map(function (t) {
|
||||
|
||||
@ -1,11 +1,3 @@
|
||||
/**
|
||||
Helpers to build HTML strings as well as custom fragments.
|
||||
|
||||
@class HTML
|
||||
@namespace Discourse
|
||||
@module Discourse
|
||||
**/
|
||||
|
||||
var customizations = {};
|
||||
|
||||
Discourse.HTML = {
|
||||
@ -15,9 +7,6 @@ Discourse.HTML = {
|
||||
using `setCustomHTML(key, html)`. This is used by a handlebars helper to find
|
||||
the HTML content it wants. It will also check the `PreloadStore` for any server
|
||||
side preloaded HTML.
|
||||
|
||||
@method getCustomHTML
|
||||
@param {String} key to lookup
|
||||
**/
|
||||
getCustomHTML: function(key) {
|
||||
var c = customizations[key];
|
||||
@ -31,104 +20,9 @@ Discourse.HTML = {
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
Set a fragment of HTML by key. It can then be looked up with `getCustomHTML(key)`.
|
||||
|
||||
@method setCustomHTML
|
||||
@param {String} key to store the html
|
||||
@param {String} html fragment to store
|
||||
**/
|
||||
// Set a fragment of HTML by key. It can then be looked up with `getCustomHTML(key)`.
|
||||
setCustomHTML: function(key, html) {
|
||||
customizations[key] = html;
|
||||
},
|
||||
|
||||
/**
|
||||
Returns the CSS styles for a category
|
||||
|
||||
@method categoryStyle
|
||||
@param {Discourse.Category} category the category whose link we want
|
||||
**/
|
||||
categoryStyle: function(category) {
|
||||
var color = Em.get(category, 'color'),
|
||||
textColor = Em.get(category, 'text_color');
|
||||
|
||||
if (!color && !textColor) { return; }
|
||||
|
||||
// Add the custom style if we need to
|
||||
var style = "";
|
||||
if (color) { style += "background-color: #" + color + "; "; }
|
||||
if (textColor) { style += "color: #" + textColor + "; "; }
|
||||
return style;
|
||||
},
|
||||
|
||||
/**
|
||||
Create a category badge
|
||||
|
||||
@method categoryBadge
|
||||
@param {Discourse.Category} category the category whose link we want
|
||||
@param {Object} opts The options for the category link
|
||||
@param {Boolean} opts.allowUncategorized Whether we allow rendering of the uncategorized category (default false)
|
||||
@param {Boolean} opts.showParent Whether to visually show whether category is a sub-category (default false)
|
||||
@param {Boolean} opts.link Whether this category badge should link to the category (default true)
|
||||
@param {String} opts.extraClasses add this string to the class attribute of the badge
|
||||
@returns {String} the html category badge
|
||||
**/
|
||||
categoryBadge: function(category, opts) {
|
||||
opts = opts || {};
|
||||
|
||||
if ((!category) ||
|
||||
(!opts.allowUncategorized &&
|
||||
Em.get(category, 'id') === Discourse.Site.currentProp("uncategorized_category_id") &&
|
||||
Discourse.SiteSettings.suppress_uncategorized_badge
|
||||
)
|
||||
) return "";
|
||||
|
||||
var name = Em.get(category, 'name'),
|
||||
description = Em.get(category, 'description_text'),
|
||||
restricted = Em.get(category, 'read_restricted'),
|
||||
url = Discourse.getURL("/c/") + Discourse.Category.slugFor(category),
|
||||
elem = (opts.link === false ? 'span' : 'a'),
|
||||
extraClasses = (opts.extraClasses ? (' ' + opts.extraClasses) : ''),
|
||||
html = "<" + elem + " href=\"" + (opts.link === false ? '' : url) + "\" ",
|
||||
categoryStyle;
|
||||
|
||||
// Parent stripe implies onlyStripe
|
||||
if (opts.onlyStripe) { opts.showParent = true; }
|
||||
|
||||
html += "data-drop-close=\"true\" class=\"badge-category" + (restricted ? ' restricted' : '' ) +
|
||||
(opts.onlyStripe ? ' clear-badge' : '') +
|
||||
extraClasses + "\" ";
|
||||
name = Handlebars.Utils.escapeExpression(name);
|
||||
|
||||
// Add description if we have it, without tags. Server has sanitized the description value.
|
||||
if (description) html += "title=\"" + Handlebars.Utils.escapeExpression(description) + "\" ";
|
||||
|
||||
if (!opts.onlyStripe) {
|
||||
categoryStyle = Discourse.HTML.categoryStyle(category);
|
||||
if (categoryStyle) {
|
||||
html += "style=\"" + categoryStyle + "\" ";
|
||||
}
|
||||
}
|
||||
|
||||
if (restricted) {
|
||||
html += "><div><i class='fa fa-group'></i> " + name + "</div></" + elem + ">";
|
||||
} else {
|
||||
html += ">" + name + "</" + elem + ">";
|
||||
}
|
||||
|
||||
if (opts.onlyStripe || (opts.showParent && category.get('parent_category_id'))) {
|
||||
var parent = Discourse.Category.findById(category.get('parent_category_id'));
|
||||
if (!parent) { parent = category; }
|
||||
|
||||
categoryStyle = Discourse.HTML.categoryStyle(opts.onlyStripe ? category : parent) || '';
|
||||
html = "<span class='badge-wrapper'><" + elem + " class='badge-category-parent" + extraClasses + "' style=\"" + categoryStyle +
|
||||
"\" href=\"" + (opts.link === false ? '' : url) + "\"><span class='category-name'>" +
|
||||
(Em.get(parent, 'read_restricted') ? "<i class='fa fa-group'></i> " : "") +
|
||||
Em.get(parent, 'name') + "</span></" + elem + ">" +
|
||||
html + "</span>";
|
||||
}
|
||||
|
||||
return html;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
@ -8,7 +8,6 @@ var PATH_BINDINGS = {
|
||||
},
|
||||
|
||||
SELECTED_POST_BINDINGS = {
|
||||
'b': 'toggleBookmark',
|
||||
'd': 'deletePost',
|
||||
'e': 'editPost',
|
||||
'l': 'toggleLike',
|
||||
@ -50,7 +49,8 @@ var PATH_BINDINGS = {
|
||||
'ctrl+f': 'showBuiltinSearch',
|
||||
'command+f': 'showBuiltinSearch',
|
||||
'?': 'showHelpModal', // open keyboard shortcut help
|
||||
'q': 'quoteReply'
|
||||
'q': 'quoteReply',
|
||||
'b': 'toggleBookmark'
|
||||
};
|
||||
|
||||
|
||||
@ -65,6 +65,11 @@ Discourse.KeyboardShortcuts = Ember.Object.createWithMixins({
|
||||
_.each(FUNCTION_BINDINGS, this._bindToFunction, this);
|
||||
},
|
||||
|
||||
toggleBookmark: function(){
|
||||
this.sendToSelectedPost('toggleBookmark');
|
||||
this.sendToTopicListItemView('toggleBookmark');
|
||||
},
|
||||
|
||||
quoteReply: function(){
|
||||
$('.topic-post.selected button.create').click();
|
||||
// lazy but should work for now
|
||||
@ -157,19 +162,32 @@ Discourse.KeyboardShortcuts = Ember.Object.createWithMixins({
|
||||
Discourse.__container__.lookup('controller:application').send('showKeyboardShortcutsHelp');
|
||||
},
|
||||
|
||||
_bindToSelectedPost: function(action, binding) {
|
||||
sendToTopicListItemView: function(action){
|
||||
var elem = $('tr.selected.topic-list-item.ember-view')[0];
|
||||
if(elem){
|
||||
var view = Ember.View.views[elem.id];
|
||||
view.send(action);
|
||||
}
|
||||
},
|
||||
|
||||
sendToSelectedPost: function(action){
|
||||
var container = this.container;
|
||||
// TODO: We should keep track of the post without a CSS class
|
||||
var selectedPostId = parseInt($('.topic-post.selected article.boxed').data('post-id'), 10);
|
||||
if (selectedPostId) {
|
||||
var topicController = container.lookup('controller:topic'),
|
||||
post = topicController.get('postStream.posts').findBy('id', selectedPostId);
|
||||
if (post) {
|
||||
topicController.send(action, post);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_bindToSelectedPost: function(action, binding) {
|
||||
var self = this;
|
||||
|
||||
this.keyTrapper.bind(binding, function() {
|
||||
// TODO: We should keep track of the post without a CSS class
|
||||
var selectedPostId = parseInt($('.topic-post.selected article.boxed').data('post-id'), 10);
|
||||
if (selectedPostId) {
|
||||
var topicController = container.lookup('controller:topic'),
|
||||
post = topicController.get('postStream.posts').findBy('id', selectedPostId);
|
||||
if (post) {
|
||||
topicController.send(action, post);
|
||||
}
|
||||
}
|
||||
self.sendToSelectedPost(action);
|
||||
});
|
||||
},
|
||||
|
||||
@ -244,9 +262,14 @@ Discourse.KeyboardShortcuts = Ember.Object.createWithMixins({
|
||||
var $article = $articles.eq(index + direction);
|
||||
|
||||
if ($article.size() > 0) {
|
||||
|
||||
$articles.removeClass('selected');
|
||||
$article.addClass('selected');
|
||||
|
||||
if($article.is('.topic-list-item')){
|
||||
this.sendToTopicListItemView('select');
|
||||
}
|
||||
|
||||
if ($article.is('.topic-post')) {
|
||||
var tabLoc = $article.find('a.tabLoc');
|
||||
if (tabLoc.length === 0) {
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
var jumpScheduled = false,
|
||||
rewrites = [];
|
||||
|
||||
Discourse.URL = Em.Object.createWithMixins({
|
||||
Discourse.URL = Ember.Object.createWithMixins({
|
||||
|
||||
// Used for matching a topic
|
||||
TOPIC_REGEXP: /\/t\/([^\/]+)\/(\d+)\/?(\d+)?/,
|
||||
@ -203,7 +203,6 @@ Discourse.URL = Em.Object.createWithMixins({
|
||||
|
||||
var container = Discourse.__container__,
|
||||
topicController = container.lookup('controller:topic'),
|
||||
topicProgressController = container.lookup('controller:topic-progress'),
|
||||
opts = {},
|
||||
postStream = topicController.get('postStream');
|
||||
|
||||
@ -211,16 +210,18 @@ Discourse.URL = Em.Object.createWithMixins({
|
||||
if (path.match(/last$/)) { opts.nearPost = topicController.get('highest_post_number'); }
|
||||
var closest = opts.nearPost || 1;
|
||||
|
||||
var self = this;
|
||||
postStream.refresh(opts).then(function() {
|
||||
topicController.setProperties({
|
||||
currentPost: closest,
|
||||
highlightOnInsert: closest,
|
||||
enteredAt: new Date().getTime().toString()
|
||||
});
|
||||
var closestPost = postStream.closestPostForPostNumber(closest),
|
||||
progress = postStream.progressIndexOfPost(closestPost);
|
||||
topicProgressController.set('progressPosition', progress);
|
||||
Discourse.PostView.considerHighlighting(topicController, closest);
|
||||
progress = postStream.progressIndexOfPost(closestPost),
|
||||
progressController = container.lookup('controller:topic-progress');
|
||||
|
||||
progressController.set('progressPosition', progress);
|
||||
self.appEvents.trigger('post:highlight', closest);
|
||||
}).then(function() {
|
||||
Discourse.URL.jumpToPost(closest);
|
||||
});
|
||||
|
||||
@ -162,10 +162,13 @@ Discourse.Utilities = {
|
||||
}
|
||||
|
||||
var upload = files[0];
|
||||
var type = Discourse.Utilities.isAnImage(upload.name) ? 'image' : 'attachment';
|
||||
|
||||
// CHROME ONLY: if the image was pasted, sets its name to a default one
|
||||
if (upload instanceof Blob && !(upload instanceof File) && upload.type === "image/png") { upload.name = "blob.png"; }
|
||||
if (typeof Blob !== "undefined" && typeof File !== "undefined") {
|
||||
if (upload instanceof Blob && !(upload instanceof File) && upload.type === "image/png") { upload.name = "blob.png"; }
|
||||
}
|
||||
|
||||
var type = Discourse.Utilities.isAnImage(upload.name) ? 'image' : 'attachment';
|
||||
|
||||
return Discourse.Utilities.validateUploadedFile(upload, type, bypassNewUserRestriction);
|
||||
},
|
||||
@ -286,7 +289,7 @@ Discourse.Utilities = {
|
||||
// deal with meaningful errors first
|
||||
if (data.jqXHR) {
|
||||
switch (data.jqXHR.status) {
|
||||
// cancel from the user
|
||||
// cancelled by the user
|
||||
case 0: return;
|
||||
|
||||
// entity too large, usually returned from the web server
|
||||
|
||||
@ -1,23 +1,23 @@
|
||||
// Mix this in to a view that has a `categoryId` property to automatically
|
||||
// Mix this in to a view that has a `categorySlug` property to automatically
|
||||
// add it to the body as the view is entered / left / model is changed.
|
||||
// This is used for keeping the `body` style in sync for the background image.
|
||||
export default {
|
||||
_observeOnce: function() { this.get('categoryId'); }.on('init'),
|
||||
_enterView: function() { this.get('categorySlug'); }.on('init'),
|
||||
|
||||
_removeClasses: function() {
|
||||
$('body').removeClass(function(idx, css) {
|
||||
return (css.match(/\bcategory-\d+/g) || []).join(' ');
|
||||
return (css.match(/\bcategory-\S+/g) || []).join(' ');
|
||||
});
|
||||
},
|
||||
|
||||
_categoryChanged: function() {
|
||||
var categoryId = this.get('categoryId');
|
||||
var categorySlug = this.get('categorySlug');
|
||||
this._removeClasses();
|
||||
|
||||
if (categoryId) {
|
||||
$('body').addClass('category-' + categoryId);
|
||||
if (categorySlug) {
|
||||
$('body').addClass('category-' + categorySlug);
|
||||
}
|
||||
}.observes('categoryId'),
|
||||
}.observes('categorySlug'),
|
||||
|
||||
_leaveView: function() { this._removeClasses(); }.on('willDestroyElement')
|
||||
};
|
||||
|
||||
@ -63,6 +63,8 @@ Discourse.Ajax = Em.Mixin.create({
|
||||
xhr.jqTextStatus = textStatus;
|
||||
xhr.requestedUrl = url;
|
||||
|
||||
// TODO is this sequence correct? we are calling catch defined externally before
|
||||
// the error that was defined inline, it should probably be in reverse
|
||||
Ember.run(null, reject, xhr);
|
||||
if (oldError) oldError(xhr);
|
||||
};
|
||||
|
||||
@ -11,17 +11,15 @@ export default Em.Mixin.create({
|
||||
},
|
||||
|
||||
_initializeUploader: function() {
|
||||
// NOTE: we can't cache this as fileupload replaces the input after upload
|
||||
// cf. https://github.com/blueimp/jQuery-File-Upload/wiki/Frequently-Asked-Questions#why-is-the-file-input-field-cloned-and-replaced-after-each-selection
|
||||
var $upload = this.$('input[type=file]'),
|
||||
self = this;
|
||||
var $upload = this.$(),
|
||||
self = this,
|
||||
csrf = Discourse.Session.currentProp("csrfToken");
|
||||
|
||||
$upload.fileupload({
|
||||
url: this.get('uploadUrl'),
|
||||
url: this.get('uploadUrl') + ".json?authenticity_token=" + encodeURIComponent(csrf),
|
||||
dataType: "json",
|
||||
fileInput: $upload,
|
||||
dropZone: this.$(),
|
||||
pasteZone: this.$()
|
||||
dropZone: $upload,
|
||||
pasteZone: $upload
|
||||
});
|
||||
|
||||
$upload.on('fileuploadsubmit', function (e, data) {
|
||||
@ -39,14 +37,20 @@ export default Em.Mixin.create({
|
||||
});
|
||||
|
||||
$upload.on("fileuploaddone", function(e, data) {
|
||||
if(data.result.url) {
|
||||
self.uploadDone(data);
|
||||
} else {
|
||||
if (data.result.message) {
|
||||
bootbox.alert(data.result.message);
|
||||
if (data.result) {
|
||||
if (data.result.url) {
|
||||
self.uploadDone(data);
|
||||
} else {
|
||||
bootbox.alert(I18n.t('post.errors.upload'));
|
||||
if (data.result.message) {
|
||||
bootbox.alert(data.result.message);
|
||||
} else if (data.result.length > 0) {
|
||||
bootbox.alert(data.result.join("\n"));
|
||||
} else {
|
||||
bootbox.alert(I18n.t('post.errors.upload'));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
bootbox.alert(I18n.t('post.errors.upload'));
|
||||
}
|
||||
});
|
||||
|
||||
@ -60,12 +64,9 @@ export default Em.Mixin.create({
|
||||
}.on('didInsertElement'),
|
||||
|
||||
_destroyUploader: function() {
|
||||
this.$('input[type=file]').fileupload('destroy');
|
||||
}.on('willDestroyElement'),
|
||||
|
||||
actions: {
|
||||
selectFile: function() {
|
||||
this.$('input[type=file]').click();
|
||||
}
|
||||
}
|
||||
var $upload = this.$();
|
||||
try { $upload.fileupload('destroy'); }
|
||||
catch (e) { /* wasn't initialized yet */ }
|
||||
$upload.off();
|
||||
}.on('willDestroyElement')
|
||||
});
|
||||
|
||||
@ -4,16 +4,9 @@
|
||||
//
|
||||
// This is useful if you want to get around Ember's default
|
||||
// behavior of not refreshing when navigating to the same place.
|
||||
export default Em.Mixin.create({
|
||||
_initURLRefresh: function() {
|
||||
this.appEvents.on('url:refresh', this, '_urlRefresh');
|
||||
}.on('didInsertElement'),
|
||||
|
||||
_tearDownURLRefresh: function() {
|
||||
this.appEvents.off('url:refresh', this, '_urlRefresh');
|
||||
}.on('willDestroyElement'),
|
||||
import { createViewListener } from 'discourse/lib/app-events';
|
||||
|
||||
_urlRefresh: function() {
|
||||
this.get('controller').send('refresh');
|
||||
}
|
||||
export default createViewListener('url:refresh', function() {
|
||||
this.get('controller').send('refresh');
|
||||
});
|
||||
|
||||
@ -470,7 +470,7 @@ Discourse.Post.reopenClass({
|
||||
|
||||
loadRevision: function(postId, version) {
|
||||
return Discourse.ajax("/posts/" + postId + "/revisions/" + version + ".json").then(function (result) {
|
||||
return Em.Object.create(result);
|
||||
return Ember.Object.create(result);
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
@ -232,6 +232,7 @@ Discourse.Category.reopenClass({
|
||||
},
|
||||
|
||||
findById: function(id) {
|
||||
if (!id) { return; }
|
||||
return Discourse.Category.idMap()[id];
|
||||
},
|
||||
|
||||
|
||||
@ -1,11 +1,3 @@
|
||||
/**
|
||||
A data model for containing a list of categories
|
||||
|
||||
@class CategoryList
|
||||
@extends Discourse.Model
|
||||
@namespace Discourse
|
||||
@module Discourse
|
||||
**/
|
||||
Discourse.CategoryList = Ember.ArrayProxy.extend({
|
||||
init: function() {
|
||||
this.set('content', []);
|
||||
@ -50,7 +42,8 @@ Discourse.CategoryList.reopenClass({
|
||||
var self = this;
|
||||
return Discourse.ajax('/categories.json?parent_category_id=' + category.get('id')).then(function(result) {
|
||||
return Discourse.CategoryList.create({
|
||||
categories: self.categoriesFrom(result)
|
||||
categories: self.categoriesFrom(result),
|
||||
parentCategory: category
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
@ -328,36 +328,6 @@ Discourse.Composer = Discourse.Model.extend({
|
||||
Discourse.KeyValueStore.set({ key: 'composer.showPreview', value: this.get('showPreview') });
|
||||
},
|
||||
|
||||
importQuote: function() {
|
||||
var postStream = this.get('topic.postStream'),
|
||||
postId = this.get('post.id');
|
||||
|
||||
if (!postId && postStream) {
|
||||
postId = postStream.get('firstPostId');
|
||||
}
|
||||
|
||||
// If we're editing a post, fetch the reply when importing a quote
|
||||
if (this.get('editingPost')) {
|
||||
var replyToPostNumber = this.get('post.reply_to_post_number');
|
||||
if (replyToPostNumber) {
|
||||
var replyPost = postStream.get('posts').findBy('post_number', replyToPostNumber);
|
||||
if (replyPost) {
|
||||
postId = replyPost.get('id');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If there is no current post, use the post id from the stream
|
||||
if (postId) {
|
||||
this.set('loading', true);
|
||||
var composer = this;
|
||||
return Discourse.Post.load(postId).then(function(post) {
|
||||
composer.appendText(Discourse.Quote.build(post, post.get('raw')));
|
||||
composer.set('loading', false);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
/*
|
||||
Open a composer
|
||||
|
||||
@ -467,14 +437,19 @@ Discourse.Composer = Discourse.Model.extend({
|
||||
editPost: function(opts) {
|
||||
var post = this.get('post'),
|
||||
oldCooked = post.get('cooked'),
|
||||
composer = this;
|
||||
self = this,
|
||||
promise;
|
||||
|
||||
// Update the title if we've changed it
|
||||
if (this.get('title') && post.get('post_number') === 1) {
|
||||
// Update the title if we've changed it, otherwise consider it a
|
||||
// successful resolved promise
|
||||
if (this.get('title') &&
|
||||
post.get('post_number') === 1 &&
|
||||
this.get('topic.details.can_edit')) {
|
||||
|
||||
var topicProps = this.getProperties(Object.keys(_edit_topic_serializer));
|
||||
|
||||
Discourse.Topic.update(this.get('topic'), topicProps);
|
||||
promise = Discourse.Topic.update(this.get('topic'), topicProps);
|
||||
} else {
|
||||
promise = Ember.RSVP.resolve();
|
||||
}
|
||||
|
||||
post.setProperties({
|
||||
@ -485,19 +460,19 @@ Discourse.Composer = Discourse.Model.extend({
|
||||
});
|
||||
this.set('composeState', CLOSED);
|
||||
|
||||
return new Ember.RSVP.Promise(function(resolve, reject) {
|
||||
post.save(function(result) {
|
||||
return promise.then(function() {
|
||||
return post.save(function(result) {
|
||||
post.updateFromPost(result);
|
||||
composer.clearState();
|
||||
}, function(error) {
|
||||
self.clearState();
|
||||
}).catch(function(error) {
|
||||
var response = $.parseJSON(error.responseText);
|
||||
if (response && response.errors) {
|
||||
reject(response.errors[0]);
|
||||
return(response.errors[0]);
|
||||
} else {
|
||||
reject(I18n.t('generic_error'));
|
||||
return(I18n.t('generic_error'));
|
||||
}
|
||||
post.set('cooked', oldCooked);
|
||||
composer.set('composeState', OPEN);
|
||||
self.set('composeState', OPEN);
|
||||
});
|
||||
});
|
||||
},
|
||||
@ -530,9 +505,11 @@ Discourse.Composer = Discourse.Model.extend({
|
||||
imageSizes: opts.imageSizes,
|
||||
cooked: this.getCookedHtml(),
|
||||
reply_count: 0,
|
||||
name: currentUser.get('name'),
|
||||
display_username: currentUser.get('name'),
|
||||
username: currentUser.get('username'),
|
||||
user_id: currentUser.get('id'),
|
||||
user_title: currentUser.get('title'),
|
||||
uploaded_avatar_id: currentUser.get('uploaded_avatar_id'),
|
||||
user_custom_fields: currentUser.get('custom_fields'),
|
||||
post_type: Discourse.Site.currentProp('post_types.regular'),
|
||||
|
||||
@ -11,6 +11,15 @@ Discourse.Group = Discourse.Model.extend({
|
||||
offset: 0,
|
||||
user_count: 0,
|
||||
|
||||
emailDomains: function() {
|
||||
var value = this.get("automatic_membership_email_domains");
|
||||
return Em.isEmpty(value) ? "" : value;
|
||||
}.property("automatic_membership_email_domains"),
|
||||
|
||||
type: function() {
|
||||
return this.get("automatic") ? "automatic" : "custom";
|
||||
}.property("automatic"),
|
||||
|
||||
userCountDisplay: function(){
|
||||
var c = this.get('user_count');
|
||||
// don't display zero its ugly
|
||||
@ -20,7 +29,8 @@ Discourse.Group = Discourse.Model.extend({
|
||||
findMembers: function() {
|
||||
if (Em.isEmpty(this.get('name'))) { return ; }
|
||||
|
||||
var self = this, offset = Math.min(this.get("user_count"), Math.max(this.get("offset"), 0));
|
||||
var self = this,
|
||||
offset = Math.min(this.get("user_count"), Math.max(this.get("offset"), 0));
|
||||
|
||||
return Discourse.ajax('/groups/' + this.get('name') + '/members.json', {
|
||||
data: {
|
||||
@ -63,7 +73,9 @@ Discourse.Group = Discourse.Model.extend({
|
||||
return {
|
||||
name: this.get('name'),
|
||||
alias_level: this.get('alias_level'),
|
||||
visible: !!this.get('visible')
|
||||
visible: !!this.get('visible'),
|
||||
automatic_membership_email_domains: this.get('emailDomains'),
|
||||
automatic_membership_retroactive: !!this.get('automatic_membership_retroactive')
|
||||
};
|
||||
},
|
||||
|
||||
@ -100,7 +112,7 @@ Discourse.Group = Discourse.Model.extend({
|
||||
|
||||
Discourse.Group.reopenClass({
|
||||
findAll: function(opts){
|
||||
return Discourse.ajax("/admin/groups.json", { data: opts }).then(function(groups){
|
||||
return Discourse.ajax("/admin/groups.json", { data: opts }).then(function (groups){
|
||||
return groups.map(function(g) { return Discourse.Group.create(g); });
|
||||
});
|
||||
},
|
||||
@ -112,8 +124,8 @@ Discourse.Group.reopenClass({
|
||||
},
|
||||
|
||||
find: function(name) {
|
||||
return Discourse.ajax("/groups/" + name + ".json").then(function(g) {
|
||||
return Discourse.Group.create(g.basic_group);
|
||||
return Discourse.ajax("/groups/" + name + ".json").then(function (result) {
|
||||
return Discourse.Group.create(result.basic_group);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@ -188,15 +188,23 @@ Discourse.Topic = Discourse.Model.extend({
|
||||
|
||||
return Discourse.ajax('/t/' + this.get('id') + '/bookmark', {
|
||||
type: 'PUT',
|
||||
data: { bookmarked: self.get('bookmarked') }
|
||||
}).then(null, function (error) {
|
||||
self.toggleProperty('bookmarked');
|
||||
if (self.get("postStream.firstPostPresent")) { firstPost.toggleProperty('bookmarked'); }
|
||||
data: { bookmarked: self.get('bookmarked') },
|
||||
error: function(error){
|
||||
self.toggleProperty('bookmarked');
|
||||
if (self.get("postStream.firstPostPresent")) { firstPost.toggleProperty('bookmarked'); }
|
||||
|
||||
if (error && error.responseText) {
|
||||
bootbox.alert($.parseJSON(error.responseText).errors);
|
||||
} else {
|
||||
bootbox.alert(I18n.t('generic_error'));
|
||||
var showGenericError = true;
|
||||
|
||||
if (error && error.responseText) {
|
||||
try {
|
||||
bootbox.alert($.parseJSON(error.responseText).errors);
|
||||
showGenericError = false;
|
||||
} catch(e){}
|
||||
}
|
||||
|
||||
if(showGenericError){
|
||||
bootbox.alert(I18n.t('generic_error'));
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
@ -1,12 +1,3 @@
|
||||
/**
|
||||
A data model representing a list of topics
|
||||
|
||||
@class TopicList
|
||||
@extends Discourse.Model
|
||||
@namespace Discourse
|
||||
@module Discourse
|
||||
**/
|
||||
|
||||
function finderFor(filter, params) {
|
||||
return function() {
|
||||
var url = Discourse.getURL("/") + filter + ".json";
|
||||
@ -264,6 +255,12 @@ Discourse.TopicList.reopenClass({
|
||||
return PreloadStore.getAndRemove("topic_list_" + filter, finderFor(filter, params)).then(function(result) {
|
||||
return Discourse.TopicList.from(result, filter, params);
|
||||
});
|
||||
},
|
||||
|
||||
// Sets `hideCategory` if all topics in the last have a particular category
|
||||
hideUniformCategory: function(list, category) {
|
||||
var hideCategory = !list.get('topics').any(function (t) { return t.get('category') !== category; });
|
||||
list.set('hideCategory', hideCategory);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
@ -72,10 +72,9 @@ Discourse.User = Discourse.Model.extend({
|
||||
@type {String}
|
||||
**/
|
||||
profileBackground: function() {
|
||||
var background = this.get('profile_background');
|
||||
if(Em.isEmpty(background) || !Discourse.SiteSettings.allow_profile_backgrounds) { return; }
|
||||
|
||||
return 'background-image: url(' + background + ')';
|
||||
var url = this.get('profile_background');
|
||||
if (Em.isEmpty(url) || !Discourse.SiteSettings.allow_profile_backgrounds) { return; }
|
||||
return 'background-image: url(' + Discourse.getURLWithCDN(url) + ')';
|
||||
}.property('profile_background'),
|
||||
|
||||
/**
|
||||
@ -352,10 +351,13 @@ Discourse.User = Discourse.Model.extend({
|
||||
Change avatar selection
|
||||
*/
|
||||
pickAvatar: function(uploadId) {
|
||||
this.set("uploaded_avatar_id", uploadId);
|
||||
var self = this;
|
||||
|
||||
return Discourse.ajax("/users/" + this.get("username_lower") + "/preferences/avatar/pick", {
|
||||
type: 'PUT',
|
||||
data: { upload_id: uploadId }
|
||||
}).then(function(){
|
||||
self.set('uploaded_avatar_id', uploadId);
|
||||
});
|
||||
},
|
||||
|
||||
@ -442,6 +444,7 @@ Discourse.User.reopenClass(Discourse.Singleton, {
|
||||
|
||||
avatarTemplate: function(username, uploadedAvatarId) {
|
||||
var url;
|
||||
|
||||
if (uploadedAvatarId) {
|
||||
url = "/user_avatar/" +
|
||||
Discourse.BaseUrl +
|
||||
@ -456,11 +459,7 @@ Discourse.User.reopenClass(Discourse.Singleton, {
|
||||
Discourse.LetterAvatarVersion + ".png";
|
||||
}
|
||||
|
||||
url = Discourse.getURL(url);
|
||||
if (Discourse.CDN) {
|
||||
url = Discourse.CDN + url;
|
||||
}
|
||||
return url;
|
||||
return Discourse.getURLWithCDN(url);
|
||||
},
|
||||
|
||||
/**
|
||||
|
||||
@ -132,18 +132,13 @@ var ApplicationRoute = Discourse.Route.extend({
|
||||
},
|
||||
|
||||
editCategory: function(category) {
|
||||
var router = this;
|
||||
var self = this;
|
||||
|
||||
if (category.get('isUncategorizedCategory')) {
|
||||
Discourse.Route.showModal(router, 'editCategory', category);
|
||||
router.controllerFor('editCategory').set('selectedTab', 'general');
|
||||
} else {
|
||||
Discourse.Category.reloadById(category.get('id')).then(function (c) {
|
||||
Discourse.Site.current().updateCategory(c);
|
||||
Discourse.Route.showModal(router, 'editCategory', c);
|
||||
router.controllerFor('editCategory').set('selectedTab', 'general');
|
||||
});
|
||||
}
|
||||
Discourse.Category.reloadById(category.get('id')).then(function (c) {
|
||||
self.site.updateCategory(c);
|
||||
Discourse.Route.showModal(self, 'editCategory', c);
|
||||
self.controllerFor('editCategory').set('selectedTab', 'general');
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
|
||||
@ -54,9 +54,7 @@ export default function(filter, params) {
|
||||
extras = { cached: this.isPoppedState(transition) };
|
||||
|
||||
return Discourse.TopicList.list(listFilter, findOpts, extras).then(function(list) {
|
||||
// If all the categories are the same, we can hide them
|
||||
var hideCategory = !list.get('topics').find(function (t) { return t.get('category') !== model; });
|
||||
list.set('hideCategory', hideCategory);
|
||||
Discourse.TopicList.hideUniformCategory(list, model);
|
||||
self.set('topics', list);
|
||||
});
|
||||
},
|
||||
|
||||
@ -89,23 +89,53 @@ Discourse.Route.reopenClass({
|
||||
},
|
||||
|
||||
mapRoutes: function() {
|
||||
var resources = {};
|
||||
|
||||
// If a module is defined as `route-map` in discourse or a plugin, its routes
|
||||
// will be built automatically. You can supply a `resource` property to
|
||||
// automatically put it in that resource, such as `admin`. That way plugins
|
||||
// can define admin routes.
|
||||
Ember.keys(requirejs._eak_seen).forEach(function(key) {
|
||||
if (/route-map$/.test(key)) {
|
||||
var module = require(key, null, null, true);
|
||||
if (!module || !module.default) { throw new Error(key + ' must export a route map.'); }
|
||||
|
||||
var mapObj = module.default;
|
||||
if (typeof mapObj === 'function') {
|
||||
mapObj = { resource: 'root', map: mapObj };
|
||||
}
|
||||
|
||||
if (!resources[mapObj.resource]) { resources[mapObj.resource] = []; }
|
||||
resources[mapObj.resource].push(mapObj.map);
|
||||
}
|
||||
});
|
||||
|
||||
Discourse.Router.map(function() {
|
||||
var router = this;
|
||||
|
||||
// Do the root resources first
|
||||
if (resources.root) {
|
||||
resources.root.forEach(function(m) {
|
||||
m.call(router);
|
||||
});
|
||||
delete resources.root;
|
||||
}
|
||||
|
||||
// Apply other resources next
|
||||
Object.keys(resources).forEach(function(r) {
|
||||
router.resource(r, function() {
|
||||
var res = this;
|
||||
resources[r].forEach(function(m) {
|
||||
m.call(res);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
if (routeBuilder) {
|
||||
Ember.warn("The Discourse `routeBuilder` is deprecated. Export a `route-map` instead");
|
||||
routeBuilder.call(router);
|
||||
}
|
||||
|
||||
// If a module is defined as `route-map` in discourse or a plugin, its routes
|
||||
// will be built automatically.
|
||||
Ember.keys(requirejs._eak_seen).forEach(function(key) {
|
||||
if (/route-map$/.test(key)) {
|
||||
var module = require(key, null, null, true);
|
||||
if (!module) { throw new Error(key + ' must export a map function.'); }
|
||||
module.default.call(router);
|
||||
}
|
||||
});
|
||||
|
||||
this.route('unknown', {path: '*path'});
|
||||
});
|
||||
|
||||
@ -45,15 +45,17 @@ export default RestrictedUserRoute.extend(ShowFooter, {
|
||||
|
||||
// sends the information to the server if it has changed
|
||||
if (avatarSelector.get('selectedUploadId') !== user.get('uploaded_avatar_id')) {
|
||||
user.pickAvatar(avatarSelector.get('selectedUploadId'));
|
||||
user.pickAvatar(avatarSelector.get('selectedUploadId'))
|
||||
.then(function(){
|
||||
user.setProperties(avatarSelector.getProperties(
|
||||
'system_avatar_upload_id',
|
||||
'gravatar_avatar_upload_id',
|
||||
'custom_avatar_upload_id'
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
// saves the data back
|
||||
user.setProperties(avatarSelector.getProperties(
|
||||
'system_avatar_upload_id',
|
||||
'gravatar_avatar_upload_id',
|
||||
'custom_avatar_upload_id'
|
||||
));
|
||||
avatarSelector.send('closeModal');
|
||||
},
|
||||
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
// This route is used for retrieving a topic based on params
|
||||
|
||||
export default Discourse.Route.extend({
|
||||
|
||||
setupController: function(controller, params) {
|
||||
@ -15,6 +14,7 @@ export default Discourse.Route.extend({
|
||||
// I sincerely hope no topic gets this many posts
|
||||
if (params.nearPost === "last") { params.nearPost = 999999999; }
|
||||
|
||||
var self = this;
|
||||
postStream.refresh(params).then(function () {
|
||||
|
||||
// TODO we are seeing errors where closest post is null and this is exploding
|
||||
@ -28,13 +28,17 @@ export default Discourse.Route.extend({
|
||||
topicController.setProperties({
|
||||
currentPost: closest,
|
||||
enteredAt: new Date().getTime().toString(),
|
||||
highlightOnInsert: closest
|
||||
});
|
||||
|
||||
topicProgressController.setProperties({
|
||||
progressPosition: progress,
|
||||
expanded: false
|
||||
});
|
||||
|
||||
// Highlight our post after the next render
|
||||
Ember.run.scheduleOnce('afterRender', function() {
|
||||
self.appEvents.trigger('post:highlight', closest);
|
||||
});
|
||||
Discourse.URL.jumpToPost(closest);
|
||||
|
||||
if (topic.present('draft')) {
|
||||
|
||||
@ -37,26 +37,37 @@
|
||||
<th> </th>
|
||||
<th>{{i18n 'about.stat.all_time'}}</th>
|
||||
<th>{{i18n 'about.stat.last_7_days'}}</th>
|
||||
<th>{{i18n 'about.stat.last_30_days'}}</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class='title'>{{i18n 'about.topic_count'}}</td>
|
||||
<td>{{number stats.topic_count}}</td>
|
||||
<td>{{number stats.topics_7_days}}</td>
|
||||
<td>{{number stats.topics_30_days}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{i18n 'about.post_count'}}</td>
|
||||
<td>{{number stats.post_count}}</td>
|
||||
<td>{{number stats.posts_7_days}}</td>
|
||||
<td>{{number stats.posts_30_days}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{i18n 'about.user_count'}}</td>
|
||||
<td>{{number stats.user_count}}</td>
|
||||
<td>{{number stats.users_7_days}}</td>
|
||||
<td>{{number stats.users_30_days}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{i18n 'about.active_user_count'}}</td>
|
||||
<td></td>
|
||||
<td>{{number stats.active_users_7_days}}</td>
|
||||
<td>{{number stats.active_users_30_days}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{i18n 'about.like_count'}}</td>
|
||||
<td>{{number stats.like_count}}</td>
|
||||
<td>{{number stats.likes_7_days}}</td>
|
||||
<td>{{number stats.likes_30_days}}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</section>
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
<div class='autocomplete'>
|
||||
<ul>
|
||||
{{#each option in options}}
|
||||
<li>{{category-link option allowUncategorized="true"}}</li>
|
||||
<li><a>{{category-link option allowUncategorized="true" link="false"}}</a></li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
<input type="file" accept="image/*" style="display:none" />
|
||||
<button class="btn" {{action "selectFile"}} {{bind-attr disabled="uploading"}} title="{{i18n 'user.change_avatar.upload_title'}}">
|
||||
<i class="fa fa-picture-o"></i> {{uploadButtonText}}
|
||||
</button>
|
||||
<label class="btn" {{bind-attr disabled="uploading"}} title="{{i18n 'user.change_avatar.upload_title'}}">
|
||||
{{fa-icon "picture-o"}} {{uploadButtonText}}
|
||||
<input {{bind-attr disabled="uploading"}} type="file" accept="image/*" style="visibility: hidden; position: absolute;" />
|
||||
</label>
|
||||
{{#if uploading}}
|
||||
<span>{{i18n 'upload_selector.uploading'}} {{view.uploadProgress}}%</span>
|
||||
{{/if}}
|
||||
|
||||
@ -1,27 +1,27 @@
|
||||
{{#if category}}
|
||||
<a href="#" {{action "expand"}} class="badge-category" {{bind-attr style="badgeStyle"}}>
|
||||
<a href {{action "expand"}} class="badge-category" {{bind-attr style="badgeStyle"}}>
|
||||
{{#if category.read_restricted}}
|
||||
<i class='fa fa-group'></i>
|
||||
{{fa-icon "lock"}}
|
||||
{{/if}}
|
||||
{{category.name}}
|
||||
</a>
|
||||
{{else}}
|
||||
{{#if noSubcategories}}
|
||||
<a href='#' {{action "expand"}} class='badge-category home' {{bind-attr style="badgeStyle"}}>{{i18n 'categories.no_subcategory'}}</a>
|
||||
<a href {{action "expand"}} class='badge-category home' {{bind-attr style="badgeStyle"}}>{{i18n 'categories.no_subcategory'}}</a>
|
||||
{{else}}
|
||||
<a href='#' {{action "expand"}} class='badge-category home' {{bind-attr style="badgeStyle"}}>{{allCategoriesLabel}}</a>
|
||||
<a href {{action "expand"}} class='badge-category home' {{bind-attr style="badgeStyle"}}>{{allCategoriesLabel}}</a>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
|
||||
{{#if categories}}
|
||||
<a href='#' {{action "expand"}} {{bind-attr class="dropdownButtonClass" style="badgeStyle"}}><i {{bind-attr class="iconClass"}}></i></a>
|
||||
<a href {{action "expand"}} {{bind-attr class="dropdownButtonClass" style="badgeStyle"}}><i {{bind-attr class="iconClass"}}></i></a>
|
||||
<section {{bind-attr class="expanded::hidden :category-dropdown-menu"}} class='chooser'>
|
||||
<div class='cat'><a {{bind-attr href=allCategoriesUrl}} data-drop-close="true" class='badge-category home'>{{allCategoriesLabel}}</a></div>
|
||||
{{#if subCategory}}
|
||||
<div class='cat'><a {{bind-attr href=noCategoriesUrl}} data-drop-close="true" class='badge-category home'>{{i18n 'categories.no_subcategory'}}</a></div>
|
||||
<div class='cat'><a {{bind-attr href=noCategoriesUrl}} data-drop-close="true" class='badge-category home'>{{i18n 'categories.no_subcategory'}}</a></div>
|
||||
{{/if}}
|
||||
{{#if renderCategories}}
|
||||
{{#each c in categories}}<div class='cat'>{{category-link c allowUncategorized=true}}</div>{{/each}}
|
||||
{{#each c in categories}}<div class='cat'>{{category-link c allowUncategorized=true hideParent=subCategory}}</div>{{/each}}
|
||||
{{/if}}
|
||||
</section>
|
||||
{{/if}}
|
||||
|
||||
@ -0,0 +1,5 @@
|
||||
{{fa-icon icon modifier="stack-2x"}}
|
||||
|
||||
{{#if disabled}}
|
||||
{{fa-icon "ban" modifier="stack-2x"}}
|
||||
{{/if}}
|
||||
@ -1,6 +1,6 @@
|
||||
{{text-field name="name" placeholderKey="admin.emoji.name" value=name}}
|
||||
<input type="file" accept=".png,.gif" style="display:none" />
|
||||
<button {{bind-attr disabled="addDisabled"}} {{action "selectFile"}} class='btn btn-primary'>
|
||||
<label class="btn btn-primary" {{bind-attr disabled="addDisabled"}}>
|
||||
{{fa-icon "plus"}}
|
||||
{{i18n 'admin.emoji.add'}}
|
||||
</button>
|
||||
<input {{bind-attr disabled="addDisabled"}} type="file" accept=".png,.gif" style="visibility: hidden; position: absolute;" />
|
||||
</label>
|
||||
|
||||
@ -1,7 +1,9 @@
|
||||
<input type="file" accept="image/*" style="display:none" />
|
||||
<div class="uploaded-image-preview" class="input-xxlarge" {{bind-attr style="backgroundStyle"}}>
|
||||
<div class="image-upload-controls">
|
||||
<button {{action "selectFile"}} class="btn pad-left no-text">{{fa-icon "picture-o"}}</button>
|
||||
<label class="btn pad-left no-text" {{bind-attr disabled="uploading"}}>
|
||||
{{fa-icon "picture-o"}}
|
||||
<input {{bind-attr disabled="uploading"}} type="file" accept="image/*" style="visibility: hidden; position: absolute;" />
|
||||
</label>
|
||||
{{#if backgroundStyle}}
|
||||
<button {{action "trash"}} class="btn btn-danger pad-left no-text">{{fa-icon "trash-o"}}</button>
|
||||
{{/if}}
|
||||
|
||||
@ -0,0 +1,6 @@
|
||||
{{#if topic.category.parentCategory}}
|
||||
{{bound-category-link topic.category.parentCategory}}
|
||||
{{/if}}
|
||||
{{bound-category-link topic.category hideParent=true}}
|
||||
|
||||
{{plugin-outlet "topic-category"}}
|
||||
@ -48,7 +48,7 @@ so I'm going to stop rendering it until we figure out what's up
|
||||
{{#if showWarning}}
|
||||
<div class='add-warning'>
|
||||
<label>
|
||||
{{input type="checkbox" checked=model.isWarning}}
|
||||
{{input type="checkbox" checked=model.isWarning tabindex="3"}}
|
||||
{{i18n "composer.add_warning"}}
|
||||
</label>
|
||||
</div>
|
||||
@ -101,6 +101,7 @@ so I'm going to stop rendering it until we figure out what's up
|
||||
|
||||
{{#if currentUser}}
|
||||
<div class='submit-panel'>
|
||||
{{plugin-outlet "composer-fields-below"}}
|
||||
<button {{action "save"}} tabindex="5" {{bind-attr class=":btn :btn-primary :create disableSubmit:disabled"}} title="{{i18n 'composer.title'}}">{{{model.saveIcon}}}{{model.saveText}}</button>
|
||||
<a href='#' {{action "cancel"}} class='cancel' tabindex="6">{{i18n 'cancel'}}</a>
|
||||
</div>
|
||||
|
||||
@ -10,71 +10,71 @@
|
||||
</thead>
|
||||
<tbody>
|
||||
{{#each c in categories}}
|
||||
<tr data-category_id='{{unbound c.id}}' {{bind-attr class="c.description_excerpt:has-description:no-description c.logo_url:has-logo:no-logo"}}>
|
||||
<td class='category' style="border-color: #{{unbound c.color}}">
|
||||
<div>
|
||||
<div class="pull-left">
|
||||
{{category-title-link category=c}}
|
||||
{{#if c.unreadTopics}}
|
||||
<a href={{unbound c.unreadUrl}} class='badge new-posts badge-notification' title='{{i18n 'topic.unread_topics' count=c.unreadTopics}}'>{{i18n 'filters.unread.lower_title_with_count' count=c.unreadTopics}}</a>
|
||||
{{/if}}
|
||||
{{#if c.newTopics}}
|
||||
<a href={{unbound c.newUrl}} class='badge new-posts badge-notification' title='{{i18n 'topic.new_topics' count=c.newTopics}}'>{{i18n 'filters.new.lower_title_with_count' count=c.newTopics}}</a>
|
||||
{{/if}}
|
||||
</div>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
{{#if c.description_excerpt}}
|
||||
<div class="category-description">
|
||||
{{{c.description_excerpt}}}
|
||||
</div>
|
||||
{{/if}}
|
||||
{{#if c.subcategories}}
|
||||
<div class='subcategories'>
|
||||
{{#each s in c.subcategories}}
|
||||
{{category-link s showParent="true" onlyStripe="true"}}
|
||||
{{#if s.unreadTopics}}
|
||||
<a href={{unbound s.unreadUrl}} class='badge new-posts badge-notification' title='{{i18n 'topic.unread_topics' count=s.unreadTopics}}'>{{unbound s.unreadTopics}}</a>
|
||||
<tr data-category_id='{{unbound c.id}}' {{bind-attr class="c.description_excerpt:has-description:no-description c.logo_url:has-logo:no-logo"}}>
|
||||
<td class='category' style="border-color: #{{unbound c.color}}">
|
||||
<div>
|
||||
<div class="pull-left">
|
||||
{{category-title-link category=c}}
|
||||
{{#if c.unreadTopics}}
|
||||
<a href={{unbound c.unreadUrl}} class='badge new-posts badge-notification' title='{{i18n 'topic.unread_topics' count=c.unreadTopics}}'>{{i18n 'filters.unread.lower_title_with_count' count=c.unreadTopics}}</a>
|
||||
{{/if}}
|
||||
{{#if s.newTopics}}
|
||||
<a href={{unbound s.newUrl}} class='badge new-posts badge-notification' title='{{i18n 'topic.new_topics' count=s.newTopics}}'>{{unbound s.newTopics}}</a>
|
||||
{{#if c.newTopics}}
|
||||
<a href={{unbound c.newUrl}} class='badge new-posts badge-notification' title='{{i18n 'topic.new_topics' count=c.newTopics}}'>{{i18n 'filters.new.lower_title_with_count' count=c.newTopics}}</a>
|
||||
{{/if}}
|
||||
{{/each}}
|
||||
</div>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
{{/if}}
|
||||
</td>
|
||||
<td {{bind-attr class="c.archived :latest"}}>
|
||||
{{#each f in c.featuredTopics}}
|
||||
<div class="featured-topic">
|
||||
{{topic-status topic=f}}
|
||||
<a class='title' href="{{unbound f.lastUnreadUrl}}">{{{unbound f.fancy_title}}}</a>
|
||||
{{topic-post-badges newPosts=f.totalUnread unseen=f.unseen url=f.lastUnreadUrl}}
|
||||
{{#if c.description_excerpt}}
|
||||
<div class="category-description">
|
||||
{{{c.description_excerpt}}}
|
||||
</div>
|
||||
{{/if}}
|
||||
{{#if c.subcategories}}
|
||||
<div class='subcategories'>
|
||||
{{#each s in c.subcategories}}
|
||||
{{category-link s hideParent="true"}}
|
||||
{{#if s.unreadTopics}}
|
||||
<a href={{unbound s.unreadUrl}} class='badge new-posts badge-notification' title='{{i18n 'topic.unread_topics' count=s.unreadTopics}}'>{{unbound s.unreadTopics}}</a>
|
||||
{{/if}}
|
||||
{{#if s.newTopics}}
|
||||
<a href={{unbound s.newUrl}} class='badge new-posts badge-notification' title='{{i18n 'topic.new_topics' count=s.newTopics}}'>{{unbound s.newTopics}}</a>
|
||||
{{/if}}
|
||||
{{/each}}
|
||||
</div>
|
||||
{{/if}}
|
||||
</td>
|
||||
<td {{bind-attr class="c.archived :latest"}}>
|
||||
{{#each f in c.featuredTopics}}
|
||||
<div class="featured-topic">
|
||||
{{topic-status topic=f}}
|
||||
<a class='title' href="{{unbound f.lastUnreadUrl}}">{{{unbound f.fancy_title}}}</a>
|
||||
{{topic-post-badges newPosts=f.totalUnread unseen=f.unseen url=f.lastUnreadUrl}}
|
||||
|
||||
{{#if controller.latestTopicOnly}}
|
||||
<div class='last-user-info'>
|
||||
{{i18n 'categories.latest_by'}} <a href="{{{unbound f.lastPosterUrl}}}">{{unbound f.last_poster.username}}</a>
|
||||
<a href="{{unbound f.lastPostUrl}}">{{format-age f.last_posted_at}}</a>
|
||||
</div>
|
||||
{{else}}
|
||||
|
||||
<a href="{{unbound f.lastPostUrl}}" class="last-posted-at">{{format-age f.last_posted_at}}</a>
|
||||
{{/if}}
|
||||
</div>
|
||||
{{/each}}
|
||||
</td>
|
||||
<td class='stats' {{bind-attr title="c.topicStatsTitle"}}>
|
||||
<table class="categoryStats">
|
||||
<tbody>
|
||||
{{#each s in c.topicCountStats}}
|
||||
<tr>
|
||||
<td class="value">{{s.value}}</td>
|
||||
<td class="unit"> / {{s.unit}}</td>
|
||||
</tr>
|
||||
{{#if controller.latestTopicOnly}}
|
||||
<div class='last-user-info'>
|
||||
{{i18n 'categories.latest_by'}} <a href="{{{unbound f.lastPosterUrl}}}">{{unbound f.last_poster.username}}</a>
|
||||
<a href="{{unbound f.lastPostUrl}}">{{format-age f.last_posted_at}}</a>
|
||||
</div>
|
||||
{{else}}
|
||||
|
||||
<a href="{{unbound f.lastPostUrl}}" class="last-posted-at">{{format-age f.last_posted_at}}</a>
|
||||
{{/if}}
|
||||
</div>
|
||||
{{/each}}
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</td>
|
||||
<td class='stats' {{bind-attr title="c.topicStatsTitle"}}>
|
||||
<table class="categoryStats">
|
||||
<tbody>
|
||||
{{#each s in c.topicCountStats}}
|
||||
<tr>
|
||||
<td class="value">{{s.value}}</td>
|
||||
<td class="unit"> / {{s.unit}}</td>
|
||||
</tr>
|
||||
{{/each}}
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
{{/each}}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@ -16,7 +16,7 @@
|
||||
|
||||
{{#if selected}}
|
||||
<div id='bulk-select'>
|
||||
<button class='btn no-text' {{action "showBulkActions"}}><i class="fa fa-wrench"></i></button>
|
||||
{{d-button action="showBulkActions" icon="wrench" class="no-text"}}
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
@ -37,6 +37,7 @@
|
||||
|
||||
{{#if hasTopics}}
|
||||
{{topic-list
|
||||
showTopicPostBadges=showTopicPostBadges
|
||||
showPosters=true
|
||||
currentUser=currentUser
|
||||
canBulkSelect=canBulkSelect
|
||||
|
||||
@ -10,9 +10,10 @@
|
||||
<div class="topic-meta-data">
|
||||
{{poster-name post=this}}
|
||||
{{#if view.parentView.previousPost}}<a href='{{unbound url}}' class="post-info arrow" title="{{i18n 'topic.jump_reply_up'}}"><i class='fa fa-arrow-up'></i></a>{{/if}}
|
||||
<div class='post-info post-date'>{{age-with-tooltip created_at}}</div>
|
||||
{{#unless view.parentView.previousPost}}<a href='{{unbound url}}' class="post-info arrow" title="{{i18n 'topic.jump_reply_down'}}"><i class='fa fa-arrow-down'></i></a>{{/unless}}
|
||||
<a href='{{unbound url}}'><div class='post-info post-date'>{{age-with-tooltip created_at}}</div></a>
|
||||
</div>
|
||||
{{{unbound cooked}}}
|
||||
{{#unless view.parentView.previousPost}}<a href='{{unbound url}}' class="arrow" title="{{i18n 'topic.jump_reply_down'}}"><i class='fa fa-arrow-down'></i></a>{{/unless}}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
<th colspan="3" class="seen">{{i18n 'last_seen'}}</th>
|
||||
</tr>
|
||||
{{#each m in members}}
|
||||
<tr>
|
||||
<tr>
|
||||
<td class='avatar'>
|
||||
{{avatar m imageSize="large"}}
|
||||
</td>
|
||||
@ -16,7 +16,6 @@
|
||||
<span class='last-seen-at'>{{bound-date m.last_seen_at}}</span>
|
||||
</td>
|
||||
</tr>
|
||||
</div>
|
||||
{{/each}}
|
||||
</table>
|
||||
{{/if}}
|
||||
|
||||
@ -6,13 +6,9 @@
|
||||
<div class='panel clearfix'>
|
||||
{{#unless currentUser}}
|
||||
{{#if showSignUpButton}}
|
||||
<button {{action "showCreateAccount"}} class='btn btn-primary btn-small sign-up-button'>
|
||||
{{i18n 'sign_up'}}
|
||||
</button>
|
||||
{{d-button action="showCreateAccount" class="btn-primary btn-small sign-up-button" label="sign_up"}}
|
||||
{{/if}}
|
||||
<button {{action "showLogin"}} class='btn btn-primary btn-small login-button'>
|
||||
{{fa-icon "user"}} {{i18n 'log_in'}}
|
||||
</button>
|
||||
{{d-button action="showLogin" class="btn-primary btn-small login-button" icon="user" label="log_in"}}
|
||||
{{/unless}}
|
||||
<ul class='icons clearfix' role='navigation'>
|
||||
{{#if currentUser}}
|
||||
@ -111,15 +107,10 @@
|
||||
{{else}}
|
||||
{{#if topic.errorLoading}}
|
||||
<span class="error">{{topic.errorTitle}}</span>
|
||||
{{else}}
|
||||
<span class="loading">{{i18n 'topic.loading'}}</span>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</h1>
|
||||
{{#if topic.category.parentCategory}}
|
||||
{{bound-category-link topic.category.parentCategory onlyStripe="true"}}
|
||||
{{/if}}
|
||||
{{bound-category-link topic.category onlyStripe="true"}}
|
||||
{{topic-category topic=topic}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -1 +1 @@
|
||||
<td class='category'>{{category-link category showParent="true"}}</td>
|
||||
<td class='category'>{{category-link category}}</td>
|
||||
|
||||
@ -32,7 +32,7 @@
|
||||
</div>
|
||||
{{#unless controller.hideCategory}}
|
||||
<div class='category'>
|
||||
{{category-link t.category showParent="true"}}
|
||||
{{category-link t.category}}
|
||||
</div>
|
||||
{{/unless}}
|
||||
{{#if controller.showParticipants}}
|
||||
|
||||
@ -46,7 +46,7 @@
|
||||
<td>
|
||||
<div class='subcategories'>
|
||||
{{#each subcategory in c.subcategories}}
|
||||
{{category-link subcategory showParent="true"}}
|
||||
{{category-link subcategory}}
|
||||
{{/each}}
|
||||
</div>
|
||||
</td>
|
||||
|
||||
@ -14,7 +14,6 @@
|
||||
|
||||
{{#if hasTopics}}
|
||||
{{topic-list
|
||||
skipHeader=true
|
||||
showPosters=true
|
||||
currentUser=currentUser
|
||||
hideCategory=hideCategory
|
||||
|
||||
@ -11,7 +11,7 @@
|
||||
<div class="topic-item-stats clearfix">
|
||||
{{#unless controller.hideCategory}}
|
||||
<div class='category'>
|
||||
{{category-link content.category showParent="true"}}
|
||||
{{category-link content.category}}
|
||||
</div>
|
||||
{{/unless}}
|
||||
|
||||
|
||||
@ -15,7 +15,7 @@
|
||||
{{#if subCategories}}
|
||||
<label>{{i18n 'categories.subcategories'}}</label>
|
||||
{{#each s in subCategories}}
|
||||
{{category-badge s}}
|
||||
{{category-badge s hideParent="true"}}
|
||||
{{/each}}
|
||||
{{else}}
|
||||
<label>{{i18n 'category.parent'}}</label>
|
||||
|
||||
@ -40,12 +40,12 @@
|
||||
→ {{bound-avatar-template user_changes.current.avatar_template "small"}} {{user_changes.current.username}}
|
||||
{{/if}}
|
||||
{{#if wiki_changes}}
|
||||
— {{{wiki_diff}}}
|
||||
— {{disabled-icon icon="pencil-square-o" secondary=wikiDisabled}}
|
||||
{{/if}}
|
||||
{{#if post_type_changes}}
|
||||
— {{{post_type_diff}}}
|
||||
— {{disabled-icon icon="shield" disabled=postTypeDisabled}}
|
||||
{{/if}}
|
||||
{{#if category_changes}}
|
||||
{{#if category_id_changes}}
|
||||
— {{{previousCategory}}} → {{{currentCategory}}}
|
||||
{{/if}}
|
||||
{{/unless}}
|
||||
@ -53,7 +53,7 @@
|
||||
<div id="revisions" {{bind-attr class="hiddenClasses"}}>
|
||||
{{#if title_changes}}
|
||||
<div class="row">
|
||||
<h2>{{{title_diff}}}</h2>
|
||||
<h2>{{{titleDiff}}}</h2>
|
||||
</div>
|
||||
{{/if}}
|
||||
{{#if site.mobileView}}
|
||||
@ -65,22 +65,25 @@
|
||||
{{/if}}
|
||||
{{#if wiki_changes}}
|
||||
<div class="row">
|
||||
{{{wiki_diff}}}
|
||||
{{disabled-icon icon="pencil-square-o" secondary=wikiDisabled}}
|
||||
</div>
|
||||
{{/if}}
|
||||
{{#if post_type_changes}}
|
||||
<div class="row">
|
||||
{{{post_type_diff}}}
|
||||
{{disabled-icon icon="shield" disabled=postTypeDisabled}}
|
||||
</div>
|
||||
{{/if}}
|
||||
{{#if category_changes}}
|
||||
{{#if category_id_changes}}
|
||||
<div class="row">
|
||||
{{{previousCategory}}} → {{{currentCategory}}}
|
||||
</div>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
|
||||
{{plugin-outlet "post-revisions"}}
|
||||
|
||||
<div class="row">
|
||||
{{{body_diff}}}
|
||||
{{{bodyDiff}}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -46,7 +46,7 @@
|
||||
|
||||
{{#each c in categories itemController='site-map-category'}}
|
||||
<li class="category">
|
||||
{{category-link c allowUncategorized="true" showParent="true"}}
|
||||
{{category-link c allowUncategorized="true"}}
|
||||
|
||||
{{#if c.unreadTotal}}
|
||||
<a href={{unbound c.url}} class='badge badge-notification'>{{c.unreadTotal}}</a>
|
||||
|
||||
@ -2,48 +2,48 @@
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
<button {{action "toggleMultiSelect"}} class='btn btn-admin'>{{fa-icon "tasks"}} {{i18n 'topic.actions.multi_select'}}</button>
|
||||
{{d-button action="toggleMultiSelect" icon="tasks" label="topic.actions.multi_select" class="btn-admin"}}
|
||||
</li>
|
||||
|
||||
{{#if details.can_delete}}
|
||||
<li>
|
||||
<button {{action "deleteTopic"}} class='btn btn-admin btn-danger'>{{fa-icon "trash-o"}} {{i18n 'topic.actions.delete'}}</button>
|
||||
{{d-button action="deleteTopic" icon="trash-o" label="topic.actions.delete" class="btn-admin btn-danger"}}
|
||||
</li>
|
||||
{{/if}}
|
||||
|
||||
{{#if showRecover}}
|
||||
<li>
|
||||
<button {{action "recoverTopic"}} class='btn btn-admin'>{{fa-icon "undo"}} {{i18n 'topic.actions.recover'}}</button>
|
||||
{{d-button action="recoverTopic" icon="undo" label="topic.actions.recover" class="btn-admin"}}
|
||||
</li>
|
||||
{{/if}}
|
||||
|
||||
<li>
|
||||
{{#if closed}}
|
||||
<button {{action "toggleClosed"}} class='btn btn-admin'>{{fa-icon "unlock"}} {{i18n 'topic.actions.open'}}</button>
|
||||
{{d-button action="toggleClosed" icon="unlock" label="topic.actions.open" class="btn-admin"}}
|
||||
{{else}}
|
||||
<button {{action "toggleClosed"}} class='btn btn-admin'>{{fa-icon "lock"}} {{i18n 'topic.actions.close'}}</button>
|
||||
<button {{action "showAutoClose"}} class='btn btn-admin'>{{fa-icon "clock-o"}} {{i18n 'topic.actions.auto_close'}}</button>
|
||||
{{d-button action="toggleClosed" icon="lock" label="topic.actions.close" class="btn-admin"}}
|
||||
{{d-button action="showAutoClose" icon="clock-o" label="topic.actions.auto_close" class="btn-admin"}}
|
||||
{{/if}}
|
||||
</li>
|
||||
|
||||
{{#unless isPrivateMessage}}
|
||||
<li>
|
||||
{{#if isBanner}}
|
||||
<button {{action "removeBanner"}} class='btn btn-admin'>{{fa-icon "bullhorn"}} {{i18n 'topic.actions.remove_banner'}}</button>
|
||||
{{d-button action="removeBanner" icon="bullhorn" label="topic.actions.remove_banner" class="btn-admin"}}
|
||||
{{else}}
|
||||
{{#if visible}}
|
||||
<button {{action "makeBanner"}} class='btn btn-admin'>{{fa-icon "bullhorn"}} {{i18n 'topic.actions.make_banner'}}</button>
|
||||
{{d-button action="makeBanner" icon="bullhorn" label="topic.actions.make_banner" class="btn-admin"}}
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</li>
|
||||
|
||||
<li>
|
||||
{{#if pinned_at}}
|
||||
<button {{action "togglePinned"}} class='btn btn-admin'>{{fa-icon "thumb-tack"}} {{i18n 'topic.actions.unpin'}}</button>
|
||||
{{d-button action="togglePinned" icon="thumb-tack" label="topic.actions.unpin" class="btn-admin"}}
|
||||
{{else}}
|
||||
{{#if visible}}
|
||||
<button {{action "togglePinned"}} class='btn btn-admin'>{{fa-icon "thumb-tack"}} {{i18n 'topic.actions.pin'}}</button>
|
||||
<button {{action "togglePinnedGlobally"}} class='btn btn-admin'>{{fa-icon "thumb-tack"}} {{i18n 'topic.actions.pin_globally'}}</button>
|
||||
{{d-button action="togglePinned" icon="thumb-tack" label="topic.actions.pin" class="btn-admin"}}
|
||||
{{d-button action="togglePinnedGlobally" icon="thumb-tack" label="topic.actions.pin_globally" class="btn-admin"}}
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</li>
|
||||
@ -51,18 +51,19 @@
|
||||
|
||||
<li>
|
||||
{{#if archived}}
|
||||
<button {{action "toggleArchived"}} class='btn btn-admin'>{{fa-icon "folder"}} {{i18n 'topic.actions.unarchive'}}</button>
|
||||
{{d-button action="toggleArchived" icon="folder" label="topic.actions.unarchive" class="btn-admin"}}
|
||||
{{else}}
|
||||
<button {{action "toggleArchived"}} class='btn btn-admin'>{{fa-icon "folder"}} {{i18n 'topic.actions.archive'}}</button>
|
||||
{{d-button action="toggleArchived" icon="folder" label="topic.actions.archive" class="btn-admin"}}
|
||||
{{/if}}
|
||||
</li>
|
||||
|
||||
<li>
|
||||
{{#if visible}}
|
||||
<button {{action "toggleVisibility"}} class='btn btn-admin'>{{fa-icon "eye-slash"}} {{i18n 'topic.actions.invisible'}}</button>
|
||||
{{d-button action="toggleVisibility" icon="eye-slash" label="topic.actions.invisible" class="btn-admin"}}
|
||||
{{else}}
|
||||
<button {{action "toggleVisibility"}} class='btn btn-admin'>{{fa-icon "eye"}} {{i18n 'topic.actions.visible'}}</button>
|
||||
{{d-button action="toggleVisibility" icon="eye" label="topic.actions.visible" class="btn-admin"}}
|
||||
{{/if}}
|
||||
</li>
|
||||
|
||||
{{plugin-outlet "topic-admin-menu-buttons"}}
|
||||
</ul>
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
<button {{action "enterTop"}} class='btn full no-text jump-top'>
|
||||
<i class='fa fa-caret-up'></i> {{{topDate}}}
|
||||
</button>
|
||||
<button {{action "enterBottom"}} class='btn full no-text jump-bottom'>
|
||||
{{{bottomDate}}} <i class='fa fa-caret-down'></i>
|
||||
</button>
|
||||
{{#d-button action="enterTop" class="full no-text jump-top"}}
|
||||
{{fa-icon 'caret-up'}} {{{topDate}}}
|
||||
{{/d-button}}
|
||||
|
||||
{{#d-button action="enterBottom" class="full no-text jump-button"}}
|
||||
{{{bottomDate}}} {{fa-icon 'caret-down'}}
|
||||
{{/d-button}}
|
||||
|
||||
@ -1,16 +1,19 @@
|
||||
{{#if expanded}}
|
||||
<nav id='topic-progress-expanded'>
|
||||
<button {{action "jumpTop"}} {{bind-attr disabled=jumpTopDisabled}} class='btn full no-text'>
|
||||
<i class="fa fa-caret-up"></i>
|
||||
{{i18n 'topic.progress.go_top'}}
|
||||
</button>
|
||||
{{d-button action="jumpTop"
|
||||
disabled=jumpTopDisabled
|
||||
class="full no-text"
|
||||
icon="caret-up"
|
||||
label="topic.progress.go_top"}}
|
||||
<div class='jump-form'>
|
||||
{{input value=toPostIndex}} <button {{action "jumpPost"}} class='btn'>{{i18n 'topic.progress.go'}}</button>
|
||||
{{input value=toPostIndex}}
|
||||
{{d-button action="jumpPost" label="topic.progress.go"}}
|
||||
</div>
|
||||
<button {{action "jumpBottom"}} {{bind-attr disabled=jumpBottomDisabled}} class='btn full no-text jump-bottom'>
|
||||
{{i18n 'topic.progress.go_bottom'}}
|
||||
<i class="fa fa-caret-down"></i>
|
||||
</button>
|
||||
{{d-button action="jumpBottom"
|
||||
disabled=jumpBottomDisabled
|
||||
class="full no-text jump-bottom"
|
||||
icon="caret-down"
|
||||
label="topic.progress.go_bottom"}}
|
||||
</nav>
|
||||
{{/if}}
|
||||
<nav id='topic-progress' title="{{i18n 'topic.progress.title'}}" {{bind-attr class="hideProgress:hidden"}}>
|
||||
|
||||
@ -22,8 +22,8 @@
|
||||
|
||||
{{plugin-outlet "edit-topic"}}
|
||||
|
||||
<button class='btn btn-primary btn-small no-text' {{action "finishedEditingTopic"}}>{{fa-icon "check"}}</button>
|
||||
<button class='btn btn-small no-text' {{action "cancelEditingTopic"}}>{{fa-icon "times"}}</button>
|
||||
{{d-button action="finishedEditingTopic" class="btn-primary btn-small no-text" icon="check"}}
|
||||
{{d-button action="cancelEditingTopic" class="btn-small no-text" icon="times"}}
|
||||
{{else}}
|
||||
<h1>
|
||||
{{#unless is_warning}}
|
||||
@ -43,11 +43,7 @@
|
||||
</h1>
|
||||
|
||||
{{#unless isPrivateMessage}}
|
||||
{{#if category.parentCategory}}
|
||||
{{bound-category-link category.parentCategory}}
|
||||
{{/if}}
|
||||
{{bound-category-link category}}
|
||||
{{plugin-outlet "topic-category"}}
|
||||
{{topic-category topic=model}}
|
||||
{{/unless}}
|
||||
{{/if}}
|
||||
</div>
|
||||
@ -118,10 +114,10 @@
|
||||
<div>{{message}}</div>
|
||||
{{#if noRetry}}
|
||||
{{#unless currentUser}}
|
||||
<button {{action "showLogin"}} class='btn btn-primary topic-retry'>{{fa-icon "user"}} {{i18n 'log_in'}}</button>
|
||||
{{d-button action="showLogin" class="btn-primary topic-retry" icon="user" label="log_in"}}
|
||||
{{/unless}}
|
||||
{{else}}
|
||||
<button class="btn btn-primary topic-retry" {{action "retryLoading"}}>{{fa-icon "refresh"}} {{i18n 'errors.buttons.again'}}</button>
|
||||
{{d-button action="retryLoading" class="btn-primary topic-retry" icon="refresh" label="errors.buttons.again"}}
|
||||
{{/if}}
|
||||
</div>
|
||||
{{loading-spinner condition=retrying}}
|
||||
|
||||
@ -8,6 +8,6 @@
|
||||
</li>
|
||||
<li>{{#link-to 'userActivity.bookmarks' currentUser}}{{i18n 'user.bookmarks'}}{{/link-to}}</li>
|
||||
<li>{{#link-to 'preferences' currentUser}}{{i18n 'user.preferences'}}{{/link-to}}</li>
|
||||
<li><button {{action "logout"}} class='btn btn-danger right logout'><i class='fa fa-sign-out'></i>{{i18n 'user.log_out'}}</button></li>
|
||||
<li>{{d-button action="logout" class="btn-danger right logout" icon="sign-out" label="user.log_out"}}</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user