Merge master

This commit is contained in:
Neil Lalonde 2019-06-17 20:04:04 -04:00
commit a4308fdd43
12424 changed files with 146552 additions and 76571 deletions
.eslintignore.eslintrc.gitignore.prettierignore.rspec_parallel.rubocop.yml.ruby-version.sample.travis.ymlBrewfileDangerfileGemfileGemfile.lockProcfileREADME.mdRakefile
app/assets
images
javascripts/admin
adapters
components
controllers

@ -6,9 +6,6 @@ app/assets/javascripts/ember-addons/
app/assets/javascripts/discourse/lib/autosize.js.es6
lib/javascripts/locale/
lib/javascripts/messageformat.js
lib/javascripts/moment.js
lib/javascripts/moment-timezone-with-data.js
lib/javascripts/moment_locale/
lib/highlight_js/
plugins/**/lib/javascripts/locale
public/javascripts/

@ -5,7 +5,7 @@
"es6": true,
"jasmine": true,
"mocha": true,
"node": true,
"node": true
},
"parserOptions": {
"ecmaVersion": 7,
@ -16,12 +16,11 @@
"_": true,
"andThen": true,
"asyncRender": true,
"asyncTestDiscourse": true,
"Blob": true,
"bootbox": true,
"click": true,
"collapseSelectKit": true,
"controllerFor": true,
"waitUntil": true,
"getSettledState": true,
"count": true,
"currentPath": true,
"currentRouteName": true,
@ -30,11 +29,9 @@
"Discourse": true,
"Ember": true,
"exists": true,
"expandSelectKit": true,
"File": true,
"fillIn": true,
"find": true,
"fixture": true,
"Handlebars": true,
"hasModule": true,
"I18n": true,
@ -51,12 +48,6 @@
"requirejs": true,
"RSVP": true,
"sandbox": true,
"selectKit": true,
"selectKitFillInFilter": true,
"selectKitSelectNoneRow": true,
"selectKitSelectRowByIndex": true,
"selectKitSelectRowByName": true,
"selectKitSelectRowByValue": true,
"sinon": true,
"test": true,
"triggerEvent": true,
@ -99,7 +90,7 @@
"semi": 2,
"strict": 0,
"valid-typeof": 2,
"wrap-iife": [2, "inside"],
"wrap-iife": [2, "inside"]
},
"parser": "babel-eslint"
}

3
.gitignore vendored

@ -124,3 +124,6 @@ vendor/bundle/*
/package-lock.json
/vendor/data/GeoLite2-City.mmdb
# Vagrant
.vagrant

@ -2,4 +2,5 @@ app/assets/stylesheets/vendor/
plugins/**/assets/stylesheets/vendor/
package.json
config/locales/**/*.yml
!config/locales/**/*.en.yml
!config/locales/**/*.en*.yml
script/import_scripts/**/*.yml

4
.rspec_parallel Normal file

@ -0,0 +1,4 @@
--format progress
--format ParallelTests::RSpec::RuntimeLogger --out tmp/parallel_runtime_rspec.log
--format ParallelTests::RSpec::SummaryLogger --out tmp/spec_summary.log
--format ParallelTests::RSpec::FailuresLogger --out tmp/failing_specs.log

@ -7,11 +7,15 @@ AllCops:
- 'vendor/**/*'
- 'node_modules/**/*'
- 'public/**/*'
- 'plugins/**/*'
# Prefer &&/|| over and/or.
Style/AndOr:
Enabled: true
Style/FrozenStringLiteralComment:
Enabled: true
# Do not use braces for hash literals when they are the last argument of a
# method call.
Style/BracesAroundHashParameters:

@ -1 +1 @@
2.5.3
2.6.1

@ -34,7 +34,7 @@ matrix:
fast_finish: true
rvm:
- 2.5.3
- 2.6.3
services:
- redis-server
@ -68,14 +68,14 @@ 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 [ '$QUNIT_RUN' == '1' ] || [ '$RUN_LINT' == '1' ]; then yarn install --dev; fi"
- bash -c "if [ '$RUN_LINT' != '1' ]; then LOAD_PLUGINS=1 bundle exec rake db:create db:migrate; fi"
- bash -c "if [ '$RUN_LINT' != '1' ]; then bundle exec rake db:create && LOAD_PLUGINS=1 bundle exec rake db:migrate; fi"
script:
- |
bash -c "
if [ '$RUN_LINT' == '1' ]; then
bundle exec rubocop --parallel && \
bundle exec danger && \
yarn prettier --list-different "app/assets/stylesheets/**/*.scss" "app/assets/javascripts/**/*.es6" "test/javascripts/**/*.es6"
yarn eslint --ext .es6 app/assets/javascripts && \
yarn eslint --ext .es6 test/javascripts && \
yarn eslint --ext .es6 plugins/**/assets/javascripts && \
@ -83,8 +83,8 @@ script:
yarn eslint app/assets/javascripts test/javascripts
else
if [ '$QUNIT_RUN' == '1' ]; then
bundle exec rake qunit:test['500000'] && \
bundle exec rake qunit:test['500000','/wizard/qunit'] && \
bundle exec rake qunit:test['1200000'] && \
bundle exec rake qunit:test['1200000','/wizard/qunit'] && \
bundle exec rake plugin:qunit
else
bundle exec rspec && bundle exec rake plugin:spec

@ -1,3 +1,5 @@
# frozen_string_literal: true
# Install development dependencies on Mac OS X using Homebrew (http://mxcl.github.com/homebrew)
# you probably already have git installed; ensure that it is the latest version

