Version bump
This commit is contained in:
commit
9e96152788
@ -14,11 +14,13 @@ matrix:
|
||||
env: "RAILS_MASTER=1"
|
||||
- rvm: 2.1
|
||||
env: "RAILS_MASTER=1"
|
||||
- rvm: rbx-2
|
||||
fast_finish: true
|
||||
|
||||
rvm:
|
||||
- 2.0.0
|
||||
- 2.1
|
||||
- rbx-2
|
||||
|
||||
services:
|
||||
- redis-server
|
||||
|
||||
76
Gemfile.lock
76
Gemfile.lock
@ -11,31 +11,31 @@ GEM
|
||||
execjs (~> 2.0)
|
||||
6to5-source (3.3.7)
|
||||
CFPropertyList (2.2.8)
|
||||
actionmailer (4.1.8)
|
||||
actionpack (= 4.1.8)
|
||||
actionview (= 4.1.8)
|
||||
actionmailer (4.1.9)
|
||||
actionpack (= 4.1.9)
|
||||
actionview (= 4.1.9)
|
||||
mail (~> 2.5, >= 2.5.4)
|
||||
actionpack (4.1.8)
|
||||
actionview (= 4.1.8)
|
||||
activesupport (= 4.1.8)
|
||||
actionpack (4.1.9)
|
||||
actionview (= 4.1.9)
|
||||
activesupport (= 4.1.9)
|
||||
rack (~> 1.5.2)
|
||||
rack-test (~> 0.6.2)
|
||||
actionpack-action_caching (1.1.1)
|
||||
actionpack (>= 4.0.0, < 5.0)
|
||||
actionview (4.1.8)
|
||||
activesupport (= 4.1.8)
|
||||
actionview (4.1.9)
|
||||
activesupport (= 4.1.9)
|
||||
builder (~> 3.1)
|
||||
erubis (~> 2.7.0)
|
||||
active_model_serializers (0.8.2)
|
||||
activemodel (>= 3.0)
|
||||
activemodel (4.1.8)
|
||||
activesupport (= 4.1.8)
|
||||
activemodel (4.1.9)
|
||||
activesupport (= 4.1.9)
|
||||
builder (~> 3.1)
|
||||
activerecord (4.1.8)
|
||||
activemodel (= 4.1.8)
|
||||
activesupport (= 4.1.8)
|
||||
activerecord (4.1.9)
|
||||
activemodel (= 4.1.9)
|
||||
activesupport (= 4.1.9)
|
||||
arel (~> 5.0.0)
|
||||
activesupport (4.1.8)
|
||||
activesupport (4.1.9)
|
||||
i18n (~> 0.6, >= 0.6.9)
|
||||
json (~> 1.7, >= 1.7.7)
|
||||
minitest (~> 5.1)
|
||||
@ -66,9 +66,7 @@ GEM
|
||||
debug_inspector (0.0.2)
|
||||
diff-lcs (1.2.5)
|
||||
docile (1.1.5)
|
||||
dotenv (0.11.1)
|
||||
dotenv-deployment (~> 0.0.2)
|
||||
dotenv-deployment (0.0.2)
|
||||
dotenv (1.0.2)
|
||||
email_reply_parser (0.5.8)
|
||||
ember-data-source (0.14)
|
||||
ember-source
|
||||
@ -171,8 +169,8 @@ GEM
|
||||
fog-xml (0.1.1)
|
||||
fog-core
|
||||
nokogiri (~> 1.5, >= 1.5.11)
|
||||
foreman (0.75.0)
|
||||
dotenv (~> 0.11.1)
|
||||
foreman (0.77.0)
|
||||
dotenv (~> 1.0.2)
|
||||
thor (~> 0.19.1)
|
||||
formatador (0.2.5)
|
||||
fspath (2.0.6)
|
||||
@ -181,13 +179,13 @@ GEM
|
||||
sorcerer (>= 0.3.7)
|
||||
guess_html_encoding (0.0.9)
|
||||
handlebars-source (2.0.0)
|
||||
hashie (3.3.2)
|
||||
hashie (3.4.0)
|
||||
highline (1.6.21)
|
||||
hike (1.2.3)
|
||||
hiredis (0.6.0)
|
||||
hitimes (1.2.2)
|
||||
htmlentities (4.3.3)
|
||||
i18n (0.6.11)
|
||||
i18n (0.7.0)
|
||||
image_optim (0.9.1)
|
||||
exifr (~> 1.1.3)
|
||||
fspath (~> 2.0.5)
|
||||
@ -209,8 +207,8 @@ GEM
|
||||
thor (~> 0.15)
|
||||
libv8 (3.16.14.7)
|
||||
listen (0.7.3)
|
||||
logster (0.1.6)
|
||||
lru_redux (0.8.1)
|
||||
logster (0.1.7)
|
||||
lru_redux (0.8.4)
|
||||
mail (2.5.4)
|
||||
mime-types (~> 1.16)
|
||||
treetop (~> 1.4.8)
|
||||
@ -274,7 +272,7 @@ GEM
|
||||
omniauth-twitter (1.0.1)
|
||||
multi_json (~> 1.3)
|
||||
omniauth-oauth (~> 1.0)
|
||||
onebox (1.5.12)
|
||||
onebox (1.5.13)
|
||||
moneta (~> 0.7)
|
||||
multi_json (~> 1.7)
|
||||
mustache (~> 0.99)
|
||||
@ -298,30 +296,30 @@ GEM
|
||||
qunit-rails (0.0.7)
|
||||
railties
|
||||
rack (1.5.2)
|
||||
rack-mini-profiler (0.9.2)
|
||||
rack-mini-profiler (0.9.3)
|
||||
rack (>= 1.1.3)
|
||||
rack-openid (1.3.1)
|
||||
rack (>= 1.1.0)
|
||||
ruby-openid (>= 2.1.8)
|
||||
rack-protection (1.5.3)
|
||||
rack
|
||||
rack-test (0.6.2)
|
||||
rack-test (0.6.3)
|
||||
rack (>= 1.0)
|
||||
rails (4.1.8)
|
||||
actionmailer (= 4.1.8)
|
||||
actionpack (= 4.1.8)
|
||||
actionview (= 4.1.8)
|
||||
activemodel (= 4.1.8)
|
||||
activerecord (= 4.1.8)
|
||||
activesupport (= 4.1.8)
|
||||
rails (4.1.9)
|
||||
actionmailer (= 4.1.9)
|
||||
actionpack (= 4.1.9)
|
||||
actionview (= 4.1.9)
|
||||
activemodel (= 4.1.9)
|
||||
activerecord (= 4.1.9)
|
||||
activesupport (= 4.1.9)
|
||||
bundler (>= 1.3.0, < 2.0)
|
||||
railties (= 4.1.8)
|
||||
railties (= 4.1.9)
|
||||
sprockets-rails (~> 2.0)
|
||||
rails-observers (0.1.2)
|
||||
activemodel (~> 4.0)
|
||||
railties (4.1.8)
|
||||
actionpack (= 4.1.8)
|
||||
activesupport (= 4.1.8)
|
||||
railties (4.1.9)
|
||||
actionpack (= 4.1.9)
|
||||
activesupport (= 4.1.9)
|
||||
rake (>= 0.8.7)
|
||||
thor (>= 0.18.1, < 2.0)
|
||||
raindrops (0.13.0)
|
||||
@ -391,7 +389,7 @@ GEM
|
||||
shoulda-context (1.2.1)
|
||||
shoulda-matchers (2.7.0)
|
||||
activesupport (>= 3.0.0)
|
||||
sidekiq (3.3.1)
|
||||
sidekiq (3.3.2)
|
||||
celluloid (>= 0.16.0)
|
||||
connection_pool (>= 2.1.1)
|
||||
json
|
||||
@ -434,7 +432,7 @@ GEM
|
||||
thor (0.19.1)
|
||||
thread_safe (0.3.4)
|
||||
tilt (1.4.1)
|
||||
timecop (0.7.1)
|
||||
timecop (0.7.3)
|
||||
timers (4.0.1)
|
||||
hitimes
|
||||
treetop (1.4.15)
|
||||
|
||||
@ -0,0 +1,3 @@
|
||||
export default Ember.Component.extend({
|
||||
tagName: 'tbody'
|
||||
});
|
||||
@ -13,6 +13,42 @@ export default Ember.ArrayController.extend({
|
||||
sortProperties: ['granted_at'],
|
||||
sortAscending: false,
|
||||
|
||||
groupedBadges: function(){
|
||||
const badges = this.get('model');
|
||||
|
||||
var grouped = _.groupBy(badges, badge => badge.badge_id);
|
||||
|
||||
var expanded = [];
|
||||
const expandedBadges = badges.get('expandedBadges');
|
||||
|
||||
_(grouped).each(function(badges){
|
||||
var lastGranted = badges[0].granted_at;
|
||||
|
||||
_.each(badges, function(badge) {
|
||||
lastGranted = lastGranted < badge.granted_at ? badge.granted_at : lastGranted;
|
||||
});
|
||||
|
||||
if(badges.length===1 || _.include(expandedBadges, badges[0].badge.id)){
|
||||
_.each(badges, badge => expanded.push(badge));
|
||||
return;
|
||||
}
|
||||
|
||||
var result = {
|
||||
badge: badges[0].badge,
|
||||
granted_at: lastGranted,
|
||||
badges: badges,
|
||||
count: badges.length,
|
||||
grouped: true
|
||||
};
|
||||
|
||||
expanded.push(result);
|
||||
});
|
||||
|
||||
return _(expanded).sortBy(group => group.granted_at).reverse().value();
|
||||
|
||||
|
||||
}.property('model', 'model.@each', 'model.expandedBadges.@each'),
|
||||
|
||||
/**
|
||||
Array of badges that have not been granted to this user.
|
||||
|
||||
@ -45,6 +81,12 @@ export default Ember.ArrayController.extend({
|
||||
|
||||
actions: {
|
||||
|
||||
expandGroup: function(userBadge){
|
||||
const model = this.get('model');
|
||||
model.set('expandedBadges', model.get('expandedBadges') || []);
|
||||
model.get('expandedBadges').pushObject(userBadge.badge.id);
|
||||
},
|
||||
|
||||
/**
|
||||
Grant the selected badge to the user.
|
||||
|
||||
@ -53,7 +95,8 @@ export default Ember.ArrayController.extend({
|
||||
**/
|
||||
grantBadge: function(badgeId) {
|
||||
var self = this;
|
||||
Discourse.UserBadge.grant(badgeId, this.get('user.username')).then(function(userBadge) {
|
||||
Discourse.UserBadge.grant(badgeId, this.get('user.username'), this.get('badgeReason')).then(function(userBadge) {
|
||||
self.set('badgeReason', '');
|
||||
self.pushObject(userBadge);
|
||||
Ember.run.next(function() {
|
||||
// Update the selected badge ID after the combobox has re-rendered.
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
import ModalFunctionality from 'discourse/mixins/modal-functionality';
|
||||
|
||||
import ObjectController from 'discourse/controllers/object';
|
||||
|
||||
export default ObjectController.extend(ModalFunctionality, {
|
||||
@ -7,14 +6,16 @@ export default ObjectController.extend(ModalFunctionality, {
|
||||
newSelected: Ember.computed.equal('selectedTab', 'new'),
|
||||
|
||||
onShow: function() {
|
||||
this.selectNew();
|
||||
this.send("selectNew");
|
||||
},
|
||||
|
||||
selectNew: function() {
|
||||
this.set('selectedTab', 'new');
|
||||
},
|
||||
actions: {
|
||||
selectNew: function() {
|
||||
this.set('selectedTab', 'new');
|
||||
},
|
||||
|
||||
selectPrevious: function() {
|
||||
this.set('selectedTab', 'previous');
|
||||
selectPrevious: function() {
|
||||
this.set('selectedTab', 'previous');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@ -92,9 +92,9 @@ Discourse.Report = Discourse.Model.extend({
|
||||
icon: function() {
|
||||
switch( this.get('type') ) {
|
||||
case 'flags':
|
||||
return 'fa-flag';
|
||||
return 'flag';
|
||||
case 'likes':
|
||||
return 'fa-heart';
|
||||
return 'heart';
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -1,8 +1,4 @@
|
||||
Discourse.AdminRoute = Discourse.Route.extend({
|
||||
renderTemplate: function() {
|
||||
this.render('admin/templates/admin');
|
||||
},
|
||||
|
||||
titleToken: function() {
|
||||
return I18n.t('admin_title');
|
||||
}
|
||||
|
||||
@ -8,7 +8,7 @@ Discourse.AdminUserRoute = Discourse.Route.extend({
|
||||
},
|
||||
|
||||
renderTemplate: function() {
|
||||
this.render({into: 'admin/templates/admin'});
|
||||
this.render({into: 'admin'});
|
||||
},
|
||||
|
||||
afterModel: function(adminUser) {
|
||||
|
||||
@ -0,0 +1,13 @@
|
||||
<tr>
|
||||
<td class="title">
|
||||
{{#if report.icon}}
|
||||
{{fa-icon report.icon}}
|
||||
{{/if}}
|
||||
<a {{bind-attr href="report.reportUrl"}}>{{report.title}}</a>
|
||||
</td>
|
||||
<td class="value">{{report.todayCount}}</td>
|
||||
<td {{bind-attr class=":value report.yesterdayTrend"}} {{bind-attr title="report.yesterdayCountTitle"}}>{{report.yesterdayCount}} {{fa-icon "caret-up" class="up"}} {{fa-icon "caret-down" class="down"}}</td>
|
||||
<td {{bind-attr class=":value report.sevenDayTrend"}} {{bind-attr title="report.sevenDayCountTitle"}}>{{report.lastSevenDaysCount}} {{fa-icon "caret-up" class="up"}} {{fa-icon "caret-down" class="down"}}</td>
|
||||
<td {{bind-attr class=":value report.thirtyDayTrend"}} {{bind-attr title="report.thirtyDayCountTitle"}}>{{report.lastThirtyDaysCount}} {{fa-icon "caret-up" class="up"}} {{fa-icon "caret-down" class="down"}}</td>
|
||||
<td class="value">{{report.total}}</td>
|
||||
</tr>
|
||||
@ -16,7 +16,7 @@
|
||||
</tr>
|
||||
</thead>
|
||||
{{#unless loading}}
|
||||
{{ render 'admin/templates/reports/trust_levels_report' users_by_trust_level tagName="tbody" }}
|
||||
{{ render 'admin/templates/reports/trust_levels_report' users_by_trust_level tagName="tbody"}}
|
||||
{{/unless}}
|
||||
</table>
|
||||
</div>
|
||||
@ -52,13 +52,13 @@
|
||||
</thead>
|
||||
{{#unless loading}}
|
||||
{{ render 'admin/templates/reports/per_day_counts_report' visits tagName="tbody"}}
|
||||
{{ render 'admin_report_counts' signups }}
|
||||
{{ render 'admin_report_counts' topics }}
|
||||
{{ render 'admin_report_counts' posts }}
|
||||
{{ render 'admin_report_counts' likes }}
|
||||
{{ render 'admin_report_counts' flags }}
|
||||
{{ render 'admin_report_counts' bookmarks }}
|
||||
{{ render 'admin_report_counts' emails }}
|
||||
{{admin-report-counts report=signups}}
|
||||
{{admin-report-counts report=topics}}
|
||||
{{admin-report-counts report=posts}}
|
||||
{{admin-report-counts report=likes}}
|
||||
{{admin-report-counts report=flags}}
|
||||
{{admin-report-counts report=bookmarks}}
|
||||
{{admin-report-counts report=emails}}
|
||||
{{/unless}}
|
||||
</table>
|
||||
</div>
|
||||
@ -76,10 +76,10 @@
|
||||
</tr>
|
||||
</thead>
|
||||
{{#unless loading}}
|
||||
{{ render 'admin_report_counts' page_view_anon_reqs }}
|
||||
{{ render 'admin_report_counts' page_view_logged_in_reqs }}
|
||||
{{ render 'admin_report_counts' page_view_crawler_reqs }}
|
||||
{{ render 'admin_report_counts' page_view_total_reqs }}
|
||||
{{admin-report-counts report=page_view_anon_reqs}}
|
||||
{{admin-report-counts report=page_view_logged_in_reqs}}
|
||||
{{admin-report-counts report=page_view_crawler_reqs}}
|
||||
{{admin-report-counts report=page_view_total_reqs}}
|
||||
{{/unless}}
|
||||
</table>
|
||||
</div>
|
||||
@ -98,11 +98,11 @@
|
||||
</tr>
|
||||
</thead>
|
||||
{{#unless loading}}
|
||||
{{ render 'admin_report_counts' user_to_user_private_messages }}
|
||||
{{ render 'admin_report_counts' system_private_messages }}
|
||||
{{ render 'admin_report_counts' notify_moderators_private_messages }}
|
||||
{{ render 'admin_report_counts' notify_user_private_messages }}
|
||||
{{ render 'admin_report_counts' moderator_warning_private_messages }}
|
||||
{{admin-report-counts report=user_to_user_private_messages}}
|
||||
{{admin-report-counts report=system_private_messages}}
|
||||
{{admin-report-counts report=notify_moderators_private_messages}}
|
||||
{{admin-report-counts report=notify_user_private_messages}}
|
||||
{{admin-report-counts report=moderator_warning_private_messages}}
|
||||
{{/unless}}
|
||||
</table>
|
||||
</div>
|
||||
@ -145,12 +145,12 @@
|
||||
</tr>
|
||||
</thead>
|
||||
{{#unless loading}}
|
||||
{{ render 'admin_report_counts' http_2xx_reqs }}
|
||||
{{ render 'admin_report_counts' http_3xx_reqs}}
|
||||
{{ render 'admin_report_counts' http_4xx_reqs}}
|
||||
{{ render 'admin_report_counts' http_5xx_reqs}}
|
||||
{{ render 'admin_report_counts' http_background_reqs }}
|
||||
{{ render 'admin_report_counts' http_total_reqs }}
|
||||
{{admin-report-counts report=http_2xx_reqs}}
|
||||
{{admin-report-counts report=http_3xx_reqs}}
|
||||
{{admin-report-counts report=http_4xx_reqs}}
|
||||
{{admin-report-counts report=http_5xx_reqs}}
|
||||
{{admin-report-counts report=http_background_reqs}}
|
||||
{{admin-report-counts report=http_total_reqs}}
|
||||
{{/unless}}
|
||||
</table>
|
||||
</div>
|
||||
|
||||
@ -1,19 +1,73 @@
|
||||
<section class="field">
|
||||
<b>{{i18n 'admin.customize.css'}}</b>:
|
||||
{{#if stylesheet}}
|
||||
({{i18n 'character_count' count=stylesheet.length}})
|
||||
{{/if}}
|
||||
<br/>
|
||||
{{textarea value=stylesheet class="plain"}}
|
||||
</section>
|
||||
<section class="field">
|
||||
<b>{{i18n 'admin.customize.header'}}</b>:
|
||||
{{#if header}}
|
||||
({{i18n 'character_count' count=header.length}})
|
||||
{{/if}}
|
||||
<br/>
|
||||
{{textarea value=header class="plain"}}
|
||||
</section>
|
||||
<section class="field">
|
||||
<b>{{i18n 'admin.customize.enabled'}}</b>: {{enabled}}
|
||||
</section>
|
||||
{{#if stylesheet}}
|
||||
<section class="field">
|
||||
<b>{{i18n 'admin.customize.css'}}</b>: ({{i18n 'character_count' count=stylesheet.length}})
|
||||
<br/>
|
||||
{{textarea value=stylesheet class="plain"}}
|
||||
</section>
|
||||
{{/if}}
|
||||
{{#if mobile_stylesheet}}
|
||||
<section class="field">
|
||||
<b>{{fa-icon "mobile"}} {{i18n 'admin.customize.css'}}</b>: ({{i18n 'character_count' count=mobile_stylesheet.length}})
|
||||
<br/>
|
||||
{{textarea value=mobile_stylesheet class="plain"}}
|
||||
</section>
|
||||
{{/if}}
|
||||
{{#if header}}
|
||||
<section class="field">
|
||||
<b>{{i18n 'admin.customize.header'}}</b>: ({{i18n 'character_count' count=header.length}})
|
||||
<br/>
|
||||
{{textarea value=header class="plain"}}
|
||||
</section>
|
||||
{{/if}}
|
||||
{{#if mobile_header}}
|
||||
<section class="field">
|
||||
<b>{{fa-icon "mobile"}} {{i18n 'admin.customize.header'}}</b>: ({{i18n 'character_count' count=mobile_header.length}})
|
||||
<br/>
|
||||
{{textarea value=mobile_header class="plain"}}
|
||||
</section>
|
||||
{{/if}}
|
||||
{{#if top}}
|
||||
<section class="field">
|
||||
<b>{{i18n 'admin.customize.top'}}</b>: ({{i18n 'character_count' count=top.length}})
|
||||
<br/>
|
||||
{{textarea value=top class="plain"}}
|
||||
</section>
|
||||
{{/if}}
|
||||
{{#if mobile_top}}
|
||||
<section class="field">
|
||||
<b>{{fa-icon "mobile"}} {{i18n 'admin.customize.top'}}</b>: ({{i18n 'character_count' count=mobile_top.length}})
|
||||
<br/>
|
||||
{{textarea value=mobile_top class="plain"}}
|
||||
</section>
|
||||
{{/if}}
|
||||
{{#if footer}}
|
||||
<section class="field">
|
||||
<b>{{i18n 'admin.customize.footer'}}</b>: ({{i18n 'character_count' count=footer.length}})
|
||||
<br/>
|
||||
{{textarea value=footer class="plain"}}
|
||||
</section>
|
||||
{{/if}}
|
||||
{{#if mobile_footer}}
|
||||
<section class="field">
|
||||
<b>{{fa-icon "mobile"}} {{i18n 'admin.customize.footer'}}</b>: ({{i18n 'character_count' count=mobile_footer.length}})
|
||||
<br/>
|
||||
{{textarea value=mobile_footer class="plain"}}
|
||||
</section>
|
||||
{{/if}}
|
||||
{{#if head_tag}}
|
||||
<section class="field">
|
||||
<b>{{fa-icon "file-text-o"}} {{i18n 'admin.customize.head_tag.text'}}</b>: ({{i18n 'character_count' count=head_tag.length}})
|
||||
<br/>
|
||||
{{textarea value=head_tag class="plain"}}
|
||||
</section>
|
||||
{{/if}}
|
||||
{{#if body_tag}}
|
||||
<section class="field">
|
||||
<b>{{fa-icon "file-text-o"}} {{i18n 'admin.customize.body_tag.text'}}</b>: ({{i18n 'character_count' count=body_tag.length}})
|
||||
<br/>
|
||||
{{textarea value=body_tag class="plain"}}
|
||||
</section>
|
||||
{{/if}}
|
||||
|
||||
@ -1,13 +0,0 @@
|
||||
<tr>
|
||||
<td class="title">
|
||||
{{#if icon}}
|
||||
<i {{bind-attr class=":fa icon"}}></i>
|
||||
{{/if}}
|
||||
<a {{bind-attr href="reportUrl"}}>{{title}}</a>
|
||||
</td>
|
||||
<td class="value">{{todayCount}}</td>
|
||||
<td {{bind-attr class=":value yesterdayTrend"}} {{bind-attr title="yesterdayCountTitle"}}>{{yesterdayCount}} <i class="fa up fa-caret-up"></i><i class="fa down fa-caret-down"></i></td>
|
||||
<td {{bind-attr class=":value sevenDayTrend"}} {{bind-attr title="sevenDayCountTitle"}}>{{lastSevenDaysCount}} <i class="fa up fa-caret-up"></i><i class="fa down fa-caret-down"></i></td>
|
||||
<td {{bind-attr class=":value thirtyDayTrend"}} {{bind-attr title="thirtyDayCountTitle"}}>{{lastThirtyDaysCount}} <i class="fa up fa-caret-up"></i><i class="fa down fa-caret-down"></i></td>
|
||||
<td class="value">{{total}}</td>
|
||||
</tr>
|
||||
@ -3,6 +3,7 @@
|
||||
</div>
|
||||
<div class="setting-value">
|
||||
{{list-setting settingValue=value choices=choices settingName=setting}}
|
||||
<div {{bind-attr class=":validation-error validationMessage::hidden"}}><i class='fa fa-times'></i> {{validationMessage}}</div>
|
||||
<div class='desc'>{{{unbound description}}}</div>
|
||||
</div>
|
||||
{{#if dirty}}
|
||||
|
||||
@ -9,40 +9,53 @@
|
||||
{{#loading-spinner condition=loading}}
|
||||
<div class='admin-container user-badges'>
|
||||
<h2>{{i18n 'admin.badges.grant_badge'}}</h2>
|
||||
<br>
|
||||
{{#if noBadges}}
|
||||
<p>{{i18n 'admin.badges.no_badges'}}</p>
|
||||
{{else}}
|
||||
<br>
|
||||
<form class="form-horizontal">
|
||||
<div>
|
||||
<label>{{i18n 'admin.badges.badge'}}</label>
|
||||
{{combo-box valueAttribute="id" value=controller.selectedBadgeId content=controller.grantableBadges}}
|
||||
</div>
|
||||
<label>
|
||||
<label>{{i18n 'admin.badges.reason'}}</label>
|
||||
{{input type="text" value=badgeReason}}<br><small>{{i18n 'admin.badges.reason_help'}}</small>
|
||||
</label>
|
||||
<button class='btn btn-primary' {{action "grantBadge" controller.selectedBadgeId}}>{{i18n 'admin.badges.grant'}}</button>
|
||||
</form>
|
||||
{{/if}}
|
||||
|
||||
<br>
|
||||
<br>
|
||||
|
||||
<h2>{{i18n 'admin.badges.granted_badges'}}</h2>
|
||||
<br>
|
||||
|
||||
<table>
|
||||
<table id='user-badges'>
|
||||
<tr>
|
||||
<th>{{i18n 'admin.badges.badge'}}</th>
|
||||
<th>{{i18n 'admin.badges.granted_by'}}</th>
|
||||
<th class='reason'>{{i18n 'admin.badges.reason'}}</th>
|
||||
<th>{{i18n 'admin.badges.granted_at'}}</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
|
||||
{{#each}}
|
||||
{{#each userBadge in groupedBadges}}
|
||||
<tr>
|
||||
<td>{{user-badge badge=badge}}</td>
|
||||
<td>{{user-badge badge=userBadge.badge count=userBadge.count}}</td>
|
||||
<td>
|
||||
{{#link-to 'adminUser' badge.granted_by}}
|
||||
{{avatar granted_by imageSize="tiny"}}
|
||||
{{granted_by.username}}
|
||||
{{#link-to 'adminUser' userBadge.badge.granted_by}}
|
||||
{{avatar userBadge.granted_by imageSize="tiny"}}
|
||||
{{userBadge.granted_by.username}}
|
||||
{{/link-to}}
|
||||
</td>
|
||||
<td>{{age-with-tooltip granted_at}}</td>
|
||||
<td class='reason'>
|
||||
{{#if userBadge.postUrl}}
|
||||
<a href="{{unbound userBadge.postUrl}}">{{userBadge.topic_title}}</a>
|
||||
{{/if}}
|
||||
</td>
|
||||
<td>{{age-with-tooltip userBadge.granted_at}}</td>
|
||||
<td>
|
||||
<button class='btn' {{action "revokeBadge" this}}>{{i18n 'admin.badges.revoke'}}</button>
|
||||
{{#if userBadge.grouped}}
|
||||
<button class='btn' {{action "expandGroup" userBadge}}>{{{i18n 'admin.badges.expand'}}}</button>
|
||||
{{else}}
|
||||
<button class='btn btn-danger' {{action "revokeBadge" userBadge}}>{{i18n 'admin.badges.revoke'}}</button>
|
||||
{{/if}}
|
||||
</td>
|
||||
</tr>
|
||||
{{else}}
|
||||
|
||||
@ -14,7 +14,7 @@
|
||||
{{#unless loading}}
|
||||
<tbody>
|
||||
<td class="title">{{i18n 'admin.dashboard.version'}}</td>
|
||||
<td class="version-number"><a {{bind-attr href="versionCheck.gitLink"}} target="_blank">{{ versionCheck.installed_version }}</a></td>
|
||||
<td class="version-number"><a {{bind-attr href="versionCheck.gitLink"}} target="_blank">{{ versionCheck.installed_describe }}</a></td>
|
||||
|
||||
{{#if versionCheck.noCheckPerformed}}
|
||||
<td class="version-number"> </td>
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
Discourse.AdminBackupsView = Discourse.View.extend({
|
||||
export default Discourse.View.extend({
|
||||
classNames: ["admin-backups"],
|
||||
|
||||
_hijackDownloads: function() {
|
||||
12
app/assets/javascripts/admin/views/admin.js.es6
Normal file
12
app/assets/javascripts/admin/views/admin.js.es6
Normal file
@ -0,0 +1,12 @@
|
||||
export default Discourse.View.extend({
|
||||
_disableCustomStylesheets: function() {
|
||||
if (this.session.get("disableCustomCSS")) {
|
||||
$("link.custom-css").attr("rel", "");
|
||||
this.session.set("disableCustomCSS", false);
|
||||
}
|
||||
}.on("willInsertElement"),
|
||||
|
||||
_enableCustomStylesheets: function() {
|
||||
$("link.custom-css").attr("rel", "stylesheet");
|
||||
}.on("willDestroyElement")
|
||||
});
|
||||
@ -1,3 +0,0 @@
|
||||
Discourse.AdminApiView = Discourse.View.extend({
|
||||
templateName: 'admin/templates/api'
|
||||
});
|
||||
@ -1,13 +0,0 @@
|
||||
/**
|
||||
The default view in the admin section
|
||||
|
||||
@class AdminDashboardView
|
||||
@extends Discourse.View
|
||||
@namespace Discourse
|
||||
@module Discourse
|
||||
**/
|
||||
Discourse.AdminDashboardView = Discourse.View.extend({
|
||||
templateName: 'admin/templates/dashboard'
|
||||
});
|
||||
|
||||
|
||||
@ -1,4 +0,0 @@
|
||||
Discourse.AdminReportCountsView = Discourse.View.extend({
|
||||
templateName: 'admin/templates/reports/summed_counts_report',
|
||||
tagName: 'tbody'
|
||||
});
|
||||
@ -1,12 +1,4 @@
|
||||
/**
|
||||
A view to display a site setting with edit controls
|
||||
|
||||
@class SiteSettingView
|
||||
@extends Discourse.View
|
||||
@namespace Discourse
|
||||
@module Discourse
|
||||
**/
|
||||
Discourse.SiteSettingView = Discourse.View.extend(Discourse.ScrollTop, {
|
||||
export default Discourse.View.extend(Discourse.ScrollTop, {
|
||||
classNameBindings: [':row', ':setting', 'content.overridden'],
|
||||
|
||||
preview: function() {
|
||||
@ -126,3 +126,6 @@ window.Discourse = Ember.Application.createWithMixins(Discourse.Ajax, {
|
||||
}.property()
|
||||
|
||||
});
|
||||
|
||||
// TODO: Remove this, it is in for backwards compatibiltiy with plugins
|
||||
Discourse.HasCurrentUser = {};
|
||||
|
||||
@ -1,29 +1,52 @@
|
||||
const ADMIN_MODELS = ['plugin'];
|
||||
|
||||
function plural(type) {
|
||||
return type + 's';
|
||||
}
|
||||
|
||||
function pathFor(type) {
|
||||
const path = "/" + plural(type);
|
||||
|
||||
if (ADMIN_MODELS.indexOf(type) !== -1) {
|
||||
return "/admin/" + path;
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
const _identityMap = {};
|
||||
|
||||
const RestModel = Ember.Object.extend({
|
||||
update(attrs) {
|
||||
const self = this;
|
||||
return this.store.update(this.get('__type'), this.get('id'), attrs).then(function(result) {
|
||||
self.setProperties(attrs);
|
||||
return result;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
export default Ember.Object.extend({
|
||||
serverName(type) {
|
||||
return Ember.String.underscore(type + 's');
|
||||
},
|
||||
|
||||
pathFor(type, id) {
|
||||
let path = "/" + this.serverName(type);
|
||||
|
||||
if (ADMIN_MODELS.indexOf(type) !== -1) { path = "/admin/" + path; }
|
||||
if (id) { path += "/" + id; }
|
||||
|
||||
return path;
|
||||
},
|
||||
|
||||
findAll(type) {
|
||||
var self = this;
|
||||
return Discourse.ajax(pathFor(type)).then(function(result) {
|
||||
return result[plural(type)].map(obj => self._hydrate(type, obj));
|
||||
return Discourse.ajax(this.pathFor(type)).then(function(result) {
|
||||
return result[self.serverName(type)].map(obj => self._hydrate(type, obj));
|
||||
});
|
||||
},
|
||||
|
||||
find(type, id) {
|
||||
var self = this;
|
||||
return Discourse.ajax(this.pathFor(type, id)).then(function(result) {
|
||||
return self._hydrate(type, result[self.serverName(type)]);
|
||||
});
|
||||
},
|
||||
|
||||
update(type, id, attrs) {
|
||||
const data = {};
|
||||
data[this.serverName(type)] = attrs;
|
||||
|
||||
return Discourse.ajax(this.pathFor(type, id), { method: 'PUT', data });
|
||||
},
|
||||
|
||||
_hydrate(type, obj) {
|
||||
if (!obj) { throw "Can't hydrate " + type + " of `null`"; }
|
||||
if (!obj.id) { throw "Can't hydrate " + type + " without an `id`"; }
|
||||
@ -37,7 +60,10 @@ export default Ember.Object.extend({
|
||||
return existing;
|
||||
}
|
||||
|
||||
const klass = this.container.lookupFactory('model:' + type) || Ember.Object;
|
||||
obj.store = this;
|
||||
obj.__type = type;
|
||||
|
||||
const klass = this.container.lookupFactory('model:' + type) || RestModel;
|
||||
const model = klass.create(obj);
|
||||
_identityMap[type][obj.id] = model;
|
||||
return model;
|
||||
|
||||
@ -1,18 +1,11 @@
|
||||
/**
|
||||
A breadcrumb including category drop downs
|
||||
|
||||
@class BreadCrumbsComponent
|
||||
@extends Ember.Component
|
||||
@namespace Discourse
|
||||
@module Discourse
|
||||
**/
|
||||
// A breadcrumb including category drop downs
|
||||
export default Ember.Component.extend({
|
||||
classNames: ['category-breadcrumb'],
|
||||
tagName: 'ol',
|
||||
parentCategory: Em.computed.alias('category.parentCategory'),
|
||||
|
||||
parentCategories: Em.computed.filter('categories', function(c) {
|
||||
if (c.id === Discourse.Site.currentProp("uncategorized_category_id") && !Discourse.SiteSettings.allow_uncategorized_topics) {
|
||||
if (c.id === this.site.get("uncategorized_category_id") && !this.siteSettings.allow_uncategorized_topics) {
|
||||
// Don't show "uncategorized" if allow_uncategorized_topics setting is false.
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -1,16 +1,13 @@
|
||||
import NotificationsButton from 'discourse/views/notifications-button';
|
||||
import NotificationsButton from 'discourse/components/notifications-button';
|
||||
|
||||
export default NotificationsButton.extend({
|
||||
classNames: ['notification-options', 'category-notification-menu'],
|
||||
buttonIncludesText: false,
|
||||
longDescriptionBinding: null,
|
||||
hidden: Em.computed.alias('category.deleted'),
|
||||
notificationLevels: Discourse.Category.NotificationLevel,
|
||||
notificationLevel: Em.computed.alias('category.notification_level'),
|
||||
i18nPrefix: 'category.notifications',
|
||||
i18nPostfix: '',
|
||||
|
||||
clicked: function(id) {
|
||||
clicked(id) {
|
||||
this.get('category').setNotification(id);
|
||||
}
|
||||
});
|
||||
@ -1,13 +1,13 @@
|
||||
import StringBuffer from 'discourse/mixins/string-buffer';
|
||||
|
||||
export default Discourse.View.extend(StringBuffer, {
|
||||
export default Ember.Component.extend(StringBuffer, {
|
||||
classNameBindings: [':btn-group', 'hidden'],
|
||||
rerenderTriggers: ['text', 'longDescription'],
|
||||
|
||||
_bindClick: function() {
|
||||
// If there's a click handler, call it
|
||||
if (this.clicked) {
|
||||
var self = this;
|
||||
const self = this;
|
||||
this.$().on('click.dropdown-button', 'ul li', function(e) {
|
||||
e.preventDefault();
|
||||
if ($(e.currentTarget).data('id') !== self.get('activeItem')) {
|
||||
@ -23,7 +23,7 @@ export default Discourse.View.extend(StringBuffer, {
|
||||
this.$().off('click.dropdown-button', 'ul li');
|
||||
}.on('willDestroyElement'),
|
||||
|
||||
renderString: function(buffer) {
|
||||
renderString(buffer) {
|
||||
|
||||
buffer.push("<h4 class='title'>" + this.get('title') + "</h4>");
|
||||
buffer.push("<button class='btn standard dropdown-toggle' data-toggle='dropdown'>");
|
||||
@ -31,27 +31,24 @@ export default Discourse.View.extend(StringBuffer, {
|
||||
buffer.push("</button>");
|
||||
buffer.push("<ul class='dropdown-menu'>");
|
||||
|
||||
var contents = this.get('dropDownContent');
|
||||
const contents = this.get('dropDownContent');
|
||||
if (contents) {
|
||||
var self = this;
|
||||
const self = this;
|
||||
contents.forEach(function(row) {
|
||||
var id = row.id,
|
||||
title = row.title,
|
||||
iconClass = row.styleClasses,
|
||||
description = row.description,
|
||||
className = (self.get('activeItem') === id ? 'disabled': '');
|
||||
const id = row.id,
|
||||
className = (self.get('activeItem') === id ? 'disabled': '');
|
||||
|
||||
buffer.push("<li data-id=\"" + id + "\" class=\"" + className + "\"><a href>");
|
||||
buffer.push("<span class='icon " + iconClass + "'></span>");
|
||||
buffer.push("<div><span class='title'>" + title + "</span>");
|
||||
buffer.push("<span>" + description + "</span></div>");
|
||||
buffer.push("<span class='icon " + row.styleClasses + "'></span>");
|
||||
buffer.push("<div><span class='title'>" + row.title + "</span>");
|
||||
buffer.push("<span>" + row.description + "</span></div>");
|
||||
buffer.push("</a></li>");
|
||||
});
|
||||
}
|
||||
|
||||
buffer.push("</ul>");
|
||||
|
||||
var desc = this.get('longDescription');
|
||||
const desc = this.get('longDescription');
|
||||
if (desc) {
|
||||
buffer.push("<p>");
|
||||
buffer.push(desc);
|
||||
@ -0,0 +1,11 @@
|
||||
export default Ember.Component.extend({
|
||||
classNameBindings: [':featured-topic'],
|
||||
|
||||
click(e) {
|
||||
const $target = $(e.target);
|
||||
if ($target.closest('.last-posted-at').length) {
|
||||
this.sendAction('action', {topic: this.get('topic'), position: $target.offset()});
|
||||
return false;
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -1,11 +1,11 @@
|
||||
import DropdownButtonView from 'discourse/views/dropdown-button';
|
||||
import DropdownButton from 'discourse/components/dropdown-button';
|
||||
import NotificationLevels from 'discourse/lib/notification-levels';
|
||||
|
||||
export default DropdownButtonView.extend({
|
||||
const NotificationsButton = DropdownButton.extend({
|
||||
classNames: ['notification-options'],
|
||||
title: '',
|
||||
buttonIncludesText: true,
|
||||
activeItem: Em.computed.alias('notificationLevel'),
|
||||
notificationLevels: [],
|
||||
i18nPrefix: '',
|
||||
i18nPostfix: '',
|
||||
watchingClasses: 'fa fa-exclamation-circle watching',
|
||||
@ -21,15 +21,14 @@ export default DropdownButtonView.extend({
|
||||
}.property(),
|
||||
|
||||
dropDownContent: function() {
|
||||
var contents = [],
|
||||
prefix = this.get('i18nPrefix'),
|
||||
postfix = this.get('i18nPostfix'),
|
||||
levels = this.get('notificationLevels');
|
||||
const contents = [],
|
||||
prefix = this.get('i18nPrefix'),
|
||||
postfix = this.get('i18nPostfix');
|
||||
|
||||
_.each(this.get('options'), function(pair) {
|
||||
if (postfix === '_pm' && pair[1] === 'regular') { return; }
|
||||
contents.push({
|
||||
id: levels[pair[0]],
|
||||
id: NotificationLevels[pair[0]],
|
||||
title: I18n.t(prefix + '.' + pair[1] + postfix + '.title'),
|
||||
description: I18n.t(prefix + '.' + pair[1] + postfix + '.description'),
|
||||
styleClasses: pair[2]
|
||||
@ -40,21 +39,20 @@ export default DropdownButtonView.extend({
|
||||
}.property(),
|
||||
|
||||
text: function() {
|
||||
var self = this,
|
||||
prefix = this.get('i18nPrefix'),
|
||||
postfix = this.get('i18nPostfix'),
|
||||
levels = this.get('notificationLevels');
|
||||
const self = this,
|
||||
prefix = this.get('i18nPrefix'),
|
||||
postfix = this.get('i18nPostfix');
|
||||
|
||||
var key = (function() {
|
||||
const key = (function() {
|
||||
switch (this.get('notificationLevel')) {
|
||||
case levels.WATCHING: return 'watching';
|
||||
case levels.TRACKING: return 'tracking';
|
||||
case levels.MUTED: return 'muted';
|
||||
case NotificationLevels.WATCHING: return 'watching';
|
||||
case NotificationLevels.TRACKING: return 'tracking';
|
||||
case NotificationLevels.MUTED: return 'muted';
|
||||
default: return 'regular';
|
||||
}
|
||||
}).call(this);
|
||||
|
||||
var icon = (function() {
|
||||
const icon = (function() {
|
||||
switch (key) {
|
||||
case 'watching': return '<i class="' + self.watchingClasses + '"></i> ';
|
||||
case 'tracking': return '<i class="' + self.trackingClasses + '"></i> ';
|
||||
@ -65,8 +63,11 @@ export default DropdownButtonView.extend({
|
||||
return icon + ( this.get('buttonIncludesText') ? I18n.t(prefix + '.' + key + postfix + ".title") : '') + "<span class='caret'></span>";
|
||||
}.property('notificationLevel'),
|
||||
|
||||
clicked: function(/* id */) {
|
||||
clicked(/* id */) {
|
||||
// sub-class needs to implement this
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
export default NotificationsButton;
|
||||
export { NotificationLevels };
|
||||
@ -1,22 +1,20 @@
|
||||
import DropdownButtonView from 'discourse/views/dropdown-button';
|
||||
import DropdownButton from 'discourse/components/dropdown-button';
|
||||
|
||||
export default DropdownButtonView.extend({
|
||||
export default DropdownButton.extend({
|
||||
descriptionKey: 'help',
|
||||
classNames: ['pinned-options'],
|
||||
title: '',
|
||||
longDescription: function(){
|
||||
var topic = this.get('topic');
|
||||
var globally = topic.get('pinned_globally') ? '_globally' : '';
|
||||
|
||||
var key = 'topic_statuses.' + (topic.get('pinned') ? 'pinned' + globally : 'unpinned') + '.help';
|
||||
const topic = this.get('topic');
|
||||
const globally = topic.get('pinned_globally') ? '_globally' : '';
|
||||
const key = 'topic_statuses.' + (topic.get('pinned') ? 'pinned' + globally : 'unpinned') + '.help';
|
||||
return I18n.t(key);
|
||||
}.property('topic.pinned'),
|
||||
|
||||
topic: Em.computed.alias('controller.model'),
|
||||
target: Em.computed.alias('topic'),
|
||||
|
||||
hidden: function(){
|
||||
var topic = this.get('topic');
|
||||
const topic = this.get('topic');
|
||||
return topic.get('deleted') || (!topic.get('pinned') && !topic.get('unpinned'));
|
||||
}.property('topic.pinned', 'topic.deleted', 'topic.unpinned'),
|
||||
|
||||
@ -25,7 +23,7 @@ export default DropdownButtonView.extend({
|
||||
}.property('topic.pinned'),
|
||||
|
||||
dropDownContent: function() {
|
||||
var globally = this.get('topic.pinned_globally') ? '_globally' : '';
|
||||
const globally = this.get('topic.pinned_globally') ? '_globally' : '';
|
||||
return [
|
||||
{id: 'pinned',
|
||||
title: I18n.t('topic_statuses.pinned' + globally + '.title'),
|
||||
@ -39,15 +37,15 @@ export default DropdownButtonView.extend({
|
||||
}.property(),
|
||||
|
||||
text: function() {
|
||||
var globally = this.get('topic.pinned_globally') ? '_globally' : '';
|
||||
var state = this.get('topic.pinned') ? 'pinned' + globally : 'unpinned';
|
||||
const globally = this.get('topic.pinned_globally') ? '_globally' : '';
|
||||
const state = this.get('topic.pinned') ? 'pinned' + globally : 'unpinned';
|
||||
|
||||
return '<span class="fa fa-thumb-tack' + (state === 'unpinned' ? ' unpinned' : "") + '"></span> ' +
|
||||
I18n.t('topic_statuses.' + state + '.title') + "<span class='caret'></span>";
|
||||
}.property('topic.pinned', 'topic.unpinned'),
|
||||
|
||||
clicked: function(id) {
|
||||
var topic = this.get('topic');
|
||||
clicked(id) {
|
||||
const topic = this.get('topic');
|
||||
if(id==='unpinned'){
|
||||
topic.clearPin();
|
||||
} else {
|
||||
@ -0,0 +1,16 @@
|
||||
import NotificationsButton from 'discourse/components/notifications-button';
|
||||
|
||||
export default NotificationsButton.extend({
|
||||
longDescription: Em.computed.alias('topic.details.notificationReasonText'),
|
||||
hidden: Em.computed.alias('topic.deleted'),
|
||||
notificationLevel: Em.computed.alias('topic.details.notification_level'),
|
||||
i18nPrefix: 'topic.notifications',
|
||||
|
||||
i18nPostfix: function() {
|
||||
return this.get('topic.isPrivateMessage') ? '_pm' : '';
|
||||
}.property('topic.isPrivateMessage'),
|
||||
|
||||
clicked(id) {
|
||||
this.get('topic.details').updateNotifications(id);
|
||||
}
|
||||
});
|
||||
@ -1,9 +1,13 @@
|
||||
import ObjectController from 'discourse/controllers/object';
|
||||
|
||||
export default ObjectController.extend({
|
||||
faqOverriden: Ember.computed.gt('siteSettings.faq_url.length', 0),
|
||||
|
||||
contactInfo: function() {
|
||||
if (Discourse.SiteSettings.contact_email) {
|
||||
return I18n.t('about.contact_info', {contact_email: Discourse.SiteSettings.contact_email});
|
||||
if (this.siteSettings.contact_url) {
|
||||
return I18n.t('about.contact_info', {contact_info: "<a href='"+ this.siteSettings.contact_url +"' target='_blank'>"+ this.siteSettings.contact_url +"</a>"});
|
||||
} else if (this.siteSettings.contact_email) {
|
||||
return I18n.t('about.contact_info', {contact_info: this.siteSettings.contact_email});
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -48,6 +48,10 @@ export default ObjectController.extend({
|
||||
|
||||
_showFooter: function() {
|
||||
this.set("controllers.application.showFooter", !this.get("canLoadMore"));
|
||||
}.observes("canLoadMore")
|
||||
}.observes("canLoadMore"),
|
||||
|
||||
showLongDescription: function(){
|
||||
return window.location.search.match("long-description");
|
||||
}.property('userBadges')
|
||||
|
||||
});
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
import NotificationLevels from 'discourse/lib/notification-levels';
|
||||
|
||||
// Support for changing the notification level of various topics
|
||||
export default Em.Controller.extend({
|
||||
needs: ['topic-bulk-actions'],
|
||||
@ -5,9 +7,9 @@ export default Em.Controller.extend({
|
||||
|
||||
notificationLevels: function() {
|
||||
var result = [];
|
||||
Object.keys(Discourse.Topic.NotificationLevel).forEach(function(k) {
|
||||
Object.keys(NotificationLevels).forEach(function(k) {
|
||||
result.push({
|
||||
id: Discourse.Topic.NotificationLevel[k].toString(),
|
||||
id: NotificationLevels[k].toString(),
|
||||
name: I18n.t('topic.notifications.' + k.toLowerCase() + ".title"),
|
||||
description: I18n.t('topic.notifications.' + k.toLowerCase() + ".description")
|
||||
});
|
||||
|
||||
@ -5,50 +5,31 @@ export default Ember.ArrayController.extend({
|
||||
// Whether we've checked our messages
|
||||
checkedMessages: false,
|
||||
|
||||
/**
|
||||
Initialize the controller
|
||||
**/
|
||||
init: function() {
|
||||
init() {
|
||||
this._super();
|
||||
this.reset();
|
||||
},
|
||||
|
||||
actions: {
|
||||
/**
|
||||
Closes and hides a message.
|
||||
|
||||
@method closeMessage
|
||||
@params {Object} message The message to dismiss
|
||||
**/
|
||||
closeMessage: function(message) {
|
||||
closeMessage(message) {
|
||||
this.removeObject(message);
|
||||
},
|
||||
|
||||
hideMessage: function(message) {
|
||||
var messagesByTemplate = this.get('messagesByTemplate'),
|
||||
templateName = message.get('templateName');
|
||||
|
||||
// kind of hacky but the visibility depends on this
|
||||
messagesByTemplate[templateName] = undefined;
|
||||
hideMessage(message) {
|
||||
this.removeObject(message);
|
||||
}
|
||||
},
|
||||
// kind of hacky but the visibility depends on this
|
||||
this.get('messagesByTemplate')[message.get('templateName')] = undefined;
|
||||
},
|
||||
|
||||
/**
|
||||
Displays a new message
|
||||
popup(message) {
|
||||
let messagesByTemplate = this.get('messagesByTemplate');
|
||||
const templateName = message.get('templateName');
|
||||
|
||||
@method popup
|
||||
@params {Object} msg The message to display
|
||||
**/
|
||||
popup: function(msg) {
|
||||
var messagesByTemplate = this.get('messagesByTemplate'),
|
||||
templateName = msg.get('templateName'),
|
||||
existing = messagesByTemplate[templateName];
|
||||
|
||||
if (!existing) {
|
||||
this.pushObject(msg);
|
||||
messagesByTemplate[templateName] = msg;
|
||||
}
|
||||
if (!messagesByTemplate[templateName]) {
|
||||
this.pushObject(message);
|
||||
messagesByTemplate[templateName] = message;
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
@ -56,11 +37,13 @@ export default Ember.ArrayController.extend({
|
||||
|
||||
@method reset
|
||||
**/
|
||||
reset: function() {
|
||||
reset() {
|
||||
this.clear();
|
||||
this.set('messagesByTemplate', {});
|
||||
this.set('queuedForTyping', []);
|
||||
this.set('checkedMessages', false);
|
||||
this.setProperties({
|
||||
messagesByTemplate: {},
|
||||
queuedForTyping: [],
|
||||
checkedMessages: false
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
@ -69,11 +52,8 @@ export default Ember.ArrayController.extend({
|
||||
|
||||
@method typedReply
|
||||
**/
|
||||
typedReply: function() {
|
||||
var self = this;
|
||||
this.get('queuedForTyping').forEach(function (msg) {
|
||||
self.popup(msg);
|
||||
});
|
||||
typedReply() {
|
||||
this.get('queuedForTyping').forEach(msg => this.popup(msg));
|
||||
},
|
||||
|
||||
/**
|
||||
@ -82,11 +62,11 @@ export default Ember.ArrayController.extend({
|
||||
@method queryFor
|
||||
@params {Discourse.Composer} composer The composer model
|
||||
**/
|
||||
queryFor: function(composer) {
|
||||
queryFor(composer) {
|
||||
if (this.get('checkedMessages')) { return; }
|
||||
|
||||
var self = this,
|
||||
queuedForTyping = self.get('queuedForTyping');
|
||||
const self = this;
|
||||
let queuedForTyping = self.get('queuedForTyping');
|
||||
|
||||
Discourse.ComposerMessage.find(composer).then(function (messages) {
|
||||
self.set('checkedMessages', true);
|
||||
|
||||
@ -29,18 +29,18 @@ export default DiscourseController.extend({
|
||||
|
||||
actions: {
|
||||
// Toggle the reply view
|
||||
toggle: function() {
|
||||
toggle() {
|
||||
this.toggle();
|
||||
},
|
||||
|
||||
togglePreview: function() {
|
||||
togglePreview() {
|
||||
this.get('model').togglePreview();
|
||||
},
|
||||
|
||||
// Import a quote from the post
|
||||
importQuote: function() {
|
||||
var postStream = this.get('topic.postStream'),
|
||||
postId = this.get('model.post.id');
|
||||
importQuote() {
|
||||
const postStream = this.get('topic.postStream');
|
||||
let postId = this.get('model.post.id');
|
||||
|
||||
// If there is no current post, use the first post id from the stream
|
||||
if (!postId && postStream) {
|
||||
@ -49,9 +49,9 @@ export default DiscourseController.extend({
|
||||
|
||||
// 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');
|
||||
const replyToPostNumber = this.get('model.post.reply_to_post_number');
|
||||
if (replyToPostNumber) {
|
||||
var replyPost = postStream.get('posts').findBy('post_number', replyToPostNumber);
|
||||
const replyPost = postStream.get('posts').findBy('post_number', replyToPostNumber);
|
||||
if (replyPost) {
|
||||
postId = replyPost.get('id');
|
||||
}
|
||||
@ -60,34 +60,34 @@ export default DiscourseController.extend({
|
||||
|
||||
if (postId) {
|
||||
this.set('model.loading', true);
|
||||
var composer = this;
|
||||
const composer = this;
|
||||
return Discourse.Post.load(postId).then(function(post) {
|
||||
var quote = Discourse.Quote.build(post, post.get("raw"));
|
||||
const quote = Discourse.Quote.build(post, post.get("raw"));
|
||||
composer.appendBlockAtCursor(quote);
|
||||
composer.set('model.loading', false);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
cancel: function() {
|
||||
cancel() {
|
||||
this.cancelComposer();
|
||||
},
|
||||
|
||||
save: function() {
|
||||
save() {
|
||||
this.save();
|
||||
},
|
||||
|
||||
displayEditReason: function() {
|
||||
displayEditReason() {
|
||||
this.set("showEditReason", true);
|
||||
},
|
||||
|
||||
hitEsc: function() {
|
||||
hitEsc() {
|
||||
if (this.get('model.viewOpen')) {
|
||||
this.shrink();
|
||||
}
|
||||
},
|
||||
|
||||
openIfDraft: function() {
|
||||
openIfDraft() {
|
||||
if (this.get('model.viewDraft')) {
|
||||
this.set('model.composeState', Discourse.Composer.OPEN);
|
||||
}
|
||||
@ -95,35 +95,33 @@ export default DiscourseController.extend({
|
||||
|
||||
},
|
||||
|
||||
updateDraftStatus: function() {
|
||||
var c = this.get('model');
|
||||
updateDraftStatus() {
|
||||
const c = this.get('model');
|
||||
if (c) { c.updateDraftStatus(); }
|
||||
},
|
||||
|
||||
appendText: function(text, opts) {
|
||||
var c = this.get('model');
|
||||
appendText(text, opts) {
|
||||
const c = this.get('model');
|
||||
if (c) {
|
||||
opts = opts || {};
|
||||
var wmd = $('#wmd-input');
|
||||
var val = wmd.val() || '';
|
||||
var position = opts.position === "cursor" ? wmd.caret() : val.length;
|
||||
const wmd = $('#wmd-input'),
|
||||
val = wmd.val() || '',
|
||||
position = opts.position === "cursor" ? wmd.caret() : val.length,
|
||||
caret = c.appendText(text, position, opts);
|
||||
|
||||
var caret = c.appendText(text, position, opts);
|
||||
if(wmd[0]){
|
||||
Em.run.next(function(){
|
||||
Discourse.Utilities.setCaretPosition(wmd[0], caret);
|
||||
});
|
||||
if (wmd[0]) {
|
||||
Em.run.next(() => Discourse.Utilities.setCaretPosition(wmd[0], caret));
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
appendTextAtCursor: function(text, opts) {
|
||||
appendTextAtCursor(text, opts) {
|
||||
opts = opts || {};
|
||||
opts.position = "cursor";
|
||||
this.appendText(text, opts);
|
||||
},
|
||||
|
||||
appendBlockAtCursor: function(text, opts) {
|
||||
appendBlockAtCursor(text, opts) {
|
||||
opts = opts || {};
|
||||
opts.position = "cursor";
|
||||
opts.block = true;
|
||||
@ -135,7 +133,7 @@ export default DiscourseController.extend({
|
||||
}.property(),
|
||||
|
||||
|
||||
toggle: function() {
|
||||
toggle() {
|
||||
this.closeAutocomplete();
|
||||
switch (this.get('model.composeState')) {
|
||||
case Discourse.Composer.OPEN:
|
||||
@ -158,9 +156,9 @@ export default DiscourseController.extend({
|
||||
return this.get('model.loading');
|
||||
}.property('model.loading'),
|
||||
|
||||
save: function(force) {
|
||||
var composer = this.get('model'),
|
||||
self = this;
|
||||
save(force) {
|
||||
const composer = this.get('model'),
|
||||
self = this;
|
||||
|
||||
// Clear the warning state if we're not showing the checkbox anymore
|
||||
if (!this.get('showWarning')) {
|
||||
@ -168,7 +166,7 @@ export default DiscourseController.extend({
|
||||
}
|
||||
|
||||
if(composer.get('cantSubmitPost')) {
|
||||
var now = Date.now();
|
||||
const now = Date.now();
|
||||
this.setProperties({
|
||||
'view.showTitleTip': now,
|
||||
'view.showCategoryTip': now,
|
||||
@ -182,12 +180,12 @@ export default DiscourseController.extend({
|
||||
// for now handle a very narrow use case
|
||||
// if we are replying to a topic AND not on the topic pop the window up
|
||||
if (!force && composer.get('replyingToTopic')) {
|
||||
var topic = this.get('topic');
|
||||
const topic = this.get('topic');
|
||||
if (!topic || topic.get('id') !== composer.get('topic.id'))
|
||||
{
|
||||
var message = I18n.t("composer.posting_not_on_topic");
|
||||
const message = I18n.t("composer.posting_not_on_topic");
|
||||
|
||||
var buttons = [{
|
||||
let buttons = [{
|
||||
"label": I18n.t("composer.cancel"),
|
||||
"class": "cancel",
|
||||
"link": true
|
||||
@ -231,7 +229,7 @@ export default DiscourseController.extend({
|
||||
opts = opts || {};
|
||||
self.close();
|
||||
|
||||
var currentUser = Discourse.User.current();
|
||||
const currentUser = Discourse.User.current();
|
||||
if (composer.get('creatingTopic')) {
|
||||
currentUser.set('topic_count', currentUser.get('topic_count') + 1);
|
||||
} else {
|
||||
@ -255,7 +253,7 @@ export default DiscourseController.extend({
|
||||
|
||||
@method checkReplyLength
|
||||
**/
|
||||
checkReplyLength: function() {
|
||||
checkReplyLength() {
|
||||
if (this.present('model.reply')) {
|
||||
// Notify the composer messages controller that a reply has been typed. Some
|
||||
// messages only appear after typing.
|
||||
@ -269,55 +267,52 @@ export default DiscourseController.extend({
|
||||
|
||||
@method findSimilarTopics
|
||||
**/
|
||||
findSimilarTopics: function() {
|
||||
|
||||
findSimilarTopics() {
|
||||
// We don't care about similar topics unless creating a topic
|
||||
if (!this.get('model.creatingTopic')) return;
|
||||
if (!this.get('model.creatingTopic')) { return; }
|
||||
|
||||
var body = this.get('model.reply'),
|
||||
title = this.get('model.title'),
|
||||
self = this,
|
||||
message;
|
||||
let body = this.get('model.reply');
|
||||
const title = this.get('model.title');
|
||||
|
||||
// Ensure the fields are of the minimum length
|
||||
if (body.length < Discourse.SiteSettings.min_body_similar_length ||
|
||||
title.length < Discourse.SiteSettings.min_title_similar_length) { return; }
|
||||
if (body.length < Discourse.SiteSettings.min_body_similar_length) { return; }
|
||||
if (title.length < Discourse.SiteSettings.min_title_similar_length) { return; }
|
||||
|
||||
// TODO pass the 200 in from somewhere
|
||||
body = body.substr(0, 200);
|
||||
|
||||
// Done search over and over
|
||||
if((title + body) === this.get('lastSimilaritySearch')) { return; }
|
||||
if ((title + body) === this.get('lastSimilaritySearch')) { return; }
|
||||
this.set('lastSimilaritySearch', title + body);
|
||||
|
||||
var messageController = this.get('controllers.composer-messages'),
|
||||
similarTopics = this.get('similarTopics');
|
||||
const messageController = this.get('controllers.composer-messages'),
|
||||
similarTopics = this.get('similarTopics');
|
||||
|
||||
let message = this.get('similarTopicsMessage');
|
||||
if (!message) {
|
||||
message = Discourse.ComposerMessage.create({
|
||||
templateName: 'composer/similar_topics',
|
||||
extraClass: 'similar-topics'
|
||||
});
|
||||
this.set('similarTopicsMessage', message);
|
||||
}
|
||||
|
||||
Discourse.Topic.findSimilarTo(title, body).then(function (newTopics) {
|
||||
similarTopics.clear();
|
||||
similarTopics.pushObjects(newTopics);
|
||||
|
||||
if (similarTopics.get('length') > 0) {
|
||||
message = Discourse.ComposerMessage.create({
|
||||
templateName: 'composer/similar_topics',
|
||||
similarTopics: similarTopics,
|
||||
extraClass: 'similar-topics'
|
||||
});
|
||||
|
||||
self.set('similarTopicsMessage', message);
|
||||
messageController.popup(message);
|
||||
} else {
|
||||
message = self.get('similarTopicsMessage');
|
||||
if (message) {
|
||||
messageController.send('hideMessage', message);
|
||||
}
|
||||
message.set('similarTopics', similarTopics);
|
||||
messageController.send("popup", message);
|
||||
} else if (message) {
|
||||
messageController.send("hideMessage", message);
|
||||
}
|
||||
});
|
||||
|
||||
},
|
||||
|
||||
saveDraft: function() {
|
||||
var model = this.get('model');
|
||||
saveDraft() {
|
||||
const model = this.get('model');
|
||||
if (model) { model.saveDraft(); }
|
||||
},
|
||||
|
||||
@ -331,7 +326,7 @@ export default DiscourseController.extend({
|
||||
@param {Discourse.Topic} [opts.topic] The topic we're replying to
|
||||
@param {String} [opts.quote] If we're opening a reply from a quote, the quote we're making
|
||||
**/
|
||||
open: function(opts) {
|
||||
open(opts) {
|
||||
opts = opts || {};
|
||||
|
||||
if (!opts.draftKey) {
|
||||
@ -345,15 +340,17 @@ export default DiscourseController.extend({
|
||||
this.set('scopedCategoryId', opts.categoryId);
|
||||
}
|
||||
|
||||
var composerMessages = this.get('controllers.composer-messages'),
|
||||
self = this,
|
||||
composerModel = this.get('model');
|
||||
const composerMessages = this.get('controllers.composer-messages'),
|
||||
self = this;
|
||||
|
||||
let composerModel = this.get('model');
|
||||
|
||||
this.setProperties({ showEditReason: false, editReason: null });
|
||||
composerMessages.reset();
|
||||
|
||||
// If we want a different draft than the current composer, close it and clear our model.
|
||||
if (composerModel && opts.draftKey !== composerModel.draftKey &&
|
||||
if (composerModel &&
|
||||
opts.draftKey !== composerModel.draftKey &&
|
||||
composerModel.composeState === Discourse.Composer.DRAFT) {
|
||||
this.close();
|
||||
composerModel = null;
|
||||
@ -396,7 +393,7 @@ export default DiscourseController.extend({
|
||||
},
|
||||
|
||||
// Given a potential instance and options, set the model for this composer.
|
||||
_setModel: function(composerModel, opts) {
|
||||
_setModel(composerModel, opts) {
|
||||
if (opts.draft) {
|
||||
composerModel = Discourse.Composer.loadDraft(opts.draftKey, opts.draftSequence, opts.draft);
|
||||
if (composerModel) {
|
||||
@ -411,26 +408,25 @@ export default DiscourseController.extend({
|
||||
composerModel.set('composeState', Discourse.Composer.OPEN);
|
||||
composerModel.set('isWarning', false);
|
||||
|
||||
var composerMessages = this.get('controllers.composer-messages');
|
||||
composerMessages.queryFor(composerModel);
|
||||
this.get('controllers.composer-messages').queryFor(composerModel);
|
||||
},
|
||||
|
||||
// View a new reply we've made
|
||||
viewNewReply: function() {
|
||||
viewNewReply() {
|
||||
Discourse.URL.routeTo(this.get('createdPost.url'));
|
||||
this.close();
|
||||
return false;
|
||||
},
|
||||
|
||||
destroyDraft: function() {
|
||||
var key = this.get('model.draftKey');
|
||||
destroyDraft() {
|
||||
const key = this.get('model.draftKey');
|
||||
if (key) {
|
||||
Discourse.Draft.clear(key, this.get('model.draftSequence'));
|
||||
}
|
||||
},
|
||||
|
||||
cancelComposer: function() {
|
||||
var self = this;
|
||||
cancelComposer() {
|
||||
const self = this;
|
||||
|
||||
return new Ember.RSVP.Promise(function (resolve) {
|
||||
if (self.get('model.hasMetaData') || self.get('model.replyDirty')) {
|
||||
@ -454,7 +450,7 @@ export default DiscourseController.extend({
|
||||
},
|
||||
|
||||
|
||||
shrink: function() {
|
||||
shrink() {
|
||||
if (this.get('model.replyDirty')) {
|
||||
this.collapse();
|
||||
} else {
|
||||
@ -462,12 +458,12 @@ export default DiscourseController.extend({
|
||||
}
|
||||
},
|
||||
|
||||
collapse: function() {
|
||||
collapse() {
|
||||
this.saveDraft();
|
||||
this.set('model.composeState', Discourse.Composer.DRAFT);
|
||||
},
|
||||
|
||||
close: function() {
|
||||
close() {
|
||||
this.setProperties({
|
||||
model: null,
|
||||
'view.showTitleTip': false,
|
||||
@ -476,11 +472,11 @@ export default DiscourseController.extend({
|
||||
});
|
||||
},
|
||||
|
||||
closeAutocomplete: function() {
|
||||
closeAutocomplete() {
|
||||
$('#wmd-input').autocomplete({ cancel: true });
|
||||
},
|
||||
|
||||
showOptions: function() {
|
||||
showOptions() {
|
||||
var _ref;
|
||||
return (_ref = this.get('controllers.modal')) ? _ref.show(Discourse.ArchetypeOptionsModalView.create({
|
||||
archetype: this.get('model.archetype'),
|
||||
|
||||
@ -1 +1 @@
|
||||
export default Ember.Controller.extend(Discourse.Presence, Discourse.HasCurrentUser);
|
||||
export default Ember.Controller.extend(Discourse.Presence);
|
||||
|
||||
@ -310,12 +310,26 @@ export default DiscourseController.extend(ModalFunctionality, {
|
||||
});
|
||||
}
|
||||
|
||||
if (!this.blank('accountUsername') && this.get('accountPassword') === this.get('accountUsername')) {
|
||||
return Discourse.InputValidation.create({
|
||||
failed: true,
|
||||
reason: I18n.t('user.password.same_as_username')
|
||||
});
|
||||
}
|
||||
|
||||
if (!this.blank('accountEmail') && this.get('accountPassword') === this.get('accountEmail')) {
|
||||
return Discourse.InputValidation.create({
|
||||
failed: true,
|
||||
reason: I18n.t('user.password.same_as_email')
|
||||
});
|
||||
}
|
||||
|
||||
// Looks good!
|
||||
return Discourse.InputValidation.create({
|
||||
ok: true,
|
||||
reason: I18n.t('user.password.ok')
|
||||
});
|
||||
}.property('accountPassword', 'rejectedPasswords.@each'),
|
||||
}.property('accountPassword', 'rejectedPasswords.@each', 'accountUsername', 'accountEmail'),
|
||||
|
||||
fetchConfirmationValue: function() {
|
||||
var createAccountController = this;
|
||||
|
||||
@ -14,7 +14,7 @@ export default ObjectController.extend({
|
||||
this.set("controllers.application.showFooter", this.get("loadedAllItems"));
|
||||
}.observes("loadedAllItems"),
|
||||
|
||||
showMoreUrl: function(period) {
|
||||
showMoreUrl(period) {
|
||||
var url = '', category = this.get('category');
|
||||
if (category) {
|
||||
url = '/c/' + Discourse.Category.slugFor(category) + (this.get('noSubcategories') ? '/none' : '') + '/l';
|
||||
@ -24,12 +24,12 @@ export default ObjectController.extend({
|
||||
},
|
||||
|
||||
periods: function() {
|
||||
var self = this,
|
||||
periods = [];
|
||||
Discourse.Site.currentProp('periods').forEach(function(p) {
|
||||
const self = this,
|
||||
periods = [];
|
||||
this.site.get('periods').forEach(function(p) {
|
||||
periods.pushObject(TopPeriod.create({ id: p,
|
||||
showMoreUrl: self.showMoreUrl(p),
|
||||
periods: periods }));
|
||||
periods }));
|
||||
});
|
||||
return periods;
|
||||
}.property('category', 'noSubcategories'),
|
||||
|
||||
@ -7,7 +7,8 @@ export default DiscoveryController.extend({
|
||||
showPostsColumn: Em.computed.empty('withLogo'),
|
||||
|
||||
actions: {
|
||||
refresh: function() {
|
||||
|
||||
refresh() {
|
||||
|
||||
// Don't refresh if we're still loading
|
||||
if (this.get('controllers.discovery.loading')) { return; }
|
||||
@ -17,15 +18,11 @@ export default DiscoveryController.extend({
|
||||
// Lesson learned: Don't call `loading` yourself.
|
||||
this.set('controllers.discovery.loading', true);
|
||||
|
||||
var parentCategory = this.get('model.parentCategory');
|
||||
var promise;
|
||||
if (parentCategory) {
|
||||
promise = Discourse.CategoryList.listForParent(parentCategory);
|
||||
} else {
|
||||
promise = Discourse.CategoryList.list();
|
||||
}
|
||||
const parentCategory = this.get('model.parentCategory');
|
||||
const promise = parentCategory ? Discourse.CategoryList.listForParent(parentCategory) :
|
||||
Discourse.CategoryList.list();
|
||||
|
||||
var self = this;
|
||||
const self = this;
|
||||
promise.then(function(list) {
|
||||
self.set('model', list);
|
||||
self.send('loadingComplete');
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import DiscoveryController from 'discourse/controllers/discovery';
|
||||
import { queryParams } from 'discourse/controllers/discovery-sortable';
|
||||
import NotificationLevels from 'discourse/lib/notification-levels';
|
||||
|
||||
var controllerOpts = {
|
||||
needs: ['discovery'],
|
||||
@ -86,7 +87,7 @@ var controllerOpts = {
|
||||
operation = { type: 'dismiss_posts' };
|
||||
} else {
|
||||
operation = { type: 'change_notification_level',
|
||||
notification_level_id: Discourse.Topic.NotificationLevel.REGULAR };
|
||||
notification_level_id: NotificationLevels.REGULAR };
|
||||
}
|
||||
|
||||
var promise;
|
||||
|
||||
@ -17,13 +17,13 @@ export default ObjectController.extend(ModalFunctionality, {
|
||||
_.each(this.get("actions_summary"),function(a) {
|
||||
var actionSummary;
|
||||
a.flagTopic = self.get('model');
|
||||
a.actionType = Discourse.Site.current().topicFlagTypeById(a.id);
|
||||
a.actionType = self.site.topicFlagTypeById(a.id);
|
||||
actionSummary = Discourse.ActionSummary.create(a);
|
||||
lookup.set(a.actionType.get('name_key'), actionSummary);
|
||||
});
|
||||
this.set('topicActionByName', lookup);
|
||||
|
||||
return Discourse.Site.currentProp('topic_flag_types').filter(function(item) {
|
||||
return this.site.get('topic_flag_types').filter(function(item) {
|
||||
return _.any(self.get("actions_summary"), function(a) {
|
||||
return (a.id === item.get('id') && a.can_act);
|
||||
});
|
||||
|
||||
@ -16,10 +16,6 @@ export default DiscourseController.extend(ModalFunctionality, {
|
||||
this.set('loggedIn', false);
|
||||
},
|
||||
|
||||
site: function() {
|
||||
return Discourse.Site.current();
|
||||
}.property(),
|
||||
|
||||
/**
|
||||
Determines whether at least one login button is enabled
|
||||
**/
|
||||
@ -168,6 +164,12 @@ export default DiscourseController.extend(ModalFunctionality, {
|
||||
this.set('authenticate', null);
|
||||
return;
|
||||
}
|
||||
if (options.admin_not_allowed_from_ip_address) {
|
||||
this.send('showLogin');
|
||||
this.flash(I18n.t('login.admin_not_allowed_from_ip_address'), 'success');
|
||||
this.set('authenticate', null);
|
||||
return;
|
||||
}
|
||||
if (options.not_allowed_from_ip_address) {
|
||||
this.send('showLogin');
|
||||
this.flash(I18n.t('login.not_allowed_from_ip_address'), 'success');
|
||||
|
||||
@ -5,7 +5,7 @@ var INVITED_TYPE= 8;
|
||||
export default ObjectController.extend({
|
||||
|
||||
scope: function () {
|
||||
return "notifications." + Discourse.Site.currentProp("notificationLookup")[this.get("notification_type")];
|
||||
return "notifications." + this.site.get("notificationLookup")[this.get("notification_type")];
|
||||
}.property("notification_type"),
|
||||
|
||||
username: Em.computed.alias("data.display_username"),
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
export default Ember.ArrayController.extend(Discourse.HasCurrentUser, {
|
||||
export default Ember.ArrayController.extend({
|
||||
needs: ['header'],
|
||||
loadingNotifications: Em.computed.alias('controllers.header.loadingNotifications')
|
||||
});
|
||||
|
||||
@ -1 +1 @@
|
||||
export default Ember.ObjectController.extend(Discourse.Presence, Discourse.HasCurrentUser);
|
||||
export default Ember.ObjectController.extend(Discourse.Presence);
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
export default Ember.ObjectController.extend(Discourse.HasCurrentUser, {
|
||||
export default Ember.ObjectController.extend({
|
||||
needs: ['site-map'],
|
||||
|
||||
unreadTotal: function() {
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
export default Ember.ArrayController.extend(Discourse.HasCurrentUser, {
|
||||
export default Ember.ArrayController.extend({
|
||||
needs: ['application'],
|
||||
|
||||
showBadgesLink: function(){return Discourse.SiteSettings.enable_badges;}.property(),
|
||||
|
||||
@ -435,10 +435,10 @@ export default ObjectController.extend(Discourse.SelectedPostsCount, BufferedCon
|
||||
post.toggleProperty('wiki');
|
||||
},
|
||||
|
||||
togglePostType: function (post) {
|
||||
togglePostType(post) {
|
||||
// the request to the server is made in an observer in the post class
|
||||
var regular = Discourse.Site.currentProp('post_types.regular'),
|
||||
moderator = Discourse.Site.currentProp('post_types.moderator_action');
|
||||
const regular = this.site.get('post_types.regular'),
|
||||
moderator = this.site.get('post_types.moderator_action');
|
||||
|
||||
if (post.get("post_type") === moderator) {
|
||||
post.set("post_type", regular);
|
||||
|
||||
@ -71,7 +71,7 @@ export default ObjectController.extend({
|
||||
this.setProperties({ user: null, userLoading: username, cardTarget: target });
|
||||
|
||||
var self = this;
|
||||
Discourse.User.findByUsername(username).then(function (user) {
|
||||
Discourse.User.findByUsername(username, {stats: false}).then(function (user) {
|
||||
user = Discourse.User.create(user);
|
||||
self.setProperties({ user: user, avatar: user, visible: true});
|
||||
self.appEvents.trigger('usercard:shown');
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
export default Ember.ArrayController.extend(Discourse.HasCurrentUser, {
|
||||
export default Ember.ArrayController.extend({
|
||||
showAdminLinks: Em.computed.alias("currentUser.staff"),
|
||||
|
||||
actions: {
|
||||
logout: function() {
|
||||
logout() {
|
||||
Discourse.logout();
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import ObjectController from 'discourse/controllers/object';
|
||||
|
||||
// Lists of topics on a user's page.
|
||||
export default ObjectController.extend(Discourse.HasCurrentUser, {
|
||||
export default ObjectController.extend({
|
||||
needs: ["application", "user"],
|
||||
hideCategory: false,
|
||||
showParticipants: false,
|
||||
|
||||
@ -171,6 +171,11 @@ export default Ember.DefaultResolver.extend({
|
||||
const compTemplate = Ember.TEMPLATES['admin/templates/' + decamelized];
|
||||
if (compTemplate) { return compTemplate; }
|
||||
}
|
||||
|
||||
if (decamelized === "javascripts/admin") {
|
||||
return Ember.TEMPLATES['admin/templates/admin'];
|
||||
}
|
||||
|
||||
if (decamelized.indexOf('admin') === 0 || decamelized.indexOf('javascripts/admin') === 0) {
|
||||
decamelized = decamelized.replace(/^admin\_/, 'admin/templates/');
|
||||
decamelized = decamelized.replace(/^admin\./, 'admin/templates/');
|
||||
|
||||
@ -1,24 +1,27 @@
|
||||
import registerUnbound from 'discourse/helpers/register-unbound';
|
||||
|
||||
function iconClasses(icon, modifier) {
|
||||
function iconClasses(icon, params) {
|
||||
var classes = "fa fa-" + icon;
|
||||
if (modifier) { classes += " fa-" + modifier; }
|
||||
if (params.modifier) { classes += " fa-" + params.modifier; }
|
||||
if (params['class']) { classes += ' ' + params['class']; }
|
||||
return classes;
|
||||
}
|
||||
|
||||
function iconHTML(icon, label, modifier) {
|
||||
var html = "<i class='" + iconClasses(icon, modifier) + "'";
|
||||
if (label) { html += " aria-hidden='true'"; }
|
||||
function iconHTML(icon, params) {
|
||||
params = params || {};
|
||||
|
||||
var html = "<i class='" + iconClasses(icon, params) + "'";
|
||||
if (params.label) { html += " aria-hidden='true'"; }
|
||||
html += "></i>";
|
||||
if (label) {
|
||||
html += "<span class='sr-only'>" + I18n.t(label) + "</span>";
|
||||
if (params.label) {
|
||||
html += "<span class='sr-only'>" + I18n.t(params.label) + "</span>";
|
||||
}
|
||||
return html;
|
||||
}
|
||||
|
||||
|
||||
registerUnbound('fa-icon', function(icon, params) {
|
||||
return new Handlebars.SafeString(iconHTML(icon, params.label, params.modifier));
|
||||
return new Handlebars.SafeString(iconHTML(icon, params));
|
||||
});
|
||||
|
||||
export { iconClasses, iconHTML };
|
||||
export { iconHTML };
|
||||
|
||||
@ -42,9 +42,10 @@ export default {
|
||||
app.inject('view', 'session', 'session:main');
|
||||
app.inject('model', 'session', 'session:main');
|
||||
|
||||
// Inject currentUser. Components only for now to prevent any breakage
|
||||
app.register('current-user:main', Discourse.User.current(), { instantiate: false });
|
||||
app.inject('component', 'currentUser', 'current-user:main');
|
||||
app.inject('route', 'currentUser', 'current-user:main');
|
||||
app.inject('controller', 'currentUser', 'current-user:main');
|
||||
|
||||
app.register('store:main', Store);
|
||||
app.inject('route', 'store', 'store:main');
|
||||
|
||||
@ -1672,14 +1672,7 @@
|
||||
// sure the URL and the optinal title are "nice".
|
||||
function properlyEncoded(linkdef) {
|
||||
return linkdef.replace(/^\s*(.*?)(?:\s+"(.+)")?\s*$/, function (wholematch, link, title) {
|
||||
link = link.replace(/\?.*$/, function (querypart) {
|
||||
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, '%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
|
||||
});
|
||||
link = link.replace(/ /g, '%20').replace(/'/g, '%27').replace(/\(/g, '%28').replace(/\)/g, '%29');
|
||||
if (title) {
|
||||
title = title.trim ? title.trim() : title.replace(/^\s*/, "").replace(/\s*$/, "");
|
||||
title = title.replace(/"/g, "quot;").replace(/\(/g, "(").replace(/\)/g, ")").replace(/</g, "<").replace(/>/g, ">");
|
||||
|
||||
@ -5,6 +5,7 @@
|
||||
@namespace Discourse
|
||||
@module Discourse
|
||||
**/
|
||||
|
||||
Discourse.ClickTrack = {
|
||||
|
||||
/**
|
||||
@ -43,15 +44,11 @@ Discourse.ClickTrack = {
|
||||
if (!ownLink) {
|
||||
var $badge = $('span.badge', $link);
|
||||
if ($badge.length === 1) {
|
||||
// don't update counts in category badge
|
||||
if ($link.closest('.badge-category').length === 0) {
|
||||
// nor in oneboxes (except when we force it)
|
||||
if (($link.closest(".onebox-result").length === 0 && $link.closest('.onebox-body').length === 0) || $link.hasClass("track-link")) {
|
||||
var html = $badge.html();
|
||||
if (/^\d+$/.test(html)) {
|
||||
$badge.html(parseInt(html, 10) + 1);
|
||||
}
|
||||
}
|
||||
// don't update counts in category badge nor in oneboxes (except when we force it)
|
||||
if ($link.hasClass("track-link") ||
|
||||
$link.closest('.badge-category,.onebox-result,.onebox-body').length === 0) {
|
||||
var html = $badge.html();
|
||||
if (/^\d+$/.test(html)) { $badge.html(parseInt(html, 10) + 1); }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,6 @@
|
||||
export default {
|
||||
WATCHING: 3,
|
||||
TRACKING: 2,
|
||||
REGULAR: 1,
|
||||
MUTED: 0
|
||||
};
|
||||
@ -313,6 +313,7 @@ Discourse.Utilities = {
|
||||
} else {
|
||||
return new Ember.RSVP.Promise(function(resolve) {
|
||||
var image = document.createElement("img");
|
||||
image.crossOrigin = 'Anonymous';
|
||||
// this event will be fired as soon as the image is loaded
|
||||
image.onload = function(e) {
|
||||
var img = e.target;
|
||||
|
||||
@ -1,12 +0,0 @@
|
||||
/**
|
||||
This mixin provides a `currentUser` property that can be used to retrieve information
|
||||
about the currently logged in user. It is mostly useful to controllers so it can be
|
||||
exposted to templates.
|
||||
**/
|
||||
Discourse.HasCurrentUser = Em.Mixin.create({
|
||||
|
||||
currentUser: function() {
|
||||
return Discourse.User.current();
|
||||
}.property().volatile()
|
||||
|
||||
});
|
||||
@ -192,13 +192,6 @@ var _uncategorized;
|
||||
|
||||
Discourse.Category.reopenClass({
|
||||
|
||||
NotificationLevel: {
|
||||
WATCHING: 3,
|
||||
TRACKING: 2,
|
||||
REGULAR: 1,
|
||||
MUTED: 0
|
||||
},
|
||||
|
||||
findUncategorized: function() {
|
||||
_uncategorized = _uncategorized || Discourse.Category.list().findBy('id', Discourse.Site.currentProp('uncategorized_category_id'));
|
||||
return _uncategorized;
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
Discourse.Site = Discourse.Model.extend({
|
||||
const Site = Discourse.Model.extend({
|
||||
|
||||
isReadOnly: Em.computed.alias('is_readonly'),
|
||||
|
||||
notificationLookup: function() {
|
||||
var result = [];
|
||||
const result = [];
|
||||
_.each(this.get('notification_types'), function(v,k) {
|
||||
result[v] = k;
|
||||
});
|
||||
@ -11,23 +11,23 @@ Discourse.Site = Discourse.Model.extend({
|
||||
}.property('notification_types'),
|
||||
|
||||
flagTypes: function() {
|
||||
var postActionTypes = this.get('post_action_types');
|
||||
const postActionTypes = this.get('post_action_types');
|
||||
if (!postActionTypes) return [];
|
||||
return postActionTypes.filterProperty('is_flag', true);
|
||||
}.property('post_action_types.@each'),
|
||||
|
||||
categoriesByCount: Em.computed.sort('categories', function(a, b) {
|
||||
categoriesByCount: Ember.computed.sort('categories', function(a, b) {
|
||||
return (b.get('topic_count') || 0) - (a.get('topic_count') || 0);
|
||||
}),
|
||||
|
||||
// Sort subcategories under parents
|
||||
sortedCategories: function() {
|
||||
var cats = this.get('categoriesByCount'),
|
||||
const cats = this.get('categoriesByCount'),
|
||||
result = [],
|
||||
remaining = {};
|
||||
|
||||
cats.forEach(function(c) {
|
||||
var parentCategoryId = parseInt(c.get('parent_category_id'), 10);
|
||||
const parentCategoryId = parseInt(c.get('parent_category_id'), 10);
|
||||
if (!parentCategoryId) {
|
||||
result.pushObject(c);
|
||||
} else {
|
||||
@ -37,7 +37,7 @@ Discourse.Site = Discourse.Model.extend({
|
||||
});
|
||||
|
||||
Ember.keys(remaining).forEach(function(parentCategoryId) {
|
||||
var category = result.findBy('id', parseInt(parentCategoryId, 10)),
|
||||
const category = result.findBy('id', parseInt(parentCategoryId, 10)),
|
||||
index = result.indexOf(category);
|
||||
|
||||
if (index !== -1) {
|
||||
@ -48,16 +48,16 @@ Discourse.Site = Discourse.Model.extend({
|
||||
return result;
|
||||
}.property(),
|
||||
|
||||
postActionTypeById: function(id) {
|
||||
postActionTypeById(id) {
|
||||
return this.get("postActionByIdLookup.action" + id);
|
||||
},
|
||||
|
||||
topicFlagTypeById: function(id) {
|
||||
topicFlagTypeById(id) {
|
||||
return this.get("topicFlagByIdLookup.action" + id);
|
||||
},
|
||||
|
||||
updateCategory: function(newCategory) {
|
||||
var existingCategory = this.get('categories').findProperty('id', Em.get(newCategory, 'id'));
|
||||
updateCategory(newCategory) {
|
||||
const existingCategory = this.get('categories').findProperty('id', Em.get(newCategory, 'id'));
|
||||
if (existingCategory) {
|
||||
// Don't update null permissions
|
||||
if (newCategory.permission === null) { delete newCategory.permission; }
|
||||
@ -67,20 +67,15 @@ Discourse.Site = Discourse.Model.extend({
|
||||
}
|
||||
});
|
||||
|
||||
Discourse.Site.reopenClass(Discourse.Singleton, {
|
||||
Site.reopenClass(Discourse.Singleton, {
|
||||
|
||||
/**
|
||||
The current singleton will retrieve its attributes from the `PreloadStore`.
|
||||
|
||||
@method createCurrent
|
||||
@returns {Discourse.Site} the site
|
||||
**/
|
||||
createCurrent: function() {
|
||||
// The current singleton will retrieve its attributes from the `PreloadStore`.
|
||||
createCurrent() {
|
||||
return Discourse.Site.create(PreloadStore.get('site'));
|
||||
},
|
||||
|
||||
create: function() {
|
||||
var result = this._super.apply(this, arguments);
|
||||
create() {
|
||||
const result = this._super.apply(this, arguments);
|
||||
|
||||
if (result.categories) {
|
||||
result.categoriesById = {};
|
||||
@ -107,7 +102,7 @@ Discourse.Site.reopenClass(Discourse.Singleton, {
|
||||
if (result.post_action_types) {
|
||||
result.postActionByIdLookup = Em.Object.create();
|
||||
result.post_action_types = _.map(result.post_action_types,function(p) {
|
||||
var actionType = Discourse.PostActionType.create(p);
|
||||
const actionType = Discourse.PostActionType.create(p);
|
||||
result.postActionByIdLookup.set("action" + p.id, actionType);
|
||||
return actionType;
|
||||
});
|
||||
@ -116,7 +111,7 @@ Discourse.Site.reopenClass(Discourse.Singleton, {
|
||||
if (result.topic_flag_types) {
|
||||
result.topicFlagByIdLookup = Em.Object.create();
|
||||
result.topic_flag_types = _.map(result.topic_flag_types,function(p) {
|
||||
var actionType = Discourse.PostActionType.create(p);
|
||||
const actionType = Discourse.PostActionType.create(p);
|
||||
result.topicFlagByIdLookup.set("action" + p.id, actionType);
|
||||
return actionType;
|
||||
});
|
||||
@ -138,4 +133,4 @@ Discourse.Site.reopenClass(Discourse.Singleton, {
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
export default Site;
|
||||
@ -2,5 +2,11 @@ export default Ember.Object.extend({
|
||||
findAll(type) {
|
||||
const adapter = this.container.lookup('adapter:' + type) || this.container.lookup('adapter:rest');
|
||||
return adapter.findAll(type);
|
||||
},
|
||||
|
||||
find(type, id) {
|
||||
const adapter = this.container.lookup('adapter:' + type) || this.container.lookup('adapter:rest');
|
||||
return adapter.find(type, id);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
@ -1,16 +1,18 @@
|
||||
function isNew(topic){
|
||||
import NotificationLevels from 'discourse/lib/notification-levels';
|
||||
|
||||
function isNew(topic) {
|
||||
return topic.last_read_post_number === null &&
|
||||
((topic.notification_level !== 0 && !topic.notification_level) ||
|
||||
topic.notification_level >= Discourse.Topic.NotificationLevel.TRACKING);
|
||||
topic.notification_level >= NotificationLevels.TRACKING);
|
||||
}
|
||||
|
||||
function isUnread(topic){
|
||||
function isUnread(topic) {
|
||||
return topic.last_read_post_number !== null &&
|
||||
topic.last_read_post_number < topic.highest_post_number &&
|
||||
topic.notification_level >= Discourse.Topic.NotificationLevel.TRACKING;
|
||||
topic.notification_level >= NotificationLevels.TRACKING;
|
||||
}
|
||||
|
||||
Discourse.TopicTrackingState = Discourse.Model.extend({
|
||||
const TopicTrackingState = Discourse.Model.extend({
|
||||
messageCount: 0,
|
||||
|
||||
_setup: function() {
|
||||
@ -19,17 +21,17 @@ Discourse.TopicTrackingState = Discourse.Model.extend({
|
||||
this.states = {};
|
||||
}.on('init'),
|
||||
|
||||
establishChannels: function() {
|
||||
var tracker = this;
|
||||
establishChannels() {
|
||||
const tracker = this;
|
||||
|
||||
var process = function(data){
|
||||
const process = function(data){
|
||||
if (data.message_type === "delete") {
|
||||
tracker.removeTopic(data.topic_id);
|
||||
tracker.incrementMessageCount();
|
||||
}
|
||||
|
||||
if (data.message_type === "new_topic" || data.message_type === "latest") {
|
||||
var ignored_categories = Discourse.User.currentProp("muted_category_ids");
|
||||
const ignored_categories = Discourse.User.currentProp("muted_category_ids");
|
||||
if(_.include(ignored_categories, data.payload.category_id)){
|
||||
return;
|
||||
}
|
||||
@ -41,7 +43,7 @@ Discourse.TopicTrackingState = Discourse.Model.extend({
|
||||
|
||||
if (data.message_type === "new_topic" || data.message_type === "unread" || data.message_type === "read") {
|
||||
tracker.notify(data);
|
||||
var old = tracker.states["t" + data.topic_id];
|
||||
const old = tracker.states["t" + data.topic_id];
|
||||
|
||||
if(!_.isEqual(old, data.payload)){
|
||||
tracker.states["t" + data.topic_id] = data.payload;
|
||||
@ -52,32 +54,32 @@ Discourse.TopicTrackingState = Discourse.Model.extend({
|
||||
|
||||
Discourse.MessageBus.subscribe("/new", process);
|
||||
Discourse.MessageBus.subscribe("/latest", process);
|
||||
var currentUser = Discourse.User.current();
|
||||
const currentUser = Discourse.User.current();
|
||||
if(currentUser) {
|
||||
Discourse.MessageBus.subscribe("/unread/" + currentUser.id, process);
|
||||
}
|
||||
},
|
||||
|
||||
updateSeen: function(topicId, highestSeen) {
|
||||
updateSeen(topicId, highestSeen) {
|
||||
if(!topicId || !highestSeen) { return; }
|
||||
var state = this.states["t" + topicId];
|
||||
const state = this.states["t" + topicId];
|
||||
if(state && (!state.last_read_post_number || state.last_read_post_number < highestSeen)) {
|
||||
state.last_read_post_number = highestSeen;
|
||||
this.incrementMessageCount();
|
||||
}
|
||||
},
|
||||
|
||||
notify: function(data){
|
||||
notify(data){
|
||||
if (!this.newIncoming) { return; }
|
||||
|
||||
var filter = this.get("filter");
|
||||
const filter = this.get("filter");
|
||||
|
||||
if ((filter === "all" || filter === "latest" || filter === "new") && data.message_type === "new_topic" ) {
|
||||
this.addIncoming(data.topic_id);
|
||||
}
|
||||
|
||||
if ((filter === "all" || filter === "unread") && data.message_type === "unread") {
|
||||
var old = this.states["t" + data.topic_id];
|
||||
const old = this.states["t" + data.topic_id];
|
||||
if(!old || old.highest_post_number === old.last_read_post_number) {
|
||||
this.addIncoming(data.topic_id);
|
||||
}
|
||||
@ -90,47 +92,47 @@ Discourse.TopicTrackingState = Discourse.Model.extend({
|
||||
this.set("incomingCount", this.newIncoming.length);
|
||||
},
|
||||
|
||||
addIncoming: function(topicId) {
|
||||
addIncoming(topicId) {
|
||||
if(this.newIncoming.indexOf(topicId) === -1){
|
||||
this.newIncoming.push(topicId);
|
||||
}
|
||||
},
|
||||
|
||||
resetTracking: function(){
|
||||
resetTracking(){
|
||||
this.newIncoming = [];
|
||||
this.set("incomingCount", 0);
|
||||
},
|
||||
|
||||
// track how many new topics came for this filter
|
||||
trackIncoming: function(filter) {
|
||||
trackIncoming(filter) {
|
||||
this.newIncoming = [];
|
||||
this.set("filter", filter);
|
||||
this.set("incomingCount", 0);
|
||||
},
|
||||
|
||||
hasIncoming: function(){
|
||||
var count = this.get('incomingCount');
|
||||
const count = this.get('incomingCount');
|
||||
return count && count > 0;
|
||||
}.property('incomingCount'),
|
||||
|
||||
removeTopic: function(topic_id) {
|
||||
removeTopic(topic_id) {
|
||||
delete this.states["t" + topic_id];
|
||||
},
|
||||
|
||||
// If we have a cached topic list, we can update it from our tracking
|
||||
// information.
|
||||
updateTopics: function(topics) {
|
||||
updateTopics(topics) {
|
||||
if (Em.isEmpty(topics)) { return; }
|
||||
|
||||
var states = this.states;
|
||||
const states = this.states;
|
||||
topics.forEach(function(t) {
|
||||
var state = states['t' + t.get('id')];
|
||||
const state = states['t' + t.get('id')];
|
||||
|
||||
if (state) {
|
||||
var lastRead = t.get('last_read_post_number');
|
||||
const lastRead = t.get('last_read_post_number');
|
||||
if (lastRead !== state.last_read_post_number) {
|
||||
var postsCount = t.get('posts_count'),
|
||||
newPosts = postsCount - state.highest_post_number,
|
||||
const postsCount = t.get('posts_count');
|
||||
let newPosts = postsCount - state.highest_post_number,
|
||||
unread = postsCount - state.last_read_post_number;
|
||||
|
||||
if (newPosts < 0) { newPosts = 0; }
|
||||
@ -151,16 +153,16 @@ Discourse.TopicTrackingState = Discourse.Model.extend({
|
||||
});
|
||||
},
|
||||
|
||||
sync: function(list, filter) {
|
||||
var tracker = this,
|
||||
sync(list, filter) {
|
||||
const tracker = this,
|
||||
states = tracker.states;
|
||||
|
||||
if (!list || !list.topics) { return; }
|
||||
|
||||
// compensate for delayed "new" topics
|
||||
// client side we know they are not new, server side we think they are
|
||||
for (var i=list.topics.length-1; i>=0; i--) {
|
||||
var state = states["t"+ list.topics[i].id];
|
||||
for (let i=list.topics.length-1; i>=0; i--) {
|
||||
const state = states["t"+ list.topics[i].id];
|
||||
if (state && state.last_read_post_number > 0) {
|
||||
if (filter === "new") {
|
||||
list.topics.splice(i, 1);
|
||||
@ -172,7 +174,7 @@ Discourse.TopicTrackingState = Discourse.Model.extend({
|
||||
}
|
||||
|
||||
list.topics.forEach(function(topic){
|
||||
var row = tracker.states["t" + topic.id] || {};
|
||||
const row = tracker.states["t" + topic.id] || {};
|
||||
row.topic_id = topic.id;
|
||||
row.notification_level = topic.notification_level;
|
||||
|
||||
@ -199,7 +201,7 @@ Discourse.TopicTrackingState = Discourse.Model.extend({
|
||||
// Correct missing states, safeguard in case message bus is corrupt
|
||||
if((filter === "new" || filter === "unread") && !list.more_topics_url){
|
||||
|
||||
var ids = {};
|
||||
const ids = {};
|
||||
list.topics.forEach(function(r){
|
||||
ids["t" + r.id] = true;
|
||||
});
|
||||
@ -224,11 +226,11 @@ Discourse.TopicTrackingState = Discourse.Model.extend({
|
||||
this.incrementMessageCount();
|
||||
},
|
||||
|
||||
incrementMessageCount: function() {
|
||||
incrementMessageCount() {
|
||||
this.set("messageCount", this.get("messageCount") + 1);
|
||||
},
|
||||
|
||||
countNew: function(category_id){
|
||||
countNew(category_id){
|
||||
return _.chain(this.states)
|
||||
.where(isNew)
|
||||
.where(function(topic){ return topic.category_id === category_id || !category_id;})
|
||||
@ -236,8 +238,8 @@ Discourse.TopicTrackingState = Discourse.Model.extend({
|
||||
.length;
|
||||
},
|
||||
|
||||
resetNew: function() {
|
||||
var self = this;
|
||||
resetNew() {
|
||||
const self = this;
|
||||
Object.keys(this.states).forEach(function (id) {
|
||||
if (self.states[id].last_read_post_number === null) {
|
||||
delete self.states[id];
|
||||
@ -245,7 +247,7 @@ Discourse.TopicTrackingState = Discourse.Model.extend({
|
||||
});
|
||||
},
|
||||
|
||||
countUnread: function(category_id){
|
||||
countUnread(category_id){
|
||||
return _.chain(this.states)
|
||||
.where(isUnread)
|
||||
.where(function(topic){ return topic.category_id === category_id || !category_id;})
|
||||
@ -253,19 +255,19 @@ Discourse.TopicTrackingState = Discourse.Model.extend({
|
||||
.length;
|
||||
},
|
||||
|
||||
countCategory: function(category_id) {
|
||||
var count = 0;
|
||||
countCategory(category_id) {
|
||||
let sum = 0;
|
||||
_.each(this.states, function(topic){
|
||||
if (topic.category_id === category_id) {
|
||||
count += (topic.last_read_post_number === null ||
|
||||
sum += (topic.last_read_post_number === null ||
|
||||
topic.last_read_post_number < topic.highest_post_number) ? 1 : 0;
|
||||
}
|
||||
});
|
||||
return count;
|
||||
return sum;
|
||||
},
|
||||
|
||||
lookupCount: function(name, category){
|
||||
var categoryName = category ? Em.get(category, "name") : null;
|
||||
lookupCount(name, category){
|
||||
let categoryName = category ? Em.get(category, "name") : null;
|
||||
if(name === "new") {
|
||||
return this.countNew(categoryName);
|
||||
} else if(name === "unread") {
|
||||
@ -277,9 +279,9 @@ Discourse.TopicTrackingState = Discourse.Model.extend({
|
||||
}
|
||||
}
|
||||
},
|
||||
loadStates: function (data) {
|
||||
loadStates(data) {
|
||||
// not exposed
|
||||
var states = this.states;
|
||||
const states = this.states;
|
||||
|
||||
if(data) {
|
||||
_.each(data,function(topic){
|
||||
@ -290,19 +292,21 @@ Discourse.TopicTrackingState = Discourse.Model.extend({
|
||||
});
|
||||
|
||||
|
||||
Discourse.TopicTrackingState.reopenClass({
|
||||
createFromStates: function(data){
|
||||
var instance = Discourse.TopicTrackingState.create();
|
||||
TopicTrackingState.reopenClass({
|
||||
createFromStates(data){
|
||||
const instance = Discourse.TopicTrackingState.create();
|
||||
instance.loadStates(data);
|
||||
instance.establishChannels();
|
||||
return instance;
|
||||
},
|
||||
current: function(){
|
||||
current(){
|
||||
if (!this.tracker) {
|
||||
var data = PreloadStore.get('topicTrackingStates');
|
||||
const data = PreloadStore.get('topicTrackingStates');
|
||||
this.tracker = this.createFromStates(data);
|
||||
PreloadStore.remove('topicTrackingStates');
|
||||
}
|
||||
return this.tracker;
|
||||
}
|
||||
});
|
||||
|
||||
export default TopicTrackingState;
|
||||
@ -26,6 +26,10 @@ const Topic = Discourse.Model.extend({
|
||||
return PostStream.create({topic: this});
|
||||
}.property(),
|
||||
|
||||
replyCount: function() {
|
||||
return this.get('posts_count') - 1;
|
||||
}.property('posts_count'),
|
||||
|
||||
details: function() {
|
||||
return TopicDetails.create({topic: this});
|
||||
}.property(),
|
||||
@ -411,7 +415,6 @@ Topic.reopenClass({
|
||||
});
|
||||
|
||||
return Discourse.ajax(topic.get('url'), { type: 'PUT', data: props }).then(function(result) {
|
||||
|
||||
// The title can be cleaned up server side
|
||||
props.title = result.basic_topic.title;
|
||||
props.fancy_title = result.basic_topic.fancy_title;
|
||||
|
||||
@ -292,11 +292,11 @@ Discourse.User = Discourse.Model.extend({
|
||||
return this.get('stats').rejectProperty('isPM');
|
||||
}.property('stats.@each.isPM'),
|
||||
|
||||
findDetails: function() {
|
||||
findDetails: function(options) {
|
||||
var user = this;
|
||||
|
||||
return PreloadStore.getAndRemove("user_" + user.get('username'), function() {
|
||||
return Discourse.ajax("/users/" + user.get('username') + '.json');
|
||||
return Discourse.ajax("/users/" + user.get('username') + '.json', {data: options});
|
||||
}).then(function (json) {
|
||||
|
||||
if (!Em.isEmpty(json.user.stats)) {
|
||||
@ -468,9 +468,9 @@ Discourse.User.reopenClass(Discourse.Singleton, {
|
||||
@method findByUsername
|
||||
@returns {Promise} a promise that resolves to a `Discourse.User`
|
||||
**/
|
||||
findByUsername: function(username) {
|
||||
findByUsername: function(username, options) {
|
||||
var user = Discourse.User.create({username: username});
|
||||
return user.findDetails();
|
||||
return user.findDetails(options);
|
||||
},
|
||||
|
||||
/**
|
||||
|
||||
@ -7,6 +7,11 @@
|
||||
@module Discourse
|
||||
**/
|
||||
Discourse.UserBadge = Discourse.Model.extend({
|
||||
postUrl: function() {
|
||||
if(this.get('topic_title')) {
|
||||
return "/t/-/" + this.get('topic_id') + "/" + this.get('post_number');
|
||||
}
|
||||
}.property(), // avoid the extra bindings for now
|
||||
/**
|
||||
Revoke this badge.
|
||||
|
||||
@ -93,7 +98,7 @@ Discourse.UserBadge.reopenClass({
|
||||
@returns {Promise} a promise that resolves to an array of `Discourse.UserBadge`.
|
||||
**/
|
||||
findByUsername: function(username, options) {
|
||||
var url = "/users/" + username + "/badges_json.json";
|
||||
var url = "/user-badges/" + username + ".json";
|
||||
if (options && options.grouped) {
|
||||
url += "?grouped=true";
|
||||
}
|
||||
@ -128,12 +133,13 @@ Discourse.UserBadge.reopenClass({
|
||||
@param {String} username username of the user to be granted the badge.
|
||||
@returns {Promise} a promise that resolves to an instance of `Discourse.UserBadge`.
|
||||
**/
|
||||
grant: function(badgeId, username) {
|
||||
grant: function(badgeId, username, reason) {
|
||||
return Discourse.ajax("/user_badges", {
|
||||
type: "POST",
|
||||
data: {
|
||||
username: username,
|
||||
badge_id: badgeId
|
||||
badge_id: badgeId,
|
||||
reason: reason
|
||||
}
|
||||
}).then(function(json) {
|
||||
return Discourse.UserBadge.createFromJson(json);
|
||||
|
||||
@ -28,8 +28,7 @@ export default function(filter, params) {
|
||||
category: model,
|
||||
filterMode: filterMode,
|
||||
noSubcategories: params && params.no_subcategories,
|
||||
canEditCategory: model.get('can_edit'),
|
||||
canChangeCategoryNotificationLevel: Discourse.User.current()
|
||||
canEditCategory: model.get('can_edit')
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
@ -2,7 +2,7 @@ import { queryParams } from 'discourse/controllers/discovery-sortable';
|
||||
|
||||
// A helper to build a topic route for a filter
|
||||
function filterQueryParams(params, defaultParams) {
|
||||
var findOpts = defaultParams || {};
|
||||
const findOpts = defaultParams || {};
|
||||
if (params) {
|
||||
Ember.keys(queryParams).forEach(function(opt) {
|
||||
if (params[opt]) { findOpts[opt] = params[opt]; }
|
||||
@ -16,47 +16,47 @@ export default function(filter, extras) {
|
||||
return Discourse.Route.extend({
|
||||
queryParams: queryParams,
|
||||
|
||||
beforeModel: function() {
|
||||
beforeModel() {
|
||||
this.controllerFor('navigation/default').set('filterMode', filter);
|
||||
},
|
||||
|
||||
model: function(data, transition) {
|
||||
model(data, transition) {
|
||||
|
||||
// attempt to stop early cause we need this to be called before .sync
|
||||
Discourse.ScreenTrack.current().stop();
|
||||
|
||||
var findOpts = filterQueryParams(transition.queryParams),
|
||||
extras = { cached: this.isPoppedState(transition) };
|
||||
const findOpts = filterQueryParams(transition.queryParams),
|
||||
extras = { cached: this.isPoppedState(transition) };
|
||||
|
||||
return Discourse.TopicList.list(filter, findOpts, extras);
|
||||
},
|
||||
|
||||
titleToken: function() {
|
||||
titleToken() {
|
||||
if (filter === Discourse.Utilities.defaultHomepage()) { return; }
|
||||
|
||||
var filterText = I18n.t('filters.' + filter.replace('/', '.') + '.title', {count: 0});
|
||||
const filterText = I18n.t('filters.' + filter.replace('/', '.') + '.title', {count: 0});
|
||||
return I18n.t('filters.with_topics', {filter: filterText});
|
||||
},
|
||||
|
||||
setupController: function(controller, model, trans) {
|
||||
setupController(controller, model, trans) {
|
||||
if (trans) {
|
||||
controller.setProperties(Em.getProperties(trans, _.keys(queryParams).map(function(v){
|
||||
return 'queryParams.' + v;
|
||||
})));
|
||||
}
|
||||
|
||||
var periods = this.controllerFor('discovery').get('periods'),
|
||||
periodId = model.get('for_period') || (filter.indexOf('/') > 0 ? filter.split('/')[1] : '');
|
||||
const periods = this.controllerFor('discovery').get('periods'),
|
||||
periodId = model.get('for_period') || (filter.indexOf('/') > 0 ? filter.split('/')[1] : '');
|
||||
|
||||
var topicOpts = {
|
||||
model: model,
|
||||
const topicOpts = {
|
||||
model,
|
||||
category: null,
|
||||
period: periods.findBy('id', periodId),
|
||||
selected: [],
|
||||
expandGloballyPinned: true
|
||||
};
|
||||
|
||||
var params = model.get('params');
|
||||
const params = model.get('params');
|
||||
if (params && Object.keys(params).length) {
|
||||
topicOpts.order = params.order;
|
||||
topicOpts.ascending = params.ascending;
|
||||
@ -67,7 +67,7 @@ export default function(filter, extras) {
|
||||
this.controllerFor('navigation/default').set('canCreateTopic', model.get('can_create_topic'));
|
||||
},
|
||||
|
||||
renderTemplate: function() {
|
||||
renderTemplate() {
|
||||
this.render('navigation/default', { outlet: 'navigation-bar' });
|
||||
this.render('discovery/topics', { controller: 'discovery/topics', outlet: 'list-container' });
|
||||
}
|
||||
|
||||
@ -1,22 +1,22 @@
|
||||
import ShowFooter from "discourse/mixins/show-footer";
|
||||
|
||||
Discourse.DiscoveryCategoriesRoute = Discourse.Route.extend(Discourse.OpenComposer, ShowFooter, {
|
||||
renderTemplate: function() {
|
||||
renderTemplate() {
|
||||
this.render('navigation/categories', { outlet: 'navigation-bar' });
|
||||
this.render('discovery/categories', { outlet: 'list-container' });
|
||||
},
|
||||
|
||||
beforeModel: function() {
|
||||
beforeModel() {
|
||||
this.controllerFor('navigation/categories').set('filterMode', 'categories');
|
||||
},
|
||||
|
||||
model: function() {
|
||||
model() {
|
||||
// TODO: Remove this and ensure server side does not supply `topic_list`
|
||||
// if default page is categories
|
||||
PreloadStore.remove("topic_list");
|
||||
|
||||
return Discourse.CategoryList.list('categories').then(function(list) {
|
||||
var tracking = Discourse.TopicTrackingState.current();
|
||||
const tracking = Discourse.TopicTrackingState.current();
|
||||
if (tracking) {
|
||||
tracking.sync(list, 'categories');
|
||||
tracking.trackIncoming('categories');
|
||||
@ -25,11 +25,12 @@ Discourse.DiscoveryCategoriesRoute = Discourse.Route.extend(Discourse.OpenCompos
|
||||
});
|
||||
},
|
||||
|
||||
titleToken: function() {
|
||||
titleToken() {
|
||||
if (Discourse.Utilities.defaultHomepage() === "categories") { return; }
|
||||
return I18n.t('filters.categories.title');
|
||||
},
|
||||
|
||||
setupController: function(controller, model) {
|
||||
setupController(controller, model) {
|
||||
controller.set('model', model);
|
||||
|
||||
// Only show either the Create Category or Create Topic button
|
||||
@ -40,22 +41,19 @@ Discourse.DiscoveryCategoriesRoute = Discourse.Route.extend(Discourse.OpenCompos
|
||||
},
|
||||
|
||||
actions: {
|
||||
createCategory: function() {
|
||||
var groups = Discourse.Site.current().groups;
|
||||
var everyone_group = groups.findBy('id', 0);
|
||||
var group_names = groups.map(function(group) {
|
||||
return group.name;
|
||||
});
|
||||
createCategory() {
|
||||
const groups = this.site.groups,
|
||||
everyoneName = groups.findBy('id', 0).name;
|
||||
|
||||
Discourse.Route.showModal(this, 'editCategory', Discourse.Category.create({
|
||||
color: 'AB9364', text_color: 'FFFFFF', group_permissions: [{group_name: everyone_group.name, permission_type: 1}],
|
||||
available_groups: group_names,
|
||||
color: 'AB9364', text_color: 'FFFFFF', group_permissions: [{group_name: everyoneName, permission_type: 1}],
|
||||
available_groups: groups.map(g => g.name),
|
||||
allow_badges: true
|
||||
}));
|
||||
this.controllerFor('editCategory').set('selectedTab', 'general');
|
||||
},
|
||||
|
||||
createTopic: function() {
|
||||
createTopic() {
|
||||
this.openComposer(this.controllerFor('discovery/categories'));
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,82 +1,97 @@
|
||||
<div class='container body-page'>
|
||||
<section class='about'>
|
||||
<h2>{{i18n 'about.title' title=title}}</h2>
|
||||
<p>{{description}}</p>
|
||||
</section>
|
||||
<div class='container'>
|
||||
<div class='contents clearfix body-page'>
|
||||
|
||||
{{#if admins}}
|
||||
<section class='about admins'>
|
||||
<h3>{{i18n 'about.our_admins'}}</h3>
|
||||
|
||||
{{#each a in admins}}
|
||||
{{user-small user=a}}
|
||||
{{/each}}
|
||||
<div class='clearfix'></div>
|
||||
<ul class="nav-pills">
|
||||
<li class="nav-item-about">{{#link-to 'about' class="active"}}{{i18n 'about.simple_title'}}{{/link-to}}</li>
|
||||
{{#if faqOverriden}}
|
||||
<li class="nav-item-guidelines">{{#link-to 'guidelines'}}{{i18n 'guidelines'}}{{/link-to}}</li>
|
||||
<li class="nav-item-faq">{{#link-to 'faq'}}{{i18n 'faq'}}{{/link-to}}</li>
|
||||
{{else}}
|
||||
<li class="nav-item-faq">{{#link-to 'faq'}}{{i18n 'faq'}}{{/link-to}}</li>
|
||||
{{/if}}
|
||||
<li class="nav-item-tos">{{#link-to 'tos'}}{{i18n 'terms_of_service'}}{{/link-to}}</li>
|
||||
<li class="nav-item-privacy">{{#link-to 'privacy'}}{{i18n 'privacy'}}{{/link-to}}</li>
|
||||
</ul>
|
||||
|
||||
<section class='about'>
|
||||
<h2>{{i18n 'about.title' title=title}}</h2>
|
||||
<p>{{description}}</p>
|
||||
</section>
|
||||
{{/if}}
|
||||
|
||||
{{#if moderators}}
|
||||
<section class='about moderators'>
|
||||
<h3>{{i18n 'about.our_moderators'}}</h3>
|
||||
{{#if admins}}
|
||||
<section class='about admins'>
|
||||
<h3>{{i18n 'about.our_admins'}}</h3>
|
||||
|
||||
<div class='users'>
|
||||
{{#each m in moderators}}
|
||||
{{user-small user=m}}
|
||||
{{#each a in admins}}
|
||||
{{user-small user=a}}
|
||||
{{/each}}
|
||||
</div>
|
||||
<div class='clearfix'></div>
|
||||
<div class='clearfix'></div>
|
||||
|
||||
</section>
|
||||
{{/if}}
|
||||
|
||||
{{#if moderators}}
|
||||
<section class='about moderators'>
|
||||
<h3>{{i18n 'about.our_moderators'}}</h3>
|
||||
|
||||
<div class='users'>
|
||||
{{#each m in moderators}}
|
||||
{{user-small user=m}}
|
||||
{{/each}}
|
||||
</div>
|
||||
<div class='clearfix'></div>
|
||||
</section>
|
||||
{{/if}}
|
||||
|
||||
<section class='about stats'>
|
||||
<h3>{{i18n 'about.stats'}}</h3>
|
||||
|
||||
<table class='table'>
|
||||
<tr>
|
||||
<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>
|
||||
{{/if}}
|
||||
|
||||
<section class='about stats'>
|
||||
<h3>{{i18n 'about.stats'}}</h3>
|
||||
|
||||
<table class='table'>
|
||||
<tr>
|
||||
<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>
|
||||
|
||||
{{#if contactInfo}}
|
||||
<section class='about contact'>
|
||||
<h3>{{i18n 'about.contact'}}</h3>
|
||||
<p>{{contactInfo}}</p>
|
||||
</section>
|
||||
{{/if}}
|
||||
{{#if contactInfo}}
|
||||
<section class='about contact'>
|
||||
<h3>{{i18n 'about.contact'}}</h3>
|
||||
<p>{{{contactInfo}}}</p>
|
||||
</section>
|
||||
{{/if}}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -17,6 +17,12 @@
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
{{#if showLongDescription}}
|
||||
<div class='long-description banner'>
|
||||
{{{long_description}}}
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
{{#if userBadges}}
|
||||
<div class={{unbound layoutClass}}>
|
||||
{{#each ub in userBadges}}
|
||||
|
||||
@ -1,17 +0,0 @@
|
||||
<div>
|
||||
<button class='btn standard dropdown-toggle' data-toggle='dropdown'>
|
||||
<i {{bind-attr class=view.icon}}></i>
|
||||
<span class='caret'></span>
|
||||
</button>
|
||||
|
||||
|
||||
<ul class='notification-dropdown-menu'>
|
||||
{{#each level in view.dropDownContent}}
|
||||
<li data-id="{{level.id}}">
|
||||
<a {{action "setNotification" level.id}} href="#">
|
||||
<span class="title"><i {{bind-attr class=level.styleClasses}}></i> {{level.title}}</span><span>{{level.description}}</span>
|
||||
</a>
|
||||
</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
</div>
|
||||
@ -0,0 +1,13 @@
|
||||
{{topic-status topic=topic}}
|
||||
<a class='title' href="{{unbound topic.lastUnreadUrl}}">{{{unbound topic.fancy_title}}}</a>
|
||||
{{topic-post-badges newPosts=topic.totalUnread unseen=topic.unseen url=topic.lastUnreadUrl}}
|
||||
|
||||
{{#if latestTopicOnly}}
|
||||
<div class='last-user-info'>
|
||||
{{i18n 'categories.latest_by'}} <a href="{{{unbound topic.lastPosterUrl}}}">{{unbound topic.last_poster.username}}</a>
|
||||
<a href="{{unbound topic.lastPostUrl}}">{{format-age topic.last_posted_at}}</a>
|
||||
</div>
|
||||
{{else}}
|
||||
|
||||
<a href class="last-posted-at">{{format-age topic.last_posted_at}}</a>
|
||||
{{/if}}
|
||||
@ -1,6 +1,6 @@
|
||||
<label>{{{field.name}}}</label>
|
||||
<div class='controls'>
|
||||
{{input value=value}}
|
||||
{{input value=value maxlength=site.user_field_max_length}}
|
||||
{{#if field.required}}<span class='required'>*</span>{{/if}}
|
||||
<p>{{{field.description}}}</p>
|
||||
</div>
|
||||
|
||||
@ -1,4 +1,9 @@
|
||||
{{#link-to 'user' user.username}}
|
||||
{{avatar user imageSize="tiny"}}
|
||||
{{user.username}}
|
||||
{{/link-to}}
|
||||
<div class="user-image">
|
||||
{{#link-to 'user' user.username}}{{avatar user imageSize="large"}}{{/link-to}}
|
||||
</div>
|
||||
|
||||
<div class="user-detail">
|
||||
<span class="username">{{#link-to 'user' user.username}}{{user.username}}{{/link-to}}</span>
|
||||
<span class="name">{{user.name}}</span>
|
||||
<span class="title">{{user.title}}</span>
|
||||
</div>
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
<a href='#' {{action "closeMessage" this}} class='close'><i class='fa fa-times-circle'></i></a>
|
||||
<a href='#' {{action "closeMessage" this}} class='close'>{{fa-icon "times-circle"}}</a>
|
||||
<h3>{{i18n 'composer.similar_topics'}}</h3>
|
||||
|
||||
<ul class='topics'>
|
||||
|
||||
@ -16,10 +16,10 @@
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
@ -45,21 +45,7 @@
|
||||
</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>
|
||||
{{featured-topic topic=f latestTopicOnly=latestTopicOnly action="showTopicEntrance"}}
|
||||
{{/each}}
|
||||
</td>
|
||||
<td class='stats' {{bind-attr title="c.topicStatsTitle"}}>
|
||||
|
||||
@ -1,3 +1,3 @@
|
||||
<{{view.tagName}} class='num posts-map posts {{view.likesHeat}}' title='{{view.title}}'>
|
||||
<a href class='posts-map badge-posts {{view.likesHeat}}'>{{topic.posts_count}}</a>
|
||||
<a href class='posts-map badge-posts {{view.likesHeat}}'>{{topic.replyCount}}</a>
|
||||
</{{view.tagName}}>
|
||||
|
||||
@ -15,7 +15,7 @@
|
||||
|
||||
{{#if t.hasExcerpt}}
|
||||
<div class="topic-excerpt">
|
||||
{{t.excerpt}}
|
||||
{{{t.excerpt}}}
|
||||
{{#if t.excerptTruncated}}
|
||||
{{#unless t.canClearPin}}<a href="{{unbound t.url}}">{{i18n 'read_more'}}</a>{{/unless}}
|
||||
{{/if}}
|
||||
|
||||
@ -10,8 +10,8 @@
|
||||
{{custom-html "extraNavItem"}}
|
||||
</ul>
|
||||
|
||||
{{#if canChangeCategoryNotificationLevel}}
|
||||
{{view 'category-notifications-button' category=category}}
|
||||
{{#if currentUser}}
|
||||
{{category-notifications-button category=category}}
|
||||
{{/if}}
|
||||
|
||||
{{#if canCreateTopic}}
|
||||
|
||||
@ -10,7 +10,7 @@
|
||||
{{#if showPosters}}
|
||||
{{raw "topic-list-header-column" order='posters' name='users'}}
|
||||
{{/if}}
|
||||
{{raw "topic-list-header-column" sortable=sortable number='true' order='posts' name='posts'}}
|
||||
{{raw "topic-list-header-column" sortable=sortable number='true' order='posts' name='replies'}}
|
||||
{{#if showParticipants}}
|
||||
{{raw "topic-list-header-column" order='participants' name='users'}}
|
||||
{{/if}}
|
||||
|
||||
@ -145,6 +145,10 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{#each uf in userFields}}
|
||||
{{user-field field=uf.field value=uf.value}}
|
||||
{{/each}}
|
||||
|
||||
<div class="control-group pref-location">
|
||||
<label class="control-label">{{i18n 'user.location'}}</label>
|
||||
<div class="controls">
|
||||
@ -212,10 +216,6 @@
|
||||
{{plugin-outlet "user_custom_preferences"}}
|
||||
</div>
|
||||
|
||||
{{#each uf in userFields}}
|
||||
{{user-field field=uf.field value=uf.value}}
|
||||
{{/each}}
|
||||
|
||||
<div class="control-group category">
|
||||
<label class="control-label">{{i18n 'user.categories_settings'}}</label>
|
||||
<div class="controls category-controls">
|
||||
|
||||
@ -3,7 +3,8 @@
|
||||
import userSearch from 'discourse/lib/user-search';
|
||||
import afterTransition from 'discourse/lib/after-transition';
|
||||
|
||||
var ComposerView = Discourse.View.extend(Ember.Evented, {
|
||||
const ComposerView = Discourse.View.extend(Ember.Evented, {
|
||||
_lastKeyTimeout: null,
|
||||
templateName: 'composer',
|
||||
elementId: 'reply-control',
|
||||
classNameBindings: ['model.creatingPrivateMessage:private-message',
|
||||
@ -48,12 +49,12 @@ var ComposerView = Discourse.View.extend(Ember.Evented, {
|
||||
Ember.run.scheduleOnce('afterRender', this, 'refreshPreview');
|
||||
}.observes('model.reply', 'model.hidePreview'),
|
||||
|
||||
focusIn: function() {
|
||||
var controller = this.get('controller');
|
||||
focusIn() {
|
||||
const controller = this.get('controller');
|
||||
if (controller) controller.updateDraftStatus();
|
||||
},
|
||||
|
||||
movePanels: function(sizePx) {
|
||||
movePanels(sizePx) {
|
||||
$('#main-outlet').css('padding-bottom', sizePx);
|
||||
$('.composer-popup').css('bottom', sizePx);
|
||||
// signal the progress bar it should move!
|
||||
@ -61,14 +62,14 @@ var ComposerView = Discourse.View.extend(Ember.Evented, {
|
||||
},
|
||||
|
||||
resize: function() {
|
||||
var self = this;
|
||||
const self = this;
|
||||
Em.run.scheduleOnce('afterRender', function() {
|
||||
var h = $('#reply-control').height() || 0;
|
||||
const h = $('#reply-control').height() || 0;
|
||||
self.movePanels.apply(self, [h + "px"]);
|
||||
|
||||
// Figure out the size of the fields
|
||||
var $fields = self.$('.composer-fields'),
|
||||
pos = $fields.position();
|
||||
const $fields = self.$('.composer-fields');
|
||||
let pos = $fields.position();
|
||||
|
||||
if (pos) {
|
||||
self.$('.wmd-controls').css('top', $fields.height() + pos.top + 5);
|
||||
@ -83,17 +84,19 @@ var ComposerView = Discourse.View.extend(Ember.Evented, {
|
||||
});
|
||||
}.observes('model.composeState', 'model.action'),
|
||||
|
||||
keyUp: function() {
|
||||
var controller = this.get('controller');
|
||||
keyUp() {
|
||||
const controller = this.get('controller');
|
||||
controller.checkReplyLength();
|
||||
|
||||
var lastKeyUp = new Date();
|
||||
const lastKeyUp = new Date();
|
||||
this.set('lastKeyUp', lastKeyUp);
|
||||
|
||||
// One second from now, check to see if the last key was hit when
|
||||
// we recorded it. If it was, the user paused typing.
|
||||
var self = this;
|
||||
Em.run.later(function() {
|
||||
const self = this;
|
||||
|
||||
Ember.run.cancel(this._lastKeyTimeout);
|
||||
this._lastKeyTimeout = Ember.run.later(function() {
|
||||
if (lastKeyUp !== self.get('lastKeyUp')) return;
|
||||
|
||||
// Search for similar topics if the user pauses typing
|
||||
@ -101,7 +104,7 @@ var ComposerView = Discourse.View.extend(Ember.Evented, {
|
||||
}, 1000);
|
||||
},
|
||||
|
||||
keyDown: function(e) {
|
||||
keyDown(e) {
|
||||
if (e.which === 27) {
|
||||
// ESC
|
||||
this.get('controller').send('hitEsc');
|
||||
@ -114,12 +117,12 @@ var ComposerView = Discourse.View.extend(Ember.Evented, {
|
||||
},
|
||||
|
||||
_enableResizing: function() {
|
||||
var $replyControl = $('#reply-control'),
|
||||
const $replyControl = $('#reply-control'),
|
||||
self = this;
|
||||
|
||||
$replyControl.DivResizer({
|
||||
resize: this.resize.bind(self),
|
||||
onDrag: function (sizePx) { self.movePanels.apply(self, [sizePx]); }
|
||||
onDrag(sizePx) { self.movePanels.apply(self, [sizePx]); }
|
||||
});
|
||||
afterTransition($replyControl, this.resize.bind(self));
|
||||
this.ensureMaximumDimensionForImagesInPreview();
|
||||
@ -130,14 +133,14 @@ var ComposerView = Discourse.View.extend(Ember.Evented, {
|
||||
this.set('controller.view', null);
|
||||
}.on('willDestroyElement'),
|
||||
|
||||
ensureMaximumDimensionForImagesInPreview: function() {
|
||||
ensureMaximumDimensionForImagesInPreview() {
|
||||
// This enforce maximum dimensions of images in the preview according
|
||||
// to the current site settings.
|
||||
// For interactivity, we immediately insert the locally cooked version
|
||||
// of the post into the stream when the user hits reply. We therefore also
|
||||
// need to enforce these rules on the .cooked version.
|
||||
// Meanwhile, the server is busy post-processing the post and generating thumbnails.
|
||||
var style = Discourse.Mobile.mobileView ?
|
||||
const style = Discourse.Mobile.mobileView ?
|
||||
'max-width: 100%; height: auto;' :
|
||||
'max-width:' + Discourse.SiteSettings.max_image_width + 'px;' +
|
||||
'max-height:' + Discourse.SiteSettings.max_image_height + 'px;';
|
||||
@ -145,17 +148,17 @@ var ComposerView = Discourse.View.extend(Ember.Evented, {
|
||||
$('<style>#wmd-preview img:not(.thumbnail), .cooked img:not(.thumbnail) {' + style + '}</style>').appendTo('head');
|
||||
},
|
||||
|
||||
click: function() {
|
||||
click() {
|
||||
this.get('controller').send('openIfDraft');
|
||||
},
|
||||
|
||||
// Called after the preview renders. Debounced for performance
|
||||
afterRender: function() {
|
||||
var $wmdPreview = $('#wmd-preview');
|
||||
afterRender() {
|
||||
const $wmdPreview = $('#wmd-preview');
|
||||
if ($wmdPreview.length === 0) return;
|
||||
|
||||
var post = this.get('model.post'),
|
||||
refresh = false;
|
||||
const post = this.get('model.post');
|
||||
let refresh = false;
|
||||
|
||||
// If we are editing a post, we'll refresh its contents once. This is a feature that
|
||||
// allows a user to refresh its contents once.
|
||||
@ -175,17 +178,17 @@ var ComposerView = Discourse.View.extend(Ember.Evented, {
|
||||
this.trigger('previewRefreshed', $wmdPreview);
|
||||
},
|
||||
|
||||
_applyEmojiAutocomplete: function() {
|
||||
_applyEmojiAutocomplete() {
|
||||
if (!this.siteSettings.enable_emoji) { return; }
|
||||
|
||||
var template = this.container.lookup('template:emoji-selector-autocomplete.raw');
|
||||
const template = this.container.lookup('template:emoji-selector-autocomplete.raw');
|
||||
$('#wmd-input').autocomplete({
|
||||
template: template,
|
||||
key: ":",
|
||||
transformComplete: function(v){ return v.code + ":"; },
|
||||
dataSource: function(term){
|
||||
transformComplete(v) { return v.code + ":"; },
|
||||
dataSource(term){
|
||||
return new Ember.RSVP.Promise(function(resolve) {
|
||||
var full = ":" + term;
|
||||
const full = ":" + term;
|
||||
term = term.toLowerCase();
|
||||
|
||||
if (term === "") {
|
||||
@ -196,7 +199,7 @@ var ComposerView = Discourse.View.extend(Ember.Evented, {
|
||||
return resolve([Discourse.Emoji.translations[full]]);
|
||||
}
|
||||
|
||||
var options = Discourse.Emoji.search(term, {maxResults: 5});
|
||||
const options = Discourse.Emoji.search(term, {maxResults: 5});
|
||||
|
||||
return resolve(options);
|
||||
}).then(function(list) {
|
||||
@ -208,10 +211,11 @@ var ComposerView = Discourse.View.extend(Ember.Evented, {
|
||||
});
|
||||
},
|
||||
|
||||
initEditor: function() {
|
||||
initEditor() {
|
||||
// not quite right, need a callback to pass in, meaning this gets called once,
|
||||
// but if you start replying to another topic it will get the avatars wrong
|
||||
var $wmdInput, editor, self = this;
|
||||
let $wmdInput, editor;
|
||||
const self = this;
|
||||
this.wmdInput = $wmdInput = $('#wmd-input');
|
||||
if ($wmdInput.length === 0 || $wmdInput.data('init') === true) return;
|
||||
|
||||
@ -219,11 +223,11 @@ var ComposerView = Discourse.View.extend(Ember.Evented, {
|
||||
ComposerView.trigger("initWmdEditor");
|
||||
this._applyEmojiAutocomplete();
|
||||
|
||||
var template = this.container.lookup('template:user-selector-autocomplete.raw');
|
||||
const template = this.container.lookup('template:user-selector-autocomplete.raw');
|
||||
$wmdInput.data('init', true);
|
||||
$wmdInput.autocomplete({
|
||||
template: template,
|
||||
dataSource: function(term) {
|
||||
dataSource(term) {
|
||||
return userSearch({
|
||||
term: term,
|
||||
topicId: self.get('controller.controllers.topic.model.id'),
|
||||
@ -231,7 +235,7 @@ var ComposerView = Discourse.View.extend(Ember.Evented, {
|
||||
});
|
||||
},
|
||||
key: "@",
|
||||
transformComplete: function(v) {
|
||||
transformComplete(v) {
|
||||
if (v.username) {
|
||||
return v.username;
|
||||
} else {
|
||||
@ -241,10 +245,10 @@ var ComposerView = Discourse.View.extend(Ember.Evented, {
|
||||
});
|
||||
|
||||
this.editor = editor = Discourse.Markdown.createEditor({
|
||||
lookupAvatarByPostNumber: function(postNumber) {
|
||||
var posts = self.get('controller.controllers.topic.postStream.posts');
|
||||
lookupAvatarByPostNumber(postNumber) {
|
||||
const posts = self.get('controller.controllers.topic.postStream.posts');
|
||||
if (posts) {
|
||||
var quotedPost = posts.findProperty("post_number", postNumber);
|
||||
const quotedPost = posts.findProperty("post_number", postNumber);
|
||||
if (quotedPost) {
|
||||
return Discourse.Utilities.tinyAvatar(quotedPost.get("avatar_template"));
|
||||
}
|
||||
@ -273,7 +277,7 @@ var ComposerView = Discourse.View.extend(Ember.Evented, {
|
||||
this.set('editor', this.editor);
|
||||
this.loadingChanged();
|
||||
|
||||
var saveDraft = Discourse.debounce((function() {
|
||||
const saveDraft = Discourse.debounce((function() {
|
||||
return self.get('controller').saveDraft();
|
||||
}), 2000);
|
||||
|
||||
@ -282,7 +286,7 @@ var ComposerView = Discourse.View.extend(Ember.Evented, {
|
||||
return true;
|
||||
});
|
||||
|
||||
var $replyTitle = $('#reply-title');
|
||||
const $replyTitle = $('#reply-title');
|
||||
|
||||
$replyTitle.keyup(function() {
|
||||
saveDraft();
|
||||
@ -305,9 +309,9 @@ var ComposerView = Discourse.View.extend(Ember.Evented, {
|
||||
// in case it's still bound somehow
|
||||
this._unbindUploadTarget();
|
||||
|
||||
var $uploadTarget = $('#reply-control'),
|
||||
csrf = Discourse.Session.currentProp('csrfToken'),
|
||||
cancelledByTheUser;
|
||||
const $uploadTarget = $('#reply-control'),
|
||||
csrf = Discourse.Session.currentProp('csrfToken');
|
||||
let cancelledByTheUser;
|
||||
|
||||
// NOTE: we need both the .json extension and the CSRF token as a query parameter for IE9
|
||||
$uploadTarget.fileupload({
|
||||
@ -318,7 +322,7 @@ var ComposerView = Discourse.View.extend(Ember.Evented, {
|
||||
|
||||
// submit - this event is triggered for each upload
|
||||
$uploadTarget.on('fileuploadsubmit', function (e, data) {
|
||||
var result = Discourse.Utilities.validateUploadedFiles(data.files);
|
||||
const result = Discourse.Utilities.validateUploadedFiles(data.files);
|
||||
// reset upload status when everything is ok
|
||||
if (result) self.setProperties({ uploadProgress: 0, isUploading: true });
|
||||
return result;
|
||||
@ -331,7 +335,7 @@ var ComposerView = Discourse.View.extend(Ember.Evented, {
|
||||
self.get('controller').send('closeModal');
|
||||
// NOTE: IE9 doesn't support XHR
|
||||
if (data["xhr"]) {
|
||||
var jqHXR = data.xhr();
|
||||
const jqHXR = data.xhr();
|
||||
if (jqHXR) {
|
||||
// need to wait for the link to show up in the DOM
|
||||
Em.run.schedule('afterRender', function() {
|
||||
@ -351,7 +355,7 @@ var ComposerView = Discourse.View.extend(Ember.Evented, {
|
||||
|
||||
// progress all
|
||||
$uploadTarget.on('fileuploadprogressall', function (e, data) {
|
||||
var progress = parseInt(data.loaded / data.total * 100, 10);
|
||||
const progress = parseInt(data.loaded / data.total * 100, 10);
|
||||
self.set('uploadProgress', progress);
|
||||
});
|
||||
|
||||
@ -360,7 +364,7 @@ var ComposerView = Discourse.View.extend(Ember.Evented, {
|
||||
if (!cancelledByTheUser) {
|
||||
// make sure we have a url
|
||||
if (data.result.url) {
|
||||
var markdown = Discourse.Utilities.getUploadMarkdown(data.result);
|
||||
const markdown = Discourse.Utilities.getUploadMarkdown(data.result);
|
||||
// appends a space at the end of the inserted markdown
|
||||
self.addMarkdown(markdown + " ");
|
||||
self.set('isUploading', false);
|
||||
@ -385,7 +389,7 @@ var ComposerView = Discourse.View.extend(Ember.Evented, {
|
||||
// Firefox. This is pretty dangerous because it can potentially break
|
||||
// Ctrl+v to paste so we should be conservative about what browsers this runs
|
||||
// in.
|
||||
var uaMatch = navigator.userAgent.match(/Firefox\/(\d+)\.\d/);
|
||||
const uaMatch = navigator.userAgent.match(/Firefox\/(\d+)\.\d/);
|
||||
if (uaMatch && parseInt(uaMatch[1]) >= 24) {
|
||||
self.$().append( Ember.$("<div id='contenteditable' contenteditable='true' style='height: 0; width: 0; overflow: hidden'></div>") );
|
||||
self.$("textarea").off('keydown.contenteditable');
|
||||
@ -395,18 +399,18 @@ var ComposerView = Discourse.View.extend(Ember.Evented, {
|
||||
// after we switch focus, probably because it is being executed too late.
|
||||
if ((event.ctrlKey || event.metaKey) && (event.keyCode === 86)) {
|
||||
// Save the current textarea selection.
|
||||
var textarea = self.$("textarea")[0],
|
||||
const textarea = self.$("textarea")[0],
|
||||
selectionStart = textarea.selectionStart,
|
||||
selectionEnd = textarea.selectionEnd;
|
||||
|
||||
// Focus the contenteditable div.
|
||||
var contentEditableDiv = self.$('#contenteditable');
|
||||
const contentEditableDiv = self.$('#contenteditable');
|
||||
contentEditableDiv.focus();
|
||||
|
||||
// The paste doesn't finish immediately and we don't have any onpaste
|
||||
// event, so wait for 100ms which _should_ be enough time.
|
||||
setTimeout(function() {
|
||||
var pastedImg = contentEditableDiv.find('img');
|
||||
const pastedImg = contentEditableDiv.find('img');
|
||||
|
||||
if ( pastedImg.length === 1 ) {
|
||||
pastedImg.remove();
|
||||
@ -414,11 +418,11 @@ var ComposerView = Discourse.View.extend(Ember.Evented, {
|
||||
|
||||
// For restoring the selection.
|
||||
textarea.focus();
|
||||
var textareaContent = $(textarea).val(),
|
||||
const textareaContent = $(textarea).val(),
|
||||
startContent = textareaContent.substring(0, selectionStart),
|
||||
endContent = textareaContent.substring(selectionEnd);
|
||||
|
||||
var restoreSelection = function(pastedText) {
|
||||
const restoreSelection = function(pastedText) {
|
||||
$(textarea).val( startContent + pastedText + endContent );
|
||||
textarea.selectionStart = selectionStart + pastedText.length;
|
||||
textarea.selectionEnd = textarea.selectionStart;
|
||||
@ -435,20 +439,20 @@ var ComposerView = Discourse.View.extend(Ember.Evented, {
|
||||
// to a Blob and upload that, but if it is a regular URL that
|
||||
// operation is prevented for security purposes. When we get a regular
|
||||
// URL let's just create an <img> tag for the image.
|
||||
var imageSrc = pastedImg.attr('src');
|
||||
const imageSrc = pastedImg.attr('src');
|
||||
|
||||
if (imageSrc.match(/^data:image/)) {
|
||||
// Restore the cursor position, and remove any selected text.
|
||||
restoreSelection("");
|
||||
|
||||
// Create a Blob to upload.
|
||||
var image = new Image();
|
||||
const image = new Image();
|
||||
image.onload = function() {
|
||||
// Create a new canvas.
|
||||
var canvas = document.createElementNS('http://www.w3.org/1999/xhtml', 'canvas');
|
||||
const canvas = document.createElementNS('http://www.w3.org/1999/xhtml', 'canvas');
|
||||
canvas.height = image.height;
|
||||
canvas.width = image.width;
|
||||
var ctx = canvas.getContext('2d');
|
||||
const ctx = canvas.getContext('2d');
|
||||
ctx.drawImage(image, 0, 0);
|
||||
|
||||
canvas.toBlob(function(blob) {
|
||||
@ -488,8 +492,8 @@ var ComposerView = Discourse.View.extend(Ember.Evented, {
|
||||
}, 400);
|
||||
},
|
||||
|
||||
addMarkdown: function(text) {
|
||||
var ctrl = $('#wmd-input').get(0),
|
||||
addMarkdown(text) {
|
||||
const ctrl = $('#wmd-input').get(0),
|
||||
caretPosition = Discourse.Utilities.caretPosition(ctrl),
|
||||
current = this.get('model.reply');
|
||||
this.set('model.reply', current.substring(0, caretPosition) + text + current.substring(caretPosition, current.length));
|
||||
@ -500,10 +504,10 @@ var ComposerView = Discourse.View.extend(Ember.Evented, {
|
||||
},
|
||||
|
||||
// Uses javascript to get the image sizes from the preview, if present
|
||||
imageSizes: function() {
|
||||
var result = {};
|
||||
imageSizes() {
|
||||
const result = {};
|
||||
$('#wmd-preview img').each(function(i, e) {
|
||||
var $img = $(e),
|
||||
const $img = $(e),
|
||||
src = $img.prop('src');
|
||||
|
||||
if (src && src.length) {
|
||||
@ -513,12 +517,12 @@ var ComposerView = Discourse.View.extend(Ember.Evented, {
|
||||
return result;
|
||||
},
|
||||
|
||||
childDidInsertElement: function() {
|
||||
childDidInsertElement() {
|
||||
return this.initEditor();
|
||||
},
|
||||
|
||||
childWillDestroyElement: function() {
|
||||
var self = this;
|
||||
childWillDestroyElement() {
|
||||
const self = this;
|
||||
|
||||
this._unbindUploadTarget();
|
||||
|
||||
@ -532,9 +536,9 @@ var ComposerView = Discourse.View.extend(Ember.Evented, {
|
||||
},
|
||||
|
||||
titleValidation: function() {
|
||||
var titleLength = this.get('model.titleLength'),
|
||||
missingChars = this.get('model.missingTitleCharacters'),
|
||||
reason;
|
||||
const titleLength = this.get('model.titleLength'),
|
||||
missingChars = this.get('model.missingTitleCharacters');
|
||||
let reason;
|
||||
if( titleLength < 1 ){
|
||||
reason = I18n.t('composer.error.title_missing');
|
||||
} else if( missingChars > 0 ) {
|
||||
@ -555,9 +559,9 @@ var ComposerView = Discourse.View.extend(Ember.Evented, {
|
||||
}.property('model.categoryId'),
|
||||
|
||||
replyValidation: function() {
|
||||
var replyLength = this.get('model.replyLength'),
|
||||
missingChars = this.get('model.missingReplyCharacters'),
|
||||
reason;
|
||||
const replyLength = this.get('model.replyLength'),
|
||||
missingChars = this.get('model.missingReplyCharacters');
|
||||
let reason;
|
||||
if( replyLength < 1 ){
|
||||
reason = I18n.t('composer.error.post_missing');
|
||||
} else if( missingChars > 0 ) {
|
||||
@ -569,8 +573,8 @@ var ComposerView = Discourse.View.extend(Ember.Evented, {
|
||||
}
|
||||
}.property('model.reply', 'model.replyLength', 'model.missingReplyCharacters', 'model.minimumPostLength'),
|
||||
|
||||
_unbindUploadTarget: function() {
|
||||
var $uploadTarget = $('#reply-control');
|
||||
_unbindUploadTarget() {
|
||||
const $uploadTarget = $('#reply-control');
|
||||
try { $uploadTarget.fileupload('destroy'); }
|
||||
catch (e) { /* wasn't initialized yet */ }
|
||||
$uploadTarget.off();
|
||||
|
||||
@ -1,26 +1,11 @@
|
||||
export default Ember.ContainerView.extend(Discourse.Presence, {
|
||||
|
||||
/**
|
||||
Attaches a view and wires up the container properly
|
||||
|
||||
@method attachViewWithArgs
|
||||
@param {Object} viewArgs The arguments to pass when creating the view
|
||||
@param {Class} viewClass The view class we want to create
|
||||
**/
|
||||
attachViewWithArgs: function(viewArgs, viewClass) {
|
||||
attachViewWithArgs(viewArgs, viewClass) {
|
||||
if (!viewClass) { viewClass = Ember.View.extend(); }
|
||||
var view = this.createChildView(viewClass, viewArgs);
|
||||
this.pushObject(view);
|
||||
this.pushObject(this.createChildView(viewClass, viewArgs));
|
||||
},
|
||||
|
||||
/**
|
||||
Attaches a view with no arguments and wires up the container properly
|
||||
|
||||
@method attachViewClass
|
||||
@param {Class} viewClass The view class we want to add
|
||||
**/
|
||||
attachViewClass: function(viewClass) {
|
||||
attachViewClass(viewClass) {
|
||||
this.attachViewWithArgs(null, viewClass);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
@ -11,7 +11,7 @@ export default Ember.Object.extend({
|
||||
|
||||
title: function() {
|
||||
return I18n.messageFormat('posts_likes_MF', {
|
||||
count: this.get('topic.posts_count'),
|
||||
count: this.get('topic.replyCount'),
|
||||
ratio: this.get('ratioText')
|
||||
}).trim();
|
||||
}.property(),
|
||||
|
||||
@ -48,12 +48,12 @@ var PostView = Discourse.GroupedView.extend(Ember.Evented, {
|
||||
Em.run.scheduleOnce('afterRender', this, '_cookedWasChanged');
|
||||
}.observes('post.cooked'),
|
||||
|
||||
_cookedWasChanged: function() {
|
||||
_cookedWasChanged() {
|
||||
this.trigger('postViewUpdated', this.$());
|
||||
this._insertQuoteControls();
|
||||
},
|
||||
|
||||
mouseUp: function(e) {
|
||||
mouseUp(e) {
|
||||
if (this.get('controller.multiSelect') && (e.metaKey || e.ctrlKey)) {
|
||||
this.get('controller').toggledSelectedPost(this.get('post'));
|
||||
}
|
||||
@ -74,7 +74,7 @@ var PostView = Discourse.GroupedView.extend(Ember.Evented, {
|
||||
|
||||
repliesShown: Em.computed.gt('post.replies.length', 0),
|
||||
|
||||
_updateQuoteElements: function($aside, desc) {
|
||||
_updateQuoteElements($aside, desc) {
|
||||
var navLink = "",
|
||||
quoteTitle = I18n.t("post.follow_quote"),
|
||||
postNumber = $aside.data('post');
|
||||
@ -108,7 +108,7 @@ var PostView = Discourse.GroupedView.extend(Ember.Evented, {
|
||||
$('.quote-controls', $aside).html(expandContract + navLink);
|
||||
},
|
||||
|
||||
_toggleQuote: function($aside) {
|
||||
_toggleQuote($aside) {
|
||||
if (this.get('expanding')) { return; }
|
||||
this.set('expanding', true);
|
||||
|
||||
@ -151,23 +151,29 @@ var PostView = Discourse.GroupedView.extend(Ember.Evented, {
|
||||
},
|
||||
|
||||
// Show how many times links have been clicked on
|
||||
_showLinkCounts: function() {
|
||||
var self = this,
|
||||
link_counts = this.get('post.link_counts');
|
||||
_showLinkCounts() {
|
||||
const self = this,
|
||||
link_counts = this.get('post.link_counts');
|
||||
|
||||
if (!link_counts) return;
|
||||
if (!link_counts) { return; }
|
||||
|
||||
link_counts.forEach(function(lc) {
|
||||
if (!lc.clicks || lc.clicks < 1) return;
|
||||
if (!lc.clicks || lc.clicks < 1) { return; }
|
||||
|
||||
self.$(".cooked a[href]").each(function() {
|
||||
var link = $(this);
|
||||
if ((!lc.internal || lc.url[0] === "/") && link.attr('href') === lc.url) {
|
||||
// don't display badge counts on category badge
|
||||
if (link.closest('.badge-category').length === 0 && ((link.closest(".onebox-result").length === 0 && link.closest('.onebox-body').length === 0) || link.hasClass("track-link"))) {
|
||||
link.append("<span class='badge badge-notification clicks' title='" +
|
||||
I18n.t("topic_map.clicks", {count: lc.clicks}) +
|
||||
"'>" + Discourse.Formatter.number(lc.clicks) + "</span>");
|
||||
const $link = $(this),
|
||||
href = $link.attr('href');
|
||||
|
||||
let valid = !lc.internal && href === lc.url;
|
||||
|
||||
// this might be an attachment
|
||||
if (lc.internal) { valid = href.indexOf(lc.url) >= 0; }
|
||||
|
||||
if (valid) {
|
||||
// don't display badge counts on category badge & oneboxes (unless when explicitely stated)
|
||||
if ($link.hasClass("track-link") ||
|
||||
$link.closest('.badge-category,.onebox-result,.onebox-body').length === 0) {
|
||||
$link.append("<span class='badge badge-notification clicks' title='" + I18n.t("topic_map.clicks", {count: lc.clicks}) + "'>" + Discourse.Formatter.number(lc.clicks) + "</span>");
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -176,7 +182,7 @@ var PostView = Discourse.GroupedView.extend(Ember.Evented, {
|
||||
|
||||
actions: {
|
||||
// Toggle the replies this post is a reply to
|
||||
toggleReplyHistory: function(post) {
|
||||
toggleReplyHistory(post) {
|
||||
|
||||
var replyHistory = post.get('replyHistory'),
|
||||
topicController = this.get('controller'),
|
||||
@ -227,7 +233,7 @@ var PostView = Discourse.GroupedView.extend(Ember.Evented, {
|
||||
},
|
||||
|
||||
// Add the quote controls to a post
|
||||
_insertQuoteControls: function() {
|
||||
_insertQuoteControls() {
|
||||
var self = this,
|
||||
$quotes = this.$('aside.quote');
|
||||
|
||||
|
||||
@ -5,23 +5,24 @@ import BookmarkButton from 'discourse/views/bookmark-button';
|
||||
import ShareButton from 'discourse/views/share-button';
|
||||
import InviteReplyButton from 'discourse/views/invite-reply-button';
|
||||
import ReplyButton from 'discourse/views/reply-button';
|
||||
import PinnedButton from 'discourse/views/pinned-button';
|
||||
import TopicNotificationsButton from 'discourse/views/topic-notifications-button';
|
||||
import PinnedButton from 'discourse/components/pinned-button';
|
||||
import TopicNotificationsButton from 'discourse/components/topic-notifications-button';
|
||||
import DiscourseContainerView from 'discourse/views/container';
|
||||
|
||||
export default DiscourseContainerView.extend({
|
||||
elementId: 'topic-footer-buttons',
|
||||
topicBinding: 'controller.content',
|
||||
|
||||
init: function() {
|
||||
init() {
|
||||
this._super();
|
||||
this.createButtons();
|
||||
},
|
||||
|
||||
// Add the buttons below a topic
|
||||
createButtons: function() {
|
||||
var topic = this.get('topic');
|
||||
createButtons() {
|
||||
const topic = this.get('topic');
|
||||
if (Discourse.User.current()) {
|
||||
const viewArgs = {topic};
|
||||
if (Discourse.User.currentProp("staff")) {
|
||||
this.attachViewClass(TopicAdminMenuButton);
|
||||
}
|
||||
@ -39,8 +40,8 @@ export default DiscourseContainerView.extend({
|
||||
if (this.get('topic.details.can_create_post')) {
|
||||
this.attachViewClass(ReplyButton);
|
||||
}
|
||||
this.attachViewClass(PinnedButton);
|
||||
this.attachViewClass(TopicNotificationsButton);
|
||||
this.attachViewWithArgs(viewArgs, PinnedButton);
|
||||
this.attachViewWithArgs(viewArgs, TopicNotificationsButton);
|
||||
|
||||
this.trigger('additionalButtons', this);
|
||||
} else {
|
||||
|
||||
@ -1,20 +0,0 @@
|
||||
import NotificationsButton from 'discourse/views/notifications-button';
|
||||
|
||||
export default NotificationsButton.extend({
|
||||
longDescriptionBinding: 'topic.details.notificationReasonText',
|
||||
topic: Em.computed.alias('controller.model'),
|
||||
target: Em.computed.alias('topic'),
|
||||
hidden: Em.computed.alias('topic.deleted'),
|
||||
notificationLevels: Discourse.Topic.NotificationLevel,
|
||||
notificationLevel: Em.computed.alias('topic.details.notification_level'),
|
||||
isPrivateMessage: Em.computed.alias('topic.isPrivateMessage'),
|
||||
i18nPrefix: 'topic.notifications',
|
||||
|
||||
i18nPostfix: function() {
|
||||
return this.get('isPrivateMessage') ? '_pm' : '';
|
||||
}.property('isPrivateMessage'),
|
||||
|
||||
clicked: function(id) {
|
||||
this.get('topic.details').updateNotifications(id);
|
||||
}
|
||||
});
|
||||
@ -51,8 +51,8 @@ var TopicView = Discourse.View.extend(AddCategoryClass, Discourse.Scrolling, {
|
||||
|
||||
var $target = $(e.target);
|
||||
if ($target.hasClass('mention') || $target.parents('.expanded-embed').length) { return false; }
|
||||
return Discourse.ClickTrack.trackClick(e);
|
||||
|
||||
return Discourse.ClickTrack.trackClick(e);
|
||||
});
|
||||
|
||||
}.on('didInsertElement'),
|
||||
@ -126,7 +126,7 @@ var TopicView = Discourse.View.extend(AddCategoryClass, Discourse.Scrolling, {
|
||||
var opts = { latestLink: "<a href=\"" + Discourse.getURL("/latest") + "\">" + I18n.t("topic.view_latest_topics") + "</a>" },
|
||||
category = this.get('controller.content.category');
|
||||
|
||||
if(Em.get(category, 'id') === Discourse.Site.currentProp("uncategorized_category_id")) {
|
||||
if(category && Em.get(category, 'id') === Discourse.Site.currentProp("uncategorized_category_id")) {
|
||||
category = null;
|
||||
}
|
||||
|
||||
|
||||
@ -6,7 +6,7 @@ var clickOutsideEventName = "mousedown.outside-user-card",
|
||||
|
||||
export default Discourse.View.extend(CleansUp, {
|
||||
elementId: 'user-card',
|
||||
classNameBindings: ['controller.visible::hidden', 'controller.showBadges', 'controller.hasCardBadgeImage'],
|
||||
classNameBindings: ['controller.visible:show', 'controller.showBadges', 'controller.hasCardBadgeImage'],
|
||||
allowBackgrounds: Discourse.computed.setting('allow_profile_backgrounds'),
|
||||
|
||||
addBackground: function() {
|
||||
|
||||
@ -522,7 +522,7 @@ I18n.enable_verbose_localization = function(){
|
||||
if (!_.isEmpty(value)) {
|
||||
message += ", parameters: " + JSON.stringify(value);
|
||||
}
|
||||
//window.console.log(message);
|
||||
Em.Logger.info(message);
|
||||
}
|
||||
return t.apply(I18n, [scope, value]) + " (t" + current + ")";
|
||||
};
|
||||
|
||||
@ -10,6 +10,7 @@
|
||||
//
|
||||
|
||||
// Stuff we need to load first
|
||||
//= require ./discourse/lib/notification-levels
|
||||
//= require ./discourse/lib/app-events
|
||||
//= require ./discourse/helpers/i18n
|
||||
//= require ./discourse/helpers/fa-icon
|
||||
@ -42,9 +43,9 @@
|
||||
//= require ./discourse/views/flag
|
||||
//= require ./discourse/views/combo-box
|
||||
//= require ./discourse/views/button
|
||||
//= require ./discourse/views/dropdown-button
|
||||
//= require ./discourse/views/notifications-button
|
||||
//= require ./discourse/views/topic-notifications-button
|
||||
//= require ./discourse/components/dropdown-button
|
||||
//= require ./discourse/components/notifications-button
|
||||
//= require ./discourse/components/topic-notifications-button
|
||||
//= require ./discourse/views/pagedown-preview
|
||||
//= require ./discourse/views/composer
|
||||
//= require ./discourse/routes/discourse_route
|
||||
|
||||
@ -1440,3 +1440,9 @@ tr.not-activated {
|
||||
.preview {
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
table#user-badges {
|
||||
.reason {
|
||||
max-width: 200px;
|
||||
}
|
||||
}
|
||||
|
||||
@ -159,7 +159,7 @@
|
||||
.badge-category {
|
||||
padding: 4px 10px;
|
||||
display: inline-block;
|
||||
line-height: 24px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.category-dropdown-menu .badge-category {
|
||||
|
||||
@ -6,13 +6,43 @@ section.about {
|
||||
}
|
||||
|
||||
.user-small {
|
||||
padding: 5px;
|
||||
width: 200px;
|
||||
padding: 8px;
|
||||
width: 205px;
|
||||
height: 60px;
|
||||
float: left;
|
||||
|
||||
img {
|
||||
.user-image {
|
||||
float: left;
|
||||
padding-right: 4px;
|
||||
}
|
||||
|
||||
.user-detail {
|
||||
float: left;
|
||||
width: 70%;
|
||||
|
||||
span {
|
||||
float: left;
|
||||
width: 90%;
|
||||
padding-left: 5px;
|
||||
}
|
||||
|
||||
.username a {
|
||||
font-weight: bold;
|
||||
font-size: 16px;
|
||||
color: scale-color($primary, $lightness: 30%);
|
||||
}
|
||||
|
||||
.name {
|
||||
font-size: 13px;
|
||||
color: scale-color($primary, $lightness: 30%);
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 13px;
|
||||
color: scale-color($primary, $lightness: 50%);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
p {
|
||||
|
||||
@ -651,15 +651,15 @@ button {
|
||||
/* start state */
|
||||
.mfp-content {
|
||||
opacity: 0;
|
||||
transition: all 0.2s ease-in-out;
|
||||
-webkit-transform: scale(0.8);
|
||||
-ms-transform: scale(0.8);
|
||||
transform: scale(0.8);
|
||||
transition: all .2s;
|
||||
-webkit-transform: scale(.8);
|
||||
-ms-transform: scale(.8);
|
||||
transform: scale(.8);
|
||||
}
|
||||
|
||||
&.mfp-bg {
|
||||
opacity: 0;
|
||||
transition: all 0.3s ease-out;
|
||||
transition: all .3s ease-out;
|
||||
}
|
||||
|
||||
/* animate in */
|
||||
@ -679,9 +679,9 @@ button {
|
||||
&.mfp-removing {
|
||||
|
||||
.mfp-content {
|
||||
-webkit-transform: scale(0.8);
|
||||
-ms-transform: scale(0.8);
|
||||
transform: scale(0.8);
|
||||
-webkit-transform: scale(.8);
|
||||
-ms-transform: scale(.8);
|
||||
transform: scale(.8);
|
||||
opacity: 0;
|
||||
}
|
||||
&.mfp-bg {
|
||||
|
||||
@ -179,7 +179,11 @@ kbd
|
||||
margin: 0 .1em;
|
||||
padding: .1em .6em;
|
||||
|
||||
* * { display: none; }
|
||||
// don't allow more than 3 nested elements to prevent FF from crashing
|
||||
// cf. http://what.thedailywtf.com/t/nested-elements/7927
|
||||
// 3 levels are needed to prevent highlighted words being hidden
|
||||
// cf. https://meta.discourse.org/t/word-disappears-when-searched-and-in-details-summary-kbd-b/25741
|
||||
* * * { display: none; }
|
||||
}
|
||||
|
||||
// we assume blockquotes have their own margins, so all blockquotes
|
||||
|
||||
@ -159,3 +159,8 @@ table.badges-listing {
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
|
||||
.long-description.banner {
|
||||
width: 88%;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
@ -132,12 +132,21 @@ header .title-wrapper .bar .badge-category {
|
||||
|
||||
.category-breadcrumb li.bar > .badge-category {
|
||||
background: dark-light-diff($primary, $secondary, 95%, -65%) !important;
|
||||
line-height: 24px;
|
||||
&:not(.home):first-child {
|
||||
border-left-width: 5px;
|
||||
border-left-style: solid;
|
||||
}
|
||||
}
|
||||
|
||||
.category-breadcrumb .box > a.badge-category {
|
||||
margin-bottom: 0;
|
||||
height: 24px;
|
||||
// TODO clean this up
|
||||
padding-top: 6px !important;
|
||||
padding-bottom: 0 !important;
|
||||
}
|
||||
|
||||
.category-dropdown-menu .cat .badge-wrapper.box {
|
||||
width: 110%;
|
||||
}
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
// Banner
|
||||
// --------------------------------------------------
|
||||
|
||||
#banner {
|
||||
#banner, .banner {
|
||||
padding: 10px;
|
||||
border-radius: 5px;
|
||||
background: scale-color($tertiary, $lightness: 90%);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user