Version bump

This commit is contained in:
Neil Lalonde 2016-03-29 15:13:55 -04:00
commit 1b62a7e2a1
274 changed files with 7421 additions and 6853 deletions

View File

@ -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
View File

@ -58,6 +58,9 @@ log/
# Ignore Eclipse .buildpath file
/.buildpath
# Ignore byebug history
/.byebug_history
# Ignore RubyMine settings
/.idea

View File

@ -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

View File

@ -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

View File

@ -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')

View File

@ -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;

View File

@ -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);
});
}
}
});

View File

@ -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);
});
}
}
});

View File

@ -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'),
/**

View File

@ -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>

View File

@ -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}}
&mdash;
{{/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}}
&mdash;
{{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}}

View File

@ -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}}
&mdash;
{{/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}}
&mdash;
{{/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}}

View File

@ -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}}
&mdash;
{{/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}}
&mdash;
{{/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}}

View File

@ -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}}
&mdash;
{{/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}}
&mdash;
{{/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}}

View File

@ -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>

View File

@ -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>

View File

@ -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));
}
}
});

View File

@ -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));
}
}
});

View File

@ -1,5 +0,0 @@
import AdminEmailIncomingsView from "admin/views/admin-email-incomings";
export default AdminEmailIncomingsView.extend({
templateName: "admin/templates/email-received"
});

View File

@ -1,5 +0,0 @@
import AdminEmailIncomingsView from "admin/views/admin-email-incomings";
export default AdminEmailIncomingsView.extend({
templateName: "admin/templates/email-rejected"
});

View File

@ -1,5 +0,0 @@
import AdminEmailLogsView from "admin/views/admin-email-logs";
export default AdminEmailLogsView.extend({
templateName: "admin/templates/email-sent"
});

View File

@ -1,5 +0,0 @@
import AdminEmailLogsView from "admin/views/admin-email-logs";
export default AdminEmailLogsView.extend({
templateName: "admin/templates/email-skipped"
});

View File

@ -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() {

View File

@ -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')
});

View File

@ -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');
}
});

View File

@ -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>`);
}
});

View File

@ -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(""));
}

View File

@ -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>');
}
});

View 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();
}
}
});

View File

@ -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);
}
}
}

View File

@ -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;
}
});

View File

@ -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();

View 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;
}
}
});

View File

@ -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')
});

View File

@ -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;
});

View File

@ -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')
}
});

View File

@ -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",

View File

@ -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);

View File

@ -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 {

View File

@ -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,

View File

@ -25,3 +25,5 @@ registerUnbound('format-date', function(val, params) {
return new Handlebars.SafeString(autoUpdatingRelativeAge(date, {format: format, title: title, leaveAgo: leaveAgo}));
}
});

View File

@ -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));
}

View File

@ -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
};

View File

@ -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
};

View File

@ -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);
}

View File

@ -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) {

View File

@ -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;

View File

@ -66,6 +66,10 @@ Discourse.Ajax = Em.Mixin.create({
});
}
if (args.returnXHR) {
data = { result: data, xhr: xhr };
}
Ember.run(null, resolve, data);
};

View File

@ -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() {

View File

@ -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) {

View File

@ -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.

View File

@ -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"
});

View File

@ -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;
});
}

View File

@ -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");
}
},

View File

@ -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')
})
);

View File

@ -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>

View File

@ -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>

View File

@ -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}}

View File

@ -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}}

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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}}

View File

@ -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>

View File

@ -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>

View File

@ -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"}}

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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()
});

View File

@ -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 });
}
/**

View File

@ -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');
}
});

View File

@ -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);
}
}

View File

@ -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() });
}
},

View File

@ -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];

View File

@ -0,0 +1,3 @@
//= depend_on 'client.gl.yml'
//= require locales/i18n
<%= JsLocaleHelper.output_locale(:gl) %>

View File

@ -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

View File

@ -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;
}
}
}

View File

@ -1,6 +1,10 @@
.directory {
margin-bottom: 100px;
.user-info {
margin-bottom: 0;
}
.period-chooser {
float: left;
}

View File

@ -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;
}

View File

@ -21,6 +21,7 @@
}
}
.buttons {
display: inline-flex;
margin-top: 15px;
button {

View File

@ -17,6 +17,9 @@
.title {
float: left;
a, a:visited {
color: $header_primary;
}
}
#site-logo {

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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 {

View File

@ -120,7 +120,7 @@ h2#site-text-logo
list-style: none;
overflow: visible;
position: relative;
width: 40%;
width: 45%;
> li > a {
padding: 8px 10px;

View File

@ -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 {

View File

@ -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;

View File

@ -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;
}

View File

@ -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",

View File

@ -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

View File

@ -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

View File

@ -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])

View File

@ -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) }

View File

@ -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)

View File

@ -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