@ -1,3 +1,5 @@
# frozen_string_literal: true
if github.pr_json && (github.pr_json["additions"] || 0) > 250 || (github.pr_json["deletions"] || 0) > 250
warn("This pull request is big! We prefer smaller PRs whenever possible, as they are easier to review. Can this be split into a few smaller PRs?")
end
@ -11,8 +13,8 @@ This PR doesn't match our required code formatting standards, as enforced by pre
})
end
locales_changes = git.modified_files.grep(/config\/locales/)
has_non_en_locales_changes = locales_changes.grep_v(/config\/locales\/(client|server)\.en\.yml/).any?
locales_changes = git.modified_files.grep(%r{config/locales})
has_non_en_locales_changes = locales_changes.grep_v(%r{config/locales/(?:client|server)\.(?:en|en_US)\.yml}).any?
if locales_changes.any? && has_non_en_locales_changes
fail("Please submit your non-English translation updates via [Transifex](https://www.transifex.com/discourse/discourse-org/). You can read more on how to contribute translations [here](https://meta.discourse.org/t/contribute-a-translation-to-discourse/14882).")
@ -22,21 +24,50 @@ files = (git.added_files + git.modified_files)
.select { |path| !path.start_with?("plugins/") }
.select { |path| path.end_with?("es6") || path.end_with?("rb") }
super_offenses = []
js_files = files.select { |path| path.end_with?(".js.es6") }
js_test_files = js_files.select { |path| path.end_with?("-test.js.es6") }
files.each do |path|
super_offenses = []
self_offenses = []
js_files.each do |path|
diff = git.diff_for_file(path)
next if !diff
diff.patch.lines.grep(/^\+\s\s/).each do |added_line|
super_offenses << path if added_line['this._super()']
self_offenses << path if added_line[/(?:(^|\W)self\.?)/]
end
end
jquery_find_offenses = []
js_test_files.each do |path|
diff = git.diff_for_file(path)
next if !diff
diff.patch.lines.grep(/^\+\s\s/).each do |added_line|
jquery_find_offenses << path if added_line['this.$(']
end
end
if !self_offenses.empty?
warn(%{
Use fat arrow instead of self pattern.\n
#{self_offenses.uniq.map { |o| github.html_link(o) }.join("\n")}
})
end
if !super_offenses.empty?
warn(%{
When possible use `this._super(...arguments)` instead of `this._super()`\n
#{super_offenses.uniq.map { |o| github.html_link(o) }.join("\n")}
})
end
if !jquery_find_offenses.empty?
warn(%{
Use `find()` instead of `this.$` in js tests`\n
#{jquery_find_offenses.uniq.map { |o| github.html_link(o) }.join("\n")}
})
end

65
Gemfile

@ -1,3 +1,5 @@
# frozen_string_literal: true
source 'https://rubygems.org'
# if there is a super emergency and rubygems is playing up, try
#source 'http://production.cf.rubygems.org'
@ -11,46 +13,49 @@ end
if rails_master?
gem 'arel', git: 'https://github.com/rails/arel.git'
gem 'rails', git: 'https://github.com/rails/rails.git'
gem 'seed-fu', git: 'https://github.com/SamSaffron/seed-fu.git', branch: 'discourse'
else
# until rubygems gives us optional dependencies we are stuck with this
# bundle update actionmailer actionpack actionview activemodel activerecord activesupport railties
gem 'actionmailer', '5.2.2.1'
gem 'actionpack', '5.2.2.1'
gem 'actionview', '5.2.2.1'
gem 'activemodel', '5.2.2.1'
gem 'activerecord', '5.2.2.1'
gem 'activesupport', '5.2.2.1'
gem 'railties', '5.2.2.1'
gem 'actionmailer', '5.2.3'
gem 'actionpack', '5.2.3'
gem 'actionview', '5.2.3'
gem 'activemodel', '5.2.3'
gem 'activerecord', '5.2.3'
gem 'activesupport', '5.2.3'
gem 'railties', '5.2.3'
gem 'sprockets-rails'
gem 'seed-fu'
end
gem 'mail', '2.7.1.rc1', require: false
gem 'seed-fu'
gem 'mail', require: false
gem 'mini_mime'
gem 'mini_suffix'
gem 'hiredis'
gem 'redis', require: ["redis", "redis/connection/hiredis"]
# holding off redis upgrade temporarily as it is having issues with our current
# freedom patch, we will follow this up.
#
# FrozenError: can't modify frozen Hash
# /var/www/discourse/vendor/bundle/ruby/2.5.0/gems/redis-4.1.0/lib/redis/client.rb:93:in `delete'
# /var/www/discourse/vendor/bundle/ruby/2.5.0/gems/redis-4.1.0/lib/redis/client.rb:93:in `initialize'
# /var/www/discourse/lib/freedom_patches/redis.rb:7:in `initialize'
gem 'redis', '4.0.1', require: ["redis", "redis/connection/hiredis"]
gem 'redis-namespace'
gem 'active_model_serializers', '~> 0.8.3'
gem 'onebox', '1.8.77'
gem 'onebox', '1.8.92'
gem 'http_accept_language', '~>2.0.5', require: false
gem 'ember-rails', '0.18.5'
gem 'discourse-ember-source', '~> 3.5.1'
gem 'discourse-ember-source', '~> 3.8.0'
gem 'ember-handlebars-template', '0.8.0'
gem 'barber'
# message bus 2.2.0 should be very stable
# we trimmed some of the internal API surface down so we went with
# a pre release here to make we don't do a full release prior to
# baking here. Remove 2.2.0.pre no later than Jan 2019 and move back
# to the standard releases
gem 'message_bus', '2.2.0.pre.1'
gem 'message_bus'
gem 'rails_multisite'
@ -62,6 +67,7 @@ gem 'fast_xor', platform: :mri
gem 'fastimage'
gem 'aws-sdk-s3', require: false
gem 'aws-sdk-sns', require: false
gem 'excon', require: false
gem 'unf', require: false
@ -84,6 +90,7 @@ gem 'omniauth-github'
gem 'omniauth-oauth2', require: false
gem 'omniauth-google-oauth2'
gem 'oj'
gem 'pg'
gem 'mini_sql'
@ -92,6 +99,7 @@ gem 'r2', '~> 0.2.5', require: false
gem 'rake'
gem 'thor', require: false
gem 'diffy', require: false
gem 'rinku'
gem 'sanitize'
gem 'sidekiq'
@ -116,7 +124,8 @@ group :test do
gem 'webmock', require: false
gem 'fakeweb', '~> 1.3.0', require: false
gem 'minitest', require: false
gem 'danger'
gem 'simplecov', require: false
gem "test-prof"
end
group :test, :development do
@ -130,11 +139,12 @@ group :test, :development do
gem 'rb-fsevent', require: RUBY_PLATFORM =~ /darwin/i ? 'rb-fsevent' : false
gem 'rb-inotify', '~> 0.9', require: RUBY_PLATFORM =~ /linux/i ? 'rb-inotify' : false
gem 'rspec-rails', require: false
gem 'shoulda', require: false
gem 'shoulda-matchers', '~> 3.1', '>= 3.1.3', require: false
gem 'rspec-html-matchers'
gem 'pry-nav'
gem 'byebug', require: ENV['RM_INFO'].nil?
gem 'rubocop', require: false
gem 'parallel_tests'
end
group :development do
@ -142,8 +152,13 @@ group :development do
gem 'bullet', require: !!ENV['BULLET']
gem 'better_errors'
gem 'binding_of_caller'
gem 'annotate'
gem 'foreman', require: false
# waiting on 2.7.5 per: https://github.com/ctran/annotate_models/pull/595
if rails_master?
gem 'annotate', git: 'https://github.com/ctran/annotate_models.git'
else
gem 'annotate'
end
end
# this is an optional gem, it provides a high performance replacement
@ -182,6 +197,7 @@ gem 'logstash-logger', require: false
gem 'logster'
gem 'sassc', require: false
gem "sassc-rails"
gem 'rotp'
gem 'rqrcode'
@ -193,10 +209,11 @@ gem 'rchardet', require: false
if ENV["IMPORT"] == "1"
gem 'mysql2'
gem 'redcarpet'
gem 'sqlite3', '~> 1.3.13'
gem 'sqlite3', '~> 1.3', '>= 1.3.13'
gem 'ruby-bbcode-to-md', git: 'https://github.com/nlalonde/ruby-bbcode-to-md'
gem 'reverse_markdown'
gem 'tiny_tds'
gem 'csv', '~> 3.0'
end
gem 'webpush', require: false

@ -1,117 +1,104 @@
GEM
remote: https://rubygems.org/
specs:
actionmailer (5.2.2.1)
actionpack (= 5.2.2.1)
actionview (= 5.2.2.1)
activejob (= 5.2.2.1)
actionmailer (5.2.3)
actionpack (= 5.2.3)
actionview (= 5.2.3)
activejob (= 5.2.3)
mail (~> 2.5, >= 2.5.4)
rails-dom-testing (~> 2.0)
actionpack (5.2.2.1)
actionview (= 5.2.2.1)
activesupport (= 5.2.2.1)
actionpack (5.2.3)
actionview (= 5.2.3)
activesupport (= 5.2.3)
rack (~> 2.0)
rack-test (>= 0.6.3)
rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.0, >= 1.0.2)
actionview (5.2.2.1)
activesupport (= 5.2.2.1)
actionview (5.2.3)
activesupport (= 5.2.3)
builder (~> 3.1)
erubi (~> 1.4)
rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.0, >= 1.0.3)
active_model_serializers (0.8.4)
activemodel (>= 3.0)
activejob (5.2.2.1)
activesupport (= 5.2.2.1)
activejob (5.2.3)
activesupport (= 5.2.3)
globalid (>= 0.3.6)
activemodel (5.2.2.1)
activesupport (= 5.2.2.1)
activerecord (5.2.2.1)
activemodel (= 5.2.2.1)
activesupport (= 5.2.2.1)
activemodel (5.2.3)
activesupport (= 5.2.3)
activerecord (5.2.3)
activemodel (= 5.2.3)
activesupport (= 5.2.3)
arel (>= 9.0)
activesupport (5.2.2.1)
activesupport (5.2.3)
concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 0.7, < 2)
minitest (~> 5.1)
tzinfo (~> 1.1)
addressable (2.5.2)
public_suffix (>= 2.0.2, < 4.0)
annotate (2.7.4)
activerecord (>= 3.2, < 6.0)
annotate (2.7.5)
activerecord (>= 3.2, < 7.0)
rake (>= 10.4, < 13.0)
arel (9.0.0)
ast (2.4.0)
aws-eventstream (1.0.1)
aws-partitions (1.104.0)
aws-sdk-core (3.27.0)
aws-eventstream (~> 1.0)
aws-eventstream (1.0.3)
aws-partitions (1.154.0)
aws-sdk-core (3.48.6)
aws-eventstream (~> 1.0, >= 1.0.2)
aws-partitions (~> 1.0)
aws-sigv4 (~> 1.0)
aws-sigv4 (~> 1.1)
jmespath (~> 1.0)
aws-sdk-kms (1.9.0)
aws-sdk-core (~> 3, >= 3.26.0)
aws-sigv4 (~> 1.0)
aws-sdk-s3 (1.19.0)
aws-sdk-core (~> 3, >= 3.26.0)
aws-sdk-kms (1.17.0)
aws-sdk-core (~> 3, >= 3.48.2)
aws-sigv4 (~> 1.1)
aws-sdk-s3 (1.36.1)
aws-sdk-core (~> 3, >= 3.48.2)
aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.0)
aws-sigv4 (1.0.3)
barber (0.12.0)
aws-sdk-sns (1.13.0)
aws-sdk-core (~> 3, >= 3.48.2)
aws-sigv4 (~> 1.1)
aws-sigv4 (1.1.0)
aws-eventstream (~> 1.0, >= 1.0.2)
barber (0.12.2)
ember-source (>= 1.0, < 3.1)
execjs (>= 1.2, < 3)
better_errors (2.4.0)
better_errors (2.5.1)
coderay (>= 1.0.0)
erubi (>= 1.0.0)
rack (>= 0.9.0)
binding_of_caller (0.8.0)
debug_inspector (>= 0.0.1)
bootsnap (1.3.0)
bootsnap (1.4.4)
msgpack (~> 1.0)
builder (3.2.3)
bullet (5.7.5)
bullet (6.0.0)
activesupport (>= 3.0.0)
uniform_notifier (~> 1.11.0)
byebug (10.0.2)
uniform_notifier (~> 1.11)
byebug (11.0.1)
certified (1.0.0)
chunky_png (1.3.10)
claide (1.0.2)
claide-plugins (0.9.2)
cork
nap
open4 (~> 1.3)
chunky_png (1.3.11)
coderay (1.1.2)
colored2 (3.1.2)
concurrent-ruby (1.1.4)
concurrent-ruby (1.1.5)
connection_pool (2.2.2)
cork (0.3.0)
colored2 (~> 3.1)
cppjieba_rb (0.3.0)
cppjieba_rb (0.3.3)
crack (0.4.3)
safe_yaml (~> 1.0.0)
crass (1.0.4)
danger (5.11.1)
claide (~> 1.0)
claide-plugins (>= 0.9.2)
colored2 (~> 3.1)
cork (~> 0.1)
faraday (~> 0.9)
faraday-http-cache (~> 1.0)
git (~> 1.5)
kramdown (~> 1.5)
no_proxy_fix
octokit (~> 4.7)
terminal-table (~> 1)
debug_inspector (0.0.3)
diff-lcs (1.3)
discourse-ember-source (3.5.1.3)
diffy (3.3.0)
discourse-ember-source (3.8.0.1)
discourse_image_optim (0.26.2)
exifr (~> 1.2, >= 1.2.2)
fspath (~> 3.0)
image_size (~> 1.5)
in_threads (~> 1.3)
progress (~> 3.0, >= 3.0.1)
docile (1.3.1)
email_reply_trimmer (0.1.12)
ember-data-source (3.0.2)
ember-source (>= 2, < 3.0)
@ -126,58 +113,53 @@ GEM
jquery-rails (>= 1.0.17)
railties (>= 3.1)
ember-source (2.18.2)
erubi (1.7.1)
excon (0.62.0)
erubi (1.8.0)
excon (0.64.0)
execjs (2.7.0)
exifr (1.3.4)
exifr (1.3.6)
fabrication (2.20.1)
fakeweb (1.3.0)
faraday (0.15.4)
multipart-post (>= 1.2, < 3)
faraday-http-cache (1.3.1)
faraday (~> 0.8)
fast_blank (1.0.0)
fast_xor (1.1.3)
rake
rake-compiler
fast_xs (0.8.0)
fastimage (2.1.3)
ffi (1.9.25)
fastimage (2.1.5)
ffi (1.10.0)
flamegraph (0.9.5)
foreman (0.85.0)
thor (~> 0.19.1)
fspath (3.1.0)
gc_tracer (1.5.1)
git (1.5.0)
globalid (0.4.1)
globalid (0.4.2)
activesupport (>= 4.2.0)
guess_html_encoding (0.0.11)
hashdiff (0.3.7)
hashdiff (0.3.9)
hashie (3.6.0)
highline (1.7.10)
hiredis (0.6.1)
hiredis (0.6.3)
hkdf (0.3.0)
htmlentities (4.3.4)
http_accept_language (2.0.5)
i18n (1.1.1)
i18n (1.6.0)
concurrent-ruby (~> 1.0)
image_size (1.5.0)
in_threads (1.5.0)
jaro_winkler (1.5.1)
in_threads (1.5.1)
jaro_winkler (1.5.2)
jmespath (1.4.0)
jquery-rails (4.3.3)
rails-dom-testing (>= 1, < 3)
railties (>= 4.2.0)
thor (>= 0.14, < 2.0)
jwt (2.1.0)
json (2.2.0)
jwt (2.2.1)
kgio (2.11.2)
kramdown (1.17.0)
libv8 (7.3.492.27.1)
listen (3.1.5)
rb-fsevent (~> 0.9, >= 0.9.4)
rb-inotify (~> 0.9, >= 0.9.7)
ruby_dep (~> 1.2)
lograge (0.10.0)
lograge (0.11.0)
actionpack (>= 4)
activesupport (>= 4)
railties (>= 4)
@ -185,44 +167,42 @@ GEM
logstash-event (1.2.02)
logstash-logger (0.26.1)
logstash-event (~> 1.2)
logster (2.0.1)
logster (2.3.0)
loofah (2.2.3)
crass (~> 1.0.2)
nokogiri (>= 1.5.9)
lru_redux (1.1.0)
mail (2.7.1.rc1)
mail (2.7.1)
mini_mime (>= 0.1.1)
maxminddb (0.1.21)
memory_profiler (0.9.12)
message_bus (2.2.0.pre.1)
maxminddb (0.1.22)
memory_profiler (0.9.13)
message_bus (2.2.0)
rack (>= 1.1.3)
metaclass (0.0.4)
method_source (0.8.2)
method_source (0.9.2)
mini_mime (1.0.1)
mini_portile2 (2.4.0)
mini_racer (0.2.5)
mini_racer (0.2.6)
libv8 (>= 6.9.411)
mini_scheduler (0.9.1)
mini_scheduler (0.9.2)
sidekiq
mini_sql (0.1.10)
mini_sql (0.2.2)
mini_suffix (0.3.0)
ffi (~> 1.9)
minitest (5.11.3)
mocha (1.5.0)
mocha (1.8.0)
metaclass (~> 0.0.1)
mock_redis (0.18.0)
moneta (1.0.0)
msgpack (1.2.4)
mock_redis (0.19.0)
moneta (1.1.1)
msgpack (1.2.10)
multi_json (1.13.1)
multi_xml (0.6.0)
multipart-post (2.0.0)
mustache (1.0.5)
nap (1.1.0)
no_proxy_fix (0.1.2)
multipart-post (2.1.1)
mustache (1.1.0)
nokogiri (1.10.3)
mini_portile2 (~> 2.4.0)
nokogumbo (1.5.0)
nokogiri
nokogumbo (2.0.1)
nokogiri (~> 1.8, >= 1.8.4)
oauth (0.5.4)
oauth2 (1.4.1)
faraday (>= 0.8, < 0.16.0)
@ -230,9 +210,7 @@ GEM
multi_json (~> 1.3)
multi_xml (~> 0.5)
rack (>= 1.2, < 3)
octokit (4.13.0)
sawyer (~> 0.8.0, >= 0.5.3)
oj (3.6.2)
oj (3.7.12)
omniauth (1.9.0)
hashie (>= 3.4.6, < 3.7.0)
rack (>= 1.6.2, < 3)
@ -241,7 +219,7 @@ GEM
omniauth-github (1.3.0)
omniauth (~> 1.5)
omniauth-oauth2 (>= 1.4.0, < 2.0)
omniauth-google-oauth2 (0.6.0)
omniauth-google-oauth2 (0.7.0)
jwt (>= 2.0)
omniauth (>= 1.1.1)
omniauth-oauth2 (>= 1.5)
@ -260,41 +238,41 @@ GEM
omniauth-twitter (1.4.0)
omniauth-oauth (~> 1.1)
rack
onebox (1.8.77)
onebox (1.8.92)
htmlentities (~> 4.3)
moneta (~> 1.0)
multi_json (~> 1.11)
mustache
nokogiri (~> 1.7)
sanitize
open4 (1.3.4)
openid-redis-store (0.0.2)
redis
ruby-openid
parallel (1.12.1)
parser (2.5.3.0)
optimist (3.0.0)
parallel (1.17.0)
parallel_tests (2.28.0)
parallel
parser (2.6.3.0)
ast (~> 2.4.0)
pg (1.1.3)
powerpack (0.1.2)
progress (3.4.0)
pry (0.10.4)
pg (1.1.4)
progress (3.5.0)
pry (0.12.2)
coderay (~> 1.1.0)
method_source (~> 0.8.1)
slop (~> 3.4)
pry-nav (0.2.4)
pry (>= 0.9.10, < 0.11.0)
pry-rails (0.3.6)
method_source (~> 0.9.0)
pry-nav (0.3.0)
pry (>= 0.9.10, < 0.13.0)
pry-rails (0.3.9)
pry (>= 0.10.4)
public_suffix (3.0.3)
puma (3.11.4)
puma (3.12.1)
r2 (0.2.7)
rack (2.0.6)
rack-mini-profiler (1.0.1)
rack (2.0.7)
rack-mini-profiler (1.0.2)
rack (>= 1.2.0)
rack-openid (1.3.1)
rack (>= 1.1.0)
ruby-openid (>= 2.1.8)
rack-protection (2.0.3)
rack-protection (2.0.5)
rack
rack-test (1.1.0)
rack (>= 1.0, < 3)
@ -303,70 +281,69 @@ GEM
nokogiri (>= 1.6)
rails-html-sanitizer (1.0.4)
loofah (~> 2.2, >= 2.2.2)
rails_multisite (2.0.6)
activerecord (> 4.2, < 6)
railties (> 4.2, < 6)
railties (5.2.2.1)
actionpack (= 5.2.2.1)
activesupport (= 5.2.2.1)
rails_multisite (2.0.7)
activerecord (> 4.2, < 7)
railties (> 4.2, < 7)
railties (5.2.3)
actionpack (= 5.2.3)
activesupport (= 5.2.3)
method_source
rake (>= 0.8.7)
thor (>= 0.19.0, < 2.0)
rainbow (3.0.0)
raindrops (0.19.0)
rake (12.3.2)
rake-compiler (1.0.4)
rake-compiler (1.0.7)
rake
rb-fsevent (0.10.3)
rb-inotify (0.9.10)
ffi (>= 0.5.0, < 2)
rbtrace (0.4.10)
rb-inotify (0.10.0)
ffi (~> 1.0)
rbtrace (0.4.11)
ffi (>= 1.0.6)
msgpack (>= 0.4.3)
trollop (>= 1.16.2)
optimist (>= 3.0.0)
rchardet (1.8.0)
redis (4.0.1)
redis-namespace (1.6.0)
redis (>= 3.0.4)
request_store (1.4.1)
rack (>= 1.4)
rinku (2.0.4)
rinku (2.0.6)
rotp (3.3.1)
rqrcode (0.10.1)
chunky_png (~> 1.0)
rspec (3.7.0)
rspec-core (~> 3.7.0)
rspec-expectations (~> 3.7.0)
rspec-mocks (~> 3.7.0)
rspec-core (3.7.1)
rspec-support (~> 3.7.0)
rspec-expectations (3.7.0)
rspec (3.8.0)
rspec-core (~> 3.8.0)
rspec-expectations (~> 3.8.0)
rspec-mocks (~> 3.8.0)
rspec-core (3.8.0)
rspec-support (~> 3.8.0)
rspec-expectations (3.8.3)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.7.0)
rspec-support (~> 3.8.0)
rspec-html-matchers (0.9.1)
nokogiri (~> 1)
rspec (>= 3.0.0.a, < 4)
rspec-mocks (3.7.0)
rspec-mocks (3.8.0)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.7.0)
rspec-rails (3.7.2)
rspec-support (~> 3.8.0)
rspec-rails (3.8.2)
actionpack (>= 3.0)
activesupport (>= 3.0)
railties (>= 3.0)
rspec-core (~> 3.7.0)
rspec-expectations (~> 3.7.0)
rspec-mocks (~> 3.7.0)
rspec-support (~> 3.7.0)
rspec-support (3.7.1)
rspec-core (~> 3.8.0)
rspec-expectations (~> 3.8.0)
rspec-mocks (~> 3.8.0)
rspec-support (~> 3.8.0)
rspec-support (3.8.0)
rtlit (0.0.5)
rubocop (0.61.1)
rubocop (0.69.0)
jaro_winkler (~> 1.5.1)
parallel (~> 1.10)
parser (>= 2.5, != 2.5.1.1)
powerpack (~> 0.1)
parser (>= 2.6)
rainbow (>= 2.2.2, < 4.0)
ruby-progressbar (~> 1.7)
unicode-display_width (~> 1.4.0)
unicode-display_width (>= 1.4.0, < 1.7)
ruby-openid (2.7.0)
ruby-prof (0.17.0)
ruby-progressbar (1.10.0)
@ -374,38 +351,35 @@ GEM
guess_html_encoding (>= 0.0.4)
nokogiri (>= 1.6.0)
ruby_dep (1.5.0)
safe_yaml (1.0.4)
sanitize (4.6.5)
safe_yaml (1.0.5)
sanitize (5.0.0)
crass (~> 1.0.2)
nokogiri (>= 1.4.4)
nokogumbo (~> 1.4)
sass (3.5.6)
sass-listen (~> 4.0.0)
sass-listen (4.0.0)
rb-fsevent (~> 0.9, >= 0.9.4)
rb-inotify (~> 0.9, >= 0.9.7)
sassc (1.11.4)
bundler
ffi (~> 1.9.6)
sass (>= 3.3.0)
sawyer (0.8.1)
addressable (>= 2.3.5, < 2.6)
faraday (~> 0.8, < 1.0)
nokogiri (>= 1.8.0)
nokogumbo (~> 2.0)
sassc (2.0.1)
ffi (~> 1.9)
rake
sassc-rails (2.1.1)
railties (>= 4.0.0)
sassc (>= 2.0)
sprockets (> 3.0)
sprockets-rails
tilt
seed-fu (2.3.9)
activerecord (>= 3.1)
activesupport (>= 3.1)
shoulda (3.5.0)
shoulda-context (~> 1.0, >= 1.0.1)
shoulda-matchers (>= 1.4.1, < 3.0)
shoulda-context (1.2.2)
shoulda-matchers (2.8.0)
activesupport (>= 3.0.0)
sidekiq (5.1.3)
concurrent-ruby (~> 1.0)
connection_pool (~> 2.2, >= 2.2.0)
shoulda-matchers (3.1.3)
activesupport (>= 4.0.0)
sidekiq (5.2.7)
connection_pool (~> 2.2, >= 2.2.2)
rack (>= 1.5.0)
rack-protection (>= 1.5.0)
redis (>= 3.3.5, < 5)
slop (3.6.0)
simplecov (0.16.1)
docile (~> 1.1)
json (>= 1.8, < 3)
simplecov-html (~> 0.10.0)
simplecov-html (0.10.2)
sprockets (3.7.2)
concurrent-ruby (~> 1.0)
rack (> 1, < 3)
@ -413,31 +387,29 @@ GEM
actionpack (>= 4.0)
activesupport (>= 4.0)
sprockets (>= 3.0.0)
sshkey (1.9.0)
stackprof (0.2.11)
terminal-table (1.8.0)
unicode-display_width (~> 1.1, >= 1.1.1)
thor (0.19.4)
sshkey (2.0.0)
stackprof (0.2.12)
test-prof (0.9.0)
thor (0.20.3)
thread_safe (0.3.6)
tilt (2.0.8)
trollop (2.1.2)
tilt (2.0.9)
tzinfo (1.2.5)
thread_safe (~> 0.1)
uglifier (4.1.11)
uglifier (4.1.20)
execjs (>= 0.3.0, < 3)
unf (0.1.4)
unf_ext
unf_ext (0.0.7.5)
unicode-display_width (1.4.1)
unicorn (5.4.0)
unf_ext (0.0.7.6)
unicode-display_width (1.6.0)
unicorn (5.5.1)
kgio (~> 2.6)
raindrops (~> 0.7)
uniform_notifier (1.11.0)
webmock (3.4.2)
uniform_notifier (1.12.1)
webmock (3.5.1)
addressable (>= 2.3.6)
crack (>= 0.3.2)
hashdiff
webpush (0.3.6)
webpush (0.3.8)
hkdf (~> 0.2)
jwt (~> 2.0)
@ -445,15 +417,16 @@ PLATFORMS
ruby
DEPENDENCIES
actionmailer (= 5.2.2.1)
actionpack (= 5.2.2.1)
actionview (= 5.2.2.1)
actionmailer (= 5.2.3)
actionpack (= 5.2.3)
actionview (= 5.2.3)
active_model_serializers (~> 0.8.3)
activemodel (= 5.2.2.1)
activerecord (= 5.2.2.1)
activesupport (= 5.2.2.1)
activemodel (= 5.2.3)
activerecord (= 5.2.3)
activesupport (= 5.2.3)
annotate
aws-sdk-s3
aws-sdk-sns
barber
better_errors
binding_of_caller
@ -463,8 +436,8 @@ DEPENDENCIES
certified
colored2
cppjieba_rb
danger
discourse-ember-source (~> 3.5.1)
diffy
discourse-ember-source (~> 3.8.0)
discourse_image_optim
email_reply_trimmer (~> 0.1)
ember-handlebars-template (= 0.8.0)
@ -478,7 +451,6 @@ DEPENDENCIES
fast_xs
fastimage
flamegraph
foreman
gc_tracer
highline (~> 1.7.0)
hiredis
@ -490,10 +462,10 @@ DEPENDENCIES
logstash-logger
logster
lru_redux
mail (= 2.7.1.rc1)
mail
maxminddb
memory_profiler
message_bus (= 2.2.0.pre.1)
message_bus
mini_mime
mini_racer
mini_scheduler
@ -514,8 +486,9 @@ DEPENDENCIES
omniauth-oauth2
omniauth-openid
omniauth-twitter
onebox (= 1.8.77)
onebox (= 1.8.92)
openid-redis-store
parallel_tests
pg
pry-nav
pry-rails
@ -524,13 +497,13 @@ DEPENDENCIES
rack-mini-profiler
rack-protection
rails_multisite
railties (= 5.2.2.1)
railties (= 5.2.3)
rake
rb-fsevent
rb-inotify (~> 0.9)
rbtrace
rchardet
redis
redis (= 4.0.1)
redis-namespace
rinku
rotp
@ -544,12 +517,15 @@ DEPENDENCIES
ruby-readability
sanitize
sassc
sassc-rails
seed-fu
shoulda
shoulda-matchers (~> 3.1, >= 3.1.3)
sidekiq
simplecov
sprockets-rails
sshkey
stackprof
test-prof
thor
tilt
uglifier

