Version bump
This commit is contained in:
commit
7d40cd92f8
@ -24,7 +24,7 @@ matrix:
|
||||
fast_finish: true
|
||||
|
||||
rvm:
|
||||
- 2.3.1
|
||||
- 2.3.3
|
||||
|
||||
services:
|
||||
- redis-server
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
[main]
|
||||
host = https://www.transifex.com
|
||||
lang_map = es_ES: es, fr_FR: fr, ko_KR: ko, pt_PT: pt, sk_SK: sk, vi_VN: vi
|
||||
lang_map = el_GR: el, es_ES: es, fr_FR: fr, ko_KR: ko, pt_PT: pt, sk_SK: sk, vi_VN: vi
|
||||
|
||||
[discourse-org.clientenyml]
|
||||
file_filter = config/locales/client.<lang>.yml
|
||||
|
||||
2
Gemfile
2
Gemfile
@ -92,7 +92,7 @@ gem 'pry-rails', require: false
|
||||
gem 'r2', '~> 0.2.5', require: false
|
||||
gem 'rake'
|
||||
|
||||
|
||||
gem 'thor', require: false
|
||||
gem 'rest-client'
|
||||
gem 'rinku'
|
||||
gem 'sanitize'
|
||||
|
||||
@ -206,7 +206,7 @@ GEM
|
||||
omniauth-twitter (1.3.0)
|
||||
omniauth-oauth (~> 1.1)
|
||||
rack
|
||||
onebox (1.8.1)
|
||||
onebox (1.8.2)
|
||||
fast_blank (>= 1.0.0)
|
||||
htmlentities (~> 4.3.4)
|
||||
moneta (~> 0.8)
|
||||
@ -470,6 +470,7 @@ DEPENDENCIES
|
||||
sinatra
|
||||
spork-rails
|
||||
stackprof
|
||||
thor
|
||||
timecop
|
||||
uglifier
|
||||
unf
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
import DiscourseURL from 'discourse/lib/url';
|
||||
import { ajax } from 'discourse/lib/ajax';
|
||||
|
||||
export default Ember.Controller.extend({
|
||||
@ -39,7 +38,11 @@ export default Ember.Controller.extend({
|
||||
},
|
||||
|
||||
download(backup) {
|
||||
DiscourseURL.redirectTo(backup.get('link'));
|
||||
let link = backup.get('filename');
|
||||
ajax("/admin/backups/" + link, { type: "PUT" })
|
||||
.then(() => {
|
||||
bootbox.alert(I18n.t("admin.backups.operations.download.alert"));
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
@ -48,7 +51,7 @@ export default Ember.Controller.extend({
|
||||
ajax("/admin/backups/readonly", {
|
||||
type: "PUT",
|
||||
data: { enable: enable }
|
||||
}).then(function() {
|
||||
}).then(() => {
|
||||
site.set("isReadOnly", enable);
|
||||
});
|
||||
}
|
||||
|
||||
@ -2,6 +2,7 @@ import ModalFunctionality from 'discourse/mixins/modal-functionality';
|
||||
import IncomingEmail from 'admin/models/incoming-email';
|
||||
import computed from 'ember-addons/ember-computed-decorators';
|
||||
import { longDate } from 'discourse/lib/formatter';
|
||||
import { popupAjaxError } from 'discourse/lib/ajax-error';
|
||||
|
||||
export default Ember.Controller.extend(ModalFunctionality, {
|
||||
|
||||
@ -12,6 +13,15 @@ export default Ember.Controller.extend(ModalFunctionality, {
|
||||
|
||||
load(id) {
|
||||
return IncomingEmail.find(id).then(result => this.set("model", result));
|
||||
},
|
||||
|
||||
loadFromBounced(id) {
|
||||
return IncomingEmail.findByBounced(id)
|
||||
.then(result => this.set("model", result))
|
||||
.catch(error => {
|
||||
this.send("closeModal");
|
||||
popupAjaxError(error);
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
@ -19,6 +19,11 @@ IncomingEmail.reopenClass({
|
||||
return ajax(`/admin/email/incoming/${id}.json`);
|
||||
},
|
||||
|
||||
findByBounced(id) {
|
||||
return ajax(`/admin/email/incoming_from_bounced/${id}.json`);
|
||||
},
|
||||
|
||||
|
||||
findAll(filter, offset) {
|
||||
filter = filter || {};
|
||||
offset = offset || 0;
|
||||
|
||||
@ -1,2 +1,14 @@
|
||||
import showModal from 'discourse/lib/show-modal';
|
||||
import AdminEmailLogs from 'admin/routes/admin-email-logs';
|
||||
export default AdminEmailLogs.extend({ status: "bounced" });
|
||||
|
||||
export default AdminEmailLogs.extend({
|
||||
status: "bounced",
|
||||
|
||||
actions: {
|
||||
showIncomingEmail(id) {
|
||||
showModal('admin-incoming-email', { admin: true });
|
||||
this.controllerFor("modals/admin-incoming-email").loadFromBounced(id);
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
export default Discourse.Route.extend({
|
||||
redirect: function() {
|
||||
redirect() {
|
||||
this.replaceWith('adminFlags.list', 'active');
|
||||
}
|
||||
});
|
||||
|
||||
@ -6,11 +6,6 @@ export default Discourse.Route.extend({
|
||||
this.render('admin/templates/logs/staff-action-logs', {into: 'adminLogs'});
|
||||
},
|
||||
|
||||
setupController: function(controller) {
|
||||
controller.resetFilters();
|
||||
controller.refresh();
|
||||
},
|
||||
|
||||
actions: {
|
||||
showDetailsModal(model) {
|
||||
showModal('admin-staff-action-log-details', { model, admin: true });
|
||||
|
||||
@ -28,6 +28,14 @@ export default Discourse.Route.extend({
|
||||
showSuspendModal(model) {
|
||||
showModal('admin-suspend-user', { model, admin: true });
|
||||
this.controllerFor('modal').set('modalClass', 'suspend-user-modal');
|
||||
},
|
||||
|
||||
viewActionLogs(username) {
|
||||
const controller = this.controllerFor('adminLogs.staffActionLogs');
|
||||
this.transitionTo('adminLogs.staffActionLogs').then(() => {
|
||||
controller.set('filters', Ember.Object.create());
|
||||
controller._changeFilters({ target_user: username });
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@ -261,8 +261,8 @@
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="title">{{top_referrers.title}} ({{i18n 'admin.dashboard.reports.last_30_days'}})</th>
|
||||
<th>{{number top_referrers.ytitles.num_clicks}}</th>
|
||||
<th>{{number top_referrers.ytitles.num_topics}}</th>
|
||||
<th>{{top_referrers.ytitles.num_clicks}}</th>
|
||||
<th>{{top_referrers.ytitles.num_topics}}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
{{#each top_referrers.data as |r|}}
|
||||
|
||||
@ -28,7 +28,7 @@
|
||||
{{/if}}
|
||||
</td>
|
||||
<td><a href='mailto:{{unbound l.to_address}}'>{{l.to_address}}</a></td>
|
||||
<td>{{l.email_type}}</td>
|
||||
<td><a {{action "showIncomingEmail" l.id}}>{{l.email_type}}</a></td>
|
||||
</tr>
|
||||
{{else}}
|
||||
<tr><td colspan="4">{{i18n 'admin.email.logs.none'}}</td></tr>
|
||||
|
||||
@ -7,6 +7,9 @@
|
||||
{{i18n 'admin.user.show_public_profile'}}
|
||||
{{/link-to}}
|
||||
{{/if}}
|
||||
{{#if model.can_view_action_logs}}
|
||||
{{d-button action="viewActionLogs" actionParam=model.username icon="list-alt" label="admin.user.action_logs"}}
|
||||
{{/if}}
|
||||
{{#if model.active}}
|
||||
{{#if model.can_impersonate}}
|
||||
{{d-button class="btn-danger" action="impersonate" icon="crosshairs" label="admin.impersonate.title" title="admin.impersonate.help"}}
|
||||
|
||||
@ -9,6 +9,7 @@
|
||||
|
||||
// Stuff we need to load first
|
||||
//= require ./discourse/lib/utilities
|
||||
//= require ./discourse/lib/page-visible
|
||||
//= require ./discourse/lib/ajax
|
||||
//= require ./discourse/lib/text
|
||||
//= require ./discourse/lib/hash
|
||||
|
||||
@ -68,7 +68,8 @@ export default Ember.Component.extend({
|
||||
|
||||
if (loadOnebox && loadOnebox.then) {
|
||||
loadOnebox.then( () => {
|
||||
this._updatePost(lookupCache(this.get('composer.title')));
|
||||
const v = lookupCache(this.get('composer.title'));
|
||||
this._updatePost(v ? v : link);
|
||||
}).finally(() => {
|
||||
this.set('composer.loading', false);
|
||||
Ember.run.schedule('afterRender', () => { this.$('input').putCursorAtEnd(); });
|
||||
|
||||
@ -8,6 +8,6 @@ export default Ember.Component.extend({
|
||||
}.property('tagRecord.id'),
|
||||
|
||||
href: function() {
|
||||
return '/tags/' + this.get('tagRecord.id');
|
||||
return Discourse.getURL('/tags/' + this.get('tagRecord.id'));
|
||||
}.property('tagRecord.id'),
|
||||
});
|
||||
|
||||
@ -21,5 +21,13 @@ export default buildCategoryPanel('settings', {
|
||||
{name: I18n.t('category.sort_ascending'), value: 'true'},
|
||||
{name: I18n.t('category.sort_descending'), value: 'false'}
|
||||
];
|
||||
},
|
||||
|
||||
@computed
|
||||
availableViews() {
|
||||
return [
|
||||
{name: I18n.t('filters.latest.title'), value: 'latest'},
|
||||
{name: I18n.t('filters.top.title'), value: 'top'}
|
||||
];
|
||||
}
|
||||
});
|
||||
|
||||
@ -1,11 +1,17 @@
|
||||
import { default as computed, observes } from "ember-addons/ember-computed-decorators";
|
||||
import DiscourseURL from 'discourse/lib/url';
|
||||
import { renderedConnectorsFor } from 'discourse/lib/plugin-connectors';
|
||||
|
||||
export default Ember.Component.extend({
|
||||
tagName: 'ul',
|
||||
classNameBindings: [':nav', ':nav-pills'],
|
||||
id: 'navigation-bar',
|
||||
|
||||
init() {
|
||||
this._super();
|
||||
this.set('connectors', renderedConnectorsFor("extra-nav-item", null, this));
|
||||
},
|
||||
|
||||
@computed("filterMode", "navItems")
|
||||
selectedNavItem(filterMode, navItems){
|
||||
var item = navItems.find(i => i.get('filterMode').indexOf(filterMode) === 0);
|
||||
|
||||
@ -29,7 +29,7 @@
|
||||
The list of disabled plugins is returned via the `Site` singleton.
|
||||
|
||||
**/
|
||||
import { connectorsFor } from 'discourse/lib/plugin-connectors';
|
||||
import { renderedConnectorsFor } from 'discourse/lib/plugin-connectors';
|
||||
|
||||
export default Ember.Component.extend({
|
||||
tagName: 'span',
|
||||
@ -38,14 +38,9 @@ export default Ember.Component.extend({
|
||||
init() {
|
||||
this._super();
|
||||
const name = this.get('name');
|
||||
|
||||
if (name) {
|
||||
const args = this.get('args');
|
||||
const connectors = connectorsFor(name).filter(con => {
|
||||
return con.connectorClass.shouldRender(args, this);
|
||||
});
|
||||
|
||||
this.set('connectors', connectors);
|
||||
this.set('connectors', renderedConnectorsFor(name, args, this));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@ -3,24 +3,26 @@ import { escapeExpression } from 'discourse/lib/utilities';
|
||||
|
||||
const REGEXP_BLOCKS = /(([^" \t\n\x0B\f\r]+)?(("[^"]+")?))/g;
|
||||
|
||||
const REGEXP_USERNAME_PREFIX = /(user:|@)/ig;
|
||||
const REGEXP_CATEGORY_PREFIX = /(category:|#)/ig;
|
||||
const REGEXP_GROUP_PREFIX = /group:/ig;
|
||||
const REGEXP_BADGE_PREFIX = /badge:/ig;
|
||||
const REGEXP_TAGS_PREFIX = /tags?:/ig;
|
||||
const REGEXP_IN_PREFIX = /in:/ig;
|
||||
const REGEXP_STATUS_PREFIX = /status:/ig;
|
||||
const REGEXP_MIN_POST_COUNT_PREFIX = /min_post_count:/ig;
|
||||
const REGEXP_POST_TIME_PREFIX = /(before|after):/ig;
|
||||
const REGEXP_USERNAME_PREFIX = /^(user:|@)/ig;
|
||||
const REGEXP_CATEGORY_PREFIX = /^(category:|#)/ig;
|
||||
const REGEXP_GROUP_PREFIX = /^group:/ig;
|
||||
const REGEXP_BADGE_PREFIX = /^badge:/ig;
|
||||
const REGEXP_TAGS_PREFIX = /^(tags?:|#(?=[a-z0-9\-]+::tag))/ig;
|
||||
const REGEXP_IN_PREFIX = /^in:/ig;
|
||||
const REGEXP_STATUS_PREFIX = /^status:/ig;
|
||||
const REGEXP_MIN_POST_COUNT_PREFIX = /^min_post_count:/ig;
|
||||
const REGEXP_POST_TIME_PREFIX = /^(before|after):/ig;
|
||||
const REGEXP_TAGS_REPLACE = /(^(tags?:|#(?=[a-z0-9\-]+::tag))|::tag\s?$)/ig;
|
||||
|
||||
const REGEXP_IN_MATCH = /in:(posted|watching|tracking|bookmarks|first|pinned|unpinned)/ig;
|
||||
const REGEXP_SPECIAL_IN_LIKES_MATCH = /in:likes/ig;
|
||||
const REGEXP_SPECIAL_IN_PRIVATE_MATCH = /in:private/ig;
|
||||
const REGEXP_SPECIAL_IN_WIKI_MATCH = /in:wiki/ig;
|
||||
|
||||
const REGEXP_CATEGORY_SLUG = /(\#[a-zA-Z0-9\-:]+)/ig;
|
||||
const REGEXP_CATEGORY_ID = /(category:[0-9]+)/ig;
|
||||
const REGEXP_POST_TIME_WHEN = /(before|after)/ig;
|
||||
const REGEXP_IN_MATCH = /^in:(posted|watching|tracking|bookmarks|first|pinned|unpinned)/ig;
|
||||
const REGEXP_SPECIAL_IN_LIKES_MATCH = /^in:likes/ig;
|
||||
const REGEXP_SPECIAL_IN_PRIVATE_MATCH = /^in:private/ig;
|
||||
const REGEXP_SPECIAL_IN_WIKI_MATCH = /^in:wiki/ig;
|
||||
|
||||
const REGEXP_CATEGORY_SLUG = /^(\#[a-zA-Z0-9\-:]+)/ig;
|
||||
const REGEXP_CATEGORY_ID = /^(category:[0-9]+)/ig;
|
||||
const REGEXP_POST_TIME_WHEN = /^(before|after)/ig;
|
||||
|
||||
export default Em.Component.extend({
|
||||
classNames: ['search-advanced-options'],
|
||||
@ -48,8 +50,8 @@ export default Em.Component.extend({
|
||||
|
||||
init() {
|
||||
this._super();
|
||||
this._init();
|
||||
Ember.run.scheduleOnce('afterRender', () => {
|
||||
this._init();
|
||||
this._update();
|
||||
});
|
||||
},
|
||||
@ -228,7 +230,7 @@ export default Em.Component.extend({
|
||||
|
||||
if (match.length !== 0) {
|
||||
const existingInput = _.isArray(tags) ? tags.join(',') : tags;
|
||||
const userInput = match[0].replace(REGEXP_TAGS_PREFIX, '');
|
||||
const userInput = match[0].replace(REGEXP_TAGS_REPLACE, '');
|
||||
|
||||
if (existingInput !== userInput) {
|
||||
this.set('searchedTerms.tags', (userInput.length !== 0) ? userInput.split(',') : []);
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import computed from 'ember-addons/ember-computed-decorators';
|
||||
import { on } from 'ember-addons/ember-computed-decorators';
|
||||
import TextField from 'discourse/components/text-field';
|
||||
import { applySearchAutocomplete } from "discourse/lib/search";
|
||||
|
||||
export default TextField.extend({
|
||||
@computed('searchService.searchContextEnabled')
|
||||
@ -10,10 +11,13 @@ export default TextField.extend({
|
||||
|
||||
@on("didInsertElement")
|
||||
becomeFocused() {
|
||||
const $searchInput = this.$();
|
||||
applySearchAutocomplete($searchInput, this.siteSettings);
|
||||
|
||||
if (!this.get('hasAutofocus')) { return; }
|
||||
// iOS is crazy, without this we will not be
|
||||
// at the top of the page
|
||||
$(window).scrollTop(0);
|
||||
this.$().focus();
|
||||
$searchInput.focus();
|
||||
}
|
||||
});
|
||||
|
||||
@ -1,11 +1,15 @@
|
||||
import { iconHTML } from 'discourse-common/helpers/fa-icon';
|
||||
import Combobox from 'discourse-common/components/combo-box';
|
||||
import { on, observes } from 'ember-addons/ember-computed-decorators';
|
||||
import { observes } from 'ember-addons/ember-computed-decorators';
|
||||
|
||||
export default Combobox.extend({
|
||||
none: "topic.controls",
|
||||
|
||||
@on('init')
|
||||
init() {
|
||||
this._super();
|
||||
this._createContent();
|
||||
},
|
||||
|
||||
_createContent() {
|
||||
const content = [];
|
||||
const topic = this.get('topic');
|
||||
|
||||
@ -404,13 +404,15 @@ export default Ember.Controller.extend({
|
||||
disableSubmit: Ember.computed.or("model.loading", "isUploading"),
|
||||
|
||||
save(force) {
|
||||
const composer = this.get('model');
|
||||
if (this.get("disableSubmit")) return;
|
||||
|
||||
// Clear the warning state if we're not showing the checkbox anymore
|
||||
if (!this.get('showWarning')) {
|
||||
this.set('model.isWarning', false);
|
||||
}
|
||||
|
||||
const composer = this.get('model');
|
||||
|
||||
if (composer.get('cantSubmitPost')) {
|
||||
this.set('lastValidatedAt', Date.now());
|
||||
return;
|
||||
@ -561,14 +563,17 @@ export default Ember.Controller.extend({
|
||||
return;
|
||||
}
|
||||
|
||||
this.setProperties({ showEditReason: false, editReason: null, scopedCategoryId: null });
|
||||
|
||||
// If we show the subcategory list, scope the categories drop down to
|
||||
// the category we opened the composer with.
|
||||
if (this.siteSettings.show_subcategory_list && opts.draftKey !== 'reply_as_new_topic') {
|
||||
this.set('scopedCategoryId', opts.categoryId);
|
||||
if (opts.categoryId && opts.draftKey !== 'reply_as_new_topic') {
|
||||
const category = this.site.categories.findBy('id', opts.categoryId);
|
||||
if (category && (category.get('show_subcategory_list') || category.get('parentCategory.show_subcategory_list'))) {
|
||||
this.set('scopedCategoryId', opts.categoryId);
|
||||
}
|
||||
}
|
||||
|
||||
this.setProperties({ showEditReason: false, editReason: null });
|
||||
|
||||
// If we want a different draft than the current composer, close it and clear our model.
|
||||
if (composerModel &&
|
||||
opts.draftKey !== composerModel.draftKey &&
|
||||
|
||||
@ -1,17 +1,15 @@
|
||||
import { ajax } from 'discourse/lib/ajax';
|
||||
import debounce from 'discourse/lib/debounce';
|
||||
import ModalFunctionality from 'discourse/mixins/modal-functionality';
|
||||
import { setting } from 'discourse/lib/computed';
|
||||
import { on } from 'ember-addons/ember-computed-decorators';
|
||||
import { emailValid } from 'discourse/lib/utilities';
|
||||
import InputValidation from 'discourse/models/input-validation';
|
||||
import PasswordValidation from "discourse/mixins/password-validation";
|
||||
import UsernameValidation from "discourse/mixins/username-validation";
|
||||
|
||||
export default Ember.Controller.extend(ModalFunctionality, PasswordValidation, {
|
||||
export default Ember.Controller.extend(ModalFunctionality, PasswordValidation, UsernameValidation, {
|
||||
login: Ember.inject.controller(),
|
||||
|
||||
uniqueUsernameValidation: null,
|
||||
globalNicknameExists: false,
|
||||
complete: false,
|
||||
accountPasswordConfirm: 0,
|
||||
accountChallenge: 0,
|
||||
@ -24,8 +22,6 @@ export default Ember.Controller.extend(ModalFunctionality, PasswordValidation, {
|
||||
hasAuthOptions: Em.computed.notEmpty('authOptions'),
|
||||
canCreateLocal: setting('enable_local_logins'),
|
||||
showCreateForm: Em.computed.or('hasAuthOptions', 'canCreateLocal'),
|
||||
maxUsernameLength: setting('max_username_length'),
|
||||
minUsernameLength: setting('min_username_length'),
|
||||
|
||||
resetForm() {
|
||||
// We wrap the fields in a structure so we can assign a value
|
||||
@ -35,7 +31,6 @@ export default Ember.Controller.extend(ModalFunctionality, PasswordValidation, {
|
||||
accountUsername: '',
|
||||
accountPassword: '',
|
||||
authOptions: null,
|
||||
globalNicknameExists: false,
|
||||
complete: false,
|
||||
formSubmitted: false,
|
||||
rejectedEmails: [],
|
||||
@ -167,128 +162,6 @@ export default Ember.Controller.extend(ModalFunctionality, PasswordValidation, {
|
||||
}
|
||||
}.observes('emailValidation', 'accountEmail'),
|
||||
|
||||
fetchExistingUsername: debounce(function() {
|
||||
const self = this;
|
||||
Discourse.User.checkUsername(null, this.get('accountEmail')).then(function(result) {
|
||||
if (result.suggestion && (Ember.isEmpty(self.get('accountUsername')) || self.get('accountUsername') === self.get('authOptions.username'))) {
|
||||
self.set('accountUsername', result.suggestion);
|
||||
self.set('prefilledUsername', result.suggestion);
|
||||
}
|
||||
});
|
||||
}, 500),
|
||||
|
||||
usernameMatch: function() {
|
||||
if (this.usernameNeedsToBeValidatedWithEmail()) {
|
||||
if (this.get('emailValidation.failed')) {
|
||||
if (this.shouldCheckUsernameMatch()) {
|
||||
return this.set('uniqueUsernameValidation', InputValidation.create({
|
||||
failed: true,
|
||||
reason: I18n.t('user.username.enter_email')
|
||||
}));
|
||||
} else {
|
||||
return this.set('uniqueUsernameValidation', InputValidation.create({ failed: true }));
|
||||
}
|
||||
} else if (this.shouldCheckUsernameMatch()) {
|
||||
this.set('uniqueUsernameValidation', InputValidation.create({
|
||||
failed: true,
|
||||
reason: I18n.t('user.username.checking')
|
||||
}));
|
||||
return this.checkUsernameAvailability();
|
||||
}
|
||||
}
|
||||
}.observes('accountEmail'),
|
||||
|
||||
basicUsernameValidation: function() {
|
||||
this.set('uniqueUsernameValidation', null);
|
||||
|
||||
if (this.get('accountUsername') === this.get('prefilledUsername')) {
|
||||
return InputValidation.create({
|
||||
ok: true,
|
||||
reason: I18n.t('user.username.prefilled')
|
||||
});
|
||||
}
|
||||
|
||||
// If blank, fail without a reason
|
||||
if (Ember.isEmpty(this.get('accountUsername'))) {
|
||||
return InputValidation.create({
|
||||
failed: true
|
||||
});
|
||||
}
|
||||
|
||||
// If too short
|
||||
if (this.get('accountUsername').length < Discourse.SiteSettings.min_username_length) {
|
||||
return InputValidation.create({
|
||||
failed: true,
|
||||
reason: I18n.t('user.username.too_short')
|
||||
});
|
||||
}
|
||||
|
||||
// If too long
|
||||
if (this.get('accountUsername').length > this.get('maxUsernameLength')) {
|
||||
return InputValidation.create({
|
||||
failed: true,
|
||||
reason: I18n.t('user.username.too_long')
|
||||
});
|
||||
}
|
||||
|
||||
this.checkUsernameAvailability();
|
||||
// Let's check it out asynchronously
|
||||
return InputValidation.create({
|
||||
failed: true,
|
||||
reason: I18n.t('user.username.checking')
|
||||
});
|
||||
}.property('accountUsername'),
|
||||
|
||||
shouldCheckUsernameMatch: function() {
|
||||
return !Ember.isEmpty(this.get('accountUsername')) && this.get('accountUsername').length >= this.get('minUsernameLength');
|
||||
},
|
||||
|
||||
checkUsernameAvailability: debounce(function() {
|
||||
const _this = this;
|
||||
if (this.shouldCheckUsernameMatch()) {
|
||||
return Discourse.User.checkUsername(this.get('accountUsername'), this.get('accountEmail')).then(function(result) {
|
||||
_this.set('isDeveloper', false);
|
||||
if (result.available) {
|
||||
if (result.is_developer) {
|
||||
_this.set('isDeveloper', true);
|
||||
}
|
||||
return _this.set('uniqueUsernameValidation', InputValidation.create({
|
||||
ok: true,
|
||||
reason: I18n.t('user.username.available')
|
||||
}));
|
||||
} else {
|
||||
if (result.suggestion) {
|
||||
return _this.set('uniqueUsernameValidation', InputValidation.create({
|
||||
failed: true,
|
||||
reason: I18n.t('user.username.not_available', result)
|
||||
}));
|
||||
} else if (result.errors) {
|
||||
return _this.set('uniqueUsernameValidation', InputValidation.create({
|
||||
failed: true,
|
||||
reason: result.errors.join(' ')
|
||||
}));
|
||||
} else {
|
||||
return _this.set('uniqueUsernameValidation', InputValidation.create({
|
||||
failed: true,
|
||||
reason: I18n.t('user.username.enter_email')
|
||||
}));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}, 500),
|
||||
|
||||
// Actually wait for the async name check before we're 100% sure we're good to go
|
||||
usernameValidation: function() {
|
||||
const basicValidation = this.get('basicUsernameValidation');
|
||||
const uniqueUsername = this.get('uniqueUsernameValidation');
|
||||
return uniqueUsername ? uniqueUsername : basicValidation;
|
||||
}.property('uniqueUsernameValidation', 'basicUsernameValidation'),
|
||||
|
||||
usernameNeedsToBeValidatedWithEmail() {
|
||||
return( this.get('globalNicknameExists') || false );
|
||||
},
|
||||
|
||||
@on('init')
|
||||
fetchConfirmationValue() {
|
||||
return ajax('/users/hp.json').then(json => {
|
||||
|
||||
@ -7,7 +7,9 @@ export const queryParams = {
|
||||
search: { replace: true, refreshModel: true },
|
||||
max_posts: { replace: true, refreshModel: true },
|
||||
q: { replace: true, refreshModel: true },
|
||||
tags: { replace: true }
|
||||
tags: { replace: true },
|
||||
before: { replace: true, refreshModel: true},
|
||||
bumped_before: { replace: true, refreshModel: true}
|
||||
};
|
||||
|
||||
// Basic controller options
|
||||
@ -19,4 +21,14 @@ const controllerOpts = {
|
||||
// Aliases for the values
|
||||
controllerOpts.queryParams.forEach(p => controllerOpts[p] = Ember.computed.alias(`discoveryTopics.${p}`));
|
||||
|
||||
export default Ember.Controller.extend(controllerOpts);
|
||||
const Controller = Ember.Controller.extend(controllerOpts);
|
||||
|
||||
export const addDiscoveryQueryParam = function(p, opts) {
|
||||
queryParams[p] = opts;
|
||||
const cOpts = {};
|
||||
cOpts[p] = Ember.computed.alias(`discoveryTopics.${p}`);
|
||||
cOpts["queryParams"] = Object.keys(queryParams);
|
||||
Controller.reopen(cOpts);
|
||||
};
|
||||
|
||||
export default Controller;
|
||||
|
||||
@ -19,6 +19,10 @@ const controllerOpts = {
|
||||
expandGloballyPinned: false,
|
||||
expandAllPinned: false,
|
||||
|
||||
resetParams() {
|
||||
this.setProperties({ order: "default", ascending: false });
|
||||
},
|
||||
|
||||
actions: {
|
||||
|
||||
changeSort(sortBy) {
|
||||
@ -43,8 +47,7 @@ const controllerOpts = {
|
||||
|
||||
refresh() {
|
||||
const filter = this.get('model.filter');
|
||||
|
||||
this.setProperties({ order: "default", ascending: false });
|
||||
this.resetParams();
|
||||
|
||||
// Don't refresh if we're still loading
|
||||
if (this.get('discovery.loading')) { return; }
|
||||
|
||||
@ -0,0 +1,66 @@
|
||||
import { default as computed } from 'ember-addons/ember-computed-decorators';
|
||||
import getUrl from 'discourse-common/lib/get-url';
|
||||
import DiscourseURL from 'discourse/lib/url';
|
||||
import { ajax } from 'discourse/lib/ajax';
|
||||
import PasswordValidation from "discourse/mixins/password-validation";
|
||||
import UsernameValidation from "discourse/mixins/username-validation";
|
||||
import { findAll as findLoginMethods } from 'discourse/models/login-method';
|
||||
|
||||
export default Ember.Controller.extend(PasswordValidation, UsernameValidation, {
|
||||
invitedBy: Ember.computed.alias('model.invited_by'),
|
||||
email: Ember.computed.alias('model.email'),
|
||||
accountUsername: Ember.computed.alias('model.username'),
|
||||
passwordRequired: Ember.computed.notEmpty('accountPassword'),
|
||||
successMessage: null,
|
||||
errorMessage: null,
|
||||
inviteImageUrl: getUrl('/images/envelope.svg'),
|
||||
|
||||
@computed
|
||||
welcomeTitle() {
|
||||
return I18n.t('invites.welcome_to', {site_name: this.siteSettings.title});
|
||||
},
|
||||
|
||||
@computed('email')
|
||||
yourEmailMessage(email) {
|
||||
return I18n.t('invites.your_email', {email: email});
|
||||
},
|
||||
|
||||
@computed
|
||||
externalAuthsEnabled() {
|
||||
return findLoginMethods(this.siteSettings, this.capabilities, this.site.isMobileDevice).length > 0;
|
||||
},
|
||||
|
||||
@computed('usernameValidation.failed', 'passwordValidation.failed')
|
||||
submitDisabled(usernameFailed, passwordFailed) {
|
||||
return usernameFailed || passwordFailed;
|
||||
},
|
||||
|
||||
actions: {
|
||||
submit() {
|
||||
ajax({
|
||||
url: `/invites/show/${this.get('model.token')}.json`,
|
||||
type: 'PUT',
|
||||
data: {
|
||||
username: this.get('accountUsername'),
|
||||
password: this.get('accountPassword')
|
||||
}
|
||||
}).then(result => {
|
||||
if (result.success) {
|
||||
this.set('successMessage', result.message || I18n.t('invites.success'));
|
||||
this.set('redirectTo', result.redirect_to);
|
||||
DiscourseURL.redirectTo(result.redirect_to || '/');
|
||||
} else {
|
||||
if (result.errors && result.errors.password && result.errors.password.length > 0) {
|
||||
this.get('rejectedPasswords').pushObject(this.get('accountPassword'));
|
||||
this.get('rejectedPasswordsMessages').set(this.get('accountPassword'), result.errors.password[0]);
|
||||
}
|
||||
if (result.message) {
|
||||
this.set('errorMessage', result.message);
|
||||
}
|
||||
}
|
||||
}).catch(response => {
|
||||
throw response;
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -6,9 +6,13 @@ import { findAll } from 'discourse/models/login-method';
|
||||
import { escape } from 'pretty-text/sanitizer';
|
||||
|
||||
// This is happening outside of the app via popup
|
||||
const AuthErrors =
|
||||
['requires_invite', 'awaiting_approval', 'awaiting_confirmation', 'admin_not_allowed_from_ip_address',
|
||||
'not_allowed_from_ip_address'];
|
||||
const AuthErrors = [
|
||||
'requires_invite',
|
||||
'awaiting_approval',
|
||||
'awaiting_activation',
|
||||
'admin_not_allowed_from_ip_address',
|
||||
'not_allowed_from_ip_address'
|
||||
];
|
||||
|
||||
export default Ember.Controller.extend(ModalFunctionality, {
|
||||
|
||||
|
||||
@ -1,11 +1,9 @@
|
||||
import computed from "ember-addons/ember-computed-decorators";
|
||||
import NavigationDefaultController from 'discourse/controllers/navigation/default';
|
||||
import { setting } from 'discourse/lib/computed';
|
||||
|
||||
export default NavigationDefaultController.extend({
|
||||
subcategoryListSetting: setting('show_subcategory_list'),
|
||||
showingParentCategory: Em.computed.none('category.parentCategory'),
|
||||
showingSubcategoryList: Em.computed.and('subcategoryListSetting', 'showingParentCategory'),
|
||||
showingSubcategoryList: Em.computed.and('category.show_subcategory_list', 'showingParentCategory'),
|
||||
|
||||
@computed("showingSubcategoryList", "category", "noSubcategories")
|
||||
navItems(showingSubcategoryList, category, noSubcategories) {
|
||||
|
||||
@ -15,6 +15,7 @@ addBulkButton('showNotificationLevel', 'notification_level');
|
||||
addBulkButton('resetRead', 'reset_read');
|
||||
addBulkButton('unlistTopics', 'unlist_topics');
|
||||
addBulkButton('showTagTopics', 'change_tags');
|
||||
addBulkButton('showAppendTagTopics', 'append_tags');
|
||||
|
||||
// Modal for performing bulk actions on topics
|
||||
export default Ember.Controller.extend(ModalFunctionality, {
|
||||
@ -78,6 +79,9 @@ export default Ember.Controller.extend(ModalFunctionality, {
|
||||
actions: {
|
||||
showTagTopics() {
|
||||
this.set('tags', '');
|
||||
this.set('action', 'changeTags');
|
||||
this.set('label', 'change_tags');
|
||||
this.set('title', 'choose_new_tags');
|
||||
this.send('changeBulkTemplate', 'bulk-tag');
|
||||
},
|
||||
|
||||
@ -85,6 +89,18 @@ export default Ember.Controller.extend(ModalFunctionality, {
|
||||
this.performAndRefresh({type: 'change_tags', tags: this.get('tags')});
|
||||
},
|
||||
|
||||
showAppendTagTopics() {
|
||||
this.set('tags', '');
|
||||
this.set('action', 'appendTags');
|
||||
this.set('label', 'append_tags');
|
||||
this.set('title', 'choose_append_tags');
|
||||
this.send('changeBulkTemplate', 'bulk-tag');
|
||||
},
|
||||
|
||||
appendTags() {
|
||||
this.performAndRefresh({type: 'append_tags', tags: this.get('tags')});
|
||||
},
|
||||
|
||||
showChangeCategory() {
|
||||
this.send('changeBulkTemplate', 'modal/bulk-change-category');
|
||||
this.set('modal.modalClass', 'topic-bulk-actions-modal full');
|
||||
|
||||
@ -313,6 +313,7 @@ export default Ember.Controller.extend(SelectedPostsCount, BufferedContent, {
|
||||
|
||||
const quoteState = this.get('quoteState');
|
||||
const postStream = this.get('model.postStream');
|
||||
if (!postStream) return;
|
||||
const quotedPost = postStream.findLoadedPost(quoteState.postId);
|
||||
const quotedText = Quote.build(quotedPost, quoteState.buffer);
|
||||
|
||||
@ -912,6 +913,7 @@ export default Ember.Controller.extend(SelectedPostsCount, BufferedContent, {
|
||||
if (data.reload_topic) {
|
||||
topic.reload().then(() => {
|
||||
this.send('postChangedRoute', topic.get('post_number') || 1);
|
||||
this.appEvents.trigger('header:show-topic', topic);
|
||||
});
|
||||
} else {
|
||||
if (topic.get('isPrivateMessage') &&
|
||||
|
||||
@ -0,0 +1,6 @@
|
||||
import { registerUnbound } from 'discourse-common/lib/helpers';
|
||||
import renderTags from 'discourse/lib/render-tags';
|
||||
|
||||
export default registerUnbound('discourse-tags', function(topic, params) {
|
||||
return new Handlebars.SafeString(renderTags(topic, params));
|
||||
});
|
||||
@ -4,10 +4,31 @@ export default {
|
||||
name: 'localization',
|
||||
after: 'inject-objects',
|
||||
|
||||
initialize: function(container) {
|
||||
enableVerboseLocalization() {
|
||||
let counter = 0;
|
||||
let keys = {};
|
||||
let t = I18n.t;
|
||||
|
||||
I18n.noFallbacks = true;
|
||||
|
||||
I18n.t = I18n.translate = function(scope, value){
|
||||
let current = keys[scope];
|
||||
if (!current) {
|
||||
current = keys[scope] = ++counter;
|
||||
let message = "Translation #" + current + ": " + scope;
|
||||
if (!_.isEmpty(value)) {
|
||||
message += ", parameters: " + JSON.stringify(value);
|
||||
}
|
||||
Em.Logger.info(message);
|
||||
}
|
||||
return t.apply(I18n, [scope, value]) + " (#" + current + ")";
|
||||
};
|
||||
},
|
||||
|
||||
initialize(container) {
|
||||
const siteSettings = container.lookup('site-settings:main');
|
||||
if (siteSettings.verbose_localization) {
|
||||
I18n.enable_verbose_localization();
|
||||
this.enableVerboseLocalization();
|
||||
}
|
||||
|
||||
// Merge any overrides into our object
|
||||
@ -16,24 +37,26 @@ export default {
|
||||
const v = overrides[k];
|
||||
|
||||
// Special case: Message format keys are functions
|
||||
if (/\_MF$/.test(k)) {
|
||||
if (/_MF$/.test(k)) {
|
||||
k = k.replace(/^[a-z_]*js\./, '');
|
||||
I18n._compiledMFs[k] = new Function('transKey', `return (${v})(transKey);`);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
k = k.replace('admin_js', 'js');
|
||||
|
||||
const segs = k.split('.');
|
||||
|
||||
let node = I18n.translations[I18n.locale];
|
||||
let i = 0;
|
||||
for (; node && i<segs.length-1; i++) {
|
||||
|
||||
for (; i < segs.length - 1; i++) {
|
||||
if (!(segs[i] in node)) node[segs[i]] = {};
|
||||
node = node[segs[i]];
|
||||
}
|
||||
|
||||
if (node && i === segs.length-1) {
|
||||
node[segs[segs.length-1]] = v;
|
||||
}
|
||||
node[segs[segs.length-1]] = v;
|
||||
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
// Initialize the message bus to receive messages.
|
||||
import pageVisible from 'discourse/lib/page-visible';
|
||||
|
||||
export default {
|
||||
name: "message-bus",
|
||||
after: 'inject-objects',
|
||||
@ -36,9 +38,21 @@ export default {
|
||||
messageBus.ajax = function(opts) {
|
||||
opts.headers = opts.headers || {};
|
||||
opts.headers['X-Shared-Session-Key'] = $('meta[name=shared_session_key]').attr('content');
|
||||
if (pageVisible()) {
|
||||
opts.headers['Discourse-Visible'] = "true";
|
||||
}
|
||||
return $.ajax(opts);
|
||||
};
|
||||
} else {
|
||||
|
||||
messageBus.ajax = function(opts) {
|
||||
opts.headers = opts.headers || {};
|
||||
if (pageVisible()) {
|
||||
opts.headers['Discourse-Visible'] = "true";
|
||||
}
|
||||
return $.ajax(opts);
|
||||
};
|
||||
|
||||
messageBus.baseUrl = Discourse.getURL('/');
|
||||
}
|
||||
|
||||
|
||||
@ -55,7 +55,7 @@ export default {
|
||||
const staleIndex = _.findIndex(oldNotifications, {id: lastNotification.id});
|
||||
|
||||
if (staleIndex === -1) {
|
||||
// this gets a bit tricky, uread pms are bumped to front
|
||||
// this gets a bit tricky, unread pms are bumped to front
|
||||
let insertPosition = 0;
|
||||
if (lastNotification.notification_type !== 6) {
|
||||
insertPosition = _.findIndex(oldNotifications, n => n.notification_type !== 6 || n.read);
|
||||
|
||||
@ -15,7 +15,7 @@ export default {
|
||||
|
||||
if (currentUser) {
|
||||
const username = currentUser.get('username');
|
||||
DiscourseURL.rewrite(new RegExp(`^/users/${username}/?$`, "i"), `/users/${username}/summary`);
|
||||
DiscourseURL.rewrite(new RegExp(`^/users/${username}/?$`, "i"), `/users/${username}/activity`);
|
||||
}
|
||||
|
||||
DiscourseURL.rewrite(/^\/users\/([^\/]+)\/?$/, "/users/$1/activity");
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
import pageVisible from 'discourse/lib/page-visible';
|
||||
|
||||
let _trackView = false;
|
||||
let _transientHeader = null;
|
||||
|
||||
@ -14,6 +16,7 @@ export function viewTrackingRequired() {
|
||||
for performance reasons. Also automatically adjusts the URL to support installs
|
||||
in subfolders.
|
||||
**/
|
||||
|
||||
export function ajax() {
|
||||
let url, args;
|
||||
let ajaxObj;
|
||||
@ -47,6 +50,10 @@ export function ajax() {
|
||||
args.headers['Discourse-Track-View'] = "true";
|
||||
}
|
||||
|
||||
if (pageVisible()) {
|
||||
args.headers['Discourse-Visible'] = "true";
|
||||
}
|
||||
|
||||
args.success = (data, textStatus, xhr) => {
|
||||
if (xhr.getResponseHeader('Discourse-Readonly')) {
|
||||
Ember.run(() => Discourse.Site.currentProp('isReadOnly', true));
|
||||
|
||||
@ -82,7 +82,7 @@ export default function(options) {
|
||||
let prevTerm = null;
|
||||
|
||||
// input is handled differently
|
||||
const isInput = this[0].tagName === "INPUT";
|
||||
const isInput = this[0].tagName === "INPUT" && !options.treatAsTextarea;
|
||||
let inputSelectedItems = [];
|
||||
|
||||
function closeAutocomplete() {
|
||||
@ -175,8 +175,10 @@ export default function(options) {
|
||||
wrap.width(width);
|
||||
}
|
||||
|
||||
if(options.single) {
|
||||
this.css("width","100%");
|
||||
if(options.single && !options.width) {
|
||||
this.css("width", "100%");
|
||||
} else if (options.width) {
|
||||
this.css("width", options.width);
|
||||
} else {
|
||||
this.width(150);
|
||||
}
|
||||
@ -238,6 +240,7 @@ export default function(options) {
|
||||
var pos = null;
|
||||
var vOffset = 0;
|
||||
var hOffset = 0;
|
||||
|
||||
if (isInput) {
|
||||
pos = {
|
||||
left: 0,
|
||||
@ -250,7 +253,9 @@ export default function(options) {
|
||||
pos: completeStart,
|
||||
key: options.key
|
||||
});
|
||||
|
||||
hOffset = 27;
|
||||
if (options.treatAsTextarea) vOffset = -32;
|
||||
}
|
||||
div.css({
|
||||
left: "-1000px"
|
||||
@ -258,7 +263,7 @@ export default function(options) {
|
||||
|
||||
me.parent().append(div);
|
||||
|
||||
if (!isInput) {
|
||||
if (!isInput && !options.treatAsTextarea) {
|
||||
vOffset = div.height();
|
||||
|
||||
if ((window.innerHeight - me.outerHeight() - $("header.d-header").innerHeight()) < vOffset) {
|
||||
|
||||
12
app/assets/javascripts/discourse/lib/page-visible.js.es6
Normal file
12
app/assets/javascripts/discourse/lib/page-visible.js.es6
Normal file
@ -0,0 +1,12 @@
|
||||
// for android we test webkit
|
||||
var hiddenProperty = document.hidden !== undefined ? "hidden" : (
|
||||
document.webkitHidden !== undefined ? "webkitHidden" : undefined
|
||||
);
|
||||
|
||||
export default function() {
|
||||
if (hiddenProperty !== undefined){
|
||||
return !document[hiddenProperty];
|
||||
} else {
|
||||
return document && document.hasFocus;
|
||||
}
|
||||
};
|
||||
@ -13,9 +13,14 @@ import { addFlagProperty } from 'discourse/components/site-header';
|
||||
import { addPopupMenuOptionsCallback } from 'discourse/controllers/composer';
|
||||
import { extraConnectorClass } from 'discourse/lib/plugin-connectors';
|
||||
import { addPostSmallActionIcon } from 'discourse/widgets/post-small-action';
|
||||
import { addDiscoveryQueryParam } from 'discourse/controllers/discovery-sortable';
|
||||
import { addTagsHtmlCallback } from 'discourse/lib/render-tags';
|
||||
import { addUserMenuGlyph } from 'discourse/widgets/user-menu';
|
||||
import { addPostClassesCallback } from 'discourse/widgets/post';
|
||||
import { addPostTransformCallback } from 'discourse/widgets/post-stream';
|
||||
|
||||
// If you add any methods to the API ensure you bump up this number
|
||||
const PLUGIN_API_VERSION = 0.8;
|
||||
const PLUGIN_API_VERSION = '0.8.5';
|
||||
|
||||
class PluginApi {
|
||||
constructor(version, container) {
|
||||
@ -377,12 +382,108 @@ class PluginApi {
|
||||
addPostSmallActionIcon(key, icon) {
|
||||
addPostSmallActionIcon(key, icon);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register an additional query param with topic discovery,
|
||||
* this allows for filters on the topic list
|
||||
*
|
||||
**/
|
||||
addDiscoveryQueryParam(param, options) {
|
||||
addDiscoveryQueryParam(param, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a callback to be called every time tags render
|
||||
* highest priority callbacks are called first
|
||||
* example:
|
||||
*
|
||||
* callback = function(topic, params) {
|
||||
* if (topic.get("created_at") < "2000-00-01") {
|
||||
* return "<span class='discourse-tag'>ANCIENT</span>"
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* api.addTagsHtmlCallback(callback, {priority: 100});
|
||||
*
|
||||
**/
|
||||
addTagsHtmlCallback(callback, options) {
|
||||
addTagsHtmlCallback(callback, options);
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds a glyph to user menu after bookmarks
|
||||
* WARNING: there is limited space there
|
||||
*
|
||||
* example:
|
||||
*
|
||||
* api.addUserMenuGlyph({
|
||||
* label: 'awesome.label',
|
||||
* className: 'my-class',
|
||||
* icon: 'my-icon',
|
||||
* href: `/some/path`
|
||||
* });
|
||||
*
|
||||
*/
|
||||
addUserMenuGlyph(glyph) {
|
||||
addUserMenuGlyph(glyph);
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds a callback to be called before rendering any post that
|
||||
* that returns custom classes to add to the post
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* addPostClassesCallback((atts) => {if (atts.post_number == 1) return ["first"];})
|
||||
**/
|
||||
addPostClassesCallback(callback) {
|
||||
addPostClassesCallback(callback);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Adds a callback to be executed on the "transformed" post that is passed to the post
|
||||
* widget.
|
||||
*
|
||||
* This allows you to apply transformations on the actual post that is about to be rendered.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* addPostTransformCallback((t)=>{
|
||||
* // post number 7 is overrated, don't show it ever
|
||||
* if (t.post_number === 7) { t.cooked = ""; }
|
||||
* })
|
||||
*/
|
||||
addPostTransformCallback(callback) {
|
||||
addPostTransformCallback(callback);
|
||||
}
|
||||
}
|
||||
|
||||
let _pluginv01;
|
||||
|
||||
|
||||
// from http://stackoverflow.com/questions/6832596/how-to-compare-software-version-number-using-js-only-number
|
||||
function cmpVersions (a, b) {
|
||||
var i, diff;
|
||||
var regExStrip0 = /(\.0+)+$/;
|
||||
var segmentsA = a.replace(regExStrip0, '').split('.');
|
||||
var segmentsB = b.replace(regExStrip0, '').split('.');
|
||||
var l = Math.min(segmentsA.length, segmentsB.length);
|
||||
|
||||
for (i = 0; i < l; i++) {
|
||||
diff = parseInt(segmentsA[i], 10) - parseInt(segmentsB[i], 10);
|
||||
if (diff) {
|
||||
return diff;
|
||||
}
|
||||
}
|
||||
return segmentsA.length - segmentsB.length;
|
||||
}
|
||||
|
||||
|
||||
|
||||
function getPluginApi(version) {
|
||||
version = parseFloat(version);
|
||||
if (version <= PLUGIN_API_VERSION) {
|
||||
version = version.toString();
|
||||
if (cmpVersions(version,PLUGIN_API_VERSION) <= 0) {
|
||||
if (!_pluginv01) {
|
||||
_pluginv01 = new PluginApi(version, Discourse.__container__);
|
||||
}
|
||||
|
||||
@ -92,6 +92,13 @@ export function connectorsFor(outletName) {
|
||||
return _connectorCache[outletName] || [];
|
||||
}
|
||||
|
||||
export function renderedConnectorsFor(outletName, args, context) {
|
||||
return connectorsFor(outletName).filter(con => {
|
||||
return con.connectorClass.shouldRender(args, context);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
export function rawConnectorsFor(outletName) {
|
||||
if (!_rawConnectorCache) { buildRawConnectorCache(); }
|
||||
return _rawConnectorCache[outletName] || [];
|
||||
|
||||
@ -1,5 +1,3 @@
|
||||
import { h } from 'virtual-dom';
|
||||
|
||||
export default function renderTag(tag, params) {
|
||||
params = params || {};
|
||||
tag = Handlebars.Utils.escapeExpression(tag);
|
||||
@ -19,19 +17,3 @@ export default function renderTag(tag, params) {
|
||||
|
||||
return val;
|
||||
};
|
||||
|
||||
export function tagNode(tag, params) {
|
||||
const classes = ['tag-' + tag, 'discourse-tag'];
|
||||
const tagName = params.tagName || "a";
|
||||
|
||||
if (Discourse.SiteSettings.tag_style || params.style) {
|
||||
classes.push(params.style || Discourse.SiteSettings.tag_style);
|
||||
}
|
||||
|
||||
if (tagName === 'a') {
|
||||
const href = Discourse.getURL(`/tags/${tag}`);
|
||||
return h(tagName, { className: classes.join(' '), attributes: { href } }, tag);
|
||||
} else {
|
||||
return h(tagName, { className: classes.join(' ') }, tag);
|
||||
}
|
||||
}
|
||||
|
||||
57
app/assets/javascripts/discourse/lib/render-tags.js.es6
Normal file
57
app/assets/javascripts/discourse/lib/render-tags.js.es6
Normal file
@ -0,0 +1,57 @@
|
||||
import renderTag from 'discourse/lib/render-tag';
|
||||
|
||||
let callbacks = null;
|
||||
let priorities = null;
|
||||
|
||||
export function addTagsHtmlCallback(callback, options) {
|
||||
callbacks = callbacks || [];
|
||||
priorities = priorities || [];
|
||||
const priority = (options && options.priority) || 0;
|
||||
|
||||
let i = 0;
|
||||
while(i < priorities.length && priorities[i] > priority) {
|
||||
i += 1;
|
||||
}
|
||||
|
||||
priorities.splice(i, 0, priority);
|
||||
callbacks.splice(i, 0, callback);
|
||||
};
|
||||
|
||||
export default function(topic, params){
|
||||
let tags = topic.tags;
|
||||
let buffer = "";
|
||||
|
||||
if (params && params.mode === "list") {
|
||||
tags = topic.get("visibleListTags");
|
||||
}
|
||||
|
||||
let customHtml = null;
|
||||
if (callbacks) {
|
||||
callbacks.forEach((c) => {
|
||||
const html = c(topic, params);
|
||||
if (html) {
|
||||
if (customHtml) {
|
||||
customHtml += html;
|
||||
} else {
|
||||
customHtml = html;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (customHtml || (tags && tags.length > 0)) {
|
||||
buffer = "<div class='discourse-tags'>";
|
||||
if (tags) {
|
||||
for(let i=0; i<tags.length; i++){
|
||||
buffer += renderTag(tags[i]);
|
||||
}
|
||||
}
|
||||
|
||||
if (customHtml) {
|
||||
buffer += customHtml;
|
||||
}
|
||||
|
||||
buffer += "</div>";
|
||||
}
|
||||
return buffer;
|
||||
};
|
||||
@ -1,4 +1,4 @@
|
||||
function applicable() {
|
||||
export function isAppleDevice() {
|
||||
// IE has no DOMNodeInserted so can not get this hack despite saying it is like iPhone
|
||||
// This will apply hack on all iDevices
|
||||
return navigator.userAgent.match(/(iPad|iPhone|iPod)/g) &&
|
||||
@ -7,19 +7,15 @@ function applicable() {
|
||||
}
|
||||
|
||||
|
||||
// we can't tell what the actual visible window height is
|
||||
// because we cannot account for the height of the mobile keyboard
|
||||
// and any other mobile autocomplete UI that may appear
|
||||
// so let's be conservative here rather than trying to max out every
|
||||
// available pixel of height for the editor
|
||||
function calcHeight(composingTopic) {
|
||||
const winHeight = window.innerHeight;
|
||||
|
||||
// Hard code some known iOS resolutions
|
||||
switch(winHeight) {
|
||||
case 460: return composingTopic ? 250 : 260;
|
||||
case 559: return composingTopic ? 325 : 308;
|
||||
case 627:
|
||||
case 628: return 360;
|
||||
}
|
||||
|
||||
const ratio = composingTopic ? 0.54 : 0.6;
|
||||
const min = composingTopic ? 300 : 350;
|
||||
const ratio = composingTopic ? 0.45 : 0.45;
|
||||
const min = composingTopic ? 300 : 300;
|
||||
return Math.max(parseInt(winHeight*ratio), min);
|
||||
}
|
||||
|
||||
@ -32,7 +28,7 @@ export function isWorkaroundActive() {
|
||||
|
||||
// per http://stackoverflow.com/questions/29001977/safari-in-ios8-is-scrolling-screen-when-fixed-elements-get-focus/29064810
|
||||
function positioningWorkaround($fixedElement) {
|
||||
if (!applicable()) {
|
||||
if (!isAppleDevice()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@ -1,9 +1,14 @@
|
||||
import { ajax } from 'discourse/lib/ajax';
|
||||
import { findRawTemplate } from 'discourse/lib/raw-templates';
|
||||
import { TAG_HASHTAG_POSTFIX } from 'discourse/lib/tag-hashtags';
|
||||
import { SEPARATOR } from 'discourse/lib/category-hashtags';
|
||||
import Category from 'discourse/models/category';
|
||||
import { search as searchCategoryTag } from 'discourse/lib/category-tag-search';
|
||||
import userSearch from 'discourse/lib/user-search';
|
||||
|
||||
export function translateResults(results, opts) {
|
||||
|
||||
const User = require('discourse/models/user').default;
|
||||
const Category = require('discourse/models/category').default;
|
||||
const Post = require('discourse/models/post').default;
|
||||
const Topic = require('discourse/models/topic').default;
|
||||
|
||||
@ -124,3 +129,39 @@ export function isValidSearchTerm(searchTerm) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
export function applySearchAutocomplete($input, siteSettings, appEvents) {
|
||||
const afterComplete = function() {
|
||||
if (appEvents) {
|
||||
appEvents.trigger("search-autocomplete:after-complete");
|
||||
}
|
||||
};
|
||||
|
||||
$input.autocomplete({
|
||||
template: findRawTemplate('category-tag-autocomplete'),
|
||||
key: '#',
|
||||
width: '100%',
|
||||
treatAsTextarea: true,
|
||||
transformComplete(obj) {
|
||||
if (obj.model) {
|
||||
return Category.slugFor(obj.model, SEPARATOR);
|
||||
} else {
|
||||
return `${obj.text}${TAG_HASHTAG_POSTFIX}`;
|
||||
}
|
||||
},
|
||||
dataSource(term) {
|
||||
return searchCategoryTag(term, siteSettings);
|
||||
},
|
||||
afterComplete
|
||||
});
|
||||
|
||||
$input.autocomplete({
|
||||
template: findRawTemplate('user-selector-autocomplete'),
|
||||
key: "@",
|
||||
width: '100%',
|
||||
treatAsTextarea: true,
|
||||
transformComplete: v => v.username || v.name,
|
||||
dataSource: term => userSearch({ term, includeGroups: true }),
|
||||
afterComplete
|
||||
});
|
||||
};
|
||||
|
||||
@ -326,6 +326,11 @@ const DiscourseURL = Ember.Object.extend({
|
||||
if (opts.replaceURL) {
|
||||
this.replaceState(path);
|
||||
} else {
|
||||
const discoveryTopics = this.controllerFor('discovery/topics');
|
||||
if (discoveryTopics) {
|
||||
discoveryTopics.resetParams();
|
||||
}
|
||||
|
||||
router.router.updateURL(path);
|
||||
}
|
||||
|
||||
|
||||
@ -0,0 +1,106 @@
|
||||
import InputValidation from 'discourse/models/input-validation';
|
||||
import debounce from 'discourse/lib/debounce';
|
||||
import { setting } from 'discourse/lib/computed';
|
||||
import { default as computed } from 'ember-addons/ember-computed-decorators';
|
||||
|
||||
export default Ember.Mixin.create({
|
||||
|
||||
uniqueUsernameValidation: null,
|
||||
|
||||
maxUsernameLength: setting('max_username_length'),
|
||||
minUsernameLength: setting('min_username_length'),
|
||||
|
||||
fetchExistingUsername: debounce(function() {
|
||||
const self = this;
|
||||
Discourse.User.checkUsername(null, this.get('accountEmail')).then(function(result) {
|
||||
if (result.suggestion && (Ember.isEmpty(self.get('accountUsername')) || self.get('accountUsername') === self.get('authOptions.username'))) {
|
||||
self.set('accountUsername', result.suggestion);
|
||||
self.set('prefilledUsername', result.suggestion);
|
||||
}
|
||||
});
|
||||
}, 500),
|
||||
|
||||
@computed('accountUsername')
|
||||
basicUsernameValidation(accountUsername) {
|
||||
this.set('uniqueUsernameValidation', null);
|
||||
|
||||
|
||||
if (accountUsername === this.get('prefilledUsername')) {
|
||||
return InputValidation.create({
|
||||
ok: true,
|
||||
reason: I18n.t('user.username.prefilled')
|
||||
});
|
||||
}
|
||||
|
||||
// If blank, fail without a reason
|
||||
if (Ember.isEmpty(accountUsername)) {
|
||||
return InputValidation.create({
|
||||
failed: true
|
||||
});
|
||||
}
|
||||
|
||||
// If too short
|
||||
if (accountUsername.length < Discourse.SiteSettings.min_username_length) {
|
||||
return InputValidation.create({
|
||||
failed: true,
|
||||
reason: I18n.t('user.username.too_short')
|
||||
});
|
||||
}
|
||||
|
||||
// If too long
|
||||
if (accountUsername.length > this.get('maxUsernameLength')) {
|
||||
return InputValidation.create({
|
||||
failed: true,
|
||||
reason: I18n.t('user.username.too_long')
|
||||
});
|
||||
}
|
||||
|
||||
this.checkUsernameAvailability();
|
||||
// Let's check it out asynchronously
|
||||
return InputValidation.create({
|
||||
failed: true,
|
||||
reason: I18n.t('user.username.checking')
|
||||
});
|
||||
},
|
||||
|
||||
shouldCheckUsernameAvailability: function() {
|
||||
return !Ember.isEmpty(this.get('accountUsername')) && this.get('accountUsername').length >= this.get('minUsernameLength');
|
||||
},
|
||||
|
||||
checkUsernameAvailability: debounce(function() {
|
||||
if (this.shouldCheckUsernameAvailability()) {
|
||||
return Discourse.User.checkUsername(this.get('accountUsername'), this.get('accountEmail')).then(result => {
|
||||
this.set('isDeveloper', false);
|
||||
if (result.available) {
|
||||
if (result.is_developer) {
|
||||
this.set('isDeveloper', true);
|
||||
}
|
||||
return this.set('uniqueUsernameValidation', InputValidation.create({
|
||||
ok: true,
|
||||
reason: I18n.t('user.username.available')
|
||||
}));
|
||||
} else {
|
||||
if (result.suggestion) {
|
||||
return this.set('uniqueUsernameValidation', InputValidation.create({
|
||||
failed: true,
|
||||
reason: I18n.t('user.username.not_available', result)
|
||||
}));
|
||||
} else {
|
||||
return this.set('uniqueUsernameValidation', InputValidation.create({
|
||||
failed: true,
|
||||
reason: result.errors ? result.errors.join(' ') : I18n.t('user.username.not_available_no_suggestion')
|
||||
}));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}, 500),
|
||||
|
||||
// Actually wait for the async name check before we're 100% sure we're good to go
|
||||
@computed('uniqueUsernameValidation', 'basicUsernameValidation')
|
||||
usernameValidation() {
|
||||
const basicValidation = this.get('basicUsernameValidation');
|
||||
const uniqueUsername = this.get('uniqueUsernameValidation');
|
||||
return uniqueUsername ? uniqueUsername : basicValidation;
|
||||
}
|
||||
});
|
||||
@ -70,7 +70,7 @@ const Category = RestModel.extend({
|
||||
|
||||
@computed("topic_count")
|
||||
moreTopics(topicCount) {
|
||||
return topicCount > Discourse.SiteSettings.category_featured_topics;
|
||||
return topicCount > (this.get('num_featured_topics') || 2);
|
||||
},
|
||||
|
||||
save() {
|
||||
@ -102,7 +102,10 @@ const Category = RestModel.extend({
|
||||
allowed_tag_groups: this.get('allowed_tag_groups'),
|
||||
sort_order: this.get('sort_order'),
|
||||
sort_ascending: this.get('sort_ascending'),
|
||||
topic_featured_link_allowed: this.get('topic_featured_link_allowed')
|
||||
topic_featured_link_allowed: this.get('topic_featured_link_allowed'),
|
||||
show_subcategory_list: this.get('show_subcategory_list'),
|
||||
num_featured_topics: this.get('num_featured_topics'),
|
||||
default_view: this.get('default_view')
|
||||
},
|
||||
type: id ? 'PUT' : 'POST'
|
||||
});
|
||||
@ -148,7 +151,7 @@ const Category = RestModel.extend({
|
||||
@computed("topics")
|
||||
featuredTopics(topics) {
|
||||
if (topics && topics.length) {
|
||||
return topics.slice(0, Discourse.SiteSettings.category_featured_topics || 2);
|
||||
return topics.slice(0, this.get('num_featured_topics') || 2);
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@ -98,13 +98,22 @@ TopicList.reopenClass({
|
||||
if (!result) { return; }
|
||||
|
||||
// Stitch together our side loaded data
|
||||
|
||||
const categories = Discourse.Category.list(),
|
||||
users = Model.extractByKey(result.users, Discourse.User);
|
||||
users = Model.extractByKey(result.users, Discourse.User),
|
||||
groups = Model.extractByKey(result.primary_groups, Ember.Object);
|
||||
|
||||
return result.topic_list.topics.map(function (t) {
|
||||
t.category = categories.findBy('id', t.category_id);
|
||||
t.posters.forEach(function(p) {
|
||||
p.user = users[p.user_id];
|
||||
p.extraClasses = p.extras;
|
||||
if (p.primary_group_id) {
|
||||
p.primary_group = groups[p.primary_group_id];
|
||||
if (p.primary_group) {
|
||||
p.extraClasses = `${p.extraClasses||''} group-${p.primary_group.name}`;
|
||||
}
|
||||
}
|
||||
});
|
||||
if (t.participants) {
|
||||
t.participants.forEach(function(p) {
|
||||
|
||||
@ -13,9 +13,9 @@ export default {
|
||||
app.DiscoveryCategoryNoneController = DiscoverySortableController.extend();
|
||||
app.DiscoveryCategoryWithIDController = DiscoverySortableController.extend();
|
||||
|
||||
app.DiscoveryCategoryRoute = buildCategoryRoute('latest');
|
||||
app.DiscoveryParentCategoryRoute = buildCategoryRoute('latest');
|
||||
app.DiscoveryCategoryNoneRoute = buildCategoryRoute('latest', {no_subcategories: true});
|
||||
app.DiscoveryCategoryRoute = buildCategoryRoute('default');
|
||||
app.DiscoveryParentCategoryRoute = buildCategoryRoute('default');
|
||||
app.DiscoveryCategoryNoneRoute = buildCategoryRoute('default', {no_subcategories: true});
|
||||
|
||||
const site = Discourse.Site.current();
|
||||
site.get('filters').forEach(filter => {
|
||||
|
||||
@ -142,4 +142,8 @@ export default function() {
|
||||
this.route('tagGroups', {path: '/tag_groups', resetNamespace: true}, function() {
|
||||
this.route('show', {path: '/:id'});
|
||||
});
|
||||
|
||||
this.route('invites', { path: '/invites', resetNamespace: true }, function() {
|
||||
this.route('show', { path: '/:token' });
|
||||
});
|
||||
}
|
||||
|
||||
@ -6,7 +6,7 @@ import CategoryList from 'discourse/models/category-list';
|
||||
import Category from 'discourse/models/category';
|
||||
|
||||
// A helper function to create a category route with parameters
|
||||
export default (filter, params) => {
|
||||
export default (filterArg, params) => {
|
||||
return Discourse.Route.extend({
|
||||
queryParams,
|
||||
|
||||
@ -37,9 +37,13 @@ export default (filter, params) => {
|
||||
this._retrieveTopicList(model.category, transition)]);
|
||||
},
|
||||
|
||||
filter(category) {
|
||||
return filterArg === 'default' ? (category.get('default_view') || 'latest') : filterArg;
|
||||
},
|
||||
|
||||
_setupNavigation(category) {
|
||||
const noSubcategories = params && !!params.no_subcategories,
|
||||
filterMode = `c/${Discourse.Category.slugFor(category)}${noSubcategories ? "/none" : ""}/l/${filter}`;
|
||||
filterMode = `c/${Discourse.Category.slugFor(category)}${noSubcategories ? "/none" : ""}/l/${this.filter(category)}`;
|
||||
|
||||
this.controllerFor('navigation/category').setProperties({
|
||||
category,
|
||||
@ -51,7 +55,7 @@ export default (filter, params) => {
|
||||
|
||||
_createSubcategoryList(category) {
|
||||
this._categoryList = null;
|
||||
if (Em.isNone(category.get('parentCategory')) && Discourse.SiteSettings.show_subcategory_list) {
|
||||
if (Em.isNone(category.get('parentCategory')) && category.get('show_subcategory_list')) {
|
||||
return CategoryList.listForParent(this.store, category).then(list => this._categoryList = list);
|
||||
}
|
||||
|
||||
@ -60,7 +64,7 @@ export default (filter, params) => {
|
||||
},
|
||||
|
||||
_retrieveTopicList(category, transition) {
|
||||
const listFilter = `c/${Discourse.Category.slugFor(category)}/l/${filter}`,
|
||||
const listFilter = `c/${Discourse.Category.slugFor(category)}/l/${this.filter(category)}`,
|
||||
findOpts = filterQueryParams(transition.queryParams, params),
|
||||
extras = { cached: this.isPoppedState(transition) };
|
||||
|
||||
@ -72,8 +76,8 @@ export default (filter, params) => {
|
||||
},
|
||||
|
||||
titleToken() {
|
||||
const filterText = I18n.t('filters.' + filter.replace('/', '.') + '.title'),
|
||||
category = this.currentModel.category;
|
||||
const category = this.currentModel.category,
|
||||
filterText = I18n.t('filters.' + this.filter(category).replace('/', '.') + '.title');
|
||||
|
||||
return I18n.t('filters.with_category', { filter: filterText, category: category.get('name') });
|
||||
},
|
||||
@ -82,7 +86,8 @@ export default (filter, params) => {
|
||||
const topics = this.get('topics'),
|
||||
category = model.category,
|
||||
canCreateTopic = topics.get('can_create_topic'),
|
||||
canCreateTopicOnCategory = category.get('permission') === PermissionType.FULL;
|
||||
canCreateTopicOnCategory = category.get('permission') === PermissionType.FULL,
|
||||
filter = this.filter(category);
|
||||
|
||||
this.controllerFor('navigation/category').setProperties({
|
||||
canCreateTopicOnCategory: canCreateTopicOnCategory,
|
||||
|
||||
13
app/assets/javascripts/discourse/routes/invites-show.js.es6
Normal file
13
app/assets/javascripts/discourse/routes/invites-show.js.es6
Normal file
@ -0,0 +1,13 @@
|
||||
import PreloadStore from 'preload-store';
|
||||
|
||||
export default Discourse.Route.extend({
|
||||
titleToken() {
|
||||
return I18n.t('invites.accept_title');
|
||||
},
|
||||
|
||||
model(params) {
|
||||
if (PreloadStore.get("invite_info")) {
|
||||
return PreloadStore.getAndRemove("invite_info").then(json => _.merge(params, json));
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -4,7 +4,7 @@ export default Discourse.Route.extend({
|
||||
|
||||
const { currentUser } = this;
|
||||
const viewingMe = (currentUser && currentUser.get('username') === this.modelFor('user').get('username'));
|
||||
const destination = viewingMe ? 'user.summary' : 'userActivity';
|
||||
const destination = viewingMe ? 'userActivity' : 'user.summary';
|
||||
|
||||
// HACK: Something with the way the user card intercepts clicks seems to break how the
|
||||
// transition into a user's activity works. This makes the back button work on mobile
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { default as computed, observes } from 'ember-addons/ember-computed-decorators';
|
||||
|
||||
export default Ember.Object.extend({
|
||||
searchContextEnabled: false,
|
||||
searchContextEnabled: false, // checkbox to scope search
|
||||
searchContext: null,
|
||||
term: null,
|
||||
highlightTerm: null,
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<p>{{i18n "topics.bulk.choose_new_tags"}}</p>
|
||||
<p>{{i18n (concat "topics.bulk." title)}}</p>
|
||||
|
||||
<p>{{tag-chooser tags=tags categoryId=categoryId}}</p>
|
||||
|
||||
{{d-button action="changeTags" disabled=emptyTags label="topics.bulk.change_tags"}}
|
||||
{{d-button action=action disabled=emptyTags label=(concat "topics.bulk." label)}}
|
||||
|
||||
@ -19,6 +19,15 @@
|
||||
</label>
|
||||
</section>
|
||||
|
||||
{{#unless category.parent_category_id}}
|
||||
<section class="field">
|
||||
<label>
|
||||
{{input type="checkbox" checked=category.show_subcategory_list}}
|
||||
{{i18n "category.show_subcategory_list"}}
|
||||
</label>
|
||||
</section>
|
||||
{{/unless}}
|
||||
|
||||
<section class="field">
|
||||
<label>
|
||||
{{input type="checkbox" checked=category.all_topics_wiki}}
|
||||
@ -47,6 +56,13 @@
|
||||
</label>
|
||||
</section>
|
||||
|
||||
<section class="field default-view-field">
|
||||
<label>
|
||||
{{i18n "category.default_view"}}
|
||||
{{combo-box valueAttribute="value" content=availableViews value=category.default_view}}
|
||||
</label>
|
||||
</section>
|
||||
|
||||
{{#if emailInEnabled}}
|
||||
<section class='field'>
|
||||
<label>
|
||||
@ -66,8 +82,19 @@
|
||||
{{plugin-outlet name="category-email-in" args=(hash category=category)}}
|
||||
{{/if}}
|
||||
|
||||
<section class="field num-featured-topics-fields">
|
||||
<label>
|
||||
{{#if category.parent_category_id}}
|
||||
{{i18n "category.subcategory_num_featured_topics"}}
|
||||
{{else}}
|
||||
{{i18n "category.num_featured_topics"}}
|
||||
{{/if}}
|
||||
{{text-field value=category.num_featured_topics}}
|
||||
</label>
|
||||
</section>
|
||||
|
||||
{{#if showPositionInput}}
|
||||
<section class='field'>
|
||||
<section class='field position-fields'>
|
||||
<label>
|
||||
{{i18n 'category.position'}}
|
||||
{{text-field value=category.position class="position-input"}}
|
||||
|
||||
@ -19,13 +19,7 @@
|
||||
</tr>
|
||||
<tr>
|
||||
{{category-link topic.category}}
|
||||
{{#if topic.tags}}
|
||||
<div class="discourse-tags">
|
||||
{{#each topic.visibleListTags as |tag|}}
|
||||
{{discourse-tag tag}}
|
||||
{{/each}}
|
||||
</div>
|
||||
{{/if}}
|
||||
{{discourse-tags topic mode="list"}}
|
||||
</tr>
|
||||
</td>
|
||||
<td class="topic-stats">
|
||||
|
||||
@ -1,5 +1,8 @@
|
||||
{{#each navItems as |navItem|}}
|
||||
{{navigation-item content=navItem filterMode=filterMode}}
|
||||
{{/each}}
|
||||
{{custom-html name="extraNavItem"}}
|
||||
{{plugin-outlet name="extra-nav-item" connectorTagName="li"}}
|
||||
{{custom-html name="extraNavItem" tagName="li"}}
|
||||
{{!- this is done to avoid DIV in the UL, originally {{plugin-outlet name="extra-nav-item"}}
|
||||
{{#each connectors as |c|}}
|
||||
{{plugin-connector connector=c class=c.classNames tagName="li" args=(hash category=category filterMode=filterMode)}}
|
||||
{{/each}}
|
||||
|
||||
@ -13,7 +13,7 @@
|
||||
<div class="control-group pull-left">
|
||||
<label class="control-label" for="search-in-category">{{i18n "search.advanced.in_category.label"}}</label>
|
||||
<div class="controls">
|
||||
{{category-selector categories=searchedTerms.category single="true"}}
|
||||
{{category-selector categories=searchedTerms.category single="true" canReceiveUpdates="true"}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
1
app/assets/javascripts/discourse/templates/invites.hbs
Normal file
1
app/assets/javascripts/discourse/templates/invites.hbs
Normal file
@ -0,0 +1 @@
|
||||
{{outlet}}
|
||||
55
app/assets/javascripts/discourse/templates/invites/show.hbs
Normal file
55
app/assets/javascripts/discourse/templates/invites/show.hbs
Normal file
@ -0,0 +1,55 @@
|
||||
<div class="container invites-show clearfix">
|
||||
|
||||
<h2>{{welcomeTitle}}</h2>
|
||||
|
||||
<div class="two-col">
|
||||
<div class="col-image">
|
||||
<img src={{inviteImageUrl}}>
|
||||
</div>
|
||||
|
||||
<div class="col-form">
|
||||
<p>{{i18n 'invites.invited_by'}}</p>
|
||||
|
||||
<p>{{user-info user=invitedBy}}</p>
|
||||
|
||||
{{#if successMessage}}
|
||||
<p>{{successMessage}}</p>
|
||||
{{else}}
|
||||
|
||||
<p>{{{yourEmailMessage}}}
|
||||
{{#if externalAuthsEnabled}}
|
||||
{{i18n 'invites.social_login_available'}}
|
||||
{{/if}}
|
||||
</p>
|
||||
|
||||
<form>
|
||||
<label>{{i18n 'user.username.title'}}</label>
|
||||
|
||||
<div class="input username-input">
|
||||
{{input value=accountUsername id="new-account-username" name="username" maxlength=maxUsernameLength autocomplete="off"}}
|
||||
{{input-tip validation=usernameValidation id="username-validation"}}
|
||||
</div>
|
||||
|
||||
<label>{{i18n 'invites.password_label'}}</label>
|
||||
|
||||
<div class="input password-input">
|
||||
{{password-field value=accountPassword type="password" id="new-account-password" capsLockOn=capsLockOn}}
|
||||
{{input-tip validation=passwordValidation}}
|
||||
</div>
|
||||
|
||||
<div class="instructions">
|
||||
<div class="caps-lock-warning {{unless capsLockOn 'invisible'}}"><i class="fa fa-exclamation-triangle"></i> {{i18n 'login.caps_lock_warning'}}</div>
|
||||
</div>
|
||||
|
||||
<button class='btn btn-primary' {{action "submit"}} disabled={{submitDisabled}}>{{i18n 'invites.accept_invite'}}</button>
|
||||
|
||||
{{#if errorMessage}}
|
||||
<br/><br/>
|
||||
<div class='alert alert-error'>{{errorMessage}}</div>
|
||||
{{/if}}
|
||||
|
||||
</form>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -1,5 +1,5 @@
|
||||
<td class='posters'>
|
||||
{{#each posters as |poster|}}
|
||||
<a href="{{poster.user.path}}" data-user-card="{{poster.user.username}}" class="{{poster.extras}}">{{avatar poster avatarTemplatePath="user.avatar_template" usernamePath="user.username" imageSize="small"}}</a>
|
||||
<a href="{{poster.user.path}}" data-user-card="{{poster.user.username}}" class="{{poster.extraClasses}}">{{avatar poster avatarTemplatePath="user.avatar_template" usernamePath="user.username" imageSize="small"}}</a>
|
||||
{{/each}}
|
||||
</td>
|
||||
|
||||
@ -18,14 +18,7 @@
|
||||
{{/if}}
|
||||
</span>
|
||||
|
||||
{{#if topic.tags}}
|
||||
<div class='discourse-tags'>
|
||||
{{#each topic.visibleListTags as |tag|}}
|
||||
{{discourse-tag tag}}
|
||||
{{/each}}
|
||||
</div>
|
||||
{{/if}}
|
||||
{{raw-plugin-outlet name="topic-list-tags"}}
|
||||
{{discourse-tags topic mode="list"}}
|
||||
{{#if expandPinned}}
|
||||
{{raw "list/topic-excerpt" topic=topic}}
|
||||
{{/if}}
|
||||
|
||||
@ -32,15 +32,7 @@
|
||||
</div>
|
||||
{{/unless}}
|
||||
|
||||
{{#if topic.tags}}
|
||||
<div class='discourse-tags'>
|
||||
{{#each topic.visibleListTags as |tag|}}
|
||||
{{discourse-tag tag}}
|
||||
{{/each}}
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
{{raw-plugin-outlet name="topic-list-tags"}}
|
||||
{{discourse-tags topic mode="list"}}
|
||||
|
||||
<div class="pull-right">
|
||||
<div class='num activity last'>
|
||||
|
||||
@ -1,24 +1,26 @@
|
||||
<div class="container">
|
||||
<div class='directory'>
|
||||
{{#load-more selector=".directory .user" action="loadMore"}}
|
||||
<div class="container">
|
||||
<div class='directory'>
|
||||
|
||||
<div class='clearfix user-controls'>
|
||||
{{period-chooser period=period}}
|
||||
{{text-field value=nameInput placeholderKey="directory.filter_name" class="filter-name no-blur"}}
|
||||
</div>
|
||||
|
||||
{{#conditional-loading-spinner condition=model.loading}}
|
||||
{{#if model.length}}
|
||||
<div class='total-rows'>{{i18n "directory.total_rows" count=model.totalRows}}</div>
|
||||
{{#each model as |item|}}
|
||||
{{directory-item tagName="div" class="user" item=item showTimeRead=showTimeRead}}
|
||||
{{/each}}
|
||||
|
||||
{{conditional-loading-spinner condition=model.loadingMore}}
|
||||
{{else}}
|
||||
<div class='clearfix'></div>
|
||||
<p>{{i18n "directory.no_results"}}</p>
|
||||
{{/if}}
|
||||
{{/conditional-loading-spinner}}
|
||||
|
||||
<div class='clearfix user-controls'>
|
||||
{{period-chooser period=period}}
|
||||
{{text-field value=nameInput placeholderKey="directory.filter_name" class="filter-name no-blur"}}
|
||||
</div>
|
||||
|
||||
{{#conditional-loading-spinner condition=model.loading}}
|
||||
{{#if model.length}}
|
||||
<div class='total-rows'>{{i18n "directory.total_rows" count=model.totalRows}}</div>
|
||||
{{#each model as |item|}}
|
||||
{{directory-item tagName="div" class="user" item=item showTimeRead=showTimeRead}}
|
||||
{{/each}}
|
||||
|
||||
{{conditional-loading-spinner condition=model.loadingMore}}
|
||||
{{else}}
|
||||
<div class='clearfix'></div>
|
||||
<p>{{i18n "directory.no_results"}}</p>
|
||||
{{/if}}
|
||||
{{/conditional-loading-spinner}}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
{{/load-more}}
|
||||
|
||||
@ -15,7 +15,7 @@
|
||||
noSubcategories=noSubcategories
|
||||
hideSubcategories=showingSubcategoryList}}
|
||||
|
||||
{{navigation-bar navItems=navItems filterMode=filterMode}}
|
||||
{{navigation-bar navItems=navItems filterMode=filterMode category=category}}
|
||||
|
||||
{{#if currentUser}}
|
||||
{{category-notifications-button category=category}}
|
||||
|
||||
@ -21,6 +21,7 @@
|
||||
{{i18n 'user.messages.archive'}}
|
||||
{{/link-to}}
|
||||
</li>
|
||||
{{plugin-outlet name="user-messages-nav" connectorTagName='li' args=(hash model=model)}}
|
||||
{{#each model.groups as |group|}}
|
||||
{{#if group.has_messages}}
|
||||
<li>
|
||||
|
||||
@ -26,7 +26,11 @@ createWidget('small-user-list', {
|
||||
users = users.concat(avatarAtts(currentUser));
|
||||
}
|
||||
|
||||
let description = I18n.t(atts.description, { icons: '' });
|
||||
let description = null;
|
||||
|
||||
if (atts.description) {
|
||||
description = I18n.t(atts.description, { icons: '' });
|
||||
}
|
||||
|
||||
// oddly post_url is on the user
|
||||
let postUrl;
|
||||
@ -38,7 +42,13 @@ createWidget('small-user-list', {
|
||||
if (postUrl) {
|
||||
description = h('a', { attributes: { href: Discourse.getURL(postUrl) } }, description);
|
||||
}
|
||||
return [icons, description, '.'];
|
||||
|
||||
let buffer = [icons];
|
||||
if (description) {
|
||||
buffer.push(description);
|
||||
buffer.push(".");
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@ -36,7 +36,7 @@ export default createWidget('hamburger-menu', {
|
||||
const { currentUser } = this;
|
||||
|
||||
const links = [{ route: 'admin', className: 'admin-link', icon: 'wrench', label: 'admin_title' },
|
||||
{ route: 'adminFlags',
|
||||
{ href: '/admin/flags/active',
|
||||
className: 'flagged-posts-link',
|
||||
icon: 'flag',
|
||||
label: 'flags_title',
|
||||
@ -117,11 +117,10 @@ export default createWidget('hamburger-menu', {
|
||||
|
||||
listCategories() {
|
||||
const hideUncategorized = !this.siteSettings.allow_uncategorized_topics;
|
||||
const showSubcatList = this.siteSettings.show_subcategory_list;
|
||||
const isStaff = Discourse.User.currentProp('staff');
|
||||
|
||||
const categories = Discourse.Category.list().reject((c) => {
|
||||
if (showSubcatList && c.get('parent_category_id')) { return true; }
|
||||
if (c.get('parentCategory.show_subcategory_list')) { return true; }
|
||||
if (hideUncategorized && c.get('isUncategorizedCategory') && !isStaff) { return true; }
|
||||
return false;
|
||||
});
|
||||
|
||||
@ -1,15 +1,15 @@
|
||||
import { createWidget } from 'discourse/widgets/widget';
|
||||
import { applyDecorators, createWidget } from 'discourse/widgets/widget';
|
||||
import { h } from 'virtual-dom';
|
||||
import { iconNode } from 'discourse/helpers/fa-icon-node';
|
||||
import DiscourseURL from 'discourse/lib/url';
|
||||
import RawHtml from 'discourse/widgets/raw-html';
|
||||
import { tagNode } from 'discourse/lib/render-tag';
|
||||
import renderTags from 'discourse/lib/render-tags';
|
||||
import { topicFeaturedLinkNode } from 'discourse/lib/render-topic-featured-link';
|
||||
|
||||
export default createWidget('header-topic-info', {
|
||||
tagName: 'div.extra-info-wrapper',
|
||||
|
||||
html(attrs) {
|
||||
html(attrs, state) {
|
||||
const topic = attrs.topic;
|
||||
|
||||
const heading = [];
|
||||
@ -45,13 +45,14 @@ export default createWidget('header-topic-info', {
|
||||
title.push(this.attach('category-link', { category }));
|
||||
}
|
||||
|
||||
const extra = [];
|
||||
if (this.siteSettings.tagging_enabled) {
|
||||
const tags = topic.get('tags') || [];
|
||||
if (tags.length) {
|
||||
extra.push(h('div.list-tags', tags.map(tagNode)));
|
||||
}
|
||||
let extra = [];
|
||||
const tags = renderTags(topic);
|
||||
if (tags && tags.length > 0) {
|
||||
extra.push(new RawHtml({html: tags}));
|
||||
}
|
||||
|
||||
extra = extra.concat(applyDecorators(this, 'after-tags', attrs, state));
|
||||
|
||||
if (this.siteSettings.topic_featured_link_enabled) {
|
||||
const featured = topicFeaturedLinkNode(attrs.topic);
|
||||
if (featured) {
|
||||
|
||||
@ -3,6 +3,7 @@ import { iconNode } from 'discourse/helpers/fa-icon-node';
|
||||
import { avatarImg } from 'discourse/widgets/post';
|
||||
import DiscourseURL from 'discourse/lib/url';
|
||||
import { wantsNewWindow } from 'discourse/lib/intercept-click';
|
||||
import { applySearchAutocomplete } from "discourse/lib/search";
|
||||
|
||||
import { h } from 'virtual-dom';
|
||||
|
||||
@ -163,6 +164,8 @@ createWidget('header-buttons', {
|
||||
}
|
||||
});
|
||||
|
||||
const forceContextEnabled = ['category', 'user', 'private_messages'];
|
||||
|
||||
export default createWidget('header', {
|
||||
tagName: 'header.d-header.clearfix',
|
||||
buildKey: () => `header`,
|
||||
@ -172,7 +175,6 @@ export default createWidget('header', {
|
||||
searchVisible: false,
|
||||
hamburgerVisible: false,
|
||||
userVisible: false,
|
||||
contextEnabled: false,
|
||||
ringBackdrop: true
|
||||
};
|
||||
|
||||
@ -192,6 +194,19 @@ export default createWidget('header', {
|
||||
flagCount: attrs.flagCount })];
|
||||
|
||||
if (state.searchVisible) {
|
||||
const contextType = this.searchContextType();
|
||||
|
||||
if (state.searchContextType !== contextType) {
|
||||
state.contextEnabled = undefined;
|
||||
state.searchContextType = contextType;
|
||||
}
|
||||
|
||||
if (state.contextEnabled === undefined) {
|
||||
if (forceContextEnabled.includes(contextType)) {
|
||||
state.contextEnabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
panels.push(this.attach('search-menu', { contextEnabled: state.contextEnabled }));
|
||||
} else if (state.hamburgerVisible) {
|
||||
panels.push(this.attach('hamburger-menu'));
|
||||
@ -244,7 +259,11 @@ export default createWidget('header', {
|
||||
this.updateHighlight();
|
||||
|
||||
if (this.state.searchVisible) {
|
||||
Ember.run.schedule('afterRender', () => $('#search-term').focus().select());
|
||||
Ember.run.schedule('afterRender', () => {
|
||||
const $searchInput = $('#search-term');
|
||||
$searchInput.focus().select();
|
||||
applySearchAutocomplete($searchInput, this.siteSettings, this.appEvents);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
@ -289,6 +308,7 @@ export default createWidget('header', {
|
||||
},
|
||||
|
||||
searchMenuContextChanged(value) {
|
||||
this.state.contextType = this.register.lookup('search-service:main').get('contextType');
|
||||
this.state.contextEnabled = value;
|
||||
},
|
||||
|
||||
@ -318,6 +338,16 @@ export default createWidget('header', {
|
||||
}
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
searchContextType() {
|
||||
const service = this.register.lookup('search-service:main');
|
||||
if (service) {
|
||||
const ctx = service.get('searchContext');
|
||||
if (ctx) {
|
||||
return Ember.get(ctx, 'type');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
@ -3,6 +3,21 @@ import transformPost from 'discourse/lib/transform-post';
|
||||
import { Placeholder } from 'discourse/lib/posts-with-placeholders';
|
||||
import { addWidgetCleanCallback } from 'discourse/components/mount-widget';
|
||||
|
||||
let transformCallbacks = null;
|
||||
function postTransformCallbacks(transformed) {
|
||||
if (transformCallbacks === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
for(let i=0; i < transformCallbacks.length; i++) {
|
||||
transformCallbacks[i].call(this, transformed);
|
||||
}
|
||||
}
|
||||
export function addPostTransformCallback(callback){
|
||||
transformCallbacks = transformCallbacks || [];
|
||||
transformCallbacks.push(callback);
|
||||
};
|
||||
|
||||
const CLOAKING_ENABLED = !window.inTestEnv;
|
||||
const DAY = 1000 * 60 * 60 * 24;
|
||||
|
||||
@ -96,6 +111,8 @@ export default createWidget('post-stream', {
|
||||
transformed.height = _heights[post.id];
|
||||
transformed.cloaked = _cloaked[post.id];
|
||||
|
||||
postTransformCallbacks(transformed);
|
||||
|
||||
if (transformed.isSmallAction) {
|
||||
result.push(this.attach('post-small-action', transformed, { model: post }));
|
||||
} else {
|
||||
|
||||
@ -376,6 +376,12 @@ createWidget('post-article', {
|
||||
|
||||
});
|
||||
|
||||
let addPostClassesCallbacks = null;
|
||||
export function addPostClassesCallback(callback) {
|
||||
addPostClassesCallbacks = addPostClassesCallbacks || [];
|
||||
addPostClassesCallbacks.push(callback);
|
||||
}
|
||||
|
||||
export default createWidget('post', {
|
||||
buildKey: attrs => `post-${attrs.id}`,
|
||||
shadowTree: true,
|
||||
@ -405,6 +411,14 @@ export default createWidget('post', {
|
||||
} else {
|
||||
classNames.push('regular');
|
||||
}
|
||||
if (addPostClassesCallbacks) {
|
||||
for(let i=0; i<addPostClassesCallbacks.length; i++) {
|
||||
let pluginClasses = addPostClassesCallbacks[i].call(this, attrs);
|
||||
if (pluginClasses) {
|
||||
classNames.push.apply(classNames, pluginClasses);
|
||||
}
|
||||
}
|
||||
}
|
||||
return classNames;
|
||||
},
|
||||
|
||||
|
||||
@ -5,6 +5,15 @@ import { createWidget } from 'discourse/widgets/widget';
|
||||
createWidget('search-term', {
|
||||
tagName: 'input',
|
||||
buildId: () => 'search-term',
|
||||
buildKey: (attrs) => `search-term-${attrs.id}`,
|
||||
|
||||
defaultState() {
|
||||
this.appEvents.on("search-autocomplete:after-complete", () => {
|
||||
this.state.afterAutocomplete = true;
|
||||
});
|
||||
|
||||
return { afterAutocomplete: false };
|
||||
},
|
||||
|
||||
buildAttributes(attrs) {
|
||||
return { type: 'text',
|
||||
@ -14,7 +23,11 @@ createWidget('search-term', {
|
||||
|
||||
keyUp(e) {
|
||||
if (e.which === 13) {
|
||||
return this.sendWidgetAction('fullSearch');
|
||||
if (this.state.afterAutocomplete) {
|
||||
this.state.afterAutocomplete = false;
|
||||
} else {
|
||||
return this.sendWidgetAction('fullSearch');
|
||||
}
|
||||
}
|
||||
|
||||
const val = this.attrs.value;
|
||||
|
||||
@ -46,7 +46,7 @@ function postResult(result, link, term) {
|
||||
}
|
||||
|
||||
createSearchResult('user', 'path', function(u) {
|
||||
return [ avatarImg('small', { template: u.avatar_template, username: u.username }), ' ', u.username ];
|
||||
return [ avatarImg('small', { template: u.avatar_template, username: u.username }), ' ', h('span.user-results', h('b', u.username)), ' ', h('span.user-results', u.name ? u.name : '') ];
|
||||
});
|
||||
|
||||
createSearchResult('topic', 'url', function(result, term) {
|
||||
|
||||
@ -148,7 +148,12 @@ export default createWidget('search-menu', {
|
||||
},
|
||||
|
||||
html(attrs) {
|
||||
searchData.contextEnabled = attrs.contextEnabled;
|
||||
if (searchData.contextEnabled !== attrs.contextEnabled) {
|
||||
searchData.contextEnabled = attrs.contextEnabled;
|
||||
this.triggerSearch();
|
||||
} else {
|
||||
searchData.contextEnabled = attrs.contextEnabled;
|
||||
}
|
||||
|
||||
return this.attach('menu-panel', { maxWidth: 500, contents: () => this.panelContents() });
|
||||
},
|
||||
@ -170,6 +175,7 @@ export default createWidget('search-menu', {
|
||||
},
|
||||
|
||||
searchContextChanged(enabled) {
|
||||
// This indicates the checkbox has been clicked, NOT that the context has changed.
|
||||
searchData.typeFilter = null;
|
||||
this.sendWidgetAction('searchMenuContextChanged', enabled);
|
||||
searchData.contextEnabled = enabled;
|
||||
|
||||
@ -1,6 +1,13 @@
|
||||
import { createWidget } from 'discourse/widgets/widget';
|
||||
import { h } from 'virtual-dom';
|
||||
|
||||
let extraGlyphs;
|
||||
|
||||
export function addUserMenuGlyph(glyph) {
|
||||
extraGlyphs = extraGlyphs || [];
|
||||
extraGlyphs.push(glyph);
|
||||
}
|
||||
|
||||
createWidget('user-menu-links', {
|
||||
tagName: 'div.menu-links-header',
|
||||
|
||||
@ -13,10 +20,18 @@ createWidget('user-menu-links', {
|
||||
isAnon;
|
||||
|
||||
const path = attrs.path;
|
||||
const glyphs = [{ label: 'user.bookmarks',
|
||||
const glyphs = [];
|
||||
|
||||
if (extraGlyphs) {
|
||||
// yes glyphs.push(...extraGlyphs) is nicer, but pulling in
|
||||
// _toConsumableArray seems totally uneeded here
|
||||
glyphs.push.apply(glyphs, extraGlyphs);
|
||||
}
|
||||
|
||||
glyphs.push({ label: 'user.bookmarks',
|
||||
className: 'user-bookmarks-link',
|
||||
icon: 'bookmark',
|
||||
href: `${path}/activity/bookmarks` }];
|
||||
href: `${path}/activity/bookmarks` });
|
||||
|
||||
if (siteSettings.enable_private_messages) {
|
||||
glyphs.push({ label: 'user.private_messages',
|
||||
|
||||
@ -1,12 +1,3 @@
|
||||
//= depend_on 'client.ar.yml'
|
||||
//= require locales/i18n
|
||||
<%= JsLocaleHelper.output_locale(:ar) %>
|
||||
|
||||
I18n.pluralizationRules['ar'] = function (n) {
|
||||
if (n == 0) return "zero";
|
||||
if (n == 1) return "one";
|
||||
if (n == 2) return "two";
|
||||
if (n%100 >= 3 && n%100 <= 10) return "few";
|
||||
if (n%100 >= 11 && n%100 <= 99) return "many";
|
||||
return "other";
|
||||
};
|
||||
|
||||
@ -1,10 +1,3 @@
|
||||
//= depend_on 'client.cs.yml'
|
||||
//= require locales/i18n
|
||||
<%= JsLocaleHelper.output_locale(:cs) %>
|
||||
|
||||
I18n.pluralizationRules['cs'] = function (n) {
|
||||
if (n == 0) return ["zero", "none", "other"];
|
||||
if (n == 1) return "one";
|
||||
if (n >= 2 && n <= 4) return "few";
|
||||
return "other";
|
||||
};
|
||||
|
||||
3
app/assets/javascripts/locales/el.js.erb
Normal file
3
app/assets/javascripts/locales/el.js.erb
Normal file
@ -0,0 +1,3 @@
|
||||
//= depend_on 'client.el.yml'
|
||||
//= require locales/i18n
|
||||
<%= JsLocaleHelper.output_locale(:el) %>
|
||||
@ -1,7 +1,3 @@
|
||||
//= depend_on 'client.fa_IR.yml'
|
||||
//= require locales/i18n
|
||||
<%= JsLocaleHelper.output_locale(:fa_IR) %>
|
||||
|
||||
I18n.pluralizationRules['fa_IR'] = function (n) {
|
||||
return "other";
|
||||
};
|
||||
|
||||
@ -1,48 +1,17 @@
|
||||
/*global I18n:true */
|
||||
|
||||
// https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/indexOf
|
||||
if (!Array.prototype.indexOf) {
|
||||
Array.prototype.indexOf = function (searchElement, fromIndex) {
|
||||
if ( this === undefined || this === null ) {
|
||||
throw new TypeError( '"this" is null or not defined' );
|
||||
}
|
||||
|
||||
var length = this.length >>> 0; // Hack to convert object.length to a UInt32
|
||||
|
||||
fromIndex = +fromIndex || 0;
|
||||
|
||||
if (Math.abs(fromIndex) === Infinity) {
|
||||
fromIndex = 0;
|
||||
}
|
||||
|
||||
if (fromIndex < 0) {
|
||||
fromIndex += length;
|
||||
if (fromIndex < 0) {
|
||||
fromIndex = 0;
|
||||
}
|
||||
}
|
||||
|
||||
for (;fromIndex < length; fromIndex++) {
|
||||
if (this[fromIndex] === searchElement) {
|
||||
return fromIndex;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
};
|
||||
}
|
||||
|
||||
// Instantiate the object
|
||||
var I18n = I18n || {};
|
||||
|
||||
// Set default locale to english
|
||||
I18n.defaultLocale = "en";
|
||||
|
||||
// Set default handling of translation fallbacks to false
|
||||
I18n.fallbacks = false;
|
||||
|
||||
// Set default separator
|
||||
I18n.defaultSeparator = ".";
|
||||
// Set default pluralization rule
|
||||
I18n.pluralizationRules = {
|
||||
en: function(n) {
|
||||
return n === 0 ? ["zero", "none", "other"] : n === 1 ? "one" : "other";
|
||||
}
|
||||
};
|
||||
|
||||
// Set current locale to null
|
||||
I18n.locale = null;
|
||||
@ -50,44 +19,10 @@ I18n.locale = null;
|
||||
// Set the placeholder format. Accepts `{{placeholder}}` and `%{placeholder}`.
|
||||
I18n.PLACEHOLDER = /(?:\{\{|%\{)(.*?)(?:\}\}?)/gm;
|
||||
|
||||
I18n.fallbackRules = {};
|
||||
I18n.SEPARATOR = ".";
|
||||
|
||||
I18n.noFallbacks = false;
|
||||
|
||||
I18n.pluralizationRules = {
|
||||
en: function(n) {
|
||||
return n === 0 ? ["zero", "none", "other"] : n === 1 ? "one" : "other";
|
||||
},
|
||||
"zh_CN": function(n) {
|
||||
return n === 0 ? ["zero", "none", "other"] : "other";
|
||||
},
|
||||
"zh_TW": function(n) {
|
||||
return n === 0 ? ["zero", "none", "other"] : "other";
|
||||
},
|
||||
"ko": function(n) {
|
||||
return n === 0 ? ["zero", "none", "other"] : "other";
|
||||
}
|
||||
};
|
||||
|
||||
I18n.getFallbacks = function(locale) {
|
||||
if (locale === I18n.defaultLocale) {
|
||||
return [];
|
||||
} else if (!I18n.fallbackRules[locale]) {
|
||||
var rules = [],
|
||||
components = locale.split("-");
|
||||
|
||||
for (var l = 1; l < components.length; l++) {
|
||||
rules.push(components.slice(0, l).join("-"));
|
||||
}
|
||||
|
||||
rules.push(I18n.defaultLocale);
|
||||
|
||||
I18n.fallbackRules[locale] = rules;
|
||||
}
|
||||
|
||||
return I18n.fallbackRules[locale];
|
||||
};
|
||||
|
||||
I18n.isValidNode = function(obj, node, undefined) {
|
||||
return obj[node] !== null && obj[node] !== undefined;
|
||||
};
|
||||
@ -95,25 +30,24 @@ I18n.isValidNode = function(obj, node, undefined) {
|
||||
function checkExtras(origScope, sep, extras) {
|
||||
if (!extras || extras.length === 0) { return; }
|
||||
|
||||
for (var i=0; i<extras.length; i++) {
|
||||
for (var i = 0; i < extras.length; i++) {
|
||||
var messages = extras[i];
|
||||
scope = origScope.split(sep);
|
||||
if (scope[0] === 'js') {
|
||||
scope.shift();
|
||||
}
|
||||
|
||||
if (scope[0] === 'js') { scope.shift(); }
|
||||
|
||||
while (messages && scope.length > 0) {
|
||||
currentScope = scope.shift();
|
||||
messages = messages[currentScope];
|
||||
}
|
||||
if (messages !== undefined) {
|
||||
return messages;
|
||||
}
|
||||
|
||||
if (messages !== undefined) { return messages; }
|
||||
}
|
||||
}
|
||||
|
||||
I18n.lookup = function(scope, options) {
|
||||
options = options || {};
|
||||
|
||||
var lookupInitialScope = scope,
|
||||
translations = this.prepareOptions(I18n.translations),
|
||||
locale = options.locale || I18n.currentLocale(),
|
||||
@ -123,16 +57,16 @@ I18n.lookup = function(scope, options) {
|
||||
options = this.prepareOptions(options);
|
||||
|
||||
if (typeof scope === "object") {
|
||||
scope = scope.join(this.defaultSeparator);
|
||||
scope = scope.join(this.SEPARATOR);
|
||||
}
|
||||
|
||||
if (options.scope) {
|
||||
scope = options.scope.toString() + this.defaultSeparator + scope;
|
||||
scope = options.scope.toString() + this.SEPARATOR + scope;
|
||||
}
|
||||
|
||||
var origScope = "" + scope;
|
||||
|
||||
scope = origScope.split(this.defaultSeparator);
|
||||
scope = origScope.split(this.SEPARATOR);
|
||||
|
||||
while (messages && scope.length > 0) {
|
||||
currentScope = scope.shift();
|
||||
@ -140,24 +74,11 @@ I18n.lookup = function(scope, options) {
|
||||
}
|
||||
|
||||
if (messages === undefined) {
|
||||
messages = checkExtras(origScope, this.defaultSeparator, this.extras);
|
||||
messages = checkExtras(origScope, this.SEPARATOR, this.extras);
|
||||
}
|
||||
|
||||
|
||||
if (messages === undefined) {
|
||||
if (I18n.fallbacks) {
|
||||
var fallbacks = this.getFallbacks(locale);
|
||||
for (var fallback = 0; fallback < fallbacks.length; fallbacks++) {
|
||||
messages = I18n.lookup(lookupInitialScope, this.prepareOptions({locale: fallbacks[fallback]}, options));
|
||||
if (messages !== undefined) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (messages === undefined && this.isValidNode(options, "defaultValue")) {
|
||||
messages = options.defaultValue;
|
||||
}
|
||||
messages = options.defaultValue;
|
||||
}
|
||||
|
||||
return messages;
|
||||
@ -193,14 +114,13 @@ I18n.prepareOptions = function() {
|
||||
|
||||
I18n.interpolate = function(message, options) {
|
||||
options = this.prepareOptions(options);
|
||||
|
||||
var matches = message.match(this.PLACEHOLDER),
|
||||
placeholder,
|
||||
value,
|
||||
name;
|
||||
|
||||
if (!matches) {
|
||||
return message;
|
||||
}
|
||||
if (!matches) { return message; }
|
||||
|
||||
for (var i = 0; placeholder = matches[i]; i++) {
|
||||
name = placeholder.replace(this.PLACEHOLDER, "$1");
|
||||
@ -219,24 +139,25 @@ I18n.interpolate = function(message, options) {
|
||||
};
|
||||
|
||||
I18n.translate = function(scope, options) {
|
||||
|
||||
options = this.prepareOptions(options);
|
||||
|
||||
var translation = this.lookup(scope, options);
|
||||
// Fallback to the default locale
|
||||
if (!translation && this.currentLocale() !== this.defaultLocale && !this.noFallbacks) {
|
||||
options.locale = this.defaultLocale;
|
||||
translation = this.lookup(scope, options);
|
||||
}
|
||||
if (!translation && this.currentLocale() !== 'en' && !this.noFallbacks) {
|
||||
options.locale = 'en';
|
||||
translation = this.lookup(scope, options);
|
||||
|
||||
if (!this.noFallbacks) {
|
||||
if (!translation && this.currentLocale() !== this.defaultLocale) {
|
||||
options.locale = this.defaultLocale;
|
||||
translation = this.lookup(scope, options);
|
||||
}
|
||||
if (!translation && this.currentLocale() !== 'en') {
|
||||
options.locale = 'en';
|
||||
translation = this.lookup(scope, options);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
if (typeof translation === "object") {
|
||||
if (typeof options.count === "number") {
|
||||
return this.pluralize(options.count, scope, options);
|
||||
return this.pluralize(translation, scope, options);
|
||||
} else {
|
||||
return translation;
|
||||
}
|
||||
@ -248,158 +169,16 @@ I18n.translate = function(scope, options) {
|
||||
}
|
||||
};
|
||||
|
||||
I18n.localize = function(scope, value) {
|
||||
switch (scope) {
|
||||
case "currency":
|
||||
return this.toCurrency(value);
|
||||
case "number":
|
||||
scope = this.lookup("number.format");
|
||||
return this.toNumber(value, scope);
|
||||
case "percentage":
|
||||
return this.toPercentage(value);
|
||||
default:
|
||||
if (scope.match(/^(date|time)/)) {
|
||||
return this.toTime(scope, value);
|
||||
} else {
|
||||
return value.toString();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
I18n.parseDate = function(date) {
|
||||
var matches, convertedDate;
|
||||
|
||||
// we have a date, so just return it.
|
||||
if (typeof date === "object") {
|
||||
return date;
|
||||
}
|
||||
|
||||
// it matches the following formats:
|
||||
// yyyy-mm-dd
|
||||
// yyyy-mm-dd[ T]hh:mm::ss
|
||||
// yyyy-mm-dd[ T]hh:mm::ss
|
||||
// yyyy-mm-dd[ T]hh:mm::ssZ
|
||||
// yyyy-mm-dd[ T]hh:mm::ss+0000
|
||||
//
|
||||
matches = date.toString().match(/(\d{4})-(\d{2})-(\d{2})(?:[ T](\d{2}):(\d{2}):(\d{2}))?(Z|\+0000)?/);
|
||||
|
||||
if (matches) {
|
||||
for (var i = 1; i <= 6; i++) {
|
||||
matches[i] = parseInt(matches[i], 10) || 0;
|
||||
}
|
||||
|
||||
// month starts on 0
|
||||
matches[2] -= 1;
|
||||
|
||||
if (matches[7]) {
|
||||
convertedDate = new Date(Date.UTC(matches[1], matches[2], matches[3], matches[4], matches[5], matches[6]));
|
||||
} else {
|
||||
convertedDate = new Date(matches[1], matches[2], matches[3], matches[4], matches[5], matches[6]);
|
||||
}
|
||||
} else if (typeof date === "number") {
|
||||
// UNIX timestamp
|
||||
convertedDate = new Date();
|
||||
convertedDate.setTime(date);
|
||||
} else if (date.match(/\d+ \d+:\d+:\d+ [+-]\d+ \d+/)) {
|
||||
// a valid javascript format with timezone info
|
||||
convertedDate = new Date();
|
||||
convertedDate.setTime(Date.parse(date));
|
||||
} else {
|
||||
// an arbitrary javascript string
|
||||
convertedDate = new Date();
|
||||
convertedDate.setTime(Date.parse(date));
|
||||
}
|
||||
|
||||
return convertedDate;
|
||||
};
|
||||
|
||||
I18n.toTime = function(scope, d) {
|
||||
var date = this.parseDate(d),
|
||||
format = this.lookup(scope);
|
||||
|
||||
if (date.toString().match(/invalid/i)) {
|
||||
return date.toString();
|
||||
}
|
||||
|
||||
if (!format) {
|
||||
return date.toString();
|
||||
}
|
||||
|
||||
return this.strftime(date, format);
|
||||
};
|
||||
|
||||
I18n.strftime = function(date, format) {
|
||||
var options = this.lookup("date");
|
||||
|
||||
if (!options) {
|
||||
return date.toString();
|
||||
}
|
||||
|
||||
options.meridian = options.meridian || ["AM", "PM"];
|
||||
|
||||
var weekDay = date.getDay(),
|
||||
day = date.getDate(),
|
||||
year = date.getFullYear(),
|
||||
month = date.getMonth() + 1,
|
||||
hour = date.getHours(),
|
||||
hour12 = hour,
|
||||
meridian = hour > 11 ? 1 : 0,
|
||||
secs = date.getSeconds(),
|
||||
mins = date.getMinutes(),
|
||||
offset = date.getTimezoneOffset(),
|
||||
absOffsetHours = Math.floor(Math.abs(offset / 60)),
|
||||
absOffsetMinutes = Math.abs(offset) - (absOffsetHours * 60),
|
||||
timezoneoffset = (offset > 0 ? "-" : "+") + (absOffsetHours.toString().length < 2 ? "0" + absOffsetHours : absOffsetHours) + (absOffsetMinutes.toString().length < 2 ? "0" + absOffsetMinutes : absOffsetMinutes);
|
||||
|
||||
if (hour12 > 12) {
|
||||
hour12 = hour12 - 12;
|
||||
} else if (hour12 === 0) {
|
||||
hour12 = 12;
|
||||
}
|
||||
|
||||
var padding = function(n) {
|
||||
var s = "0" + n.toString();
|
||||
return s.substr(s.length - 2);
|
||||
};
|
||||
|
||||
var f = format;
|
||||
f = f.replace("%a", options.abbr_day_names[weekDay]);
|
||||
f = f.replace("%A", options.day_names[weekDay]);
|
||||
f = f.replace("%b", options.abbr_month_names[month]);
|
||||
f = f.replace("%B", options.month_names[month]);
|
||||
f = f.replace("%d", padding(day));
|
||||
f = f.replace("%e", day);
|
||||
f = f.replace("%-d", day);
|
||||
f = f.replace("%H", padding(hour));
|
||||
f = f.replace("%-H", hour);
|
||||
f = f.replace("%I", padding(hour12));
|
||||
f = f.replace("%-I", hour12);
|
||||
f = f.replace("%m", padding(month));
|
||||
f = f.replace("%-m", month);
|
||||
f = f.replace("%M", padding(mins));
|
||||
f = f.replace("%-M", mins);
|
||||
f = f.replace("%p", options.meridian[meridian]);
|
||||
f = f.replace("%S", padding(secs));
|
||||
f = f.replace("%-S", secs);
|
||||
f = f.replace("%w", weekDay);
|
||||
f = f.replace("%y", padding(year));
|
||||
f = f.replace("%-y", padding(year).replace(/^0+/, ""));
|
||||
f = f.replace("%Y", year);
|
||||
f = f.replace("%z", timezoneoffset);
|
||||
|
||||
return f;
|
||||
};
|
||||
|
||||
I18n.toNumber = function(number, options) {
|
||||
options = this.prepareOptions(
|
||||
options,
|
||||
this.lookup("number.format"),
|
||||
{precision: 3, separator: ".", delimiter: ",", strip_insignificant_zeros: false}
|
||||
{precision: 3, separator: this.SEPARATOR, delimiter: ",", strip_insignificant_zeros: false}
|
||||
);
|
||||
|
||||
var negative = number < 0,
|
||||
string = Math.abs(number).toFixed(options.precision).toString(),
|
||||
parts = string.split("."),
|
||||
parts = string.split(this.SEPARATOR),
|
||||
precision,
|
||||
buffer = [],
|
||||
formattedNumber;
|
||||
@ -437,23 +216,6 @@ I18n.toNumber = function(number, options) {
|
||||
return formattedNumber;
|
||||
};
|
||||
|
||||
I18n.toCurrency = function(number, options) {
|
||||
options = this.prepareOptions(
|
||||
options,
|
||||
this.lookup("number.currency.format"),
|
||||
this.lookup("number.format"),
|
||||
{unit: "$", precision: 2, format: "%u%n", delimiter: ",", separator: "."}
|
||||
);
|
||||
|
||||
number = this.toNumber(number, options);
|
||||
number = options.format
|
||||
.replace("%u", options.unit)
|
||||
.replace("%n", number)
|
||||
;
|
||||
|
||||
return number;
|
||||
};
|
||||
|
||||
I18n.toHumanSize = function(number, options) {
|
||||
var kb = 1024,
|
||||
size = number,
|
||||
@ -488,18 +250,6 @@ I18n.toHumanSize = function(number, options) {
|
||||
return number;
|
||||
};
|
||||
|
||||
I18n.toPercentage = function(number, options) {
|
||||
options = this.prepareOptions(
|
||||
options,
|
||||
this.lookup("number.percentage.format"),
|
||||
this.lookup("number.format"),
|
||||
{precision: 3, separator: ".", delimiter: ""}
|
||||
);
|
||||
|
||||
number = this.toNumber(number, options);
|
||||
return number + "%";
|
||||
};
|
||||
|
||||
I18n.pluralizer = function(locale) {
|
||||
var pluralizer = this.pluralizationRules[locale];
|
||||
if (pluralizer !== undefined) return pluralizer;
|
||||
@ -514,16 +264,11 @@ I18n.findAndTranslateValidNode = function(keys, translation) {
|
||||
return null;
|
||||
};
|
||||
|
||||
I18n.pluralize = function(count, scope, options) {
|
||||
var translation;
|
||||
|
||||
try { translation = this.lookup(scope, options); } catch (error) {}
|
||||
if (!translation) { return this.missingTranslation(scope); }
|
||||
|
||||
I18n.pluralize = function(translation, scope, options) {
|
||||
options = this.prepareOptions(options);
|
||||
options.count = count.toString();
|
||||
var count = options.count.toString();
|
||||
|
||||
var pluralizer = this.pluralizer(this.currentLocale());
|
||||
var pluralizer = this.pluralizer(options.locale || this.currentLocale());
|
||||
var key = pluralizer(Math.abs(count));
|
||||
var keys = ((typeof key === "object") && (key instanceof Array)) ? key : [key];
|
||||
|
||||
@ -534,52 +279,14 @@ I18n.pluralize = function(count, scope, options) {
|
||||
};
|
||||
|
||||
I18n.missingTranslation = function(scope, key) {
|
||||
var message = '[' + this.currentLocale() + "." + scope;
|
||||
if (key) { message += "." + key; }
|
||||
var message = '[' + this.currentLocale() + this.SEPARATOR + scope;
|
||||
if (key) { message += this.SEPARATOR + key; }
|
||||
return message + ']';
|
||||
};
|
||||
|
||||
I18n.currentLocale = function() {
|
||||
return (I18n.locale || I18n.defaultLocale);
|
||||
return I18n.locale || I18n.defaultLocale;
|
||||
};
|
||||
|
||||
// shortcuts
|
||||
I18n.t = I18n.translate;
|
||||
I18n.l = I18n.localize;
|
||||
I18n.p = I18n.pluralize;
|
||||
|
||||
I18n.enable_verbose_localization = function(){
|
||||
var counter = 0;
|
||||
var keys = {};
|
||||
var t = I18n.t;
|
||||
|
||||
I18n.noFallbacks = true;
|
||||
|
||||
I18n.t = I18n.translate = function(scope, value){
|
||||
var current = keys[scope];
|
||||
if(!current) {
|
||||
current = keys[scope] = ++counter;
|
||||
var message = "Translation #" + current + ": " + scope;
|
||||
if (!_.isEmpty(value)) {
|
||||
message += ", parameters: " + JSON.stringify(value);
|
||||
}
|
||||
Em.Logger.info(message);
|
||||
}
|
||||
return t.apply(I18n, [scope, value]) + " (t" + current + ")";
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
I18n.verbose_localization_session = function(){
|
||||
sessionStorage.setItem("verbose_localization", "true");
|
||||
I18n.enable_verbose_localization();
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
if(sessionStorage && sessionStorage.getItem("verbose_localization")) {
|
||||
I18n.enable_verbose_localization();
|
||||
}
|
||||
} catch(e){
|
||||
// we don't care really, can happen if cookies disabled
|
||||
}
|
||||
|
||||
@ -1,7 +1,3 @@
|
||||
//= depend_on 'client.ja.yml'
|
||||
//= require locales/i18n
|
||||
<%= JsLocaleHelper.output_locale(:ja) %>
|
||||
|
||||
I18n.pluralizationRules['ja'] = function (n) {
|
||||
return n === 0 ? ["zero", "none", "other"] : "other";
|
||||
};
|
||||
|
||||
@ -1,9 +1,3 @@
|
||||
//= depend_on 'client.ro.yml'
|
||||
//= require locales/i18n
|
||||
<%= JsLocaleHelper.output_locale(:ro) %>
|
||||
|
||||
I18n.pluralizationRules['ro'] = function (n) {
|
||||
if (n == 1) return "one";
|
||||
if (n === 0 || n % 100 >= 1 && n % 100 <= 19) return "few";
|
||||
return "other";
|
||||
};
|
||||
|
||||
@ -1,9 +1,3 @@
|
||||
//= depend_on 'client.ru.yml'
|
||||
//= require locales/i18n
|
||||
<%= JsLocaleHelper.output_locale(:ru) %>
|
||||
|
||||
I18n.pluralizationRules['ru'] = function (n) {
|
||||
if (n % 10 == 1 && n % 100 != 11) return "one";
|
||||
if (n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 12 || n % 100 > 14)) return "few";
|
||||
return "other";
|
||||
};
|
||||
|
||||
@ -1,9 +1,3 @@
|
||||
//= depend_on 'client.sk.yml'
|
||||
//= require locales/i18n
|
||||
<%= JsLocaleHelper.output_locale(:sk) %>
|
||||
|
||||
I18n.pluralizationRules['sk'] = function (n) {
|
||||
if (n == 1) return "one";
|
||||
if (n >= 2 && n <= 4) return "few";
|
||||
return "other";
|
||||
};
|
||||
|
||||
@ -1,5 +1,3 @@
|
||||
//= depend_on 'client.tr_TR.yml'
|
||||
//= require locales/i18n
|
||||
<%= JsLocaleHelper.output_locale(:tr_TR) %>
|
||||
|
||||
I18n.pluralizationRules['tr_TR'] = function(n) { return "other"; }
|
||||
|
||||
@ -1,10 +1,3 @@
|
||||
//= depend_on 'client.uk.yml'
|
||||
//= require locales/i18n
|
||||
<%= JsLocaleHelper.output_locale(:uk) %>
|
||||
|
||||
I18n.pluralizationRules['uk'] = function (n) {
|
||||
if (n == 0) return ["zero", "none", "other"];
|
||||
if (n % 10 == 1 && n % 100 != 11) return "one";
|
||||
if (n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 >= 20)) return "few";
|
||||
return "other"; // TODO: should be "many" but is not defined in translations
|
||||
};
|
||||
|
||||
@ -148,34 +148,6 @@ div.ac-wrap {
|
||||
}
|
||||
}
|
||||
|
||||
.edit-auto-close-modal {
|
||||
.btn.pull-right {
|
||||
margin-right: 10px;
|
||||
}
|
||||
form {
|
||||
margin: 0;
|
||||
}
|
||||
.auto-close-fields {
|
||||
i.fa-clock-o {
|
||||
font-size: 1.143em;
|
||||
}
|
||||
input {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.edit-category-modal {
|
||||
.auto-close-fields {
|
||||
input[type=text] {
|
||||
width: 50px;
|
||||
}
|
||||
label {
|
||||
font-size: .929em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#reply-control {
|
||||
.composer-loading {
|
||||
position: absolute;
|
||||
|
||||
@ -72,6 +72,28 @@ $input-width: 220px;
|
||||
}
|
||||
}
|
||||
|
||||
.invites-show {
|
||||
.two-col {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.col-image {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
form {
|
||||
margin-top: 24px;
|
||||
button.btn-primary {
|
||||
margin-top: 10px;
|
||||
}
|
||||
label {
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// alternate login / create new account buttons should be de-emphasized
|
||||
|
||||
|
||||
@ -184,6 +184,10 @@
|
||||
display: block;
|
||||
padding: 5px;
|
||||
transition: all linear .15s;
|
||||
|
||||
.user-results {
|
||||
color: dark-light-choose(scale-color($primary, $lightness: 30%), scale-color($secondary, $lightness: 70%));
|
||||
}
|
||||
}
|
||||
|
||||
&:hover a:not(.badge-notification) {
|
||||
|
||||
@ -300,3 +300,33 @@
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.edit-auto-close-modal {
|
||||
.btn.pull-right {
|
||||
margin-right: 10px;
|
||||
}
|
||||
form {
|
||||
margin: 0;
|
||||
}
|
||||
.auto-close-fields {
|
||||
i.fa-clock-o {
|
||||
font-size: 1.143em;
|
||||
}
|
||||
input {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.edit-category-modal {
|
||||
.auto-close-fields, .num-featured-topics-fields, .position-fields {
|
||||
input[type=text] {
|
||||
width: 50px;
|
||||
}
|
||||
}
|
||||
|
||||
.auto-close-fields label {
|
||||
font-size: .929em;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -139,6 +139,12 @@ aside.onebox {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
// tighten bottom margin on last para
|
||||
p:last-child {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
// twitter fixes
|
||||
.tweet-images {
|
||||
display: block;
|
||||
clear: both;
|
||||
@ -150,6 +156,7 @@ aside.onebox {
|
||||
float: none;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -15,7 +15,7 @@
|
||||
-o-transform: rotate(#{$degrees}deg);
|
||||
transform: rotate(#{$degrees}deg);
|
||||
|
||||
filter: progid:DXImageTransform.Microsoft.Matrix(sizingMethod='auto expand', M11=#{cos($degrees)}, M12=-#{sin($degrees)}, M21=#{sin($degrees)}, M22=#{cos($degrees)});
|
||||
filter: "progid:DXImageTransform.Microsoft.Matrix(sizingMethod='auto expand', M11=#{cos($degrees)}, M12=-#{sin($degrees)}, M21=#{sin($degrees)}, M22=#{cos($degrees)})";
|
||||
-ms-filter: "progid:DXImageTransform.Microsoft.Matrix(sizingMethod='auto expand', M11=#{cos($degrees)}, M12=-#{sin($degrees)}, M21=#{sin($degrees)}, M22=#{cos($degrees)})";
|
||||
zoom: 1;
|
||||
}
|
||||
|
||||
@ -48,6 +48,8 @@
|
||||
}
|
||||
|
||||
.public-user-fields {
|
||||
margin-top: 8px;
|
||||
margin-bottom: 8px;
|
||||
.user-field-name {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
@ -8,7 +8,7 @@
|
||||
%nav {
|
||||
margin-left: 0;
|
||||
list-style: none;
|
||||
li > a {
|
||||
li a {
|
||||
display: block;
|
||||
text-decoration: none;
|
||||
}
|
||||
@ -56,7 +56,7 @@
|
||||
&:last-of-type {
|
||||
border-bottom: 0;
|
||||
}
|
||||
> a {
|
||||
a {
|
||||
margin: 0;
|
||||
padding: 13px 13px 13px 30px;
|
||||
font-size: 1.143em;
|
||||
|
||||
@ -59,16 +59,46 @@
|
||||
|
||||
}
|
||||
|
||||
.password-reset {
|
||||
.password-reset, .invites-show {
|
||||
.col-form {
|
||||
padding-top: 40px;
|
||||
padding-left: 20px;
|
||||
}
|
||||
h2 {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.password-reset-img {
|
||||
.col-image img {
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
}
|
||||
}
|
||||
|
||||
.password-reset {
|
||||
.col-form {
|
||||
padding-top: 40px;
|
||||
}
|
||||
}
|
||||
|
||||
.invites-show {
|
||||
padding-top: 20px;
|
||||
|
||||
.two-col {
|
||||
margin-top: 30px;
|
||||
}
|
||||
.col-image {
|
||||
width: 200px;
|
||||
img {
|
||||
width: 200px;
|
||||
}
|
||||
}
|
||||
.col-form {
|
||||
margin-left: 200px;
|
||||
.inline-invite-img {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
form {
|
||||
label, .input {
|
||||
margin-left: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -83,7 +83,7 @@
|
||||
border-top: 1px solid dark-light-diff($primary, $secondary, 90%, -75%);
|
||||
padding-top: 10px;
|
||||
height: 20px;
|
||||
width: 757px;
|
||||
max-width: 757px;
|
||||
}
|
||||
|
||||
#topic-progress-wrapper {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user