Merge master
This commit is contained in:
commit
f83a39f8ba
@ -11,7 +11,6 @@ lib/javascripts/messageformat.js
|
||||
lib/javascripts/moment.js
|
||||
lib/javascripts/moment_locale/
|
||||
lib/highlight_js/
|
||||
lib/es6_module_transpiler/support/es6-module-transpiler.js
|
||||
public/javascripts/
|
||||
spec/phantom_js/smoke_test.js
|
||||
vendor/
|
||||
|
||||
23
.eslintrc
23
.eslintrc
@ -14,23 +14,19 @@
|
||||
{"Ember":true,
|
||||
"jQuery":true,
|
||||
"$":true,
|
||||
"QUnit":true,
|
||||
"RSVP":true,
|
||||
"Discourse":true,
|
||||
"Em":true,
|
||||
"Handlebars":true,
|
||||
"I18n":true,
|
||||
"bootbox":true,
|
||||
"module":true,
|
||||
"moduleFor":true,
|
||||
"moduleForComponent":true,
|
||||
"Pretender":true,
|
||||
"sandbox":true,
|
||||
"controllerFor":true,
|
||||
"test":true,
|
||||
"ok":true,
|
||||
"not":true,
|
||||
"expect":true,
|
||||
"equal":true,
|
||||
"visit":true,
|
||||
"andThen":true,
|
||||
"click":true,
|
||||
@ -45,18 +41,21 @@
|
||||
"visible":true,
|
||||
"invisible":true,
|
||||
"asyncRender":true,
|
||||
"selectDropdown":true,
|
||||
"selectKit":true,
|
||||
"expandSelectKit":true,
|
||||
"collapseSelectKit":true,
|
||||
"selectKitSelectRowByValue":true,
|
||||
"selectKitSelectRowByName":true,
|
||||
"selectKitSelectRowByIndex":true,
|
||||
"selectKitSelectNoneRow":true,
|
||||
"selectKitFillInFilter":true,
|
||||
"asyncTestDiscourse":true,
|
||||
"fixture":true,
|
||||
"find":true,
|
||||
"sinon":true,
|
||||
"moment":true,
|
||||
"start":true,
|
||||
"_":true,
|
||||
"alert":true,
|
||||
"containsInstance":true,
|
||||
"deepEqual":true,
|
||||
"notEqual":true,
|
||||
"define":true,
|
||||
"require":true,
|
||||
"requirejs":true,
|
||||
@ -100,7 +99,9 @@
|
||||
"wrap-iife": [
|
||||
2,
|
||||
"inside"
|
||||
]
|
||||
],
|
||||
"no-mixed-spaces-and-tabs": 2,
|
||||
"no-trailing-spaces": 2
|
||||
},
|
||||
"parser": "babel-eslint"
|
||||
}
|
||||
|
||||
7
.gitignore
vendored
7
.gitignore
vendored
@ -36,6 +36,7 @@ config/discourse.conf
|
||||
*.sql.gz
|
||||
/db/*.sqlite3
|
||||
/db/structure.sql
|
||||
/db/schema.rb
|
||||
|
||||
# Ignore all logfiles and tempfiles.
|
||||
/log/*.log
|
||||
@ -43,6 +44,7 @@ config/discourse.conf
|
||||
/logfile
|
||||
log/
|
||||
bootsnap-load-path-cache
|
||||
bootsnap-compile-cache/
|
||||
|
||||
# Ignore plugins except for the bundled ones.
|
||||
/plugins/*
|
||||
@ -52,6 +54,7 @@ bootsnap-load-path-cache
|
||||
!/plugins/discourse-details/
|
||||
!/plugins/discourse-nginx-performance-report
|
||||
!/plugins/discourse-narrative-bot
|
||||
!/plugins/discourse-presence
|
||||
/plugins/*/auto_generated/
|
||||
|
||||
/spec/fixtures/plugins/my_plugin/auto_generated
|
||||
@ -116,3 +119,7 @@ vendor/bundle/*
|
||||
|
||||
#ignore jetbrains ide file
|
||||
*.iml
|
||||
|
||||
# ignore nodejs files
|
||||
/node_modules
|
||||
/package-lock.json
|
||||
|
||||
@ -1,12 +0,0 @@
|
||||
skip_missing_workers: true
|
||||
allow_lossy: false
|
||||
# PNG
|
||||
advpng: false
|
||||
optipng:
|
||||
level: 2
|
||||
pngcrush: false
|
||||
pngout: false
|
||||
pngquant: false
|
||||
# JPG
|
||||
jpegrecompress: false
|
||||
timeout: 15
|
||||
23
.overcommit.yml
Normal file
23
.overcommit.yml
Normal file
@ -0,0 +1,23 @@
|
||||
# Use this file to configure the Overcommit hooks you wish to use. This will
|
||||
# extend the default configuration defined in:
|
||||
# https://github.com/brigade/overcommit/blob/master/config/default.yml
|
||||
#
|
||||
# At the topmost level of this YAML file is a key representing type of hook
|
||||
# being run (e.g. pre-commit, commit-msg, etc.). Within each type you can
|
||||
# customize each hook, such as whether to only run it on certain files (via
|
||||
# `include`), whether to only display output if it fails (via `quiet`), etc.
|
||||
#
|
||||
# For a complete list of hooks, see:
|
||||
# https://github.com/brigade/overcommit/tree/master/lib/overcommit/hook
|
||||
#
|
||||
# For a complete list of options that you can use to customize hooks, see:
|
||||
# https://github.com/brigade/overcommit#configuration
|
||||
|
||||
PreCommit:
|
||||
RuboCop:
|
||||
enabled: true
|
||||
command: ['bundle', 'exec', 'rubocop']
|
||||
EsLint:
|
||||
enabled: true
|
||||
command: ['eslint', '--ext', '.es6', '-f', 'compact']
|
||||
include: '**/*.es6'
|
||||
119
.rubocop.yml
119
.rubocop.yml
@ -1,14 +1,113 @@
|
||||
AllCops:
|
||||
TargetRubyVersion: 2.3
|
||||
TargetRubyVersion: 2.4
|
||||
DisabledByDefault: true
|
||||
Exclude:
|
||||
- 'db/schema.rb'
|
||||
- 'bundle/**/*'
|
||||
- 'vendor/**/*'
|
||||
- 'node_modules/**/*'
|
||||
- 'public/**/*'
|
||||
|
||||
Metrics/LineLength:
|
||||
Max: 120
|
||||
# Prefer &&/|| over and/or.
|
||||
Style/AndOr:
|
||||
Enabled: true
|
||||
|
||||
Metrics/MethodLength:
|
||||
# Do not use braces for hash literals when they are the last argument of a
|
||||
# method call.
|
||||
Style/BracesAroundHashParameters:
|
||||
Enabled: true
|
||||
|
||||
# Align `when` with `case`.
|
||||
Layout/CaseIndentation:
|
||||
Enabled: true
|
||||
|
||||
# Align comments with method definitions.
|
||||
Layout/CommentIndentation:
|
||||
Enabled: true
|
||||
|
||||
# No extra empty lines.
|
||||
Layout/EmptyLines:
|
||||
Enabled: true
|
||||
|
||||
# Use Ruby >= 1.9 syntax for hashes. Prefer { a: :b } over { :a => :b }.
|
||||
Style/HashSyntax:
|
||||
Enabled: true
|
||||
|
||||
# Two spaces, no tabs (for indentation).
|
||||
Layout/IndentationWidth:
|
||||
Enabled: true
|
||||
|
||||
Layout/SpaceAfterColon:
|
||||
Enabled: true
|
||||
|
||||
Layout/SpaceAfterComma:
|
||||
Enabled: true
|
||||
|
||||
Layout/SpaceAroundEqualsInParameterDefault:
|
||||
Enabled: true
|
||||
|
||||
Layout/SpaceAroundKeyword:
|
||||
Enabled: true
|
||||
|
||||
Layout/SpaceAroundOperators:
|
||||
Enabled: true
|
||||
|
||||
Layout/SpaceBeforeFirstArg:
|
||||
Enabled: true
|
||||
|
||||
# Defining a method with parameters needs parentheses.
|
||||
Style/MethodDefParentheses:
|
||||
Enabled: true
|
||||
|
||||
# Use `foo {}` not `foo{}`.
|
||||
Layout/SpaceBeforeBlockBraces:
|
||||
Enabled: true
|
||||
|
||||
# Use `foo { bar }` not `foo {bar}`.
|
||||
Layout/SpaceInsideBlockBraces:
|
||||
Enabled: true
|
||||
|
||||
# Use `{ a: 1 }` not `{a:1}`.
|
||||
Layout/SpaceInsideHashLiteralBraces:
|
||||
Enabled: true
|
||||
|
||||
Layout/SpaceInsideParens:
|
||||
Enabled: true
|
||||
|
||||
# Detect hard tabs, no hard tabs.
|
||||
Layout/Tab:
|
||||
Enabled: true
|
||||
|
||||
# Blank lines should not have any spaces.
|
||||
Layout/TrailingBlankLines:
|
||||
Enabled: true
|
||||
|
||||
# No trailing whitespace.
|
||||
Layout/TrailingWhitespace:
|
||||
Enabled: true
|
||||
|
||||
Lint/Debugger:
|
||||
Enabled: true
|
||||
|
||||
Lint/BlockAlignment:
|
||||
Enabled: true
|
||||
|
||||
# Align `end` with the matching keyword or starting expression except for
|
||||
# assignments, where it should be aligned with the LHS.
|
||||
Lint/EndAlignment:
|
||||
Enabled: true
|
||||
EnforcedStyleAlignWith: variable
|
||||
|
||||
# Use my_method(my_arg) not my_method( my_arg ) or my_method my_arg.
|
||||
Lint/RequireParentheses:
|
||||
Enabled: true
|
||||
|
||||
Layout/MultilineMethodCallIndentation:
|
||||
Enabled: true
|
||||
EnforcedStyle: indented
|
||||
|
||||
Layout/AlignHash:
|
||||
Enabled: true
|
||||
|
||||
Bundler/OrderedGems:
|
||||
Enabled: false
|
||||
|
||||
Style/Documentation:
|
||||
Enabled: false
|
||||
|
||||
Style/FrozenStringLiteralComment:
|
||||
Enabled: False
|
||||
|
||||
@ -10,6 +10,7 @@ env:
|
||||
- "RAILS_MASTER=0 QUNIT_RUN=0 RUN_LINT=1"
|
||||
|
||||
addons:
|
||||
chrome: stable
|
||||
postgresql: 9.5
|
||||
apt:
|
||||
packages:
|
||||
@ -20,8 +21,11 @@ addons:
|
||||
|
||||
matrix:
|
||||
fast_finish: true
|
||||
allow_failures:
|
||||
- rvm: 2.5.0
|
||||
|
||||
rvm:
|
||||
- 2.5.0
|
||||
- 2.4.2
|
||||
- 2.3.4
|
||||
|
||||
@ -44,12 +48,14 @@ before_install:
|
||||
- git clone --depth=1 https://github.com/discourse/discourse-canned-replies.git plugins/discourse-canned-replies
|
||||
- git clone --depth=1 https://github.com/discourse/discourse-chat-integration.git plugins/discourse-chat-integration
|
||||
- git clone --depth=1 https://github.com/discourse/discourse-assign.git plugins/discourse-assign
|
||||
- git clone --depth=1 https://github.com/discourse/discourse-patreon.git plugins/discourse-patreon
|
||||
- export PATH=$HOME/.yarn/bin:$PATH
|
||||
|
||||
install:
|
||||
- bash -c "if [ '$RAILS_MASTER' == '1' ]; then bundle update --retry=3 --jobs=3 arel rails seed-fu; fi"
|
||||
- bash -c "if [ '$RAILS_MASTER' == '0' ]; then bundle install --without development --deployment --retry=3 --jobs=3; fi"
|
||||
- bash -c "if [ '$RUN_LINT' == '1' ]; then yarn global add eslint babel-eslint; fi"
|
||||
- bash -c "if [ '$QUNIT_RUN' == '1' ]; then yarn install --dev; fi"
|
||||
|
||||
script:
|
||||
- |
|
||||
@ -65,7 +71,8 @@ script:
|
||||
bundle exec rake db:create db:migrate
|
||||
|
||||
if [ '$QUNIT_RUN' == '1' ]; then
|
||||
LOAD_PLUGINS=1 bundle exec rake qunit:test['400000']
|
||||
bundle exec rake qunit:test['400000'] && \
|
||||
bundle exec rake plugin:spec
|
||||
else
|
||||
bundle exec rspec && bundle exec rake plugin:spec
|
||||
fi
|
||||
|
||||
18
.tx/config
18
.tx/config
@ -26,12 +26,6 @@ source_file = plugins/poll/config/locales/server.en.yml
|
||||
source_lang = en
|
||||
type = YML
|
||||
|
||||
[discourse-org.imgurserverenyml]
|
||||
file_filter = vendor/gems/discourse_imgur/lib/discourse_imgur/locale/server.<lang>.yml
|
||||
source_file = vendor/gems/discourse_imgur/lib/discourse_imgur/locale/server.en.yml
|
||||
source_lang = en
|
||||
type = YML
|
||||
|
||||
[discourse-org.narrativeclientenyml]
|
||||
file_filter = plugins/discourse-narrative-bot/config/locales/client.<lang>.yml
|
||||
source_file = plugins/discourse-narrative-bot/config/locales/client.en.yml
|
||||
@ -44,6 +38,18 @@ source_file = plugins/discourse-narrative-bot/config/locales/server.en.yml
|
||||
source_lang = en
|
||||
type = YML
|
||||
|
||||
[discourse-org.discourse-presenceclientenyml]
|
||||
file_filter = plugins/discourse-presence/config/locales/client.<lang>.yml
|
||||
source_file = plugins/discourse-presence/config/locales/client.en.yml
|
||||
source_lang = en
|
||||
type = YML
|
||||
|
||||
[discourse-org.discourse-presenceserverenyml]
|
||||
file_filter = plugins/discourse-presence/config/locales/server.<lang>.yml
|
||||
source_file = plugins/discourse-presence/config/locales/server.en.yml
|
||||
source_lang = en
|
||||
type = YML
|
||||
|
||||
[discourse-org.403html]
|
||||
file_filter = public/403.<lang>.html
|
||||
source_file = public/403.html
|
||||
|
||||
3
Brewfile
3
Brewfile
@ -14,6 +14,3 @@ brew 'postgresql'
|
||||
|
||||
# install the Redis datastore
|
||||
brew 'redis'
|
||||
|
||||
# install headless Javascript testing library
|
||||
brew 'phantomjs'
|
||||
|
||||
77
Gemfile
77
Gemfile
@ -14,28 +14,21 @@ if rails_master?
|
||||
gem 'rails', git: 'https://github.com/rails/rails.git'
|
||||
gem 'seed-fu', git: 'https://github.com/SamSaffron/seed-fu.git', branch: 'discourse'
|
||||
else
|
||||
# Rails 5 is going to ship with Action Cable, we have no use for it as
|
||||
# we already ship MessageBus, AC introduces dependencies on Event Machine,
|
||||
# Celluloid and Faye Web Sockets.
|
||||
#
|
||||
# Note this means upgrading Rails is more annoying, to do so, comment out the
|
||||
# explicit dependencies, and add gem 'rails', bundle update rails and then
|
||||
# comment back the explicit dependencies. Leaving this in a comment till we
|
||||
# upgrade to Rails 5
|
||||
#
|
||||
# gem 'activesupport'
|
||||
# gem 'actionpack'
|
||||
# gem 'activerecord'
|
||||
# gem 'actionmailer'
|
||||
# gem 'activejob'
|
||||
# gem 'railties'
|
||||
# gem 'sprockets-rails'
|
||||
gem 'rails', '~> 4.2'
|
||||
gem 'seed-fu', '~> 2.3.5'
|
||||
gem 'actionmailer', '~> 5.1'
|
||||
gem 'actionpack', '~> 5.1'
|
||||
gem 'actionview', '~> 5.1'
|
||||
gem 'activemodel', '~> 5.1'
|
||||
gem 'activerecord', '~> 5.1'
|
||||
gem 'activesupport', '~> 5.1'
|
||||
gem 'railties', '~> 5.1'
|
||||
gem 'sprockets-rails'
|
||||
gem 'seed-fu'
|
||||
end
|
||||
|
||||
gem 'mail'
|
||||
gem 'mime-types', require: 'mime/types/columnar'
|
||||
gem 'mini_mime'
|
||||
gem 'mini_suffix'
|
||||
|
||||
gem 'hiredis'
|
||||
gem 'redis', require: ["redis", "redis/connection/hiredis"]
|
||||
@ -43,7 +36,7 @@ gem 'redis-namespace'
|
||||
|
||||
gem 'active_model_serializers', '~> 0.8.3'
|
||||
|
||||
gem 'onebox'
|
||||
gem 'onebox', '1.8.33'
|
||||
|
||||
gem 'http_accept_language', '~>2.0.5', require: false
|
||||
|
||||
@ -51,7 +44,6 @@ gem 'ember-rails', '0.18.5'
|
||||
gem 'ember-source'
|
||||
gem 'ember-handlebars-template', '0.7.5'
|
||||
gem 'barber'
|
||||
gem 'babel-transpiler'
|
||||
|
||||
gem 'message_bus'
|
||||
|
||||
@ -61,28 +53,31 @@ gem 'fast_xs'
|
||||
|
||||
gem 'fast_xor'
|
||||
|
||||
gem 'fastimage', '2.1.0'
|
||||
gem 'aws-sdk', require: false
|
||||
# Forked until https://github.com/sdsykes/fastimage/pull/93 is merged
|
||||
gem 'discourse_fastimage', require: 'fastimage'
|
||||
|
||||
gem 'aws-sdk-s3', require: false
|
||||
gem 'excon', require: false
|
||||
gem 'unf', require: false
|
||||
|
||||
gem 'email_reply_trimmer', '0.1.6'
|
||||
gem 'email_reply_trimmer', '0.1.8'
|
||||
|
||||
# TODO Use official image_optim gem once https://github.com/toy/image_optim/pull/149
|
||||
# is merged.
|
||||
# Forked until https://github.com/toy/image_optim/pull/149 is merged
|
||||
gem 'discourse_image_optim', require: 'image_optim'
|
||||
gem 'multi_json'
|
||||
gem 'mustache'
|
||||
gem 'nokogiri'
|
||||
|
||||
# this may end up deprecating nokogiri
|
||||
gem 'oga', require: false
|
||||
|
||||
gem 'omniauth'
|
||||
gem 'omniauth-openid'
|
||||
gem 'openid-redis-store'
|
||||
gem 'omniauth-facebook'
|
||||
gem 'omniauth-twitter'
|
||||
gem 'omniauth-instagram'
|
||||
|
||||
# forked while https://github.com/intridea/omniauth-github/pull/41 is being upstreamd
|
||||
gem 'omniauth-github-discourse', require: 'omniauth-github'
|
||||
gem 'omniauth-github'
|
||||
|
||||
gem 'omniauth-oauth2', require: false
|
||||
|
||||
@ -94,21 +89,20 @@ gem 'r2', '~> 0.2.5', require: false
|
||||
gem 'rake'
|
||||
|
||||
gem 'thor', require: false
|
||||
gem 'rest-client'
|
||||
gem 'rinku'
|
||||
gem 'sanitize'
|
||||
gem 'sidekiq'
|
||||
|
||||
# for sidekiq web
|
||||
gem 'sinatra', require: false
|
||||
gem 'tilt', require: false
|
||||
|
||||
gem 'execjs', require: false
|
||||
gem 'mini_racer'
|
||||
gem 'highline', require: false
|
||||
gem 'rack-protection' # security
|
||||
|
||||
# Gems used only for assets and not required
|
||||
# in production environments by default.
|
||||
# allow everywhere for now cause we are allowing asset debugging in prd
|
||||
# Gems used only for assets and not required in production environments by default.
|
||||
# Allow everywhere for now cause we are allowing asset debugging in production
|
||||
group :assets do
|
||||
gem 'uglifier'
|
||||
gem 'rtlit', require: false # for css rtling
|
||||
@ -118,9 +112,6 @@ group :test do
|
||||
gem 'webmock', require: false
|
||||
gem 'fakeweb', '~> 1.3.0', require: false
|
||||
gem 'minitest', require: false
|
||||
gem 'timecop'
|
||||
# TODO: Remove once we upgrade to Rails 5.
|
||||
gem 'test_after_commit'
|
||||
end
|
||||
|
||||
group :test, :development do
|
||||
@ -137,12 +128,13 @@ group :test, :development do
|
||||
gem 'rspec-rails', require: false
|
||||
gem 'shoulda', require: false
|
||||
gem 'rspec-html-matchers'
|
||||
gem 'spork-rails'
|
||||
gem 'pry-nav'
|
||||
gem 'byebug', require: ENV['RM_INFO'].nil?
|
||||
gem 'rubocop', require: false
|
||||
end
|
||||
|
||||
group :development do
|
||||
gem 'ruby-prof', require: false
|
||||
gem 'bullet', require: !!ENV['BULLET']
|
||||
gem 'better_errors'
|
||||
gem 'binding_of_caller'
|
||||
@ -153,7 +145,7 @@ end
|
||||
# this is an optional gem, it provides a high performance replacement
|
||||
# to String#blank? a method that is called quite frequently in current
|
||||
# ActiveRecord, this may change in the future
|
||||
gem 'fast_blank' #, github: "SamSaffron/fast_blank"
|
||||
gem 'fast_blank'
|
||||
|
||||
# this provides a very efficient lru cache
|
||||
gem 'lru_redux'
|
||||
@ -173,22 +165,23 @@ gem 'rbtrace', require: false, platform: :mri
|
||||
gem 'gc_tracer', require: false, platform: :mri
|
||||
|
||||
# required for feed importing and embedding
|
||||
#
|
||||
gem 'ruby-readability', require: false
|
||||
gem 'simple-rss', require: false
|
||||
|
||||
gem 'stackprof', require: false, platform: :mri
|
||||
gem 'memory_profiler', require: false, platform: :mri
|
||||
|
||||
gem 'rmmseg-cpp', require: false
|
||||
gem 'cppjieba_rb', require: false
|
||||
|
||||
gem 'lograge', require: false
|
||||
gem 'logstash-event', require: false
|
||||
gem 'logstash-logger', require: false
|
||||
gem 'logster'
|
||||
|
||||
gem 'sassc', require: false
|
||||
|
||||
|
||||
if ENV["IMPORT"] == "1"
|
||||
gem 'mysql2'
|
||||
gem 'redcarpet'
|
||||
gem 'sqlite3', '~> 1.3.13'
|
||||
gem 'ruby-bbcode-to-md', github: 'nlalonde/ruby-bbcode-to-md'
|
||||
end
|
||||
|
||||
374
Gemfile.lock
374
Gemfile.lock
@ -1,58 +1,62 @@
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
actionmailer (4.2.8)
|
||||
actionpack (= 4.2.8)
|
||||
actionview (= 4.2.8)
|
||||
activejob (= 4.2.8)
|
||||
actionmailer (5.1.4)
|
||||
actionpack (= 5.1.4)
|
||||
actionview (= 5.1.4)
|
||||
activejob (= 5.1.4)
|
||||
mail (~> 2.5, >= 2.5.4)
|
||||
rails-dom-testing (~> 1.0, >= 1.0.5)
|
||||
actionpack (4.2.8)
|
||||
actionview (= 4.2.8)
|
||||
activesupport (= 4.2.8)
|
||||
rack (~> 1.6)
|
||||
rack-test (~> 0.6.2)
|
||||
rails-dom-testing (~> 1.0, >= 1.0.5)
|
||||
rails-dom-testing (~> 2.0)
|
||||
actionpack (5.1.4)
|
||||
actionview (= 5.1.4)
|
||||
activesupport (= 5.1.4)
|
||||
rack (~> 2.0)
|
||||
rack-test (>= 0.6.3)
|
||||
rails-dom-testing (~> 2.0)
|
||||
rails-html-sanitizer (~> 1.0, >= 1.0.2)
|
||||
actionview (4.2.8)
|
||||
activesupport (= 4.2.8)
|
||||
actionview (5.1.4)
|
||||
activesupport (= 5.1.4)
|
||||
builder (~> 3.1)
|
||||
erubis (~> 2.7.0)
|
||||
rails-dom-testing (~> 1.0, >= 1.0.5)
|
||||
erubi (~> 1.4)
|
||||
rails-dom-testing (~> 2.0)
|
||||
rails-html-sanitizer (~> 1.0, >= 1.0.3)
|
||||
active_model_serializers (0.8.3)
|
||||
activemodel (>= 3.0)
|
||||
activejob (4.2.8)
|
||||
activesupport (= 4.2.8)
|
||||
globalid (>= 0.3.0)
|
||||
activemodel (4.2.8)
|
||||
activesupport (= 4.2.8)
|
||||
builder (~> 3.1)
|
||||
activerecord (4.2.8)
|
||||
activemodel (= 4.2.8)
|
||||
activesupport (= 4.2.8)
|
||||
arel (~> 6.0)
|
||||
activesupport (4.2.8)
|
||||
activejob (5.1.4)
|
||||
activesupport (= 5.1.4)
|
||||
globalid (>= 0.3.6)
|
||||
activemodel (5.1.4)
|
||||
activesupport (= 5.1.4)
|
||||
activerecord (5.1.4)
|
||||
activemodel (= 5.1.4)
|
||||
activesupport (= 5.1.4)
|
||||
arel (~> 8.0)
|
||||
activesupport (5.1.4)
|
||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||
i18n (~> 0.7)
|
||||
minitest (~> 5.1)
|
||||
thread_safe (~> 0.3, >= 0.3.4)
|
||||
tzinfo (~> 1.1)
|
||||
addressable (2.5.1)
|
||||
public_suffix (~> 2.0, >= 2.0.2)
|
||||
annotate (2.7.1)
|
||||
annotate (2.7.2)
|
||||
activerecord (>= 3.2, < 6.0)
|
||||
rake (>= 10.4, < 12.0)
|
||||
arel (6.0.4)
|
||||
aws-sdk (2.5.3)
|
||||
aws-sdk-resources (= 2.5.3)
|
||||
aws-sdk-core (2.5.3)
|
||||
rake (>= 10.4, < 13.0)
|
||||
ansi (1.5.0)
|
||||
arel (8.0.0)
|
||||
ast (2.3.0)
|
||||
aws-partitions (1.24.0)
|
||||
aws-sdk-core (3.6.0)
|
||||
aws-partitions (~> 1.0)
|
||||
aws-sigv4 (~> 1.0)
|
||||
jmespath (~> 1.0)
|
||||
aws-sdk-resources (2.5.3)
|
||||
aws-sdk-core (= 2.5.3)
|
||||
babel-source (5.8.34)
|
||||
babel-transpiler (0.7.0)
|
||||
babel-source (>= 4.0, < 6)
|
||||
execjs (~> 2.0)
|
||||
aws-sdk-kms (1.2.0)
|
||||
aws-sdk-core (~> 3)
|
||||
aws-sigv4 (~> 1.0)
|
||||
aws-sdk-s3 (1.4.0)
|
||||
aws-sdk-core (~> 3)
|
||||
aws-sdk-kms (~> 1)
|
||||
aws-sigv4 (~> 1.0)
|
||||
aws-sigv4 (1.0.2)
|
||||
barber (0.11.2)
|
||||
ember-source (>= 1.0, < 3)
|
||||
execjs (>= 1.2, < 3)
|
||||
@ -62,33 +66,33 @@ GEM
|
||||
rack (>= 0.9.0)
|
||||
binding_of_caller (0.7.2)
|
||||
debug_inspector (>= 0.0.1)
|
||||
bootsnap (0.3.0)
|
||||
bootsnap (1.0.0)
|
||||
msgpack (~> 1.0)
|
||||
builder (3.2.3)
|
||||
bullet (5.4.2)
|
||||
bullet (5.5.1)
|
||||
activesupport (>= 3.0.0)
|
||||
uniform_notifier (~> 1.10.0)
|
||||
byebug (9.0.6)
|
||||
certified (1.0.0)
|
||||
coderay (1.1.1)
|
||||
concurrent-ruby (1.0.5)
|
||||
connection_pool (2.2.0)
|
||||
connection_pool (2.2.1)
|
||||
cppjieba_rb (0.3.0)
|
||||
crack (0.4.3)
|
||||
safe_yaml (~> 1.0.0)
|
||||
crass (1.0.2)
|
||||
debug_inspector (0.0.2)
|
||||
debug_inspector (0.0.3)
|
||||
diff-lcs (1.3)
|
||||
discourse-qunit-rails (0.0.9)
|
||||
discourse-qunit-rails (0.0.11)
|
||||
railties
|
||||
discourse_fastimage (2.1.0)
|
||||
discourse_image_optim (0.24.5)
|
||||
exifr (~> 1.2, >= 1.2.2)
|
||||
fspath (~> 3.0)
|
||||
image_size (~> 1.5)
|
||||
in_threads (~> 1.3)
|
||||
progress (~> 3.0, >= 3.0.1)
|
||||
domain_name (0.5.25)
|
||||
unf (>= 0.0.5, < 1.0.0)
|
||||
email_reply_trimmer (0.1.6)
|
||||
email_reply_trimmer (0.1.8)
|
||||
ember-data-source (2.2.1)
|
||||
ember-source (>= 1.8, < 3.0)
|
||||
ember-handlebars-template (0.7.5)
|
||||
@ -101,9 +105,10 @@ GEM
|
||||
ember-source (>= 1.1.0)
|
||||
jquery-rails (>= 1.0.17)
|
||||
railties (>= 3.1)
|
||||
ember-source (2.10.0)
|
||||
ember-source (2.13.3)
|
||||
erubi (1.6.1)
|
||||
erubis (2.7.0)
|
||||
excon (0.55.0)
|
||||
excon (0.56.0)
|
||||
execjs (2.7.0)
|
||||
exifr (1.2.5)
|
||||
fabrication (2.9.8)
|
||||
@ -115,68 +120,78 @@ GEM
|
||||
rake
|
||||
rake-compiler
|
||||
fast_xs (0.8.0)
|
||||
fastimage (2.1.0)
|
||||
ffi (1.9.18)
|
||||
flamegraph (0.9.5)
|
||||
foreman (0.82.0)
|
||||
foreman (0.84.0)
|
||||
thor (~> 0.19.1)
|
||||
fspath (3.1.0)
|
||||
gc_tracer (1.5.1)
|
||||
globalid (0.3.7)
|
||||
activesupport (>= 4.1.0)
|
||||
globalid (0.4.0)
|
||||
activesupport (>= 4.2.0)
|
||||
guess_html_encoding (0.0.11)
|
||||
hashdiff (0.3.4)
|
||||
hashie (3.5.5)
|
||||
highline (1.7.8)
|
||||
hiredis (0.6.1)
|
||||
htmlentities (4.3.4)
|
||||
http-cookie (1.0.2)
|
||||
domain_name (~> 0.5)
|
||||
http_accept_language (2.0.5)
|
||||
i18n (0.8.1)
|
||||
i18n (0.8.6)
|
||||
image_size (1.5.0)
|
||||
in_threads (1.4.0)
|
||||
jmespath (1.3.1)
|
||||
jquery-rails (4.2.1)
|
||||
jquery-rails (4.3.1)
|
||||
rails-dom-testing (>= 1, < 3)
|
||||
railties (>= 4.2.0)
|
||||
thor (>= 0.14, < 2.0)
|
||||
jwt (1.5.6)
|
||||
kgio (2.11.0)
|
||||
libv8 (5.3.332.38.5)
|
||||
libv8 (5.9.211.38.1)
|
||||
listen (3.1.5)
|
||||
rb-fsevent (~> 0.9, >= 0.9.4)
|
||||
rb-inotify (~> 0.9, >= 0.9.7)
|
||||
ruby_dep (~> 1.2)
|
||||
logster (1.2.7)
|
||||
loofah (2.0.3)
|
||||
lograge (0.7.1)
|
||||
actionpack (>= 4, < 5.2)
|
||||
activesupport (>= 4, < 5.2)
|
||||
railties (>= 4, < 5.2)
|
||||
request_store (~> 1.0)
|
||||
logstash-event (1.2.02)
|
||||
logstash-logger (0.25.1)
|
||||
logstash-event (~> 1.2)
|
||||
logster (1.2.9)
|
||||
loofah (2.1.1)
|
||||
crass (~> 1.0.2)
|
||||
nokogiri (>= 1.5.9)
|
||||
lru_redux (1.1.0)
|
||||
mail (2.6.6.rc1)
|
||||
mail (2.6.6)
|
||||
mime-types (>= 1.16, < 4)
|
||||
memory_profiler (0.9.7)
|
||||
message_bus (2.0.2)
|
||||
memory_profiler (0.9.8)
|
||||
message_bus (2.1.1)
|
||||
rack (>= 1.1.3)
|
||||
metaclass (0.0.4)
|
||||
method_source (0.8.2)
|
||||
mime-types (2.99.3)
|
||||
mime-types (3.1)
|
||||
mime-types-data (~> 3.2015)
|
||||
mime-types-data (3.2016.0521)
|
||||
mini_mime (0.1.3)
|
||||
mini_portile2 (2.3.0)
|
||||
mini_racer (0.1.9)
|
||||
libv8 (~> 5.3)
|
||||
minitest (5.10.1)
|
||||
mocha (1.1.0)
|
||||
mini_racer (0.1.11)
|
||||
libv8 (~> 5.7)
|
||||
mini_suffix (0.3.0)
|
||||
ffi (~> 1.9)
|
||||
minitest (5.10.3)
|
||||
mocha (1.2.1)
|
||||
metaclass (~> 0.0.1)
|
||||
mock_redis (0.15.4)
|
||||
mock_redis (0.17.3)
|
||||
moneta (1.0.0)
|
||||
msgpack (1.1.0)
|
||||
multi_json (1.12.1)
|
||||
multi_xml (0.6.0)
|
||||
multipart-post (2.0.0)
|
||||
mustache (1.0.5)
|
||||
netrc (0.11.0)
|
||||
nokogiri (1.8.1)
|
||||
mini_portile2 (~> 2.3.0)
|
||||
nokogumbo (1.4.10)
|
||||
nokogumbo (1.4.13)
|
||||
nokogiri
|
||||
oauth (0.5.1)
|
||||
oauth2 (1.3.1)
|
||||
@ -185,15 +200,18 @@ GEM
|
||||
multi_json (~> 1.3)
|
||||
multi_xml (~> 0.5)
|
||||
rack (>= 1.2, < 3)
|
||||
oj (3.0.5)
|
||||
oga (2.10)
|
||||
ast
|
||||
ruby-ll (~> 2.1)
|
||||
oj (3.1.0)
|
||||
omniauth (1.6.1)
|
||||
hashie (>= 3.4.6, < 3.6.0)
|
||||
rack (>= 1.6.2, < 3)
|
||||
omniauth-facebook (4.0.0)
|
||||
omniauth-oauth2 (~> 1.2)
|
||||
omniauth-github-discourse (1.1.2)
|
||||
omniauth (~> 1.0)
|
||||
omniauth-oauth2 (~> 1.1)
|
||||
omniauth-github (1.3.0)
|
||||
omniauth (~> 1.5)
|
||||
omniauth-oauth2 (>= 1.4.0, < 2.0)
|
||||
omniauth-google-oauth2 (0.3.1)
|
||||
jwt (~> 1.0)
|
||||
multi_json (~> 1.3)
|
||||
@ -214,7 +232,7 @@ GEM
|
||||
omniauth-twitter (1.3.0)
|
||||
omniauth-oauth (~> 1.1)
|
||||
rack
|
||||
onebox (1.8.12)
|
||||
onebox (1.8.33)
|
||||
fast_blank (>= 1.0.0)
|
||||
htmlentities (~> 4.3)
|
||||
moneta (~> 1.0)
|
||||
@ -225,7 +243,11 @@ GEM
|
||||
openid-redis-store (0.0.2)
|
||||
redis
|
||||
ruby-openid
|
||||
pg (0.19.0)
|
||||
parallel (1.12.0)
|
||||
parser (2.4.0.0)
|
||||
ast (~> 2.2)
|
||||
pg (0.20.0)
|
||||
powerpack (0.1.1)
|
||||
progress (3.3.1)
|
||||
pry (0.10.4)
|
||||
coderay (~> 1.1.0)
|
||||
@ -236,128 +258,117 @@ GEM
|
||||
pry-rails (0.3.4)
|
||||
pry (>= 0.9.10)
|
||||
public_suffix (2.0.5)
|
||||
puma (3.6.0)
|
||||
puma (3.9.1)
|
||||
r2 (0.2.6)
|
||||
rack (1.6.8)
|
||||
rack-mini-profiler (0.10.4)
|
||||
rack (2.0.3)
|
||||
rack-mini-profiler (0.10.7)
|
||||
rack (>= 1.2.0)
|
||||
rack-openid (1.3.1)
|
||||
rack (>= 1.1.0)
|
||||
ruby-openid (>= 2.1.8)
|
||||
rack-protection (1.5.3)
|
||||
rack-protection (2.0.0)
|
||||
rack
|
||||
rack-test (0.6.3)
|
||||
rack (>= 1.0)
|
||||
rails (4.2.8)
|
||||
actionmailer (= 4.2.8)
|
||||
actionpack (= 4.2.8)
|
||||
actionview (= 4.2.8)
|
||||
activejob (= 4.2.8)
|
||||
activemodel (= 4.2.8)
|
||||
activerecord (= 4.2.8)
|
||||
activesupport (= 4.2.8)
|
||||
bundler (>= 1.3.0, < 2.0)
|
||||
railties (= 4.2.8)
|
||||
sprockets-rails
|
||||
rails-deprecated_sanitizer (1.0.3)
|
||||
activesupport (>= 4.2.0.alpha)
|
||||
rails-dom-testing (1.0.8)
|
||||
activesupport (>= 4.2.0.beta, < 5.0)
|
||||
nokogiri (~> 1.6)
|
||||
rails-deprecated_sanitizer (>= 1.0.1)
|
||||
rack-test (0.7.0)
|
||||
rack (>= 1.0, < 3)
|
||||
rails-dom-testing (2.0.3)
|
||||
activesupport (>= 4.2.0)
|
||||
nokogiri (>= 1.6)
|
||||
rails-html-sanitizer (1.0.3)
|
||||
loofah (~> 2.0)
|
||||
rails_multisite (1.0.6)
|
||||
rails (> 4.2, < 5)
|
||||
railties (4.2.8)
|
||||
actionpack (= 4.2.8)
|
||||
activesupport (= 4.2.8)
|
||||
rails_multisite (1.1.2)
|
||||
activerecord (> 4.2, < 6)
|
||||
railties (> 4.2, < 6)
|
||||
railties (5.1.4)
|
||||
actionpack (= 5.1.4)
|
||||
activesupport (= 5.1.4)
|
||||
method_source
|
||||
rake (>= 0.8.7)
|
||||
thor (>= 0.18.1, < 2.0)
|
||||
raindrops (0.18.0)
|
||||
rake (11.3.0)
|
||||
rake-compiler (0.9.9)
|
||||
rainbow (2.2.2)
|
||||
rake
|
||||
rb-fsevent (0.9.7)
|
||||
rb-inotify (0.9.7)
|
||||
raindrops (0.19.0)
|
||||
rake (12.1.0)
|
||||
rake-compiler (1.0.4)
|
||||
rake
|
||||
rb-fsevent (0.9.8)
|
||||
rb-inotify (0.9.8)
|
||||
ffi (>= 0.5.0)
|
||||
rbtrace (0.4.8)
|
||||
ffi (>= 1.0.6)
|
||||
msgpack (>= 0.4.3)
|
||||
trollop (>= 1.16.2)
|
||||
redis (3.3.3)
|
||||
redis-namespace (1.5.2)
|
||||
redis (3.3.5)
|
||||
redis-namespace (1.5.3)
|
||||
redis (~> 3.0, >= 3.0.4)
|
||||
rest-client (1.8.0)
|
||||
http-cookie (>= 1.0.2, < 2.0)
|
||||
mime-types (>= 1.16, < 3.0)
|
||||
netrc (~> 0.7)
|
||||
rinku (2.0.0)
|
||||
rmmseg-cpp (0.2.9)
|
||||
rspec (3.4.0)
|
||||
rspec-core (~> 3.4.0)
|
||||
rspec-expectations (~> 3.4.0)
|
||||
rspec-mocks (~> 3.4.0)
|
||||
rspec-core (3.4.4)
|
||||
rspec-support (~> 3.4.0)
|
||||
rspec-expectations (3.4.0)
|
||||
request_store (1.3.2)
|
||||
rinku (2.0.2)
|
||||
rspec (3.6.0)
|
||||
rspec-core (~> 3.6.0)
|
||||
rspec-expectations (~> 3.6.0)
|
||||
rspec-mocks (~> 3.6.0)
|
||||
rspec-core (3.6.0)
|
||||
rspec-support (~> 3.6.0)
|
||||
rspec-expectations (3.6.0)
|
||||
diff-lcs (>= 1.2.0, < 2.0)
|
||||
rspec-support (~> 3.4.0)
|
||||
rspec-html-matchers (0.7.0)
|
||||
rspec-support (~> 3.6.0)
|
||||
rspec-html-matchers (0.9.1)
|
||||
nokogiri (~> 1)
|
||||
rspec (~> 3)
|
||||
rspec-mocks (3.4.1)
|
||||
rspec (>= 3.0.0.a, < 4)
|
||||
rspec-mocks (3.6.0)
|
||||
diff-lcs (>= 1.2.0, < 2.0)
|
||||
rspec-support (~> 3.4.0)
|
||||
rspec-rails (3.4.2)
|
||||
actionpack (>= 3.0, < 4.3)
|
||||
activesupport (>= 3.0, < 4.3)
|
||||
railties (>= 3.0, < 4.3)
|
||||
rspec-core (~> 3.4.0)
|
||||
rspec-expectations (~> 3.4.0)
|
||||
rspec-mocks (~> 3.4.0)
|
||||
rspec-support (~> 3.4.0)
|
||||
rspec-support (3.4.1)
|
||||
rspec-support (~> 3.6.0)
|
||||
rspec-rails (3.6.1)
|
||||
actionpack (>= 3.0)
|
||||
activesupport (>= 3.0)
|
||||
railties (>= 3.0)
|
||||
rspec-core (~> 3.6.0)
|
||||
rspec-expectations (~> 3.6.0)
|
||||
rspec-mocks (~> 3.6.0)
|
||||
rspec-support (~> 3.6.0)
|
||||
rspec-support (3.6.0)
|
||||
rtlit (0.0.5)
|
||||
rubocop (0.51.0)
|
||||
parallel (~> 1.10)
|
||||
parser (>= 2.3.3.1, < 3.0)
|
||||
powerpack (~> 0.1)
|
||||
rainbow (>= 2.2.2, < 3.0)
|
||||
ruby-progressbar (~> 1.7)
|
||||
unicode-display_width (~> 1.0, >= 1.0.1)
|
||||
ruby-ll (2.1.2)
|
||||
ansi
|
||||
ast
|
||||
ruby-openid (2.7.0)
|
||||
ruby-prof (0.16.2)
|
||||
ruby-progressbar (1.9.0)
|
||||
ruby-readability (0.7.0)
|
||||
guess_html_encoding (>= 0.0.4)
|
||||
nokogiri (>= 1.6.0)
|
||||
ruby_dep (1.5.0)
|
||||
safe_yaml (1.0.4)
|
||||
sanitize (4.4.0)
|
||||
sanitize (4.5.0)
|
||||
crass (~> 1.0.2)
|
||||
nokogiri (>= 1.4.4)
|
||||
nokogumbo (~> 1.4.1)
|
||||
sass (3.4.23)
|
||||
sass (3.4.24)
|
||||
sassc (1.11.2)
|
||||
bundler
|
||||
ffi (~> 1.9.6)
|
||||
sass (>= 3.3.0)
|
||||
seed-fu (2.3.5)
|
||||
activerecord (>= 3.1, < 4.3)
|
||||
activesupport (>= 3.1, < 4.3)
|
||||
seed-fu (2.3.7)
|
||||
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 (4.2.4)
|
||||
sidekiq (5.0.5)
|
||||
concurrent-ruby (~> 1.0)
|
||||
connection_pool (~> 2.2, >= 2.2.0)
|
||||
rack-protection (>= 1.5.0)
|
||||
redis (~> 3.2, >= 3.2.1)
|
||||
simple-rss (1.3.1)
|
||||
sinatra (1.4.6)
|
||||
rack (~> 1.4)
|
||||
rack-protection (~> 1.4)
|
||||
tilt (>= 1.3, < 3)
|
||||
redis (>= 3.3.4, < 5)
|
||||
slop (3.6.0)
|
||||
spork (1.0.0rc4)
|
||||
spork-rails (4.0.0)
|
||||
rails (>= 3.0.0, < 5)
|
||||
spork (>= 1.0rc0)
|
||||
sprockets (3.7.1)
|
||||
concurrent-ruby (~> 1.0)
|
||||
rack (> 1, < 3)
|
||||
@ -366,21 +377,19 @@ GEM
|
||||
activesupport (>= 4.0)
|
||||
sprockets (>= 3.0.0)
|
||||
stackprof (0.2.10)
|
||||
test_after_commit (1.1.0)
|
||||
activerecord (>= 3.2)
|
||||
thor (0.19.4)
|
||||
thread_safe (0.3.6)
|
||||
tilt (2.0.5)
|
||||
timecop (0.8.1)
|
||||
tilt (2.0.7)
|
||||
trollop (2.1.2)
|
||||
tzinfo (1.2.3)
|
||||
thread_safe (~> 0.1)
|
||||
uglifier (3.0.2)
|
||||
uglifier (3.2.0)
|
||||
execjs (>= 0.3.0, < 3)
|
||||
unf (0.1.4)
|
||||
unf_ext
|
||||
unf_ext (0.0.7.1)
|
||||
unicorn (5.3.0)
|
||||
unf_ext (0.0.7.4)
|
||||
unicode-display_width (1.3.0)
|
||||
unicorn (5.3.1)
|
||||
kgio (~> 2.6)
|
||||
raindrops (~> 0.7)
|
||||
uniform_notifier (1.10.0)
|
||||
@ -393,10 +402,15 @@ PLATFORMS
|
||||
ruby
|
||||
|
||||
DEPENDENCIES
|
||||
actionmailer (~> 5.1)
|
||||
actionpack (~> 5.1)
|
||||
actionview (~> 5.1)
|
||||
active_model_serializers (~> 0.8.3)
|
||||
activemodel (~> 5.1)
|
||||
activerecord (~> 5.1)
|
||||
activesupport (~> 5.1)
|
||||
annotate
|
||||
aws-sdk
|
||||
babel-transpiler
|
||||
aws-sdk-s3
|
||||
barber
|
||||
better_errors
|
||||
binding_of_caller
|
||||
@ -404,9 +418,11 @@ DEPENDENCIES
|
||||
bullet
|
||||
byebug
|
||||
certified
|
||||
cppjieba_rb
|
||||
discourse-qunit-rails
|
||||
discourse_fastimage
|
||||
discourse_image_optim
|
||||
email_reply_trimmer (= 0.1.6)
|
||||
email_reply_trimmer (= 0.1.8)
|
||||
ember-handlebars-template (= 0.7.5)
|
||||
ember-rails (= 0.18.5)
|
||||
ember-source
|
||||
@ -417,7 +433,6 @@ DEPENDENCIES
|
||||
fast_blank
|
||||
fast_xor
|
||||
fast_xs
|
||||
fastimage (= 2.1.0)
|
||||
flamegraph
|
||||
foreman
|
||||
gc_tracer
|
||||
@ -426,29 +441,35 @@ DEPENDENCIES
|
||||
htmlentities
|
||||
http_accept_language (~> 2.0.5)
|
||||
listen
|
||||
lograge
|
||||
logstash-event
|
||||
logstash-logger
|
||||
logster
|
||||
lru_redux
|
||||
mail
|
||||
memory_profiler
|
||||
message_bus
|
||||
mime-types
|
||||
mini_mime
|
||||
mini_racer
|
||||
mini_suffix
|
||||
minitest
|
||||
mocha
|
||||
mock_redis
|
||||
multi_json
|
||||
mustache
|
||||
nokogiri
|
||||
oga
|
||||
oj
|
||||
omniauth
|
||||
omniauth-facebook
|
||||
omniauth-github-discourse
|
||||
omniauth-github
|
||||
omniauth-google-oauth2
|
||||
omniauth-instagram
|
||||
omniauth-oauth2
|
||||
omniauth-openid
|
||||
omniauth-twitter
|
||||
onebox
|
||||
onebox (= 1.8.33)
|
||||
openid-redis-store
|
||||
pg
|
||||
pry-nav
|
||||
@ -457,38 +478,35 @@ DEPENDENCIES
|
||||
r2 (~> 0.2.5)
|
||||
rack-mini-profiler
|
||||
rack-protection
|
||||
rails (~> 4.2)
|
||||
rails_multisite
|
||||
railties (~> 5.1)
|
||||
rake
|
||||
rb-fsevent
|
||||
rb-inotify (~> 0.9)
|
||||
rbtrace
|
||||
redis
|
||||
redis-namespace
|
||||
rest-client
|
||||
rinku
|
||||
rmmseg-cpp
|
||||
rspec
|
||||
rspec-html-matchers
|
||||
rspec-rails
|
||||
rtlit
|
||||
rubocop
|
||||
ruby-prof
|
||||
ruby-readability
|
||||
sanitize
|
||||
sassc
|
||||
seed-fu (~> 2.3.5)
|
||||
seed-fu
|
||||
shoulda
|
||||
sidekiq
|
||||
simple-rss
|
||||
sinatra
|
||||
spork-rails
|
||||
sprockets-rails
|
||||
stackprof
|
||||
test_after_commit
|
||||
thor
|
||||
timecop
|
||||
tilt
|
||||
uglifier
|
||||
unf
|
||||
unicorn
|
||||
webmock
|
||||
|
||||
BUNDLED WITH
|
||||
1.15.4
|
||||
1.16.0
|
||||
|
||||
1
Rakefile
1
Rakefile
@ -9,4 +9,3 @@ Discourse::Application.load_tasks
|
||||
# this prevents crashes when migrating a database in production in certain
|
||||
# PostgreSQL configuations when trying to create structure.sql
|
||||
Rake::Task["db:structure:dump"].clear if Rails.env.production?
|
||||
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 1.6 KiB |
38
app/assets/javascripts/admin/adapters/flagged-post.js.es6
Normal file
38
app/assets/javascripts/admin/adapters/flagged-post.js.es6
Normal file
@ -0,0 +1,38 @@
|
||||
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;
|
||||
}
|
||||
});
|
||||
@ -1,13 +1,14 @@
|
||||
import loadScript from 'discourse/lib/load-script';
|
||||
import { observes } from 'ember-addons/ember-computed-decorators';
|
||||
|
||||
const LOAD_ASYNC = !Ember.Test;
|
||||
const LOAD_ASYNC = !Ember.testing;
|
||||
|
||||
export default Ember.Component.extend({
|
||||
mode: 'css',
|
||||
classNames: ['ace-wrapper'],
|
||||
_editor: null,
|
||||
_skipContentChangeEvent: null,
|
||||
disabled: false,
|
||||
|
||||
@observes('editorId')
|
||||
editorIdChanged() {
|
||||
@ -30,6 +31,24 @@ export default Ember.Component.extend({
|
||||
}
|
||||
},
|
||||
|
||||
@observes('disabled')
|
||||
disabledStateChanged() {
|
||||
this.changeDisabledState();
|
||||
},
|
||||
|
||||
changeDisabledState() {
|
||||
const editor = this._editor;
|
||||
if (editor) {
|
||||
const disabled = this.get('disabled');
|
||||
editor.setOptions({
|
||||
readOnly: disabled,
|
||||
highlightActiveLine: !disabled,
|
||||
highlightGutterLine: !disabled
|
||||
});
|
||||
editor.container.parentNode.setAttribute("data-disabled", disabled);
|
||||
}
|
||||
},
|
||||
|
||||
_destroyEditor: function() {
|
||||
if (this._editor) {
|
||||
this._editor.destroy();
|
||||
@ -76,6 +95,7 @@ export default Ember.Component.extend({
|
||||
|
||||
this.$().data('editor', editor);
|
||||
this._editor = editor;
|
||||
this.changeDisabledState();
|
||||
|
||||
$(window).off('ace:resize').on('ace:resize', ()=>{
|
||||
this.appEvents.trigger('ace:resize');
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { iconHTML } from 'discourse-common/helpers/fa-icon';
|
||||
import { iconHTML } from 'discourse-common/lib/icon-library';
|
||||
import { bufferedRender } from 'discourse-common/lib/buffered-render';
|
||||
|
||||
export default Ember.Component.extend(bufferedRender({
|
||||
|
||||
@ -1,43 +0,0 @@
|
||||
export default Ember.Component.extend({
|
||||
tagName: 'div',
|
||||
|
||||
_init: function(){
|
||||
this.$("input").select2({
|
||||
multiple: true,
|
||||
width: '100%',
|
||||
query: function(opts) {
|
||||
opts.callback({
|
||||
results: this.get("available").filter(function(o) {
|
||||
return -1 !== o.name.toLowerCase().indexOf(opts.term.toLowerCase());
|
||||
}).map(this._format)
|
||||
});
|
||||
}.bind(this)
|
||||
}).on("change", function(evt) {
|
||||
if (evt.added){
|
||||
this.triggerAction({
|
||||
action: "groupAdded",
|
||||
actionContext: this.get("available").findBy("id", evt.added.id)
|
||||
});
|
||||
} else if (evt.removed) {
|
||||
this.triggerAction({
|
||||
action:"groupRemoved",
|
||||
actionContext: evt.removed.id
|
||||
});
|
||||
}
|
||||
}.bind(this));
|
||||
|
||||
this._refreshOnReset();
|
||||
}.on("didInsertElement"),
|
||||
|
||||
_format(item) {
|
||||
return {
|
||||
"text": item.name,
|
||||
"id": item.id,
|
||||
"locked": item.automatic
|
||||
};
|
||||
},
|
||||
|
||||
_refreshOnReset: function() {
|
||||
this.$("input").select2("data", this.get("selected").map(this._format));
|
||||
}.observes("selected")
|
||||
});
|
||||
3
app/assets/javascripts/admin/components/admin-nav.js.es6
Normal file
3
app/assets/javascripts/admin/components/admin-nav.js.es6
Normal file
@ -0,0 +1,3 @@
|
||||
export default Ember.Component.extend({
|
||||
tagName: ''
|
||||
});
|
||||
@ -0,0 +1,19 @@
|
||||
import { iconHTML } from 'discourse-common/lib/icon-library';
|
||||
import { bufferedRender } from 'discourse-common/lib/buffered-render';
|
||||
|
||||
export default Ember.Component.extend(bufferedRender({
|
||||
classNames: ['watched-word'],
|
||||
|
||||
buildBuffer(buffer) {
|
||||
buffer.push(iconHTML('times'));
|
||||
buffer.push(' ' + this.get('word.word'));
|
||||
},
|
||||
|
||||
click() {
|
||||
this.get('word').destroy().then(() => {
|
||||
this.sendAction('action', this.get('word'));
|
||||
}).catch(e => {
|
||||
bootbox.alert(I18n.t("generic_error_with_reason", {error: `http: ${e.status} - ${e.body}`}));
|
||||
});;
|
||||
}
|
||||
}));
|
||||
@ -1,5 +1,5 @@
|
||||
import computed from 'ember-addons/ember-computed-decorators';
|
||||
import { iconHTML } from 'discourse-common/helpers/fa-icon';
|
||||
import { iconHTML } from 'discourse-common/lib/icon-library';
|
||||
import { bufferedRender } from 'discourse-common/lib/buffered-render';
|
||||
|
||||
export default Ember.Component.extend(bufferedRender({
|
||||
|
||||
@ -0,0 +1,3 @@
|
||||
export default Ember.Component.extend({
|
||||
classNames: ['flagged-post-response']
|
||||
});
|
||||
72
app/assets/javascripts/admin/components/flagged-post.js.es6
Normal file
72
app/assets/javascripts/admin/components/flagged-post.js.es6
Normal file
@ -0,0 +1,72 @@
|
||||
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,
|
||||
suspended: false,
|
||||
|
||||
tagName: 'div',
|
||||
classNameBindings: [
|
||||
':flagged-post',
|
||||
'flaggedPost.hidden:hidden-post',
|
||||
'flaggedPost.deleted'
|
||||
],
|
||||
|
||||
@computed('filter')
|
||||
canAct(filter) {
|
||||
return filter === 'active';
|
||||
},
|
||||
|
||||
removeAfter(promise) {
|
||||
return promise.then(() => {
|
||||
this.attrs.removePost();
|
||||
}).catch(() => {
|
||||
bootbox.alert(I18n.t("admin.flags.error"));
|
||||
});
|
||||
},
|
||||
|
||||
_spawnModal(name, model, modalClass) {
|
||||
let controller = showModal(name, { model, admin: true, modalClass });
|
||||
controller.removeAfter = (p) => this.removeAfter(p);
|
||||
},
|
||||
|
||||
actions: {
|
||||
removeAfter(promise) {
|
||||
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')
|
||||
});
|
||||
},
|
||||
|
||||
showSuspendModal() {
|
||||
let post = this.get('flaggedPost');
|
||||
let user = post.get('user');
|
||||
this.get('adminTools').showSuspendModal(
|
||||
user,
|
||||
{
|
||||
post,
|
||||
successCallback: result => this.set('suspended', result.suspended)
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -1,4 +1,5 @@
|
||||
import { ajax } from 'discourse/lib/ajax';
|
||||
import AdminUser from 'admin/models/admin-user';
|
||||
|
||||
export default Ember.Component.extend({
|
||||
classNames: ["ip-lookup"],
|
||||
@ -44,7 +45,6 @@ export default Ember.Component.extend({
|
||||
self.set("totalOthersWithSameIP", result.total);
|
||||
});
|
||||
|
||||
const AdminUser = require('admin/models/admin-user').default;
|
||||
AdminUser.findAll("active", data).then(function (users) {
|
||||
self.setProperties({
|
||||
other_accounts: users,
|
||||
|
||||
@ -1,54 +0,0 @@
|
||||
/**
|
||||
Provide a nice GUI for a pipe-delimited list in the site settings.
|
||||
|
||||
@param settingValue is a reference to SiteSetting.value.
|
||||
@param choices is a reference to SiteSetting.choices
|
||||
**/
|
||||
export default Ember.Component.extend({
|
||||
|
||||
_select2FormatSelection: function(selectedObject, jqueryWrapper, htmlEscaper) {
|
||||
var text = selectedObject.text;
|
||||
if (text.length <= 6) {
|
||||
jqueryWrapper.closest('li.select2-search-choice').css({"border-bottom": '7px solid #'+text});
|
||||
}
|
||||
return htmlEscaper(text);
|
||||
},
|
||||
|
||||
_initializeSelect2: function(){
|
||||
var options = {
|
||||
multiple: false,
|
||||
separator: "|",
|
||||
tokenSeparators: ["|"],
|
||||
tags : this.get("choices") || [],
|
||||
width: 'off',
|
||||
dropdownCss: this.get("choices") ? {} : {display: 'none'},
|
||||
selectOnBlur: this.get("choices") ? false : true
|
||||
};
|
||||
|
||||
var settingName = this.get('settingName');
|
||||
if (typeof settingName === 'string' && settingName.indexOf('colors') > -1) {
|
||||
options.formatSelection = this._select2FormatSelection;
|
||||
}
|
||||
|
||||
var self = this;
|
||||
this.$("input").select2(options).on("change", function(obj) {
|
||||
self.set("settingValue", obj.val.join("|"));
|
||||
self.refreshSortables();
|
||||
});
|
||||
|
||||
this.refreshSortables();
|
||||
}.on('didInsertElement'),
|
||||
|
||||
refreshOnReset: function() {
|
||||
this.$("input").select2("val", this.get("settingValue").split("|"));
|
||||
}.observes("settingValue"),
|
||||
|
||||
refreshSortables: function() {
|
||||
var self = this;
|
||||
this.$("ul.select2-choices").sortable().on('sortupdate', function() {
|
||||
self.$("input").select2("onSortEnd");
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@ -0,0 +1,3 @@
|
||||
export default Ember.Component.extend({
|
||||
tagName: 'tr',
|
||||
});
|
||||
@ -1,3 +1,5 @@
|
||||
import Permalink from 'admin/models/permalink';
|
||||
|
||||
export default Ember.Component.extend({
|
||||
classNames: ['permalink-form'],
|
||||
formSubmitted: false,
|
||||
@ -18,8 +20,6 @@ export default Ember.Component.extend({
|
||||
|
||||
actions: {
|
||||
submit: function() {
|
||||
const Permalink = require('admin/models/permalink').default;
|
||||
|
||||
if (!this.get('formSubmitted')) {
|
||||
const self = this;
|
||||
self.set('formSubmitted', true);
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { iconHTML } from 'discourse-common/lib/icon-library';
|
||||
import { bufferedRender } from 'discourse-common/lib/buffered-render';
|
||||
|
||||
/*global Resumable:true */
|
||||
@ -40,7 +41,7 @@ export default Ember.Component.extend(bufferedRender({
|
||||
|
||||
buildBuffer(buffer) {
|
||||
const icon = this.get("isUploading") ? "times" : "upload";
|
||||
buffer.push(`<i class="fa fa-${icon}"></i>`);
|
||||
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>");
|
||||
},
|
||||
|
||||
@ -2,6 +2,7 @@ import BufferedContent from 'discourse/mixins/buffered-content';
|
||||
import SiteSetting from 'admin/models/site-setting';
|
||||
import { propertyNotEqual } from 'discourse/lib/computed';
|
||||
import computed from 'ember-addons/ember-computed-decorators';
|
||||
import { categoryLinkHTML } from 'discourse/helpers/category-link';
|
||||
|
||||
const CustomTypes = ['bool', 'enum', 'list', 'url_list', 'host_list', 'category_list', 'value_list'];
|
||||
|
||||
@ -11,8 +12,19 @@ export default Ember.Component.extend(BufferedContent, {
|
||||
dirty: propertyNotEqual('buffered.value', 'setting.value'),
|
||||
validationMessage: null,
|
||||
|
||||
@computed("setting.preview", "buffered.value")
|
||||
preview(preview, value) {
|
||||
@computed("setting", "buffered.value")
|
||||
preview(setting, value) {
|
||||
// A bit hacky, but allows us to use helpers
|
||||
if (setting.get('setting') === 'category_style') {
|
||||
let category = this.site.get('categories.firstObject');
|
||||
if (category) {
|
||||
return categoryLinkHTML(category, {
|
||||
categoryStyle: value
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let preview = setting.get('preview');
|
||||
if (preview) {
|
||||
return new Handlebars.SafeString("<div class='preview'>" + preview.replace(/\{\{value\}\}/g, value) + "</div>");
|
||||
}
|
||||
@ -52,16 +64,16 @@ export default Ember.Component.extend(BufferedContent, {
|
||||
}.on("willDestroyElement"),
|
||||
|
||||
_save() {
|
||||
const self = this,
|
||||
setting = this.get('buffered');
|
||||
SiteSetting.update(setting.get('setting'), setting.get('value')).then(function() {
|
||||
self.set('validationMessage', null);
|
||||
self.commitBuffer();
|
||||
}).catch(function(e) {
|
||||
const setting = this.get('buffered'),
|
||||
action = SiteSetting.update(setting.get('setting'), setting.get('value'));
|
||||
action.then(() => {
|
||||
this.set('validationMessage', null);
|
||||
this.commitBuffer();
|
||||
}).catch((e) => {
|
||||
if (e.jqXHR.responseJSON && e.jqXHR.responseJSON.errors) {
|
||||
self.set('validationMessage', e.jqXHR.responseJSON.errors[0]);
|
||||
this.set('validationMessage', e.jqXHR.responseJSON.errors[0]);
|
||||
} else {
|
||||
self.set('validationMessage', I18n.t('generic_error'));
|
||||
this.set('validationMessage', I18n.t('generic_error'));
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
@ -0,0 +1,55 @@
|
||||
import WatchedWord from 'admin/models/watched-word';
|
||||
import { default as computed, on, observes } from 'ember-addons/ember-computed-decorators';
|
||||
|
||||
export default Ember.Component.extend({
|
||||
classNames: ['watched-word-form'],
|
||||
formSubmitted: false,
|
||||
actionKey: null,
|
||||
showSuccessMessage: false,
|
||||
|
||||
@computed('regularExpressions')
|
||||
placeholderKey(regularExpressions) {
|
||||
return "admin.watched_words.form.placeholder" +
|
||||
(regularExpressions ? "_regexp" : "");
|
||||
},
|
||||
|
||||
@observes('word')
|
||||
removeSuccessMessage() {
|
||||
if (this.get('showSuccessMessage') && !Ember.isEmpty(this.get('word'))) {
|
||||
this.set('showSuccessMessage', false);
|
||||
}
|
||||
},
|
||||
|
||||
actions: {
|
||||
submit() {
|
||||
if (!this.get('formSubmitted')) {
|
||||
this.set('formSubmitted', true);
|
||||
|
||||
const watchedWord = WatchedWord.create({ word: this.get('word'), action: this.get('actionKey') });
|
||||
|
||||
watchedWord.save().then(result => {
|
||||
this.setProperties({ word: '', formSubmitted: false, showSuccessMessage: true });
|
||||
this.sendAction('action', WatchedWord.create(result));
|
||||
Ember.run.schedule('afterRender', () => this.$('.watched-word-input').focus());
|
||||
}).catch(e => {
|
||||
this.set('formSubmitted', false);
|
||||
const msg = (e.responseJSON && e.responseJSON.errors) ?
|
||||
I18n.t("generic_error_with_reason", {error: e.responseJSON.errors.join('. ')}) :
|
||||
I18n.t("generic_error");
|
||||
bootbox.alert(msg, () => this.$('.watched-word-input').focus());
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@on("didInsertElement")
|
||||
_init() {
|
||||
Ember.run.schedule('afterRender', () => {
|
||||
this.$('.watched-word-input').keydown(e => {
|
||||
if (e.keyCode === 13) {
|
||||
this.send('submit');
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
@ -0,0 +1,25 @@
|
||||
import computed from "ember-addons/ember-computed-decorators";
|
||||
import UploadMixin from "discourse/mixins/upload";
|
||||
|
||||
export default Em.Component.extend(UploadMixin, {
|
||||
type: 'csv',
|
||||
classNames: 'watched-words-uploader',
|
||||
uploadUrl: '/admin/logs/watched_words/upload',
|
||||
addDisabled: Em.computed.alias("uploading"),
|
||||
|
||||
validateUploadedFilesOptions() {
|
||||
return { csvOnly: true };
|
||||
},
|
||||
|
||||
@computed('actionKey')
|
||||
data(actionKey) {
|
||||
return { action_key: actionKey };
|
||||
},
|
||||
|
||||
uploadDone() {
|
||||
if (this) {
|
||||
bootbox.alert(I18n.t("admin.watched_words.form.upload_successful"));
|
||||
this.sendAction("done");
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -15,6 +15,7 @@ export default Ember.Controller.extend(bufferedProperty('emailTemplate'), {
|
||||
|
||||
actions: {
|
||||
saveChanges() {
|
||||
this.set('saved', false);
|
||||
const buffered = this.get('buffered');
|
||||
this.get('emailTemplate').save(buffered.getProperties('subject', 'body')).then(() => {
|
||||
this.set('saved', true);
|
||||
|
||||
@ -7,7 +7,7 @@ import computed from 'ember-addons/ember-computed-decorators';
|
||||
|
||||
const PROBLEMS_CHECK_MINUTES = 1;
|
||||
|
||||
const ATTRIBUTES = [ 'disk_space','admins', 'moderators', 'blocked', 'suspended', 'top_traffic_sources',
|
||||
const ATTRIBUTES = [ 'disk_space','admins', 'moderators', 'silenced', 'suspended', 'top_traffic_sources',
|
||||
'top_referred_topics', 'updated_at'];
|
||||
|
||||
const REPORTS = [ 'global_reports', 'page_view_reports', 'private_message_reports', 'http_reports',
|
||||
|
||||
@ -2,11 +2,13 @@ import EmailPreview from 'admin/models/email-preview';
|
||||
import { popupAjaxError } from 'discourse/lib/ajax-error';
|
||||
|
||||
export default Ember.Controller.extend({
|
||||
username: null,
|
||||
lastSeen: null,
|
||||
|
||||
emailEmpty: Em.computed.empty('email'),
|
||||
sendEmailDisabled: Em.computed.or('emailEmpty', 'sendingEmail'),
|
||||
showSendEmailForm: Em.computed.notEmpty('model.html_content'),
|
||||
htmlEmpty: Em.computed.empty('model.html_content'),
|
||||
emailEmpty: Ember.computed.empty('email'),
|
||||
sendEmailDisabled: Ember.computed.or('emailEmpty', 'sendingEmail'),
|
||||
showSendEmailForm: Ember.computed.notEmpty('model.html_content'),
|
||||
htmlEmpty: Ember.computed.empty('model.html_content'),
|
||||
|
||||
actions: {
|
||||
refresh() {
|
||||
@ -14,7 +16,14 @@ export default Ember.Controller.extend({
|
||||
|
||||
this.set('loading', true);
|
||||
this.set('sentEmail', false);
|
||||
EmailPreview.findDigest(this.get('lastSeen'), this.get('username')).then(email => {
|
||||
|
||||
let username = this.get('username');
|
||||
if (!username) {
|
||||
username = this.currentUser.get('username');
|
||||
this.set('username', username);
|
||||
}
|
||||
|
||||
EmailPreview.findDigest(username, this.get('lastSeen')).then(email => {
|
||||
model.setProperties(email.getProperties('html_content', 'text_content'));
|
||||
this.set('loading', false);
|
||||
});
|
||||
@ -28,16 +37,14 @@ export default Ember.Controller.extend({
|
||||
this.set('sendingEmail', true);
|
||||
this.set('sentEmail', false);
|
||||
|
||||
const self = this;
|
||||
|
||||
EmailPreview.sendDigest(this.get('lastSeen'), this.get('username'), this.get('email')).then(result => {
|
||||
EmailPreview.sendDigest(this.get('username'), this.get('lastSeen'), this.get('email')).then(result => {
|
||||
if (result.errors) {
|
||||
bootbox.alert(result.errors);
|
||||
} else {
|
||||
self.set('sentEmail', true);
|
||||
this.set('sentEmail', true);
|
||||
}
|
||||
}).catch(popupAjaxError).finally(function() {
|
||||
self.set('sendingEmail', false);
|
||||
}).catch(popupAjaxError).finally(() => {
|
||||
this.set('sendingEmail', false);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,41 +0,0 @@
|
||||
import FlaggedPost from 'admin/models/flagged-post';
|
||||
|
||||
export default Ember.Controller.extend({
|
||||
query: null,
|
||||
|
||||
adminOldFlagsView: Em.computed.equal("query", "old"),
|
||||
adminActiveFlagsView: Em.computed.equal("query", "active"),
|
||||
|
||||
actions: {
|
||||
disagreeFlags(flaggedPost) {
|
||||
flaggedPost.disagreeFlags().then(() => {
|
||||
this.get('model').removeObject(flaggedPost);
|
||||
}, function () {
|
||||
bootbox.alert(I18n.t("admin.flags.error"));
|
||||
});
|
||||
},
|
||||
|
||||
deferFlags(flaggedPost) {
|
||||
flaggedPost.deferFlags().then(() => {
|
||||
this.get('model').removeObject(flaggedPost);
|
||||
}, function () {
|
||||
bootbox.alert(I18n.t("admin.flags.error"));
|
||||
});
|
||||
},
|
||||
|
||||
doneTopicFlags(item) {
|
||||
this.send("disagreeFlags", item);
|
||||
},
|
||||
|
||||
loadMore() {
|
||||
const flags = this.get('model');
|
||||
return FlaggedPost.findAll(this.get('query'), flags.length+1).then(data => {
|
||||
if (data.length===0) {
|
||||
flags.set("allLoaded",true);
|
||||
}
|
||||
flags.addObjects(data);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
@ -15,6 +15,15 @@ export default Ember.Controller.extend({
|
||||
];
|
||||
}.property(),
|
||||
|
||||
visibilityLevelOptions: function() {
|
||||
return [
|
||||
{ name: I18n.t("groups.visibility_levels.public"), value: 0 },
|
||||
{ name: I18n.t("groups.visibility_levels.members"), value: 1 },
|
||||
{ name: I18n.t("groups.visibility_levels.staff"), value: 2 },
|
||||
{ name: I18n.t("groups.visibility_levels.owners"), value: 3 }
|
||||
];
|
||||
}.property(),
|
||||
|
||||
trustLevelOptions: function() {
|
||||
return [
|
||||
{ name: I18n.t("groups.trust_levels.none"), value: 0 },
|
||||
@ -22,14 +31,16 @@ export default Ember.Controller.extend({
|
||||
];
|
||||
}.property(),
|
||||
|
||||
@computed('model.visible', 'model.public')
|
||||
disableMembershipRequestSetting(visible, publicGroup) {
|
||||
return !visible || publicGroup;
|
||||
@computed('model.visibility_level', 'model.public_admission')
|
||||
disableMembershipRequestSetting(visibility_level, publicAdmission) {
|
||||
visibility_level = parseInt(visibility_level);
|
||||
return (visibility_level !== 0) || publicAdmission;
|
||||
},
|
||||
|
||||
@computed('model.visible', 'model.allow_membership_requests')
|
||||
disablePublicSetting(visible, allowMembershipRequests) {
|
||||
return !visible || allowMembershipRequests;
|
||||
@computed('model.visibility_level', 'model.allow_membership_requests')
|
||||
disablePublicSetting(visibility_level, allowMembershipRequests) {
|
||||
visibility_level = parseInt(visibility_level);
|
||||
return (visibility_level !== 0) || allowMembershipRequests;
|
||||
},
|
||||
|
||||
actions: {
|
||||
|
||||
@ -30,7 +30,7 @@ export default Ember.Controller.extend({
|
||||
|
||||
showInstructions: Ember.computed.gt('model.length', 0),
|
||||
|
||||
refresh: function() {
|
||||
_refresh() {
|
||||
this.set('loading', true);
|
||||
|
||||
var filters = this.get('filters'),
|
||||
@ -65,14 +65,18 @@ export default Ember.Controller.extend({
|
||||
});
|
||||
},
|
||||
|
||||
scheduleRefresh() {
|
||||
Ember.run.scheduleOnce('afterRender', this, this._refresh);
|
||||
},
|
||||
|
||||
resetFilters: function() {
|
||||
this.set('filters', Ember.Object.create());
|
||||
this.refresh();
|
||||
this.scheduleRefresh();
|
||||
}.on('init'),
|
||||
|
||||
_changeFilters: function(props) {
|
||||
this.get('filters').setProperties(props);
|
||||
this.refresh();
|
||||
this.scheduleRefresh();
|
||||
},
|
||||
|
||||
actions: {
|
||||
@ -91,7 +95,7 @@ export default Ember.Controller.extend({
|
||||
this._changeFilters(changed);
|
||||
},
|
||||
|
||||
clearAllFilters: function() {
|
||||
clearAllFilters() {
|
||||
this.set("filterActionId", null);
|
||||
this.resetFilters();
|
||||
},
|
||||
|
||||
@ -0,0 +1,11 @@
|
||||
export default Ember.Controller.extend({
|
||||
loading: false,
|
||||
period: "all",
|
||||
searchType: "all",
|
||||
|
||||
searchTypeOptions: [
|
||||
{id: 'all', name: I18n.t('admin.logs.search_logs.types.all_search_types')},
|
||||
{id: 'header', name: I18n.t('admin.logs.search_logs.types.header')},
|
||||
{id: 'full_page', name: I18n.t('admin.logs.search_logs.types.full_page')}
|
||||
]
|
||||
});
|
||||
@ -0,0 +1,13 @@
|
||||
export default Ember.Controller.extend({
|
||||
loading: false,
|
||||
term: null,
|
||||
period: "yearly",
|
||||
searchType: "all",
|
||||
|
||||
searchTypeOptions: [
|
||||
{id: 'all', name: I18n.t('admin.logs.search_logs.types.all_search_types')},
|
||||
{id: 'header', name: I18n.t('admin.logs.search_logs.types.header')},
|
||||
{id: 'full_page', name: I18n.t('admin.logs.search_logs.types.full_page')},
|
||||
{id: 'click_through_only', name: I18n.t('admin.logs.search_logs.types.click_through_only')}
|
||||
]
|
||||
});
|
||||
@ -6,6 +6,7 @@ import { popupAjaxError } from 'discourse/lib/ajax-error';
|
||||
import computed from 'ember-addons/ember-computed-decorators';
|
||||
|
||||
export default Ember.Controller.extend(CanCheckEmails, {
|
||||
adminTools: Ember.inject.service(),
|
||||
editingUsername: false,
|
||||
editingName: false,
|
||||
editingTitle: false,
|
||||
@ -57,13 +58,22 @@ export default Ember.Controller.extend(CanCheckEmails, {
|
||||
saveTrustLevel() { return this.get("model").saveTrustLevel(); },
|
||||
restoreTrustLevel() { return this.get("model").restoreTrustLevel(); },
|
||||
lockTrustLevel(locked) { return this.get("model").lockTrustLevel(locked); },
|
||||
unsuspend() { return this.get("model").unsuspend(); },
|
||||
unblock() { return this.get("model").unblock(); },
|
||||
block() { return this.get("model").block(); },
|
||||
unsilence() { return this.get("model").unsilence(); },
|
||||
silence() { return this.get("model").silence(); },
|
||||
deleteAllPosts() { return this.get("model").deleteAllPosts(); },
|
||||
anonymize() { return this.get('model').anonymize(); },
|
||||
destroy() { return this.get('model').destroy(); },
|
||||
|
||||
showSuspendModal() {
|
||||
this.get('adminTools').showSuspendModal(this.get('model'));
|
||||
},
|
||||
unsuspend() {
|
||||
this.get("model").unsuspend().catch(popupAjaxError);
|
||||
},
|
||||
showSilenceModal() {
|
||||
this.get('adminTools').showSilenceModal(this.get('model'));
|
||||
},
|
||||
|
||||
toggleUsernameEdit() {
|
||||
this.set('userUsernameValue', this.get('model.username'));
|
||||
this.toggleProperty('editingUsername');
|
||||
|
||||
@ -0,0 +1,65 @@
|
||||
import computed from 'ember-addons/ember-computed-decorators';
|
||||
import WatchedWord from 'admin/models/watched-word';
|
||||
|
||||
export default Ember.Controller.extend({
|
||||
actionNameKey: null,
|
||||
adminWatchedWords: Ember.inject.controller(),
|
||||
showWordsList: Ember.computed.or('adminWatchedWords.filtered', 'adminWatchedWords.showWords'),
|
||||
|
||||
findAction(actionName) {
|
||||
return (this.get('adminWatchedWords.model') || []).findBy('nameKey', actionName);
|
||||
},
|
||||
|
||||
@computed('adminWatchedWords.model', 'actionNameKey')
|
||||
filteredContent() {
|
||||
if (!this.get('actionNameKey')) { return []; }
|
||||
|
||||
const a = this.findAction(this.get('actionNameKey'));
|
||||
return a ? a.words : [];
|
||||
},
|
||||
|
||||
@computed('actionNameKey')
|
||||
actionDescription(actionNameKey) {
|
||||
return I18n.t('admin.watched_words.action_descriptions.' + actionNameKey);
|
||||
},
|
||||
|
||||
actions: {
|
||||
recordAdded(arg) {
|
||||
const a = this.findAction(this.get('actionNameKey'));
|
||||
if (a) {
|
||||
a.words.unshiftObject(arg);
|
||||
a.incrementProperty('count');
|
||||
Em.run.schedule('afterRender', () => {
|
||||
// remove from other actions lists
|
||||
let match = null;
|
||||
this.get('adminWatchedWords.model').forEach(action => {
|
||||
if (match) return;
|
||||
|
||||
if (action.nameKey !== this.get('actionNameKey')) {
|
||||
match = action.words.findBy('id', arg.id);
|
||||
if (match) {
|
||||
action.words.removeObject(match);
|
||||
action.decrementProperty('count');
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
recordRemoved(arg) {
|
||||
const a = this.findAction(this.get('actionNameKey'));
|
||||
if (a) {
|
||||
a.words.removeObject(arg);
|
||||
a.decrementProperty('count');
|
||||
}
|
||||
},
|
||||
|
||||
uploadComplete() {
|
||||
WatchedWord.findAll().then(data => {
|
||||
this.set('adminWatchedWords.model', data);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
@ -0,0 +1,52 @@
|
||||
import debounce from 'discourse/lib/debounce';
|
||||
|
||||
export default Ember.Controller.extend({
|
||||
filter: null,
|
||||
filtered: false,
|
||||
showWords: false,
|
||||
disableShowWords: Ember.computed.alias('filtered'),
|
||||
regularExpressions: null,
|
||||
|
||||
filterContentNow() {
|
||||
|
||||
if (!!Ember.isEmpty(this.get('allWatchedWords'))) return;
|
||||
|
||||
let filter;
|
||||
if (this.get('filter')) {
|
||||
filter = this.get('filter').toLowerCase();
|
||||
}
|
||||
|
||||
if (filter === undefined || filter.length < 1) {
|
||||
this.set('model', this.get('allWatchedWords'));
|
||||
return;
|
||||
}
|
||||
|
||||
const matchesByAction = [];
|
||||
|
||||
this.get('allWatchedWords').forEach(wordsForAction => {
|
||||
const wordRecords = wordsForAction.words.filter(wordRecord => {
|
||||
return (wordRecord.word.indexOf(filter) > -1);
|
||||
});
|
||||
matchesByAction.pushObject( Ember.Object.create({
|
||||
nameKey: wordsForAction.nameKey,
|
||||
name: wordsForAction.name,
|
||||
words: wordRecords,
|
||||
count: wordRecords.length
|
||||
}) );
|
||||
});
|
||||
|
||||
this.set('model', matchesByAction);
|
||||
},
|
||||
|
||||
filterContent: debounce(function() {
|
||||
this.filterContentNow();
|
||||
this.set('filtered', !Ember.isEmpty(this.get('filter')));
|
||||
}, 250).observes('filter'),
|
||||
|
||||
actions: {
|
||||
clearFilter() {
|
||||
this.setProperties({ filter: '' });
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
@ -1,5 +1,20 @@
|
||||
import computed from 'ember-addons/ember-computed-decorators';
|
||||
|
||||
export default Ember.Controller.extend({
|
||||
showBadges: function() {
|
||||
return this.get('currentUser.admin') && this.siteSettings.enable_badges;
|
||||
}.property()
|
||||
application: Ember.inject.controller(),
|
||||
|
||||
@computed
|
||||
showBadges() {
|
||||
return this.currentUser.get('admin') && this.siteSettings.enable_badges;
|
||||
},
|
||||
|
||||
@computed('application.currentPath')
|
||||
adminContentsClassName(currentPath) {
|
||||
return currentPath.split('.').filter(segment => {
|
||||
return segment !== 'index' &&
|
||||
segment !== 'loading' &&
|
||||
segment !== 'show' &&
|
||||
segment !== 'admin';
|
||||
}).map(Ember.String.dasherize).join(' ');
|
||||
}
|
||||
});
|
||||
|
||||
@ -16,16 +16,17 @@ export default Ember.Controller.extend(ModalFunctionality, {
|
||||
|
||||
@computed('name')
|
||||
nameValid(name) {
|
||||
return name && name.match(/^[a-zA-Z0-9-_]+$/);
|
||||
return name && name.match(/^[a-z_][a-z0-9_-]*$/i);
|
||||
},
|
||||
|
||||
@observes('name')
|
||||
uploadChanged(){
|
||||
let file = $('#file-input')[0];
|
||||
uploadChanged() {
|
||||
const file = $('#file-input')[0];
|
||||
this.set('fileSelected', file && file.files[0]);
|
||||
},
|
||||
|
||||
actions: {
|
||||
|
||||
updateName() {
|
||||
let name = this.get('name');
|
||||
if (Em.isEmpty(name)) {
|
||||
@ -34,20 +35,21 @@ export default Ember.Controller.extend(ModalFunctionality, {
|
||||
}
|
||||
this.uploadChanged();
|
||||
},
|
||||
upload() {
|
||||
|
||||
let options = {
|
||||
type: 'POST'
|
||||
upload() {
|
||||
const file = $('#file-input')[0].files[0];
|
||||
|
||||
const options = {
|
||||
type: 'POST',
|
||||
processData: false,
|
||||
contentType: false,
|
||||
data: new FormData()
|
||||
};
|
||||
|
||||
options.processData = false;
|
||||
options.contentType = false;
|
||||
options.data = new FormData();
|
||||
let file = $('#file-input')[0].files[0];
|
||||
options.data.append('file', file);
|
||||
|
||||
ajax('/admin/themes/upload_asset', options).then(result=>{
|
||||
let upload = {
|
||||
ajax('/admin/themes/upload_asset', options).then(result => {
|
||||
const upload = {
|
||||
upload_id: result.upload_id,
|
||||
name: this.get('name'),
|
||||
original_filename: file.name
|
||||
@ -57,7 +59,6 @@ export default Ember.Controller.extend(ModalFunctionality, {
|
||||
}).catch(e => {
|
||||
popupAjaxError(e);
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@ -1,24 +0,0 @@
|
||||
import ModalFunctionality from 'discourse/mixins/modal-functionality';
|
||||
|
||||
export default Ember.Controller.extend(ModalFunctionality, {
|
||||
adminFlagsList: Ember.inject.controller(),
|
||||
|
||||
_agreeFlag: function (actionOnPost) {
|
||||
const adminFlagController = this.get("adminFlagsList");
|
||||
const post = this.get("content");
|
||||
|
||||
return post.agreeFlags(actionOnPost).then(() => {
|
||||
adminFlagController.get('model').removeObject(post);
|
||||
this.send("closeModal");
|
||||
}, function () {
|
||||
bootbox.alert(I18n.t("admin.flags.error"));
|
||||
});
|
||||
},
|
||||
|
||||
actions: {
|
||||
agreeFlagHidePost: function () { return this._agreeFlag("hide"); },
|
||||
agreeFlagKeepPost: function () { return this._agreeFlag("keep"); },
|
||||
agreeFlagRestorePost: function () { return this._agreeFlag("restore"); }
|
||||
}
|
||||
|
||||
});
|
||||
@ -1,31 +0,0 @@
|
||||
import ModalFunctionality from 'discourse/mixins/modal-functionality';
|
||||
|
||||
export default Ember.Controller.extend(ModalFunctionality, {
|
||||
adminFlagsList: Ember.inject.controller(),
|
||||
|
||||
actions: {
|
||||
deletePostDeferFlag() {
|
||||
const adminFlagController = this.get("adminFlagsList");
|
||||
const post = this.get("content");
|
||||
|
||||
return post.deferFlags(true).then(() => {
|
||||
adminFlagController.get('model').removeObject(post);
|
||||
this.send("closeModal");
|
||||
}, function () {
|
||||
bootbox.alert(I18n.t("admin.flags.error"));
|
||||
});
|
||||
},
|
||||
|
||||
deletePostAgreeFlag() {
|
||||
const adminFlagController = this.get("adminFlagsList");
|
||||
const post = this.get("content");
|
||||
|
||||
return post.agreeFlags("delete").then(() => {
|
||||
adminFlagController.get('model').removeObject(post);
|
||||
this.send("closeModal");
|
||||
}, function () {
|
||||
bootbox.alert(I18n.t("admin.flags.error"));
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -0,0 +1,18 @@
|
||||
import ModalFunctionality from 'discourse/mixins/modal-functionality';
|
||||
|
||||
export default Ember.Controller.extend(ModalFunctionality, {
|
||||
loading: null,
|
||||
historyTarget: null,
|
||||
history: null,
|
||||
|
||||
onShow() {
|
||||
this.set('loading', true);
|
||||
this.set('history', null);
|
||||
},
|
||||
|
||||
loadHistory(target) {
|
||||
this.store.findAll('moderation-history', target).then(result => {
|
||||
this.set('history', result);
|
||||
}).finally(() => this.set('loading', false));
|
||||
}
|
||||
});
|
||||
@ -0,0 +1,50 @@
|
||||
import ModalFunctionality from 'discourse/mixins/modal-functionality';
|
||||
import computed from 'ember-addons/ember-computed-decorators';
|
||||
import { popupAjaxError } from 'discourse/lib/ajax-error';
|
||||
|
||||
export default Ember.Controller.extend(ModalFunctionality, {
|
||||
silenceUntil: null,
|
||||
reason: null,
|
||||
message: null,
|
||||
silencing: false,
|
||||
user: null,
|
||||
post: null,
|
||||
successCallback: null,
|
||||
|
||||
onShow() {
|
||||
this.setProperties({
|
||||
silenceUntil: null,
|
||||
reason: null,
|
||||
message: null,
|
||||
silencing: false,
|
||||
loadingUser: true,
|
||||
post: null,
|
||||
successCallback: null,
|
||||
});
|
||||
},
|
||||
|
||||
@computed('silenceUntil', 'reason', 'silencing')
|
||||
submitDisabled(silenceUntil, reason, silencing) {
|
||||
return (silencing || Ember.isEmpty(silenceUntil) || !reason || reason.length < 1);
|
||||
},
|
||||
|
||||
actions: {
|
||||
silence() {
|
||||
if (this.get('submitDisabled')) { return; }
|
||||
|
||||
this.set('silencing', true);
|
||||
this.get('user').silence({
|
||||
silenced_till: this.get('silenceUntil'),
|
||||
reason: this.get('reason'),
|
||||
message: this.get('message'),
|
||||
post_id: this.get('post.id')
|
||||
}).then(result => {
|
||||
this.send('closeModal');
|
||||
let callback = this.get('successCallback');
|
||||
if (callback) {
|
||||
callback(result);
|
||||
}
|
||||
}).catch(popupAjaxError).finally(() => this.set('silencing', false));
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -1,25 +1,50 @@
|
||||
import ModalFunctionality from 'discourse/mixins/modal-functionality';
|
||||
import computed from 'ember-addons/ember-computed-decorators';
|
||||
import { popupAjaxError } from 'discourse/lib/ajax-error';
|
||||
|
||||
export default Ember.Controller.extend(ModalFunctionality, {
|
||||
suspendUntil: null,
|
||||
reason: null,
|
||||
message: null,
|
||||
suspending: false,
|
||||
user: null,
|
||||
post: null,
|
||||
successCallback: null,
|
||||
|
||||
submitDisabled: function() {
|
||||
return (!this.get('reason') || this.get('reason').length < 1);
|
||||
}.property('reason'),
|
||||
onShow() {
|
||||
this.setProperties({
|
||||
suspendUntil: null,
|
||||
reason: null,
|
||||
message: null,
|
||||
suspending: false,
|
||||
loadingUser: true,
|
||||
post: null,
|
||||
successCallback: null,
|
||||
});
|
||||
},
|
||||
|
||||
@computed('suspendUntil', 'reason', 'suspending')
|
||||
submitDisabled(suspendUntil, reason, suspending) {
|
||||
return (suspending || Ember.isEmpty(suspendUntil) || !reason || reason.length < 1);
|
||||
},
|
||||
|
||||
actions: {
|
||||
suspend: function() {
|
||||
if (this.get('submitDisabled')) return;
|
||||
var duration = parseInt(this.get('duration'), 10);
|
||||
if (duration > 0) {
|
||||
var self = this;
|
||||
this.send('hideModal');
|
||||
this.get('model').suspend(duration, this.get('reason')).then(function() {
|
||||
window.location.reload();
|
||||
}, function(e) {
|
||||
var error = I18n.t('admin.user.suspend_failed', { error: "http: " + e.status + " - " + e.body });
|
||||
bootbox.alert(error, function() { self.send('reopenModal'); });
|
||||
});
|
||||
}
|
||||
suspend() {
|
||||
if (this.get('submitDisabled')) { return; }
|
||||
|
||||
this.set('suspending', true);
|
||||
this.get('user').suspend({
|
||||
suspend_until: this.get('suspendUntil'),
|
||||
reason: this.get('reason'),
|
||||
message: this.get('message'),
|
||||
post_id: this.get('post.id')
|
||||
}).then(result => {
|
||||
this.send('closeModal');
|
||||
let callback = this.get('successCallback');
|
||||
if (callback) {
|
||||
callback(result);
|
||||
}
|
||||
}).catch(popupAjaxError).finally(() => this.set('suspending', false));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
7
app/assets/javascripts/admin/helpers/check-icon.js.es6
Normal file
7
app/assets/javascripts/admin/helpers/check-icon.js.es6
Normal file
@ -0,0 +1,7 @@
|
||||
import { registerUnbound } from 'discourse-common/lib/helpers';
|
||||
import { renderIcon } from 'discourse-common/lib/icon-library';
|
||||
|
||||
registerUnbound('check-icon', function(value) {
|
||||
let icon = value ? "check" : "times";
|
||||
return new Handlebars.SafeString(renderIcon('string', icon));
|
||||
});
|
||||
15
app/assets/javascripts/admin/helpers/disposition-icon.js.es6
Normal file
15
app/assets/javascripts/admin/helpers/disposition-icon.js.es6
Normal file
@ -0,0 +1,15 @@
|
||||
import { iconHTML } from 'discourse-common/lib/icon-library';
|
||||
|
||||
export default Ember.Helper.extend({
|
||||
compute([disposition]) {
|
||||
if (!disposition) { return null; }
|
||||
let icon;
|
||||
let title = 'admin.flags.dispositions.' + disposition;
|
||||
switch (disposition) {
|
||||
case "deferred": { icon = "external-link"; break; }
|
||||
case "agreed": { icon = "thumbs-o-up"; break; }
|
||||
case "disagreed": { icon = "thumbs-o-down"; break; }
|
||||
}
|
||||
return iconHTML(icon, { title }).htmlSafe();
|
||||
}
|
||||
});
|
||||
@ -0,0 +1,12 @@
|
||||
function postActionTitle([id, nameKey]) {
|
||||
let title = I18n.t(`admin.flags.short_names.${nameKey}`, { defaultValue: null });
|
||||
|
||||
// TODO: We can remove this once other translations have been updated
|
||||
if (!title) {
|
||||
return I18n.t(`admin.flags.summary.action_type_${id}`, { count: 1 });
|
||||
}
|
||||
|
||||
return title;
|
||||
}
|
||||
|
||||
export default Ember.Helper.helper(postActionTitle);
|
||||
@ -1,3 +1,4 @@
|
||||
import { iconHTML } from 'discourse-common/lib/icon-library';
|
||||
import { ajax } from 'discourse/lib/ajax';
|
||||
import computed from 'ember-addons/ember-computed-decorators';
|
||||
import { propertyNotEqual } from 'discourse/lib/computed';
|
||||
@ -7,8 +8,10 @@ import Group from 'discourse/models/group';
|
||||
import TL3Requirements from 'admin/models/tl3-requirements';
|
||||
import { userPath } from 'discourse/lib/url';
|
||||
|
||||
const AdminUser = Discourse.User.extend({
|
||||
const wrapAdmin = user => user ? AdminUser.create(user) : null;
|
||||
|
||||
const AdminUser = Discourse.User.extend({
|
||||
adminUserView: true,
|
||||
customGroups: Ember.computed.filter("groups", g => !g.automatic && Group.create(g)),
|
||||
automaticGroups: Ember.computed.filter("groups", g => g.automatic && Group.create(g)),
|
||||
|
||||
@ -105,10 +108,10 @@ const AdminUser = Discourse.User.extend({
|
||||
message = I18n.messageFormat('admin.user.delete_all_posts_confirm_MF', { "POSTS": user.get('post_count'), "TOPICS": user.get('topic_count') }),
|
||||
buttons = [{
|
||||
"label": I18n.t("composer.cancel"),
|
||||
"class": "cancel-inline",
|
||||
"class": "d-modal-cancel",
|
||||
"link": true
|
||||
}, {
|
||||
"label": '<i class="fa fa-exclamation-triangle"></i> ' + I18n.t("admin.user.delete_all_posts"),
|
||||
"label": `${iconHTML('exclamation-triangle')} ` + I18n.t("admin.user.delete_all_posts"),
|
||||
"class": "btn btn-danger",
|
||||
"callback": function() {
|
||||
ajax("/admin/users/" + user.get('id') + "/delete_all_posts", {
|
||||
@ -231,6 +234,7 @@ const AdminUser = Discourse.User.extend({
|
||||
}.property('trust_level'),
|
||||
|
||||
isSuspended: Em.computed.equal('suspended', true),
|
||||
isSilenced: Ember.computed.equal('silenced', true),
|
||||
canSuspend: Em.computed.not('staff'),
|
||||
|
||||
suspendDuration: function() {
|
||||
@ -239,22 +243,17 @@ const AdminUser = Discourse.User.extend({
|
||||
return suspended_at.format('L') + " - " + suspended_till.format('L');
|
||||
}.property('suspended_till', 'suspended_at'),
|
||||
|
||||
suspend(duration, reason) {
|
||||
return ajax("/admin/users/" + this.id + "/suspend", {
|
||||
suspend(data) {
|
||||
return ajax(`/admin/users/${this.id}/suspend`, {
|
||||
type: 'PUT',
|
||||
data: { duration: duration, reason: reason }
|
||||
});
|
||||
data
|
||||
}).then(result => this.setProperties(result.suspension));
|
||||
},
|
||||
|
||||
unsuspend() {
|
||||
return ajax("/admin/users/" + this.id + "/unsuspend", {
|
||||
return ajax(`/admin/users/${this.id}/unsuspend`, {
|
||||
type: 'PUT'
|
||||
}).then(function() {
|
||||
window.location.reload();
|
||||
}).catch(function(e) {
|
||||
var error = I18n.t('admin.user.unsuspend_failed', { error: "http: " + e.status + " - " + e.body });
|
||||
bootbox.alert(error);
|
||||
});
|
||||
}).then(result => this.setProperties(result.suspension));
|
||||
},
|
||||
|
||||
logOut() {
|
||||
@ -303,46 +302,38 @@ const AdminUser = Discourse.User.extend({
|
||||
});
|
||||
},
|
||||
|
||||
unblock() {
|
||||
this.set('blockingUser', true);
|
||||
return ajax('/admin/users/' + this.id + '/unblock', {
|
||||
unsilence() {
|
||||
this.set('silencingUser', true);
|
||||
|
||||
return ajax(`/admin/users/${this.id}/unsilence`, {
|
||||
type: 'PUT'
|
||||
}).then(function() {
|
||||
window.location.reload();
|
||||
}).catch(function(e) {
|
||||
var error = I18n.t('admin.user.unblock_failed', { error: "http: " + e.status + " - " + e.body });
|
||||
}).then(result => {
|
||||
this.setProperties(result.unsilence);
|
||||
}).catch(e => {
|
||||
let error = I18n.t('admin.user.unsilence_failed', {
|
||||
error: `http: ${e.status} - ${e.body}`
|
||||
});
|
||||
bootbox.alert(error);
|
||||
}).finally(() => {
|
||||
this.set('silencingUser', false);
|
||||
});
|
||||
},
|
||||
|
||||
block() {
|
||||
const user = this,
|
||||
message = I18n.t("admin.user.block_confirm");
|
||||
|
||||
const performBlock = function() {
|
||||
user.set('blockingUser', true);
|
||||
return ajax('/admin/users/' + user.id + '/block', {
|
||||
type: 'PUT'
|
||||
}).then(function() {
|
||||
window.location.reload();
|
||||
}).catch(function(e) {
|
||||
var error = I18n.t('admin.user.block_failed', { error: "http: " + e.status + " - " + e.body });
|
||||
bootbox.alert(error);
|
||||
user.set('blockingUser', false);
|
||||
silence(data) {
|
||||
this.set('silencingUser', true);
|
||||
return ajax(`/admin/users/${this.id}/silence`, {
|
||||
type: 'PUT',
|
||||
data
|
||||
}).then(result => {
|
||||
this.setProperties(result.silence);
|
||||
}).catch(e => {
|
||||
let error = I18n.t('admin.user.silence_failed', {
|
||||
error: `http: ${e.status} - ${e.body}`
|
||||
});
|
||||
};
|
||||
|
||||
const buttons = [{
|
||||
"label": I18n.t("composer.cancel"),
|
||||
"class": "cancel",
|
||||
"link": true
|
||||
}, {
|
||||
"label": '<i class="fa fa-exclamation-triangle"></i>' + I18n.t('admin.user.block_accept'),
|
||||
"class": "btn btn-danger",
|
||||
"callback": function() { performBlock(); }
|
||||
}];
|
||||
|
||||
bootbox.dialog(message, buttons, { "classes": "delete-user-modal" });
|
||||
bootbox.alert(error);
|
||||
}).finally(() => {
|
||||
this.set('silencingUser', false);
|
||||
});
|
||||
},
|
||||
|
||||
sendActivationEmail() {
|
||||
@ -386,7 +377,7 @@ const AdminUser = Discourse.User.extend({
|
||||
"class": "cancel",
|
||||
"link": true
|
||||
}, {
|
||||
"label": '<i class="fa fa-exclamation-triangle"></i>' + I18n.t('admin.user.anonymize_yes'),
|
||||
"label": `${iconHTML('exclamation-triangle')} ` + I18n.t('admin.user.anonymize_yes'),
|
||||
"class": "btn btn-danger",
|
||||
"callback": function() { performAnonymize(); }
|
||||
}];
|
||||
@ -450,7 +441,7 @@ const AdminUser = Discourse.User.extend({
|
||||
"class": "btn",
|
||||
"link": true
|
||||
}, {
|
||||
"label": '<i class="fa fa-exclamation-triangle"></i>' + I18n.t('admin.user.delete_and_block'),
|
||||
"label": `${iconHTML('exclamation-triangle')} ` + I18n.t('admin.user.delete_and_block'),
|
||||
"class": "btn btn-danger",
|
||||
"callback": function(){ performDestroy(true); }
|
||||
}, {
|
||||
@ -462,52 +453,6 @@ const AdminUser = Discourse.User.extend({
|
||||
bootbox.dialog(message, buttons, { "classes": "delete-user-modal" });
|
||||
},
|
||||
|
||||
deleteAsSpammer(successCallback) {
|
||||
const user = this;
|
||||
|
||||
user.checkEmail().then(function() {
|
||||
const data = {
|
||||
"POSTS": user.get('post_count'),
|
||||
"TOPICS": user.get('topic_count'),
|
||||
email: user.get('email') || I18n.t("flagging.hidden_email_address"),
|
||||
ip_address: user.get('ip_address') || I18n.t("flagging.ip_address_missing")
|
||||
};
|
||||
|
||||
const message = I18n.messageFormat('flagging.delete_confirm_MF', data),
|
||||
buttons = [{
|
||||
"label": I18n.t("composer.cancel"),
|
||||
"class": "cancel-inline",
|
||||
"link": true
|
||||
}, {
|
||||
"label": '<i class="fa fa-exclamation-triangle"></i> ' + I18n.t("flagging.yes_delete_spammer"),
|
||||
"class": "btn btn-danger",
|
||||
"callback": function() {
|
||||
return ajax("/admin/users/" + user.get('id') + '.json', {
|
||||
type: 'DELETE',
|
||||
data: {
|
||||
delete_posts: true,
|
||||
block_email: true,
|
||||
block_urls: true,
|
||||
block_ip: true,
|
||||
delete_as_spammer: true,
|
||||
context: window.location.pathname
|
||||
}
|
||||
}).then(function(result) {
|
||||
if (result.deleted) {
|
||||
if (successCallback) successCallback();
|
||||
} else {
|
||||
bootbox.alert(I18n.t("admin.user.delete_failed"));
|
||||
}
|
||||
}).catch(function() {
|
||||
bootbox.alert(I18n.t("admin.user.delete_failed"));
|
||||
});
|
||||
}
|
||||
}];
|
||||
|
||||
bootbox.dialog(message, buttons, {"classes": "flagging-delete-spammer"});
|
||||
});
|
||||
},
|
||||
|
||||
loadDetails() {
|
||||
const user = this;
|
||||
|
||||
@ -525,17 +470,14 @@ const AdminUser = Discourse.User.extend({
|
||||
}
|
||||
}.property('tl3_requirements'),
|
||||
|
||||
suspendedBy: function() {
|
||||
if (this.get('suspended_by')) {
|
||||
return AdminUser.create(this.get('suspended_by'));
|
||||
}
|
||||
}.property('suspended_by'),
|
||||
@computed('suspended_by')
|
||||
suspendedBy: wrapAdmin,
|
||||
|
||||
approvedBy: function() {
|
||||
if (this.get('approved_by')) {
|
||||
return AdminUser.create(this.get('approved_by'));
|
||||
}
|
||||
}.property('approved_by')
|
||||
@computed('silenced_by')
|
||||
silencedBy: wrapAdmin,
|
||||
|
||||
@computed('approved_by')
|
||||
approvedBy: wrapAdmin,
|
||||
|
||||
});
|
||||
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
import AdminUser from 'admin/models/admin-user';
|
||||
import { ajax } from 'discourse/lib/ajax';
|
||||
|
||||
const ApiKey = Discourse.Model.extend({
|
||||
|
||||
/**
|
||||
@ -36,8 +38,7 @@ ApiKey.reopenClass({
|
||||
@param {...} var_args the properties to initialize this with
|
||||
@returns {ApiKey} the ApiKey instance
|
||||
**/
|
||||
create: function() {
|
||||
const AdminUser = require('admin/models/admin-user').default;
|
||||
create() {
|
||||
var result = this._super.apply(this, arguments);
|
||||
if (result.user) {
|
||||
result.user = AdminUser.create(result.user);
|
||||
|
||||
@ -1,42 +1,24 @@
|
||||
import { ajax } from 'discourse/lib/ajax';
|
||||
const EmailPreview = Discourse.Model.extend({});
|
||||
|
||||
export function oneWeekAgo() {
|
||||
return moment().locale('en').subtract(7, 'days').format('YYYY-MM-DD');
|
||||
}
|
||||
|
||||
EmailPreview.reopenClass({
|
||||
findDigest: function(lastSeenAt, username) {
|
||||
|
||||
if (Em.isEmpty(lastSeenAt)) {
|
||||
lastSeenAt = this.oneWeekAgo();
|
||||
}
|
||||
|
||||
if (Em.isEmpty(username)) {
|
||||
username = Discourse.User.current().username;
|
||||
}
|
||||
|
||||
findDigest(username, lastSeenAt) {
|
||||
return ajax("/admin/email/preview-digest.json", {
|
||||
data: { last_seen_at: lastSeenAt, username: username }
|
||||
}).then(function (result) {
|
||||
return EmailPreview.create(result);
|
||||
});
|
||||
data: { last_seen_at: lastSeenAt || oneWeekAgo(), username }
|
||||
}).then(result => EmailPreview.create(result));
|
||||
},
|
||||
|
||||
sendDigest: function(lastSeenAt, username, email) {
|
||||
if (Em.isEmpty(lastSeenAt)) {
|
||||
lastSeenAt = this.oneWeekAgo();
|
||||
}
|
||||
|
||||
if (Em.isEmpty(username)) {
|
||||
username = Discourse.User.current().username;
|
||||
}
|
||||
|
||||
sendDigest(username, lastSeenAt, email) {
|
||||
return ajax("/admin/email/send-digest.json", {
|
||||
data: { last_seen_at: lastSeenAt, username: username, email: email }
|
||||
data: { last_seen_at: lastSeenAt || oneWeekAgo(), username, email }
|
||||
});
|
||||
},
|
||||
|
||||
oneWeekAgo() {
|
||||
const en = moment().locale('en');
|
||||
return en.subtract(7, 'days').format('YYYY-MM-DD');
|
||||
}
|
||||
});
|
||||
|
||||
export default EmailPreview;
|
||||
|
||||
10
app/assets/javascripts/admin/models/flag-type.js.es6
Normal file
10
app/assets/javascripts/admin/models/flag-type.js.es6
Normal file
@ -0,0 +1,10 @@
|
||||
import RestModel from 'discourse/models/rest';
|
||||
import computed from 'ember-addons/ember-computed-decorators';
|
||||
|
||||
export default RestModel.extend({
|
||||
@computed('id')
|
||||
name(id) {
|
||||
return I18n.t(`admin.flags.summary.action_type_${id}`, { count: 1});
|
||||
}
|
||||
});
|
||||
|
||||
@ -1,111 +1,50 @@
|
||||
import { ajax } from 'discourse/lib/ajax';
|
||||
import AdminUser from 'admin/models/admin-user';
|
||||
import Topic from 'discourse/models/topic';
|
||||
import Post from 'discourse/models/post';
|
||||
import computed from 'ember-addons/ember-computed-decorators';
|
||||
import { popupAjaxError } from 'discourse/lib/ajax-error';
|
||||
|
||||
export default Post.extend({
|
||||
|
||||
const FlaggedPost = Post.extend({
|
||||
|
||||
summary: function () {
|
||||
@computed
|
||||
summary() {
|
||||
return _(this.post_actions)
|
||||
.groupBy(function (a) { return a.post_action_type_id; })
|
||||
.map(function (v,k) { return I18n.t('admin.flags.summary.action_type_' + k, { count: v.length }); })
|
||||
.join(',');
|
||||
}.property(),
|
||||
|
||||
flaggers: function () {
|
||||
var self = this;
|
||||
var flaggers = [];
|
||||
|
||||
_.each(this.post_actions, function (postAction) {
|
||||
flaggers.push({
|
||||
user: self.userLookup[postAction.user_id],
|
||||
topic: self.topicLookup[postAction.topic_id],
|
||||
flagType: I18n.t('admin.flags.summary.action_type_' + postAction.post_action_type_id, { count: 1 }),
|
||||
flaggedAt: postAction.created_at,
|
||||
disposedBy: postAction.disposed_by_id ? self.userLookup[postAction.disposed_by_id] : null,
|
||||
disposedAt: postAction.disposed_at,
|
||||
dispositionIcon: self.dispositionIcon(postAction.disposition),
|
||||
tookAction: postAction.staff_took_action
|
||||
});
|
||||
});
|
||||
|
||||
return flaggers;
|
||||
}.property(),
|
||||
|
||||
dispositionIcon: function (disposition) {
|
||||
if (!disposition) { return null; }
|
||||
var icon, title = I18n.t('admin.flags.dispositions.' + disposition);
|
||||
switch (disposition) {
|
||||
case "deferred": { icon = "fa-external-link"; break; }
|
||||
case "agreed": { icon = "fa-thumbs-o-up"; break; }
|
||||
case "disagreed": { icon = "fa-thumbs-o-down"; break; }
|
||||
}
|
||||
return "<i class='fa " + icon + "' title='" + title + "'></i>";
|
||||
},
|
||||
|
||||
wasEdited: function () {
|
||||
@computed('last_revised_at', 'post_actions.@each.created_at')
|
||||
wasEdited(lastRevisedAt) {
|
||||
if (Ember.isEmpty(this.get("last_revised_at"))) { return false; }
|
||||
var lastRevisedAt = Date.parse(this.get("last_revised_at"));
|
||||
lastRevisedAt = Date.parse(lastRevisedAt);
|
||||
return _.some(this.get("post_actions"), function (postAction) {
|
||||
return Date.parse(postAction.created_at) < lastRevisedAt;
|
||||
});
|
||||
}.property("last_revised_at", "post_actions.@each.created_at"),
|
||||
},
|
||||
|
||||
conversations: function () {
|
||||
var self = this;
|
||||
var conversations = [];
|
||||
@computed('post_actions.@each.name_key')
|
||||
flaggedForSpam() {
|
||||
return this.get('post_actions').every(action => action.name_key === 'spam');
|
||||
},
|
||||
|
||||
_.each(this.post_actions, function (postAction) {
|
||||
if (postAction.conversation) {
|
||||
var conversation = {
|
||||
permalink: postAction.permalink,
|
||||
hasMore: postAction.conversation.has_more,
|
||||
response: {
|
||||
excerpt: postAction.conversation.response.excerpt,
|
||||
user: self.userLookup[postAction.conversation.response.user_id]
|
||||
}
|
||||
};
|
||||
|
||||
if (postAction.conversation.reply) {
|
||||
conversation["reply"] = {
|
||||
excerpt: postAction.conversation.reply.excerpt,
|
||||
user: self.userLookup[postAction.conversation.reply.user_id]
|
||||
};
|
||||
}
|
||||
|
||||
conversations.push(conversation);
|
||||
}
|
||||
});
|
||||
|
||||
return conversations;
|
||||
}.property(),
|
||||
|
||||
user: function() {
|
||||
return this.userLookup[this.user_id];
|
||||
}.property(),
|
||||
|
||||
topic: function () {
|
||||
return this.topicLookup[this.topic_id];
|
||||
}.property(),
|
||||
|
||||
flaggedForSpam: function() {
|
||||
return !_.every(this.get('post_actions'), function(action) { return action.name_key !== 'spam'; });
|
||||
}.property('post_actions.@each.name_key'),
|
||||
|
||||
topicFlagged: function() {
|
||||
@computed('post_actions.@each.targets_topic')
|
||||
topicFlagged() {
|
||||
return _.any(this.get('post_actions'), function(action) { return action.targets_topic; });
|
||||
}.property('post_actions.@each.targets_topic'),
|
||||
},
|
||||
|
||||
postAuthorFlagged: function() {
|
||||
@computed('post_actions.@each.targets_topic')
|
||||
postAuthorFlagged() {
|
||||
return _.any(this.get('post_actions'), function(action) { return !action.targets_topic; });
|
||||
}.property('post_actions.@each.targets_topic'),
|
||||
},
|
||||
|
||||
canDeleteAsSpammer: function() {
|
||||
return Discourse.User.currentProp('staff') && this.get('flaggedForSpam') && this.get('user.can_delete_all_posts') && this.get('user.can_be_deleted');
|
||||
}.property('flaggedForSpam'),
|
||||
@computed('flaggedForSpam')
|
||||
canDeleteAsSpammer(flaggedForSpam) {
|
||||
return flaggedForSpam &&
|
||||
this.get('user.can_delete_all_posts') &&
|
||||
this.get('user.can_be_deleted');
|
||||
},
|
||||
|
||||
deletePost: function() {
|
||||
deletePost() {
|
||||
if (this.get('post_number') === 1) {
|
||||
return ajax('/t/' + this.topic_id, { type: 'DELETE', cache: false });
|
||||
} else {
|
||||
@ -113,64 +52,20 @@ const FlaggedPost = Post.extend({
|
||||
}
|
||||
},
|
||||
|
||||
disagreeFlags: function () {
|
||||
return ajax('/admin/flags/disagree/' + this.id, { type: 'POST', cache: false });
|
||||
disagreeFlags() {
|
||||
return ajax('/admin/flags/disagree/' + this.id, { type: 'POST', cache: false }).catch(popupAjaxError);
|
||||
},
|
||||
|
||||
deferFlags: function (deletePost) {
|
||||
return ajax('/admin/flags/defer/' + this.id, { type: 'POST', cache: false, data: { delete_post: deletePost } });
|
||||
deferFlags(deletePost) {
|
||||
return ajax('/admin/flags/defer/' + this.id, { type: 'POST', cache: false, data: { delete_post: deletePost } }).catch(popupAjaxError);
|
||||
},
|
||||
|
||||
agreeFlags: function (actionOnPost) {
|
||||
return ajax('/admin/flags/agree/' + this.id, { type: 'POST', cache: false, data: { action_on_post: actionOnPost } });
|
||||
agreeFlags(actionOnPost) {
|
||||
return ajax('/admin/flags/agree/' + this.id, { type: 'POST', cache: false, data: { action_on_post: actionOnPost } }).catch(popupAjaxError);
|
||||
},
|
||||
|
||||
postHidden: Em.computed.alias('hidden'),
|
||||
postHidden: Ember.computed.alias('hidden'),
|
||||
|
||||
extraClasses: function() {
|
||||
var classes = [];
|
||||
if (this.get('hidden')) { classes.push('hidden-post'); }
|
||||
if (this.get('deleted')) { classes.push('deleted'); }
|
||||
return classes.join(' ');
|
||||
}.property(),
|
||||
|
||||
deleted: Em.computed.or('deleted_at', 'topic_deleted_at')
|
||||
deleted: Ember.computed.or('deleted_at', 'topic_deleted_at'),
|
||||
|
||||
});
|
||||
|
||||
FlaggedPost.reopenClass({
|
||||
findAll: function (filter, offset) {
|
||||
offset = offset || 0;
|
||||
|
||||
var result = Em.A();
|
||||
result.set('loading', true);
|
||||
|
||||
return ajax('/admin/flags/' + filter + '.json?offset=' + offset).then(function (data) {
|
||||
// users
|
||||
var userLookup = {};
|
||||
_.each(data.users, function (user) {
|
||||
userLookup[user.id] = AdminUser.create(user);
|
||||
});
|
||||
|
||||
// topics
|
||||
var topicLookup = {};
|
||||
_.each(data.topics, function (topic) {
|
||||
topicLookup[topic.id] = Topic.create(topic);
|
||||
});
|
||||
|
||||
// posts
|
||||
_.each(data.posts, function (post) {
|
||||
var f = FlaggedPost.create(post);
|
||||
f.userLookup = userLookup;
|
||||
f.topicLookup = topicLookup;
|
||||
result.pushObject(f);
|
||||
});
|
||||
|
||||
result.set('loading', false);
|
||||
|
||||
return result;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
export default FlaggedPost;
|
||||
|
||||
@ -48,7 +48,7 @@ SiteSetting.reopenClass({
|
||||
update(key, value) {
|
||||
const data = {};
|
||||
data[key] = value;
|
||||
return ajax("/admin/site_settings/" + key, { type: 'PUT', data });
|
||||
return ajax(`/admin/site_settings/${key}`, { type: 'PUT', data });
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@ -8,18 +8,6 @@ const VersionCheck = Discourse.Model.extend({
|
||||
return updatedAt === null;
|
||||
},
|
||||
|
||||
@computed('updated_at', 'version_check_pending')
|
||||
dataIsOld(updatedAt, versionCheckPending) {
|
||||
return versionCheckPending || moment().diff(moment(updatedAt), 'hours') >= 48;
|
||||
},
|
||||
|
||||
@computed('dataIsOld', 'installed_version', 'latest_version', 'missing_versions_count')
|
||||
staleData(dataIsOld, installedVersion, latestVersion, missingVersionsCount) {
|
||||
return dataIsOld ||
|
||||
(installedVersion !== latestVersion && missingVersionsCount === 0) ||
|
||||
(installedVersion === latestVersion && missingVersionsCount !== 0);
|
||||
},
|
||||
|
||||
@computed('missing_versions_count')
|
||||
upToDate(missingVersionsCount) {
|
||||
return missingVersionsCount === 0 || missingVersionsCount === null;
|
||||
|
||||
43
app/assets/javascripts/admin/models/watched-word.js.es6
Normal file
43
app/assets/javascripts/admin/models/watched-word.js.es6
Normal file
@ -0,0 +1,43 @@
|
||||
import { ajax } from 'discourse/lib/ajax';
|
||||
|
||||
const WatchedWord = Discourse.Model.extend({
|
||||
save() {
|
||||
return ajax("/admin/logs/watched_words" + (this.id ? '/' + this.id : '') + ".json", {
|
||||
type: this.id ? 'PUT' : 'POST',
|
||||
data: {word: this.get('word'), action_key: this.get('action')},
|
||||
dataType: 'json'
|
||||
});
|
||||
},
|
||||
|
||||
destroy() {
|
||||
return ajax("/admin/logs/watched_words/" + this.get('id') + ".json", {type: 'DELETE'});
|
||||
}
|
||||
});
|
||||
|
||||
WatchedWord.reopenClass({
|
||||
findAll() {
|
||||
return ajax("/admin/logs/watched_words").then(list => {
|
||||
const actions = {};
|
||||
list.words.forEach(s => {
|
||||
if (!actions[s.action]) { actions[s.action] = []; }
|
||||
actions[s.action].pushObject(WatchedWord.create(s));
|
||||
});
|
||||
|
||||
list.actions.forEach(a => {
|
||||
if (!actions[a]) { actions[a] = []; }
|
||||
});
|
||||
|
||||
return Object.keys(actions).map(n => {
|
||||
return Ember.Object.create({
|
||||
nameKey: n,
|
||||
name: I18n.t('admin.watched_words.actions.' + n),
|
||||
words: actions[n],
|
||||
count: actions[n].length,
|
||||
regularExpressions: list.regular_expressions
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
export default WatchedWord;
|
||||
@ -37,7 +37,7 @@ export default RestModel.extend({
|
||||
},
|
||||
|
||||
groupFinder(term) {
|
||||
return Group.findAll({search: term, ignore_automatic: false});
|
||||
return Group.findAll({ term: term, ignore_automatic: false });
|
||||
},
|
||||
|
||||
@computed('wildcard_web_hook', 'web_hook_event_types.[]')
|
||||
@ -82,4 +82,3 @@ export default RestModel.extend({
|
||||
return this.createProperties();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@ -68,7 +68,7 @@ export default Discourse.Route.extend({
|
||||
function(confirmed) {
|
||||
if (confirmed) {
|
||||
backup.destroy().then(function() {
|
||||
self.controllerFor("adminBackupsIndex").removeObject(backup);
|
||||
self.controllerFor("adminBackupsIndex").get('model').removeObject(backup);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,16 +1,17 @@
|
||||
import EmailPreview from 'admin/models/email-preview';
|
||||
import { default as EmailPreview, oneWeekAgo } from 'admin/models/email-preview';
|
||||
|
||||
export default Discourse.Route.extend({
|
||||
|
||||
model() {
|
||||
return EmailPreview.findDigest();
|
||||
return EmailPreview.findDigest(this.currentUser.get('username'));
|
||||
},
|
||||
|
||||
afterModel(model) {
|
||||
const controller = this.controllerFor('adminEmailPreviewDigest');
|
||||
controller.setProperties({
|
||||
model: model,
|
||||
lastSeen: moment().subtract(7, 'days').format('YYYY-MM-DD'),
|
||||
model,
|
||||
username: this.currentUser.get('username'),
|
||||
lastSeen: oneWeekAgo(),
|
||||
showHtml: true
|
||||
});
|
||||
}
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
export default Discourse.Route.extend({
|
||||
redirect() {
|
||||
this.replaceWith('adminFlags.list', 'active');
|
||||
let segment = this.siteSettings.flags_default_topics ? 'topics' : 'postsActive';
|
||||
this.replaceWith(`adminFlags.${segment}`);
|
||||
}
|
||||
});
|
||||
|
||||
@ -1,27 +0,0 @@
|
||||
import showModal from 'discourse/lib/show-modal';
|
||||
import FlaggedPost from 'admin/models/flagged-post';
|
||||
|
||||
export default Discourse.Route.extend({
|
||||
model(params) {
|
||||
this.filter = params.filter;
|
||||
return FlaggedPost.findAll(params.filter);
|
||||
},
|
||||
|
||||
setupController(controller, model) {
|
||||
controller.set('model', model);
|
||||
controller.set('query', this.filter);
|
||||
},
|
||||
|
||||
actions: {
|
||||
showAgreeFlagModal(model) {
|
||||
showModal('admin-agree-flag', { model, admin: true });
|
||||
this.controllerFor('modal').set('modalClass', 'agree-flag-modal');
|
||||
},
|
||||
|
||||
showDeleteFlagModal(model) {
|
||||
showModal('admin-delete-flag', { model, admin: true });
|
||||
this.controllerFor('modal').set('modalClass', 'delete-flag-modal');
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
@ -0,0 +1,5 @@
|
||||
export default Discourse.Route.extend({
|
||||
model() {
|
||||
return this.store.findAll('flagged-post', { filter: 'active' });
|
||||
}
|
||||
});
|
||||
@ -0,0 +1,5 @@
|
||||
export default Discourse.Route.extend({
|
||||
model() {
|
||||
return this.store.findAll('flagged-post', { filter: 'old' });
|
||||
},
|
||||
});
|
||||
@ -0,0 +1,9 @@
|
||||
export default Discourse.Route.extend({
|
||||
model() {
|
||||
return this.store.findAll('flagged-topic');
|
||||
},
|
||||
|
||||
setupController(controller, model) {
|
||||
controller.set('flaggedTopics', model);
|
||||
}
|
||||
});
|
||||
@ -0,0 +1,20 @@
|
||||
import { loadTopicView } from 'discourse/models/topic';
|
||||
|
||||
export default Ember.Route.extend({
|
||||
model(params) {
|
||||
let topicRecord = this.store.createRecord('topic', { id: params.id });
|
||||
let topic = loadTopicView(topicRecord).then(() => topicRecord);
|
||||
|
||||
return Ember.RSVP.hash({
|
||||
topic,
|
||||
flaggedPosts: this.store.findAll('flagged-post', {
|
||||
filter: 'active',
|
||||
topic_id: params.id
|
||||
})
|
||||
});
|
||||
},
|
||||
|
||||
setupController(controller, hash) {
|
||||
controller.setProperties(hash);
|
||||
}
|
||||
});
|
||||
@ -4,7 +4,7 @@ export default Discourse.Route.extend({
|
||||
|
||||
model(params) {
|
||||
if (params.name === 'new') {
|
||||
return Group.create({ automatic: false, visible: true });
|
||||
return Group.create({ automatic: false, visibility_level: 0 });
|
||||
}
|
||||
|
||||
const group = this.modelFor('adminGroupsType').findBy('name', params.name);
|
||||
|
||||
@ -1,16 +1,9 @@
|
||||
/**
|
||||
Handles routes for admin reports
|
||||
import Report from 'admin/models/report';
|
||||
|
||||
@class AdminReportsRoute
|
||||
@extends Discourse.Route
|
||||
@namespace Discourse
|
||||
@module Discourse
|
||||
**/
|
||||
export default Discourse.Route.extend({
|
||||
queryParams: { mode: {}, "start_date": {}, "end_date": {}, "category_id": {}, "group_id": {} },
|
||||
|
||||
model: function(params) {
|
||||
const Report = require('admin/models/report').default;
|
||||
model(params) {
|
||||
return Report.find(params.type, params['start_date'], params['end_date'], params['category_id'], params['group_id']);
|
||||
},
|
||||
|
||||
|
||||
@ -54,7 +54,11 @@ export default function() {
|
||||
this.route('adminReports', { path: '/reports/:type', resetNamespace: true });
|
||||
|
||||
this.route('adminFlags', { path: '/flags', resetNamespace: true }, function() {
|
||||
this.route('list', { path: '/:filter' });
|
||||
this.route('postsActive', { path: 'active' });
|
||||
this.route('postsOld', { path: 'old' });
|
||||
this.route('topics', { path: 'topics' }, function() {
|
||||
this.route('show', { path: ":id" });
|
||||
});
|
||||
});
|
||||
|
||||
this.route('adminLogs', { path: '/logs', resetNamespace: true }, function() {
|
||||
@ -62,6 +66,14 @@ export default function() {
|
||||
this.route('screenedEmails', { path: '/screened_emails' });
|
||||
this.route('screenedIpAddresses', { path: '/screened_ip_addresses' });
|
||||
this.route('screenedUrls', { path: '/screened_urls' });
|
||||
this.route('adminSearchLogs', { path: '/search_logs', resetNamespace: true}, function() {
|
||||
this.route('index', { path: '/' });
|
||||
this.route('term', { path: '/term/:term' });
|
||||
});
|
||||
this.route('adminWatchedWords', { path: '/watched_words', resetNamespace: true}, function() {
|
||||
this.route('index', { path: '/' });
|
||||
this.route('action', { path: '/action/:action_id' });
|
||||
});
|
||||
});
|
||||
|
||||
this.route('adminGroups', { path: '/groups', resetNamespace: true }, function() {
|
||||
|
||||
@ -0,0 +1,20 @@
|
||||
import { ajax } from 'discourse/lib/ajax';
|
||||
|
||||
export default Discourse.Route.extend({
|
||||
queryParams: {
|
||||
period: { refreshModel: true },
|
||||
searchType: { refreshModel: true }
|
||||
},
|
||||
|
||||
model(params) {
|
||||
this._params = params;
|
||||
return ajax('/admin/logs/search_logs.json', { data: { period: params.period, search_type: params.searchType } }).then(search_logs => {
|
||||
return search_logs.map(sl => Ember.Object.create(sl));
|
||||
});
|
||||
},
|
||||
|
||||
setupController(controller, model) {
|
||||
const params = this._params;
|
||||
controller.setProperties({ model, period: params.period, searchType: params.searchType });
|
||||
}
|
||||
});
|
||||
@ -0,0 +1,33 @@
|
||||
import { ajax } from 'discourse/lib/ajax';
|
||||
|
||||
export default Discourse.Route.extend({
|
||||
queryParams: {
|
||||
period: { refreshModel: true },
|
||||
searchType: { refreshModel: true }
|
||||
},
|
||||
|
||||
model(params) {
|
||||
this._params = params;
|
||||
|
||||
return ajax(`/admin/logs/search_logs/term/${params.term}.json`, {
|
||||
data: {
|
||||
period: params.period,
|
||||
search_type: params.searchType
|
||||
}
|
||||
}).then(json => {
|
||||
const model = Ember.Object.create({ type: "search_log_term" });
|
||||
model.setProperties(json.term);
|
||||
return model;
|
||||
});
|
||||
},
|
||||
|
||||
setupController(controller, model) {
|
||||
const params = this._params;
|
||||
controller.setProperties({
|
||||
model,
|
||||
term: params.term,
|
||||
period: params.period,
|
||||
searchType: params.searchType
|
||||
});
|
||||
}
|
||||
});
|
||||
@ -1,4 +1,3 @@
|
||||
import showModal from 'discourse/lib/show-modal';
|
||||
import Group from 'discourse/models/group';
|
||||
|
||||
export default Discourse.Route.extend({
|
||||
@ -25,11 +24,6 @@ export default Discourse.Route.extend({
|
||||
},
|
||||
|
||||
actions: {
|
||||
showSuspendModal(model) {
|
||||
showModal('admin-suspend-user', { model, admin: true });
|
||||
this.controllerFor('modal').set('modalClass', 'suspend-user-modal');
|
||||
},
|
||||
|
||||
viewActionLogs(username) {
|
||||
const controller = this.controllerFor('adminLogs.staffActionLogs');
|
||||
this.transitionTo('adminLogs.staffActionLogs').then(() => {
|
||||
|
||||
@ -0,0 +1,5 @@
|
||||
export default Discourse.Route.extend({
|
||||
redirect: function() {
|
||||
this.transitionTo('adminUsersList');
|
||||
}
|
||||
});
|
||||
@ -0,0 +1,11 @@
|
||||
export default Discourse.Route.extend({
|
||||
model(params) {
|
||||
this.controllerFor('adminWatchedWordsAction').set('actionNameKey', params.action_id);
|
||||
let filteredContent = this.controllerFor('adminWatchedWordsAction').get('filteredContent');
|
||||
return Ember.Object.create({
|
||||
nameKey: params.action_id,
|
||||
name: I18n.t('admin.watched_words.actions.' + params.action_id),
|
||||
words: filteredContent
|
||||
});
|
||||
}
|
||||
});
|
||||
@ -0,0 +1,5 @@
|
||||
export default Discourse.Route.extend({
|
||||
beforeModel() {
|
||||
this.replaceWith('adminWatchedWords.action', this.modelFor('adminWatchedWords')[0].nameKey);
|
||||
}
|
||||
});
|
||||
@ -0,0 +1,22 @@
|
||||
import WatchedWord from 'admin/models/watched-word';
|
||||
|
||||
export default Discourse.Route.extend({
|
||||
queryParams: {
|
||||
filter: { replace: true }
|
||||
},
|
||||
|
||||
model() {
|
||||
return WatchedWord.findAll();
|
||||
},
|
||||
|
||||
setupController(controller, model) {
|
||||
controller.set('model', model);
|
||||
if (model && model.length) {
|
||||
controller.set('regularExpressions', model[0].get('regularExpressions'));
|
||||
}
|
||||
},
|
||||
|
||||
afterModel(watchedWordsList) {
|
||||
this.controllerFor('adminWatchedWords').set('allWatchedWords', watchedWordsList);
|
||||
}
|
||||
});
|
||||
127
app/assets/javascripts/admin/services/admin-tools.js.es6
Normal file
127
app/assets/javascripts/admin/services/admin-tools.js.es6
Normal file
@ -0,0 +1,127 @@
|
||||
// A service that can act as a bridge between the front end Discourse application
|
||||
// and the admin application. Use this if you need front end code to access admin
|
||||
// modules. Inject it optionally, and if it exists go to town!
|
||||
|
||||
import AdminUser from 'admin/models/admin-user';
|
||||
import { iconHTML } from 'discourse-common/lib/icon-library';
|
||||
import { ajax } from 'discourse/lib/ajax';
|
||||
import showModal from 'discourse/lib/show-modal';
|
||||
import { getOwner } from 'discourse-common/lib/get-owner';
|
||||
|
||||
export default Ember.Service.extend({
|
||||
|
||||
init() {
|
||||
this._super();
|
||||
|
||||
// TODO: Make `siteSettings` a service that can be injected
|
||||
this.siteSettings = getOwner(this).lookup('site-settings:main');
|
||||
},
|
||||
|
||||
checkSpammer(userId) {
|
||||
return AdminUser.find(userId).then(au => this.spammerDetails(au));
|
||||
},
|
||||
|
||||
spammerDetails(adminUser) {
|
||||
return {
|
||||
deleteUser: () => this._deleteSpammer(adminUser),
|
||||
canDelete: adminUser.get('can_be_deleted') && adminUser.get('can_delete_all_posts')
|
||||
};
|
||||
},
|
||||
|
||||
_showControlModal(type, user, opts) {
|
||||
opts = opts || {};
|
||||
|
||||
let controller = showModal(`admin-${type}-user`, {
|
||||
admin: true,
|
||||
modalClass: `${type}-user-modal`
|
||||
});
|
||||
if (opts.post) {
|
||||
controller.set('post', opts.post);
|
||||
}
|
||||
|
||||
let promise = user.adminUserView ?
|
||||
Ember.RSVP.resolve(user) :
|
||||
AdminUser.find(user.get('id'));
|
||||
|
||||
promise.then(loadedUser => {
|
||||
controller.setProperties({
|
||||
user: loadedUser,
|
||||
loadingUser: false,
|
||||
successCallback: opts.successCallback
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
showSilenceModal(user, opts) {
|
||||
this._showControlModal('silence', user, opts);
|
||||
},
|
||||
|
||||
showSuspendModal(user, opts) {
|
||||
this._showControlModal('suspend', user, opts);
|
||||
},
|
||||
|
||||
showModerationHistory(target) {
|
||||
let controller = showModal('admin-moderation-history', { admin: true });
|
||||
controller.loadHistory(target);
|
||||
},
|
||||
|
||||
_deleteSpammer(adminUser) {
|
||||
|
||||
// Try loading the email if the site supports it
|
||||
let tryEmail = this.siteSettings.show_email_on_profile ?
|
||||
adminUser.checkEmail() :
|
||||
Ember.RSVP.resolve();
|
||||
|
||||
return tryEmail.then(() => {
|
||||
|
||||
let message = I18n.messageFormat('flagging.delete_confirm_MF', {
|
||||
"POSTS": adminUser.get('post_count'),
|
||||
"TOPICS": adminUser.get('topic_count'),
|
||||
email: adminUser.get('email') || I18n.t("flagging.hidden_email_address"),
|
||||
ip_address: adminUser.get('ip_address') || I18n.t("flagging.ip_address_missing")
|
||||
});
|
||||
|
||||
let userId = adminUser.get('id');
|
||||
|
||||
return new Ember.RSVP.Promise((resolve, reject) => {
|
||||
const buttons = [
|
||||
{
|
||||
label: I18n.t("composer.cancel"),
|
||||
class: "d-modal-cancel",
|
||||
link: true
|
||||
},
|
||||
{
|
||||
label: `${iconHTML('exclamation-triangle')} ` + I18n.t("flagging.yes_delete_spammer"),
|
||||
class: "btn btn-danger confirm-delete",
|
||||
callback() {
|
||||
return ajax(`/admin/users/${userId}.json`, {
|
||||
type: 'DELETE',
|
||||
data: {
|
||||
delete_posts: true,
|
||||
block_email: true,
|
||||
block_urls: true,
|
||||
block_ip: true,
|
||||
delete_as_spammer: true,
|
||||
context: window.location.pathname
|
||||
}
|
||||
}).then(result => {
|
||||
if (result.deleted) {
|
||||
resolve();
|
||||
} else {
|
||||
throw 'failed to delete';
|
||||
}
|
||||
}).catch(() => {
|
||||
bootbox.alert(I18n.t("admin.user.delete_failed"));
|
||||
reject();
|
||||
});
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
bootbox.dialog(message, buttons, {classes: "flagging-delete-spammer"});
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
@ -20,14 +20,16 @@
|
||||
{{#if currentUser.admin}}
|
||||
{{nav-item route='adminCustomize' label='admin.customize.title'}}
|
||||
{{nav-item route='adminApi' label='admin.api.title'}}
|
||||
{{nav-item route='admin.backups' label='admin.backups.title'}}
|
||||
{{#if siteSettings.enable_backups}}
|
||||
{{nav-item route='admin.backups' label='admin.backups.title'}}
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
{{nav-item route='adminPlugins' label='admin.plugins.title'}}
|
||||
{{plugin-outlet name="admin-menu" connectorTagName="li"}}
|
||||
</ul>
|
||||
|
||||
<div class='boxed white admin-content'>
|
||||
<div class='admin-contents'>
|
||||
<div class='admin-contents {{adminContentsClassName}}'>
|
||||
{{outlet}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -29,5 +29,5 @@
|
||||
{{/if}}
|
||||
|
||||
{{#unless hasMasterKey}}
|
||||
<button class='btn' {{action "generateMasterKey"}}><i class="fa fa-key"></i>{{i18n 'admin.api.generate_master'}}</button>
|
||||
<button class='btn' {{action "generateMasterKey"}}>{{d-icon "key"}}</button>
|
||||
{{/unless}}
|
||||
|
||||
@ -1,10 +1,8 @@
|
||||
<div class="api">
|
||||
{{#admin-nav}}
|
||||
{{nav-item route='adminApiKeys' label='admin.api.title'}}
|
||||
{{nav-item route='adminWebHooks' label='admin.web_hooks.title'}}
|
||||
{{/admin-nav}}
|
||||
{{#admin-nav}}
|
||||
{{nav-item route='adminApiKeys' label='admin.api.title'}}
|
||||
{{nav-item route='adminWebHooks' label='admin.web_hooks.title'}}
|
||||
{{/admin-nav}}
|
||||
|
||||
<div class="admin-container">
|
||||
{{outlet}}
|
||||
</div>
|
||||
<div class="admin-container">
|
||||
{{outlet}}
|
||||
</div>
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
<ul class="nav nav-pills">
|
||||
{{nav-item route='admin.backups.index' label='admin.backups.menu.backups'}}
|
||||
{{nav-item route='admin.backups.logs' label='admin.backups.menu.logs'}}
|
||||
{{plugin-outlet name="downloader" tagName=""}}
|
||||
</ul>
|
||||
</div>
|
||||
<div class="pull-right">
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
|
||||
<div>
|
||||
{{#link-to 'adminBadges.show' 'new' class="btn"}}
|
||||
{{fa-icon "plus"}} {{i18n 'admin.badges.new'}}
|
||||
{{d-icon "plus"}} {{i18n 'admin.badges.new'}}
|
||||
{{/link-to}}
|
||||
</div>
|
||||
{{/d-section}}
|
||||
|
||||
@ -26,9 +26,8 @@
|
||||
{{combo-box name="badge_type_id"
|
||||
value=buffered.badge_type_id
|
||||
content=badgeTypes
|
||||
optionValuePath="content.id"
|
||||
optionLabelPath="content.name"
|
||||
disabled=readOnly}}
|
||||
allowInitialValueMutation=true
|
||||
isDisabled=readOnly}}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
@ -36,9 +35,8 @@
|
||||
{{combo-box name="badge_grouping_id"
|
||||
value=buffered.badge_grouping_id
|
||||
content=badgeGroupings
|
||||
optionValuePath="content.id"
|
||||
optionLabelPath="content.displayName"}}
|
||||
<button {{action "editGroupings"}} class='btn'>{{fa-icon 'pencil'}}</button>
|
||||
nameProperty="name"}}
|
||||
<button {{action "editGroupings"}} class='btn'>{{d-icon 'pencil'}}</button>
|
||||
</div>
|
||||
|
||||
|
||||
@ -63,7 +61,7 @@
|
||||
{{#if siteSettings.enable_badge_sql}}
|
||||
<div>
|
||||
<label for="query">{{i18n 'admin.badges.query'}}</label>
|
||||
{{textarea name="query" value=buffered.query disabled=readOnly}}
|
||||
{{ace-editor content=buffered.query mode="sql" disabled=readOnly}}
|
||||
</div>
|
||||
|
||||
{{#if hasQuery}}
|
||||
|
||||
@ -15,7 +15,7 @@
|
||||
{{/each}}
|
||||
</ul>
|
||||
{{#link-to 'adminBadges.show' 'new' class="btn"}}
|
||||
{{fa-icon "plus"}} {{i18n 'admin.badges.new'}}
|
||||
{{d-icon "plus"}} {{i18n 'admin.badges.new'}}
|
||||
{{/link-to}}
|
||||
<br>
|
||||
<br>
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
<td class="title">
|
||||
{{#if report.icon}}
|
||||
{{fa-icon report.icon}}
|
||||
{{d-icon report.icon}}
|
||||
{{/if}}
|
||||
<a href="{{report.reportUrl}}">{{report.title}}</a>
|
||||
</td>
|
||||
@ -8,15 +8,15 @@
|
||||
<td class="value">{{number report.todayCount}}</td>
|
||||
|
||||
<td class="value {{report.yesterdayTrend}}" title={{report.yesterdayCountTitle}}>
|
||||
{{number report.yesterdayCount}} {{fa-icon "caret-up" class="up"}} {{fa-icon "caret-down" class="down"}}
|
||||
{{number report.yesterdayCount}} {{d-icon "caret-up" class="up"}} {{d-icon "caret-down" class="down"}}
|
||||
</td>
|
||||
|
||||
<td class="value {{report.sevenDayTrend}}" title={{report.sevenDayCountTitle}}>
|
||||
{{number report.lastSevenDaysCount}} {{fa-icon "caret-up" class="up"}} {{fa-icon "caret-down" class="down"}}
|
||||
{{number report.lastSevenDaysCount}} {{d-icon "caret-up" class="up"}} {{d-icon "caret-down" class="down"}}
|
||||
</td>
|
||||
|
||||
<td class="value {{report.thirtyDayTrend}}" title={{report.thirtyDayCountTitle}}>
|
||||
{{number report.lastThirtyDaysCount}} {{fa-icon "caret-up" class="up"}} {{fa-icon "caret-down" class="down"}}
|
||||
{{number report.lastThirtyDaysCount}} {{d-icon "caret-up" class="up"}} {{d-icon "caret-down" class="down"}}
|
||||
</td>
|
||||
|
||||
<td class="value">{{number report.total}}</td>
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{{#if editing}}
|
||||
{{#admin-form-row label="admin.user_fields.type"}}
|
||||
{{combo-box content=fieldTypes valueAttribute="id" value=buffered.field_type}}
|
||||
{{combo-box content=fieldTypes value=buffered.field_type}}
|
||||
{{/admin-form-row}}
|
||||
|
||||
{{#admin-form-row label="admin.user_fields.name"}}
|
||||
|
||||
@ -9,7 +9,7 @@
|
||||
{{input value=buffered.path_whitelist placeholder="/blog/.*" enter="save" class="path-whitelist"}}
|
||||
</td>
|
||||
<td>
|
||||
{{category-chooser value=categoryId}}
|
||||
{{category-chooser value=categoryId class="small"}}
|
||||
</td>
|
||||
<td>
|
||||
{{d-button icon="check" action="save" class="btn-primary" disabled=cantSave}}
|
||||
|
||||
@ -0,0 +1,18 @@
|
||||
<div class='flag-user'>
|
||||
{{#link-to 'adminUser' user.id user.username class='flag-user-avatar'}}
|
||||
{{avatar user imageSize="small"}}
|
||||
{{/link-to}}
|
||||
<div class='flag-user-details'>
|
||||
<div class='flag-user-who'>
|
||||
{{#link-to 'adminUser' user.id user.username class="flag-user-username"}}
|
||||
{{user.username}}
|
||||
{{/link-to}}
|
||||
<div class='flag-user-date'>
|
||||
{{format-age date}}
|
||||
</div>
|
||||
</div>
|
||||
<div class='flag-user-extra'>
|
||||
{{yield}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -0,0 +1,7 @@
|
||||
{{#link-to 'adminUser' response.user.id response.user.username class="response-avatar"}}
|
||||
{{avatar response.user imageSize="small"}}
|
||||
{{/link-to}}
|
||||
<div class='excerpt'>{{{response.excerpt}}}</div>
|
||||
{{#if hasMore}}
|
||||
<a href={{permalink}} class="has-more">{{i18n 'admin.flags.more'}}</a>
|
||||
{{/if}}
|
||||
@ -0,0 +1,163 @@
|
||||
<div class='flagged-post-details'>
|
||||
<div class="flagged-post-avatar">
|
||||
{{#if flaggedPost.postAuthorFlagged}}
|
||||
{{#if flaggedPost.user}}
|
||||
{{#link-to 'adminUser' flaggedPost.user.id flaggedPost.user.username}}
|
||||
{{avatar flaggedPost.user imageSize="large"}}
|
||||
{{/link-to}}
|
||||
{{#if flaggedPost.wasEdited}}
|
||||
<div class='edited-after'>
|
||||
{{d-icon "pencil" title="admin.flags.was_edited"}}
|
||||
</div>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
{{#if canAct}}
|
||||
{{#if flaggedPost.previous_flags_count}}
|
||||
<span title="{{i18n 'admin.flags.previous_flags_count' count=flaggedPost.previous_flags_count}}" class="badge-notification previous-flagged-posts">{{flaggedPost.previous_flags_count}}</span>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
<div class="flagged-post-contents">
|
||||
<div class='flagged-post-user-details'>
|
||||
<a class='username' href={{user.path}} data-user-card={{flaggedPost.user.username}}>{{format-username flaggedPost.user.username}}</a>
|
||||
</div>
|
||||
|
||||
<div class='flagged-post-excerpt'>
|
||||
{{#unless hideTitle}}
|
||||
<h3>
|
||||
{{#if flaggedPost.topic.isPrivateMessage}}
|
||||
<span class="private-message-glyph">{{d-icon "envelope"}}</span>
|
||||
{{/if}}
|
||||
{{topic-status topic=flaggedPost.topic}}
|
||||
<a href='{{unbound flaggedPost.url}}'>{{{unbound flaggedPost.topic.fancyTitle}}}</a>
|
||||
</h3>
|
||||
{{/unless}}
|
||||
{{#if flaggedPost.postAuthorFlagged}}
|
||||
{{#if expanded}}
|
||||
{{{flaggedPost.cooked}}}
|
||||
{{else}}
|
||||
<p>
|
||||
{{{flaggedPost.excerpt}}}
|
||||
<a href {{action "expand"}}>{{i18n "admin.flags.show_full"}}</a>
|
||||
</p>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
{{#if flaggedPost.topicFlagged}}
|
||||
<div class='flagged-post-message'>
|
||||
<span class='text'>{{{i18n 'admin.flags.topic_flagged'}}}</span>
|
||||
<a href={{flaggedPost.url}} class="btn">{{i18n 'admin.flags.visit_topic'}}</a>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
{{#each flaggedPost.conversations as |c|}}
|
||||
<div class='flag-conversation'>
|
||||
{{#if c.response}}
|
||||
{{flagged-post-response response=c.response}}
|
||||
{{#if c.reply}}
|
||||
{{flagged-post-response response=c.reply hasMore=c.hasMore permalink=c.permalink}}
|
||||
{{/if}}
|
||||
<a href={{c.permalink}} class="btn reply-conversation btn-small">
|
||||
{{d-icon "reply"}}
|
||||
{{i18n "admin.flags.reply_message"}}
|
||||
</a>
|
||||
{{/if}}
|
||||
</div>
|
||||
{{/each}}
|
||||
|
||||
<div class='flag-user-lists'>
|
||||
<div class='flagged-by'>
|
||||
<div class='user-list-title'>
|
||||
{{i18n "admin.flags.flagged_by"}}
|
||||
</div>
|
||||
<div class='flag-users'>
|
||||
{{#each flaggedPost.post_actions as |postAction|}}
|
||||
{{#flag-user user=postAction.user date=postAction.created_at}}
|
||||
<div class='flagger-flag-type'>
|
||||
{{post-action-title postAction.post_action_type_id postAction.name_key}}
|
||||
</div>
|
||||
{{/flag-user}}
|
||||
{{/each}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{#if showResolvedBy}}
|
||||
<div class='flagged-post-resolved-by'>
|
||||
<div class='user-list-title'>
|
||||
{{i18n "admin.flags.resolved_by"}}
|
||||
</div>
|
||||
<div class='flag-users'>
|
||||
{{#each flaggedPost.post_actions as |postAction|}}
|
||||
{{#flag-user user=postAction.disposed_by date=postAction.disposed_at}}
|
||||
{{disposition-icon postAction.disposition}}
|
||||
{{#if postAction.staff_took_action}}
|
||||
{{d-icon "gavel" title="admin.flags.took_action"}}
|
||||
{{/if}}
|
||||
{{/flag-user}}
|
||||
{{/each}}
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
{{#if suspended}}
|
||||
<div class='suspended-message'>
|
||||
The user was suspended for this post.
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
<div class='flagged-post-controls'>
|
||||
{{#if canAct}}
|
||||
{{admin-agree-flag-dropdown
|
||||
post=flaggedPost
|
||||
removeAfter=(action "removeAfter") }}
|
||||
|
||||
{{#if flaggedPost.postHidden}}
|
||||
{{d-button
|
||||
title="admin.flags.disagree_flag_unhide_post_title"
|
||||
class="disagree-flag"
|
||||
action="disagree"
|
||||
icon="thumbs-o-down"
|
||||
label="admin.flags.disagree_flag_unhide_post"}}
|
||||
{{else}}
|
||||
{{d-button
|
||||
title="admin.flags.disagree_flag_title"
|
||||
class="disagree-flag"
|
||||
action="disagree"
|
||||
icon="thumbs-o-down"
|
||||
label="admin.flags.disagree_flag"}}
|
||||
{{/if}}
|
||||
|
||||
{{d-button
|
||||
class="defer-flag"
|
||||
title="admin.flags.defer_flag_title"
|
||||
action="defer"
|
||||
icon="external-link"
|
||||
label="admin.flags.defer_flag"}}
|
||||
|
||||
{{admin-delete-flag-dropdown post=flaggedPost removeAfter=(action "removeAfter")}}
|
||||
|
||||
{{#unless suspended}}
|
||||
{{d-button
|
||||
class="btn-danger suspend-user"
|
||||
icon="ban"
|
||||
label="admin.flags.suspend_user"
|
||||
title="admin.flags.suspend_user_title"
|
||||
action=(action "showSuspendModal")}}
|
||||
{{/unless}}
|
||||
{{/if}}
|
||||
|
||||
{{d-button
|
||||
icon="list"
|
||||
label="admin.flags.moderation_history"
|
||||
action=(action "showModerationHistory")}}
|
||||
</div>
|
||||
{{plugin-outlet
|
||||
name="flagged-post-below-controls"
|
||||
tagName=""
|
||||
args=(hash flaggedPost=flaggedPost canAct=canAct)}}
|
||||
</div>
|
||||
</div>
|
||||
@ -0,0 +1,16 @@
|
||||
{{#if flaggedPosts}}
|
||||
{{#load-more selector=".flagged-post" action=(action "loadMore")}}
|
||||
<div class='flagged-posts'>
|
||||
{{#each flaggedPosts as |flaggedPost|}}
|
||||
{{flagged-post
|
||||
flaggedPost=flaggedPost
|
||||
filter=filter
|
||||
showResolvedBy=showResolvedBy
|
||||
removePost=(action "removePost" flaggedPost)
|
||||
hideTitle=topic}}
|
||||
{{/each}}
|
||||
</div>
|
||||
{{/load-more}}
|
||||
{{else}}
|
||||
<p>{{i18n 'admin.flags.no_results'}}</p>
|
||||
{{/if}}
|
||||
@ -0,0 +1,5 @@
|
||||
{{#each users as |u|}}
|
||||
{{#link-to 'adminUser' u.id u.username class="flagged-topic-user"}}
|
||||
{{avatar u imageSize="small"}}
|
||||
{{/link-to}}
|
||||
{{/each}}
|
||||
@ -0,0 +1,17 @@
|
||||
<td class='date'>
|
||||
{{format-date item.created_at}}
|
||||
</td>
|
||||
<td class='history-item-action'>
|
||||
<div class='action-name'>
|
||||
{{i18n (concat "admin.moderation_history.actions." item.action_name)}}
|
||||
</div>
|
||||
<div class='action-details'>{{item.details}}</div>
|
||||
</td>
|
||||
<td class='history-item-actor'>
|
||||
{{#if item.acting_user}}
|
||||
{{#user-link user=item.acting_user}}
|
||||
{{avatar item.acting_user imageSize="small"}}
|
||||
<span>{{format-username item.acting_user.username}}</span>
|
||||
{{/user-link}}
|
||||
{{/if}}
|
||||
</td>
|
||||
@ -1,4 +1,4 @@
|
||||
<div class="validation-error {{unless message 'hidden'}}">
|
||||
{{fa-icon "times"}}
|
||||
{{d-icon "times"}}
|
||||
{{message}}
|
||||
</div>
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
<h3>{{unbound settingName}}</h3>
|
||||
</div>
|
||||
<div class="setting-value">
|
||||
{{component componentName setting=setting value=buffered.value validationMessage=validationMessage}}
|
||||
{{component componentName setting=setting value=buffered.value validationMessage=validationMessage preview=preview}}
|
||||
</div>
|
||||
{{#if dirty}}
|
||||
<div class='setting-controls'>
|
||||
|
||||
@ -1,3 +1,3 @@
|
||||
{{category-selector categories=selectedCategories blacklist=selectedCategories}}
|
||||
{{category-selector categories=selectedCategories}}
|
||||
<div class='desc'>{{{unbound setting.description}}}</div>
|
||||
{{setting-validation-message message=validationMessage}}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
{{combo-box valueAttribute="value" content=setting.validValues value=value none=setting.allowsNone}}
|
||||
{{combo-box castInteger=true valueAttribute="value" content=setting.validValues value=value none=setting.allowsNone}}
|
||||
{{preview}}
|
||||
{{setting-validation-message message=validationMessage}}
|
||||
<div class='desc'>{{{unbound setting.description}}}</div>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user