Version bump
This commit is contained in:
commit
a2ebae2d5b
23
.travis.yml
23
.travis.yml
@ -1,9 +1,15 @@
|
||||
language: ruby
|
||||
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
- beta
|
||||
- stable
|
||||
|
||||
env:
|
||||
global:
|
||||
- DISCOURSE_HOSTNAME=www.example.com
|
||||
- RUBY_GC_MALLOC_LIMIT=50000000
|
||||
- RUBY_GLOBAL_METHOD_CACHE_SIZE=131072
|
||||
matrix:
|
||||
- "RAILS_MASTER=0 QUNIT_RUN=0 RUN_LINT=0"
|
||||
- "RAILS_MASTER=0 QUNIT_RUN=1 RUN_LINT=0"
|
||||
@ -11,8 +17,9 @@ env:
|
||||
|
||||
addons:
|
||||
chrome: stable
|
||||
postgresql: 9.5
|
||||
postgresql: 9.6
|
||||
apt:
|
||||
update: true
|
||||
packages:
|
||||
- gifsicle
|
||||
- jpegoptim
|
||||
@ -33,6 +40,7 @@ sudo: required
|
||||
dist: trusty
|
||||
|
||||
cache:
|
||||
apt: true
|
||||
yarn: true
|
||||
directories:
|
||||
- vendor/bundle
|
||||
@ -50,10 +58,11 @@ before_install:
|
||||
- export PATH=$HOME/.yarn/bin:$PATH
|
||||
|
||||
install:
|
||||
- bash -c "if [ '$RAILS_MASTER' == '1' ]; then bundle update --retry=3 --jobs=3 arel rails seed-fu; fi"
|
||||
- bash -c "if [ '$RAILS_MASTER' == '0' ]; then bundle install --without development --deployment --retry=3 --jobs=3; fi"
|
||||
- bash -c "if [ '$RUN_LINT' == '1' ]; then yarn global add eslint babel-eslint; fi"
|
||||
- bash -c "if [ '$QUNIT_RUN' == '1' ]; then yarn install --dev; fi"
|
||||
- bash -c "if [ '$RAILS_MASTER' == '1' ]; then bundle update --retry=3 --jobs=3 arel rails seed-fu > /dev/null; fi"
|
||||
- bash -c "if [ '$RAILS_MASTER' == '0' ]; then bundle install --without development --deployment --retry=3 --jobs=3 > /dev/null; fi"
|
||||
- bash -c "if [ '$RUN_LINT' == '1' ]; then yarn global add eslint babel-eslint > /dev/null; fi"
|
||||
- bash -c "if [ '$QUNIT_RUN' == '1' ]; then yarn install --dev > /dev/null; fi"
|
||||
- bash -c "if [ '$RUN_LINT' != '1' ]; then bundle exec rake db:create db:migrate > /dev/null; fi"
|
||||
|
||||
script:
|
||||
- |
|
||||
@ -66,8 +75,6 @@ script:
|
||||
eslint --ext .es6 plugins/**/test/javascripts && \
|
||||
eslint app/assets/javascripts test/javascripts
|
||||
else
|
||||
bundle exec rake db:create db:migrate
|
||||
|
||||
if [ '$QUNIT_RUN' == '1' ]; then
|
||||
bundle exec rake qunit:test['500000'] && \
|
||||
bundle exec rake plugin:qunit
|
||||
|
||||
6
Gemfile
6
Gemfile
@ -34,7 +34,7 @@ gem 'redis-namespace'
|
||||
|
||||
gem 'active_model_serializers', '~> 0.8.3'
|
||||
|
||||
gem 'onebox', '1.8.47'
|
||||
gem 'onebox', '1.8.48'
|
||||
|
||||
gem 'http_accept_language', '~>2.0.5', require: false
|
||||
|
||||
@ -58,7 +58,7 @@ gem 'aws-sdk-s3', require: false
|
||||
gem 'excon', require: false
|
||||
gem 'unf', require: false
|
||||
|
||||
gem 'email_reply_trimmer', '0.1.11'
|
||||
gem 'email_reply_trimmer', '~> 0.1'
|
||||
|
||||
# Forked until https://github.com/toy/image_optim/pull/149 is merged
|
||||
gem 'discourse_image_optim', require: 'image_optim'
|
||||
@ -115,7 +115,7 @@ group :test, :development do
|
||||
gem 'listen', require: false
|
||||
gem 'certified', require: false
|
||||
# later appears to break Fabricate(:topic, category: category)
|
||||
gem 'fabrication', '2.9.8', require: false
|
||||
gem 'fabrication', require: false
|
||||
gem 'mocha', require: false
|
||||
gem 'rb-fsevent', require: RUBY_PLATFORM =~ /darwin/i ? 'rb-fsevent' : false
|
||||
gem 'rb-inotify', '~> 0.9', require: RUBY_PLATFORM =~ /linux/i ? 'rb-inotify' : false
|
||||
|
||||
10
Gemfile.lock
10
Gemfile.lock
@ -89,7 +89,7 @@ GEM
|
||||
image_size (~> 1.5)
|
||||
in_threads (~> 1.3)
|
||||
progress (~> 3.0, >= 3.0.1)
|
||||
email_reply_trimmer (0.1.11)
|
||||
email_reply_trimmer (0.1.12)
|
||||
ember-data-source (2.2.1)
|
||||
ember-source (>= 1.8, < 3.0)
|
||||
ember-handlebars-template (0.7.5)
|
||||
@ -225,7 +225,7 @@ GEM
|
||||
omniauth-twitter (1.3.0)
|
||||
omniauth-oauth (~> 1.1)
|
||||
rack
|
||||
onebox (1.8.47)
|
||||
onebox (1.8.48)
|
||||
htmlentities (~> 4.3)
|
||||
moneta (~> 1.0)
|
||||
multi_json (~> 1.11)
|
||||
@ -415,13 +415,13 @@ DEPENDENCIES
|
||||
certified
|
||||
cppjieba_rb
|
||||
discourse_image_optim
|
||||
email_reply_trimmer (= 0.1.11)
|
||||
email_reply_trimmer (~> 0.1)
|
||||
ember-handlebars-template (= 0.7.5)
|
||||
ember-rails (= 0.18.5)
|
||||
ember-source (= 2.13.3)
|
||||
excon
|
||||
execjs
|
||||
fabrication (= 2.9.8)
|
||||
fabrication
|
||||
fakeweb (~> 1.3.0)
|
||||
fast_blank
|
||||
fast_xor
|
||||
@ -461,7 +461,7 @@ DEPENDENCIES
|
||||
omniauth-oauth2
|
||||
omniauth-openid
|
||||
omniauth-twitter
|
||||
onebox (= 1.8.47)
|
||||
onebox (= 1.8.48)
|
||||
openid-redis-store
|
||||
pg (~> 0.21.0)
|
||||
pry-nav
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import loadScript from 'discourse/lib/load-script';
|
||||
import { number } from 'discourse/lib/formatter';
|
||||
|
||||
export default Ember.Component.extend({
|
||||
tagName: 'canvas',
|
||||
@ -22,10 +23,16 @@ export default Ember.Component.extend({
|
||||
data: data,
|
||||
options: {
|
||||
responsive: true,
|
||||
tooltips: {
|
||||
callbacks: {
|
||||
title: (context) => moment(context[0].xLabel, "YYYY-MM-DD").format("LL")
|
||||
}
|
||||
},
|
||||
scales: {
|
||||
yAxes: [{
|
||||
display: true,
|
||||
ticks: {
|
||||
callback: (label) => number(label),
|
||||
suggestedMin: 0
|
||||
}
|
||||
}]
|
||||
|
||||
@ -1,32 +1,13 @@
|
||||
import { ajax } from "discourse/lib/ajax";
|
||||
import Report from "admin/models/report";
|
||||
import AsyncReport from "admin/mixins/async-report";
|
||||
|
||||
export default Ember.Component.extend(AsyncReport, {
|
||||
classNames: ["dashboard-table", "dashboard-inline-table", "fixed"],
|
||||
help: null,
|
||||
helpPage: null,
|
||||
|
||||
loadReport(report_json) {
|
||||
return Report.create(report_json);
|
||||
},
|
||||
classNames: ["dashboard-inline-table"],
|
||||
|
||||
fetchReport() {
|
||||
this._super();
|
||||
|
||||
let payload = { data: { cache: true, facets: ["total", "prev30Days"] } };
|
||||
|
||||
if (this.get("startDate")) {
|
||||
payload.data.start_date = this.get("startDate").format("YYYY-MM-DD[T]HH:mm:ss.SSSZZ");
|
||||
}
|
||||
|
||||
if (this.get("endDate")) {
|
||||
payload.data.end_date = this.get("endDate").format("YYYY-MM-DD[T]HH:mm:ss.SSSZZ");
|
||||
}
|
||||
|
||||
if (this.get("limit")) {
|
||||
payload.data.limit = this.get("limit");
|
||||
}
|
||||
let payload = this.buildPayload(["total", "prev30Days"]);
|
||||
|
||||
return Ember.RSVP.Promise.all(this.get("dataSources").map(dataSource => {
|
||||
return ajax(dataSource, payload)
|
||||
|
||||
@ -52,17 +52,7 @@ export default Ember.Component.extend(AsyncReport, {
|
||||
fetchReport() {
|
||||
this._super();
|
||||
|
||||
let payload = {
|
||||
data: { cache: true, facets: ["prev_period"] }
|
||||
};
|
||||
|
||||
if (this.get("startDate")) {
|
||||
payload.data.start_date = this.get("startDate").locale('en').format('YYYY-MM-DD[T]HH:mm:ss.SSSZZ');
|
||||
}
|
||||
|
||||
if (this.get("endDate")) {
|
||||
payload.data.end_date = this.get("endDate").locale('en').format('YYYY-MM-DD[T]HH:mm:ss.SSSZZ');
|
||||
}
|
||||
let payload = this.buildPayload(["prev_period"]);
|
||||
|
||||
if (this._chart) {
|
||||
this._chart.destroy();
|
||||
@ -110,7 +100,7 @@ export default Ember.Component.extend(AsyncReport, {
|
||||
labels,
|
||||
datasets: reportsForPeriod.map(report => {
|
||||
return {
|
||||
data: Ember.makeArray(report.data).map(d => d.y),
|
||||
data: Ember.makeArray(report.data).map(d => number(d.y, { ceil: true })),
|
||||
backgroundColor: "rgba(200,220,240,0.3)",
|
||||
borderColor: report.color
|
||||
};
|
||||
@ -157,7 +147,7 @@ export default Ember.Component.extend(AsyncReport, {
|
||||
scales: {
|
||||
yAxes: [{
|
||||
display: true,
|
||||
ticks: { callback: (label) => number(label) }
|
||||
ticks: { callback: (label) => number(label, { ceil: true }) }
|
||||
}],
|
||||
xAxes: [{
|
||||
display: true,
|
||||
|
||||
@ -0,0 +1,19 @@
|
||||
import { ajax } from "discourse/lib/ajax";
|
||||
import AsyncReport from "admin/mixins/async-report";
|
||||
|
||||
export default Ember.Component.extend(AsyncReport, {
|
||||
classNames: ["dashboard-table"],
|
||||
|
||||
fetchReport() {
|
||||
this._super();
|
||||
|
||||
let payload = this.buildPayload(["total", "prev30Days"]);
|
||||
|
||||
return Ember.RSVP.Promise.all(this.get("dataSources").map(dataSource => {
|
||||
return ajax(dataSource, payload)
|
||||
.then(response => {
|
||||
this.get("reports").pushObject(this.loadReport(response.report));
|
||||
});
|
||||
}));
|
||||
}
|
||||
});
|
||||
@ -5,7 +5,7 @@ export default Ember.Component.extend({
|
||||
classNames: ['watched-word-form'],
|
||||
formSubmitted: false,
|
||||
actionKey: null,
|
||||
showSuccessMessage: false,
|
||||
showMessage: false,
|
||||
|
||||
@computed('regularExpressions')
|
||||
placeholderKey(regularExpressions) {
|
||||
@ -14,21 +14,33 @@ export default Ember.Component.extend({
|
||||
},
|
||||
|
||||
@observes('word')
|
||||
removeSuccessMessage() {
|
||||
if (this.get('showSuccessMessage') && !Ember.isEmpty(this.get('word'))) {
|
||||
this.set('showSuccessMessage', false);
|
||||
removeMessage() {
|
||||
if (this.get('showMessage') && !Ember.isEmpty(this.get('word'))) {
|
||||
this.set('showMessage', false);
|
||||
}
|
||||
},
|
||||
|
||||
@computed('word')
|
||||
isUniqueWord(word) {
|
||||
const words = this.get("filteredContent") || [];
|
||||
const filtered = words.filter(content => content.action === this.get("actionKey"));
|
||||
return filtered.every(content => content.word.toLowerCase() !== word.toLowerCase());
|
||||
},
|
||||
|
||||
actions: {
|
||||
submit() {
|
||||
if (!this.get("isUniqueWord")) {
|
||||
this.setProperties({ showMessage: true, message: I18n.t('admin.watched_words.form.exists') });
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.get('formSubmitted')) {
|
||||
this.set('formSubmitted', true);
|
||||
|
||||
const watchedWord = WatchedWord.create({ word: this.get('word'), action: this.get('actionKey') });
|
||||
|
||||
watchedWord.save().then(result => {
|
||||
this.setProperties({ word: '', formSubmitted: false, showSuccessMessage: true });
|
||||
this.setProperties({ word: '', formSubmitted: false, showMessage: true, message: I18n.t('admin.watched_words.form.success') });
|
||||
this.sendAction('action', WatchedWord.create(result));
|
||||
Ember.run.schedule('afterRender', () => this.$('.watched-word-input').focus());
|
||||
}).catch(e => {
|
||||
|
||||
@ -4,6 +4,7 @@ import Report from 'admin/models/report';
|
||||
import computed from 'ember-addons/ember-computed-decorators';
|
||||
|
||||
export default Ember.Controller.extend({
|
||||
classNames: ["admin-reports"],
|
||||
queryParams: ["mode", "start_date", "end_date", "category_id", "group_id"],
|
||||
viewMode: 'graph',
|
||||
viewingTable: Em.computed.equal('viewMode', 'table'),
|
||||
|
||||
@ -50,6 +50,36 @@ export default Ember.Controller.extend(CanCheckEmails, {
|
||||
return userPath(`${username}/preferences`);
|
||||
},
|
||||
|
||||
@computed('model.can_delete_all_posts', 'model.staff', 'model.post_count')
|
||||
deleteAllPostsExplanation(canDeleteAllPosts, staff, postCount) {
|
||||
if (canDeleteAllPosts) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (staff) {
|
||||
return I18n.t('admin.user.delete_posts_forbidden_because_staff');
|
||||
}
|
||||
if (postCount > this.siteSettings.delete_all_posts_max) {
|
||||
return I18n.t('admin.user.cant_delete_all_too_many_posts', {count: this.siteSettings.delete_all_posts_max});
|
||||
} else {
|
||||
return I18n.t('admin.user.cant_delete_all_posts', {count: this.siteSettings.delete_user_max_post_age});
|
||||
}
|
||||
},
|
||||
|
||||
@computed('model.canBeDeleted', 'model.staff')
|
||||
deleteExplanation(canBeDeleted, staff) {
|
||||
if (canBeDeleted) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (staff) {
|
||||
return I18n.t('admin.user.delete_forbidden_because_staff');
|
||||
} else {
|
||||
return I18n.t('admin.user.delete_forbidden', {count: this.siteSettings.delete_user_max_post_age});
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
actions: {
|
||||
|
||||
impersonate() { return this.get("model").impersonate(); },
|
||||
@ -73,6 +103,16 @@ export default Ember.Controller.extend(CanCheckEmails, {
|
||||
anonymize() { return this.get('model').anonymize(); },
|
||||
disableSecondFactor() { return this.get('model').disableSecondFactor(); },
|
||||
|
||||
|
||||
clearPenaltyHistory() {
|
||||
let user = this.get('model');
|
||||
return ajax(`/admin/users/${user.get('id')}/penalty_history`, {
|
||||
type: 'DELETE'
|
||||
}).then(() => {
|
||||
user.set('tl3_requirements.penalty_counts.total', 0);
|
||||
}).catch(popupAjaxError);
|
||||
},
|
||||
|
||||
destroy() {
|
||||
const postCount = this.get('model.post_count');
|
||||
if (postCount <= 5) {
|
||||
|
||||
@ -46,6 +46,10 @@ export default Ember.Controller.extend({
|
||||
actions: {
|
||||
clearFilter() {
|
||||
this.setProperties({ filter: '' });
|
||||
},
|
||||
|
||||
toggleMenu() {
|
||||
$('.admin-detail').toggleClass('mobile-closed mobile-open');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
import computed from "ember-addons/ember-computed-decorators";
|
||||
import Report from "admin/models/report";
|
||||
|
||||
export default Ember.Mixin.create({
|
||||
classNameBindings: ["isLoading"],
|
||||
classNameBindings: ["isLoading", "dataSourceNames"],
|
||||
reports: null,
|
||||
isLoading: false,
|
||||
dataSourceNames: "",
|
||||
@ -17,6 +18,24 @@ export default Ember.Mixin.create({
|
||||
return dataSourceNames.split(",").map(source => `/admin/reports/${source}`);
|
||||
},
|
||||
|
||||
buildPayload(facets) {
|
||||
let payload = { data: { cache: true, facets } };
|
||||
|
||||
if (this.get("startDate")) {
|
||||
payload.data.start_date = this.get("startDate").format("YYYY-MM-DD[T]HH:mm:ss.SSSZZ");
|
||||
}
|
||||
|
||||
if (this.get("endDate")) {
|
||||
payload.data.end_date = this.get("endDate").format("YYYY-MM-DD[T]HH:mm:ss.SSSZZ");
|
||||
}
|
||||
|
||||
if (this.get("limit")) {
|
||||
payload.data.limit = this.get("limit");
|
||||
}
|
||||
|
||||
return payload;
|
||||
},
|
||||
|
||||
@computed("reports.[]", "startDate", "endDate", "dataSourceNames")
|
||||
reportsForPeriod(reports, startDate, endDate, dataSourceNames) {
|
||||
// on a slow network fetchReport could be called multiple times between
|
||||
@ -25,7 +44,6 @@ export default Ember.Mixin.create({
|
||||
// the array contains only unique values
|
||||
reports = reports.uniqBy("report_key");
|
||||
|
||||
|
||||
const sort = (r) => {
|
||||
if (r.length > 1) {
|
||||
return dataSourceNames
|
||||
@ -40,7 +58,6 @@ export default Ember.Mixin.create({
|
||||
return sort(reports);
|
||||
}
|
||||
|
||||
|
||||
return sort(reports.filter(report => {
|
||||
return report.report_key.includes(startDate.format("YYYYMMDD")) &&
|
||||
report.report_key.includes(endDate.format("YYYYMMDD"));
|
||||
@ -71,7 +88,9 @@ export default Ember.Mixin.create({
|
||||
this.set("isLoading", false);
|
||||
},
|
||||
|
||||
loadReport() {},
|
||||
loadReport(jsonReport) {
|
||||
return Report.create(jsonReport);
|
||||
},
|
||||
|
||||
fetchReport() {
|
||||
this.set("reports", []);
|
||||
|
||||
@ -87,21 +87,6 @@ const AdminUser = Discourse.User.extend({
|
||||
}).then(() => this.set('api_key', null));
|
||||
},
|
||||
|
||||
deleteAllPostsExplanation: function() {
|
||||
if (!this.get('can_delete_all_posts')) {
|
||||
if (this.get('deleteForbidden') && this.get('staff')) {
|
||||
return I18n.t('admin.user.delete_posts_forbidden_because_staff');
|
||||
}
|
||||
if (this.get('post_count') > Discourse.SiteSettings.delete_all_posts_max) {
|
||||
return I18n.t('admin.user.cant_delete_all_too_many_posts', {count: Discourse.SiteSettings.delete_all_posts_max});
|
||||
} else {
|
||||
return I18n.t('admin.user.cant_delete_all_posts', {count: Discourse.SiteSettings.delete_user_max_post_age});
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}.property('can_delete_all_posts', 'deleteForbidden'),
|
||||
|
||||
deleteAllPosts() {
|
||||
const user = this,
|
||||
message = I18n.messageFormat('admin.user.delete_all_posts_confirm_MF', { "POSTS": user.get('post_count'), "TOPICS": user.get('topic_count') }),
|
||||
@ -240,8 +225,6 @@ const AdminUser = Discourse.User.extend({
|
||||
return this.get('trust_level') < 4;
|
||||
}.property('trust_level'),
|
||||
|
||||
isSuspended: Em.computed.equal('suspended', true),
|
||||
isSilenced: Ember.computed.equal('silenced', true),
|
||||
canSuspend: Em.computed.not('staff'),
|
||||
|
||||
suspendDuration: function() {
|
||||
@ -353,8 +336,6 @@ const AdminUser = Discourse.User.extend({
|
||||
}).catch(popupAjaxError);
|
||||
},
|
||||
|
||||
anonymizeForbidden: Em.computed.not("can_be_anonymized"),
|
||||
|
||||
anonymize() {
|
||||
const user = this,
|
||||
message = I18n.t("admin.user.anonymize_confirm");
|
||||
@ -393,20 +374,6 @@ const AdminUser = Discourse.User.extend({
|
||||
bootbox.dialog(message, buttons, { "classes": "delete-user-modal" });
|
||||
},
|
||||
|
||||
deleteForbidden: Em.computed.not("canBeDeleted"),
|
||||
|
||||
deleteExplanation: function() {
|
||||
if (this.get('deleteForbidden')) {
|
||||
if (this.get('staff')) {
|
||||
return I18n.t('admin.user.delete_forbidden_because_staff');
|
||||
} else {
|
||||
return I18n.t('admin.user.delete_forbidden', {count: Discourse.SiteSettings.delete_user_max_post_age});
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}.property('deleteForbidden'),
|
||||
|
||||
destroy(opts) {
|
||||
const user = this,
|
||||
message = I18n.t("admin.user.delete_confirm"),
|
||||
|
||||
@ -1,22 +1,24 @@
|
||||
import { ajax } from 'discourse/lib/ajax';
|
||||
import { ajax } from "discourse/lib/ajax";
|
||||
import round from "discourse/lib/round";
|
||||
import { fillMissingDates } from 'discourse/lib/utilities';
|
||||
import computed from 'ember-addons/ember-computed-decorators';
|
||||
import { fillMissingDates } from "discourse/lib/utilities";
|
||||
import computed from "ember-addons/ember-computed-decorators";
|
||||
import { number } from 'discourse/lib/formatter';
|
||||
|
||||
const Report = Discourse.Model.extend({
|
||||
average: false,
|
||||
percent: false,
|
||||
higher_is_better: true,
|
||||
|
||||
@computed("type", "start_date", "end_date")
|
||||
reportUrl(type, start_date, end_date) {
|
||||
start_date = moment(start_date).locale('en').format("YYYY-MM-DD");
|
||||
end_date = moment(end_date).locale('en').format("YYYY-MM-DD");
|
||||
start_date = moment(start_date).locale("en").format("YYYY-MM-DD");
|
||||
end_date = moment(end_date).locale("en").format("YYYY-MM-DD");
|
||||
return Discourse.getURL(`/admin/reports/${type}?start_date=${start_date}&end_date=${end_date}`);
|
||||
},
|
||||
|
||||
valueAt(numDaysAgo) {
|
||||
if (this.data) {
|
||||
const wantedDate = moment().subtract(numDaysAgo, "days").locale('en').format("YYYY-MM-DD");
|
||||
const wantedDate = moment().subtract(numDaysAgo, "days").locale("en").format("YYYY-MM-DD");
|
||||
const item = this.data.find(d => d.x === wantedDate);
|
||||
if (item) {
|
||||
return item.y;
|
||||
@ -29,7 +31,7 @@ const Report = Discourse.Model.extend({
|
||||
if (this.data) {
|
||||
const earliestDate = moment().subtract(endDaysAgo, "days").startOf("day");
|
||||
const latestDate = moment().subtract(startDaysAgo, "days").startOf("day");
|
||||
var d, sum = 0, count = 0;
|
||||
let d, sum = 0, count = 0;
|
||||
_.each(this.data, datum => {
|
||||
d = moment(datum.x);
|
||||
if (d >= earliestDate && d <= latestDate) {
|
||||
@ -46,6 +48,7 @@ const Report = Discourse.Model.extend({
|
||||
yesterdayCount: function() { return this.valueAt(1); }.property("data", "average"),
|
||||
sevenDaysAgoCount: function() { return this.valueAt(7); }.property("data", "average"),
|
||||
thirtyDaysAgoCount: function() { return this.valueAt(30); }.property("data", "average"),
|
||||
|
||||
lastSevenDaysCount: function() {
|
||||
return this.averageCount(7, this.valueFor(1, 7));
|
||||
}.property("data", "average"),
|
||||
@ -57,112 +60,66 @@ const Report = Discourse.Model.extend({
|
||||
return this.get("average") ? value / count : value;
|
||||
},
|
||||
|
||||
@computed('yesterdayCount')
|
||||
yesterdayTrend(yesterdayCount) {
|
||||
const yesterdayVal = yesterdayCount;
|
||||
const twoDaysAgoVal = this.valueAt(2);
|
||||
const change = ((yesterdayVal - twoDaysAgoVal) / yesterdayVal) * 100;
|
||||
|
||||
if (change > 50) {
|
||||
return "high-trending-up";
|
||||
} else if (change > 0) {
|
||||
return "trending-up";
|
||||
} else if (change === 0) {
|
||||
return "no-change";
|
||||
} else if (change < -50) {
|
||||
return "high-trending-down";
|
||||
} else if (change < 0) {
|
||||
return "trending-down";
|
||||
}
|
||||
@computed("yesterdayCount", "higher_is_better")
|
||||
yesterdayTrend(yesterdayCount, higherIsBetter) {
|
||||
return this._computeTrend(this.valueAt(2), yesterdayCount, higherIsBetter);
|
||||
},
|
||||
|
||||
@computed('lastSevenDaysCount')
|
||||
sevenDayTrend(lastSevenDaysCount) {
|
||||
const currentPeriod = lastSevenDaysCount;
|
||||
const prevPeriod = this.valueFor(8, 14);
|
||||
const change = ((currentPeriod - prevPeriod) / prevPeriod) * 100;
|
||||
|
||||
if (change > 50) {
|
||||
return "high-trending-up";
|
||||
} else if (change > 0) {
|
||||
return "trending-up";
|
||||
} else if (change === 0) {
|
||||
return "no-change";
|
||||
} else if (change < -50) {
|
||||
return "high-trending-down";
|
||||
} else if (change < 0) {
|
||||
return "trending-down";
|
||||
}
|
||||
@computed("lastSevenDaysCount", "higher_is_better")
|
||||
sevenDaysTrend(lastSevenDaysCount, higherIsBetter) {
|
||||
return this._computeTrend(this.valueFor(8, 14), lastSevenDaysCount, higherIsBetter);
|
||||
},
|
||||
|
||||
@computed('data')
|
||||
@computed("data")
|
||||
currentTotal(data){
|
||||
return _.reduce(data, (cur, pair) => cur + pair.y, 0);
|
||||
},
|
||||
|
||||
@computed('data', 'currentTotal')
|
||||
@computed("data", "currentTotal")
|
||||
currentAverage(data, total) {
|
||||
return Ember.makeArray(data).length === 0 ? 0 : parseFloat((total / parseFloat(data.length)).toFixed(1));
|
||||
},
|
||||
|
||||
@computed("trend")
|
||||
trendIcon(trend) {
|
||||
switch (trend) {
|
||||
case "trending-up":
|
||||
return "angle-up";
|
||||
case "trending-down":
|
||||
return "angle-down";
|
||||
case "high-trending-up":
|
||||
return "angle-double-up";
|
||||
case "high-trending-down":
|
||||
return "angle-double-down";
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
@computed("trend", "higher_is_better")
|
||||
trendIcon(trend, higherIsBetter) {
|
||||
return this._iconForTrend(trend, higherIsBetter);
|
||||
},
|
||||
|
||||
@computed('prev_period', 'currentTotal', 'currentAverage')
|
||||
trend(prev, currentTotal, currentAverage) {
|
||||
const total = this.get('average') ? currentAverage : currentTotal;
|
||||
const change = ((total - prev) / total) * 100;
|
||||
|
||||
if (change > 50) {
|
||||
return "high-trending-up";
|
||||
} else if (change > 0) {
|
||||
return "trending-up";
|
||||
} else if (change === 0) {
|
||||
return "no-change";
|
||||
} else if (change < -50) {
|
||||
return "high-trending-down";
|
||||
} else if (change < 0) {
|
||||
return "trending-down";
|
||||
}
|
||||
@computed("sevenDaysTrend", "higher_is_better")
|
||||
sevenDaysTrendIcon(sevenDaysTrend, higherIsBetter) {
|
||||
return this._iconForTrend(sevenDaysTrend, higherIsBetter);
|
||||
},
|
||||
|
||||
@computed('prev30Days', 'lastThirtyDaysCount')
|
||||
thirtyDayTrend(prev30Days, lastThirtyDaysCount) {
|
||||
const currentPeriod = lastThirtyDaysCount;
|
||||
const change = ((currentPeriod - prev30Days) / currentPeriod) * 100;
|
||||
|
||||
if (change > 50) {
|
||||
return "high-trending-up";
|
||||
} else if (change > 0) {
|
||||
return "trending-up";
|
||||
} else if (change === 0) {
|
||||
return "no-change";
|
||||
} else if (change < -50) {
|
||||
return "high-trending-down";
|
||||
} else if (change < 0) {
|
||||
return "trending-down";
|
||||
}
|
||||
@computed("thirtyDaysTrend", "higher_is_better")
|
||||
thirtyDaysTrendIcon(thirtyDaysTrend, higherIsBetter) {
|
||||
return this._iconForTrend(thirtyDaysTrend, higherIsBetter);
|
||||
},
|
||||
|
||||
@computed('type')
|
||||
@computed("yesterdayTrend", "higher_is_better")
|
||||
yesterdayTrendIcon(yesterdayTrend, higherIsBetter) {
|
||||
return this._iconForTrend(yesterdayTrend, higherIsBetter);
|
||||
},
|
||||
|
||||
@computed("prev_period", "currentTotal", "currentAverage", "higher_is_better")
|
||||
trend(prev, currentTotal, currentAverage, higherIsBetter) {
|
||||
const total = this.get("average") ? currentAverage : currentTotal;
|
||||
return this._computeTrend(prev, total, higherIsBetter);
|
||||
},
|
||||
|
||||
@computed("prev30Days", "lastThirtyDaysCount", "higher_is_better")
|
||||
thirtyDaysTrend(prev30Days, lastThirtyDaysCount, higherIsBetter) {
|
||||
return this._computeTrend(prev30Days, lastThirtyDaysCount, higherIsBetter);
|
||||
},
|
||||
|
||||
@computed("type")
|
||||
icon(type) {
|
||||
if (type.indexOf("message") > -1) {
|
||||
return "envelope";
|
||||
}
|
||||
switch (type) {
|
||||
case "page_view_total_reqs": return "file";
|
||||
case "visits": return "user";
|
||||
case "time_to_first_response": return "reply";
|
||||
case "flags": return "flag";
|
||||
case "likes": return "heart";
|
||||
case "bookmarks": return "bookmark";
|
||||
@ -170,7 +127,7 @@ const Report = Discourse.Model.extend({
|
||||
}
|
||||
},
|
||||
|
||||
@computed('type')
|
||||
@computed("type")
|
||||
method(type) {
|
||||
if (type === "time_to_first_response") {
|
||||
return "average";
|
||||
@ -180,75 +137,112 @@ const Report = Discourse.Model.extend({
|
||||
},
|
||||
|
||||
percentChangeString(val1, val2) {
|
||||
const val = ((val1 - val2) / val2) * 100;
|
||||
if (isNaN(val) || !isFinite(val)) {
|
||||
const change = this._computeChange(val1, val2);
|
||||
|
||||
if (isNaN(change) || !isFinite(change)) {
|
||||
return null;
|
||||
} else if (val > 0) {
|
||||
return "+" + val.toFixed(0) + "%";
|
||||
} else if (change > 0) {
|
||||
return "+" + change.toFixed(0) + "%";
|
||||
} else {
|
||||
return val.toFixed(0) + "%";
|
||||
return change.toFixed(0) + "%";
|
||||
}
|
||||
},
|
||||
|
||||
@computed('prev_period', 'currentTotal', 'currentAverage')
|
||||
@computed("prev_period", "currentTotal", "currentAverage")
|
||||
trendTitle(prev, currentTotal, currentAverage) {
|
||||
let current = this.get('average') ? currentAverage : currentTotal;
|
||||
let percent = this.percentChangeString(current, prev);
|
||||
let current = this.get("average") ? currentAverage : currentTotal;
|
||||
let percent = this.percentChangeString(prev, current);
|
||||
|
||||
if (this.get('average')) {
|
||||
if (this.get("average")) {
|
||||
prev = prev ? prev.toFixed(1) : "0";
|
||||
if (this.get('percent')) {
|
||||
current += '%';
|
||||
prev += '%';
|
||||
if (this.get("percent")) {
|
||||
current += "%";
|
||||
prev += "%";
|
||||
}
|
||||
} else {
|
||||
prev = number(prev);
|
||||
current = number(current);
|
||||
}
|
||||
|
||||
return I18n.t('admin.dashboard.reports.trend_title', {percent: percent, prev: prev, current: current});
|
||||
return I18n.t("admin.dashboard.reports.trend_title", {percent, prev, current});
|
||||
},
|
||||
|
||||
changeTitle(val1, val2, prevPeriodString) {
|
||||
const percentChange = this.percentChangeString(val1, val2);
|
||||
var title = "";
|
||||
if (percentChange) { title += percentChange + " change. "; }
|
||||
title += "Was " + val2 + " " + prevPeriodString + ".";
|
||||
changeTitle(valAtT1, valAtT2, prevPeriodString) {
|
||||
const change = this.percentChangeString(valAtT1, valAtT2);
|
||||
let title = "";
|
||||
if (change) { title += `${change} change. `; }
|
||||
title += `Was ${number(valAtT1)} ${prevPeriodString}.`;
|
||||
return title;
|
||||
},
|
||||
|
||||
@computed('yesterdayCount')
|
||||
@computed("yesterdayCount")
|
||||
yesterdayCountTitle(yesterdayCount) {
|
||||
return this.changeTitle(yesterdayCount, this.valueAt(2), "two days ago");
|
||||
return this.changeTitle(this.valueAt(2), yesterdayCount, "two days ago");
|
||||
},
|
||||
|
||||
@computed('lastSevenDaysCount')
|
||||
sevenDayCountTitle(lastSevenDaysCount) {
|
||||
return this.changeTitle(lastSevenDaysCount, this.valueFor(8, 14), "two weeks ago");
|
||||
@computed("lastSevenDaysCount")
|
||||
sevenDaysCountTitle(lastSevenDaysCount) {
|
||||
return this.changeTitle(this.valueFor(8, 14), lastSevenDaysCount, "two weeks ago");
|
||||
},
|
||||
|
||||
@computed('prev30Days', 'lastThirtyDaysCount')
|
||||
thirtyDayCountTitle(prev30Days, lastThirtyDaysCount) {
|
||||
return this.changeTitle(lastThirtyDaysCount, prev30Days, "in the previous 30 day period");
|
||||
@computed("prev30Days", "lastThirtyDaysCount")
|
||||
thirtyDaysCountTitle(prev30Days, lastThirtyDaysCount) {
|
||||
return this.changeTitle(prev30Days, lastThirtyDaysCount, "in the previous 30 day period");
|
||||
},
|
||||
|
||||
@computed('data')
|
||||
@computed("data")
|
||||
sortedData(data) {
|
||||
return this.get('xAxisIsDate') ? data.toArray().reverse() : data.toArray();
|
||||
return this.get("xAxisIsDate") ? data.toArray().reverse() : data.toArray();
|
||||
},
|
||||
|
||||
@computed('data')
|
||||
@computed("data")
|
||||
xAxisIsDate() {
|
||||
if (!this.data[0]) return false;
|
||||
return this.data && this.data[0].x.match(/\d{4}-\d{1,2}-\d{1,2}/);
|
||||
}
|
||||
},
|
||||
|
||||
_computeChange(valAtT1, valAtT2) {
|
||||
return ((valAtT2 - valAtT1) / valAtT1) * 100;
|
||||
},
|
||||
|
||||
_computeTrend(valAtT1, valAtT2, higherIsBetter) {
|
||||
const change = this._computeChange(valAtT1, valAtT2);
|
||||
|
||||
if (change > 50) {
|
||||
return higherIsBetter ? "high-trending-up" : "high-trending-down";
|
||||
} else if (change > 2) {
|
||||
return higherIsBetter ? "trending-up" : "trending-down";
|
||||
} else if (change <= 2 && change >= -2) {
|
||||
return "no-change";
|
||||
} else if (change < -50) {
|
||||
return higherIsBetter ? "high-trending-down" : "high-trending-up";
|
||||
} else if (change < -2) {
|
||||
return higherIsBetter ? "trending-down" : "trending-up";
|
||||
}
|
||||
},
|
||||
|
||||
_iconForTrend(trend, higherIsBetter) {
|
||||
switch (trend) {
|
||||
case "trending-up":
|
||||
return higherIsBetter ? "angle-up" : "angle-down";
|
||||
case "trending-down":
|
||||
return higherIsBetter ? "angle-down" : "angle-up";
|
||||
case "high-trending-up":
|
||||
return higherIsBetter ? "angle-double-up" : "angle-double-down";
|
||||
case "high-trending-down":
|
||||
return higherIsBetter ? "angle-double-down" : "angle-double-up";
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Report.reopenClass({
|
||||
|
||||
fillMissingDates(report) {
|
||||
if (_.isArray(report.data)) {
|
||||
|
||||
const startDateFormatted = moment.utc(report.start_date).locale('en').format('YYYY-MM-DD');
|
||||
const endDateFormatted = moment.utc(report.end_date).locale('en').format('YYYY-MM-DD');
|
||||
const startDateFormatted = moment.utc(report.start_date).locale("en").format("YYYY-MM-DD");
|
||||
const endDateFormatted = moment.utc(report.end_date).locale("en").format("YYYY-MM-DD");
|
||||
report.data = fillMissingDates(report.data, startDateFormatted, endDateFormatted);
|
||||
}
|
||||
},
|
||||
@ -272,7 +266,7 @@ Report.reopenClass({
|
||||
// TODO: fillMissingDates if xaxis is date
|
||||
const related = Report.create({ type: json.report.related_report.type });
|
||||
related.setProperties(json.report.related_report);
|
||||
model.set('relatedReport', related);
|
||||
model.set("relatedReport", related);
|
||||
}
|
||||
|
||||
return model;
|
||||
|
||||
@ -1,54 +1,58 @@
|
||||
import { ajax } from 'discourse/lib/ajax';
|
||||
import AdminUser from 'admin/models/admin-user';
|
||||
import { escapeExpression } from 'discourse/lib/utilities';
|
||||
import computed from "ember-addons/ember-computed-decorators";
|
||||
import { ajax } from "discourse/lib/ajax";
|
||||
import AdminUser from "admin/models/admin-user";
|
||||
import { escapeExpression } from "discourse/lib/utilities";
|
||||
|
||||
function format(label, value, escape = true) {
|
||||
return value ? `<b>${I18n.t(label)}</b>: ${escape ? escapeExpression(value) : value}` : "";
|
||||
};
|
||||
|
||||
const StaffActionLog = Discourse.Model.extend({
|
||||
showFullDetails: false,
|
||||
|
||||
actionName: function() {
|
||||
return I18n.t("admin.logs.staff_actions.actions." + this.get('action_name'));
|
||||
}.property('action_name'),
|
||||
|
||||
formattedDetails: function() {
|
||||
let formatted = "";
|
||||
formatted += this.format('email', 'email');
|
||||
formatted += this.format('admin.logs.ip_address', 'ip_address');
|
||||
formatted += this.format('admin.logs.topic_id', 'topic_id');
|
||||
formatted += this.format('admin.logs.post_id', 'post_id');
|
||||
formatted += this.format('admin.logs.category_id', 'category_id');
|
||||
if (!this.get('useCustomModalForDetails')) {
|
||||
formatted += this.format('admin.logs.staff_actions.new_value', 'new_value');
|
||||
formatted += this.format('admin.logs.staff_actions.previous_value', 'previous_value');
|
||||
}
|
||||
if (!this.get('useModalForDetails')) {
|
||||
if (this.get('details')) formatted += escapeExpression(this.get('details')) + '<br/>';
|
||||
}
|
||||
return formatted;
|
||||
}.property('ip_address', 'email', 'topic_id', 'post_id', 'category_id'),
|
||||
|
||||
format(label, propertyName) {
|
||||
if (this.get(propertyName)) {
|
||||
let value = escapeExpression(this.get(propertyName));
|
||||
if (propertyName === 'post_id') {
|
||||
value = `<a href data-link-post-id="${value}">${value}</a>`;
|
||||
}
|
||||
return `<b>${I18n.t(label)}:</b> ${value}<br/>`;
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
@computed("action_name")
|
||||
actionName(actionName) {
|
||||
return I18n.t(`admin.logs.staff_actions.actions.${actionName}`);
|
||||
},
|
||||
|
||||
useModalForDetails: function() {
|
||||
return (this.get('details') && this.get('details').length > 100);
|
||||
}.property('action_name'),
|
||||
@computed("email", "ip_address", "topic_id", "post_id", "category_id", "new_value", "previous_value", "details", "useCustomModalForDetails", "useModalForDetails")
|
||||
formattedDetails(email, ipAddress, topicId, postId, categoryId, newValue, previousValue, details, useCustomModalForDetails, useModalForDetails) {
|
||||
const postLink = postId ? `<a href data-link-post-id="${postId}">${postId}</a>` : null;
|
||||
|
||||
useCustomModalForDetails: function() {
|
||||
return _.contains(['change_theme', 'delete_theme'], this.get('action_name'));
|
||||
}.property('action_name')
|
||||
let lines = [
|
||||
format("email", email),
|
||||
format("admin.logs.ip_address", ipAddress),
|
||||
format("admin.logs.topic_id", topicId),
|
||||
format("admin.logs.post_id", postLink, false),
|
||||
format("admin.logs.category_id", categoryId),
|
||||
];
|
||||
|
||||
if (!useCustomModalForDetails) {
|
||||
lines.push(format("admin.logs.staff_actions.new_value", newValue));
|
||||
lines.push(format("admin.logs.staff_actions.previous_value", previousValue));
|
||||
}
|
||||
|
||||
if (!useModalForDetails && details) {
|
||||
lines = [...lines, ...escapeExpression(details).split("\n")];
|
||||
}
|
||||
|
||||
const formatted = lines.filter(l => l.length > 0).join("<br/>");
|
||||
return formatted.length > 0 ? formatted + "<br/>" : "";
|
||||
},
|
||||
|
||||
@computed("details")
|
||||
useModalForDetails(details) {
|
||||
return details && details.length > 100;
|
||||
},
|
||||
|
||||
@computed("action_name")
|
||||
useCustomModalForDetails(actionName) {
|
||||
return ["change_theme", "delete_theme"].includes(actionName);
|
||||
}
|
||||
});
|
||||
|
||||
StaffActionLog.reopenClass({
|
||||
create: function(attrs) {
|
||||
create(attrs) {
|
||||
attrs = attrs || {};
|
||||
|
||||
if (attrs.acting_user) {
|
||||
@ -60,13 +64,11 @@ StaffActionLog.reopenClass({
|
||||
return this._super(attrs);
|
||||
},
|
||||
|
||||
findAll: function(filters) {
|
||||
return ajax("/admin/logs/staff_action_logs.json", { data: filters }).then((data) => {
|
||||
findAll(data) {
|
||||
return ajax("/admin/logs/staff_action_logs.json", { data }).then(result => {
|
||||
return {
|
||||
staff_action_logs: data.staff_action_logs.map(function(s) {
|
||||
return StaffActionLog.create(s);
|
||||
}),
|
||||
user_history_actions: data.user_history_actions
|
||||
staff_action_logs: result.staff_action_logs.map(s => StaffActionLog.create(s)),
|
||||
user_history_actions: result.user_history_actions
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
@ -18,7 +18,7 @@
|
||||
<div>
|
||||
<label for="image">{{i18n 'admin.badges.image'}}</label>
|
||||
{{input type="text" name="image" value=buffered.image}}
|
||||
<p class='help'>{{i18n 'admin.badges.icon_help'}}</p>
|
||||
<p class='help'>{{i18n 'admin.badges.image_help'}}</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
|
||||
@ -5,18 +5,18 @@
|
||||
<a href="{{report.reportUrl}}">{{report.title}}</a>
|
||||
</td>
|
||||
|
||||
<td class="value">{{number report.todayCount}}</td>
|
||||
<td class="value">{{number report.todayCount ceil=true}}</td>
|
||||
|
||||
<td class="value {{report.yesterdayTrend}}" title={{report.yesterdayCountTitle}}>
|
||||
{{number report.yesterdayCount}} {{d-icon "caret-up" class="up"}} {{d-icon "caret-down" class="down"}}
|
||||
{{number report.yesterdayCount ceil=true}} {{d-icon report.yesterdayTrendIcon}}
|
||||
</td>
|
||||
|
||||
<td class="value {{report.sevenDayTrend}}" title={{report.sevenDayCountTitle}}>
|
||||
{{number report.lastSevenDaysCount}} {{d-icon "caret-up" class="up"}} {{d-icon "caret-down" class="down"}}
|
||||
<td class="value {{report.sevenDaysTrend}}" title={{report.sevenDaysCountTitle}}>
|
||||
{{number report.lastSevenDaysCount ceil=true}} {{d-icon report.sevenDaysTrendIcon}}
|
||||
</td>
|
||||
|
||||
<td class="value {{report.thirtyDayTrend}}" title={{report.thirtyDayCountTitle}}>
|
||||
{{number report.lastThirtyDaysCount}} {{d-icon "caret-up" class="up"}} {{d-icon "caret-down" class="down"}}
|
||||
<td class="value {{report.thirtyDaysTrend}}" title={{report.thirtyDaysCountTitle}}>
|
||||
{{number report.lastThirtyDaysCount ceil=true}} {{d-icon report.thirtyDaysTrendIcon}}
|
||||
</td>
|
||||
|
||||
{{#if allTime}}
|
||||
|
||||
@ -1,40 +1,27 @@
|
||||
{{#conditional-loading-section isLoading=isLoading}}
|
||||
<div class="table-title">
|
||||
<h3>{{title}}</h3>
|
||||
|
||||
{{#if help}}
|
||||
<a href="{{helpPage}}">{{i18n help}}</a>
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
{{#each reportsForPeriod as |report|}}
|
||||
<div class="table-container">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
{{#if report.labels}}
|
||||
{{#each report.labels as |label|}}
|
||||
<th>{{label}}</th>
|
||||
{{/each}}
|
||||
{{else}}
|
||||
{{#each report.data as |data|}}
|
||||
<th>{{data.x}}</th>
|
||||
{{/each}}
|
||||
{{/if}}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{#unless hasBlock}}
|
||||
{{#each report.data as |data|}}
|
||||
<tr>
|
||||
<td>{{number data.y}}</td>
|
||||
</tr>
|
||||
{{/each}}
|
||||
{{else}}
|
||||
{{yield (hash report=report)}}
|
||||
{{/unless}}
|
||||
</tbody>
|
||||
</table>
|
||||
{{#unless hasBlock}}
|
||||
{{#each report.data as |data|}}
|
||||
<a class="table-cell user-{{data.key}}" href="{{data.url}}">
|
||||
<span class="label">
|
||||
{{#if data.icon}}
|
||||
{{d-icon data.icon}}
|
||||
{{/if}}
|
||||
{{data.x}}
|
||||
</span>
|
||||
<span class="value">
|
||||
{{number data.y}}
|
||||
</span>
|
||||
</a>
|
||||
{{/each}}
|
||||
{{else}}
|
||||
{{yield (hash report=report)}}
|
||||
{{/unless}}
|
||||
</div>
|
||||
{{/each}}
|
||||
{{/conditional-loading-section}}
|
||||
|
||||
@ -14,9 +14,9 @@
|
||||
<div class="trend {{report.trend}}">
|
||||
<span class="trend-value" title="{{report.trendTitle}}">
|
||||
{{#if report.average}}
|
||||
{{number report.currentAverage}}{{#if report.percent}}%{{/if}}
|
||||
{{number report.currentAverage ceil=true}}{{#if report.percent}}%{{/if}}
|
||||
{{else}}
|
||||
{{number report.currentTotal noTitle="true"}}{{#if report.percent}}%{{/if}}
|
||||
{{number report.currentTotal ceil=true noTitle="true"}}{{#if report.percent}}%{{/if}}
|
||||
{{/if}}
|
||||
</span>
|
||||
|
||||
|
||||
@ -0,0 +1,36 @@
|
||||
{{#conditional-loading-section isLoading=isLoading}}
|
||||
<div class="table-title">
|
||||
<h3>{{title}}</h3>
|
||||
</div>
|
||||
|
||||
{{#each reportsForPeriod as |report|}}
|
||||
<div class="table-container">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
{{#if report.labels}}
|
||||
{{#each report.labels as |label|}}
|
||||
<th>{{label}}</th>
|
||||
{{/each}}
|
||||
{{else}}
|
||||
{{#each report.data as |data|}}
|
||||
<th>{{data.x}}</th>
|
||||
{{/each}}
|
||||
{{/if}}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{#unless hasBlock}}
|
||||
{{#each report.data as |data|}}
|
||||
<tr>
|
||||
<td>{{number data.y}}</td>
|
||||
</tr>
|
||||
{{/each}}
|
||||
{{else}}
|
||||
{{yield (hash report=report)}}
|
||||
{{/unless}}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{{/each}}
|
||||
{{/conditional-loading-section}}
|
||||
@ -2,6 +2,6 @@
|
||||
{{text-field value=word disabled=formSubmitted class="watched-word-input" autocorrect="off" autocapitalize="off" placeholderKey=placeholderKey}}
|
||||
{{d-button action="submit" disabled=formSubmitted label="admin.watched_words.form.add"}}
|
||||
|
||||
{{#if showSuccessMessage}}
|
||||
<span class="success-message">{{i18n 'admin.watched_words.form.success'}}</span>
|
||||
{{#if showMessage}}
|
||||
<span class="success-message">{{message}}</span>
|
||||
{{/if}}
|
||||
|
||||
@ -5,6 +5,7 @@
|
||||
{{#each sortedThemes as |theme|}}
|
||||
<li>
|
||||
{{#link-to 'adminCustomizeThemes.show' theme replace=true}}
|
||||
{{plugin-outlet name="admin-customize-themes-list-item" connectorTagName='span' args=(hash theme=theme)}}
|
||||
{{theme.name}}
|
||||
{{#if theme.user_selectable}}
|
||||
{{d-icon "user"}}
|
||||
|
||||
@ -53,7 +53,7 @@
|
||||
|
||||
<div class="section-columns">
|
||||
<div class="section-column">
|
||||
<div class="dashboard-table">
|
||||
<div class="dashboard-table activity-metrics">
|
||||
{{#conditional-loading-section isLoading=isLoading title=(i18n "admin.dashboard.activity_metrics")}}
|
||||
<div class="table-title">
|
||||
<h3>{{i18n "admin.dashboard.activity_metrics"}}</h3>
|
||||
@ -79,19 +79,11 @@
|
||||
</div>
|
||||
{{/conditional-loading-section}}
|
||||
</div>
|
||||
<div class="user-metrics">
|
||||
{{dashboard-inline-table dataSourceNames="users_by_type" lastRefreshedAt=lastRefreshedAt}}
|
||||
|
||||
{{#dashboard-inline-table dataSourceNames="users_by_type,users_by_trust_level" lastRefreshedAt=lastRefreshedAt as |context|}}
|
||||
<tr>
|
||||
{{#each context.report.data as |data|}}
|
||||
<td>
|
||||
<a href="/admin/users/list/{{data.key}}">
|
||||
{{number data.y}}
|
||||
</a>
|
||||
</td>
|
||||
{{/each}}
|
||||
</tr>
|
||||
{{/dashboard-inline-table}}
|
||||
|
||||
{{dashboard-inline-table dataSourceNames="users_by_trust_level" lastRefreshedAt=lastRefreshedAt}}
|
||||
</div>
|
||||
{{#conditional-loading-section isLoading=isLoading title=(i18n "admin.dashboard.backups")}}
|
||||
<div class="misc">
|
||||
<div class="durability">
|
||||
@ -116,7 +108,6 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="last-dashboard-update">
|
||||
<div>
|
||||
<h4>{{i18n "admin.dashboard.last_updated"}} </h4>
|
||||
@ -128,58 +119,57 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p>
|
||||
{{i18n 'admin.dashboard.find_old'}} {{#link-to 'admin.dashboard'}}{{i18n "admin.dashboard.old_link"}}{{/link-to}}
|
||||
</p>
|
||||
|
||||
<p>
|
||||
{{i18n 'admin.dashboard.find_old'}} {{#link-to 'admin.dashboard'}}{{i18n "admin.dashboard.old_link"}}{{/link-to}}
|
||||
</p>
|
||||
{{/conditional-loading-section}}
|
||||
</div>
|
||||
|
||||
<div class="section-column">
|
||||
<div class="top-referred-topics">
|
||||
{{#dashboard-inline-table
|
||||
dataSourceNames="top_referred_topics"
|
||||
lastRefreshedAt=lastRefreshedAt
|
||||
limit=8
|
||||
as |context|}}
|
||||
{{#each context.report.data as |data|}}
|
||||
<tr>
|
||||
<td class='left'>
|
||||
<a href="{{data.topic_url}}">
|
||||
{{data.topic_title}}
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
{{data.num_clicks}}
|
||||
</td>
|
||||
</tr>
|
||||
{{/each}}
|
||||
{{/dashboard-inline-table}}
|
||||
{{#dashboard-table
|
||||
dataSourceNames="top_referred_topics"
|
||||
lastRefreshedAt=lastRefreshedAt
|
||||
limit=8
|
||||
as |context|}}
|
||||
{{#each context.report.data as |data|}}
|
||||
<tr>
|
||||
<td class='left'>
|
||||
<a href="{{data.topic_url}}">
|
||||
{{data.topic_title}}
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
{{data.num_clicks}}
|
||||
</td>
|
||||
</tr>
|
||||
{{/each}}
|
||||
{{/dashboard-table}}
|
||||
</div>
|
||||
|
||||
<div class="trending-search">
|
||||
{{#dashboard-inline-table
|
||||
limit=8
|
||||
dataSourceNames="trending_search"
|
||||
isEnabled=logSearchQueriesEnabled
|
||||
disabledLabel="admin.dashboard.reports.trending_search.disabled"
|
||||
startDate=lastWeek
|
||||
endDate=endDate as |context|}}
|
||||
{{#each context.report.data as |data|}}
|
||||
<tr>
|
||||
<td class='left'>
|
||||
{{data.term}}
|
||||
</td>
|
||||
<td>
|
||||
{{number data.unique_searches}}
|
||||
</td>
|
||||
<td>
|
||||
{{data.ctr}}
|
||||
</td>
|
||||
</tr>
|
||||
{{/each}}
|
||||
{{/dashboard-inline-table}}
|
||||
{{#dashboard-table
|
||||
limit=8
|
||||
dataSourceNames="trending_search"
|
||||
isEnabled=logSearchQueriesEnabled
|
||||
disabledLabel="admin.dashboard.reports.trending_search.disabled"
|
||||
startDate=lastWeek
|
||||
endDate=endDate as |context|}}
|
||||
{{#each context.report.data as |data|}}
|
||||
<tr>
|
||||
<td class='left'>
|
||||
{{data.term}}
|
||||
</td>
|
||||
<td>
|
||||
{{number data.unique_searches}}
|
||||
</td>
|
||||
<td>
|
||||
{{data.ctr}}
|
||||
</td>
|
||||
</tr>
|
||||
{{/each}}
|
||||
{{/dashboard-table}}
|
||||
{{{i18n "admin.dashboard.reports.trending_search.more"}}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -4,41 +4,49 @@
|
||||
<p>{{model.description}}</p>
|
||||
{{/if}}
|
||||
|
||||
<div class="admin-reports-filter">
|
||||
{{i18n 'admin.dashboard.reports.start_date'}} {{date-picker-past value=startDate defaultDate=startDate}}
|
||||
{{i18n 'admin.dashboard.reports.end_date'}} {{date-picker-past value=endDate defaultDate=endDate}}
|
||||
{{#if showCategoryOptions}}
|
||||
{{combo-box filterable=true valueAttribute="value" content=categoryOptions value=categoryId}}
|
||||
{{/if}}
|
||||
{{#if showGroupOptions}}
|
||||
{{combo-box filterable=true valueAttribute="value" content=groupOptions value=groupId}}
|
||||
{{/if}}
|
||||
{{d-button action="refreshReport" class="btn-primary" label="admin.dashboard.reports.refresh_report" icon="refresh"}}
|
||||
{{d-button action="exportCsv" label="admin.export_csv.button_text" icon="download"}}
|
||||
<div class="report-container">
|
||||
<div class="visualization">
|
||||
{{#conditional-loading-spinner condition=refreshing}}
|
||||
<div class='view-options'>
|
||||
{{#if viewingTable}}
|
||||
{{i18n 'admin.dashboard.reports.view_table'}}
|
||||
{{else}}
|
||||
<a href {{action "viewAsTable"}}>{{i18n 'admin.dashboard.reports.view_table'}}</a>
|
||||
{{/if}}
|
||||
|
|
||||
{{#if viewingGraph}}
|
||||
{{i18n 'admin.dashboard.reports.view_graph'}}
|
||||
{{else}}
|
||||
<a href {{action "viewAsGraph"}}>{{i18n 'admin.dashboard.reports.view_graph'}}</a>
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
{{#if viewingGraph}}
|
||||
{{admin-graph model=model}}
|
||||
{{else}}
|
||||
{{admin-table-report model=model}}
|
||||
{{/if}}
|
||||
|
||||
{{#if model.relatedReport}}
|
||||
{{admin-table-report model=model.relatedReport}}
|
||||
{{/if}}
|
||||
{{/conditional-loading-spinner}}
|
||||
</div>
|
||||
|
||||
<div class="filters">
|
||||
<span>
|
||||
{{i18n 'admin.dashboard.reports.start_date'}} {{date-picker-past value=startDate defaultDate=startDate}}
|
||||
</span>
|
||||
<span>
|
||||
{{i18n 'admin.dashboard.reports.end_date'}} {{date-picker-past value=endDate defaultDate=endDate}}
|
||||
</span>
|
||||
{{#if showCategoryOptions}}
|
||||
{{combo-box filterable=true valueAttribute="value" content=categoryOptions value=categoryId}}
|
||||
{{/if}}
|
||||
{{#if showGroupOptions}}
|
||||
{{combo-box filterable=true valueAttribute="value" content=groupOptions value=groupId}}
|
||||
{{/if}}
|
||||
{{d-button action="refreshReport" class="btn-primary" label="admin.dashboard.reports.refresh_report" icon="refresh"}}
|
||||
{{d-button action="exportCsv" label="admin.export_csv.button_text" icon="download"}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class='view-options'>
|
||||
{{#if viewingTable}}
|
||||
{{i18n 'admin.dashboard.reports.view_table'}}
|
||||
{{else}}
|
||||
<a href {{action "viewAsTable"}}>{{i18n 'admin.dashboard.reports.view_table'}}</a>
|
||||
{{/if}}
|
||||
|
|
||||
{{#if viewingGraph}}
|
||||
{{i18n 'admin.dashboard.reports.view_graph'}}
|
||||
{{else}}
|
||||
<a href {{action "viewAsGraph"}}>{{i18n 'admin.dashboard.reports.view_graph'}}</a>
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
{{#conditional-loading-spinner condition=refreshing}}
|
||||
{{#if viewingGraph}}
|
||||
{{admin-graph model=model}}
|
||||
{{else}}
|
||||
{{admin-table-report model=model}}
|
||||
{{/if}}
|
||||
|
||||
{{#if model.relatedReport}}
|
||||
{{admin-table-report model=model.relatedReport}}
|
||||
{{/if}}
|
||||
{{/conditional-loading-spinner}}
|
||||
|
||||
@ -318,18 +318,18 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="user-suspended display-row {{if model.isSuspended 'highlight-danger'}}">
|
||||
<div class='field'>{{i18n 'admin.user.suspended'}}</div>
|
||||
<div class='value'>
|
||||
{{i18n-yes-no model.isSuspended}}
|
||||
{{#if model.isSuspended}}
|
||||
{{#unless model.suspendedForever}}
|
||||
{{i18n "admin.user.suspended_until" until=model.suspendedTillDate}}
|
||||
{{/unless}}
|
||||
{{/if}}
|
||||
</div>
|
||||
<div class='controls'>
|
||||
{{#if model.isSuspended}}
|
||||
<div class="user-suspended display-row {{if model.suspended 'highlight-danger'}}">
|
||||
<div class='field'>{{i18n 'admin.user.suspended'}}</div>
|
||||
<div class='value'>
|
||||
{{i18n-yes-no model.suspended}}
|
||||
{{#if model.suspended}}
|
||||
{{#unless model.suspendedForever}}
|
||||
{{i18n "admin.user.suspended_until" until=model.suspendedTillDate}}
|
||||
{{/unless}}
|
||||
{{/if}}
|
||||
</div>
|
||||
<div class='controls'>
|
||||
{{#if model.suspended}}
|
||||
{{d-button
|
||||
class="btn-danger unsuspend-user"
|
||||
action=(action "unsuspend")
|
||||
@ -349,7 +349,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{#if model.isSuspended}}
|
||||
{{#if model.suspended}}
|
||||
<div class='display-row highlight-danger suspension-info'>
|
||||
<div class='field'>{{i18n 'admin.user.suspended_by'}}</div>
|
||||
<div class='value'>
|
||||
@ -367,7 +367,7 @@
|
||||
<div class='field'>{{i18n 'admin.user.silenced'}}</div>
|
||||
<div class='value'>
|
||||
{{i18n-yes-no model.silenced}}
|
||||
{{#if model.isSilenced}}
|
||||
{{#if model.silenced}}
|
||||
{{#unless model.silencedForever}}
|
||||
{{i18n "admin.user.suspended_until" until=model.silencedTillDate}}
|
||||
{{/unless}}
|
||||
@ -394,7 +394,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{#if model.isSilenced}}
|
||||
{{#if model.silenced}}
|
||||
<div class='display-row highlight-danger silence-info'>
|
||||
<div class='field'>{{i18n 'admin.user.silenced_by'}}</div>
|
||||
<div class='value'>
|
||||
@ -408,6 +408,21 @@
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
{{#if model.tl3_requirements.penalty_counts.total}}
|
||||
<div class='display-row clear-penalty-history'>
|
||||
<div class='field'>{{i18n 'admin.user.penalty_count'}}</div>
|
||||
<div class='value'>{{model.tl3_requirements.penalty_counts.total}}</div>
|
||||
{{#if currentUser.admin}}
|
||||
<div class='controls'>
|
||||
{{d-button label="admin.user.clear_penalty_history.title"
|
||||
icon="times"
|
||||
action=(action "clearPenaltyHistory")}}
|
||||
{{i18n "admin.user.clear_penalty_history.description"}}
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
</section>
|
||||
|
||||
{{#if currentUser.admin}}
|
||||
@ -468,7 +483,7 @@
|
||||
{{d-button class="btn-danger" action="deleteAllPosts" icon="trash-o" label="admin.user.delete_all_posts"}}
|
||||
{{/if}}
|
||||
{{else}}
|
||||
{{model.deleteAllPostsExplanation}}
|
||||
{{deleteAllPostsExplanation}}
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
@ -549,28 +564,26 @@
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
|
||||
{{#unless model.anonymizeForbidden}}
|
||||
{{#if model.can_be_anonymized}}
|
||||
{{d-button label="admin.user.anonymize"
|
||||
icon="exclamation-triangle"
|
||||
class="btn-danger"
|
||||
disabled=model.anonymizeForbidden
|
||||
action="anonymize"}}
|
||||
{{/unless}}
|
||||
{{/if}}
|
||||
|
||||
{{#unless model.deleteForbidden}}
|
||||
{{#if model.canBeDeleted}}
|
||||
{{d-button label="admin.user.delete"
|
||||
icon="exclamation-triangle"
|
||||
class="btn-danger"
|
||||
disabled=model.deleteForbidden
|
||||
action="destroy"}}
|
||||
{{/unless}}
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
{{#if model.deleteExplanation}}
|
||||
{{#if deleteExplanation}}
|
||||
<div class="clearfix"></div>
|
||||
<br>
|
||||
<div class="pull-right">
|
||||
{{d-icon "exclamation-triangle"}} {{model.deleteExplanation}}
|
||||
{{d-icon "exclamation-triangle"}} {{deleteExplanation}}
|
||||
</div>
|
||||
{{/if}}
|
||||
</section>
|
||||
|
||||
@ -5,6 +5,7 @@
|
||||
{{watched-word-form
|
||||
actionKey=actionNameKey
|
||||
action="recordAdded"
|
||||
filteredContent=filteredContent
|
||||
regularExpressions=adminWatchedWords.regularExpressions}}
|
||||
|
||||
{{watched-word-uploader uploading=uploading actionKey=actionNameKey done="uploadComplete"}}
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
<div class='admin-controls'>
|
||||
<div class='controls'>
|
||||
{{d-button action="toggleMenu" class="menu-toggle" icon="bars"}}
|
||||
{{text-field value=filter placeholderKey="admin.watched_words.search" class="no-blur"}}
|
||||
{{d-button action="clearFilter" label="admin.watched_words.clear_filter"}}
|
||||
</div>
|
||||
|
||||
@ -42,7 +42,9 @@ export default Ember.Component.extend({
|
||||
this.getProperties(
|
||||
'title',
|
||||
'rawTitle',
|
||||
'fixed'
|
||||
'fixed',
|
||||
'subtitle',
|
||||
'rawSubtitle'
|
||||
)
|
||||
);
|
||||
},
|
||||
|
||||
@ -28,6 +28,7 @@ export default Ember.Component.extend({
|
||||
|
||||
this.appEvents.on('modal:body-shown', data => {
|
||||
if (this.isDestroying || this.isDestroyed) { return; }
|
||||
|
||||
if (data.fixed) {
|
||||
this.$().removeClass('hidden');
|
||||
}
|
||||
@ -37,6 +38,16 @@ export default Ember.Component.extend({
|
||||
} else if (data.rawTitle) {
|
||||
this.set('title', data.rawTitle);
|
||||
}
|
||||
|
||||
if (data.subtitle) {
|
||||
this.set('subtitle', I18n.t(data.subtitle));
|
||||
} else if (data.rawSubtitle) {
|
||||
this.set('subtitle', data.rawSubtitle);
|
||||
} else {
|
||||
// if no subtitle provided, makes sure the previous subtitle
|
||||
// of another modal is not used
|
||||
this.set('subtitle', null);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
@ -0,0 +1,18 @@
|
||||
import { default as computed } from 'ember-addons/ember-computed-decorators';
|
||||
|
||||
export default Ember.Component.extend({
|
||||
tagName: 'span',
|
||||
|
||||
@computed("text")
|
||||
translatedText(text) {
|
||||
if (text) return I18n.t(text);
|
||||
},
|
||||
|
||||
click(event) {
|
||||
if (event.target.tagName.toUpperCase() === 'A') {
|
||||
this.sendAction("action", this.get("actionParam"));
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
});
|
||||
@ -46,5 +46,12 @@ export default Ember.Component.extend({
|
||||
}
|
||||
|
||||
this.set("topicTimer.updateTime", time);
|
||||
}
|
||||
},
|
||||
|
||||
@observes("selection")
|
||||
_updateBasedOnLastPost() {
|
||||
if (!this.get('autoClose')) {
|
||||
this.set('topicTimer.based_on_last_post', false);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@ -78,7 +78,10 @@ export default Ember.Component.extend({
|
||||
const $quoteButton = this.$();
|
||||
|
||||
// remove the marker
|
||||
markerElement.parentNode.removeChild(markerElement);
|
||||
const parent = markerElement.parentNode;
|
||||
parent.removeChild(markerElement);
|
||||
// merge back all text nodes so they don't get messed up
|
||||
parent.normalize();
|
||||
|
||||
// work around Safari that would sometimes lose the selection
|
||||
if (isSafari) {
|
||||
|
||||
@ -18,6 +18,7 @@ const REGEXP_TAGS_REPLACE = /(^(tags?:|#(?=[a-z0-9\-]+::tag))|::tag\s?$
|
||||
|
||||
const REGEXP_IN_MATCH = /^(in|with):(posted|watching|tracking|bookmarks|first|pinned|unpinned|wiki|unseen|image)/ig;
|
||||
const REGEXP_SPECIAL_IN_LIKES_MATCH = /^in:likes/ig;
|
||||
const REGEXP_SPECIAL_IN_TITLE_MATCH = /^in:title/ig;
|
||||
const REGEXP_SPECIAL_IN_PRIVATE_MATCH = /^in:private/ig;
|
||||
const REGEXP_SPECIAL_IN_SEEN_MATCH = /^in:seen/ig;
|
||||
|
||||
@ -81,6 +82,7 @@ export default Em.Component.extend({
|
||||
in: '',
|
||||
special: {
|
||||
in: {
|
||||
title: false,
|
||||
likes: false,
|
||||
private: false,
|
||||
seen: false
|
||||
@ -111,6 +113,7 @@ export default Em.Component.extend({
|
||||
this.setSearchedTermValueForTags();
|
||||
this.setSearchedTermValue('searchedTerms.in', REGEXP_IN_PREFIX, REGEXP_IN_MATCH);
|
||||
this.setSearchedTermSpecialInValue('searchedTerms.special.in.likes', REGEXP_SPECIAL_IN_LIKES_MATCH);
|
||||
this.setSearchedTermSpecialInValue('searchedTerms.special.in.title', REGEXP_SPECIAL_IN_TITLE_MATCH);
|
||||
this.setSearchedTermSpecialInValue('searchedTerms.special.in.private', REGEXP_SPECIAL_IN_PRIVATE_MATCH);
|
||||
this.setSearchedTermSpecialInValue('searchedTerms.special.in.seen', REGEXP_SPECIAL_IN_SEEN_MATCH);
|
||||
this.setSearchedTermValue('searchedTerms.status', REGEXP_STATUS_PREFIX);
|
||||
@ -424,15 +427,14 @@ export default Em.Component.extend({
|
||||
}
|
||||
},
|
||||
|
||||
@observes('searchedTerms.special.in.likes')
|
||||
updateSearchTermForSpecialInLikes() {
|
||||
const match = this.filterBlocks(REGEXP_SPECIAL_IN_LIKES_MATCH);
|
||||
const inFilter = this.get('searchedTerms.special.in.likes');
|
||||
updateInRegex(regex, filter) {
|
||||
const match = this.filterBlocks(regex);
|
||||
const inFilter = this.get('searchedTerms.special.in.' + filter);
|
||||
let searchTerm = this.get('searchTerm') || '';
|
||||
|
||||
if (inFilter) {
|
||||
if (match.length === 0) {
|
||||
searchTerm += ` in:likes`;
|
||||
searchTerm += ` in:${filter}`;
|
||||
this.set('searchTerm', searchTerm.trim());
|
||||
}
|
||||
} else if (match.length !== 0) {
|
||||
@ -441,38 +443,24 @@ export default Em.Component.extend({
|
||||
}
|
||||
},
|
||||
|
||||
@observes('searchedTerms.special.in.likes')
|
||||
updateSearchTermForSpecialInLikes() {
|
||||
this.updateInRegex(REGEXP_SPECIAL_IN_LIKES_MATCH, 'likes');
|
||||
},
|
||||
|
||||
@observes('searchedTerms.special.in.private')
|
||||
updateSearchTermForSpecialInPrivate() {
|
||||
const match = this.filterBlocks(REGEXP_SPECIAL_IN_PRIVATE_MATCH);
|
||||
const inFilter = this.get('searchedTerms.special.in.private');
|
||||
let searchTerm = this.get('searchTerm') || '';
|
||||
|
||||
if (inFilter) {
|
||||
if (match.length === 0) {
|
||||
searchTerm += ` in:private`;
|
||||
this.set('searchTerm', searchTerm.trim());
|
||||
}
|
||||
} else if (match.length !== 0) {
|
||||
searchTerm = searchTerm.replace(match, '');
|
||||
this.set('searchTerm', searchTerm.trim());
|
||||
}
|
||||
this.updateInRegex(REGEXP_SPECIAL_IN_PRIVATE_MATCH, 'private');
|
||||
},
|
||||
|
||||
@observes('searchedTerms.special.in.seen')
|
||||
updateSearchTermForSpecialInSeen() {
|
||||
const match = this.filterBlocks(REGEXP_SPECIAL_IN_SEEN_MATCH);
|
||||
const inFilter = this.get('searchedTerms.special.in.seen');
|
||||
let searchTerm = this.get('searchTerm') || '';
|
||||
this.updateInRegex(REGEXP_SPECIAL_IN_SEEN_MATCH, 'seen');
|
||||
},
|
||||
|
||||
if (inFilter) {
|
||||
if (match.length === 0) {
|
||||
searchTerm += ` in:seen`;
|
||||
this.set('searchTerm', searchTerm.trim());
|
||||
}
|
||||
} else if (match.length !== 0) {
|
||||
searchTerm = searchTerm.replace(match, '');
|
||||
this.set('searchTerm', searchTerm.trim());
|
||||
}
|
||||
@observes('searchedTerms.special.in.title')
|
||||
updateSearchTermForSpecialInTitle() {
|
||||
this.updateInRegex(REGEXP_SPECIAL_IN_TITLE_MATCH, 'title');
|
||||
},
|
||||
|
||||
@observes('searchedTerms.status')
|
||||
|
||||
@ -16,7 +16,7 @@ const SiteHeaderComponent = MountWidget.extend(Docking, {
|
||||
_topic: null,
|
||||
|
||||
@observes('currentUser.unread_notifications', 'currentUser.unread_private_messages')
|
||||
_notificationsChanged() {
|
||||
notificationsChanged() {
|
||||
this.queueRerender();
|
||||
},
|
||||
|
||||
|
||||
@ -18,7 +18,6 @@ export default Ember.Component.extend(bufferedRender({
|
||||
if (!this.get('executeAt')) return;
|
||||
|
||||
let statusUpdateAt = moment(this.get('executeAt'));
|
||||
if (statusUpdateAt < new Date()) return;
|
||||
|
||||
let duration = moment.duration(statusUpdateAt - moment());
|
||||
let minutesLeft = duration.asMinutes();
|
||||
|
||||
@ -18,7 +18,6 @@ export default Ember.Component.extend(CardContentsBase, CanCheckEmails, CleansUp
|
||||
showFilter: Ember.computed.and('viewingTopic', 'postStream.hasNoFilters', 'enoughPostsForFiltering'),
|
||||
showName: propertyNotEqual('user.name', 'user.username'),
|
||||
hasUserFilters: Ember.computed.gt('postStream.userFilters.length', 0),
|
||||
isSuspended: Ember.computed.notEmpty('user.suspend_reason'),
|
||||
showMoreBadges: Ember.computed.gt('moreBadgesCount', 0),
|
||||
showDelete: Ember.computed.and("viewingAdmin", "showName", "user.canBeDeleted"),
|
||||
linkWebsite: Ember.computed.not('user.isBasic'),
|
||||
|
||||
@ -11,5 +11,5 @@ export default Ember.Component.extend({
|
||||
],
|
||||
|
||||
moderatorAction: propertyEqual("item.post_type", "site.post_types.moderator_action"),
|
||||
actionDescription: actionDescription("item.action_code", "item.created_at", "item.username"),
|
||||
actionDescription: actionDescription("item.action_code", "item.created_at", "item.action_code_who"),
|
||||
});
|
||||
|
||||
@ -146,6 +146,11 @@ export default Ember.Controller.extend({
|
||||
}
|
||||
},
|
||||
|
||||
@computed('q')
|
||||
isPrivateMessage(q) {
|
||||
return q && this.currentUser && (q.indexOf("in:private") > -1 || q.indexOf(`private_messages:${this.currentUser.get('username_lower')}`) > -1);
|
||||
},
|
||||
|
||||
@observes('loading')
|
||||
_showFooter() {
|
||||
this.set("application.showFooter", !this.get("loading"));
|
||||
|
||||
@ -4,6 +4,7 @@ import { default as computed } from "ember-addons/ember-computed-decorators";
|
||||
import PreferencesTabController from "discourse/mixins/preferences-tab-controller";
|
||||
import { setting } from 'discourse/lib/computed';
|
||||
import { popupAjaxError } from 'discourse/lib/ajax-error';
|
||||
import showModal from 'discourse/lib/show-modal';
|
||||
|
||||
export default Ember.Controller.extend(CanCheckEmails, PreferencesTabController, {
|
||||
|
||||
@ -41,11 +42,6 @@ export default Ember.Controller.extend(CanCheckEmails, PreferencesTabController,
|
||||
return !this.siteSettings.enable_sso && this.siteSettings.enable_local_logins;
|
||||
},
|
||||
|
||||
@computed
|
||||
showTwoFactorModalText() {
|
||||
return I18n.t('user.second_factor.title').toLowerCase();
|
||||
},
|
||||
|
||||
actions: {
|
||||
save() {
|
||||
this.set('saved', false);
|
||||
@ -105,6 +101,10 @@ export default Ember.Controller.extend(CanCheckEmails, PreferencesTabController,
|
||||
}
|
||||
];
|
||||
bootbox.dialog(message, buttons, {"classes": "delete-account"});
|
||||
},
|
||||
|
||||
showTwoFactorModal() {
|
||||
showModal('second-factor-intro');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@ -14,6 +14,20 @@ import { popupAjaxError } from 'discourse/lib/ajax-error';
|
||||
import { spinnerHTML } from 'discourse/helpers/loading-spinner';
|
||||
import { userPath } from 'discourse/lib/url';
|
||||
|
||||
let customPostMessageCallbacks = {};
|
||||
|
||||
export function resetCustomPostMessageCallbacks() {
|
||||
customPostMessageCallbacks = {};
|
||||
}
|
||||
|
||||
export function registerCustomPostMessageCallback(type, callback) {
|
||||
if (customPostMessageCallbacks[type]) {
|
||||
throw `Error ${type} is an already registered post message!`;
|
||||
}
|
||||
|
||||
customPostMessageCallbacks[type] = callback;
|
||||
}
|
||||
|
||||
export default Ember.Controller.extend(BufferedContent, {
|
||||
composer: Ember.inject.controller(),
|
||||
application: Ember.inject.controller(),
|
||||
@ -935,7 +949,12 @@ export default Ember.Controller.extend(BufferedContent, {
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
Em.Logger.warn("unknown topic bus message type", data);
|
||||
let callback = customPostMessageCallbacks[data.type];
|
||||
if (callback) {
|
||||
callback(this, data);
|
||||
} else {
|
||||
Em.Logger.warn("unknown topic bus message type", data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -39,9 +39,9 @@ export default Ember.Controller.extend(CanCheckEmails, {
|
||||
|
||||
showStaffCounters: Ember.computed.or('hasGivenFlags', 'hasFlaggedPosts', 'hasDeletedPosts', 'hasBeenSuspended', 'hasReceivedWarnings'),
|
||||
|
||||
@computed('model.isSuspended', 'currentUser.staff')
|
||||
isNotSuspendedOrIsStaff(isSuspended, isStaff) {
|
||||
return !isSuspended || isStaff;
|
||||
@computed('model.suspended', 'currentUser.staff')
|
||||
isNotSuspendedOrIsStaff(suspended, isStaff) {
|
||||
return !suspended || isStaff;
|
||||
},
|
||||
|
||||
linkWebsite: Em.computed.not('model.isBasic'),
|
||||
|
||||
@ -8,6 +8,10 @@ registerUnbound('raw-date', dt => longDate(new Date(dt)));
|
||||
registerUnbound('age-with-tooltip', dt => new safe(autoUpdatingRelativeAge(new Date(dt), {title: true})));
|
||||
|
||||
registerUnbound('number', (orig, params) => {
|
||||
if (params.ceil) {
|
||||
orig = Math.ceil(orig);
|
||||
}
|
||||
|
||||
orig = parseInt(orig, 10);
|
||||
if (isNaN(orig)) { orig = 0; }
|
||||
|
||||
|
||||
@ -6,9 +6,10 @@ export default {
|
||||
(location.hostname === "localhost");
|
||||
|
||||
const isSupported= isSecured && ('serviceWorker' in navigator);
|
||||
const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
|
||||
|
||||
if (isSupported) {
|
||||
if (Discourse.ServiceWorkerURL) {
|
||||
if (Discourse.ServiceWorkerURL && !isSafari) {
|
||||
navigator.serviceWorker
|
||||
.register(`${Discourse.BaseUri}/${Discourse.ServiceWorkerURL}`)
|
||||
.catch(error => {
|
||||
|
||||
@ -35,7 +35,6 @@ export default {
|
||||
|
||||
bus.subscribe(`/notification/${user.get('id')}`, data => {
|
||||
const store = container.lookup('service:store');
|
||||
|
||||
const oldUnread = user.get('unread_notifications');
|
||||
const oldPM = user.get('unread_private_messages');
|
||||
|
||||
@ -66,7 +65,7 @@ export default {
|
||||
oldNotifications.insertAt(insertPosition, Em.Object.create(lastNotification));
|
||||
}
|
||||
|
||||
for (let idx=0; idx < data.recent.length; idx++) {
|
||||
for (let idx = 0; idx < data.recent.length; idx++) {
|
||||
let old;
|
||||
while(old = oldNotifications[idx]) {
|
||||
const info = data.recent[idx];
|
||||
@ -96,21 +95,16 @@ export default {
|
||||
});
|
||||
|
||||
bus.subscribe("/client_settings", data => Ember.set(siteSettings, data.name, data.value));
|
||||
|
||||
bus.subscribe("/refresh_client", data => {
|
||||
Discourse.set("assetVersion", data);
|
||||
});
|
||||
bus.subscribe("/refresh_client", data => Discourse.set("assetVersion", data));
|
||||
|
||||
if (!Ember.testing) {
|
||||
|
||||
bus.subscribe(alertChannel(user), data => onNotification(data, user));
|
||||
initDesktopNotifications(bus, appEvents);
|
||||
|
||||
if(isPushNotificationsEnabled(user, site.mobileView)) {
|
||||
if (isPushNotificationsEnabled(user, site.mobileView)) {
|
||||
disableDesktopNotifications();
|
||||
registerPushNotifications(Discourse.User.current(), site.mobileView, router, appEvents);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
unsubscribePushNotifications(user);
|
||||
}
|
||||
}
|
||||
|
||||
@ -301,9 +301,13 @@ export function relativeAge(date, options) {
|
||||
return "UNKNOWN FORMAT";
|
||||
}
|
||||
|
||||
export function number(val) {
|
||||
export function number(val, options = {}) {
|
||||
let formattedNumber;
|
||||
|
||||
if (options.ceil) {
|
||||
val = Math.ceil(val);
|
||||
}
|
||||
|
||||
val = parseInt(val, 10);
|
||||
if (isNaN(val)) val = 0;
|
||||
|
||||
|
||||
@ -25,9 +25,10 @@ import { modifySelectKit } from "select-kit/mixins/plugin-api";
|
||||
import { addGTMPageChangedCallback } from 'discourse/lib/page-tracker';
|
||||
import { registerCustomAvatarHelper } from 'discourse/helpers/user-avatar';
|
||||
import { disableNameSuppression } from 'discourse/widgets/poster-name';
|
||||
import { registerCustomPostMessageCallback as registerCustomPostMessageCallback1 } from 'discourse/controllers/topic';
|
||||
|
||||
// If you add any methods to the API ensure you bump up this number
|
||||
const PLUGIN_API_VERSION = '0.8.21';
|
||||
const PLUGIN_API_VERSION = '0.8.22';
|
||||
|
||||
class PluginApi {
|
||||
constructor(version, container) {
|
||||
@ -426,6 +427,24 @@ class PluginApi {
|
||||
disableNameSuppression();
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a callback that will be invoked when the server calls
|
||||
* Post#publish_change_to_clients! please ensure your type does not
|
||||
* match acted,revised,rebaked,recovered, created,move_to_inbox or archived
|
||||
*
|
||||
* callback will be called with topicController and Message
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* api.registerCustomPostMessageCallback("applied_color", (topicController, message) => {
|
||||
* let stream = topicController.get("model.postStream");
|
||||
* // etc
|
||||
* });
|
||||
*/
|
||||
registerCustomPostMessageCallback(type, callback) {
|
||||
registerCustomPostMessageCallback1(type, callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes a setting associated with a widget. For example, if
|
||||
* you wanted small avatars in the post stream:
|
||||
|
||||
@ -169,8 +169,6 @@ const User = RestModel.extend({
|
||||
isElder: Em.computed.equal('trust_level', 4),
|
||||
canManageTopic: Em.computed.or('staff', 'isElder'),
|
||||
|
||||
isSuspended: Em.computed.equal('suspended', true),
|
||||
|
||||
@computed("previous_visit_at")
|
||||
previousVisitAt(previous_visit_at) {
|
||||
return new Date(previous_visit_at);
|
||||
|
||||
@ -6,6 +6,7 @@ export default Discourse.Route.extend({
|
||||
beforeModel(transition) {
|
||||
const self = this;
|
||||
const params = transition.queryParams;
|
||||
const groupName = params.groupname || params.group_name;
|
||||
|
||||
if (self.currentUser) {
|
||||
self.replaceWith("discovery.latest").then(e => {
|
||||
@ -20,13 +21,13 @@ export default Discourse.Route.extend({
|
||||
}).catch(function() {
|
||||
bootbox.alert(I18n.t("generic_error"));
|
||||
});
|
||||
} else if (params.groupname) {
|
||||
} else if (groupName) {
|
||||
// send a message to a group
|
||||
Group.messageable(params.groupname).then(result => {
|
||||
Group.messageable(groupName).then(result => {
|
||||
if (result.messageable) {
|
||||
Ember.run.next(() => e.send("createNewMessageViaParams", params.groupname, params.title, params.body));
|
||||
Ember.run.next(() => e.send("createNewMessageViaParams", groupName, params.title, params.body));
|
||||
} else {
|
||||
bootbox.alert(I18n.t("composer.cant_send_pm", { username: params.groupname }));
|
||||
bootbox.alert(I18n.t("composer.cant_send_pm", { username: groupName }));
|
||||
}
|
||||
}).catch(function() {
|
||||
bootbox.alert(I18n.t("generic_error"));
|
||||
|
||||
@ -15,10 +15,6 @@ export default RestrictedUserRoute.extend({
|
||||
},
|
||||
|
||||
actions: {
|
||||
showTwoFactorModal() {
|
||||
showModal('second-factor-intro');
|
||||
},
|
||||
|
||||
showAvatarSelector() {
|
||||
showModal('avatar-selector');
|
||||
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
category=secondCategory
|
||||
parentCategory=firstCategory
|
||||
categories=childCategories
|
||||
subCategory="true"
|
||||
subCategory=true
|
||||
noSubcategories=noSubcategories}}
|
||||
{{/if}}
|
||||
|
||||
|
||||
@ -6,7 +6,13 @@
|
||||
<a class="close" {{action closeModal}}>{{d-icon "times"}}</a>
|
||||
</div>
|
||||
|
||||
<h3>{{title}}</h3>
|
||||
<div class="title">
|
||||
<h3>{{title}}</h3>
|
||||
|
||||
{{#if subtitle}}
|
||||
<p>{{subtitle}}</p>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id='modal-alert'></div>
|
||||
|
||||
@ -0,0 +1 @@
|
||||
{{{translatedText}}}
|
||||
@ -69,7 +69,7 @@
|
||||
<div class="controls">
|
||||
{{combo-box valueAttribute="value" content=availableSorts value=category.sort_order none="category.sort_options.default"}}
|
||||
{{#unless isDefaultSortOrder}}
|
||||
{{combo-box valueAttribute="value" content=sortAscendingOptions value=category.sort_ascending none="category.sort_options.default"}}
|
||||
{{combo-box castBoolean=true valueAttribute="value" content=sortAscendingOptions value=category.sort_ascending none="category.sort_options.default"}}
|
||||
{{/unless}}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@ -9,7 +9,7 @@
|
||||
input=topicTimer.updateTime
|
||||
statusType=selection
|
||||
includeWeekend=true
|
||||
basedOnLastPost=false}}
|
||||
basedOnLastPost=topicTimer.based_on_last_post}}
|
||||
{{else if publishToCategory}}
|
||||
<div class="control-group">
|
||||
<label>{{i18n 'topic.topic_status_update.publish_to'}}</label>
|
||||
@ -23,7 +23,7 @@
|
||||
statusType=selection
|
||||
includeWeekend=true
|
||||
categoryId=topicTimer.category_id
|
||||
basedOnLastPost=false}}
|
||||
basedOnLastPost=topicTimer.based_on_last_post}}
|
||||
{{else if autoClose}}
|
||||
{{future-date-input
|
||||
input=topicTimer.updateTime
|
||||
|
||||
@ -60,6 +60,7 @@
|
||||
<div class="controls">
|
||||
{{#if currentUser}}
|
||||
<section class='field'>
|
||||
<label>{{input type="checkbox" class="in-title" checked=searchedTerms.special.in.title}} {{i18n "search.advanced.filters.title"}}</label>
|
||||
<label>{{input type="checkbox" class="in-likes" checked=searchedTerms.special.in.likes}} {{i18n "search.advanced.filters.likes"}}</label>
|
||||
<label>{{input type="checkbox" class="in-private" checked=searchedTerms.special.in.private}} {{i18n "search.advanced.filters.private"}}</label>
|
||||
<label>{{input type="checkbox" class="in-seen" checked=searchedTerms.special.in.seen}} {{i18n "search.advanced.filters.seen"}}</label>
|
||||
|
||||
@ -84,7 +84,7 @@
|
||||
args=(hash user=user close=(action "close"))
|
||||
tagName=""}}
|
||||
|
||||
{{#if isSuspended}}
|
||||
{{#if user.suspend_reason}}
|
||||
<div class='suspended'>
|
||||
{{d-icon "ban"}}
|
||||
<b>{{i18n 'user.suspended_notice' date=user.suspendedTillDate}}</b><br>
|
||||
|
||||
@ -85,7 +85,7 @@
|
||||
{{/if}}
|
||||
{{category-link result.topic.category hideParent=true}}
|
||||
{{#each result.topic.tags as |tag|}}
|
||||
{{discourse-tag tag}}
|
||||
{{discourse-tag tag isPrivateMessage=isPrivateMessage}}
|
||||
{{/each}}
|
||||
{{plugin-outlet name="full-page-search-category" args=(hash result=result)}}
|
||||
</div>
|
||||
|
||||
@ -1,11 +1,10 @@
|
||||
{{#load-more selector=".user-stream-item" action="loadMore"}}
|
||||
<div class="user-stream">
|
||||
{{#each model as |post|}}
|
||||
{{group-post post=post class="user-stream-item item"}}
|
||||
{{else}}
|
||||
<div>{{i18n emptyText}}</div>
|
||||
{{/each}}
|
||||
{{#each model as |post|}}
|
||||
{{group-post post=post class="user-stream-item item"}}
|
||||
{{else}}
|
||||
<div>{{i18n emptyText}}</div>
|
||||
{{/each}}
|
||||
</div>
|
||||
{{conditional-loading-spinner condition=loading}}
|
||||
{{/load-more}}
|
||||
|
||||
|
||||
@ -1,9 +1,7 @@
|
||||
<div class="group-members-actions">
|
||||
{{#if hasMembers}}
|
||||
{{text-field value=filterInput
|
||||
placeholderKey=filterPlaceholder
|
||||
class="group-username-filter no-blur"}}
|
||||
{{/if}}
|
||||
|
||||
<div class="group-members-manage">
|
||||
{{#if canManageGroup}}
|
||||
|
||||
@ -9,7 +9,7 @@
|
||||
|
||||
<div class="groups-header-filters">
|
||||
{{text-field value=filterInput
|
||||
placeholderKey="groups.index.all_groups"
|
||||
placeholderKey="groups.index.all"
|
||||
class="groups-header-filters-name no-blur"}}
|
||||
|
||||
{{combo-box value=type
|
||||
|
||||
@ -14,7 +14,7 @@
|
||||
<table>
|
||||
<tr>
|
||||
<td>
|
||||
<label for='login-account-name'>{{i18n 'login.username'}} </label>
|
||||
<label for='login-account-name'>{{i18n 'login.username'}}</label>
|
||||
</td>
|
||||
<td>
|
||||
{{text-field value=loginName type="email" placeholderKey="login.email_placeholder" id="login-account-name" autocorrect="off" autocapitalize="off"}}
|
||||
@ -32,10 +32,10 @@
|
||||
{{/if}}
|
||||
<tr>
|
||||
<td>
|
||||
<label for='login-account-password'>{{i18n 'login.password'}} </label>
|
||||
<label for='login-account-password'>{{i18n 'login.password'}}</label>
|
||||
</td>
|
||||
<td>
|
||||
{{text-field value=loginPassword type="password" id="login-account-password" maxlength="200"}}
|
||||
{{text-field value=loginPassword type="password" id="login-account-password" maxlength="200"}}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@ -51,8 +51,7 @@
|
||||
{{/second-factor-form}}
|
||||
</form>
|
||||
{{/if}}
|
||||
{{authMessage}}
|
||||
<div id='login-alert' class={{alertClass}}>{{alert}}</div>
|
||||
|
||||
{{/d-modal-body}}
|
||||
|
||||
<div class="modal-footer">
|
||||
@ -74,4 +73,6 @@
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</div>
|
||||
<div class="auth-message">{{authMessage}}</div>
|
||||
<div id='login-alert' class={{alertClass}}>{{alert}}</div>
|
||||
{{/login-modal}}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
{{#d-modal
|
||||
modalClass=modalClass
|
||||
title=title
|
||||
subtitle=subtitle
|
||||
class="hidden"
|
||||
errors=errors
|
||||
closeModal=(route-action "closeModal")}}
|
||||
|
||||
@ -1,13 +1,13 @@
|
||||
{{#create-account email=accountEmail disabled=submitDisabled action="createAccount"}}
|
||||
|
||||
{{#unless complete}}
|
||||
{{#d-modal-body title="create_account.title"}}
|
||||
|
||||
{{#unless hasAuthOptions}}
|
||||
{{login-buttons externalLogin="externalLogin"}}
|
||||
{{/unless}}
|
||||
|
||||
|
||||
{{#if showCreateForm}}
|
||||
<div>
|
||||
<div class="login-form">
|
||||
<form>
|
||||
<table>
|
||||
|
||||
@ -15,12 +15,13 @@
|
||||
<td class="label"><label for='new-account-email'>{{i18n 'user.email.title'}}</label></td>
|
||||
<td>
|
||||
{{input type="email" value=accountEmail id="new-account-email" disabled=emailValidated name="email" autofocus="autofocus"}}
|
||||
{{input-tip validation=emailValidation}}
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr class="instructions create-account-email">
|
||||
<td></td>
|
||||
<td><label>{{i18n 'user.email.instructions'}}</label></td>
|
||||
{{input-tip validation=emailValidation}}
|
||||
<td><label>{{i18n 'user.email.instructions'}}</label></td>
|
||||
</tr>
|
||||
|
||||
{{#if usernameRequired}}
|
||||
@ -28,11 +29,11 @@
|
||||
<td class="label"><label for='new-account-username'>{{i18n 'user.username.title'}}</label></td>
|
||||
<td>
|
||||
{{input value=accountUsername id="new-account-username" name="username" maxlength=maxUsernameLength autocomplete="off"}}
|
||||
{{input-tip validation=usernameValidation id="username-validation"}}
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="instructions">
|
||||
<td></td>
|
||||
{{input-tip validation=usernameValidation id="username-validation"}}
|
||||
<td><label>{{i18n 'user.username.instructions'}}</label></td>
|
||||
</tr>
|
||||
{{/if}}
|
||||
@ -43,11 +44,12 @@
|
||||
<label for='new-account-name'>{{i18n 'user.name.title'}}</label>
|
||||
</td>
|
||||
<td>
|
||||
{{text-field value=accountName id="new-account-name"}} {{input-tip validation=nameValidation}}
|
||||
{{text-field value=accountName id="new-account-name"}}
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="instructions">
|
||||
<td></td>
|
||||
{{input-tip validation=nameValidation}}
|
||||
<td><label>{{nameInstructions}}</label></td>
|
||||
</tr>
|
||||
{{/if}}
|
||||
@ -63,14 +65,14 @@
|
||||
<td class="label"><label for='new-account-password'>{{i18n 'user.password.title'}}</label></td>
|
||||
<td>
|
||||
{{password-field value=accountPassword type="password" id="new-account-password" capsLockOn=capsLockOn}}
|
||||
{{input-tip validation=passwordValidation}}
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="instructions">
|
||||
<td></td>
|
||||
{{input-tip validation=passwordValidation}}
|
||||
<td>
|
||||
<label>{{passwordInstructions}}</label>
|
||||
<div class="caps-lock-warning {{unless capsLockOn 'invisible'}}">
|
||||
<div class="caps-lock-warning {{unless capsLockOn 'hidden'}}">
|
||||
{{d-icon "exclamation-triangle"}} {{i18n 'login.caps_lock_warning'}}</div>
|
||||
</td>
|
||||
</tr>
|
||||
@ -97,6 +99,7 @@
|
||||
</form>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
{{/d-modal-body}}
|
||||
|
||||
{{#if showCreateForm}}
|
||||
|
||||
@ -1,12 +1,5 @@
|
||||
{{#login-modal screenX=lastX screenY=lastY loginName=loginName loginPassword=loginPassword loginSecondFactor=loginSecondFactor action="login"}}
|
||||
{{#d-modal-body title="login.title" class="login-modal"}}
|
||||
{{#if showLoginButtons}}
|
||||
{{login-buttons
|
||||
canLoginLocalWithEmail=canLoginLocalWithEmail
|
||||
processingEmailLink=processingEmailLink
|
||||
emailLogin='emailLogin'
|
||||
externalLogin='externalLogin'}}
|
||||
{{/if}}
|
||||
|
||||
{{#if canLoginLocal}}
|
||||
<form id='login-form' method='post'>
|
||||
@ -30,7 +23,7 @@
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td><div class="caps-lock-warning {{unless capsLockOn 'invisible'}}">{{d-icon "exclamation-triangle"}} {{i18n 'login.caps_lock_warning'}}</div></td>
|
||||
<td><div class="caps-lock-warning {{unless capsLockOn 'hidden'}}">{{d-icon "exclamation-triangle"}} {{i18n 'login.caps_lock_warning'}}</div></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</table>
|
||||
@ -40,8 +33,14 @@
|
||||
{{/second-factor-form}}
|
||||
</form>
|
||||
{{/if}}
|
||||
{{authMessage}}
|
||||
<div id='login-alert' class={{alertClass}}>{{alert}}</div>
|
||||
|
||||
{{#if showLoginButtons}}
|
||||
{{login-buttons
|
||||
canLoginLocalWithEmail=canLoginLocalWithEmail
|
||||
processingEmailLink=processingEmailLink
|
||||
emailLogin='emailLogin'
|
||||
externalLogin='externalLogin'}}
|
||||
{{/if}}
|
||||
{{/d-modal-body}}
|
||||
|
||||
<div class="modal-footer">
|
||||
@ -65,4 +64,7 @@
|
||||
|
||||
{{conditional-loading-spinner condition=showSpinner size="small"}}
|
||||
</div>
|
||||
|
||||
<div class="auth-message">{{authMessage}}</div>
|
||||
<div id='login-alert' class={{alertClass}}>{{alert}}</div>
|
||||
{{/login-modal}}
|
||||
|
||||
@ -71,9 +71,7 @@
|
||||
{{#if model.second_factor_enabled}}
|
||||
{{i18n 'user.second_factor.disable'}}
|
||||
{{else}}
|
||||
{{i18n 'enable'}}
|
||||
<a href {{action "showTwoFactorModal"}}>{{showTwoFactorModalText}}</a>
|
||||
{{i18n 'user.second_factor.enable'}}
|
||||
{{discourse-linked-text action="showTwoFactorModal" text="user.second_factor.enable"}}
|
||||
{{/if}}
|
||||
|
||||
{{#if isCurrentUser}}
|
||||
|
||||
@ -1,3 +1,10 @@
|
||||
{{#unless siteSettings.disable_mailing_list_mode}}
|
||||
<div class="warning-wrap">
|
||||
{{#if model.user_option.mailing_list_mode}}
|
||||
<div class="warning">{{i18n 'user.mailing_list_mode.warning'}}</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
{{/unless}}
|
||||
<div class="control-group pref-email-settings">
|
||||
<label class="control-label">{{i18n 'user.email_settings'}}</label>
|
||||
<div class='controls controls-dropdown'>
|
||||
|
||||
@ -16,7 +16,7 @@
|
||||
<div class="instructions">{{i18n 'user.watched_tags_instructions'}}</div>
|
||||
|
||||
<div class="controls tag-controls">
|
||||
<label>{{d-icon "d-regular" class="icon tracking"}} {{i18n 'user.tracked_tags'}}</label>
|
||||
<label>{{d-icon "d-tracking" class="icon tracking"}} {{i18n 'user.tracked_tags'}}</label>
|
||||
{{tag-chooser
|
||||
tags=model.tracked_tags
|
||||
blacklist=selectedTags
|
||||
|
||||
@ -107,7 +107,7 @@
|
||||
</h3>
|
||||
|
||||
<div class='bio'>
|
||||
{{#if model.isSuspended}}
|
||||
{{#if model.suspended}}
|
||||
<div class='suspended'>
|
||||
{{d-icon "ban"}}
|
||||
<b>
|
||||
|
||||
@ -69,7 +69,7 @@ registerButton('like-count', attrs => {
|
||||
const title = attrs.liked
|
||||
? count === 1 ? 'post.has_likes_title_only_you' : 'post.has_likes_title_you'
|
||||
: 'post.has_likes_title';
|
||||
const icon = attrs.yours ? 'heart' : '';
|
||||
const icon = attrs.yours ? 'd-liked' : '';
|
||||
const additionalClass = attrs.yours ? 'my-likes' : 'regular-likes';
|
||||
|
||||
return { action: 'toggleWhoLiked',
|
||||
|
||||
@ -105,7 +105,7 @@ export default createWidget('search-menu', {
|
||||
|
||||
if (contextEnabled && ctx) {
|
||||
if (this.currentUser &&
|
||||
ctx.id.toString().toLowerCase() === this.currentUser.username_lower &&
|
||||
ctx.id.toString().toLowerCase() === this.currentUser.get('username_lower') &&
|
||||
type === "private_messages") {
|
||||
query += ' in:private';
|
||||
} else {
|
||||
|
||||
@ -4,6 +4,8 @@ import { h } from 'virtual-dom';
|
||||
import { relativeAge } from 'discourse/lib/formatter';
|
||||
import { iconNode } from 'discourse-common/lib/icon-library';
|
||||
import RawHtml from 'discourse/widgets/raw-html';
|
||||
import renderTags from 'discourse/lib/render-tags';
|
||||
import renderTopicFeaturedLink from 'discourse/lib/render-topic-featured-link';
|
||||
|
||||
const SCROLLER_HEIGHT = 50;
|
||||
const LAST_READ_HEIGHT = 20;
|
||||
@ -382,6 +384,7 @@ export default createWidget('topic-timeline', {
|
||||
const createdAt = new Date(topic.created_at);
|
||||
const stream = attrs.topic.get('postStream.stream');
|
||||
const { currentUser } = this;
|
||||
const { tagging_enabled, topic_featured_link_enabled } = this.siteSettings;
|
||||
|
||||
attrs["currentUser"] = currentUser;
|
||||
|
||||
@ -399,6 +402,34 @@ export default createWidget('topic-timeline', {
|
||||
action: 'jumpTop'
|
||||
}))];
|
||||
|
||||
// duplicate of the {{topic-category}} component
|
||||
let category = [];
|
||||
|
||||
if (!topic.get("isPrivateMessage")) {
|
||||
if (topic.category.parentCategory) {
|
||||
category.push(this.attach("category-link", { category: topic.category.parentCategory }));
|
||||
}
|
||||
category.push(this.attach("category-link", { category: topic.category }));
|
||||
}
|
||||
|
||||
const showTags = tagging_enabled && topic.tags && topic.tags.length > 0;
|
||||
|
||||
if (showTags || topic_featured_link_enabled) {
|
||||
let extras = [];
|
||||
if (showTags) {
|
||||
const tagsHtml = new RawHtml({ html: renderTags(topic, { mode: "list" }) });
|
||||
extras.push(h("div.list-tags", tagsHtml));
|
||||
}
|
||||
if (topic_featured_link_enabled) {
|
||||
extras.push(new RawHtml({ html: renderTopicFeaturedLink(topic) }));
|
||||
}
|
||||
category.push(h("div.topic-header-extra", extras));
|
||||
}
|
||||
|
||||
if (category.length > 0) {
|
||||
elems.push(h("div.topic-category", category));
|
||||
}
|
||||
|
||||
if (this.state.excerpt) {
|
||||
elems.push(new RawHtml({
|
||||
html: `<div class='post-excerpt'>${this.state.excerpt}</div>`
|
||||
|
||||
3
app/assets/javascripts/locales/bg.js.erb
Normal file
3
app/assets/javascripts/locales/bg.js.erb
Normal file
@ -0,0 +1,3 @@
|
||||
//= depend_on 'client.bg.yml'
|
||||
//= require locales/i18n
|
||||
<%= JsLocaleHelper.output_locale(:bg) %>
|
||||
@ -11,7 +11,7 @@ function trailingSpaceOnly(src, start, max) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const ATTR_REGEX = /^\s*=(.+)$|((([a-z0-9]*)\s*)=)(["“”'].*["“”']|\S+)/ig;
|
||||
const ATTR_REGEX = /^\s*=(.+)$|((([a-z0-9]*)\s*)=)(["“”'].*?["“”']|\S+)/ig;
|
||||
|
||||
// parse a tag [test a=1 b=2] to a data structure
|
||||
// {tag: "test", attrs={a: "1", b: "2"}
|
||||
|
||||
@ -20,6 +20,7 @@ export default ComboBoxComponent.extend({
|
||||
fullWidthOnMobile: true,
|
||||
caretDownIcon: "caret-right",
|
||||
caretUpIcon: "caret-down",
|
||||
subCategory: false,
|
||||
|
||||
init() {
|
||||
this._super();
|
||||
@ -50,13 +51,15 @@ export default ComboBoxComponent.extend({
|
||||
collectionHeader(allCategoriesUrl, allCategoriesLabel, noCategoriesUrl, noCategoriesLabel) {
|
||||
let shortcuts = "";
|
||||
|
||||
shortcuts += `
|
||||
<a href="${allCategoriesUrl}" class="category-filter">
|
||||
${allCategoriesLabel}
|
||||
</a>
|
||||
`;
|
||||
if (this.get("hasSelection") || (this.get("noSubcategories") && this.get("subCategory"))) {
|
||||
shortcuts += `
|
||||
<a href="${allCategoriesUrl}" class="category-filter">
|
||||
${allCategoriesLabel}
|
||||
</a>
|
||||
`;
|
||||
}
|
||||
|
||||
if (this.get("subCategory")) {
|
||||
if (this.get("subCategory") && (this.get("hasSelection") || !this.get("noSubcategories"))) {
|
||||
shortcuts += `
|
||||
<a href="${noCategoriesUrl}" class="category-filter">
|
||||
${noCategoriesLabel}
|
||||
|
||||
@ -28,6 +28,7 @@ export default DropdownSelectBoxComponent.extend({
|
||||
allowInitialValueMutation: false,
|
||||
allowAutoSelectFirst: false,
|
||||
showFullTitle: false,
|
||||
isHidden: Ember.computed.empty("content"),
|
||||
|
||||
didReceiveAttrs() {
|
||||
this._super();
|
||||
@ -68,7 +69,7 @@ export default DropdownSelectBoxComponent.extend({
|
||||
content(options, canWhisper, action) {
|
||||
let items = [];
|
||||
|
||||
if (action !== CREATE_TOPIC && action !== CREATE_SHARED_DRAFT) {
|
||||
if (action !== CREATE_TOPIC && action !== CREATE_SHARED_DRAFT && _topicSnapshot) {
|
||||
items.push({
|
||||
name: I18n.t("composer.composer_actions.reply_as_new_topic.label"),
|
||||
description: I18n.t("composer.composer_actions.reply_as_new_topic.desc"),
|
||||
|
||||
@ -155,7 +155,7 @@ export default ComboBoxComponent.extend(DatetimeMixin, {
|
||||
},
|
||||
|
||||
mutateValue(value) {
|
||||
if (this.get("isCustom") || this.get("isBasedOnLastPost")) {
|
||||
if (value === 'pick_date_and_time' || this.get("isBasedOnLastPost")) {
|
||||
this.set("value", value);
|
||||
} else {
|
||||
let input = null;
|
||||
|
||||
@ -29,11 +29,15 @@ export default ComboBoxComponent.extend({
|
||||
|
||||
@computed
|
||||
collectionHeader() {
|
||||
return `
|
||||
<a href="${Discourse.getURL("/groups")}" class="group-dropdown-filter">
|
||||
${I18n.t("groups.index.all").toLowerCase()}
|
||||
</a>
|
||||
`.htmlSafe();
|
||||
if (this.siteSettings.enable_group_directory ||
|
||||
(this.currentUser && this.currentUser.get('staff'))) {
|
||||
|
||||
return `
|
||||
<a href="${Discourse.getURL("/groups")}" class="group-dropdown-filter">
|
||||
${I18n.t("groups.index.all").toLowerCase()}
|
||||
</a>
|
||||
`.htmlSafe();
|
||||
}
|
||||
},
|
||||
|
||||
actions: {
|
||||
|
||||
@ -79,7 +79,7 @@ export default SelectKitComponent.extend({
|
||||
shouldDisplayFilter() { return true; },
|
||||
|
||||
_beforeWillComputeValues(values) {
|
||||
return values.map(v => this._castInteger(v === "" ? null : v));
|
||||
return values.map(v => this._cast(v === "" ? null : v));
|
||||
},
|
||||
willComputeValues(values) { return values; },
|
||||
computeValues(values) { return values; },
|
||||
|
||||
@ -62,6 +62,7 @@ export default Ember.Component.extend(UtilsMixin, PluginApiMixin, DomHelpersMixi
|
||||
horizontalOffset: 0,
|
||||
fullWidthOnMobile: false,
|
||||
castInteger: false,
|
||||
castBoolean: false,
|
||||
allowAny: false,
|
||||
allowInitialValueMutation: false,
|
||||
content: null,
|
||||
@ -169,7 +170,7 @@ export default Ember.Component.extend(UtilsMixin, PluginApiMixin, DomHelpersMixi
|
||||
}
|
||||
|
||||
let computedContentItem = {
|
||||
value: this._castInteger(this.valueForContentItem(contentItem)),
|
||||
value: this._cast(this.valueForContentItem(contentItem)),
|
||||
name: name || this._nameForContent(contentItem),
|
||||
locked: false,
|
||||
created: options.created || false,
|
||||
@ -333,7 +334,10 @@ export default Ember.Component.extend(UtilsMixin, PluginApiMixin, DomHelpersMixi
|
||||
},
|
||||
|
||||
stopLoading() {
|
||||
this.focus();
|
||||
if (this.site && !this.site.isMobileDevice) {
|
||||
this.focusFilterOrHeader();
|
||||
}
|
||||
|
||||
this.set("isLoading", false);
|
||||
this._boundaryActionHandler("onStopLoading");
|
||||
},
|
||||
|
||||
@ -9,7 +9,7 @@ export default Ember.Component.extend({
|
||||
"tabindex",
|
||||
"ariaLabel:aria-label",
|
||||
"ariaHasPopup:aria-haspopup",
|
||||
"title",
|
||||
"sanitizedTitle:title",
|
||||
"value:data-value",
|
||||
"name:data-name",
|
||||
],
|
||||
@ -18,7 +18,7 @@ export default Ember.Component.extend({
|
||||
|
||||
ariaHasPopup: true,
|
||||
|
||||
ariaLabel: Ember.computed.or("computedContent.ariaLabel", "title"),
|
||||
ariaLabel: Ember.computed.or("computedContent.ariaLabel", "sanitizedTitle"),
|
||||
|
||||
@computed("computedContent.title", "name")
|
||||
title(computedContentTitle, name) {
|
||||
@ -28,6 +28,13 @@ export default Ember.Component.extend({
|
||||
return null;
|
||||
},
|
||||
|
||||
// this might need a more advanced solution
|
||||
// but atm it's the only case we have to handle
|
||||
@computed("title")
|
||||
sanitizedTitle(title) {
|
||||
return String(title).replace("…", "");
|
||||
},
|
||||
|
||||
label: Ember.computed.or("computedContent.label", "title", "name"),
|
||||
|
||||
name: Ember.computed.alias("computedContent.name"),
|
||||
|
||||
@ -63,7 +63,7 @@ export default SelectKitComponent.extend({
|
||||
switch (typeof value) {
|
||||
case "string":
|
||||
case "number":
|
||||
return this._castInteger(value === "" ? null : value);
|
||||
return this._cast(value === "" ? null : value);
|
||||
default:
|
||||
return value;
|
||||
}
|
||||
|
||||
@ -77,14 +77,25 @@ export default ComboBoxComponent.extend({
|
||||
|
||||
@computed("allTagsUrl", "allTagsLabel", "noTagsUrl", "noTagsLabel")
|
||||
collectionHeader(allTagsUrl, allTagsLabel, noTagsUrl, noTagsLabel) {
|
||||
return `
|
||||
<a href="${allTagsUrl}" class="tag-filter">
|
||||
${allTagsLabel}
|
||||
</a>
|
||||
<a href="${noTagsUrl}" class="tag-filter">
|
||||
${noTagsLabel}
|
||||
</a>
|
||||
`;
|
||||
let content = "";
|
||||
|
||||
if (this.get("tagId") !== "none") {
|
||||
content += `
|
||||
<a href="${noTagsUrl}" class="tag-filter">
|
||||
${noTagsLabel}
|
||||
</a>
|
||||
`;
|
||||
}
|
||||
|
||||
if (this.get("hasSelection") || this.get("tagId") === "none") {
|
||||
content += `
|
||||
<a href="${allTagsUrl}" class="tag-filter">
|
||||
${allTagsLabel}
|
||||
</a>
|
||||
`;
|
||||
}
|
||||
|
||||
return content;
|
||||
},
|
||||
|
||||
@computed("tag")
|
||||
|
||||
@ -28,7 +28,6 @@ export default Ember.Mixin.create({
|
||||
})
|
||||
.finally(() => {
|
||||
self.stopLoading();
|
||||
self.focusFilterOrHeader();
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
@ -27,6 +27,19 @@ export default Ember.Mixin.create({
|
||||
return !isNaN(parseFloat(input)) && isFinite(input);
|
||||
},
|
||||
|
||||
_cast(value) {
|
||||
if (value === this.noneValue) return value;
|
||||
return this._castInteger(this._castBoolean(value));
|
||||
},
|
||||
|
||||
_castBoolean(value) {
|
||||
if (this.get("castBoolean") && Ember.isPresent(value) && typeof(value) === "string") {
|
||||
return value === "true";
|
||||
}
|
||||
|
||||
return value;
|
||||
},
|
||||
|
||||
_castInteger(value) {
|
||||
if (this.get("castInteger") && Ember.isPresent(value) && this._isNumeric(value)) {
|
||||
return parseInt(value, 10);
|
||||
|
||||
@ -37,6 +37,8 @@ self.addEventListener('install', function(event) {
|
||||
return caches.open(CURRENT_CACHES.offline).then(function(cache) {
|
||||
return cache.put(OFFLINE_URL, response);
|
||||
});
|
||||
}).then(function(cache) {
|
||||
self.skipWaiting();
|
||||
})
|
||||
);
|
||||
});
|
||||
@ -60,11 +62,17 @@ self.addEventListener('activate', function(event) {
|
||||
}
|
||||
})
|
||||
);
|
||||
}).then(function() {
|
||||
self.clients.claim()
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
self.addEventListener('fetch', function(event) {
|
||||
// Bypass service workers if this is a url with a token param
|
||||
if(/\?.*token/i.test(event.request.url)) {
|
||||
return;
|
||||
}
|
||||
// We only want to call event.respondWith() if this is a navigation request
|
||||
// for an HTML page.
|
||||
// request.mode of 'navigate' is unfortunately not supported in Chrome
|
||||
|
||||
@ -6,6 +6,7 @@
|
||||
@import "common/admin/customize";
|
||||
@import "common/admin/flagging";
|
||||
@import "common/admin/dashboard_next";
|
||||
@import "common/admin/admin_reports";
|
||||
@import "common/admin/moderation_history";
|
||||
@import "common/admin/suspend";
|
||||
|
||||
@ -880,7 +881,6 @@ section.details {
|
||||
|
||||
.upgrade-header {
|
||||
flex: 1 1 100%;
|
||||
margin: .25em 0 1em 0;
|
||||
@media screen and (max-width: 650px) {
|
||||
margin: 0;
|
||||
}
|
||||
@ -1969,6 +1969,12 @@ table#user-badges {
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
.admin-reports, .dashboard-next {
|
||||
&.admin-contents {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.cbox0 { background: blend-primary-secondary(0%); }
|
||||
.cbox10 { background: blend-primary-secondary(10%); }
|
||||
.cbox20 { background: blend-primary-secondary(20%); }
|
||||
|
||||
53
app/assets/stylesheets/common/admin/admin_reports.scss
Normal file
53
app/assets/stylesheets/common/admin/admin_reports.scss
Normal file
@ -0,0 +1,53 @@
|
||||
.admin-reports {
|
||||
h3 {
|
||||
border-bottom: 1px solid $primary-low;
|
||||
margin-bottom: .5em;
|
||||
padding-bottom: .5em;
|
||||
}
|
||||
|
||||
.report-container {
|
||||
display: flex;
|
||||
|
||||
.loading-container {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.visualization {
|
||||
display: flex;
|
||||
flex: 4;
|
||||
}
|
||||
|
||||
.filters {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
margin-left: 2em;
|
||||
|
||||
.date-picker {
|
||||
margin: 0;
|
||||
width: 195px;
|
||||
}
|
||||
|
||||
.combo-box, .date-picker-wrapper, .btn {
|
||||
width: 100%;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
}
|
||||
|
||||
@include small-width {
|
||||
flex-direction: column;
|
||||
min-width: 100%;
|
||||
|
||||
.visualization {
|
||||
order: 2;
|
||||
}
|
||||
|
||||
.filters {
|
||||
order: 1;
|
||||
margin: 0;
|
||||
align-items: flex-start;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,8 +1,4 @@
|
||||
.dashboard-next {
|
||||
&.admin-contents {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.section-top {
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
@ -17,6 +13,15 @@
|
||||
|
||||
.section-column {
|
||||
min-width: calc(50% - .5em);
|
||||
max-width: 100%;
|
||||
|
||||
&:last-child, {
|
||||
margin-left: 1em;
|
||||
}
|
||||
|
||||
&:first-child {
|
||||
margin-right: 1em;
|
||||
}
|
||||
|
||||
@include small-width {
|
||||
min-width: 100%;
|
||||
@ -31,16 +36,9 @@
|
||||
}
|
||||
}
|
||||
|
||||
.section-column:last-child, {
|
||||
margin-left: 1em;
|
||||
}
|
||||
|
||||
.section-column:first-child {
|
||||
margin-right: 1em;
|
||||
}
|
||||
|
||||
@include small-width {
|
||||
.section-column:last-child, .section-column:first-child {
|
||||
.section-column:last-child,
|
||||
.section-column:first-child {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
@ -53,6 +51,9 @@
|
||||
}
|
||||
|
||||
display: flex;
|
||||
@media screen and (max-width: 400px) {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
border-bottom: 1px solid $primary-low;
|
||||
@ -61,7 +62,7 @@
|
||||
}
|
||||
|
||||
.section-body {
|
||||
padding: 1em 0;
|
||||
padding: 1em 0 0;
|
||||
}
|
||||
}
|
||||
|
||||
@ -103,16 +104,17 @@
|
||||
.durability, .last-dashboard-update {
|
||||
flex: 1 1 50%;
|
||||
box-sizing: border-box;
|
||||
margin: 20px 0;
|
||||
padding: 0 20px;
|
||||
margin: 1em 0;
|
||||
padding: 0 1em;
|
||||
}
|
||||
|
||||
.durability {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
|
||||
.backups, .uploads {
|
||||
flex: 1 1 100%;
|
||||
flex: 1 1 100%;
|
||||
}
|
||||
|
||||
.uploads p:last-of-type {
|
||||
@ -142,7 +144,7 @@
|
||||
border-left: 1px solid $primary-low;
|
||||
text-align: center;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
justify-content: center;
|
||||
div {
|
||||
align-self: center;
|
||||
h4 {
|
||||
@ -152,7 +154,17 @@
|
||||
}
|
||||
}
|
||||
|
||||
.top-referred-topics, .trending-search {
|
||||
th:first-of-type {
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
|
||||
.top-referred-topics {
|
||||
.dashboard-table table {
|
||||
table-layout: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.community-health {
|
||||
.period-chooser .period-chooser-header {
|
||||
@ -167,7 +179,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.dashboard-mini-chart {
|
||||
.status {
|
||||
display: flex;
|
||||
@ -186,6 +197,10 @@
|
||||
cursor: pointer;
|
||||
margin-left: .25em;
|
||||
color: $primary-low-mid;
|
||||
|
||||
&:hover {
|
||||
color: $primary-medium;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -200,6 +215,10 @@
|
||||
color: $success;
|
||||
}
|
||||
|
||||
&.no-change {
|
||||
color: $primary-medium;
|
||||
}
|
||||
|
||||
.trend-value {
|
||||
font-size: $font-up-1;
|
||||
}
|
||||
@ -217,6 +236,7 @@
|
||||
justify-content: space-between;
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
@include small-width {
|
||||
@ -234,7 +254,7 @@
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.d-icon-question-circle {
|
||||
.tooltip {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
@ -250,30 +270,6 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.high-trending-up, &.trending-up {
|
||||
.chart-trend, .data-point {
|
||||
color: $success;
|
||||
}
|
||||
}
|
||||
|
||||
&.high-trending-down, &.trending-down {
|
||||
.chart-trend, .data-point {
|
||||
color: $danger;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.top-referred-topics, .trending-search {
|
||||
th:first-of-type {
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
|
||||
.top-referred-topics {
|
||||
.dashboard-table table {
|
||||
table-layout: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.dashboard-table {
|
||||
@ -335,6 +331,7 @@
|
||||
text-align: center;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
td.left {
|
||||
text-align: left;
|
||||
}
|
||||
@ -345,9 +342,11 @@
|
||||
|
||||
td.value {
|
||||
text-align: right;
|
||||
transform: translateX(-40%);
|
||||
padding: 8px 21px 8px 8px; // accounting for negative right caret margin
|
||||
&:nth-of-type(2) {
|
||||
padding: 8px 12px 8px;
|
||||
}
|
||||
i {
|
||||
display: none;
|
||||
margin-right: -12px; // align on caret
|
||||
@media screen and (max-width: 650px) {
|
||||
margin-right: -9px;
|
||||
@ -355,25 +354,158 @@
|
||||
}
|
||||
|
||||
&.high-trending-up, &.trending-up {
|
||||
i.up {
|
||||
color: $success;
|
||||
display: inline;
|
||||
}
|
||||
i { color: $success; }
|
||||
}
|
||||
&.high-trending-down, &.trending-down {
|
||||
i.down {
|
||||
color: $danger;
|
||||
display: inline;
|
||||
}
|
||||
}
|
||||
&.no-change {
|
||||
i.down {
|
||||
display: inline;
|
||||
visibility: hidden;
|
||||
}
|
||||
i { color: $danger; }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.user-metrics {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
margin-left: -5%;
|
||||
margin: 2em 0 .75em -5%; // Negative margin allows for a margin when in 2-columns,
|
||||
.dashboard-inline-table { // and "hides" margin when the item spans 100% width
|
||||
flex: 1 0 auto;
|
||||
max-width: 95%;
|
||||
}
|
||||
.table-cell {
|
||||
display: flex;
|
||||
flex: 0 1 auto;
|
||||
margin: 0 10px 5px 0;
|
||||
border: 1px solid $primary-low;
|
||||
border-radius: 10px;
|
||||
.label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: $primary;
|
||||
background: $primary-very-low;
|
||||
justify-content: center;
|
||||
border-radius: 9px 0 0 9px;
|
||||
padding: 0 5px 0 8px;
|
||||
|
||||
.d-icon {
|
||||
margin-right: 5px;
|
||||
font-size: $font-down-1;
|
||||
}
|
||||
}
|
||||
|
||||
.value {
|
||||
padding: 0 8px 0 5px;
|
||||
}
|
||||
&.user-newuser{
|
||||
.label {
|
||||
color: $primary-high;
|
||||
}
|
||||
}
|
||||
&.user-basic , &.user-member {
|
||||
border-color: $bronze;
|
||||
.label {
|
||||
border-color: $bronze;
|
||||
background: $bronze;
|
||||
color: $secondary;
|
||||
}
|
||||
}
|
||||
&.user-regular {
|
||||
border-color: $silver;
|
||||
.label {
|
||||
border-color: $silver;
|
||||
background: $silver;
|
||||
color: $secondary;
|
||||
}
|
||||
}
|
||||
&.user-leader {
|
||||
border-color: $gold;
|
||||
.label {
|
||||
background: $gold;
|
||||
border-color: $gold;
|
||||
color: $secondary;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.dashboard-inline-table {
|
||||
margin-left: 5%;
|
||||
margin-bottom: 1.25em;
|
||||
|
||||
.table-title {
|
||||
border-bottom: 1px solid $primary-low;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.table-container {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
}
|
||||
|
||||
.dashboard-table.activity-metrics {
|
||||
table {
|
||||
@media screen and (min-width: 400px) {
|
||||
table-layout: auto;
|
||||
}
|
||||
tr th {
|
||||
text-align: right;
|
||||
}
|
||||
.d-icon {
|
||||
color: $primary-low-mid;
|
||||
min-width: 14px;
|
||||
text-align: center;
|
||||
}
|
||||
@media screen and (max-width: 400px) {
|
||||
.d-icon { display: none; }
|
||||
td.title {
|
||||
padding: 8px 0 8px 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.rtl .dashboard-next {
|
||||
.section-column {
|
||||
&:last-child, {
|
||||
margin-right: 1em;
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
&:first-child {
|
||||
margin-left: 1em;
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.dashboard-table table tbody tr {
|
||||
td.title {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
td.value i {
|
||||
margin-right: 0;
|
||||
margin-left: -12px;
|
||||
}
|
||||
}
|
||||
|
||||
.user-metrics .table-cell {
|
||||
margin: 0 0 5px 10px;
|
||||
}
|
||||
|
||||
.table-cell {
|
||||
.label {
|
||||
border-radius: 0 9px 9px 0;
|
||||
|
||||
.d-icon {
|
||||
margin-right: 0;
|
||||
margin-left: 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,19 +3,19 @@
|
||||
.category-color-editor {
|
||||
input {
|
||||
width: 70px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.color-title {
|
||||
display: inline-block;
|
||||
width: 130px;
|
||||
width: 120px;
|
||||
}
|
||||
|
||||
.colors-container {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
padding-top: 4px;
|
||||
padding-left: 15px;
|
||||
max-width: 300px;
|
||||
max-width: 240px;
|
||||
|
||||
.colorpicker {
|
||||
border: 1px solid $primary-low;
|
||||
|
||||
@ -13,6 +13,9 @@
|
||||
}
|
||||
|
||||
#login-form {
|
||||
table {
|
||||
width: 100%;
|
||||
}
|
||||
a {
|
||||
color: dark-light-choose($primary-high, $secondary-low);
|
||||
}
|
||||
@ -40,6 +43,12 @@ $input-width: 220px;
|
||||
td {
|
||||
label, input {
|
||||
margin-bottom: 0;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
.modal-footer {
|
||||
.inline-spinner {
|
||||
display: inline-flex;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -56,9 +65,8 @@ $input-width: 220px;
|
||||
}
|
||||
|
||||
.disclaimer {
|
||||
font-size: $font-down-1;
|
||||
color: dark-light-choose($primary-medium, $secondary-medium);
|
||||
clear: both;
|
||||
margin-top: .5em;
|
||||
}
|
||||
|
||||
.user-field.confirm {
|
||||
@ -106,12 +114,16 @@ $input-width: 220px;
|
||||
.invites-show {
|
||||
.two-col {
|
||||
position: relative;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.col-image {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
position: relative;
|
||||
width: 150px;
|
||||
margin-right: 20px;
|
||||
@media screen and (max-width: 600px) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
form {
|
||||
@ -135,6 +147,21 @@ $input-width: 220px;
|
||||
}
|
||||
}
|
||||
|
||||
.auth-message {
|
||||
padding: 0 15px 15px 15px;
|
||||
&:empty {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.modal tr.instructions {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-top: 0.15em;
|
||||
label {
|
||||
color: dark-light-choose($primary-medium, $secondary-medium);
|
||||
}
|
||||
}
|
||||
|
||||
// alternate login / create new account buttons should be de-emphasized
|
||||
|
||||
@ -142,4 +169,4 @@ button#login-link, button#new-account-link
|
||||
{
|
||||
background: transparent;
|
||||
color: dark-light-choose($primary-high, $secondary-low);
|
||||
}
|
||||
}
|
||||
@ -76,8 +76,9 @@
|
||||
float: left;
|
||||
background-color: transparent;
|
||||
display: inline-flex;
|
||||
margin: 0.25em 0.5em;
|
||||
width: 43%;
|
||||
padding: 0.25em 0.5em;
|
||||
width: 50%;
|
||||
box-sizing: border-box;
|
||||
.badge-notification {
|
||||
color: dark-light-choose($primary-medium, $secondary-medium);
|
||||
background-color: transparent;
|
||||
|
||||
@ -31,9 +31,17 @@
|
||||
align-items: center;
|
||||
padding: 10px 15px;
|
||||
border-bottom: 1px solid $primary-low;
|
||||
h3 {
|
||||
margin-bottom: 0;
|
||||
|
||||
.title {
|
||||
h3 {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.modal-close {
|
||||
order: 2;
|
||||
margin-left: auto;
|
||||
@ -80,12 +88,20 @@
|
||||
}
|
||||
|
||||
.modal-inner-container {
|
||||
display: table;
|
||||
width: 100%;
|
||||
min-width: 320px;
|
||||
max-width: 700px;
|
||||
margin: 0 auto;
|
||||
background-color: $secondary;
|
||||
background-clip: padding-box;
|
||||
box-shadow: shadow("modal");
|
||||
|
||||
@media screen and (min-width: 475px) {
|
||||
min-width: 475px;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.select-kit {
|
||||
width: 220px;
|
||||
|
||||
@ -145,6 +161,11 @@
|
||||
height: 10em;
|
||||
}
|
||||
}
|
||||
@media screen and (min-width: 524px) {
|
||||
.modal-inner-container {
|
||||
min-width: 525px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.modal {
|
||||
@ -253,6 +274,11 @@
|
||||
.modal-body.forgot-password-modal p {
|
||||
font-size: $font-0;
|
||||
}
|
||||
|
||||
pre code {
|
||||
white-space: pre-wrap;
|
||||
max-width: 700px;
|
||||
}
|
||||
}
|
||||
|
||||
.reply-where-modal {
|
||||
@ -308,6 +334,7 @@
|
||||
|
||||
label {
|
||||
margin-top: 7px;
|
||||
max-width: 450px;
|
||||
}
|
||||
|
||||
.optional {
|
||||
@ -404,6 +431,8 @@
|
||||
}
|
||||
.incoming-email-content {
|
||||
height: 300px;
|
||||
max-width: 700px;
|
||||
width: 90vw; // forcing textarea wider
|
||||
textarea, .incoming-email-html-part {
|
||||
height: 95%;
|
||||
border: none;
|
||||
@ -503,4 +532,3 @@
|
||||
position: absolute;
|
||||
width: 95%;
|
||||
}
|
||||
|
||||
|
||||
@ -329,7 +329,7 @@ pre.onebox code li{
|
||||
|
||||
pre.onebox code ol.lines{
|
||||
position:relative;
|
||||
margin-left: 40px;
|
||||
margin: 0 0 0 40px;
|
||||
}
|
||||
|
||||
pre.onebox code ol.lines li {
|
||||
|
||||
@ -20,15 +20,15 @@
|
||||
}
|
||||
|
||||
&.badge-type-gold .fa {
|
||||
color: rgb(231, 195, 0) !important;
|
||||
color: $gold !important;
|
||||
}
|
||||
|
||||
&.badge-type-silver .fa {
|
||||
color: #c0c0c0 !important;
|
||||
color: $silver !important;
|
||||
}
|
||||
|
||||
&.badge-type-bronze .fa {
|
||||
color: #cd7f32 !important;
|
||||
color: $bronze !important;
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
|
||||
@ -541,6 +541,11 @@
|
||||
width: 520px;
|
||||
}
|
||||
|
||||
.warning-wrap {
|
||||
height: 30px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.category-notifications .category-controls,
|
||||
.tag-notifications .tag-controls {
|
||||
margin-top: 24px;
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
.conditional-loading-section {
|
||||
|
||||
&.is-loading {
|
||||
padding: 2em;
|
||||
margin: 1em;
|
||||
|
||||
@ -22,6 +22,13 @@ $twitter: #00bced !default;
|
||||
$yahoo: #810293 !default;
|
||||
$github: #6d6d6d !default;
|
||||
|
||||
// Badge color variables
|
||||
// --------------------------------------------------
|
||||
|
||||
$gold: rgb(231, 195, 0) !default;
|
||||
$silver: #c0c0c0 !default;
|
||||
$bronze: #cd7f32 !default;
|
||||
|
||||
// Fonts
|
||||
// --------------------------------------------------
|
||||
|
||||
|
||||
@ -58,6 +58,7 @@
|
||||
.collection-header {
|
||||
max-height: 125px;
|
||||
overflow-y: auto;
|
||||
flex: 1 0 auto;
|
||||
|
||||
.selected-tags {
|
||||
display: flex;
|
||||
|
||||
@ -1,104 +1,177 @@
|
||||
// style that apply to the login popup
|
||||
// shared styles used
|
||||
// on both the login and
|
||||
// create account modals
|
||||
.login-modal,
|
||||
.create-account {
|
||||
#login-buttons:not(.hidden) {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
flex-basis: 40%;
|
||||
align-items: center;
|
||||
min-height: 175px;
|
||||
border-left: 1px solid $primary-low;
|
||||
padding: 15px;
|
||||
order: 2;
|
||||
|
||||
#login-buttons {
|
||||
button {
|
||||
margin: 0 5px 5px 0;
|
||||
min-width: 180px;
|
||||
&:lang(zh_CN) {
|
||||
min-width: 200px;
|
||||
button {
|
||||
margin: 0.35em;
|
||||
width: 160px;
|
||||
&:lang(zh_CN) {
|
||||
min-width: 200px;
|
||||
}
|
||||
}
|
||||
}
|
||||
margin-top: 10px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
// Create account
|
||||
#login-form {
|
||||
flex: 1 0 auto;
|
||||
padding: 0 15px;
|
||||
}
|
||||
|
||||
.create-account {
|
||||
form {
|
||||
tr:not(.instructions) {
|
||||
td {
|
||||
display: flex;
|
||||
padding: 5px 0 0 0;
|
||||
}
|
||||
}
|
||||
|
||||
tr.input label {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
table {
|
||||
width: 100%;
|
||||
}
|
||||
tr.input {
|
||||
td {
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
// styles used on
|
||||
// login modal only
|
||||
.d-modal.login-modal {
|
||||
.modal-body,
|
||||
#credentials {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 15px 0;
|
||||
|
||||
tr {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
input, label {
|
||||
}
|
||||
}
|
||||
|
||||
// styles used on the
|
||||
// create account
|
||||
// modal only
|
||||
.d-modal.create-account {
|
||||
.modal-body {
|
||||
display: flex;
|
||||
padding: 15px 0;
|
||||
}
|
||||
|
||||
.create-account-form tr {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.login-form {
|
||||
display: flex;
|
||||
flex: 1 1 50%;
|
||||
align-items: center;
|
||||
padding: 0 15px;
|
||||
form, table {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
tr {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-top: 0.15em;
|
||||
&.password-confirmation {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tr.input {
|
||||
input,
|
||||
label {
|
||||
margin-bottom: 0;
|
||||
width: 100%;
|
||||
}
|
||||
.tip {
|
||||
max-width: 340px;
|
||||
}
|
||||
}
|
||||
|
||||
.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 {
|
||||
.controls,
|
||||
.input {
|
||||
margin-left: 20px;
|
||||
}
|
||||
input,
|
||||
label {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.user-field .control-label:not(.checkbox-label) {
|
||||
margin-left: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.password-reset,
|
||||
.invites-show {
|
||||
.col-form {
|
||||
padding-left: 20px;
|
||||
}
|
||||
h2 {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.col-image img {
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
}
|
||||
}
|
||||
|
||||
.password-reset {
|
||||
.col-form {
|
||||
padding-top: 40px;
|
||||
}
|
||||
}
|
||||
|
||||
.tos-agree {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.disclaimer {
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.instructions {
|
||||
label {
|
||||
color: dark-light-choose($primary-medium, $secondary-medium);
|
||||
}
|
||||
}
|
||||
|
||||
.user-fields {
|
||||
border-top: 1px solid $primary-low;
|
||||
padding-top: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.password-reset, .invites-show {
|
||||
.col-form {
|
||||
padding-left: 20px;
|
||||
}
|
||||
h2 {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.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 {
|
||||
.login-form {
|
||||
.tip {
|
||||
&:not(:empty) + td {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
form {
|
||||
.controls, .input {
|
||||
margin-left: 20px;
|
||||
}
|
||||
input, label {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.user-field .control-label:not(.checkbox-label) {
|
||||
margin-left: 20px;
|
||||
&:not(:empty),
|
||||
&:empty + td {
|
||||
min-height: 1.75em;
|
||||
width: 240px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -4,10 +4,16 @@
|
||||
padding-left: 10px;
|
||||
}
|
||||
.radios {
|
||||
height: 60px;
|
||||
min-height: 60px;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
label {
|
||||
flex: 1 0 auto;
|
||||
margin-right: 1em;
|
||||
margin-top: 1px;
|
||||
}
|
||||
.inputs {
|
||||
float: right;
|
||||
width: 75%;
|
||||
width: 100%;
|
||||
input {
|
||||
width: 90%;
|
||||
margin: 0 0 5px 0;
|
||||
@ -26,8 +32,8 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
.radios:last-child {
|
||||
height: 20px;
|
||||
.radios:last-child:not(:nth-child(2)) { // last child for composer modal, but not theme import modal
|
||||
min-height: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user