@ -1,2 +0,0 @@
web: bundle exec rails server -p $PORT
worker: bundle exec sidekiq -e $RAILS_ENV

@ -1,4 +1,8 @@
<a href="http://www.discourse.org/">![Logo](images/discourse.png)</a>
<a href="https://www.discourse.org/"><img src=
"https://user-images.githubusercontent.com/1681963/52239617-e2683480-289c-11e9-922b-5da55472e5b4.png"
width="300px"></a>
Discourse is the 100% open source discussion platform built for the next decade of the Internet. Use it as a:
@ -6,17 +10,17 @@ Discourse is the 100% open source discussion platform built for the next decade
- discussion forum
- long-form chat room
To learn more about the philosophy and goals of the project, [visit **discourse.org**](http://www.discourse.org).
To learn more about the philosophy and goals of the project, [visit **discourse.org**](https://www.discourse.org).
## Screenshots
<a href="https://bbs.boingboing.net"><img alt="Boing Boing" src="https://cloud.githubusercontent.com/assets/1385470/25397876/3fe6cdac-29c0-11e7-8a41-9d0c0279f5a3.png" width="720px"></a>
<a href="https://twittercommunity.com/"><img src="https://cloud.githubusercontent.com/assets/1385470/25397920/71b24e4c-29c0-11e7-8bcf-7a47b888412e.png" width="720px"></a>
<a href="http://discuss.howtogeek.com"><img src="https://cloud.githubusercontent.com/assets/1385470/25398049/f0995962-29c0-11e7-99d7-a3b9c4f0b357.png" width="720px"></a>
<a href="https://talk.turtlerockstudios.com/"><img src="https://cloud.githubusercontent.com/assets/1385470/25398115/2d560d96-29c1-11e7-9a96-b0134a4fedff.png" width="720px"></a>
<a href="https://bbs.boingboing.net"><img alt="Boing Boing" src="https://user-images.githubusercontent.com/1681963/52239245-04ad8280-289c-11e9-9c88-8c173d4a0422.png" width="720px"></a>
<a href="https://twittercommunity.com/"><img src="https://user-images.githubusercontent.com/1681963/52239250-04ad8280-289c-11e9-9e42-574f6eaab9d7.png" width="720px"></a>
<a href="https://discuss.howtogeek.com"><img src="https://user-images.githubusercontent.com/1681963/52239247-04ad8280-289c-11e9-9706-fd66bc0749dc.png" width="720px"></a>
<a href="https://talk.turtlerockstudios.com/"><img src="https://user-images.githubusercontent.com/1681963/52239249-04ad8280-289c-11e9-9155-f0ccc5decc50.png" width="720px"></a>
<img src="https://www.discourse.org/a/img/about/mobile-devices-2x.jpg" alt="Mobile" width="414">
<img src="https://user-images.githubusercontent.com/1681963/52239118-b304f800-289b-11e9-9904-16450680d9ec.jpg" alt="Mobile" width="414">
Browse [lots more notable Discourse instances](https://www.discourse.org/customers).
@ -30,7 +34,7 @@ To get your environment setup, follow the community setup guide for your operati
If you're familiar with how Rails works and are comfortable setting up your own environment, you can also try out the [**Discourse Advanced Developer Guide**](docs/DEVELOPER-ADVANCED.md), which is aimed primarily at Ubuntu and macOS environments.
Before you get started, ensure you have the following minimum versions: [Ruby 2.5+](http://www.ruby-lang.org/en/downloads/), [PostgreSQL 10+](http://www.postgresql.org/download/), [Redis 2.6+](http://redis.io/download). If you're having trouble, please see our [**TROUBLESHOOTING GUIDE**](docs/TROUBLESHOOTING.md) first!
Before you get started, ensure you have the following minimum versions: [Ruby 2.5+](https://www.ruby-lang.org/en/downloads/), [PostgreSQL 10+](https://www.postgresql.org/download/), [Redis 2.6+](https://redis.io/download). If you're having trouble, please see our [**TROUBLESHOOTING GUIDE**](docs/TROUBLESHOOTING.md) first!
## Setting up Discourse
@ -44,17 +48,17 @@ Discourse is built for the *next* 10 years of the Internet, so our requirements
| Browsers | Tablets | Phones |
| --------------------- | ------------ | ------------ |
| Safari 6.1+ | iPad 3+ | iOS 8+ |
| Google Chrome 32+ | Android 4.3+ | Android 4.3+ |
| Safari 10+ | iPad 4+ | iOS 10+ |
| Google Chrome 57+ | Android 4.4+ | Android 4.4+ |
| Internet Explorer 11+ | | |
| Firefox 27+ | | |
| Firefox 52+ | | |
## Built With
- [Ruby on Rails](https://github.com/rails/rails) &mdash; Our back end API is a Rails app. It responds to requests RESTfully in JSON.
- [Ember.js](https://github.com/emberjs/ember.js) &mdash; Our front end is an Ember.js app that communicates with the Rails API.
- [PostgreSQL](http://www.postgresql.org/) &mdash; Our main data store is in Postgres.
- [Redis](http://redis.io/) &mdash; We use Redis as a cache and for transient data.
- [PostgreSQL](https://www.postgresql.org/) &mdash; Our main data store is in Postgres.
- [Redis](https://redis.io/) &mdash; We use Redis as a cache and for transient data.
Plus *lots* of Ruby Gems, a complete list of which is at [/master/Gemfile](https://github.com/discourse/discourse/blob/master/Gemfile).
@ -67,11 +71,11 @@ accepts contributions from the public &ndash; including you!
Before contributing to Discourse:
1. Please read the complete mission statements on [**discourse.org**](http://www.discourse.org). Yes we actually believe this stuff; you should too.
2. Read and sign the [**Electronic Discourse Forums Contribution License Agreement**](http://discourse.org/cla).
1. Please read the complete mission statements on [**discourse.org**](https://www.discourse.org). Yes we actually believe this stuff; you should too.
2. Read and sign the [**Electronic Discourse Forums Contribution License Agreement**](https://www.discourse.org/cla).
3. Dig into [**CONTRIBUTING.MD**](CONTRIBUTING.md), which covers submitting bugs, requesting new features, preparing your code for a pull request, etc.
4. Always strive to collaborate [with mutual respect](https://github.com/discourse/discourse/blob/master/docs/code-of-conduct.md).
5. Not sure what to work on? [**We've got some ideas.**](http://meta.discourse.org/t/so-you-want-to-help-out-with-discourse/3823)
5. Not sure what to work on? [**We've got some ideas.**](https://meta.discourse.org/t/so-you-want-to-help-out-with-discourse/3823)
We look forward to seeing your pull requests!
@ -82,7 +86,7 @@ We take security very seriously at Discourse; all our code is 100% open source a
## The Discourse Team
The original Discourse code contributors can be found in [**AUTHORS.MD**](docs/AUTHORS.md). For a complete list of the many individuals that contributed to the design and implementation of Discourse, please refer to [the official Discourse blog](http://blog.discourse.org/2013/02/the-discourse-team/) and [GitHub's list of contributors](https://github.com/discourse/discourse/contributors).
The original Discourse code contributors can be found in [**AUTHORS.MD**](docs/AUTHORS.md). For a complete list of the many individuals that contributed to the design and implementation of Discourse, please refer to [the official Discourse blog](https://blog.discourse.org/2013/02/the-discourse-team/) and [GitHub's list of contributors](https://github.com/discourse/discourse/contributors).
## Copyright / License
@ -93,7 +97,7 @@ Licensed under the GNU General Public License Version 2.0 (or later);
you may not use this work except in compliance with the License.
You may obtain a copy of the License in the LICENSE file, or at:
http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt
https://www.gnu.org/licenses/old-licenses/gpl-2.0.txt
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
@ -105,4 +109,4 @@ Discourse logo and “Discourse Forum” ®, Civilized Discourse Construction Ki
## Dedication
Discourse is built with [love, Internet style.](http://www.youtube.com/watch?v=Xe1TZaElTAs)
Discourse is built with [love, Internet style.](https://www.youtube.com/watch?v=Xe1TZaElTAs)

@ -1,4 +1,6 @@
#!/usr/bin/env rake
# frozen_string_literal: true
# Add your own tasks in files placed in lib/tasks ending in .rake,
# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.

Binary file not shown.

Before

(image error) Size: 4.2 KiB

After

(image error) Size: 3.7 KiB

Binary file not shown.

Before

(image error) Size: 1.6 KiB

After

(image error) Size: 1.4 KiB

Binary file not shown.

Before

(image error) Size: 919 B

After

(image error) Size: 882 B

Binary file not shown.

Before

(image error) Size: 1.9 KiB

After

(image error) Size: 1.8 KiB

Binary file not shown.

Before

(image error) Size: 1.0 KiB

After

(image error) Size: 1.0 KiB

Binary file not shown.

Before

(image error) Size: 1021 B

After

(image error) Size: 984 B

Binary file not shown.

Before

(image error) Size: 932 B

After

(image error) Size: 895 B

Binary file not shown.

Before

(image error) Size: 4.1 KiB

After

(image error) Size: 3.5 KiB

Binary file not shown.

Before

(image error) Size: 5.8 KiB

After

(image error) Size: 4.9 KiB

Binary file not shown.

Before

(image error) Size: 1.6 KiB

After

(image error) Size: 1.4 KiB

Binary file not shown.

Before

(image error) Size: 7.8 KiB

After

(image error) Size: 4.5 KiB

Binary file not shown.

Before

(image error) Size: 3.6 KiB

After

(image error) Size: 3.1 KiB

Binary file not shown.

Before

(image error) Size: 3.1 KiB

After

(image error) Size: 2.8 KiB

Binary file not shown.

Before

(image error) Size: 3.6 KiB

After

(image error) Size: 3.1 KiB

Binary file not shown.

Before

(image error) Size: 1.9 KiB

After

(image error) Size: 1.8 KiB

Binary file not shown.

Before

(image error) Size: 1.9 KiB

After

(image error) Size: 1.7 KiB

Binary file not shown.

Before

(image error) Size: 1.6 KiB

After

(image error) Size: 1.6 KiB

Binary file not shown.

Before

(image error) Size: 1.9 KiB

After

(image error) Size: 1.8 KiB

@ -1,38 +0,0 @@
import RestAdapter from "discourse/adapters/rest";
export default RestAdapter.extend({
pathFor(store, type, findArgs) {
let args = _.merge({ rest_api: true }, findArgs);
delete args.filter;
return `/admin/flags/${findArgs.filter}.json?${$.param(args)}`;
},
afterFindAll(results, helper) {
results.forEach(flag => {
let conversations = [];
flag.post_actions.forEach(pa => {
if (pa.conversation) {
let conversation = {
permalink: pa.permalink,
hasMore: pa.conversation.has_more,
response: {
excerpt: pa.conversation.response.excerpt,
user: helper.lookup("user", pa.conversation.response.user_id)
}
};
if (pa.conversation.reply) {
conversation.reply = {
excerpt: pa.conversation.reply.excerpt,
user: helper.lookup("user", pa.conversation.reply.user_id)
};
}
conversations.push(conversation);
}
});
flag.set("conversations", conversations);
});
return results;
}
});

@ -10,7 +10,7 @@ export default Ember.Component.extend({
@observes("editorId")
editorIdChanged() {
if (this.get("autofocus")) {
if (this.autofocus) {
this.send("focus");
}
},
@ -18,14 +18,14 @@ export default Ember.Component.extend({
@observes("content")
contentChanged() {
if (this._editor && !this._skipContentChangeEvent) {
this._editor.getSession().setValue(this.get("content"));
this._editor.getSession().setValue(this.content);
}
},
@observes("mode")
modeChanged() {
if (this._editor && !this._skipContentChangeEvent) {
this._editor.getSession().setMode("ace/mode/" + this.get("mode"));
this._editor.getSession().setMode("ace/mode/" + this.mode);
}
},
@ -37,7 +37,7 @@ export default Ember.Component.extend({
changeDisabledState() {
const editor = this._editor;
if (editor) {
const disabled = this.get("disabled");
const disabled = this.disabled;
editor.setOptions({
readOnly: disabled,
highlightActiveLine: !disabled,
@ -79,7 +79,7 @@ export default Ember.Component.extend({
editor.setTheme("ace/theme/chrome");
editor.setShowPrintMargin(false);
editor.setOptions({ fontSize: "14px" });
editor.getSession().setMode("ace/mode/" + this.get("mode"));
editor.getSession().setMode("ace/mode/" + this.mode);
editor.on("change", () => {
this._skipContentChangeEvent = true;
this.set("content", editor.getSession().getValue());
@ -100,10 +100,10 @@ export default Ember.Component.extend({
if (this.appEvents) {
// xxx: don't run during qunit tests
this.appEvents.on("ace:resize", () => this.resize());
this.appEvents.on("ace:resize", this, "resize");
}
if (this.get("autofocus")) {
if (this.autofocus) {
this.send("focus");
}
});

@ -25,7 +25,7 @@ export default Ember.Component.extend(
@on("init")
@observes("logs.[]")
_resetFormattedLogs() {
if (this.get("logs").length === 0) {
if (this.logs.length === 0) {
this._reset(); // reset the cached logs whenever the model is reset
this.rerenderBuffer();
}
@ -34,12 +34,12 @@ export default Ember.Component.extend(
@on("init")
@observes("logs.[]")
_updateFormattedLogs: debounce(function() {
const logs = this.get("logs");
const logs = this.logs;
if (logs.length === 0) return;
// do the log formatting only once for HELLish performance
let formattedLogs = this.get("formattedLogs");
for (let i = this.get("index"), length = logs.length; i < length; i++) {
let formattedLogs = this.formattedLogs;
for (let i = this.index, length = logs.length; i < length; i++) {
const date = logs[i].get("timestamp"),
message = escapeExpression(logs[i].get("message"));
formattedLogs += "[" + date + "] " + message + "\n";
@ -56,7 +56,7 @@ export default Ember.Component.extend(
}, 150),
buildBuffer(buffer) {
const formattedLogs = this.get("formattedLogs");
const formattedLogs = this.formattedLogs;
if (formattedLogs && formattedLogs.length > 0) {
buffer.push("<pre>");
buffer.push(formattedLogs);

@ -8,27 +8,25 @@ export default Ember.Component.extend(
rerenderTriggers: ["order", "ascending"],
buildBuffer(buffer) {
const icon = this.get("icon");
const icon = this.icon;
if (icon) {
buffer.push(iconHTML(icon));
}
buffer.push(I18n.t(this.get("i18nKey")));
buffer.push(I18n.t(this.i18nKey));
if (this.get("field") === this.get("order")) {
buffer.push(
iconHTML(this.get("ascending") ? "chevron-up" : "chevron-down")
);
if (this.field === this.order) {
buffer.push(iconHTML(this.ascending ? "chevron-up" : "chevron-down"));
}
},
click() {
const currentOrder = this.get("order");
const field = this.get("field");
const currentOrder = this.order;
const field = this.field;
if (currentOrder === field) {
this.set("ascending", this.get("ascending") ? null : true);
this.set("ascending", this.ascending ? null : true);
} else {
this.setProperties({ order: field, ascending: null });
}

@ -11,13 +11,13 @@ export default Ember.Component.extend({
actions: {
edit() {
this.set("buffer", this.get("value"));
this.set("buffer", this.value);
this.toggleProperty("editing");
},
save() {
// Action has to toggle 'editing' property.
this.action(this.get("buffer"));
this.action(this.buffer);
}
}
});

@ -1,11 +1,12 @@
import loadScript from "discourse/lib/load-script";
import { number } from "discourse/lib/formatter";
export default Ember.Component.extend({
tagName: "canvas",
type: "line",
refreshChart() {
const ctx = this.$()[0].getContext("2d");
const model = this.get("model");
const model = this.model;
const rawData = this.get("model.data");
var data = {
@ -14,14 +15,14 @@ export default Ember.Component.extend({
{
data: rawData.map(r => r.y),
label: model.get("title"),
backgroundColor: "rgba(200,220,240,0.3)",
backgroundColor: `rgba(200,220,240,${this.type === "bar" ? 1 : 0.3})`,
borderColor: "#08C"
}
]
};
const config = {
type: "line",
type: this.type,
data: data,
options: {
responsive: true,
@ -36,8 +37,7 @@ export default Ember.Component.extend({
{
display: true,
ticks: {
callback: label => number(label),
suggestedMin: 0
stepSize: 1
}
}
]

@ -35,7 +35,7 @@ export default Ember.Component.extend({
_scheduleChartRendering() {
Ember.run.schedule("afterRender", () => {
this._renderChart(this.get("model"), this.$(".chart-canvas"));
this._renderChart(this.model, this.$(".chart-canvas"));
});
},

@ -1,3 +1,5 @@
export default Ember.Component.extend({
classNames: ["admin-report-counters"]
classNames: ["admin-report-counters"],
attributeBindings: ["model.description:title"]
});

@ -33,7 +33,7 @@ export default Ember.Component.extend({
_scheduleChartRendering() {
Ember.run.schedule("afterRender", () => {
this._renderChart(this.get("model"), this.$(".chart-canvas"));
this._renderChart(this.model, this.$(".chart-canvas"));
});
},
@ -114,6 +114,7 @@ export default Ember.Component.extend({
display: true,
gridLines: { display: false },
type: "time",
offset: true,
time: {
parser: "YYYY-MM-DD",
minUnit: "day"

@ -31,6 +31,21 @@ export default Ember.Component.extend({
return reportTotal && total && twoColumns;
},
@computed("model.{average,data}", "totalsForSample.1.value", "twoColumns")
showAverage(model, sampleTotalValue, hasTwoColumns) {
return (
model.average &&
model.data.length > 0 &&
sampleTotalValue &&
hasTwoColumns
);
},
@computed("totalsForSample.1.value", "model.data.length")
averageForSample(totals, count) {
return (totals / count).toFixed(0);
},
@computed("model.data.length")
showSortingUI(dataLength) {
return dataLength >= 5;
@ -79,8 +94,8 @@ export default Ember.Component.extend({
if (sortLabel) {
const compare = (label, direction) => {
return (a, b) => {
let aValue = label.compute(a).value;
let bValue = label.compute(b).value;
const aValue = label.compute(a, { useSortProperty: true }).value;
const bValue = label.compute(b, { useSortProperty: true }).value;
const result = aValue < bValue ? -1 : aValue > bValue ? 1 : 0;
return result * direction;
};
@ -134,8 +149,8 @@ export default Ember.Component.extend({
},
sortByLabel(label) {
if (this.get("sortLabel") === label) {
this.set("sortDirection", this.get("sortDirection") === 1 ? -1 : 1);
if (this.sortLabel === label) {
this.set("sortDirection", this.sortDirection === 1 ? -1 : 1);
} else {
this.set("sortLabel", label);
}

@ -1,13 +1,9 @@
import ReportLoader from "discourse/lib/reports-loader";
import Category from "discourse/models/category";
import { exportEntity } from "discourse/lib/export-csv";
import { outputExportResult } from "discourse/lib/export-result";
import { isNumeric } from "discourse/lib/utilities";
import { SCHEMA_VERSION, default as Report } from "admin/models/report";
import computed from "ember-addons/ember-computed-decorators";
import {
registerHoverTooltip,
unregisterHoverTooltip
} from "discourse/lib/tooltip";
const TABLE_OPTIONS = {
perPage: 8,
@ -54,20 +50,15 @@ export default Ember.Component.extend({
filters: null,
startDate: null,
endDate: null,
category: null,
groupId: null,
showTrend: false,
showHeader: true,
showTitle: true,
showFilteringUI: false,
showCategoryOptions: Ember.computed.alias("model.category_filtering"),
showDatesOptions: Ember.computed.alias("model.dates_filtering"),
showGroupOptions: Ember.computed.alias("model.group_filtering"),
showExport: Ember.computed.not("model.onlyTable"),
showRefresh: Ember.computed.or(
"showCategoryOptions",
"showDatesOptions",
"showGroupOptions"
"model.available_filters.length"
),
shouldDisplayTrend: Ember.computed.and("showTrend", "model.prev_period"),
@ -77,41 +68,19 @@ export default Ember.Component.extend({
this._reports = [];
},
startDate: Ember.computed.reads("filters.startDate"),
endDate: Ember.computed.reads("filters.endDate"),
didReceiveAttrs() {
this._super(...arguments);
const state = this.get("filters") || {};
this.setProperties({
category: Category.findById(state.categoryId),
groupId: state.groupId,
startDate: state.startDate,
endDate: state.endDate
});
if (this.get("report")) {
this._renderReport(
this.get("report"),
this.get("forcedModes"),
this.get("currentMode")
);
} else if (this.get("dataSourceName")) {
if (this.report) {
this._renderReport(this.report, this.forcedModes, this.currentMode);
} else if (this.dataSourceName) {
this._fetchReport();
}
},
didRender() {
this._super(...arguments);
registerHoverTooltip($(".info[data-tooltip]"));
},
willDestroyElement() {
this._super(...arguments);
unregisterHoverTooltip($(".info[data-tooltip]"));
},
showError: Ember.computed.or(
"showTimeoutError",
"showExceptionError",
@ -139,8 +108,6 @@ export default Ember.Component.extend({
return displayedModesLength > 1;
},
categoryId: Ember.computed.alias("category.id"),
@computed("currentMode", "model.modes", "forcedModes")
displayedModes(currentMode, reportModes, forcedModes) {
const modes = forcedModes ? forcedModes.split(",") : reportModes;
@ -157,18 +124,6 @@ export default Ember.Component.extend({
});
},
@computed()
groupOptions() {
const arr = [
{ name: I18n.t("admin.dashboard.reports.groups"), value: "all" }
];
return arr.concat(
(this.site.groups || []).map(i => {
return { name: i["name"], value: i["id"] };
})
);
},
@computed("currentMode")
modeComponent(currentMode) {
return `admin-report-${currentMode}`;
@ -200,23 +155,25 @@ export default Ember.Component.extend({
@computed(
"dataSourceName",
"categoryId",
"groupId",
"normalizedStartDate",
"normalizedEndDate"
"normalizedEndDate",
"filters.customFilters"
)
reportKey(dataSourceName, categoryId, groupId, startDate, endDate) {
reportKey(dataSourceName, startDate, endDate, customFilters) {
if (!dataSourceName || !startDate || !endDate) return null;
let reportKey = "reports:";
reportKey += [
dataSourceName,
categoryId,
startDate.replace(/-/g, ""),
endDate.replace(/-/g, ""),
groupId,
"[:prev_period]",
this.get("reportOptions.table.limit"),
customFilters
? JSON.stringify(customFilters, (key, value) =>
isNumeric(value) ? value.toString() : value
)
: null,
SCHEMA_VERSION
]
.filter(x => x)
@ -227,24 +184,41 @@ export default Ember.Component.extend({
},
actions: {
applyFilter(id, value) {
let customFilters = this.get("filters.customFilters") || {};
if (typeof value === "undefined") {
delete customFilters[id];
} else {
customFilters[id] = value;
}
this.attrs.onRefresh({
type: this.get("model.type"),
startDate: this.startDate,
endDate: this.endDate,
filters: customFilters
});
},
refreshReport() {
this.attrs.onRefresh({
categoryId: this.get("categoryId"),
groupId: this.get("groupId"),
startDate: this.get("startDate"),
endDate: this.get("endDate")
type: this.get("model.type"),
startDate: this.startDate,
endDate: this.endDate,
filters: this.get("filters.customFilters")
});
},
exportCsv() {
const customFilters = this.get("filters.customFilters") || {};
exportEntity("report", {
name: this.get("model.type"),
start_date: this.get("startDate"),
end_date: this.get("endDate"),
category_id:
this.get("categoryId") === "all" ? undefined : this.get("categoryId"),
group_id:
this.get("groupId") === "all" ? undefined : this.get("groupId")
start_date: this.startDate,
end_date: this.endDate,
category_id: customFilters.category,
group_id: customFilters.group
}).then(outputExportResult);
},
@ -271,16 +245,16 @@ export default Ember.Component.extend({
const sort = r => {
if (r.length > 1) {
return r.findBy("type", this.get("dataSourceName"));
return r.findBy("type", this.dataSourceName);
} else {
return r;
}
};
if (!this.get("startDate") || !this.get("endDate")) {
if (!this.startDate || !this.endDate) {
report = sort(filteredReports)[0];
} else {
const reportKey = this.get("reportKey");
const reportKey = this.reportKey;
report = sort(
filteredReports.filter(r => r.report_key.includes(reportKey))
@ -293,11 +267,7 @@ export default Ember.Component.extend({
this.set("showFilteringUI", false);
}
this._renderReport(
report,
this.get("forcedModes"),
this.get("currentMode")
);
this._renderReport(report, this.forcedModes, this.currentMode);
},
_renderReport(report, forcedModes, currentMode) {
@ -339,37 +309,33 @@ export default Ember.Component.extend({
}
};
ReportLoader.enqueue(this.get("dataSourceName"), payload.data, callback);
ReportLoader.enqueue(this.dataSourceName, payload.data, callback);
});
},
_buildPayload(facets) {
let payload = { data: { cache: true, facets } };
if (this.get("startDate")) {
if (this.startDate) {
payload.data.start_date = moment
.utc(this.get("startDate"), "YYYY-MM-DD")
.utc(this.startDate, "YYYY-MM-DD")
.toISOString();
}
if (this.get("endDate")) {
if (this.endDate) {
payload.data.end_date = moment
.utc(this.get("endDate"), "YYYY-MM-DD")
.utc(this.endDate, "YYYY-MM-DD")
.toISOString();
}
if (this.get("groupId") && this.get("groupId") !== "all") {
payload.data.group_id = this.get("groupId");
}
if (this.get("categoryId") && this.get("categoryId") !== "all") {
payload.data.category_id = this.get("categoryId");
}
if (this.get("reportOptions.table.limit")) {
payload.data.limit = this.get("reportOptions.table.limit");
}
if (this.get("filters.customFilters")) {
payload.data.filters = this.get("filters.customFilters");
}
return payload;
},
@ -414,8 +380,8 @@ export default Ember.Component.extend({
Report.fillMissingDates(jsonReport, {
filledField: "prevChartData",
dataField: "prev_data",
starDate: jsonReport.prev_start_date,
endDate: jsonReport.prev_end_date
starDate: jsonReport.prev_startDate,
endDate: jsonReport.prev_endDate
});
if (jsonReport.prevChartData && jsonReport.prevChartData.length > 40) {

@ -0,0 +1,92 @@
import { default as computed } from "ember-addons/ember-computed-decorators";
import { fmt } from "discourse/lib/computed";
export default Ember.Component.extend({
@computed("theme.targets", "onlyOverridden", "showAdvanced")
visibleTargets(targets, onlyOverridden, showAdvanced) {
return targets.filter(target => {
if (target.advanced && !showAdvanced) {
return false;
}
if (!onlyOverridden) {
return true;
}
return target.edited;
});
},
@computed("currentTargetName", "onlyOverridden", "theme.fields")
visibleFields(targetName, onlyOverridden, fields) {
fields = fields[targetName];
if (onlyOverridden) {
fields = fields.filter(field => field.edited);
}
return fields;
},
@computed("currentTargetName", "fieldName")
activeSectionMode(targetName, fieldName) {
if (["settings", "translations"].includes(targetName)) return "yaml";
if (["extra_scss"].includes(targetName)) return "scss";
return fieldName && fieldName.indexOf("scss") > -1 ? "scss" : "html";
},
@computed("fieldName", "currentTargetName", "theme")
activeSection: {
get(fieldName, target, model) {
return model.getField(target, fieldName);
},
set(value, fieldName, target, model) {
model.setField(target, fieldName, value);
return value;
}
},
editorId: fmt("fieldName", "currentTargetName", "%@|%@"),
@computed("maximized")
maximizeIcon(maximized) {
return maximized ? "discourse-compress" : "discourse-expand";
},
@computed("currentTargetName", "theme.targets")
showAddField(currentTargetName, targets) {
return targets.find(t => t.name === currentTargetName).customNames;
},
@computed("currentTargetName", "fieldName", "theme.theme_fields.@each.error")
error(target, fieldName) {
return this.theme.getError(target, fieldName);
},
actions: {
toggleShowAdvanced() {
this.toggleProperty("showAdvanced");
},
toggleAddField() {
this.toggleProperty("addingField");
},
cancelAddField() {
this.set("addingField", false);
},
addField(name) {
if (!name) return;
name = name.replace(/[^a-zA-Z0-9-_/]/g, "");
this.theme.setField(this.currentTargetName, name, "");
this.setProperties({ newFieldName: "", addingField: false });
this.fieldAdded(this.currentTargetName, name);
},
toggleMaximize: function() {
this.toggleProperty("maximized");
Ember.run.next(() => this.appEvents.trigger("ace:resize"));
},
onlyOverriddenChanged(value) {
this.onlyOverriddenChanged(value);
}
}
});

@ -2,6 +2,12 @@ import UserField from "admin/models/user-field";
import { bufferedProperty } from "discourse/mixins/buffered-content";
import { popupAjaxError } from "discourse/lib/ajax-error";
import { propertyEqual } from "discourse/lib/computed";
import { i18n } from "discourse/lib/computed";
import {
default as computed,
observes,
on
} from "ember-addons/ember-computed-decorators";
export default Ember.Component.extend(bufferedProperty("userField"), {
editing: Ember.computed.empty("userField.id"),
@ -10,59 +16,57 @@ export default Ember.Component.extend(bufferedProperty("userField"), {
cantMoveUp: propertyEqual("userField", "firstField"),
cantMoveDown: propertyEqual("userField", "lastField"),
userFieldsDescription: function() {
return I18n.t("admin.user_fields.description");
}.property(),
userFieldsDescription: i18n("admin.user_fields.description"),
bufferedFieldType: function() {
return UserField.fieldTypeById(this.get("buffered.field_type"));
}.property("buffered.field_type"),
@computed("buffered.field_type")
bufferedFieldType(fieldType) {
return UserField.fieldTypeById(fieldType);
},
_focusOnEdit: function() {
if (this.get("editing")) {
@on("didInsertElement")
@observes("editing")
_focusOnEdit() {
if (this.editing) {
Ember.run.scheduleOnce("afterRender", this, "_focusName");
}
}
.observes("editing")
.on("didInsertElement"),
},
_focusName: function() {
_focusName() {
$(".user-field-name").select();
},
fieldName: function() {
return UserField.fieldTypeById(this.get("userField.field_type")).get(
"name"
);
}.property("userField.field_type"),
@computed("userField.field_type")
fieldName(fieldType) {
return UserField.fieldTypeById(fieldType).get("name");
},
flags: function() {
const ret = [];
if (this.get("userField.editable")) {
ret.push(I18n.t("admin.user_fields.editable.enabled"));
}
if (this.get("userField.required")) {
ret.push(I18n.t("admin.user_fields.required.enabled"));
}
if (this.get("userField.show_on_profile")) {
ret.push(I18n.t("admin.user_fields.show_on_profile.enabled"));
}
if (this.get("userField.show_on_user_card")) {
ret.push(I18n.t("admin.user_fields.show_on_user_card.enabled"));
}
return ret.join(", ");
}.property(
@computed(
"userField.editable",
"userField.required",
"userField.show_on_profile",
"userField.show_on_user_card"
),
)
flags(editable, required, showOnProfile, showOnUserCard) {
const ret = [];
if (editable) {
ret.push(I18n.t("admin.user_fields.editable.enabled"));
}
if (required) {
ret.push(I18n.t("admin.user_fields.required.enabled"));
}
if (showOnProfile) {
ret.push(I18n.t("admin.user_fields.show_on_profile.enabled"));
}
if (showOnUserCard) {
ret.push(I18n.t("admin.user_fields.show_on_user_card.enabled"));
}
return ret.join(", ");
},
actions: {
save() {
const self = this;
const buffered = this.get("buffered");
const buffered = this.buffered;
const attrs = buffered.getProperties(
"name",
"description",
@ -74,11 +78,11 @@ export default Ember.Component.extend(bufferedProperty("userField"), {
"options"
);
this.get("userField")
this.userField
.save(attrs)
.then(function() {
self.set("editing", false);
self.commitBuffer();
.then(() => {
this.set("editing", false);
this.commitBuffer();
})
.catch(popupAjaxError);
},
@ -90,7 +94,7 @@ export default Ember.Component.extend(bufferedProperty("userField"), {
cancel() {
const id = this.get("userField.id");
if (Ember.isEmpty(id)) {
this.destroyAction(this.get("userField"));
this.destroyAction(this.userField);
} else {
this.rollbackBuffer();
this.set("editing", false);

@ -11,10 +11,10 @@ export default Ember.Component.extend(
},
click() {
this.get("word")
this.word
.destroy()
.then(() => {
this.action(this.get("word"));
this.action(this.word);
})
.catch(e => {
bootbox.alert(

@ -25,8 +25,8 @@ export default Ember.Component.extend({
return eventTypeExists;
},
set(value, eventTypeExists) {
const type = this.get("type");
const model = this.get("model");
const type = this.type;
const model = this.model;
// add an association when not exists
if (value !== eventTypeExists) {
if (value) {

@ -6,6 +6,8 @@ import { ensureJSON, plainJSON, prettyJSON } from "discourse/lib/formatter";
export default Ember.Component.extend({
tagName: "li",
expandDetails: null,
expandDetailsRequestKey: "request",
expandDetailsResponseKey: "response",
@computed("model.status")
statusColorClasses(status) {
@ -29,6 +31,20 @@ export default Ember.Component.extend({
return I18n.t("admin.web_hooks.events.completed_in", { count: seconds });
},
@computed("expandDetails")
expandRequestIcon(expandDetails) {
return expandDetails === this.expandDetailsRequestKey
? "ellipsis-h"
: "ellipsis-v";
},
@computed("expandDetails")
expandResponseIcon(expandDetails) {
return expandDetails === this.expandDetailsResponseKey
? "ellipsis-h"
: "ellipsis-v";
},
actions: {
redeliver() {
return bootbox.confirm(
@ -53,9 +69,9 @@ export default Ember.Component.extend({
},
toggleRequest() {
const expandDetailsKey = "request";
const expandDetailsKey = this.expandDetailsRequestKey;
if (this.get("expandDetails") !== expandDetailsKey) {
if (this.expandDetails !== expandDetailsKey) {
let headers = _.extend(
{
"Request URL": this.get("model.request_url"),
@ -75,9 +91,9 @@ export default Ember.Component.extend({
},
toggleResponse() {
const expandDetailsKey = "response";
const expandDetailsKey = this.expandDetailsResponseKey;
if (this.get("expandDetails") !== expandDetailsKey) {
if (this.expandDetails !== expandDetailsKey) {
this.setProperties({
headers: plainJSON(this.get("model.response_headers")),
body: this.get("model.response_body"),

@ -4,8 +4,8 @@ import { bufferedRender } from "discourse-common/lib/buffered-render";
export default Ember.Component.extend(
bufferedRender({
classes: ["text-muted", "text-danger", "text-successful"],
icons: ["circle-o", "times-circle", "circle"],
classes: ["text-muted", "text-danger", "text-successful", "text-muted"],
icons: ["circle-o", "times-circle", "circle", "circle"],
@computed("deliveryStatuses", "model.last_delivery_status")
status(deliveryStatuses, lastDeliveryStatus) {
@ -23,7 +23,7 @@ export default Ember.Component.extend(
},
buildBuffer(buffer) {
buffer.push(iconHTML(this.get("icon"), { class: this.get("class") }));
buffer.push(iconHTML(this.icon, { class: this.class }));
buffer.push(
I18n.t(`admin.web_hooks.delivery_status.${this.get("status.name")}`)
);

@ -10,21 +10,21 @@ import { default as loadScript, loadCSS } from "discourse/lib/load-script";
export default Ember.Component.extend({
classNames: ["color-picker"],
hexValueChanged: function() {
var hex = this.get("hexValue");
var hex = this.hexValue;
let $text = this.$("input.hex-input");
if (this.get("valid")) {
if (this.valid) {
$text.attr(
"style",
"color: " +
(this.get("brightnessValue") > 125 ? "black" : "white") +
(this.brightnessValue > 125 ? "black" : "white") +
"; background-color: #" +
hex +
";"
);
if (this.get("pickerLoaded")) {
this.$(".picker").spectrum({ color: "#" + this.get("hexValue") });
if (this.pickerLoaded) {
this.$(".picker").spectrum({ color: "#" + this.hexValue });
}
} else {
$text.attr("style", "");
@ -36,7 +36,7 @@ export default Ember.Component.extend({
loadCSS("/javascripts/spectrum.css").then(() => {
Ember.run.schedule("afterRender", () => {
this.$(".picker")
.spectrum({ color: "#" + this.get("hexValue") })
.spectrum({ color: "#" + this.hexValue })
.on("change.spectrum", (me, color) => {
this.set("hexValue", color.toHexString().replace("#", ""));
});

@ -30,26 +30,23 @@ export default Ember.Component.extend(bufferedProperty("host"), {
},
save() {
if (this.get("cantSave")) {
if (this.cantSave) {
return;
}
const props = this.get("buffered").getProperties(
const props = this.buffered.getProperties(
"host",
"path_whitelist",
"class_name"
);
props.category_id = this.get("categoryId");
props.category_id = this.categoryId;
const host = this.get("host");
const host = this.host;
host
.save(props)
.then(() => {
host.set(
"category",
Discourse.Category.findById(this.get("categoryId"))
);
host.set("category", Discourse.Category.findById(this.categoryId));
this.set("editToggled", false);
})
.catch(popupAjaxError);
@ -58,17 +55,15 @@ export default Ember.Component.extend(bufferedProperty("host"), {
delete() {
bootbox.confirm(I18n.t("admin.embedding.confirm_delete"), result => {
if (result) {
this.get("host")
.destroyRecord()
.then(() => {
this.deleteHost(this.get("host"));
});
this.host.destroyRecord().then(() => {
this.deleteHost(this.host);
});
}
});
},
cancel() {
const host = this.get("host");
const host = this.host;
if (host.get("isNew")) {
this.deleteHost(host);
} else {

@ -1,3 +0,0 @@
export default Ember.Component.extend({
classNames: ["flagged-post-response"]
});

@ -1,58 +0,0 @@
import showModal from "discourse/lib/show-modal";
import computed from "ember-addons/ember-computed-decorators";
export default Ember.Component.extend({
adminTools: Ember.inject.service(),
expanded: false,
tagName: "div",
classNameBindings: [
":flagged-post",
"flaggedPost.hidden:hidden-post",
"flaggedPost.deleted"
],
canAct: Ember.computed.alias("actableFilter"),
@computed("filter")
actableFilter(filter) {
return filter === "active";
},
removeAfter(promise) {
return promise.then(() => this.attrs.removePost());
},
_spawnModal(name, model, modalClass) {
let controller = showModal(name, { model, admin: true, modalClass });
controller.removeAfter = p => this.removeAfter(p);
},
actions: {
removeAfter(promise) {
return this.removeAfter(promise);
},
disagree() {
this.removeAfter(this.get("flaggedPost").disagreeFlags());
},
defer() {
this.removeAfter(this.get("flaggedPost").deferFlags());
},
expand() {
this.get("flaggedPost")
.expandHidden()
.then(() => {
this.set("expanded", true);
});
},
showModerationHistory() {
this.get("adminTools").showModerationHistory({
filter: "post",
post_id: this.get("flaggedPost.id")
});
}
}
});

@ -12,12 +12,12 @@ export default Ember.Component.extend({
init() {
this._super(...arguments);
this.set("checkedInternal", this.get("checked"));
this.set("checkedInternal", this.checked);
},
@observes("checked")
checkedChanged() {
this.set("checkedInternal", this.get("checked"));
this.set("checkedInternal", this.checked);
},
@computed("labelKey")
@ -32,11 +32,11 @@ export default Ember.Component.extend({
actions: {
cancelled() {
this.set("checkedInternal", this.get("checked"));
this.set("checkedInternal", this.checked);
},
finished() {
this.set("checked", this.get("checkedInternal"));
this.set("checked", this.checkedInternal);
this.action();
}
}

@ -1,3 +1,3 @@
export default Ember.Component.extend({
tagName: "h3"
classNames: ["install-theme-item"]
});

@ -18,18 +18,18 @@ export default Ember.Component.extend({
lookup() {
this.set("show", true);
if (!this.get("location")) {
ajax("/admin/users/ip-info", { data: { ip: this.get("ip") } }).then(
location => this.set("location", Ember.Object.create(location))
if (!this.location) {
ajax("/admin/users/ip-info", { data: { ip: this.ip } }).then(location =>
this.set("location", Ember.Object.create(location))
);
}
if (!this.get("other_accounts")) {
if (!this.other_accounts) {
this.set("otherAccountsLoading", true);
const data = {
ip: this.get("ip"),
exclude: this.get("userId"),
ip: this.ip,
exclude: this.userId,
order: "trust_level DESC"
};
@ -51,8 +51,8 @@ export default Ember.Component.extend({
},
copy() {
let text = `IP: ${this.get("ip")}\n`;
const location = this.get("location");
let text = `IP: ${this.ip}\n`;
const location = this.location;
if (location) {
if (location.hostname) {
text += `${I18n.t("ip_lookup.hostname")}: ${location.hostname}\n`;
@ -97,8 +97,8 @@ export default Ember.Component.extend({
ajax("/admin/users/delete-others-with-same-ip.json", {
type: "DELETE",
data: {
ip: this.get("ip"),
exclude: this.get("userId"),
ip: this.ip,
exclude: this.userId,
order: "trust_level DESC"
}
}).then(() => this.send("lookup"));

@ -1,7 +1,9 @@
import computed from "ember-addons/ember-computed-decorators";
const ACTIONS = ["delete", "edit", "none"];
const ACTIONS = ["delete", "delete_replies", "edit", "none"];
export default Ember.Component.extend({
postId: null,
postAction: null,
postEdit: null,
@ -16,7 +18,7 @@ export default Ember.Component.extend({
actions: {
penaltyChanged() {
let postAction = this.get("postAction");
let postAction = this.postAction;
// If we switch to edit mode, jump to the edit textarea
if (postAction === "edit") {

@ -24,13 +24,13 @@ export default Ember.Component.extend({
actions: {
submit() {
if (!this.get("formSubmitted")) {
if (!this.formSubmitted) {
this.set("formSubmitted", true);
Permalink.create({
url: this.get("url"),
permalink_type: this.get("permalinkType"),
permalink_type_value: this.get("permalink_type_value")
url: this.url,
permalink_type: this.permalinkType,
permalink_type_value: this.permalink_type_value
})
.save()
.then(

@ -0,0 +1,20 @@
import Category from "discourse/models/category";
import { default as computed } from "ember-addons/ember-computed-decorators";
import FilterComponent from "admin/components/report-filters/filter";
export default FilterComponent.extend({
classNames: ["category-filter"],
layoutName: "admin/templates/components/report-filters/category",
@computed("filter.default")
category(categoryId) {
return Category.findById(categoryId);
},
actions: {
onDeselect() {
this.applyFilter(this.get("filter.id"), undefined);
}
}
});

@ -0,0 +1,7 @@
import FilterComponent from "admin/components/report-filters/filter";
export default FilterComponent.extend({
classNames: ["file-extension-filter"],
layoutName: "admin/templates/components/report-filters/file-extension"
});

@ -0,0 +1,7 @@
export default Ember.Component.extend({
actions: {
onChange(value) {
this.applyFilter(this.get("filter.id"), value);
}
}
});

@ -0,0 +1,20 @@
import FilterComponent from "admin/components/report-filters/filter";
import { default as computed } from "ember-addons/ember-computed-decorators";
export default FilterComponent.extend({
classNames: ["group-filter"],
layoutName: "admin/templates/components/report-filters/group",
@computed()
groupOptions() {
return (this.site.groups || []).map(group => {
return { name: group["name"], value: group["id"] };
});
},
@computed("filter.default")
groupId(filterDefault) {
return filterDefault ? parseInt(filterDefault, 10) : null;
}
});

@ -1,5 +1,9 @@
import { iconHTML } from "discourse-common/lib/icon-library";
import { bufferedRender } from "discourse-common/lib/buffered-render";
import {
default as computed,
on
} from "ember-addons/ember-computed-decorators";
/*global Resumable:true */
@ -19,113 +23,108 @@ export default Ember.Component.extend(
classNames: ["btn", "ru"],
classNameBindings: ["isUploading"],
attributeBindings: ["translatedTitle:title"],
resumable: null,
isUploading: false,
progress: 0,
rerenderTriggers: ["isUploading", "progress"],
translatedTitle: function() {
const title = this.get("title");
return title ? I18n.t(title) : this.get("text");
}.property("title", "text"),
@on("init")
_initialize() {
this.resumable = new Resumable({
target: Discourse.getURL(this.target),
maxFiles: 1, // only 1 file at a time
headers: {
"X-CSRF-Token": document.querySelector("meta[name='csrf-token']")
.content
}
});
text: function() {
if (this.get("isUploading")) {
return this.get("progress") + " %";
} else {
return this.get("uploadText");
}
}.property("isUploading", "progress"),
this.resumable.on("fileAdded", () => {
// automatically upload the selected file
this.resumable.upload();
buildBuffer(buffer) {
const icon = this.get("isUploading") ? "times" : "upload";
buffer.push(iconHTML(icon));
buffer.push("<span class='ru-label'>" + this.get("text") + "</span>");
buffer.push(
"<span class='ru-progress' style='width:" +
this.get("progress") +
"%'></span>"
// mark as uploading
Ember.run.later(() => this.set("isUploading", true));
});
this.resumable.on("fileProgress", file => {
// update progress
Ember.run.later(() =>
this.set("progress", parseInt(file.progress() * 100, 10))
);
});
this.resumable.on("fileSuccess", file => {
Ember.run.later(() => {
// mark as not uploading anymore
this._reset();
// fire an event to allow the parent route to reload its model
this.success(file.fileName);
});
});
this.resumable.on("fileError", (file, message) => {
Ember.run.later(() => {
// mark as not uploading anymore
this._reset();
// fire an event to allow the parent route to display the error message
this.error(file.fileName, message);
});
});
},
@on("didInsertElement")
_assignBrowse() {
Ember.run.schedule("afterRender", () =>
this.resumable.assignBrowse($(this.element))
);
},
click: function() {
if (this.get("isUploading")) {
@on("willDestroyElement")
_teardown() {
if (this.resumable) {
this.resumable.cancel();
var self = this;
Ember.run.later(function() {
self._reset();
});
this.resumable = null;
}
},
@computed("title", "text")
translatedTitle(title, text) {
return title ? I18n.t(title) : text;
},
@computed("isUploading", "progress")
text(isUploading, progress) {
if (isUploading) {
return progress + " %";
} else {
return this.uploadText;
}
},
buildBuffer(buffer) {
const icon = this.isUploading ? "times" : "upload";
buffer.push(iconHTML(icon));
buffer.push("<span class='ru-label'>" + this.text + "</span>");
buffer.push(
"<span class='ru-progress' style='width:" + this.progress + "%'></span>"
);
},
click() {
if (this.isUploading) {
this.resumable.cancel();
Ember.run.later(() => this._reset());
return false;
} else {
return true;
}
},
_reset: function() {
_reset() {
this.setProperties({ isUploading: false, progress: 0 });
},
_initialize: function() {
this.resumable = new Resumable({
target: Discourse.getURL(this.get("target")),
maxFiles: 1, // only 1 file at a time
headers: {
"X-CSRF-Token": $("meta[name='csrf-token']").attr("content")
}
});
var self = this;
this.resumable.on("fileAdded", function() {
// automatically upload the selected file
self.resumable.upload();
// mark as uploading
Ember.run.later(function() {
self.set("isUploading", true);
});
});
this.resumable.on("fileProgress", function(file) {
// update progress
Ember.run.later(function() {
self.set("progress", parseInt(file.progress() * 100, 10));
});
});
this.resumable.on("fileSuccess", function(file) {
Ember.run.later(function() {
// mark as not uploading anymore
self._reset();
// fire an event to allow the parent route to reload its model
self.success(file.fileName);
});
});
this.resumable.on("fileError", function(file, message) {
Ember.run.later(function() {
// mark as not uploading anymore
self._reset();
// fire an event to allow the parent route to display the error message
self.error(file.fileName, message);
});
});
}.on("init"),
_assignBrowse: function() {
var self = this;
Ember.run.schedule("afterRender", function() {
self.resumable.assignBrowse(self.$());
});
}.on("didInsertElement"),
_teardown: function() {
if (this.resumable) {
this.resumable.cancel();
this.resumable = null;
}
}.on("willDestroyElement")
}
})
);

@ -50,11 +50,11 @@ export default Ember.Component.extend({
actions: {
submit() {
if (!this.get("formSubmitted")) {
if (!this.formSubmitted) {
this.set("formSubmitted", true);
const screenedIpAddress = ScreenedIpAddress.create({
ip_address: this.get("ip_address"),
action_name: this.get("actionName")
ip_address: this.ip_address,
action_name: this.actionName
});
screenedIpAddress
.save()

@ -9,11 +9,11 @@ export default Ember.Component.extend({
@on("didReceiveAttrs")
_setupCollection() {
const values = this.get("values");
const values = this.values;
this.set(
"collection",
this._splitValues(values, this.get("inputDelimiter") || "\n")
this._splitValues(values, this.inputDelimiter || "\n")
);
},
@ -29,9 +29,8 @@ export default Ember.Component.extend({
},
addValue() {
if (this._checkInvalidInput([this.get("newKey"), this.get("newSecret")]))
return;
this._addValue(this.get("newKey"), this.get("newSecret"));
if (this._checkInvalidInput([this.newKey, this.newSecret])) return;
this._addValue(this.newKey, this.newSecret);
this.setProperties({ newKey: "", newSecret: "" });
},
@ -54,18 +53,18 @@ export default Ember.Component.extend({
},
_addValue(value, secret) {
this.get("collection").addObject({ key: value, secret: secret });
this.collection.addObject({ key: value, secret: secret });
this._saveValues();
},
_removeValue(value) {
const collection = this.get("collection");
const collection = this.collection;
collection.removeObject(value);
this._saveValues();
},
_replaceValue(index, newValue, keyName) {
let item = this.get("collection")[index];
let item = this.collection[index];
Ember.set(item, keyName, newValue);
this._saveValues();
@ -74,7 +73,7 @@ export default Ember.Component.extend({
_saveValues() {
this.set(
"values",
this.get("collection")
this.collection
.map(function(elem) {
return `${elem.key}|${elem.secret}`;
})

@ -4,7 +4,7 @@ import SettingComponent from "admin/mixins/setting-component";
export default Ember.Component.extend(BufferedContent, SettingComponent, {
_save() {
const setting = this.get("buffered");
const setting = this.buffered;
return SiteSetting.update(setting.get("setting"), setting.get("value"));
}
});

@ -0,0 +1,8 @@
import computed from "ember-addons/ember-computed-decorators";
export default Ember.Component.extend({
@computed()
groupChoices() {
return this.site.get("groups").map(g => g.name);
}
});

@ -17,18 +17,18 @@ export default Ember.Component.extend({
},
click() {
this.send("edit");
this.editAction(this.siteText);
},
_searchTerm() {
const regex = this.get("searchRegex");
const siteText = this.get("siteText");
const regex = this.searchRegex;
const siteText = this.siteText;
if (regex && siteText) {
const matches = siteText.value.match(new RegExp(regex, "i"));
if (matches) return matches[0];
}
return this.get("term");
return this.term;
}
});

@ -4,7 +4,7 @@ import SettingComponent from "admin/mixins/setting-component";
export default Ember.Component.extend(BufferedContent, SettingComponent, {
layoutName: "admin/templates/components/site-setting",
_save() {
return this.get("model").saveSettings(
return this.model.saveSettings(
this.get("setting.setting"),
this.get("buffered.value")
);

@ -8,7 +8,7 @@ export default Ember.Component.extend(BufferedContent, SettingComponent, {
settingName: Ember.computed.alias("translation.key"),
_save() {
return this.get("model").saveTranslation(
return this.model.saveTranslation(
this.get("translation.key"),
this.get("buffered.value")
);

@ -56,12 +56,12 @@ export default Ember.Component.extend({
"childrenExpanded"
)
children() {
const theme = this.get("theme");
const theme = this.theme;
let children = theme.get("childThemes");
if (theme.get("component") || !children) {
return [];
}
children = this.get("childrenExpanded")
children = this.childrenExpanded
? children
: children.slice(0, MAX_COMPONENTS);
return children.map(t => t.get("name"));

@ -16,7 +16,7 @@ export default Ember.Component.extend({
@computed("themes", "components", "currentTab")
themesList(themes, components) {
if (this.get("themesTabActive")) {
if (this.themesTabActive) {
return themes;
} else {
return components;
@ -30,7 +30,7 @@ export default Ember.Component.extend({
"themesList.@each.default"
)
inactiveThemes(themes) {
if (this.get("componentsTabActive")) {
if (this.componentsTabActive) {
return themes.filter(theme => theme.get("parent_themes.length") <= 0);
}
return themes.filter(
@ -45,7 +45,7 @@ export default Ember.Component.extend({
"themesList.@each.default"
)
activeThemes(themes) {
if (this.get("componentsTabActive")) {
if (this.componentsTabActive) {
return themes.filter(theme => theme.get("parent_themes.length") > 0);
} else {
themes = themes.filter(
@ -61,21 +61,9 @@ export default Ember.Component.extend({
}
},
didRender() {
this._super(...arguments);
// hide scrollbar
const $container = this.$(".themes-list-container");
const containerNode = $container[0];
if (containerNode) {
const width = containerNode.offsetWidth - containerNode.clientWidth;
$container.css("width", `calc(100% + ${width}px)`);
}
},
actions: {
changeView(newTab) {
if (newTab !== this.get("currentTab")) {
if (newTab !== this.currentTab) {
this.set("currentTab", newTab);
}
},

@ -15,15 +15,15 @@ export default Ember.Component.extend({
@on("didReceiveAttrs")
_setupCollection() {
const values = this.get("values");
if (this.get("inputType") === "array") {
const values = this.values;
if (this.inputType === "array") {
this.set("collection", values || []);
return;
}
this.set(
"collection",
this._splitValues(values, this.get("inputDelimiter") || "\n")
this._splitValues(values, this.inputDelimiter || "\n")
);
},
@ -33,7 +33,7 @@ export default Ember.Component.extend({
},
keyDown(event) {
if (event.keyCode === 13) this.send("addValue", this.get("newValue"));
if (event.keyCode === 13) this.send("addValue", this.newValue);
},
actions: {
@ -42,7 +42,7 @@ export default Ember.Component.extend({
},
addValue(newValue) {
if (this.get("inputInvalid")) return;
if (this.inputInvalid) return;
this.set("newValue", "");
this._addValue(newValue);
@ -58,31 +58,28 @@ export default Ember.Component.extend({
},
_addValue(value) {
this.get("collection").addObject(value);
this.collection.addObject(value);
this._saveValues();
},
_removeValue(value) {
const collection = this.get("collection");
const collection = this.collection;
collection.removeObject(value);
this._saveValues();
},
_replaceValue(index, newValue) {
this.get("collection").replace(index, 1, [newValue]);
this.collection.replace(index, 1, [newValue]);
this._saveValues();
},
_saveValues() {
if (this.get("inputType") === "array") {
this.set("values", this.get("collection"));
if (this.inputType === "array") {
this.set("values", this.collection);
return;
}
this.set(
"values",
this.get("collection").join(this.get("inputDelimiter") || "\n")
);
this.set("values", this.collection.join(this.inputDelimiter || "\n"));
},
_splitValues(values, delimiter) {

@ -21,17 +21,15 @@ export default Ember.Component.extend({
@observes("word")
removeMessage() {
if (this.get("showMessage") && !Ember.isEmpty(this.get("word"))) {
if (this.showMessage && !Ember.isEmpty(this.word)) {
this.set("showMessage", false);
}
},
@computed("word")
isUniqueWord(word) {
const words = this.get("filteredContent") || [];
const filtered = words.filter(
content => content.action === this.get("actionKey")
);
const words = this.filteredContent || [];
const filtered = words.filter(content => content.action === this.actionKey);
return filtered.every(
content => content.word.toLowerCase() !== word.toLowerCase()
);
@ -39,7 +37,7 @@ export default Ember.Component.extend({
actions: {
submit() {
if (!this.get("isUniqueWord")) {
if (!this.isUniqueWord) {
this.setProperties({
showMessage: true,
message: I18n.t("admin.watched_words.form.exists")
@ -47,12 +45,12 @@ export default Ember.Component.extend({
return;
}
if (!this.get("formSubmitted")) {
if (!this.formSubmitted) {
this.set("formSubmitted", true);
const watchedWord = WatchedWord.create({
word: this.get("word"),
action: this.get("actionKey")
word: this.word,
action: this.actionKey
});
watchedWord

@ -9,7 +9,7 @@ export default Ember.Controller.extend({
actions: {
generateMasterKey() {
ApiKey.generateMasterKey().then(key => this.get("model").pushObject(key));
ApiKey.generateMasterKey().then(key => this.model.pushObject(key));
},
regenerateKey(key) {
@ -32,7 +32,7 @@ export default Ember.Controller.extend({
I18n.t("yes_value"),
result => {
if (result) {
key.revoke().then(() => this.get("model").removeObject(key));
key.revoke().then(() => this.model.removeObject(key));
}
}
);

@ -1,5 +1,10 @@
export default Ember.Controller.extend({
logs: [],
adminBackups: Ember.inject.controller(),
status: Ember.computed.alias("adminBackups.model")
status: Ember.computed.alias("adminBackups.model"),
init() {
this._super(...arguments);
this.logs = [];
}
});

@ -1,6 +1,7 @@
import { popupAjaxError } from "discourse/lib/ajax-error";
import { bufferedProperty } from "discourse/mixins/buffered-content";
import { propertyNotEqual } from "discourse/lib/computed";
import computed from "ember-addons/ember-computed-decorators";
export default Ember.Controller.extend(bufferedProperty("model"), {
adminBadges: Ember.inject.controller(),
@ -17,14 +18,13 @@ export default Ember.Controller.extend(bufferedProperty("model"), {
readOnly: Ember.computed.alias("buffered.system"),
showDisplayName: propertyNotEqual("name", "displayName"),
hasQuery: function() {
const bQuery = this.get("buffered.query");
if (bQuery) {
return bQuery.trim().length > 0;
@computed("model.query", "buffered.query")
hasQuery(modelQuery, bufferedQuery) {
if (bufferedQuery) {
return bufferedQuery.trim().length > 0;
}
const mQuery = this.get("model.query");
return mQuery && mQuery.trim().length > 0;
}.property("model.query", "buffered.query"),
return modelQuery && modelQuery.trim().length > 0;
},
_resetSaving: function() {
this.set("saving", false);
@ -33,7 +33,7 @@ export default Ember.Controller.extend(bufferedProperty("model"), {
actions: {
save() {
if (!this.get("saving")) {
if (!this.saving) {
let fields = [
"allow_title",
"multiple_grant",
@ -54,7 +54,7 @@ export default Ember.Controller.extend(bufferedProperty("model"), {
];
if (this.get("buffered.system")) {
var protectedFields = this.get("protectedSystemFields") || [];
var protectedFields = this.protectedSystemFields || [];
fields = _.filter(fields, f => !protectedFields.includes(f));
}
@ -72,7 +72,7 @@ export default Ember.Controller.extend(bufferedProperty("model"), {
];
const data = {};
const buffered = this.get("buffered");
const buffered = this.buffered;
fields.forEach(function(field) {
var d = buffered.get(field);
if (boolFields.includes(field)) {
@ -81,9 +81,9 @@ export default Ember.Controller.extend(bufferedProperty("model"), {
data[field] = d;
});
const newBadge = !this.get("id");
const model = this.get("model");
this.get("model")
const newBadge = !this.id;
const model = this.model;
this.model
.save(data)
.then(() => {
if (newBadge) {
@ -107,7 +107,7 @@ export default Ember.Controller.extend(bufferedProperty("model"), {
destroy() {
const adminBadges = this.get("adminBadges.model");
const model = this.get("model");
const model = this.model;
if (!model.get("id")) {
this.transitionToRoute("adminBadges.index");

@ -23,7 +23,7 @@ export default Ember.Controller.extend({
$(".table.colors").hide();
let area = $("<textarea id='copy-range'></textarea>");
$(".table.colors").after(area);
area.text(this.get("model").schemeJson());
area.text(this.model.schemeJson());
let range = document.createRange();
range.selectNode(area[0]);
window.getSelection().addRange(range);
@ -51,7 +51,7 @@ export default Ember.Controller.extend({
},
copy() {
var newColorScheme = Ember.copy(this.get("model"), true);
var newColorScheme = Ember.copy(this.model, true);
newColorScheme.set(
"name",
I18n.t("admin.customize.colors.copy_name_prefix") +
@ -59,17 +59,17 @@ export default Ember.Controller.extend({
this.get("model.name")
);
newColorScheme.save().then(() => {
this.get("allColors").pushObject(newColorScheme);
this.allColors.pushObject(newColorScheme);
this.replaceRoute("adminCustomize.colors.show", newColorScheme);
});
},
save: function() {
this.get("model").save();
this.model.save();
},
destroy: function() {
const model = this.get("model");
const model = this.model;
return bootbox.confirm(
I18n.t("admin.customize.colors.delete_confirm"),
I18n.t("no_value"),
@ -77,7 +77,7 @@ export default Ember.Controller.extend({
result => {
if (result) {
model.destroy().then(() => {
this.get("allColors").removeObject(model);
this.allColors.removeObject(model);
this.replaceRoute("adminCustomize.colors");
});
}

@ -1,33 +1,36 @@
import showModal from "discourse/lib/show-modal";
import { default as computed } from "ember-addons/ember-computed-decorators";
export default Ember.Controller.extend({
baseColorScheme: function() {
return this.get("model").findBy("is_base", true);
}.property("model.@each.id"),
@computed("model.@each.id")
baseColorScheme() {
return this.model.findBy("is_base", true);
},
baseColorSchemes: function() {
return this.get("model").filterBy("is_base", true);
}.property("model.@each.id"),
@computed("model.@each.id")
baseColorSchemes() {
return this.model.filterBy("is_base", true);
},
baseColors: function() {
var baseColorsHash = Ember.Object.create({});
this.get("baseColorScheme.colors").forEach(color => {
@computed("baseColorScheme")
baseColors(baseColorScheme) {
const baseColorsHash = Ember.Object.create({});
baseColorScheme.get("colors").forEach(color => {
baseColorsHash.set(color.get("name"), color);
});
return baseColorsHash;
}.property("baseColorScheme"),
},
actions: {
newColorSchemeWithBase(baseKey) {
const base = this.get("baseColorSchemes").findBy(
"base_scheme_id",
baseKey
);
const base = this.baseColorSchemes.findBy("base_scheme_id", baseKey);
const newColorScheme = Ember.copy(base, true);
newColorScheme.set("name", I18n.t("admin.customize.colors.new_name"));
newColorScheme.set("base_scheme_id", base.get("base_scheme_id"));
newColorScheme.setProperties({
name: I18n.t("admin.customize.colors.new_name"),
base_scheme_id: base.get("base_scheme_id")
});
newColorScheme.save().then(() => {
this.get("model").pushObject(newColorScheme);
this.model.pushObject(newColorScheme);
newColorScheme.set("savingStatus", null);
this.replaceRoute("adminCustomize.colors.show", newColorScheme);
});
@ -35,7 +38,7 @@ export default Ember.Controller.extend({
newColorScheme() {
showModal("admin-color-scheme-select-base", {
model: this.get("baseColorSchemes"),
model: this.baseColorSchemes,
admin: true
});
}

@ -1,23 +1,24 @@
import { popupAjaxError } from "discourse/lib/ajax-error";
import { bufferedProperty } from "discourse/mixins/buffered-content";
import computed from "ember-addons/ember-computed-decorators";
export default Ember.Controller.extend(bufferedProperty("emailTemplate"), {
saved: false,
hasMultipleSubjects: function() {
const buffered = this.get("buffered");
@computed("buffered")
hasMultipleSubjects(buffered) {
if (buffered.getProperties("subject")["subject"]) {
return false;
} else {
return buffered.getProperties("id")["id"];
}
}.property("buffered"),
},
actions: {
saveChanges() {
this.set("saved", false);
const buffered = this.get("buffered");
this.get("emailTemplate")
const buffered = this.buffered;
this.emailTemplate
.save(buffered.getProperties("subject", "body"))
.then(() => {
this.set("saved", true);
@ -31,10 +32,10 @@ export default Ember.Controller.extend(bufferedProperty("emailTemplate"), {
I18n.t("admin.customize.email_templates.revert_confirm"),
result => {
if (result) {
this.get("emailTemplate")
this.emailTemplate
.revert()
.then(props => {
const buffered = this.get("buffered");
const buffered = this.buffered;
buffered.setProperties(props);
this.commitBuffer();
})

@ -1,6 +1,10 @@
export default Ember.Controller.extend({
titleSorting: ["title"],
emailTemplates: null,
sortedTemplates: Ember.computed.sort("emailTemplates", "titleSorting"),
sortedTemplates: Ember.computed.sort("emailTemplates", "titleSorting")
init() {
this._super(...arguments);
this.titleSorting = ["title"];
}
});

@ -1,163 +1,28 @@
import { url } from "discourse/lib/computed";
import {
default as computed,
observes
} from "ember-addons/ember-computed-decorators";
import { default as computed } from "ember-addons/ember-computed-decorators";
export default Ember.Controller.extend({
section: null,
currentTarget: 0,
maximized: false,
previewUrl: url("model.id", "/admin/themes/%@/preview"),
showAdvanced: false,
editRouteName: "adminCustomizeThemes.edit",
targets: [
{ id: 0, name: "common" },
{ id: 1, name: "desktop" },
{ id: 2, name: "mobile" },
{ id: 3, name: "settings" },
{ id: 4, name: "translations" }
],
fieldsForTarget: function(target) {
const common = [
"scss",
"head_tag",
"header",
"after_header",
"body_tag",
"footer"
];
switch (target) {
case "common":
return [...common, "embedded_scss"];
case "desktop":
return common;
case "mobile":
return common;
case "settings":
return ["yaml"];
}
},
@computed("onlyOverridden")
showCommon() {
return this.shouldShow("common");
},
@computed("onlyOverridden")
showDesktop() {
return this.shouldShow("desktop");
},
@computed("onlyOverridden")
showMobile() {
return this.shouldShow("mobile");
},
@observes("onlyOverridden")
onlyOverriddenChanged() {
if (this.get("onlyOverridden")) {
if (
!this.get("model").hasEdited(
this.get("currentTargetName"),
this.get("fieldName")
)
) {
let target =
(this.get("showCommon") && "common") ||
(this.get("showDesktop") && "desktop") ||
(this.get("showMobile") && "mobile");
let fields = this.get("model.theme_fields");
let field = fields && fields.find(f => f.target === target);
this.replaceRoute(
this.get("editRouteName"),
this.get("model.id"),
target,
field && field.name
);
}
}
},
shouldShow(target) {
if (!this.get("onlyOverridden")) {
return true;
}
return this.get("model").hasEdited(target);
},
showRouteName: "adminCustomizeThemes.show",
setTargetName: function(name) {
const target = this.get("targets").find(t => t.name === name);
const target = this.get("model.targets").find(t => t.name === name);
this.set("currentTarget", target && target.id);
},
@computed("currentTarget")
currentTargetName(id) {
const target = this.get("targets").find(t => t.id === parseInt(id, 10));
const target = this.get("model.targets").find(
t => t.id === parseInt(id, 10)
);
return target && target.name;
},
@computed("fieldName")
activeSectionMode(fieldName) {
if (fieldName === "yaml") return "yaml";
return fieldName && fieldName.indexOf("scss") > -1 ? "scss" : "html";
},
@computed("currentTargetName", "fieldName", "saving")
error(target, fieldName) {
return this.get("model").getError(target, fieldName);
},
@computed("fieldName", "currentTargetName")
editorId(fieldName, currentTarget) {
return fieldName + "|" + currentTarget;
},
@computed("fieldName", "currentTargetName", "model")
activeSection: {
get(fieldName, target, model) {
return model.getField(target, fieldName);
},
set(value, fieldName, target, model) {
model.setField(target, fieldName, value);
return value;
}
},
@computed("currentTargetName", "onlyOverridden")
fields(target, onlyOverridden) {
let fields = this.fieldsForTarget(target);
if (onlyOverridden) {
const model = this.get("model");
const targetName = this.get("currentTargetName");
fields = fields.filter(name => model.hasEdited(targetName, name));
}
return fields.map(name => {
let hash = {
key: `admin.customize.theme.${name}.text`,
name: name
};
if (name.indexOf("_tag") > 0) {
hash.icon = "file-text-o";
}
hash.title = I18n.t(`admin.customize.theme.${name}.title`);
return hash;
});
},
@computed("maximized")
maximizeIcon(maximized) {
return maximized ? "discourse-compress" : "discourse-expand";
},
@computed("model.isSaving")
saveButtonText(isSaving) {
return isSaving ? I18n.t("saving") : I18n.t("admin.customize.save");
@ -171,18 +36,31 @@ export default Ember.Controller.extend({
actions: {
save() {
this.set("saving", true);
this.get("model")
.saveChanges("theme_fields")
.finally(() => {
this.set("saving", false);
});
this.model.saveChanges("theme_fields").finally(() => {
this.set("saving", false);
});
},
toggleMaximize: function() {
this.toggleProperty("maximized");
Ember.run.next(() => {
this.appEvents.trigger("ace:resize");
});
fieldAdded(target, name) {
this.replaceRoute(this.editRouteName, this.get("model.id"), target, name);
},
onlyOverriddenChanged(onlyShowOverridden) {
if (onlyShowOverridden) {
if (!this.model.hasEdited(this.currentTargetName, this.fieldName)) {
let firstTarget = this.get("model.targets").find(t => t.edited);
let firstField = this.get(`model.fields.${firstTarget.name}`).find(
f => f.edited
);
this.replaceRoute(
this.editRouteName,
this.get("model.id"),
firstTarget.name,
firstField.name
);
}
}
}
}
});

@ -100,7 +100,7 @@ export default Ember.Controller.extend({
},
commitSwitchType() {
const model = this.get("model");
const model = this.model;
const newValue = !model.get("component");
model.set("component", newValue);
@ -141,16 +141,20 @@ export default Ember.Controller.extend({
},
transitionToEditRoute() {
this.transitionToRoute(
this.get("editRouteName"),
this.editRouteName,
this.get("model.id"),
"common",
"scss"
);
},
sourceIsHttp: Ember.computed.match(
"model.remote_theme.remote_url",
/^http(s)?:\/\//
),
actions: {
updateToLatest() {
this.set("updatingRemote", true);
this.get("model")
this.model
.updateToLatest()
.catch(popupAjaxError)
.finally(() => {
@ -160,7 +164,7 @@ export default Ember.Controller.extend({
checkForThemeUpdates() {
this.set("updatingRemote", true);
this.get("model")
this.model
.checkForUpdates()
.catch(popupAjaxError)
.finally(() => {
@ -173,7 +177,7 @@ export default Ember.Controller.extend({
},
addUpload(info) {
let model = this.get("model");
let model = this.model;
model.setField("common", info.name, "", info.upload_id, THEME_UPLOAD_VAR);
model.saveChanges("theme_fields").catch(e => popupAjaxError(e));
},
@ -182,23 +186,23 @@ export default Ember.Controller.extend({
this.set("colorSchemeId", this.get("model.color_scheme_id"));
},
changeScheme() {
let schemeId = this.get("colorSchemeId");
let schemeId = this.colorSchemeId;
this.set(
"model.color_scheme_id",
schemeId === null ? null : parseInt(schemeId)
);
this.get("model").saveChanges("color_scheme_id");
this.model.saveChanges("color_scheme_id");
},
startEditingName() {
this.set("oldName", this.get("model.name"));
this.set("editingName", true);
},
cancelEditingName() {
this.set("model.name", this.get("oldName"));
this.set("model.name", this.oldName);
this.set("editingName", false);
},
finishedEditingName() {
this.get("model").saveChanges("name");
this.model.saveChanges("name");
this.set("editingName", false);
},
@ -218,10 +222,10 @@ export default Ember.Controller.extend({
},
applyDefault() {
const model = this.get("model");
const model = this.model;
model.saveChanges("default").then(() => {
if (model.get("default")) {
this.get("allThemes").forEach(theme => {
this.allThemes.forEach(theme => {
if (theme !== model && theme.get("default")) {
theme.set("default", false);
}
@ -231,13 +235,13 @@ export default Ember.Controller.extend({
},
applyUserSelectable() {
this.get("model").saveChanges("user_selectable");
this.model.saveChanges("user_selectable");
},
addChildTheme() {
let themeId = parseInt(this.get("selectedChildThemeId"));
let theme = this.get("allThemes").findBy("id", themeId);
this.get("model").addChildTheme(theme);
let themeId = parseInt(this.selectedChildThemeId);
let theme = this.allThemes.findBy("id", themeId);
this.model.addChildTheme(theme);
},
removeUpload(upload) {
@ -247,26 +251,28 @@ export default Ember.Controller.extend({
I18n.t("yes_value"),
result => {
if (result) {
this.get("model").removeField(upload);
this.model.removeField(upload);
}
}
);
},
removeChildTheme(theme) {
this.get("model").removeChildTheme(theme);
this.model.removeChildTheme(theme);
},
destroy() {
return bootbox.confirm(
I18n.t("admin.customize.delete_confirm"),
I18n.t("admin.customize.delete_confirm", {
theme_name: this.get("model.name")
}),
I18n.t("no_value"),
I18n.t("yes_value"),
result => {
if (result) {
const model = this.get("model");
const model = this.model;
model.destroyRecord().then(() => {
this.get("allThemes").removeObject(model);
this.allThemes.removeObject(model);
this.transitionToRoute("adminCustomizeThemes");
});
}
@ -276,12 +282,12 @@ export default Ember.Controller.extend({
switchType() {
const relatives = this.get("model.component")
? this.get("parentThemes")
? this.parentThemes
: this.get("model.childThemes");
if (relatives && relatives.length > 0) {
const names = relatives.map(relative => relative.get("name"));
bootbox.confirm(
I18n.t(`${this.get("convertKey")}_alert`, {
I18n.t(`${this.convertKey}_alert`, {
relatives: names.join(", ")
}),
I18n.t("no_value"),

@ -12,5 +12,10 @@ export default Ember.Controller.extend({
@computed("model", "model.@each.component")
childThemes(themes) {
return themes.filter(t => t.get("component"));
},
@computed("model", "model.@each.component")
installedThemes(themes) {
return themes.map(t => t.name);
}
});

@ -1,12 +1,12 @@
import { setting } from "discourse/lib/computed";
import computed from "ember-addons/ember-computed-decorators";
import AdminDashboardNext from "admin/models/admin-dashboard-next";
import AdminDashboard from "admin/models/admin-dashboard";
import Report from "admin/models/report";
import PeriodComputationMixin from "admin/mixins/period-computation";
function staticReport(reportType) {
return function() {
return Ember.makeArray(this.get("reports")).find(
return Ember.makeArray(this.reports).find(
report => report.type === reportType
);
}.property("reports.[]");
@ -27,8 +27,8 @@ export default Ember.Controller.extend(PeriodComputationMixin, {
@computed
activityMetricsFilters() {
return {
startDate: this.get("lastMonth"),
endDate: this.get("today")
startDate: this.lastMonth,
endDate: this.today
};
},
@ -45,7 +45,7 @@ export default Ember.Controller.extend(PeriodComputationMixin, {
startDate: moment()
.subtract(6, "days")
.startOf("day"),
endDate: this.get("today")
endDate: this.today
};
},
@ -55,7 +55,7 @@ export default Ember.Controller.extend(PeriodComputationMixin, {
startDate: moment()
.subtract(1, "month")
.startOf("day"),
endDate: this.get("today")
endDate: this.today
};
},
@ -78,28 +78,28 @@ export default Ember.Controller.extend(PeriodComputationMixin, {
storageReport: staticReport("storage_report"),
fetchDashboard() {
if (this.get("isLoading")) return;
if (this.isLoading) return;
if (
!this.get("dashboardFetchedAt") ||
!this.dashboardFetchedAt ||
moment()
.subtract(30, "minutes")
.toDate() > this.get("dashboardFetchedAt")
.toDate() > this.dashboardFetchedAt
) {
this.set("isLoading", true);
AdminDashboardNext.fetchGeneral()
.then(adminDashboardNextModel => {
AdminDashboard.fetchGeneral()
.then(adminDashboardModel => {
this.setProperties({
dashboardFetchedAt: new Date(),
model: adminDashboardNextModel,
reports: Ember.makeArray(adminDashboardNextModel.reports).map(x =>
model: adminDashboardModel,
reports: Ember.makeArray(adminDashboardModel.reports).map(x =>
Report.create(x)
)
});
})
.catch(e => {
this.get("exceptionController").set("thrown", e.jqXHR);
this.exceptionController.set("thrown", e.jqXHR);
this.replaceRoute("exception");
})
.finally(() => this.set("isLoading", false));
@ -111,13 +111,6 @@ export default Ember.Controller.extend(PeriodComputationMixin, {
return { startDate, endDate };
},
@computed("model.attributes.updated_at")
updatedTimestamp(updatedAt) {
return moment(updatedAt)
.tz(moment.tz.guess())
.format("LLL");
},
_reportsForPeriodURL(period) {
return Discourse.getURL(`/admin?period=${period}`);
}

@ -1,90 +0,0 @@
import { setting } from "discourse/lib/computed";
import computed from "ember-addons/ember-computed-decorators";
import AdminDashboardNext from "admin/models/admin-dashboard-next";
import VersionCheck from "admin/models/version-check";
const PROBLEMS_CHECK_MINUTES = 1;
export default Ember.Controller.extend({
isLoading: false,
dashboardFetchedAt: null,
exceptionController: Ember.inject.controller("exception"),
showVersionChecks: setting("version_checks"),
@computed("problems.length")
foundProblems(problemsLength) {
return this.currentUser.get("admin") && (problemsLength || 0) > 0;
},
fetchProblems() {
if (this.get("isLoadingProblems")) return;
if (
!this.get("problemsFetchedAt") ||
moment()
.subtract(PROBLEMS_CHECK_MINUTES, "minutes")
.toDate() > this.get("problemsFetchedAt")
) {
this._loadProblems();
}
},
fetchDashboard() {
const versionChecks = this.siteSettings.version_checks;
if (this.get("isLoading") || !versionChecks) return;
if (
!this.get("dashboardFetchedAt") ||
moment()
.subtract(30, "minutes")
.toDate() > this.get("dashboardFetchedAt")
) {
this.set("isLoading", true);
AdminDashboardNext.fetch()
.then(model => {
let properties = {
dashboardFetchedAt: new Date()
};
if (versionChecks) {
properties.versionCheck = VersionCheck.create(model.version_check);
}
this.setProperties(properties);
})
.catch(e => {
this.get("exceptionController").set("thrown", e.jqXHR);
this.replaceRoute("exception");
})
.finally(() => {
this.set("isLoading", false);
});
}
},
_loadProblems() {
this.setProperties({
loadingProblems: true,
problemsFetchedAt: new Date()
});
AdminDashboardNext.fetchProblems()
.then(model => this.set("problems", model.problems))
.finally(() => this.set("loadingProblems", false));
},
@computed("problemsFetchedAt")
problemsTimestamp(problemsFetchedAt) {
return moment(problemsFetchedAt)
.locale("en")
.format("LLL");
},
actions: {
refreshProblems() {
this._loadProblems();
}
}
});

@ -1,78 +1,90 @@
import AdminDashboard from "admin/models/admin-dashboard";
import Report from "admin/models/report";
import AdminUser from "admin/models/admin-user";
import { setting } from "discourse/lib/computed";
import computed from "ember-addons/ember-computed-decorators";
import AdminDashboard from "admin/models/admin-dashboard";
import VersionCheck from "admin/models/version-check";
const ATTRIBUTES = [
"admins",
"moderators",
"silenced",
"suspended",
"top_traffic_sources",
"top_referred_topics",
"updated_at"
];
const PROBLEMS_CHECK_MINUTES = 1;
const REPORTS = [
"global_reports",
"page_view_reports",
"private_message_reports",
"http_reports",
"user_reports",
"mobile_reports"
];
// This controller supports the default interface when you enter the admin section.
export default Ember.Controller.extend({
loading: null,
versionCheck: null,
isLoading: false,
dashboardFetchedAt: null,
exceptionController: Ember.inject.controller("exception"),
showVersionChecks: setting("version_checks"),
@computed("problems.length")
foundProblems(problemsLength) {
return this.currentUser.get("admin") && (problemsLength || 0) > 0;
},
fetchProblems() {
if (this.isLoadingProblems) return;
if (
!this.problemsFetchedAt ||
moment()
.subtract(PROBLEMS_CHECK_MINUTES, "minutes")
.toDate() > this.problemsFetchedAt
) {
this._loadProblems();
}
},
fetchDashboard() {
const versionChecks = this.siteSettings.version_checks;
if (this.isLoading || !versionChecks) return;
if (
!this.get("dashboardFetchedAt") ||
!this.dashboardFetchedAt ||
moment()
.subtract(30, "minutes")
.toDate() > this.get("dashboardFetchedAt")
.toDate() > this.dashboardFetchedAt
) {
this.set("loading", true);
AdminDashboard.find()
.then(d => {
this.set("dashboardFetchedAt", new Date());
this.set("isLoading", true);
REPORTS.forEach(name =>
this.set(name, d[name].map(r => Report.create(r)))
);
AdminDashboard.fetch()
.then(model => {
let properties = {
dashboardFetchedAt: new Date()
};
const topReferrers = d.top_referrers;
if (topReferrers && topReferrers.data) {
d.top_referrers.data = topReferrers.data.map(user =>
AdminUser.create(user)
);
this.set("top_referrers", topReferrers);
if (versionChecks) {
properties.versionCheck = VersionCheck.create(model.version_check);
}
ATTRIBUTES.forEach(a => this.set(a, d[a]));
this.setProperties(properties);
})
.catch(e => {
this.get("exceptionController").set("thrown", e.jqXHR);
this.exceptionController.set("thrown", e.jqXHR);
this.replaceRoute("exception");
})
.finally(() => {
this.set("loading", false);
this.set("isLoading", false);
});
}
},
@computed("updated_at")
updatedTimestamp(updatedAt) {
return moment(updatedAt).format("LLL");
_loadProblems() {
this.setProperties({
loadingProblems: true,
problemsFetchedAt: new Date()
});
AdminDashboard.fetchProblems()
.then(model => this.set("problems", model.problems))
.finally(() => this.set("loadingProblems", false));
},
@computed("problemsFetchedAt")
problemsTimestamp(problemsFetchedAt) {
return moment(problemsFetchedAt)
.locale("en")
.format("LLL");
},
actions: {
showTrafficReport() {
this.set("showTrafficReport", true);
refreshProblems() {
this._loadProblems();
}
}
});

@ -14,7 +14,7 @@ export default Ember.Controller.extend({
ajax("/admin/email/advanced-test", {
type: "POST",
data: { email: this.get("email") }
data: { email: this.email }
})
.then(data => {
this.setProperties({

@ -1,9 +1,8 @@
import AdminEmailLogsController from "admin/controllers/admin-email-logs";
import debounce from "discourse/lib/debounce";
import EmailLog from "admin/models/email-log";
export default AdminEmailLogsController.extend({
filterEmailLogs: debounce(function() {
EmailLog.findAll(this.get("filter")).then(logs => this.set("model", logs));
}, 250).observes("filter.{user,address,type}")
this.loadLogs();
}, 250).observes("filter.{status,user,address,type}")
});

@ -1,25 +0,0 @@
import IncomingEmail from "admin/models/incoming-email";
export default Ember.Controller.extend({
loading: false,
actions: {
loadMore() {
if (this.get("loading") || this.get("model.allLoaded")) {
return;
}
this.set("loading", true);
IncomingEmail.findAll(this.get("filter"), this.get("model.length"))
.then(incoming => {
if (incoming.length < 50) {
this.get("model").set("allLoaded", true);
}
this.get("model").addObjects(incoming);
})
.finally(() => {
this.set("loading", false);
});
}
}
});

@ -30,10 +30,10 @@ export default Ember.Controller.extend({
ajax("/admin/email/test", {
type: "POST",
data: { email_address: this.get("testEmailAddress") }
data: { email_address: this.testEmailAddress }
})
.then(response =>
this.set("sentTestEmailMessage", response.send_test_email_message)
this.set("sentTestEmailMessage", response.sent_test_email_message)
)
.catch(e => {
if (e.responseJSON && e.responseJSON.errors) {

@ -3,23 +3,34 @@ import EmailLog from "admin/models/email-log";
export default Ember.Controller.extend({
loading: false,
loadLogs(sourceModel, loadMore) {
if ((loadMore && this.loading) || this.get("model.allLoaded")) {
return;
}
this.set("loading", true);
sourceModel = sourceModel || EmailLog;
return sourceModel
.findAll(this.filter, loadMore ? this.get("model.length") : null)
.then(logs => {
if (this.model && loadMore && logs.length < 50) {
this.model.set("allLoaded", true);
}
if (this.model && loadMore) {
this.model.addObjects(logs);
} else {
this.set("model", logs);
}
})
.finally(() => this.set("loading", false));
},
actions: {
loadMore() {
if (this.get("loading") || this.get("model.allLoaded")) {
return;
}
this.set("loading", true);
return EmailLog.findAll(this.get("filter"), this.get("model.length"))
.then(logs => {
if (logs.length < 50) {
this.get("model").set("allLoaded", true);
}
this.get("model").addObjects(logs);
})
.finally(() => {
this.set("loading", false);
});
this.loadLogs(EmailLog, true);
}
}
});

@ -12,18 +12,18 @@ export default Ember.Controller.extend({
actions: {
refresh() {
const model = this.get("model");
const model = this.model;
this.set("loading", true);
this.set("sentEmail", false);
let username = this.get("username");
let username = this.username;
if (!username) {
username = this.currentUser.get("username");
this.set("username", username);
}
EmailPreview.findDigest(username, this.get("lastSeen")).then(email => {
EmailPreview.findDigest(username, this.lastSeen).then(email => {
model.setProperties(
email.getProperties("html_content", "text_content")
);
@ -39,11 +39,7 @@ export default Ember.Controller.extend({
this.set("sendingEmail", true);
this.set("sentEmail", false);
EmailPreview.sendDigest(
this.get("username"),
this.get("lastSeen"),
this.get("email")
)
EmailPreview.sendDigest(this.username, this.lastSeen, this.email)
.then(result => {
if (result.errors) {
bootbox.alert(result.errors);

@ -1,11 +1,15 @@
import AdminEmailIncomingsController from "admin/controllers/admin-email-incomings";
import AdminEmailLogsController from "admin/controllers/admin-email-logs";
import debounce from "discourse/lib/debounce";
import IncomingEmail from "admin/models/incoming-email";
export default AdminEmailIncomingsController.extend({
export default AdminEmailLogsController.extend({
filterIncomingEmails: debounce(function() {
IncomingEmail.findAll(this.get("filter")).then(incomings =>
this.set("model", incomings)
);
}, 250).observes("filter.{from,to,subject}")
this.loadLogs(IncomingEmail);
}, 250).observes("filter.{status,from,to,subject}"),
actions: {
loadMore() {
this.loadLogs(IncomingEmail, true);
}
}
});

@ -1,11 +1,15 @@
import AdminEmailIncomingsController from "admin/controllers/admin-email-incomings";
import AdminEmailLogsController from "admin/controllers/admin-email-logs";
import debounce from "discourse/lib/debounce";
import IncomingEmail from "admin/models/incoming-email";
export default AdminEmailIncomingsController.extend({
export default AdminEmailLogsController.extend({
filterIncomingEmails: debounce(function() {
IncomingEmail.findAll(this.get("filter")).then(incomings =>
this.set("model", incomings)
);
}, 250).observes("filter.{from,to,subject,error}")
this.loadLogs(IncomingEmail);
}, 250).observes("filter.{status,from,to,subject,error}"),
actions: {
loadMore() {
this.loadLogs(IncomingEmail, true);
}
}
});

@ -1,9 +1,8 @@
import AdminEmailLogsController from "admin/controllers/admin-email-logs";
import debounce from "discourse/lib/debounce";
import EmailLog from "admin/models/email-log";
export default AdminEmailLogsController.extend({
filterEmailLogs: debounce(function() {
EmailLog.findAll(this.get("filter")).then(logs => this.set("model", logs));
}, 250).observes("filter.{user,address,type,reply_key}")
this.loadLogs();
}, 250).observes("filter.{status,user,address,type,reply_key}")
});

@ -1,9 +1,8 @@
import AdminEmailLogsController from "admin/controllers/admin-email-logs";
import debounce from "discourse/lib/debounce";
import EmailLog from "admin/models/email-log";
export default AdminEmailLogsController.extend({
filterEmailLogs: debounce(function() {
EmailLog.findAll(this.get("filter")).then(logs => this.set("model", logs));
}, 250).observes("filter.{user,address,type}")
this.loadLogs();
}, 250).observes("filter.{status,user,address,type}")
});

Some files were not shown because too many files have changed in this diff Show More