Version bump
This commit is contained in:
commit
1b62a7e2a1
@ -7,7 +7,7 @@ app/assets/javascripts/vendor.js
|
||||
app/assets/javascripts/locales/i18n.js
|
||||
app/assets/javascripts/defer/html-sanitizer-bundle.js
|
||||
app/assets/javascripts/ember-addons/
|
||||
app/assets/javascripts/admin/lib/autosize.js.es6
|
||||
app/assets/javascripts/discourse/lib/autosize.js.es6
|
||||
lib/javascripts/locale/
|
||||
lib/javascripts/messageformat.js
|
||||
lib/javascripts/moment.js
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@ -58,6 +58,9 @@ log/
|
||||
# Ignore Eclipse .buildpath file
|
||||
/.buildpath
|
||||
|
||||
# Ignore byebug history
|
||||
/.byebug_history
|
||||
|
||||
# Ignore RubyMine settings
|
||||
/.idea
|
||||
|
||||
|
||||
2
Gemfile
2
Gemfile
@ -66,7 +66,7 @@ gem 'aws-sdk', require: false
|
||||
gem 'excon', require: false
|
||||
gem 'unf', require: false
|
||||
|
||||
gem 'email_reply_trimmer', '0.1.1'
|
||||
gem 'email_reply_trimmer', '0.1.2'
|
||||
|
||||
# note: for image_optim to correctly work you need to follow
|
||||
# https://github.com/toy/image_optim
|
||||
|
||||
75
Gemfile.lock
75
Gemfile.lock
@ -1,38 +1,38 @@
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
actionmailer (4.2.5.2)
|
||||
actionpack (= 4.2.5.2)
|
||||
actionview (= 4.2.5.2)
|
||||
activejob (= 4.2.5.2)
|
||||
actionmailer (4.2.6)
|
||||
actionpack (= 4.2.6)
|
||||
actionview (= 4.2.6)
|
||||
activejob (= 4.2.6)
|
||||
mail (~> 2.5, >= 2.5.4)
|
||||
rails-dom-testing (~> 1.0, >= 1.0.5)
|
||||
actionpack (4.2.5.2)
|
||||
actionview (= 4.2.5.2)
|
||||
activesupport (= 4.2.5.2)
|
||||
actionpack (4.2.6)
|
||||
actionview (= 4.2.6)
|
||||
activesupport (= 4.2.6)
|
||||
rack (~> 1.6)
|
||||
rack-test (~> 0.6.2)
|
||||
rails-dom-testing (~> 1.0, >= 1.0.5)
|
||||
rails-html-sanitizer (~> 1.0, >= 1.0.2)
|
||||
actionview (4.2.5.2)
|
||||
activesupport (= 4.2.5.2)
|
||||
actionview (4.2.6)
|
||||
activesupport (= 4.2.6)
|
||||
builder (~> 3.1)
|
||||
erubis (~> 2.7.0)
|
||||
rails-dom-testing (~> 1.0, >= 1.0.5)
|
||||
rails-html-sanitizer (~> 1.0, >= 1.0.2)
|
||||
active_model_serializers (0.8.3)
|
||||
activemodel (>= 3.0)
|
||||
activejob (4.2.5.2)
|
||||
activesupport (= 4.2.5.2)
|
||||
activejob (4.2.6)
|
||||
activesupport (= 4.2.6)
|
||||
globalid (>= 0.3.0)
|
||||
activemodel (4.2.5.2)
|
||||
activesupport (= 4.2.5.2)
|
||||
activemodel (4.2.6)
|
||||
activesupport (= 4.2.6)
|
||||
builder (~> 3.1)
|
||||
activerecord (4.2.5.2)
|
||||
activemodel (= 4.2.5.2)
|
||||
activesupport (= 4.2.5.2)
|
||||
activerecord (4.2.6)
|
||||
activemodel (= 4.2.6)
|
||||
activesupport (= 4.2.6)
|
||||
arel (~> 6.0)
|
||||
activesupport (4.2.5.2)
|
||||
activesupport (4.2.6)
|
||||
i18n (~> 0.7)
|
||||
json (~> 1.7, >= 1.7.7)
|
||||
minitest (~> 5.1)
|
||||
@ -76,7 +76,7 @@ GEM
|
||||
docile (1.1.5)
|
||||
domain_name (0.5.25)
|
||||
unf (>= 0.0.5, < 1.0.0)
|
||||
email_reply_trimmer (0.1.1)
|
||||
email_reply_trimmer (0.1.2)
|
||||
ember-data-source (1.0.0.beta.16.1)
|
||||
ember-source (~> 1.8)
|
||||
ember-handlebars-template (0.1.5)
|
||||
@ -150,12 +150,12 @@ GEM
|
||||
thor (~> 0.15)
|
||||
libv8 (3.16.14.13)
|
||||
listen (0.7.3)
|
||||
logster (1.1.1)
|
||||
logster (1.2.2)
|
||||
loofah (2.0.3)
|
||||
nokogiri (>= 1.5.9)
|
||||
lru_redux (1.1.0)
|
||||
mail (2.6.3)
|
||||
mime-types (>= 1.16, < 3)
|
||||
mail (2.6.4)
|
||||
mime-types (>= 1.16, < 4)
|
||||
memory_profiler (0.9.6)
|
||||
message_bus (2.0.0.beta.5)
|
||||
rack (>= 1.1.3)
|
||||
@ -172,7 +172,7 @@ GEM
|
||||
multi_json (1.11.2)
|
||||
multi_xml (0.5.5)
|
||||
multipart-post (2.0.0)
|
||||
mustache (1.0.2)
|
||||
mustache (1.0.3)
|
||||
netrc (0.11.0)
|
||||
nokogiri (1.6.7.2)
|
||||
mini_portile2 (~> 2.0.0.rc2)
|
||||
@ -214,7 +214,8 @@ GEM
|
||||
omniauth-twitter (1.2.1)
|
||||
json (~> 1.3)
|
||||
omniauth-oauth (~> 1.1)
|
||||
onebox (1.5.35)
|
||||
onebox (1.5.37)
|
||||
htmlentities (~> 4.3.4)
|
||||
moneta (~> 0.8)
|
||||
multi_json (~> 1.11)
|
||||
mustache
|
||||
@ -232,7 +233,7 @@ GEM
|
||||
pry (>= 0.9.10, < 0.11.0)
|
||||
pry-rails (0.3.4)
|
||||
pry (>= 0.9.10)
|
||||
puma (2.15.3)
|
||||
puma (3.2.0)
|
||||
r2 (0.2.6)
|
||||
rack (1.6.4)
|
||||
rack-mini-profiler (0.9.9.2)
|
||||
@ -244,16 +245,16 @@ GEM
|
||||
rack
|
||||
rack-test (0.6.3)
|
||||
rack (>= 1.0)
|
||||
rails (4.2.5.2)
|
||||
actionmailer (= 4.2.5.2)
|
||||
actionpack (= 4.2.5.2)
|
||||
actionview (= 4.2.5.2)
|
||||
activejob (= 4.2.5.2)
|
||||
activemodel (= 4.2.5.2)
|
||||
activerecord (= 4.2.5.2)
|
||||
activesupport (= 4.2.5.2)
|
||||
rails (4.2.6)
|
||||
actionmailer (= 4.2.6)
|
||||
actionpack (= 4.2.6)
|
||||
actionview (= 4.2.6)
|
||||
activejob (= 4.2.6)
|
||||
activemodel (= 4.2.6)
|
||||
activerecord (= 4.2.6)
|
||||
activesupport (= 4.2.6)
|
||||
bundler (>= 1.3.0, < 2.0)
|
||||
railties (= 4.2.5.2)
|
||||
railties (= 4.2.6)
|
||||
sprockets-rails
|
||||
rails-deprecated_sanitizer (1.0.3)
|
||||
activesupport (>= 4.2.0.alpha)
|
||||
@ -266,9 +267,9 @@ GEM
|
||||
rails-observers (0.1.2)
|
||||
activemodel (~> 4.0)
|
||||
rails_multisite (1.0.3)
|
||||
railties (4.2.5.2)
|
||||
actionpack (= 4.2.5.2)
|
||||
activesupport (= 4.2.5.2)
|
||||
railties (4.2.6)
|
||||
actionpack (= 4.2.6)
|
||||
activesupport (= 4.2.6)
|
||||
rake (>= 0.8.7)
|
||||
thor (>= 0.18.1, < 2.0)
|
||||
raindrops (0.15.0)
|
||||
@ -414,7 +415,7 @@ DEPENDENCIES
|
||||
byebug
|
||||
certified
|
||||
discourse-qunit-rails
|
||||
email_reply_trimmer (= 0.1.1)
|
||||
email_reply_trimmer (= 0.1.2)
|
||||
ember-rails
|
||||
ember-source (= 1.12.2)
|
||||
excon
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { on, observes } from 'ember-addons/ember-computed-decorators';
|
||||
import autosize from 'admin/lib/autosize';
|
||||
import autosize from 'discourse/lib/autosize';
|
||||
|
||||
export default Ember.TextArea.extend({
|
||||
@on('didInsertElement')
|
||||
|
||||
@ -14,7 +14,6 @@ export default Ember.Controller.extend(BufferedContent, {
|
||||
|
||||
readOnly: Ember.computed.alias('buffered.system'),
|
||||
showDisplayName: propertyNotEqual('name', 'displayName'),
|
||||
canEditDescription: Em.computed.none('buffered.translatedDescription'),
|
||||
|
||||
hasQuery: function() {
|
||||
const bQuery = this.get('buffered.query');
|
||||
@ -37,6 +36,7 @@ export default Ember.Controller.extend(BufferedContent, {
|
||||
'listable', 'auto_revoke',
|
||||
'enabled', 'show_posts',
|
||||
'target_posts', 'name', 'description',
|
||||
'long_description',
|
||||
'icon', 'image', 'query', 'badge_grouping_id',
|
||||
'trigger', 'badge_type_id'],
|
||||
self = this;
|
||||
|
||||
@ -1,11 +1,21 @@
|
||||
import IncomingEmail from 'admin/models/incoming-email';
|
||||
|
||||
export default Ember.Controller.extend({
|
||||
loadMore() {
|
||||
return IncomingEmail.findAll(this.get("filter"), this.get("model.length"))
|
||||
.then(incoming => {
|
||||
if (incoming.length < 50) { this.get("model").set("allLoaded", true); }
|
||||
this.get("model").addObjects(incoming);
|
||||
});
|
||||
loading: false,
|
||||
|
||||
actions: {
|
||||
|
||||
loadMore() {
|
||||
if (this.get("loading") || this.get("model.allLoaded")) { return; }
|
||||
this.set('loading', true);
|
||||
|
||||
IncomingEmail.findAll(this.get("filter"), this.get("model.length"))
|
||||
.then(incoming => {
|
||||
if (incoming.length < 50) { this.get("model").set("allLoaded", true); }
|
||||
this.get("model").addObjects(incoming);
|
||||
}).finally(() => {
|
||||
this.set('loading', false);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@ -1,11 +1,20 @@
|
||||
import EmailLog from 'admin/models/email-log';
|
||||
|
||||
export default Ember.Controller.extend({
|
||||
loadMore() {
|
||||
return EmailLog.findAll(this.get("filter"), this.get("model.length"))
|
||||
.then(logs => {
|
||||
if (logs.length < 50) { this.get("model").set("allLoaded", true); }
|
||||
this.get("model").addObjects(logs);
|
||||
});
|
||||
loading: false,
|
||||
|
||||
actions: {
|
||||
loadMore() {
|
||||
if (this.get("loading") || this.get("model.allLoaded")) { return; }
|
||||
|
||||
this.set('loading', true);
|
||||
return EmailLog.findAll(this.get("filter"), this.get("model.length"))
|
||||
.then(logs => {
|
||||
if (logs.length < 50) { this.get("model").set("allLoaded", true); }
|
||||
this.get("model").addObjects(logs);
|
||||
}).finally(() => {
|
||||
this.set('loading', false);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@ -61,7 +61,7 @@ export default Ember.ArrayController.extend({
|
||||
}
|
||||
});
|
||||
|
||||
return _.sortBy(badges, badge => badge.get('displayName'));
|
||||
return _.sortBy(badges, badge => badge.get('name'));
|
||||
}.property('badges.@each', 'model.@each'),
|
||||
|
||||
/**
|
||||
|
||||
@ -44,10 +44,19 @@
|
||||
|
||||
<div>
|
||||
<label for="description">{{i18n 'admin.badges.description'}}</label>
|
||||
{{#if canEditDescription}}
|
||||
{{textarea name="description" value=buffered.description}}
|
||||
{{else}}
|
||||
{{#if buffered.system}}
|
||||
{{textarea name="description" value=buffered.displayDescription disabled=true}}
|
||||
{{else}}
|
||||
{{textarea name="description" value=buffered.description}}
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="long_description">{{i18n 'admin.badges.long_description'}}</label>
|
||||
{{#if buffered.system}}
|
||||
{{textarea name="long_description" value=buffered.long_description disabled=true}}
|
||||
{{else}}
|
||||
{{textarea name="long_description" value=buffered.long_description}}
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
|
||||
@ -1,55 +1,57 @@
|
||||
<table class='table email-list'>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{i18n 'admin.email.time'}}</th>
|
||||
<th>{{i18n 'admin.email.incoming_emails.from_address'}}</th>
|
||||
<th>{{i18n 'admin.email.incoming_emails.to_addresses'}}</th>
|
||||
<th>{{i18n 'admin.email.incoming_emails.subject'}}</th>
|
||||
{{#load-more selector=".email-list tr" action="loadMore"}}
|
||||
<table class='table email-list'>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{i18n 'admin.email.time'}}</th>
|
||||
<th>{{i18n 'admin.email.incoming_emails.from_address'}}</th>
|
||||
<th>{{i18n 'admin.email.incoming_emails.to_addresses'}}</th>
|
||||
<th>{{i18n 'admin.email.incoming_emails.subject'}}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tr class="filters">
|
||||
<td>{{i18n 'admin.email.logs.filters.title'}}</td>
|
||||
<td>{{text-field value=filter.from placeholderKey="admin.email.incoming_emails.filters.from_placeholder"}}</td>
|
||||
<td>{{text-field value=filter.to placeholderKey="admin.email.incoming_emails.filters.to_placeholder"}}</td>
|
||||
<td>{{text-field value=filter.subject placeholderKey="admin.email.incoming_emails.filters.subject_placeholder"}}</td>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tr class="filters">
|
||||
<td>{{i18n 'admin.email.logs.filters.title'}}</td>
|
||||
<td>{{text-field value=filter.from placeholderKey="admin.email.incoming_emails.filters.from_placeholder"}}</td>
|
||||
<td>{{text-field value=filter.to placeholderKey="admin.email.incoming_emails.filters.to_placeholder"}}</td>
|
||||
<td>{{text-field value=filter.subject placeholderKey="admin.email.incoming_emails.filters.subject_placeholder"}}</td>
|
||||
</tr>
|
||||
|
||||
{{#each email in model}}
|
||||
<tr>
|
||||
<td class="time">{{format-date email.created_at}}</td>
|
||||
<td class="username">
|
||||
<div>
|
||||
{{#if email.user}}
|
||||
{{#link-to 'adminUser' email.user}}
|
||||
{{avatar email.user imageSize="tiny"}}
|
||||
{{email.from_address}}
|
||||
{{/link-to}}
|
||||
{{#each email in model}}
|
||||
<tr>
|
||||
<td class="time">{{format-date email.created_at}}</td>
|
||||
<td class="username">
|
||||
<div>
|
||||
{{#if email.user}}
|
||||
{{#link-to 'adminUser' email.user}}
|
||||
{{avatar email.user imageSize="tiny"}}
|
||||
{{email.from_address}}
|
||||
{{/link-to}}
|
||||
{{else}}
|
||||
—
|
||||
{{/if}}
|
||||
</div>
|
||||
</td>
|
||||
<td class="addresses">
|
||||
{{#each to in email.to_addresses}}
|
||||
<p><a href="mailto:{{unbound to}}" title="TO">{{unbound to}}</a></p>
|
||||
{{/each}}
|
||||
{{#each cc in email.cc_addresses}}
|
||||
<p><a href="mailto:{{unbound cc}}" title="CC">{{unbound cc}}</a></p>
|
||||
{{/each}}
|
||||
</td>
|
||||
<td>
|
||||
{{#if email.post_url}}
|
||||
<a href="{{email.post_url}}">{{email.subject}}</a>
|
||||
{{else}}
|
||||
—
|
||||
{{email.subject}}
|
||||
{{/if}}
|
||||
</div>
|
||||
</td>
|
||||
<td class="addresses">
|
||||
{{#each to in email.to_addresses}}
|
||||
<p><a href="mailto:{{unbound to}}" title="TO">{{unbound to}}</a></p>
|
||||
{{/each}}
|
||||
{{#each cc in email.cc_addresses}}
|
||||
<p><a href="mailto:{{unbound cc}}" title="CC">{{unbound cc}}</a></p>
|
||||
{{/each}}
|
||||
</td>
|
||||
<td>
|
||||
{{#if email.post_url}}
|
||||
<a href="{{email.post_url}}">{{email.subject}}</a>
|
||||
{{else}}
|
||||
{{email.subject}}
|
||||
{{/if}}
|
||||
</td>
|
||||
</tr>
|
||||
{{else}}
|
||||
<tr><td colspan="4">{{i18n 'admin.email.incoming_emails.none'}}</td></tr>
|
||||
{{/each}}
|
||||
</td>
|
||||
</tr>
|
||||
{{else}}
|
||||
<tr><td colspan="4">{{i18n 'admin.email.incoming_emails.none'}}</td></tr>
|
||||
{{/each}}
|
||||
|
||||
</table>
|
||||
</table>
|
||||
{{/load-more}}
|
||||
|
||||
{{conditional-loading-spinner condition=view.loading}}
|
||||
{{conditional-loading-spinner condition=loading}}
|
||||
|
||||
@ -1,54 +1,56 @@
|
||||
<table class='table email-list'>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{i18n 'admin.email.time'}}</th>
|
||||
<th>{{i18n 'admin.email.incoming_emails.from_address'}}</th>
|
||||
<th>{{i18n 'admin.email.incoming_emails.to_addresses'}}</th>
|
||||
<th>{{i18n 'admin.email.incoming_emails.subject'}}</th>
|
||||
<th>{{i18n 'admin.email.incoming_emails.error'}}</th>
|
||||
{{#load-more selector=".email-list tr" action="loadMore"}}
|
||||
<table class='table email-list'>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{i18n 'admin.email.time'}}</th>
|
||||
<th>{{i18n 'admin.email.incoming_emails.from_address'}}</th>
|
||||
<th>{{i18n 'admin.email.incoming_emails.to_addresses'}}</th>
|
||||
<th>{{i18n 'admin.email.incoming_emails.subject'}}</th>
|
||||
<th>{{i18n 'admin.email.incoming_emails.error'}}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tr class="filters">
|
||||
<td>{{i18n 'admin.email.logs.filters.title'}}</td>
|
||||
<td>{{text-field value=filter.from placeholderKey="admin.email.incoming_emails.filters.from_placeholder"}}</td>
|
||||
<td>{{text-field value=filter.to placeholderKey="admin.email.incoming_emails.filters.to_placeholder"}}</td>
|
||||
<td>{{text-field value=filter.subject placeholderKey="admin.email.incoming_emails.filters.subject_placeholder"}}</td>
|
||||
<td>{{text-field value=filter.error placeholderKey="admin.email.incoming_emails.filters.error_placeholder"}}</td>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tr class="filters">
|
||||
<td>{{i18n 'admin.email.logs.filters.title'}}</td>
|
||||
<td>{{text-field value=filter.from placeholderKey="admin.email.incoming_emails.filters.from_placeholder"}}</td>
|
||||
<td>{{text-field value=filter.to placeholderKey="admin.email.incoming_emails.filters.to_placeholder"}}</td>
|
||||
<td>{{text-field value=filter.subject placeholderKey="admin.email.incoming_emails.filters.subject_placeholder"}}</td>
|
||||
<td>{{text-field value=filter.error placeholderKey="admin.email.incoming_emails.filters.error_placeholder"}}</td>
|
||||
</tr>
|
||||
{{#each email in model}}
|
||||
<tr>
|
||||
<td class="time">{{format-date email.created_at}}</td>
|
||||
<td class="username">
|
||||
<div>
|
||||
{{#if email.user}}
|
||||
{{#link-to 'adminUser' email.user}}
|
||||
{{avatar email.user imageSize="tiny"}}
|
||||
{{email.from_address}}
|
||||
{{/link-to}}
|
||||
{{else}}
|
||||
—
|
||||
{{/if}}
|
||||
</div>
|
||||
</td>
|
||||
<td class="addresses">
|
||||
{{#each to in email.to_addresses}}
|
||||
<p><a href="mailto:{{unbound to}}" title="TO">{{unbound to}}</a></p>
|
||||
{{/each}}
|
||||
{{#each cc in email.cc_addresses}}
|
||||
<p><a href="mailto:{{unbound cc}}" title="CC">{{unbound cc}}</a></p>
|
||||
{{/each}}
|
||||
</td>
|
||||
<td>{{email.subject}}</td>
|
||||
<td class="error">
|
||||
<a {{action "showIncomingEmail" email.id}}>{{email.error}}</a>
|
||||
</td>
|
||||
</tr>
|
||||
{{else}}
|
||||
<tr><td colspan="5">{{i18n 'admin.email.incoming_emails.none'}}</td></tr>
|
||||
{{/each}}
|
||||
|
||||
{{#each email in model}}
|
||||
<tr>
|
||||
<td class="time">{{format-date email.created_at}}</td>
|
||||
<td class="username">
|
||||
<div>
|
||||
{{#if email.user}}
|
||||
{{#link-to 'adminUser' email.user}}
|
||||
{{avatar email.user imageSize="tiny"}}
|
||||
{{email.from_address}}
|
||||
{{/link-to}}
|
||||
{{else}}
|
||||
—
|
||||
{{/if}}
|
||||
</div>
|
||||
</td>
|
||||
<td class="addresses">
|
||||
{{#each to in email.to_addresses}}
|
||||
<p><a href="mailto:{{unbound to}}" title="TO">{{unbound to}}</a></p>
|
||||
{{/each}}
|
||||
{{#each cc in email.cc_addresses}}
|
||||
<p><a href="mailto:{{unbound cc}}" title="CC">{{unbound cc}}</a></p>
|
||||
{{/each}}
|
||||
</td>
|
||||
<td>{{email.subject}}</td>
|
||||
<td class="error">
|
||||
<a {{action "showIncomingEmail" email.id}}>{{email.error}}</a>
|
||||
</td>
|
||||
</tr>
|
||||
{{else}}
|
||||
<tr><td colspan="5">{{i18n 'admin.email.incoming_emails.none'}}</td></tr>
|
||||
{{/each}}
|
||||
</table>
|
||||
{{/load-more}}
|
||||
|
||||
</table>
|
||||
|
||||
{{conditional-loading-spinner condition=view.loading}}
|
||||
{{conditional-loading-spinner condition=loading}}
|
||||
|
||||
@ -1,47 +1,49 @@
|
||||
<table class='table email-list'>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{i18n 'admin.email.sent_at'}}</th>
|
||||
<th>{{i18n 'admin.email.user'}}</th>
|
||||
<th>{{i18n 'admin.email.to_address'}}</th>
|
||||
<th>{{i18n 'admin.email.email_type'}}</th>
|
||||
<th>{{i18n 'admin.email.reply_key'}}</th>
|
||||
{{#load-more selector=".email-list tr" action="loadMore"}}
|
||||
<table class='table email-list'>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{i18n 'admin.email.sent_at'}}</th>
|
||||
<th>{{i18n 'admin.email.user'}}</th>
|
||||
<th>{{i18n 'admin.email.to_address'}}</th>
|
||||
<th>{{i18n 'admin.email.email_type'}}</th>
|
||||
<th>{{i18n 'admin.email.reply_key'}}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tr class="filters">
|
||||
<td>{{i18n 'admin.email.logs.filters.title'}}</td>
|
||||
<td>{{text-field value=filter.user placeholderKey="admin.email.logs.filters.user_placeholder"}}</td>
|
||||
<td>{{text-field value=filter.address placeholderKey="admin.email.logs.filters.address_placeholder"}}</td>
|
||||
<td>{{text-field value=filter.type placeholderKey="admin.email.logs.filters.type_placeholder"}}</td>
|
||||
<td>{{text-field value=filter.reply_key placeholderKey="admin.email.logs.filters.reply_key_placeholder"}}</td>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tr class="filters">
|
||||
<td>{{i18n 'admin.email.logs.filters.title'}}</td>
|
||||
<td>{{text-field value=filter.user placeholderKey="admin.email.logs.filters.user_placeholder"}}</td>
|
||||
<td>{{text-field value=filter.address placeholderKey="admin.email.logs.filters.address_placeholder"}}</td>
|
||||
<td>{{text-field value=filter.type placeholderKey="admin.email.logs.filters.type_placeholder"}}</td>
|
||||
<td>{{text-field value=filter.reply_key placeholderKey="admin.email.logs.filters.reply_key_placeholder"}}</td>
|
||||
</tr>
|
||||
{{#each l in model}}
|
||||
<tr>
|
||||
<td>{{format-date l.created_at}}</td>
|
||||
<td>
|
||||
{{#if l.user}}
|
||||
{{#link-to 'adminUser' l.user}}{{avatar l.user imageSize="tiny"}}{{/link-to}}
|
||||
{{#link-to 'adminUser' l.user}}{{l.user.username}}{{/link-to}}
|
||||
{{else}}
|
||||
—
|
||||
{{/if}}
|
||||
</td>
|
||||
<td><a href='mailto:{{unbound l.to_address}}'>{{l.to_address}}</a></td>
|
||||
<td>{{l.email_type}}</td>
|
||||
<td>
|
||||
{{#if l.post_url}}
|
||||
<a href="{{l.post_url}}">{{l.reply_key}}</a>
|
||||
{{else}}
|
||||
{{l.reply_key}}
|
||||
{{/if}}
|
||||
</td>
|
||||
</tr>
|
||||
{{else}}
|
||||
<tr><td colspan="5">{{i18n 'admin.email.logs.none'}}</td></tr>
|
||||
{{/each}}
|
||||
|
||||
{{#each l in model}}
|
||||
<tr>
|
||||
<td>{{format-date l.created_at}}</td>
|
||||
<td>
|
||||
{{#if l.user}}
|
||||
{{#link-to 'adminUser' l.user}}{{avatar l.user imageSize="tiny"}}{{/link-to}}
|
||||
{{#link-to 'adminUser' l.user}}{{l.user.username}}{{/link-to}}
|
||||
{{else}}
|
||||
—
|
||||
{{/if}}
|
||||
</td>
|
||||
<td><a href='mailto:{{unbound l.to_address}}'>{{l.to_address}}</a></td>
|
||||
<td>{{l.email_type}}</td>
|
||||
<td>
|
||||
{{#if l.post_url}}
|
||||
<a href="{{l.post_url}}">{{l.reply_key}}</a>
|
||||
{{else}}
|
||||
{{l.reply_key}}
|
||||
{{/if}}
|
||||
</td>
|
||||
</tr>
|
||||
{{else}}
|
||||
<tr><td colspan="5">{{i18n 'admin.email.logs.none'}}</td></tr>
|
||||
{{/each}}
|
||||
</table>
|
||||
{{/load-more}}
|
||||
|
||||
</table>
|
||||
|
||||
{{conditional-loading-spinner condition=view.loading}}
|
||||
{{conditional-loading-spinner condition=loading}}
|
||||
|
||||
@ -1,47 +1,49 @@
|
||||
<table class='table email-list'>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{i18n 'admin.email.time'}}</th>
|
||||
<th>{{i18n 'admin.email.user'}}</th>
|
||||
<th>{{i18n 'admin.email.to_address'}}</th>
|
||||
<th>{{i18n 'admin.email.email_type'}}</th>
|
||||
<th>{{i18n 'admin.email.skipped_reason'}}</th>
|
||||
{{#load-more selector=".email-list tr" action="loadMore"}}
|
||||
<table class='table email-list'>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{i18n 'admin.email.time'}}</th>
|
||||
<th>{{i18n 'admin.email.user'}}</th>
|
||||
<th>{{i18n 'admin.email.to_address'}}</th>
|
||||
<th>{{i18n 'admin.email.email_type'}}</th>
|
||||
<th>{{i18n 'admin.email.skipped_reason'}}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tr class="filters">
|
||||
<td>{{i18n 'admin.email.logs.filters.title'}}</td>
|
||||
<td>{{text-field value=filter.user placeholderKey="admin.email.logs.filters.user_placeholder"}}</td>
|
||||
<td>{{text-field value=filter.address placeholderKey="admin.email.logs.filters.address_placeholder"}}</td>
|
||||
<td>{{text-field value=filter.type placeholderKey="admin.email.logs.filters.type_placeholder"}}</td>
|
||||
<td>{{text-field value=filter.skipped_reason placeholderKey="admin.email.logs.filters.skipped_reason_placeholder"}}</td>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tr class="filters">
|
||||
<td>{{i18n 'admin.email.logs.filters.title'}}</td>
|
||||
<td>{{text-field value=filter.user placeholderKey="admin.email.logs.filters.user_placeholder"}}</td>
|
||||
<td>{{text-field value=filter.address placeholderKey="admin.email.logs.filters.address_placeholder"}}</td>
|
||||
<td>{{text-field value=filter.type placeholderKey="admin.email.logs.filters.type_placeholder"}}</td>
|
||||
<td>{{text-field value=filter.skipped_reason placeholderKey="admin.email.logs.filters.skipped_reason_placeholder"}}</td>
|
||||
</tr>
|
||||
{{#each l in model}}
|
||||
<tr>
|
||||
<td>{{format-date l.created_at}}</td>
|
||||
<td>
|
||||
{{#if l.user}}
|
||||
{{#link-to 'adminUser' l.user}}{{avatar l.user imageSize="tiny"}}{{/link-to}}
|
||||
{{#link-to 'adminUser' l.user}}{{l.user.username}}{{/link-to}}
|
||||
{{else}}
|
||||
—
|
||||
{{/if}}
|
||||
</td>
|
||||
<td><a href='mailto:{{unbound l.to_address}}'>{{l.to_address}}</a></td>
|
||||
<td>{{l.email_type}}</td>
|
||||
<td>
|
||||
{{#if l.post_url}}
|
||||
<a href="{{l.post_url}}">{{l.skipped_reason}}</a>
|
||||
{{else}}
|
||||
{{l.skipped_reason}}
|
||||
{{/if}}
|
||||
</td>
|
||||
</tr>
|
||||
{{else}}
|
||||
<tr><td colspan="5">{{i18n 'admin.email.logs.none'}}</td></tr>
|
||||
{{/each}}
|
||||
|
||||
{{#each l in model}}
|
||||
<tr>
|
||||
<td>{{format-date l.created_at}}</td>
|
||||
<td>
|
||||
{{#if l.user}}
|
||||
{{#link-to 'adminUser' l.user}}{{avatar l.user imageSize="tiny"}}{{/link-to}}
|
||||
{{#link-to 'adminUser' l.user}}{{l.user.username}}{{/link-to}}
|
||||
{{else}}
|
||||
—
|
||||
{{/if}}
|
||||
</td>
|
||||
<td><a href='mailto:{{unbound l.to_address}}'>{{l.to_address}}</a></td>
|
||||
<td>{{l.email_type}}</td>
|
||||
<td>
|
||||
{{#if l.post_url}}
|
||||
<a href="{{l.post_url}}">{{l.skipped_reason}}</a>
|
||||
{{else}}
|
||||
{{l.skipped_reason}}
|
||||
{{/if}}
|
||||
</td>
|
||||
</tr>
|
||||
{{else}}
|
||||
<tr><td colspan="5">{{i18n 'admin.email.logs.none'}}</td></tr>
|
||||
{{/each}}
|
||||
</table>
|
||||
{{/load-more}}
|
||||
|
||||
</table>
|
||||
|
||||
{{conditional-loading-spinner condition=view.loading}}
|
||||
{{conditional-loading-spinner condition=loading}}
|
||||
|
||||
@ -386,15 +386,15 @@
|
||||
|
||||
<div class='display-row'>
|
||||
<div class='field'>{{i18n 'created'}}</div>
|
||||
<div class='value'>{{{model.created_at_age}}}</div>
|
||||
<div class='value'>{{format-date model.created_at leaveAgo="true"}}</div>
|
||||
</div>
|
||||
<div class='display-row'>
|
||||
<div class='field'>{{i18n 'admin.users.last_emailed'}}</div>
|
||||
<div class='value'>{{{model.last_emailed_age}}}</div>
|
||||
<div class='value'>{{format-date model.last_emailed_at leaveAgo="true"}}</div>
|
||||
</div>
|
||||
<div class='display-row'>
|
||||
<div class='field'>{{i18n 'last_seen'}}</div>
|
||||
<div class='value'>{{{model.last_seen_age}}}</div>
|
||||
<div class='value'>{{format-date model.last_seen_at leaveAgo="true"}}</div>
|
||||
</div>
|
||||
<div class='display-row'>
|
||||
<div class='field'>{{i18n 'admin.user.like_count'}}</div>
|
||||
|
||||
@ -16,7 +16,7 @@
|
||||
<form class="form-horizontal">
|
||||
<div>
|
||||
<label>{{i18n 'admin.badges.badge'}}</label>
|
||||
{{combo-box valueAttribute="id" value=controller.selectedBadgeId content=controller.grantableBadges nameProperty="displayName"}}
|
||||
{{combo-box valueAttribute="id" value=controller.selectedBadgeId content=controller.grantableBadges nameProperty="name"}}
|
||||
</div>
|
||||
<label>
|
||||
<label>{{i18n 'admin.badges.reason'}}</label>
|
||||
|
||||
@ -1,14 +0,0 @@
|
||||
import LoadMore from "discourse/mixins/load-more";
|
||||
|
||||
export default Ember.View.extend(LoadMore, {
|
||||
loading: false,
|
||||
eyelineSelector: ".email-list tr",
|
||||
|
||||
actions: {
|
||||
loadMore() {
|
||||
if (this.get("loading") || this.get("model.allLoaded")) { return; }
|
||||
this.set("loading", true);
|
||||
return this.get("controller").loadMore().then(() => this.set("loading", false));
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -1,14 +0,0 @@
|
||||
import LoadMore from "discourse/mixins/load-more";
|
||||
|
||||
export default Ember.View.extend(LoadMore, {
|
||||
loading: false,
|
||||
eyelineSelector: ".email-list tr",
|
||||
|
||||
actions: {
|
||||
loadMore() {
|
||||
if (this.get("loading") || this.get("model.allLoaded")) { return; }
|
||||
this.set("loading", true);
|
||||
return this.get("controller").loadMore().then(() => this.set("loading", false));
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -1,5 +0,0 @@
|
||||
import AdminEmailIncomingsView from "admin/views/admin-email-incomings";
|
||||
|
||||
export default AdminEmailIncomingsView.extend({
|
||||
templateName: "admin/templates/email-received"
|
||||
});
|
||||
@ -1,5 +0,0 @@
|
||||
import AdminEmailIncomingsView from "admin/views/admin-email-incomings";
|
||||
|
||||
export default AdminEmailIncomingsView.extend({
|
||||
templateName: "admin/templates/email-rejected"
|
||||
});
|
||||
@ -1,5 +0,0 @@
|
||||
import AdminEmailLogsView from "admin/views/admin-email-logs";
|
||||
|
||||
export default AdminEmailLogsView.extend({
|
||||
templateName: "admin/templates/email-sent"
|
||||
});
|
||||
@ -1,5 +0,0 @@
|
||||
import AdminEmailLogsView from "admin/views/admin-email-logs";
|
||||
|
||||
export default AdminEmailLogsView.extend({
|
||||
templateName: "admin/templates/email-skipped"
|
||||
});
|
||||
@ -6,6 +6,8 @@ define('ember', ['exports'], function(__exports__) {
|
||||
__exports__.default = Ember;
|
||||
});
|
||||
|
||||
var _pluginCallbacks = [];
|
||||
|
||||
window.Discourse = Ember.Application.createWithMixins(Discourse.Ajax, {
|
||||
rootElement: '#main',
|
||||
_docTitle: document.title,
|
||||
@ -127,6 +129,18 @@ window.Discourse = Ember.Application.createWithMixins(Discourse.Ajax, {
|
||||
}
|
||||
});
|
||||
|
||||
// Plugins that are registered via `<script>` tags.
|
||||
var withPluginApi = require('discourse/lib/plugin-api').withPluginApi;
|
||||
var initCount = 0;
|
||||
_pluginCallbacks.forEach(function(cb) {
|
||||
Discourse.instanceInitializer({
|
||||
name: "_discourse_plugin_" + (++initCount),
|
||||
after: 'inject-objects',
|
||||
initialize: function() {
|
||||
withPluginApi(cb.version, cb.code);
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
requiresRefresh: function(){
|
||||
@ -134,6 +148,9 @@ window.Discourse = Ember.Application.createWithMixins(Discourse.Ajax, {
|
||||
return desired && Discourse.get("currentAssetVersion") !== desired;
|
||||
}.property("currentAssetVersion", "desiredAssetVersion"),
|
||||
|
||||
_registerPluginCode: function(version, code) {
|
||||
_pluginCallbacks.push({ version: version, code: code });
|
||||
},
|
||||
|
||||
assetVersion: Ember.computed({
|
||||
get: function() {
|
||||
|
||||
@ -1,7 +1,9 @@
|
||||
export default Ember.Component.extend({
|
||||
tagName: 'span',
|
||||
classNameBindings: [':user-badge', 'badge.badgeTypeClassName'],
|
||||
title: Em.computed.alias('badge.displayDescription'),
|
||||
title: function(){
|
||||
return $("<div>"+this.get('badge.description')+"</div>").text();
|
||||
}.property('badge.description'),
|
||||
attributeBindings: ['data-badge-name', 'title'],
|
||||
'data-badge-name': Em.computed.alias('badge.name')
|
||||
});
|
||||
|
||||
@ -0,0 +1,45 @@
|
||||
import computed from 'ember-addons/ember-computed-decorators';
|
||||
import DiscourseURL from 'discourse/lib/url';
|
||||
|
||||
export default Ember.Component.extend({
|
||||
size: 'medium',
|
||||
classNameBindings: [':badge-card', 'size', 'navigateOnClick:hyperlink'],
|
||||
|
||||
click(e){
|
||||
if (e.target && e.target.nodeName === "A") {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!this.get('navigateOnClick')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var url = this.get('badge.url');
|
||||
const username = this.get('username');
|
||||
if (username) {
|
||||
url = url + "?username=" + encodeURIComponent(username);
|
||||
}
|
||||
DiscourseURL.routeTo(url);
|
||||
return true;
|
||||
},
|
||||
|
||||
@computed('count', 'badge.grant_count')
|
||||
displayCount(count, grantCount) {
|
||||
const c = parseInt(count || grantCount || 0);
|
||||
if (c > 1) {
|
||||
return c;
|
||||
}
|
||||
},
|
||||
|
||||
@computed('size')
|
||||
summary(size) {
|
||||
if (size === 'large') {
|
||||
const longDescription = this.get('badge.long_description');
|
||||
if (!_.isEmpty(longDescription)) {
|
||||
return Discourse.Emoji.unescape(longDescription);
|
||||
}
|
||||
}
|
||||
return this.get('badge.description');
|
||||
}
|
||||
|
||||
});
|
||||
@ -0,0 +1,16 @@
|
||||
import computed from 'ember-addons/ember-computed-decorators';
|
||||
|
||||
export default Ember.Component.extend({
|
||||
tagName: 'span',
|
||||
classNameBindings: [':check-display', 'status'],
|
||||
|
||||
@computed('checked')
|
||||
status(checked) {
|
||||
return checked ? 'status-checked' : 'status-unchecked';
|
||||
},
|
||||
|
||||
render(buffer) {
|
||||
const icon = this.get('checked') ? 'check' : 'times';
|
||||
buffer.push(`<i class='fa fa-${icon}'></i>`);
|
||||
}
|
||||
});
|
||||
@ -27,9 +27,9 @@ export default Ember.Component.extend(StringBuffer, {
|
||||
|
||||
if (notices.length > 0) {
|
||||
buffer.push(_.map(notices, n => {
|
||||
var html = `<div class='row'><div class='alert alert-info ${n[1]}'>${n[0]}`;
|
||||
var html = `<div class='row'><div class='alert alert-info ${n[1]}'>`;
|
||||
if (n[2]) html += n[2];
|
||||
html += '</div></div>';
|
||||
html += `${n[0]}</div></div>`;
|
||||
return html;
|
||||
}).join(""));
|
||||
}
|
||||
|
||||
@ -1,38 +1,58 @@
|
||||
import DiscourseURL from 'discourse/lib/url';
|
||||
import { setting } from 'discourse/lib/computed';
|
||||
import { iconHTML } from 'discourse/helpers/fa-icon';
|
||||
import { observes } from 'ember-addons/ember-computed-decorators';
|
||||
|
||||
export default Ember.Component.extend({
|
||||
classNames: ["title"],
|
||||
widget: 'home-logo',
|
||||
showMobileLogo: null,
|
||||
linkUrl: null,
|
||||
classNames: ['title'],
|
||||
|
||||
targetUrl: function() {
|
||||
// For overriding by customizations
|
||||
return '/';
|
||||
}.property(),
|
||||
init() {
|
||||
this._super();
|
||||
this.showMobileLogo = this.site.mobileView && !Ember.isEmpty(this.siteSettings.mobile_logo_url);
|
||||
this.linkUrl = this.get('targetUrl') || '/';
|
||||
},
|
||||
|
||||
linkUrl: function() {
|
||||
return Discourse.getURL(this.get('targetUrl'));
|
||||
}.property('targetUrl'),
|
||||
@observes('minimized')
|
||||
_updateLogo() {
|
||||
// On mobile we don't minimize the logo
|
||||
if (!this.site.mobileView) {
|
||||
this.rerender();
|
||||
}
|
||||
},
|
||||
|
||||
showSmallLogo: function() {
|
||||
return !this.site.mobileView && this.get("minimized");
|
||||
}.property("minimized"),
|
||||
|
||||
showMobileLogo: function() {
|
||||
return this.site.mobileView && !Ember.isBlank(this.get('mobileBigLogoUrl'));
|
||||
}.property(),
|
||||
|
||||
smallLogoUrl: setting('logo_small_url'),
|
||||
bigLogoUrl: setting('logo_url'),
|
||||
mobileBigLogoUrl: setting('mobile_logo_url'),
|
||||
title: setting('title'),
|
||||
|
||||
click: function(e) {
|
||||
click(e) {
|
||||
// if they want to open in a new tab, let it so
|
||||
if (e.shiftKey || e.metaKey || e.ctrlKey || e.which === 2) { return true; }
|
||||
|
||||
e.preventDefault();
|
||||
|
||||
DiscourseURL.routeTo(this.get('targetUrl'));
|
||||
DiscourseURL.routeTo(this.linkUrl);
|
||||
return false;
|
||||
},
|
||||
|
||||
render(buffer) {
|
||||
const { siteSettings } = this;
|
||||
const logoUrl = siteSettings.logo_url || '';
|
||||
const title = siteSettings.title;
|
||||
|
||||
buffer.push(`<a href="${this.linkUrl}" data-auto-route="true">`);
|
||||
if (!this.site.mobileView && this.get('minimized')) {
|
||||
const logoSmallUrl = siteSettings.logo_small_url || '';
|
||||
if (logoSmallUrl.length) {
|
||||
buffer.push(`<img id='site-logo' class="logo-small" src="${logoSmallUrl}" width="33" height="33" alt="${title}">`);
|
||||
} else {
|
||||
buffer.push(iconHTML('home'));
|
||||
}
|
||||
} else if (this.showMobileLogo) {
|
||||
buffer.push(`<img id="site-logo" class="logo-big" src="${siteSettings.mobile_logo_url}" alt="${title}">`);
|
||||
} else if (logoUrl.length) {
|
||||
buffer.push(`<img id="site-logo" class="logo-big" src="${logoUrl}" alt="${title}">`);
|
||||
} else {
|
||||
buffer.push(`<h2 id="site-text-logo" class="text-logo">${title}</h2>`);
|
||||
}
|
||||
buffer.push('</a>');
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
16
app/assets/javascripts/discourse/components/load-more.js.es6
Normal file
16
app/assets/javascripts/discourse/components/load-more.js.es6
Normal file
@ -0,0 +1,16 @@
|
||||
import LoadMore from "discourse/mixins/load-more";
|
||||
|
||||
export default Ember.Component.extend(LoadMore, {
|
||||
_viaComponent: true,
|
||||
|
||||
init() {
|
||||
this._super();
|
||||
this.set('eyelineSelector', this.get('selector'));
|
||||
},
|
||||
|
||||
actions: {
|
||||
loadMore() {
|
||||
this.sendAction();
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -56,6 +56,8 @@ export default Ember.Component.extend({
|
||||
rerenderWidget() {
|
||||
Ember.run.cancel(this._timeout);
|
||||
if (this._rootNode) {
|
||||
const t0 = new Date().getTime();
|
||||
|
||||
const opts = { model: this.get('model') };
|
||||
const newTree = new this._widgetClass(this.get('args'), this.container, opts);
|
||||
|
||||
@ -86,6 +88,10 @@ export default Ember.Component.extend({
|
||||
}
|
||||
|
||||
renderedKey('*');
|
||||
if (this.profileWidget) {
|
||||
console.log(new Date().getTime() - t0);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -3,5 +3,16 @@ export default Em.Component.extend({
|
||||
|
||||
label: function() {
|
||||
return I18n.t(this.get('labelKey'));
|
||||
}.property('labelKey')
|
||||
}.property('labelKey'),
|
||||
|
||||
click() {
|
||||
const warning = this.get('warning');
|
||||
|
||||
if (warning && !this.get('checked')) {
|
||||
this.sendAction('warning');
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
@ -2,6 +2,7 @@ import DiscourseURL from 'discourse/lib/url';
|
||||
import { keyDirty } from 'discourse/widgets/widget';
|
||||
import MountWidget from 'discourse/components/mount-widget';
|
||||
import { cloak, uncloak } from 'discourse/widgets/post-stream';
|
||||
import { isWorkaroundActive } from 'discourse/lib/safari-hacks';
|
||||
|
||||
function findTopView($posts, viewportTop, min, max) {
|
||||
if (max < min) { return min; }
|
||||
@ -38,6 +39,7 @@ export default MountWidget.extend({
|
||||
|
||||
scrolled() {
|
||||
if (this.isDestroyed || this.isDestroying) { return; }
|
||||
if (isWorkaroundActive()) { return; }
|
||||
|
||||
const $w = $(window);
|
||||
const windowHeight = window.innerHeight ? window.innerHeight : $w.height();
|
||||
|
||||
23
app/assets/javascripts/discourse/components/user-info.js.es6
Normal file
23
app/assets/javascripts/discourse/components/user-info.js.es6
Normal file
@ -0,0 +1,23 @@
|
||||
import { url } from 'discourse/lib/computed';
|
||||
import computed from 'ember-addons/ember-computed-decorators';
|
||||
|
||||
function normalize(name) {
|
||||
return name.replace(/[\-\_ \.]/g, '').toLowerCase();
|
||||
}
|
||||
|
||||
export default Ember.Component.extend({
|
||||
classNameBindings: [':user-info', 'size'],
|
||||
size: 'small',
|
||||
userPath: url('user.username', '/users/%@'),
|
||||
|
||||
// TODO: In later ember releases `hasBlock` works without this
|
||||
hasBlock: Ember.computed.alias('template'),
|
||||
|
||||
@computed('user.name', 'user.username')
|
||||
name(name, username) {
|
||||
if (name && normalize(username) !== normalize(name)) {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
@ -1,15 +0,0 @@
|
||||
import { url } from 'discourse/lib/computed';
|
||||
|
||||
export default Ember.Component.extend({
|
||||
classNames: ['user-small'],
|
||||
|
||||
userPath: url('user.username', '/users/%@'),
|
||||
|
||||
name: function() {
|
||||
const name = this.get('user.name');
|
||||
if (name && this.get('user.username') !== name) {
|
||||
return name;
|
||||
}
|
||||
}.property('user.name')
|
||||
|
||||
});
|
||||
@ -3,7 +3,7 @@ export default Ember.Controller.extend({
|
||||
var sorted = _.sortBy(this.get('model'), function(badge){
|
||||
var pos = badge.get('badge_grouping.position');
|
||||
var type = badge.get('badge_type_id');
|
||||
var name = badge.get('displayName');
|
||||
var name = badge.get('name');
|
||||
|
||||
return ("000" + pos).slice(-4) + (10-type) + name;
|
||||
});
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import UserBadge from 'discourse/models/user-badge';
|
||||
import { default as computed, observes } from 'ember-addons/ember-computed-decorators';
|
||||
|
||||
export default Ember.Controller.extend({
|
||||
queryParams: ['username'],
|
||||
@ -6,63 +7,50 @@ export default Ember.Controller.extend({
|
||||
userBadges: null,
|
||||
needs: ["application"],
|
||||
|
||||
user: function(){
|
||||
if (this.get("username")) {
|
||||
@computed('username')
|
||||
user(username) {
|
||||
if (username) {
|
||||
return this.get('userBadges')[0].get('user');
|
||||
}
|
||||
}.property("username"),
|
||||
},
|
||||
|
||||
grantCount: function() {
|
||||
if (this.get("username")) {
|
||||
return this.get('userBadges.grant_count');
|
||||
} else {
|
||||
return this.get('model.grant_count');
|
||||
}
|
||||
}.property('username', 'model', 'userBadges'),
|
||||
@computed('username', 'model.grant_count', 'userBadges.grant_count')
|
||||
grantCount(username, modelCount, userCount) {
|
||||
return username ? userCount : modelCount;
|
||||
},
|
||||
|
||||
actions: {
|
||||
loadMore() {
|
||||
const self = this;
|
||||
if (this.get('loadingMore')) {
|
||||
return;
|
||||
}
|
||||
this.set('loadingMore', true);
|
||||
|
||||
const userBadges = this.get('userBadges');
|
||||
|
||||
UserBadge.findByBadgeId(this.get('model.id'), {
|
||||
offset: userBadges.length,
|
||||
username: this.get('username'),
|
||||
}).then(function(result) {
|
||||
}).then(result => {
|
||||
userBadges.pushObjects(result);
|
||||
if(userBadges.length === 0){
|
||||
self.set('noMoreBadges', true);
|
||||
if (userBadges.length === 0){
|
||||
this.set('noMoreBadges', true);
|
||||
}
|
||||
}).finally(()=>{
|
||||
this.set('loadingMore', false);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
layoutClass: function(){
|
||||
var user = this.get("user") ? " single-user" : "";
|
||||
var ub = this.get("userBadges");
|
||||
if(ub && ub[0] && ub[0].post_id){
|
||||
return "user-badge-with-posts" + user;
|
||||
} else {
|
||||
return "user-badge-no-posts" + user;
|
||||
}
|
||||
}.property("userBadges"),
|
||||
@computed('noMoreBadges', 'grantCount', 'userBadges.length')
|
||||
canLoadMore(noMoreBadges, grantCount, userBadgeLength) {
|
||||
if (noMoreBadges) { return false; }
|
||||
return grantCount > (userBadgeLength || 0);
|
||||
},
|
||||
|
||||
canLoadMore: function() {
|
||||
if (this.get('noMoreBadges')) { return false; }
|
||||
|
||||
if (this.get('userBadges')) {
|
||||
return this.get('grantCount') > this.get('userBadges.length');
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}.property('noMoreBadges', 'model.grant_count', 'userBadges.length'),
|
||||
|
||||
_showFooter: function() {
|
||||
@observes('canLoadMore')
|
||||
_showFooter() {
|
||||
this.set("controllers.application.showFooter", !this.get("canLoadMore"));
|
||||
}.observes("canLoadMore"),
|
||||
|
||||
showLongDescription: function(){
|
||||
return window.location.search.match("long-description");
|
||||
}.property('userBadges')
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
@ -11,7 +11,8 @@ var ButtonBackBright = {
|
||||
ButtonTryAgain = {
|
||||
classes: "btn-primary",
|
||||
action: "tryLoading",
|
||||
key: "errors.buttons.again"
|
||||
key: "errors.buttons.again",
|
||||
icon: "refresh"
|
||||
},
|
||||
ButtonLoadPage = {
|
||||
classes: "btn-primary",
|
||||
|
||||
@ -106,6 +106,22 @@ export default Ember.Controller.extend(CanCheckEmails, {
|
||||
|
||||
actions: {
|
||||
|
||||
checkMailingList(){
|
||||
Em.run.next(()=>{
|
||||
const postsPerDay = this.get('model.mailing_list_posts_per_day');
|
||||
if (!postsPerDay || postsPerDay < 2) {
|
||||
this.set('model.user_option.mailing_list_mode', true);
|
||||
return;
|
||||
}
|
||||
|
||||
bootbox.confirm(I18n.t("user.enable_mailing_list", {count: postsPerDay}), I18n.t("no_value"), I18n.t("yes_value"), (success) => {
|
||||
if (success) {
|
||||
this.set('model.user_option.mailing_list_mode', true);
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
save() {
|
||||
this.set('saved', false);
|
||||
|
||||
|
||||
@ -62,6 +62,9 @@ export default Ember.Controller.extend({
|
||||
markerElement.appendChild(document.createTextNode("\ufeff"));
|
||||
|
||||
const isMobileDevice = this.site.isMobileDevice;
|
||||
const capabilities = this.capabilities,
|
||||
isIOS = capabilities.isIOS,
|
||||
isAndroid = capabilities.isAndroid;
|
||||
|
||||
// collapse the range at the beginning/end of the selection
|
||||
range.collapse(!isMobileDevice);
|
||||
@ -85,7 +88,7 @@ export default Ember.Controller.extend({
|
||||
let topOff = markerOffset.top;
|
||||
let leftOff = markerOffset.left;
|
||||
|
||||
if (isMobileDevice) {
|
||||
if (isMobileDevice || isIOS || isAndroid) {
|
||||
topOff = topOff + 20;
|
||||
leftOff = Math.min(leftOff + 10, $(window).width() - $quoteButton.outerWidth());
|
||||
} else {
|
||||
|
||||
@ -169,11 +169,6 @@ Discourse.BBCode.replaceBBCodeParamsRaw("email", function(param, contents) {
|
||||
return ['a', {href: "mailto:" + param, 'data-bbcode': true}].concat(contents);
|
||||
});
|
||||
|
||||
Discourse.BBCode.register('size', function(contents, params) {
|
||||
return ['span', {'class': "bbcode-size-" + (parseInt(params, 10) || 1)}].concat(contents);
|
||||
});
|
||||
Discourse.Markdown.whiteListTag('span', 'class', /^bbcode-size-\d+$/);
|
||||
|
||||
// Handles `[code] ... [/code]` blocks
|
||||
Discourse.Dialect.replaceBlock({
|
||||
start: /(\[code\])([\s\S]*)/igm,
|
||||
|
||||
@ -25,3 +25,5 @@ registerUnbound('format-date', function(val, params) {
|
||||
return new Handlebars.SafeString(autoUpdatingRelativeAge(date, {format: format, title: title, leaveAgo: leaveAgo}));
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
@ -0,0 +1,6 @@
|
||||
import { relativeAge } from 'discourse/lib/formatter';
|
||||
|
||||
export default function(dt, params) {
|
||||
dt = params.data.view.getStream(dt).value();
|
||||
return relativeAge(new Date(dt));
|
||||
}
|
||||
@ -0,0 +1,15 @@
|
||||
export function autoLoadModules() {
|
||||
Ember.keys(requirejs.entries).forEach(entry => {
|
||||
if ((/\/helpers\//).test(entry)) {
|
||||
require(entry, null, null, true);
|
||||
}
|
||||
if ((/\/widgets\//).test(entry)) {
|
||||
require(entry, null, null, true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export default {
|
||||
name: 'auto-load-modules',
|
||||
initialize: autoLoadModules
|
||||
};
|
||||
@ -1,12 +0,0 @@
|
||||
export function loadAllHelpers() {
|
||||
Ember.keys(requirejs.entries).forEach(entry => {
|
||||
if ((/\/helpers\//).test(entry)) {
|
||||
require(entry, null, null, true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export default {
|
||||
name: 'load-all-helpers',
|
||||
initialize: loadAllHelpers
|
||||
};
|
||||
@ -75,7 +75,9 @@ class PluginApi {
|
||||
* ```
|
||||
**/
|
||||
addPosterIcon(cb) {
|
||||
decorateWidget('poster-name:after', dec => {
|
||||
const mobileView = this.container.lookup('site:main').mobileView;
|
||||
const loc = mobileView ? 'before' : 'after';
|
||||
decorateWidget(`poster-name:${loc}`, dec => {
|
||||
const attrs = dec.attrs;
|
||||
|
||||
const result = cb(attrs.userCustomFields || {}, attrs);
|
||||
@ -102,7 +104,7 @@ class PluginApi {
|
||||
}
|
||||
|
||||
|
||||
return dec.h('span',
|
||||
return dec.h('span.poster-icon',
|
||||
{ className: result.className, attributes: { title: result.title } },
|
||||
iconBody);
|
||||
}
|
||||
|
||||
@ -6,6 +6,11 @@ function applicable() {
|
||||
!navigator.userAgent.match(/Trident/g);
|
||||
}
|
||||
|
||||
let workaroundActive = false;
|
||||
export function isWorkaroundActive() {
|
||||
return workaroundActive;
|
||||
}
|
||||
|
||||
// per http://stackoverflow.com/questions/29001977/safari-in-ios8-is-scrolling-screen-when-fixed-elements-get-focus/29064810
|
||||
function positioningWorkaround($fixedElement) {
|
||||
if (!applicable()) {
|
||||
@ -37,12 +42,12 @@ function positioningWorkaround($fixedElement) {
|
||||
if (evt) {
|
||||
evt.target.removeEventListener('blur', blurred);
|
||||
}
|
||||
workaroundActive = false;
|
||||
};
|
||||
|
||||
var blurred = _.debounce(blurredNow, 250);
|
||||
|
||||
var positioningHack = function(evt){
|
||||
|
||||
const self = this;
|
||||
done = false;
|
||||
|
||||
@ -76,6 +81,7 @@ function positioningWorkaround($fixedElement) {
|
||||
|
||||
evt.preventDefault();
|
||||
self.focus();
|
||||
workaroundActive = true;
|
||||
};
|
||||
|
||||
function attachTouchStart(elem, fn) {
|
||||
|
||||
@ -85,8 +85,11 @@ export default function transformPost(currentUser, site, post, prevPost, nextPos
|
||||
|
||||
const postAtts = transformBasicPost(post);
|
||||
|
||||
const createdBy = details.created_by || {};
|
||||
|
||||
postAtts.topicId = topic.id;
|
||||
postAtts.topicOwner = details.created_by.id === post.user_id;
|
||||
postAtts.topicOwner = createdBy.id === post.user_id;
|
||||
postAtts.topicCreatedById = createdBy.id;
|
||||
postAtts.post_type = postType;
|
||||
postAtts.via_email = post.via_email;
|
||||
postAtts.isModeratorAction = postType === postTypes.moderator_action;
|
||||
@ -119,8 +122,8 @@ export default function transformPost(currentUser, site, post, prevPost, nextPos
|
||||
if (showTopicMap) {
|
||||
postAtts.showTopicMap = true;
|
||||
postAtts.topicCreatedAt = topic.created_at;
|
||||
postAtts.createdByUsername = details.created_by.username;
|
||||
postAtts.createdByAvatarTemplate = details.created_by.avatar_template;
|
||||
postAtts.createdByUsername = createdBy.username;
|
||||
postAtts.createdByAvatarTemplate = createdBy.avatar_template;
|
||||
|
||||
postAtts.lastPostUrl = topic.get('lastPostUrl');
|
||||
postAtts.lastPostUsername = details.last_poster.username;
|
||||
|
||||
@ -66,6 +66,10 @@ Discourse.Ajax = Em.Mixin.create({
|
||||
});
|
||||
}
|
||||
|
||||
if (args.returnXHR) {
|
||||
data = { result: data, xhr: xhr };
|
||||
}
|
||||
|
||||
Ember.run(null, resolve, data);
|
||||
};
|
||||
|
||||
|
||||
@ -2,12 +2,20 @@ import Eyeline from 'discourse/lib/eyeline';
|
||||
import Scrolling from 'discourse/mixins/scrolling';
|
||||
import { on } from 'ember-addons/ember-computed-decorators';
|
||||
|
||||
// Provides the ability to load more items for a view which is scrolled to the bottom.
|
||||
// Provides the ability to load more items for a view which is scrolled to the bottom.
|
||||
export default Ember.Mixin.create(Ember.ViewTargetActionSupport, Scrolling, {
|
||||
|
||||
init() {
|
||||
this._super();
|
||||
if (!this._viaComponent) {
|
||||
console.warn('Using `LoadMore` as a view mixin is deprecated. Use `{{load-more}}` instead');
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
scrolled() {
|
||||
const eyeline = this.get('eyeline');
|
||||
if (eyeline) { eyeline.update(); }
|
||||
return eyeline && eyeline.update();
|
||||
},
|
||||
|
||||
loadMoreUnlessFull() {
|
||||
|
||||
@ -18,10 +18,7 @@ export default RestModel.extend({
|
||||
},
|
||||
|
||||
togglePromise(post) {
|
||||
if (!this.get('acted')) {
|
||||
return this.act(post).then(() => true);
|
||||
}
|
||||
return this.undo(post).then(() => false);
|
||||
return this.get('acted') ? this.undo(post) : this.act(post);
|
||||
},
|
||||
|
||||
toggle(post) {
|
||||
@ -64,11 +61,15 @@ export default RestModel.extend({
|
||||
message: opts.message,
|
||||
take_action: opts.takeAction,
|
||||
flag_topic: this.get('flagTopic') ? true : false
|
||||
}
|
||||
}).then(function(result) {
|
||||
},
|
||||
returnXHR: true,
|
||||
}).then(function(data) {
|
||||
if (!self.get('flagTopic')) {
|
||||
return post.updateActionsSummary(result);
|
||||
post.updateActionsSummary(data.result);
|
||||
}
|
||||
const remaining = parseInt(data.xhr.getResponseHeader('Discourse-Actions-Remaining') || 0);
|
||||
const max = parseInt(data.xhr.getResponseHeader('Discourse-Actions-Max') || 0);
|
||||
return { acted: true, remaining, max };
|
||||
}).catch(function(error) {
|
||||
popupAjaxError(error);
|
||||
self.removeAction(post);
|
||||
@ -83,7 +84,10 @@ export default RestModel.extend({
|
||||
return Discourse.ajax("/post_actions/" + post.get('id'), {
|
||||
type: 'DELETE',
|
||||
data: { post_action_type_id: this.get('id') }
|
||||
}).then(result => post.updateActionsSummary(result));
|
||||
}).then(result => {
|
||||
post.updateActionsSummary(result);
|
||||
return { acted: false };
|
||||
});
|
||||
},
|
||||
|
||||
deferFlags(post) {
|
||||
|
||||
@ -9,63 +9,6 @@ const Badge = RestModel.extend({
|
||||
return Discourse.getURL(`/badges/${this.get('id')}/${this.get('slug')}`);
|
||||
}.property(),
|
||||
|
||||
/**
|
||||
@private
|
||||
|
||||
The name key to use for fetching i18n translations.
|
||||
|
||||
@property i18nNameKey
|
||||
@type {String}
|
||||
**/
|
||||
i18nNameKey: function() {
|
||||
return this.get('name').toLowerCase().replace(/\s/g, '_');
|
||||
}.property('name'),
|
||||
|
||||
/**
|
||||
The display name of this badge. Attempts to use a translation and falls back to
|
||||
the actual name.
|
||||
|
||||
@property displayName
|
||||
@type {String}
|
||||
**/
|
||||
displayName: function() {
|
||||
const i18nKey = "badges.badge." + this.get('i18nNameKey') + ".name";
|
||||
return I18n.t(i18nKey, {defaultValue: this.get('name')});
|
||||
}.property('name', 'i18nNameKey'),
|
||||
|
||||
/**
|
||||
The i18n translated description for this badge. Returns the null if no
|
||||
translation exists.
|
||||
|
||||
@property translatedDescription
|
||||
@type {String}
|
||||
**/
|
||||
translatedDescription: function() {
|
||||
const i18nKey = "badges.badge." + this.get('i18nNameKey') + ".description";
|
||||
let translation = I18n.t(i18nKey);
|
||||
if (translation.indexOf(i18nKey) !== -1) {
|
||||
translation = null;
|
||||
}
|
||||
return translation;
|
||||
}.property('i18nNameKey'),
|
||||
|
||||
displayDescription: function(){
|
||||
// we support html in description but in most places do not need it
|
||||
return this.get('displayDescriptionHtml').replace(/<[^>]*>/g, "");
|
||||
}.property('displayDescriptionHtml'),
|
||||
|
||||
/**
|
||||
Display-friendly description string. Returns either a translation or the
|
||||
original description string.
|
||||
|
||||
@property displayDescription
|
||||
@type {String}
|
||||
**/
|
||||
displayDescriptionHtml: function() {
|
||||
const translated = this.get('translatedDescription');
|
||||
return (translated === null ? this.get('description') : translated) || "";
|
||||
}.property('description', 'translatedDescription'),
|
||||
|
||||
/**
|
||||
Update this badge with the response returned by the server on save.
|
||||
|
||||
|
||||
@ -6,13 +6,8 @@ const UserBadge = Discourse.Model.extend({
|
||||
return "/t/-/" + this.get('topic_id') + "/" + this.get('post_number');
|
||||
}
|
||||
}.property(), // avoid the extra bindings for now
|
||||
/**
|
||||
Revoke this badge.
|
||||
|
||||
@method revoke
|
||||
@returns {Promise} a promise that resolves when the badge has been revoked.
|
||||
**/
|
||||
revoke: function() {
|
||||
revoke() {
|
||||
return Discourse.ajax("/user_badges/" + this.get('id'), {
|
||||
type: "DELETE"
|
||||
});
|
||||
|
||||
@ -168,7 +168,8 @@ const User = RestModel.extend({
|
||||
'digest_after_minutes',
|
||||
'new_topic_duration_minutes',
|
||||
'auto_track_topics_after_msecs',
|
||||
'like_notification_frequency'
|
||||
'like_notification_frequency',
|
||||
'include_tl0_in_digests'
|
||||
].forEach(s => {
|
||||
data[s] = this.get(`user_option.${s}`);
|
||||
});
|
||||
@ -409,11 +410,13 @@ const User = RestModel.extend({
|
||||
|
||||
summary.topics = summary.topic_ids.map(id => topicMap[id]);
|
||||
|
||||
summary.badges = summary.badges.map(ub => {
|
||||
const badge = badgeMap[ub.badge_id];
|
||||
badge.count = ub.count;
|
||||
return badge;
|
||||
});
|
||||
if (summary.badges) {
|
||||
summary.badges = summary.badges.map(ub => {
|
||||
const badge = badgeMap[ub.badge_id];
|
||||
badge.count = ub.count;
|
||||
return badge;
|
||||
});
|
||||
}
|
||||
return summary;
|
||||
});
|
||||
}
|
||||
|
||||
@ -15,10 +15,7 @@ export default Discourse.Route.extend({
|
||||
},
|
||||
|
||||
serialize(model) {
|
||||
return {
|
||||
id: model.get("id"),
|
||||
slug: model.get("slug")
|
||||
};
|
||||
return model.getProperties('id', 'slug');
|
||||
},
|
||||
|
||||
model(params) {
|
||||
@ -29,19 +26,18 @@ export default Discourse.Route.extend({
|
||||
}
|
||||
},
|
||||
|
||||
afterModel(model,transition) {
|
||||
afterModel(model, transition) {
|
||||
const username = transition.queryParams && transition.queryParams.username;
|
||||
|
||||
return UserBadge.findByBadgeId(model.get("id"), {username}).then(userBadges => {
|
||||
this.userBadges = userBadges;
|
||||
});
|
||||
|
||||
},
|
||||
|
||||
titleToken() {
|
||||
const model = this.modelFor("badges.show");
|
||||
if (model) {
|
||||
return model.get("displayName");
|
||||
return model.get("name");
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@ -15,6 +15,7 @@ const LogsNotice = Ember.Object.extend({
|
||||
|
||||
this.messageBus.subscribe("/logs_error_rate_exceeded", data => {
|
||||
const duration = data.duration;
|
||||
const rate = data.rate;
|
||||
var siteSettingLimit = 0;
|
||||
|
||||
if (duration === 'minute') {
|
||||
@ -23,12 +24,13 @@ const LogsNotice = Ember.Object.extend({
|
||||
siteSettingLimit = this.siteSettings.alert_admins_if_errors_per_hour;
|
||||
}
|
||||
|
||||
var translationKey = (rate === siteSettingLimit) ? 'reached' : 'exceeded';
|
||||
|
||||
this.set('text',
|
||||
I18n.t('logs_error_rate_exceeded_notice', {
|
||||
I18n.t(`logs_error_rate_notice.${translationKey}`, {
|
||||
timestamp: moment().format("YYYY-MM-DD H:mm:ss"),
|
||||
siteSettingLimit: siteSettingLimit,
|
||||
rate: data.rate,
|
||||
duration: duration,
|
||||
siteSettingRate: I18n.t('logs_error_rate_notice.rate', { count: siteSettingLimit, duration: duration }),
|
||||
rate: I18n.t('logs_error_rate_notice.rate', { count: rate, duration: duration }),
|
||||
url: Discourse.getURL('/logs')
|
||||
})
|
||||
);
|
||||
|
||||
@ -22,8 +22,8 @@
|
||||
<section class='about admins'>
|
||||
<h3>{{i18n 'about.our_admins'}}</h3>
|
||||
|
||||
{{#each a in model.admins}}
|
||||
{{user-small user=a}}
|
||||
{{#each model.admins as |a|}}
|
||||
{{user-info user=a}}
|
||||
{{/each}}
|
||||
<div class='clearfix'></div>
|
||||
|
||||
@ -35,8 +35,8 @@
|
||||
<h3>{{i18n 'about.our_moderators'}}</h3>
|
||||
|
||||
<div class='users'>
|
||||
{{#each m in model.moderators}}
|
||||
{{user-small user=m}}
|
||||
{{#each model.moderators as |m|}}
|
||||
{{user-info user=m}}
|
||||
{{/each}}
|
||||
</div>
|
||||
<div class='clearfix'></div>
|
||||
|
||||
@ -1,21 +1,17 @@
|
||||
<div class='container badges'>
|
||||
<h1>{{i18n 'badges.title'}}</h1>
|
||||
|
||||
<table class='badges-listing'>
|
||||
<tbody>
|
||||
{{#each bg in badgeGroups}}
|
||||
<tr class='title'>
|
||||
<td colspan=4><h3>{{bg.badgeGrouping.displayName}}</h3></td>
|
||||
</tr>
|
||||
{{#each b in bg.badges}}
|
||||
<tr>
|
||||
<td class='granted'>{{#if b.has_badge}}<i class='fa fa-check'></i>{{/if}}</td>
|
||||
<td class='badge'>{{user-badge badge=b}}</td>
|
||||
<td class='description'>{{{b.displayDescriptionHtml}}}</td>
|
||||
<td class='grant-count'><span title="{{i18n 'badges.granted' count=b.grant_count}}">{{b.grant_count}}</span></td>
|
||||
</tr>
|
||||
<div class='badge-groups'>
|
||||
{{#each bg in badgeGroups}}
|
||||
<div class='badge-grouping'>
|
||||
<div class='title'>
|
||||
<h3>{{bg.badgeGrouping.displayName}}</h3>
|
||||
</div>
|
||||
|
||||
{{#each bg.badges as |b|}}
|
||||
{{badge-card badge=b navigateOnClick="true"}}
|
||||
{{/each}}
|
||||
{{/each}}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{{/each}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -1,71 +1,44 @@
|
||||
<div class='container show-badge'>
|
||||
<h1>
|
||||
{{#link-to 'badges.index'}}{{i18n 'badges.title'}}{{/link-to}}
|
||||
{{fa-icon "angle-right"}}
|
||||
{{model.displayName}}
|
||||
/
|
||||
{{model.name}}
|
||||
</h1>
|
||||
|
||||
<div class='badges-listing'>
|
||||
<div class='row'>
|
||||
<div class='badge'>{{user-badge badge=model}}</div>
|
||||
<div class='description'>{{{model.displayDescriptionHtml}}}</div>
|
||||
{{#unless user}}
|
||||
<div class='grant-count'>{{i18n 'badges.granted' count=grantCount}}</div>
|
||||
{{/unless}}
|
||||
<div class='info'>{{i18n 'badges.allow_title'}} {{{view.allowTitle}}}<br>{{i18n 'badges.multiple_grant'}} {{{view.multipleGrant}}}
|
||||
<div class='show-badge-details'>
|
||||
{{badge-card badge=model size="large" count=userBadges.grant_count}}
|
||||
<div class='badge-grant-info'>
|
||||
<div>
|
||||
<div class='grant-info-item'>
|
||||
{{check-mark checked=model.allow_title}} {{i18n 'badges.allow_title'}}
|
||||
</div>
|
||||
<div class='grant-info-item'>
|
||||
{{check-mark checked=model.multiple_grant}} {{i18n 'badges.multiple_grant'}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
{{#if showLongDescription}}
|
||||
<div class='long-description banner'>
|
||||
{{{long_description}}}
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
{{#if user}}
|
||||
<div class='badge-user-info'>
|
||||
{{#link-to 'user' user}}
|
||||
{{avatar user imageSize="extra_large"}}
|
||||
<div class="details clearfix">
|
||||
<div class='username'>{{user.username}}</div>
|
||||
</div>
|
||||
{{/link-to}}
|
||||
<div class='earned'>
|
||||
{{i18n 'badges.earned_n_times' count=grantCount}}
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
{{#if userBadges}}
|
||||
<div class={{unbound layoutClass}}>
|
||||
{{#each ub in userBadges}}
|
||||
<div class="badge-user">
|
||||
{{#if user}}
|
||||
{{format-date ub.granted_at}}
|
||||
{{else}}
|
||||
{{#link-to 'user' ub.user classNames="badge-info"}}
|
||||
{{avatar ub.user imageSize="large"}}
|
||||
<div class="details">
|
||||
<span class="username">{{ub.user.username}}</span>
|
||||
{{format-date ub.granted_at}}
|
||||
</div>
|
||||
{{/link-to}}
|
||||
{{/if}}
|
||||
|
||||
{{#if ub.post_number}}
|
||||
<a class="post-link" href="{{unbound ub.topic.url}}/{{unbound ub.post_number}}">{{{ub.topic.fancyTitle}}}</a>
|
||||
{{/if}}
|
||||
</div>
|
||||
{{/each}}
|
||||
<div class="user-badges">
|
||||
{{#load-more selector=".badge-info" action="loadMore"}}
|
||||
{{#each userBadges as |ub|}}
|
||||
{{#user-info user=ub.user size="medium" class="badge-info" date=ub.granted_at}}
|
||||
<div class="granted-on">{{i18n 'badges.granted_on' date=(inline-date ub.granted_at)}}</div>
|
||||
{{#if ub.post_number}}
|
||||
<a class="post-link" href="{{unbound ub.topic.url}}/{{unbound ub.post_number}}">{{{ub.topic.fancyTitle}}}</a>
|
||||
{{/if}}
|
||||
{{/user-info}}
|
||||
{{/each}}
|
||||
{{/load-more}}
|
||||
|
||||
{{#unless canLoadMore}}
|
||||
{{#if user}}
|
||||
<a class='load-more' href='{{model.url}}'>{{i18n 'badges.more_with_badge'}}</a>
|
||||
<div class='clearfix'>
|
||||
<a class='btn' href='{{model.url}}'>{{i18n 'badges.others_count' count=model.grant_count}}</a>
|
||||
</div>
|
||||
{{/if}}
|
||||
{{/unless}}
|
||||
|
||||
</div>
|
||||
|
||||
{{conditional-loading-spinner condition=canLoadMore}}
|
||||
|
||||
@ -1,3 +1,3 @@
|
||||
{{icon-or-image badge.icon}}
|
||||
<span class="badge-display-name">{{badge.displayName}}</span>
|
||||
<span class="badge-display-name">{{badge.name}}</span>
|
||||
{{yield}}
|
||||
|
||||
@ -0,0 +1,17 @@
|
||||
{{#if displayCount}}
|
||||
<span class='grant-count' title={{i18n 'badges.granted' count=displayCount}}>{{displayCount}}</span>
|
||||
{{/if}}
|
||||
{{#if badge.has_badge}}
|
||||
<span class='check-display status-checked'>{{fa-icon "check"}}</span>
|
||||
{{/if}}
|
||||
<div class='badge-contents'>
|
||||
<div class='badge-icon {{badge.badgeTypeClassName}}'>
|
||||
{{icon-or-image badge.icon}}
|
||||
</div>
|
||||
<div class='badge-info'>
|
||||
<div class='badge-info-item'>
|
||||
<h3>{{badge.name}}</h3>
|
||||
<div class='badge-summary'>{{{summary}}}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -1,19 +0,0 @@
|
||||
<a href="{{unbound linkUrl}}" data-auto-route="true">
|
||||
{{#if showSmallLogo}}
|
||||
{{#if smallLogoUrl}}
|
||||
<img class="logo-small" src="{{unbound smallLogoUrl}}" width="33" height="33">
|
||||
{{else}}
|
||||
<i class="fa fa-home"></i>
|
||||
{{/if}}
|
||||
{{else}}
|
||||
{{#if showMobileLogo}}
|
||||
<img id="site-logo" class="logo-big" src="{{unbound mobileBigLogoUrl}}" alt="{{unbound title}}">
|
||||
{{else}}
|
||||
{{#if bigLogoUrl}}
|
||||
<img id="site-logo" class="logo-big" src="{{unbound bigLogoUrl}}" alt="{{unbound title}}">
|
||||
{{else}}
|
||||
<h2 id="site-text-logo" class="text-logo">{{unbound title}}</h2>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</a>
|
||||
@ -8,4 +8,11 @@
|
||||
<span class="name">{{unbound name}}</span>
|
||||
</div>
|
||||
<div class="title">{{unbound user.title}}</div>
|
||||
|
||||
{{#if hasBlock}}
|
||||
<div class='details'>
|
||||
{{yield}}
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
</div>
|
||||
@ -4,7 +4,8 @@
|
||||
<li>
|
||||
<a href>
|
||||
{{#if option.src}}
|
||||
<img src={{option.src}} class='emoji'> {{option.code}}
|
||||
<img src={{option.src}} class='emoji'>
|
||||
<span class='emoji-shortname'>{{option.code}}</span>
|
||||
{{else}}
|
||||
{{option.label}}
|
||||
{{/if}}
|
||||
|
||||
@ -14,7 +14,7 @@
|
||||
</div>
|
||||
<div class="buttons">
|
||||
{{#each buttonData in enabledButtons}}
|
||||
<button class="btn {{unbound buttonData.classes}}" {{action buttonData.action}}>{{boundI18n buttonData.key}}</button>
|
||||
{{d-button icon=buttonData.icon action=buttonData.action label=buttonData.key class=buttonData.classes}}
|
||||
{{/each}}
|
||||
{{conditional-loading-spinner condition=loading}}
|
||||
</div>
|
||||
|
||||
@ -18,7 +18,7 @@
|
||||
{{#each model.members as |m|}}
|
||||
<tr>
|
||||
<td class='avatar'>
|
||||
{{user-small user=m}}
|
||||
{{user-info user=m}}
|
||||
{{#if m.owner}}<span class='is-owner'>{{i18n "groups.owner"}}</span>{{/if}}
|
||||
</td>
|
||||
<td>
|
||||
|
||||
@ -13,7 +13,7 @@
|
||||
{{#each ic in model itemController="directory-item"}}
|
||||
<div class="user {{if ic.me 'me'}}">
|
||||
{{#with ic.model as |it|}}
|
||||
{{user-small user=it.user}}
|
||||
{{user-info user=it.user}}
|
||||
{{user-stat value=it.likes_received label="directory.likes_received" icon="heart"}}
|
||||
{{user-stat value=it.likes_given label="directory.likes_given" icon="heart"}}
|
||||
{{user-stat value=it.topic_count label="directory.topic_count"}}
|
||||
|
||||
@ -10,7 +10,7 @@
|
||||
<div class="control-group">
|
||||
<label class="control-label"></label>
|
||||
<div class="controls">
|
||||
{{combo-box valueAttribute="id" value=selectedUserBadgeId nameProperty="badge.displayName" content=selectableUserBadges}}
|
||||
{{combo-box valueAttribute="id" value=selectedUserBadgeId nameProperty="badge.name" content=selectableUserBadges}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@ -10,7 +10,7 @@
|
||||
<div class="control-group">
|
||||
<label class="control-label"></label>
|
||||
<div class="controls">
|
||||
{{combo-box valueAttribute="id" value=selectedUserBadgeId nameProperty="badge.displayName" content=selectableUserBadges}}
|
||||
{{combo-box valueAttribute="id" value=selectedUserBadgeId nameProperty="badge.name" content=selectableUserBadges}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<section class='user-content user-badges-list'>
|
||||
{{#each ub in controller}}
|
||||
{{user-badge badge=ub.badge count=ub.count user=user}}
|
||||
{{#each controller as |ub|}}
|
||||
{{badge-card badge=ub.badge count=ub.count navigateOnClick="true" username=user.username_lower}}
|
||||
{{/each}}
|
||||
</section>
|
||||
|
||||
@ -174,8 +174,10 @@
|
||||
<div class='controls controls-dropdown'>
|
||||
{{combo-box valueAttribute="value" content=digestFrequencies value=model.user_option.digest_after_minutes}}
|
||||
</div>
|
||||
{{preference-checkbox labelKey="user.include_tl0_in_digests" checked=model.user_option.include_tl0_in_digests}}
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
<br/>
|
||||
<div class='controls controls-dropdown'>
|
||||
<label>{{i18n 'user.email_previous_replies.title'}}</label>
|
||||
{{combo-box valueAttribute="value" content=previousRepliesOptions value=model.user_option.email_previous_replies}}
|
||||
@ -184,7 +186,7 @@
|
||||
{{preference-checkbox labelKey="user.email_private_messages" checked=model.user_option.email_private_messages}}
|
||||
{{preference-checkbox labelKey="user.email_direct" checked=model.user_option.email_direct}}
|
||||
{{#unless siteSettings.disable_mailing_list_mode}}
|
||||
{{preference-checkbox labelKey="user.mailing_list_mode" checked=model.user_option.mailing_list_mode}}
|
||||
{{preference-checkbox warning="checkMailingList" labelKey="user.mailing_list_mode" checked=model.user_option.mailing_list_mode}}
|
||||
{{/unless}}
|
||||
{{preference-checkbox labelKey="user.email_always" checked=model.user_option.email_always}}
|
||||
{{#unless model.user_option.email_always}}
|
||||
@ -276,7 +278,7 @@
|
||||
|
||||
{{plugin-outlet "user-custom-controls"}}
|
||||
|
||||
<div class="control-group">
|
||||
<div class="control-group save-button">
|
||||
<div class="controls">
|
||||
{{partial 'user/preferences/save-button'}}
|
||||
</div>
|
||||
|
||||
@ -28,7 +28,7 @@
|
||||
{{#each ic in model itemController="directory-item"}}
|
||||
<tr class="{{if ic.me 'me'}}">
|
||||
{{#with ic.model as |it|}}
|
||||
<td>{{user-small user=it.user}}</td>
|
||||
<td>{{user-info user=it.user}}</td>
|
||||
<td>{{number it.likes_received}}</td>
|
||||
<td>{{number it.likes_given}}</td>
|
||||
<td>{{number it.topic_count}}</td>
|
||||
|
||||
@ -1,11 +0,0 @@
|
||||
import LoadMore from "discourse/mixins/load-more";
|
||||
|
||||
export default Ember.View.extend(LoadMore, {
|
||||
eyelineSelector: '.badge-user',
|
||||
tickOrX: function(field){
|
||||
var icon = this.get('controller.model.' + field) ? "fa-check" : "fa-times";
|
||||
return "<i class='fa " + icon + "'></i>";
|
||||
},
|
||||
allowTitle: function() { return this.tickOrX("allow_title"); }.property(),
|
||||
multipleGrant: function() { return this.tickOrX("multiple_grant"); }.property()
|
||||
});
|
||||
@ -19,6 +19,8 @@ class DecoratorHelper {
|
||||
* // renders `<div class='some-class'><p>paragraph</p></div>`
|
||||
* return helper.h('div.some-class', helper.h('p', 'paragraph'));
|
||||
* ```
|
||||
* Check out https://github.com/Matt-Esch/virtual-dom/blob/master/virtual-hyperscript/README.md
|
||||
* for more details on how to construct markup with h.
|
||||
**/
|
||||
// h() is attached via `prototype` below
|
||||
|
||||
@ -77,8 +79,8 @@ class DecoratorHelper {
|
||||
* return helper.cooked(`<p>Cook me</p>`);
|
||||
* ```
|
||||
**/
|
||||
cooked(cookedText) {
|
||||
return new PostCooked({ cookedText });
|
||||
cooked(cooked) {
|
||||
return new PostCooked({ cooked });
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -23,7 +23,6 @@ export default createWidget('post-admin-menu', {
|
||||
const contents = [];
|
||||
contents.push(h('h3', I18n.t('admin_title')));
|
||||
|
||||
|
||||
if (!attrs.isWhisper && this.currentUser.staff) {
|
||||
const buttonAtts = { action: 'togglePostType', icon: 'shield', className: 'toggle-post-type' };
|
||||
|
||||
@ -35,25 +34,32 @@ export default createWidget('post-admin-menu', {
|
||||
contents.push(this.attach('post-admin-menu-button', buttonAtts));
|
||||
}
|
||||
|
||||
contents.push(this.attach('post-admin-menu-button', {
|
||||
icon: 'cog', label: 'post.controls.rebake', action: 'rebakePost', className: 'rebuild-html'
|
||||
}));
|
||||
|
||||
if (attrs.hidden) {
|
||||
if (attrs.canManage) {
|
||||
contents.push(this.attach('post-admin-menu-button', {
|
||||
icon: 'eye',
|
||||
label: 'post.controls.unhide',
|
||||
action: 'unhidePost',
|
||||
className: 'unhide-post'
|
||||
icon: 'cog', label: 'post.controls.rebake', action: 'rebakePost', className: 'rebuild-html'
|
||||
}));
|
||||
|
||||
if (attrs.hidden) {
|
||||
contents.push(this.attach('post-admin-menu-button', {
|
||||
icon: 'eye', label: 'post.controls.unhide', action: 'unhidePost', className: 'unhide-post'
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
if (this.currentUser.admin) {
|
||||
contents.push(this.attach('post-admin-menu-button', {
|
||||
icon: 'user',
|
||||
label: 'post.controls.change_owner',
|
||||
action: 'changePostOwner',
|
||||
className: 'change-owner'
|
||||
icon: 'user', label: 'post.controls.change_owner', action: 'changePostOwner', className: 'change-owner'
|
||||
}));
|
||||
}
|
||||
|
||||
// toggle Wiki button
|
||||
if (attrs.wiki) {
|
||||
contents.push(this.attach('post-admin-menu-button', {
|
||||
action: 'toggleWiki', label: 'post.controls.unwiki', icon: 'pencil-square-o', className: 'wiki wikied'
|
||||
}));
|
||||
} else {
|
||||
contents.push(this.attach('post-admin-menu-button', {
|
||||
action: 'toggleWiki', label: 'post.controls.wiki', icon: 'pencil-square-o', className: 'wiki'
|
||||
}));
|
||||
}
|
||||
|
||||
@ -64,4 +70,3 @@ export default createWidget('post-admin-menu', {
|
||||
this.sendWidgetAction('closeAdminMenu');
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@ -81,8 +81,7 @@ registerButton('edit', attrs => {
|
||||
className: 'edit',
|
||||
title: 'post.controls.edit',
|
||||
icon: 'pencil',
|
||||
alwaysShowYours: true,
|
||||
alwaysShowWiki: true
|
||||
alwaysShowYours: true
|
||||
};
|
||||
}
|
||||
});
|
||||
@ -161,7 +160,7 @@ registerButton('bookmark', attrs => {
|
||||
});
|
||||
|
||||
registerButton('admin', attrs => {
|
||||
if (!attrs.canManage) { return; }
|
||||
if (!attrs.canManage && !attrs.canWiki) { return; }
|
||||
return { action: 'openAdminMenu',
|
||||
title: 'post.controls.admin',
|
||||
className: 'show-post-admin-menu',
|
||||
@ -180,22 +179,6 @@ registerButton('delete', attrs => {
|
||||
}
|
||||
});
|
||||
|
||||
registerButton('wiki', attrs => {
|
||||
if (!attrs.canWiki) { return; }
|
||||
|
||||
if (attrs.wiki) {
|
||||
return { action: 'toggleWiki',
|
||||
title: 'post.controls.unwiki',
|
||||
icon: 'pencil-square-o',
|
||||
className: 'wiki wikied' };
|
||||
} else {
|
||||
return { action: 'toggleWiki',
|
||||
title: 'post.controls.wiki',
|
||||
icon: 'pencil-square-o',
|
||||
className: 'wiki' };
|
||||
}
|
||||
});
|
||||
|
||||
export default createWidget('post-menu', {
|
||||
tagName: 'section.post-menu-area.clearfix',
|
||||
|
||||
@ -229,9 +212,7 @@ export default createWidget('post-menu', {
|
||||
const button = this.attachButton(i, attrs);
|
||||
if (button) {
|
||||
allButtons.push(button);
|
||||
if ((attrs.yours && button.attrs.alwaysShowYours) ||
|
||||
(attrs.wiki && button.attrs.alwaysShowWiki) ||
|
||||
(hiddenButtons.indexOf(i) === -1)) {
|
||||
if ((attrs.yours && button.attrs.alwaysShowYours) || (hiddenButtons.indexOf(i) === -1)) {
|
||||
visibleButtons.push(button);
|
||||
}
|
||||
}
|
||||
|
||||
@ -423,7 +423,27 @@ export default createWidget('post', {
|
||||
const likeAction = post.get('likeAction');
|
||||
|
||||
if (likeAction && likeAction.get('canToggle')) {
|
||||
return likeAction.togglePromise(post);
|
||||
return likeAction.togglePromise(post).then(result => this._warnIfClose(result));
|
||||
}
|
||||
},
|
||||
|
||||
_warnIfClose(result) {
|
||||
if (!result || !result.acted) { return; }
|
||||
|
||||
const kvs = this.keyValueStore;
|
||||
const lastWarnedLikes = kvs.get('lastWarnedLikes');
|
||||
|
||||
// only warn once per day
|
||||
const yesterday = new Date().getTime() - 1000 * 60 * 60 * 24;
|
||||
if (lastWarnedLikes && parseInt(lastWarnedLikes) > yesterday) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { remaining, max } = result;
|
||||
const threshold = Math.ceil(max * 0.1);
|
||||
if (remaining === threshold) {
|
||||
bootbox.alert(I18n.t('post.few_likes_left'));
|
||||
kvs.set({ key: 'lastWarnedLikes', value: new Date().getTime() });
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@ -119,6 +119,7 @@ export default class Widget {
|
||||
this.currentUser = container.lookup('current-user:main');
|
||||
this.store = container.lookup('store:main');
|
||||
this.appEvents = container.lookup('app-events:main');
|
||||
this.keyValueStore = container.lookup('key-value-store:main');
|
||||
|
||||
if (this.name) {
|
||||
const custom = _customSettings[this.name];
|
||||
|
||||
3
app/assets/javascripts/locales/gl.js.erb
Normal file
3
app/assets/javascripts/locales/gl.js.erb
Normal file
@ -0,0 +1,3 @@
|
||||
//= depend_on 'client.gl.yml'
|
||||
//= require locales/i18n
|
||||
<%= JsLocaleHelper.output_locale(:gl) %>
|
||||
@ -9,7 +9,6 @@
|
||||
//= require admin/routes/admin-email-logs
|
||||
//= require admin/controllers/admin-email-skipped
|
||||
//= require discourse/lib/export-result
|
||||
//= require_tree ./admin/lib
|
||||
//= require_tree ./admin
|
||||
|
||||
//= require resumable.js
|
||||
|
||||
@ -13,10 +13,4 @@ span {
|
||||
&.bbcode-s {
|
||||
text-decoration: line-through;
|
||||
}
|
||||
// Font sizes
|
||||
@for $i from 4 through 40 {
|
||||
&.bbcode-size-#{$i} {
|
||||
font-size: #{$i}px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,10 @@
|
||||
.directory {
|
||||
margin-bottom: 100px;
|
||||
|
||||
.user-info {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.period-chooser {
|
||||
float: left;
|
||||
}
|
||||
|
||||
@ -100,3 +100,11 @@ table.emoji-page td {
|
||||
.emoji-modal .nav a {
|
||||
color: dark-light-choose(#333, #ccc);
|
||||
}
|
||||
|
||||
.emoji-shortname {
|
||||
display: inline-block;
|
||||
max-width: 200px;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
@ -21,6 +21,7 @@
|
||||
}
|
||||
}
|
||||
.buttons {
|
||||
display: inline-flex;
|
||||
margin-top: 15px;
|
||||
|
||||
button {
|
||||
|
||||
@ -17,6 +17,9 @@
|
||||
|
||||
.title {
|
||||
float: left;
|
||||
a, a:visited {
|
||||
color: $header_primary;
|
||||
}
|
||||
}
|
||||
|
||||
#site-logo {
|
||||
|
||||
@ -20,98 +20,18 @@
|
||||
}
|
||||
|
||||
&.badge-type-gold .fa {
|
||||
color: #ffd700;
|
||||
color: #ffd700 !important;
|
||||
}
|
||||
|
||||
&.badge-type-silver .fa {
|
||||
color: #c0c0c0;
|
||||
color: #c0c0c0 !important;
|
||||
}
|
||||
|
||||
&.badge-type-bronze .fa {
|
||||
color: #cd7f32;
|
||||
color: #cd7f32 !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* User badge listing. */
|
||||
.user-badges-list {
|
||||
text-align: center;
|
||||
|
||||
.user-badge {
|
||||
max-width: 80px;
|
||||
text-align: center;
|
||||
vertical-align: top;
|
||||
margin: 10px;
|
||||
border: none;
|
||||
|
||||
.fa {
|
||||
display: block;
|
||||
font-size: 3.571em;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
img {
|
||||
display: block;
|
||||
margin: auto auto 4px;
|
||||
width: 55px;
|
||||
height: 55px;
|
||||
}
|
||||
|
||||
.count {
|
||||
display: block;
|
||||
font-size: 0.8em;
|
||||
color: dark-light-choose(scale-color($primary, $lightness: 50%), scale-color($secondary, $lightness: 50%));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Badge listing in /badges. */
|
||||
.badges-listing {
|
||||
margin: 20px 0;
|
||||
tr {
|
||||
border-bottom: 1px solid dark-light-diff($primary, $secondary, 90%, -60%);
|
||||
td {
|
||||
padding: 10px 0;
|
||||
}
|
||||
}
|
||||
|
||||
border-bottom: 1px solid dark-light-diff($primary, $secondary, 90%, -60%);
|
||||
border-top: 1px solid dark-light-diff($primary, $secondary, 90%, -60%);
|
||||
width: 90%;
|
||||
padding: 10px;
|
||||
display: table;
|
||||
|
||||
.row {
|
||||
display: table-row;
|
||||
> div {
|
||||
display: table-cell;
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
|
||||
.user-badge {
|
||||
font-size: $base-font-size;
|
||||
}
|
||||
|
||||
.grant-count {
|
||||
text-align: center;
|
||||
color: dark-light-choose(scale-color($primary, $lightness: 40%), scale-color($secondary, $lightness: 60%));
|
||||
font-size: 120%;
|
||||
}
|
||||
|
||||
.badge, .grant-count {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.info {
|
||||
font-size: 0.9em;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.description {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@media all and (max-width: 750px) {
|
||||
.show-badge .user-badge-with-posts .badge-user a.post-link {
|
||||
width: auto;
|
||||
@ -137,66 +57,23 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
.user-info.medium.badge-info {
|
||||
min-height: 80px;
|
||||
|
||||
|
||||
/* /badges/:id/:slug page styling. */
|
||||
.show-badge {
|
||||
.badge-user {
|
||||
text-align: center;
|
||||
width: 100px;
|
||||
padding: 5px 10px;
|
||||
margin-bottom: 10px;
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
|
||||
.details {
|
||||
margin: 0 10px;
|
||||
padding-top: 3px;
|
||||
color: $primary;
|
||||
}
|
||||
|
||||
.username {
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.date {
|
||||
display: block;
|
||||
color: lighten($primary, 40%);
|
||||
font-size: 0.714em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.show-badge .user-badge-with-posts .badge-user {
|
||||
width: 45%;
|
||||
padding: 0 0 0 4%;
|
||||
margin-bottom: 20px;
|
||||
|
||||
.badge-info {
|
||||
width: 100px;
|
||||
display: block;
|
||||
float: left;
|
||||
.granted-on {
|
||||
color: dark-light-choose(scale-color($primary, $lightness: 50%), scale-color($secondary, $lightness: 50%));
|
||||
}
|
||||
|
||||
.post-link {
|
||||
width: 250px;
|
||||
display: block;
|
||||
float: left;
|
||||
margin-left: 18px;
|
||||
text-align: left;
|
||||
margin-top: 0.2em;
|
||||
}
|
||||
}
|
||||
|
||||
.show-badge .badge-user-info {
|
||||
margin-left: 2%;
|
||||
.earned {
|
||||
margin-top: 15px;
|
||||
font-size: 1.3em;
|
||||
}
|
||||
.username {
|
||||
margin-top: 5px;
|
||||
display: block;
|
||||
color: dark-light-choose(scale-color($primary, $lightness: 30%), scale-color($secondary, $lightness: 70%));
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
}
|
||||
|
||||
@ -241,5 +118,134 @@
|
||||
|
||||
.long-description.banner {
|
||||
width: 88%;
|
||||
margin-bottom: 20px;
|
||||
margin-bottom: 15px;
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.badge-card {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
background-color: dark-light-diff($primary, $secondary, 95%, -65%);
|
||||
margin-right: 5px;
|
||||
margin-bottom: 10px;
|
||||
box-shadow: 1px 1px 3px rgba(0.0, 0.0, 0.0, 0.2);
|
||||
|
||||
.check-display {
|
||||
position: absolute;
|
||||
left: 5px;
|
||||
top: 5px;
|
||||
}
|
||||
|
||||
.grant-count {
|
||||
position: absolute;
|
||||
right: 5px;
|
||||
top: 5px;
|
||||
font-weight: bold;
|
||||
color: dark-light-diff($primary, $secondary, 50%, -65%);
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
.badge-contents {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
min-height: 128px;
|
||||
|
||||
.badge-icon {
|
||||
min-width: 90px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: dark-light-diff($primary, $secondary, 92%, -60%);
|
||||
font-size: 3em;
|
||||
|
||||
img {
|
||||
max-width: 80px;
|
||||
}
|
||||
|
||||
&.badge-type-gold .fa {
|
||||
color: #ffd700 !important;
|
||||
}
|
||||
|
||||
&.badge-type-silver .fa {
|
||||
color: #c0c0c0 !important;
|
||||
}
|
||||
|
||||
&.badge-type-bronze .fa {
|
||||
color: #cd7f32 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.badge-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 15px;
|
||||
color: $primary;
|
||||
|
||||
h3 {
|
||||
margin-bottom: 0.25em;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.badge-card.medium {
|
||||
width: 350px;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.badge-card.large {
|
||||
width: 750px;
|
||||
}
|
||||
|
||||
.badge-groups {
|
||||
margin: 20px 0;
|
||||
color: dark-light-choose(scale-color($primary, $lightness: 40%), scale-color($secondary, $lightness: 60%));
|
||||
h3 {
|
||||
margin-bottom: 1.0em;
|
||||
}
|
||||
}
|
||||
|
||||
.badge-grouping {
|
||||
margin-bottom: 1.5em;
|
||||
}
|
||||
|
||||
.show-badge-details {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
margin-bottom: 2em;
|
||||
margin-top: 1em;
|
||||
|
||||
.badge-grant-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-left: 1em;
|
||||
}
|
||||
.grant-info-item {
|
||||
margin-bottom: 1em;
|
||||
color: dark-light-choose(scale-color($primary, $lightness: 40%), scale-color($secondary, $lightness: 60%));
|
||||
}
|
||||
}
|
||||
|
||||
.check-display {
|
||||
display: inline-block;
|
||||
width: 18px;
|
||||
border-radius: 10px;
|
||||
text-align: center;
|
||||
.fa {
|
||||
font-size: 0.9em;
|
||||
color: $secondary;
|
||||
}
|
||||
}
|
||||
|
||||
.check-display.status-checked {
|
||||
background-color: $success;
|
||||
}
|
||||
|
||||
.check-display.status-unchecked {
|
||||
background-color: $danger;
|
||||
}
|
||||
|
||||
.hyperlink {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
@ -19,6 +19,23 @@
|
||||
i.fa-heart {
|
||||
color: $love !important;
|
||||
}
|
||||
.nav-pills {
|
||||
a {
|
||||
color: dark-light-choose(scale-color($primary, $lightness: 40%), scale-color($secondary, $lightness: 40%));
|
||||
}
|
||||
|
||||
a:hover i {
|
||||
color: $quaternary;
|
||||
}
|
||||
|
||||
a.active i {
|
||||
color: $secondary;
|
||||
}
|
||||
|
||||
i {
|
||||
color: dark-light-choose(scale-color($primary, $lightness: 55%), scale-color($secondary, $lightness: 55%));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.user-field {
|
||||
@ -92,10 +109,10 @@
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.user-small {
|
||||
.user-info {
|
||||
display: inline-block;
|
||||
width: 333px;
|
||||
clear: both;
|
||||
margin-bottom: 1em;
|
||||
|
||||
.user-image {
|
||||
float: left;
|
||||
@ -128,7 +145,30 @@
|
||||
margin-top: 3px;
|
||||
color: dark-light-choose(scale-color($primary, $lightness: 50%), scale-color($secondary, $lightness: 50%));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.user-info.small {
|
||||
width: 333px;
|
||||
}
|
||||
|
||||
.user-info.medium {
|
||||
width: 480px;
|
||||
min-height: 60px;
|
||||
|
||||
.user-image {
|
||||
width: 55px;
|
||||
}
|
||||
.user-detail {
|
||||
width: 380px;
|
||||
}
|
||||
|
||||
.username, .name {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.name {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -11,6 +11,11 @@
|
||||
padding:8px;
|
||||
font-size: 2.1em;
|
||||
}
|
||||
|
||||
.site-text-logo {
|
||||
position: relative;
|
||||
top: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
@media all
|
||||
@ -36,3 +41,9 @@ and (max-width : 570px) {
|
||||
color: dark-light-choose(scale-color($primary, $lightness: 25%), scale-color($secondary, $lightness: 75%));
|
||||
}
|
||||
}
|
||||
|
||||
header {
|
||||
#site-text-logo {
|
||||
line-height: 40px;
|
||||
}
|
||||
}
|
||||
|
||||
@ -181,10 +181,11 @@ input {
|
||||
bottom: 35px;
|
||||
}
|
||||
.submit-panel {
|
||||
width: 50%;
|
||||
// need minimum width that fits, or this'll wrap cancel under submit/create which is super bad
|
||||
width: 180px;
|
||||
position: absolute;
|
||||
display: block;
|
||||
bottom: 2px;
|
||||
bottom: 0;
|
||||
}
|
||||
}
|
||||
.category-input {
|
||||
|
||||
@ -120,7 +120,7 @@ h2#site-text-logo
|
||||
list-style: none;
|
||||
overflow: visible;
|
||||
position: relative;
|
||||
width: 40%;
|
||||
width: 45%;
|
||||
|
||||
> li > a {
|
||||
padding: 8px 10px;
|
||||
|
||||
@ -2,6 +2,12 @@
|
||||
// Discourse header
|
||||
// --------------------------------------------------
|
||||
|
||||
@media only screen and (max-width: 320px) {
|
||||
#site-text-logo {
|
||||
font-size: 18px;
|
||||
}
|
||||
}
|
||||
|
||||
.d-header {
|
||||
|
||||
#site-logo {
|
||||
@ -10,11 +16,16 @@
|
||||
|
||||
// some protection for text-only site titles
|
||||
.title {
|
||||
max-width: 130px;
|
||||
max-width: 50%;
|
||||
height: 39px;
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
text-overflow: clip;
|
||||
display: table;
|
||||
a {
|
||||
display: table-cell;
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
|
||||
.icons {
|
||||
|
||||
@ -16,7 +16,7 @@
|
||||
|
||||
.topic-post article {
|
||||
border-top: 1px solid dark-light-diff($primary, $secondary, 90%, -60%);
|
||||
padding: 6px 0;
|
||||
padding: 8px 0;
|
||||
}
|
||||
|
||||
.post-stream {
|
||||
@ -287,7 +287,7 @@ a.star {
|
||||
.btn {
|
||||
border: 0;
|
||||
padding: 0 15px;
|
||||
color: $primary;
|
||||
color: dark-light-choose(scale-color($primary, $lightness: 60%), scale-color($secondary, $lightness: 40%));
|
||||
background: blend-primary-secondary(5%);
|
||||
border-left: 1px solid dark-light-diff($primary, $secondary, 90%, -65%);
|
||||
border-top: 1px solid dark-light-diff($primary, $secondary, 90%, -65%);
|
||||
@ -486,9 +486,13 @@ span.highlighted {
|
||||
.names {
|
||||
margin: 5px 0 0 5px;
|
||||
line-height: 17px;
|
||||
span {
|
||||
span.full-name, span.user-title, span.username {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.poster-icon {
|
||||
float: right;
|
||||
}
|
||||
}
|
||||
.post-info {
|
||||
float: right;
|
||||
|
||||
@ -56,6 +56,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
.delete-account {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.checkbox-label {
|
||||
overflow: auto;
|
||||
display: block;
|
||||
@ -140,11 +144,6 @@
|
||||
|
||||
.user-main {
|
||||
|
||||
width: 100%;
|
||||
float: left;
|
||||
margin-bottom: 50px;
|
||||
|
||||
|
||||
table.group-members {
|
||||
width: 100%;
|
||||
p {
|
||||
@ -169,18 +168,15 @@
|
||||
color: $primary;
|
||||
}
|
||||
}
|
||||
.user-small {
|
||||
.user-info {
|
||||
width: 245px;
|
||||
}
|
||||
}
|
||||
|
||||
.user-content {
|
||||
padding: 10px 8px;
|
||||
background-color: $secondary;
|
||||
border: 1px solid dark-light-diff($primary, $secondary, 90%, -60%);
|
||||
margin-bottom: 10px;
|
||||
box-sizing: border-box;
|
||||
margin-top: 20px;
|
||||
margin-top: 10px;
|
||||
|
||||
.btn.right {
|
||||
float: right
|
||||
@ -393,7 +389,6 @@
|
||||
|
||||
|
||||
.user-stream {
|
||||
padding: 0 10px;
|
||||
.excerpt {
|
||||
margin: 5px 0;
|
||||
font-size: 0.929em;
|
||||
@ -596,3 +591,33 @@
|
||||
.notification-buttons {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
// mobile fixups for badges
|
||||
|
||||
.badge-card.medium {
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
.show-badge-details {
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.user-badges {
|
||||
margin-bottom: 2em;
|
||||
}
|
||||
|
||||
.show-badge-details .badge-grant-info {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.user-info.medium {
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
.user-info.medium.user-detail {
|
||||
width: 240px;
|
||||
}
|
||||
|
||||
.user-info.medium.user-image {
|
||||
width: 50px;
|
||||
}
|
||||
|
||||
@ -11,7 +11,7 @@ class Admin::EmailTemplatesController < Admin::AdminController
|
||||
"system_messages.email_reject_destination", "system_messages.email_reject_empty",
|
||||
"system_messages.email_reject_invalid_access", "system_messages.email_reject_no_account",
|
||||
"system_messages.email_reject_parsing", "system_messages.email_reject_post_error",
|
||||
"system_messages.email_reject_post_error_specified",
|
||||
"system_messages.email_reject_post_error_specified", "system_messages.email_reject_user_not_found",
|
||||
"system_messages.email_reject_reply_key", "system_messages.email_reject_topic_closed",
|
||||
"system_messages.email_reject_topic_not_found", "system_messages.email_reject_trust_level",
|
||||
"system_messages.pending_users_reminder", "system_messages.post_hidden",
|
||||
|
||||
@ -282,9 +282,13 @@ class Admin::UsersController < Admin::AdminController
|
||||
return render nothing: true, status: 404 unless SiteSetting.enable_sso
|
||||
|
||||
sso = DiscourseSingleSignOn.parse("sso=#{params[:sso]}&sig=#{params[:sig]}")
|
||||
user = sso.lookup_or_create_user
|
||||
|
||||
render_serialized(user, AdminDetailedUserSerializer, root: false)
|
||||
begin
|
||||
user = sso.lookup_or_create_user
|
||||
render_serialized(user, AdminDetailedUserSerializer, root: false)
|
||||
rescue ActiveRecord::RecordInvalid => ex
|
||||
render json: failed_json.merge(message: ex.message), status: 403
|
||||
end
|
||||
end
|
||||
|
||||
def delete_other_accounts_with_same_ip
|
||||
|
||||
@ -100,7 +100,16 @@ class ApplicationController < ActionController::Base
|
||||
|
||||
class PluginDisabled < StandardError; end
|
||||
|
||||
rescue_from Discourse::NotFound, PluginDisabled do
|
||||
# Handles requests for giant IDs that throw pg exceptions
|
||||
rescue_from RangeError do |e|
|
||||
if e.message =~ /ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Integer/
|
||||
rescue_discourse_actions(:not_found, 404)
|
||||
else
|
||||
raise e
|
||||
end
|
||||
end
|
||||
|
||||
rescue_from Discourse::NotFound, PluginDisabled do
|
||||
rescue_discourse_actions(:not_found, 404)
|
||||
end
|
||||
|
||||
|
||||
@ -2,6 +2,8 @@ class BadgesController < ApplicationController
|
||||
skip_before_filter :check_xhr, only: [:index, :show]
|
||||
|
||||
def index
|
||||
raise Discourse::NotFound unless SiteSetting.enable_badges
|
||||
|
||||
badges = Badge.all
|
||||
|
||||
if (params[:only_listable] == "true") || !request.xhr?
|
||||
@ -28,6 +30,8 @@ class BadgesController < ApplicationController
|
||||
end
|
||||
|
||||
def show
|
||||
raise Discourse::NotFound unless SiteSetting.enable_badges
|
||||
|
||||
params.require(:id)
|
||||
badge = Badge.enabled.find(params[:id])
|
||||
|
||||
|
||||
@ -3,7 +3,6 @@ class CategoryHashtagsController < ApplicationController
|
||||
|
||||
def check
|
||||
category_slugs = params[:category_slugs]
|
||||
category_slugs.each(&:downcase!)
|
||||
|
||||
ids = category_slugs.map { |category_slug| Category.query_from_hashtag_slug(category_slug).try(:id) }
|
||||
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
class GroupsController < ApplicationController
|
||||
|
||||
before_filter :ensure_logged_in, only: [:set_notifications]
|
||||
skip_before_filter :preload_json, :check_xhr, only: [:posts_feed, :mentions_feed]
|
||||
|
||||
def show
|
||||
render_serialized(find_group(:id), BasicGroupSerializer)
|
||||
@ -29,6 +30,15 @@ class GroupsController < ApplicationController
|
||||
render_serialized posts.to_a, GroupPostSerializer
|
||||
end
|
||||
|
||||
def posts_feed
|
||||
group = find_group(:group_id)
|
||||
@posts = group.posts_for(guardian).limit(50)
|
||||
@title = "#{SiteSetting.title} - #{I18n.t("rss_description.group_posts", group_name: group.name)}"
|
||||
@link = Discourse.base_url
|
||||
@description = I18n.t("rss_description.group_posts", group_name: group.name)
|
||||
render 'posts/latest', formats: [:rss]
|
||||
end
|
||||
|
||||
def topics
|
||||
group = find_group(:group_id)
|
||||
posts = group.posts_for(guardian, params[:before_post_id]).where(post_number: 1).limit(20)
|
||||
@ -41,6 +51,15 @@ class GroupsController < ApplicationController
|
||||
render_serialized posts.to_a, GroupPostSerializer
|
||||
end
|
||||
|
||||
def mentions_feed
|
||||
group = find_group(:group_id)
|
||||
@posts = group.mentioned_posts_for(guardian).limit(50)
|
||||
@title = "#{SiteSetting.title} - #{I18n.t("rss_description.group_mentions", group_name: group.name)}"
|
||||
@link = Discourse.base_url
|
||||
@description = I18n.t("rss_description.group_mentions", group_name: group.name)
|
||||
render 'posts/latest', formats: [:rss]
|
||||
end
|
||||
|
||||
def messages
|
||||
group = find_group(:group_id)
|
||||
posts = if guardian.can_see_group_messages?(group)
|
||||
|
||||
@ -1,13 +1,15 @@
|
||||
class ManifestJsonController < ApplicationController
|
||||
layout false
|
||||
skip_before_filter :preload_json, :check_xhr
|
||||
skip_before_filter :preload_json, :check_xhr, :redirect_to_login_if_required
|
||||
|
||||
def index
|
||||
manifest = {
|
||||
short_name: SiteSetting.title,
|
||||
display: 'browser',
|
||||
display: 'standalone',
|
||||
orientation: 'portrait',
|
||||
start_url: "#{Discourse.base_uri}/"
|
||||
start_url: "#{Discourse.base_uri}/",
|
||||
background_color: "##{ColorScheme.hex_for_name('secondary')}",
|
||||
theme_color: "##{ColorScheme.hex_for_name('header_background')}"
|
||||
}
|
||||
|
||||
render json: manifest.to_json
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user