Merge master
This commit is contained in:
commit
52c10848bc
@ -1,21 +0,0 @@
|
||||
languages:
|
||||
Ruby: true
|
||||
JavaScript: true
|
||||
Python: false
|
||||
PHP: false
|
||||
|
||||
exclude_paths:
|
||||
- "app/assets/javascripts/defer/*"
|
||||
- "app/assets/javascripts/ember-addons/*"
|
||||
- "lib/autospec/*"
|
||||
- "lib/es6_module_transpiler/*"
|
||||
- "lib/highlight_js/*"
|
||||
- "lib/import/*"
|
||||
- "lib/javascripts/*"
|
||||
- "lib/tasks/*"
|
||||
- "lib/*.js"
|
||||
- "public/*"
|
||||
- "script/*"
|
||||
- "spec/*"
|
||||
- "test/*"
|
||||
- "vendor/*"
|
||||
@ -1 +0,0 @@
|
||||
RAILS_ENV=development
|
||||
97
.eslintrc
97
.eslintrc
@ -1,96 +1,7 @@
|
||||
{
|
||||
"env": {
|
||||
"browser": true,
|
||||
"builtin": true,
|
||||
"es6": true,
|
||||
"jasmine": true,
|
||||
"mocha": true,
|
||||
"node": true
|
||||
},
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 7,
|
||||
"sourceType": "module"
|
||||
},
|
||||
"globals": {
|
||||
"$": true,
|
||||
"_": true,
|
||||
"andThen": true,
|
||||
"asyncRender": true,
|
||||
"Blob": true,
|
||||
"bootbox": true,
|
||||
"click": true,
|
||||
"waitUntil": true,
|
||||
"getSettledState": true,
|
||||
"count": true,
|
||||
"currentPath": true,
|
||||
"currentRouteName": true,
|
||||
"currentURL": true,
|
||||
"define": true,
|
||||
"Discourse": true,
|
||||
"Ember": true,
|
||||
"exists": true,
|
||||
"File": true,
|
||||
"fillIn": true,
|
||||
"find": true,
|
||||
"Handlebars": true,
|
||||
"hasModule": true,
|
||||
"I18n": true,
|
||||
"invisible": true,
|
||||
"jQuery": true,
|
||||
"keyboardHelper": true,
|
||||
"keyEvent": true,
|
||||
"moduleFor": true,
|
||||
"moduleForComponent": true,
|
||||
"moment": true,
|
||||
"Pretender": true,
|
||||
"QUnit": true,
|
||||
"require": true,
|
||||
"requirejs": true,
|
||||
"RSVP": true,
|
||||
"sandbox": true,
|
||||
"sinon": true,
|
||||
"test": true,
|
||||
"triggerEvent": true,
|
||||
"visible": true,
|
||||
"visit": true,
|
||||
"pauseTest": true
|
||||
},
|
||||
"extends": "eslint-config-discourse",
|
||||
"plugins": ["discourse-ember"],
|
||||
"rules": {
|
||||
"block-scoped-var": 2,
|
||||
"dot-notation": 0,
|
||||
"eqeqeq": [2, "allow-null"],
|
||||
"guard-for-in": 2,
|
||||
"no-alert": 2,
|
||||
"no-bitwise": 2,
|
||||
"no-caller": 2,
|
||||
"no-cond-assign": 0,
|
||||
"no-console": 2,
|
||||
"no-debugger": 2,
|
||||
"no-empty": 0,
|
||||
"no-eval": 2,
|
||||
"no-extend-native": 2,
|
||||
"no-extra-parens": 0,
|
||||
"no-inner-declarations": 2,
|
||||
"no-irregular-whitespace": 2,
|
||||
"no-iterator": 2,
|
||||
"no-loop-func": 2,
|
||||
"no-mixed-spaces-and-tabs": 2,
|
||||
"no-multi-str": 2,
|
||||
"no-new": 2,
|
||||
"no-plusplus": 0,
|
||||
"no-proto": 2,
|
||||
"no-script-url": 2,
|
||||
"no-sequences": 2,
|
||||
"no-shadow": 2,
|
||||
"no-this-before-super": 2,
|
||||
"no-trailing-spaces": 2,
|
||||
"no-undef": 2,
|
||||
"no-unused-vars": 2,
|
||||
"no-with": 2,
|
||||
"semi": 2,
|
||||
"strict": 0,
|
||||
"valid-typeof": 2,
|
||||
"wrap-iife": [2, "inside"]
|
||||
},
|
||||
"parser": "babel-eslint"
|
||||
"discourse-ember/global-ember": 2
|
||||
}
|
||||
}
|
||||
|
||||
9
.git-blame-ignore-revs
Normal file
9
.git-blame-ignore-revs
Normal file
@ -0,0 +1,9 @@
|
||||
# Only add no-op commits to this file
|
||||
# To prevent these commits to show in git blame
|
||||
# git config blame.ignoreRevsFile .git-blame-ignore-revs
|
||||
|
||||
# DEV: introduces prettier for es6 files
|
||||
03a7d532cf8f09b12573b21ef013c21100d52728
|
||||
|
||||
# DEV: enforces no self-closing-void-elements
|
||||
dafd3c3b47f116c6c1dc56cb18df614c11747733
|
||||
158
.github/workflows/ci.yml
vendored
Normal file
158
.github/workflows/ci.yml
vendored
Normal file
@ -0,0 +1,158 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
branches-ignore:
|
||||
- 'tests-passed'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: "${{ matrix.target }}-${{ matrix.build_types }}"
|
||||
runs-on: ${{ matrix.os }}
|
||||
timeout-minutes: 60
|
||||
|
||||
env:
|
||||
DISCOURSE_HOSTNAME: www.example.com
|
||||
RUBY_GLOBAL_METHOD_CACHE_SIZE: 131072
|
||||
BUILD_TYPE: ${{ matrix.build_types }}
|
||||
TARGET: ${{ matrix.target }}
|
||||
RAILS_ENV: test
|
||||
PGHOST: localhost
|
||||
PGUSER: discourse
|
||||
PGPASSWORD: discourse
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
|
||||
matrix:
|
||||
build_types: [ 'BACKEND', 'FRONTEND', 'LINT' ]
|
||||
target: [ 'PLUGINS', 'CORE' ]
|
||||
os: [ ubuntu-latest ]
|
||||
ruby: [ '2.6' ]
|
||||
postgres: [ '10' ]
|
||||
redis: [ '4.x' ]
|
||||
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:${{ matrix.postgres }}
|
||||
ports:
|
||||
- 5432:5432
|
||||
env:
|
||||
POSTGRES_USER: discourse
|
||||
POSTGRES_PASSWORD: discourse
|
||||
POSTGRES_DB: discourse_test
|
||||
options: >-
|
||||
--mount type=tmpfs,destination=/var/lib/postgresql/data
|
||||
--health-cmd pg_isready
|
||||
--health-interval 10s
|
||||
--health-timeout 5s
|
||||
--health-retries 5
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Setup Git
|
||||
run: git config --global user.email "ci@ci.invalid" && git config --global user.name "Discourse CI"
|
||||
|
||||
- name: Setup packages
|
||||
if: env.BUILD_TYPE != 'LINT'
|
||||
run: |
|
||||
sudo apt-get -yqq install postgresql-client libpq-dev gifsicle jpegoptim optipng jhead && \
|
||||
wget -qO- https://raw.githubusercontent.com/discourse/discourse_docker/master/image/base/install-pngquant | sudo sh
|
||||
|
||||
- name: Setup redis
|
||||
uses: shogo82148/actions-setup-redis@v1
|
||||
if: env.BUILD_TYPE != 'LINT'
|
||||
with:
|
||||
redis-version: ${{ matrix.redis }}
|
||||
|
||||
- name: Setup ruby
|
||||
uses: actions/setup-ruby@v1
|
||||
with:
|
||||
ruby-version: ${{ matrix.ruby }}
|
||||
architecture: 'x64'
|
||||
|
||||
- name: Setup bundler
|
||||
run: gem install bundler -v 2.1.1 --no-doc
|
||||
|
||||
- name: Bundler cache
|
||||
uses: actions/cache@v1
|
||||
id: bundler-cache
|
||||
with:
|
||||
path: vendor/bundle
|
||||
key: ${{ runner.os }}-gem-${{ hashFiles('**/Gemfile.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-gem-
|
||||
|
||||
- name: Setup gems
|
||||
run: bundle install --without development --deployment --jobs 4 --retry 3
|
||||
|
||||
- name: Get yarn cache directory
|
||||
id: yarn-cache-dir
|
||||
run: echo "::set-output name=dir::$(yarn cache dir)"
|
||||
|
||||
- name: Yarn cache
|
||||
uses: actions/cache@v1
|
||||
id: yarn-cache
|
||||
with:
|
||||
path: ${{ steps.yarn-cache-dir.outputs.dir }}
|
||||
key: ${{ runner.os }}-${{ matrix.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-${{ matrix.os }}-yarn-
|
||||
|
||||
- name: Yarn install
|
||||
run: yarn install --dev
|
||||
|
||||
- name: "Checkout official plugins"
|
||||
if: env.TARGET == 'PLUGINS'
|
||||
run: bin/rake plugin:install_all_official
|
||||
|
||||
- name: Create database
|
||||
if: env.BUILD_TYPE != 'LINT'
|
||||
run: bin/rake db:create && bin/rake db:migrate
|
||||
|
||||
- name: Create parallel databases
|
||||
if: env.BUILD_TYPE == 'BACKEND' && env.TARGET == 'CORE'
|
||||
run: bin/rake parallel:create && bin/rake parallel:migrate
|
||||
|
||||
- name: Rubocop
|
||||
if: env.BUILD_TYPE == 'LINT'
|
||||
run: bundle exec rubocop .
|
||||
|
||||
- name: ESLint
|
||||
if: env.BUILD_TYPE == 'LINT'
|
||||
run: yarn eslint app/assets/javascripts test/javascripts && yarn eslint --ext .es6 app/assets/javascripts test/javascripts plugins
|
||||
|
||||
- name: Prettier
|
||||
if: env.BUILD_TYPE == 'LINT'
|
||||
run: |
|
||||
yarn prettier -v
|
||||
yarn prettier --list-different "app/assets/stylesheets/**/*.scss" "app/assets/javascripts/**/*.es6" "test/javascripts/**/*.es6" "plugins/**/*.scss" "plugins/**/*.es6"
|
||||
|
||||
- name: Core RSpec
|
||||
if: env.BUILD_TYPE == 'BACKEND' && env.TARGET == 'CORE'
|
||||
run: bin/turbo_rspec && bin/rake plugin:spec
|
||||
|
||||
- name: Plugin RSpec
|
||||
if: env.BUILD_TYPE == 'BACKEND' && env.TARGET == 'PLUGINS'
|
||||
run: bin/rake plugin:spec
|
||||
|
||||
- name: Core QUnit
|
||||
if: env.BUILD_TYPE == 'FRONTEND' && env.TARGET == 'CORE'
|
||||
run: bundle exec rake qunit:test['1200000']
|
||||
timeout-minutes: 30
|
||||
|
||||
- name: Wizard QUnit
|
||||
if: env.BUILD_TYPE == 'FRONTEND' && env.TARGET == 'CORE'
|
||||
run: bundle exec rake qunit:test['1200000','/wizard/qunit']
|
||||
timeout-minutes: 30
|
||||
|
||||
- name: Plugin QUnit # Tests core plugins in TARGET=CORE, and all plugins in TARGET=PLUGINS
|
||||
if: env.BUILD_TYPE == 'FRONTEND'
|
||||
run: bundle exec rake plugin:qunit
|
||||
timeout-minutes: 30
|
||||
8
.gitignore
vendored
8
.gitignore
vendored
@ -32,6 +32,7 @@ config/discourse.conf
|
||||
# Ignore the default SQLite database and db dumps
|
||||
*.sql
|
||||
*.sql.gz
|
||||
!/spec/fixtures/**/*.sql
|
||||
/db/*.sqlite3
|
||||
/db/structure.sql
|
||||
/db/schema.rb
|
||||
@ -46,14 +47,14 @@ bootsnap-compile-cache/
|
||||
|
||||
# Ignore plugins except for the bundled ones.
|
||||
/plugins/*
|
||||
!/plugins/lazyYT/
|
||||
!/plugins/lazy-yt/
|
||||
!/plugins/poll/
|
||||
!/plugins/discourse-details/
|
||||
!/plugins/discourse-nginx-performance-report
|
||||
!/plugins/discourse-narrative-bot
|
||||
!/plugins/discourse-presence
|
||||
!/plugins/discourse-local-dates
|
||||
!/public/plugins/discourse-internet-explorer
|
||||
!/plugins/discourse-internet-explorer
|
||||
/plugins/*/auto_generated/
|
||||
|
||||
/spec/fixtures/plugins/my_plugin/auto_generated
|
||||
@ -128,3 +129,6 @@ vendor/bundle/*
|
||||
|
||||
# Vagrant
|
||||
.vagrant
|
||||
|
||||
# ignore auto-generated plugin js assets
|
||||
/app/assets/javascripts/plugins/*
|
||||
|
||||
@ -1,45 +0,0 @@
|
||||
# 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
|
||||
required_executable: './node_modules/.bin/eslint'
|
||||
install_command: 'yarn install'
|
||||
command: ['yarn', 'eslint', '--ext', '.es6', '-f', 'compact']
|
||||
include: '**/*.es6'
|
||||
YamlSyntax:
|
||||
enabled: true
|
||||
|
||||
PostCheckout:
|
||||
BundleInstall:
|
||||
enabled: true
|
||||
YarnInstall:
|
||||
enabled: true
|
||||
|
||||
PostMerge:
|
||||
BundleInstall:
|
||||
enabled: true
|
||||
YarnInstall:
|
||||
enabled: true
|
||||
|
||||
PostRewrite:
|
||||
BundleInstall:
|
||||
enabled: true
|
||||
YarnInstall:
|
||||
enabled: true
|
||||
38
.rubocop.yml
38
.rubocop.yml
@ -1,13 +1,16 @@
|
||||
require:
|
||||
- rubocop-discourse
|
||||
|
||||
AllCops:
|
||||
TargetRubyVersion: 2.4
|
||||
DisabledByDefault: true
|
||||
Exclude:
|
||||
- 'db/schema.rb'
|
||||
- 'bundle/**/*'
|
||||
- 'vendor/**/*'
|
||||
- 'node_modules/**/*'
|
||||
- 'public/**/*'
|
||||
- 'plugins/**/*'
|
||||
- "db/schema.rb"
|
||||
- "bundle/**/*"
|
||||
- "vendor/**/*"
|
||||
- "node_modules/**/*"
|
||||
- "public/**/*"
|
||||
- "plugins/**/gems/**/*"
|
||||
|
||||
# Prefer &&/|| over and/or.
|
||||
Style/AndOr:
|
||||
@ -16,11 +19,6 @@ Style/AndOr:
|
||||
Style/FrozenStringLiteralComment:
|
||||
Enabled: true
|
||||
|
||||
# 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
|
||||
@ -57,7 +55,7 @@ Layout/SpaceAroundOperators:
|
||||
Enabled: true
|
||||
|
||||
Layout/SpaceBeforeFirstArg:
|
||||
Enabled: true
|
||||
Enabled: true
|
||||
|
||||
# Defining a method with parameters needs parentheses.
|
||||
Style/MethodDefParentheses:
|
||||
@ -83,7 +81,7 @@ Layout/Tab:
|
||||
Enabled: true
|
||||
|
||||
# Blank lines should not have any spaces.
|
||||
Layout/TrailingBlankLines:
|
||||
Layout/TrailingEmptyLines:
|
||||
Enabled: true
|
||||
|
||||
# No trailing whitespace.
|
||||
@ -113,7 +111,7 @@ Layout/MultilineMethodCallIndentation:
|
||||
Enabled: true
|
||||
EnforcedStyle: indented
|
||||
|
||||
Layout/AlignHash:
|
||||
Layout/HashAlignment:
|
||||
Enabled: true
|
||||
|
||||
Bundler/OrderedGems:
|
||||
@ -126,6 +124,18 @@ Style/Semicolon:
|
||||
Enabled: true
|
||||
AllowAsExpressionSeparator: true
|
||||
|
||||
Style/RedundantReturn:
|
||||
Enabled: true
|
||||
|
||||
DiscourseCops/NoChdir:
|
||||
Enabled: true
|
||||
Exclude:
|
||||
- 'spec/**/*' # Specs are run sequentially, so chdir can be used
|
||||
- 'plugins/*/spec/**/*'
|
||||
|
||||
DiscourseCops/NoURIEscapeEncode:
|
||||
Enabled: true
|
||||
|
||||
Style/GlobalVars:
|
||||
Enabled: true
|
||||
Severity: warning
|
||||
|
||||
@ -1 +1 @@
|
||||
2.6.1
|
||||
2.6.5
|
||||
|
||||
11
.template-lintrc.js
Normal file
11
.template-lintrc.js
Normal file
@ -0,0 +1,11 @@
|
||||
module.exports = {
|
||||
// extends: "recommended",
|
||||
ignore: ["**/*.raw"],
|
||||
|
||||
rules: {
|
||||
"self-closing-void-elements": true,
|
||||
"table-groups": true,
|
||||
"style-concatenation": true,
|
||||
"no-invalid-interactive": true
|
||||
}
|
||||
};
|
||||
15
.travis.yml
15
.travis.yml
@ -21,7 +21,7 @@ env:
|
||||
|
||||
addons:
|
||||
chrome: stable
|
||||
postgresql: 9.6
|
||||
postgresql: "9.6"
|
||||
apt:
|
||||
update: true
|
||||
packages:
|
||||
@ -40,10 +40,9 @@ services:
|
||||
- redis-server
|
||||
|
||||
sudo: required
|
||||
dist: trusty
|
||||
dist: xenial
|
||||
|
||||
cache:
|
||||
apt: true
|
||||
yarn: true
|
||||
directories:
|
||||
- vendor/bundle
|
||||
@ -60,7 +59,7 @@ before_install:
|
||||
- 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
|
||||
- git clone --depth=1 https://github.com/discourse/discourse-staff-notes.git plugins/discourse-staff-notes
|
||||
- git clone --depth=1 https://github.com/discourse/discourse-user-notes.git plugins/discourse-user-notes
|
||||
- git clone --depth=1 https://github.com/discourse/discourse-group-tracker
|
||||
- export PATH=$HOME/.yarn/bin:$PATH
|
||||
|
||||
@ -74,13 +73,7 @@ script:
|
||||
- |
|
||||
bash -c "
|
||||
if [ '$RUN_LINT' == '1' ]; then
|
||||
bundle exec rubocop --parallel && \
|
||||
yarn prettier --list-different "app/assets/stylesheets/**/*.scss" "app/assets/javascripts/**/*.es6" "test/javascripts/**/*.es6"
|
||||
yarn eslint --ext .es6 app/assets/javascripts && \
|
||||
yarn eslint --ext .es6 test/javascripts && \
|
||||
yarn eslint --ext .es6 plugins/**/assets/javascripts && \
|
||||
yarn eslint --ext .es6 plugins/**/test/javascripts && \
|
||||
yarn eslint app/assets/javascripts test/javascripts
|
||||
npx lefthook run lints
|
||||
else
|
||||
if [ '$QUNIT_RUN' == '1' ]; then
|
||||
bundle exec rake qunit:test['1200000'] && \
|
||||
|
||||
125
Gemfile
125
Gemfile
@ -14,45 +14,60 @@ if rails_master?
|
||||
gem 'arel', git: 'https://github.com/rails/arel.git'
|
||||
gem 'rails', git: 'https://github.com/rails/rails.git'
|
||||
else
|
||||
# until rubygems gives us optional dependencies we are stuck with this
|
||||
# bundle update actionmailer actionpack actionview activemodel activerecord activesupport railties
|
||||
gem 'actionmailer', '5.2.3'
|
||||
gem 'actionpack', '5.2.3'
|
||||
gem 'actionview', '5.2.3'
|
||||
gem 'activemodel', '5.2.3'
|
||||
gem 'activerecord', '5.2.3'
|
||||
gem 'activesupport', '5.2.3'
|
||||
gem 'railties', '5.2.3'
|
||||
# NOTE: Until rubygems gives us optional dependencies we are stuck with this needing to be explicit
|
||||
# this allows us to include the bits of rails we use without pieces we do not.
|
||||
#
|
||||
# To issue a rails update bump the version number here
|
||||
gem 'actionmailer', '6.0.1'
|
||||
gem 'actionpack', '6.0.1'
|
||||
gem 'actionview', '6.0.1'
|
||||
gem 'activemodel', '6.0.1'
|
||||
gem 'activerecord', '6.0.1'
|
||||
gem 'activesupport', '6.0.1'
|
||||
gem 'railties', '6.0.1'
|
||||
gem 'sprockets-rails'
|
||||
end
|
||||
|
||||
# TODO: At the moment Discourse does not work with Sprockets 4, we would need to correct internals
|
||||
# This is a desired upgrade we should get to.
|
||||
gem 'sprockets', '3.7.2'
|
||||
|
||||
# this will eventually be added to rails,
|
||||
# allows us to precompile all our templates in the unicorn master
|
||||
gem 'actionview_precompiler', require: false
|
||||
|
||||
gem 'seed-fu'
|
||||
|
||||
gem 'mail', require: false
|
||||
gem 'mini_mime'
|
||||
gem 'mini_suffix'
|
||||
|
||||
gem 'hiredis'
|
||||
gem 'redis'
|
||||
|
||||
# holding off redis upgrade temporarily as it is having issues with our current
|
||||
# freedom patch, we will follow this up.
|
||||
#
|
||||
# FrozenError: can't modify frozen Hash
|
||||
# /var/www/discourse/vendor/bundle/ruby/2.5.0/gems/redis-4.1.0/lib/redis/client.rb:93:in `delete'
|
||||
# /var/www/discourse/vendor/bundle/ruby/2.5.0/gems/redis-4.1.0/lib/redis/client.rb:93:in `initialize'
|
||||
# /var/www/discourse/lib/freedom_patches/redis.rb:7:in `initialize'
|
||||
gem 'redis', '4.0.1', require: ["redis", "redis/connection/hiredis"]
|
||||
# This is explicitly used by Sidekiq and is an optional dependency.
|
||||
# We tell Sidekiq to use the namespace "sidekiq" which triggers this
|
||||
# gem to be used. There is no explicit dependency in sidekiq cause
|
||||
# redis namespace support is optional
|
||||
# We already namespace stuff in DiscourseRedis, so we should consider
|
||||
# just using a single implementation in core vs having 2 namespace implementations
|
||||
gem 'redis-namespace'
|
||||
|
||||
# NOTE: AM serializer gets a lot slower with recent updates
|
||||
# we used an old branch which is the fastest one out there
|
||||
# are long term goal here is to fork this gem so we have a
|
||||
# better maintained living fork
|
||||
gem 'active_model_serializers', '~> 0.8.3'
|
||||
|
||||
gem 'onebox', '1.8.92'
|
||||
gem 'onebox'
|
||||
|
||||
gem 'http_accept_language', '~>2.0.5', require: false
|
||||
gem 'http_accept_language', require: false
|
||||
|
||||
# Ember related gems need to be pinned cause they control client side
|
||||
# behavior, we will push these versions up when upgrading ember
|
||||
gem 'ember-rails', '0.18.5'
|
||||
gem 'discourse-ember-source', '~> 3.8.0'
|
||||
gem 'discourse-ember-source', '~> 3.12.2'
|
||||
gem 'ember-handlebars-template', '0.8.0'
|
||||
|
||||
gem 'barber'
|
||||
|
||||
gem 'message_bus'
|
||||
@ -71,17 +86,17 @@ gem 'aws-sdk-sns', require: false
|
||||
gem 'excon', require: false
|
||||
gem 'unf', require: false
|
||||
|
||||
gem 'email_reply_trimmer', '~> 0.1'
|
||||
gem 'email_reply_trimmer'
|
||||
|
||||
# Forked until https://github.com/toy/image_optim/pull/162 is merged
|
||||
# https://github.com/discourse/image_optim
|
||||
gem 'discourse_image_optim', require: 'image_optim'
|
||||
gem 'multi_json'
|
||||
gem 'mustache'
|
||||
gem 'nokogiri'
|
||||
gem 'css_parser', require: false
|
||||
|
||||
gem 'omniauth'
|
||||
gem 'omniauth-openid'
|
||||
gem 'openid-redis-store'
|
||||
gem 'omniauth-facebook'
|
||||
gem 'omniauth-twitter'
|
||||
gem 'omniauth-instagram'
|
||||
@ -95,7 +110,7 @@ gem 'oj'
|
||||
gem 'pg'
|
||||
gem 'mini_sql'
|
||||
gem 'pry-rails', require: false
|
||||
gem 'r2', '~> 0.2.5', require: false
|
||||
gem 'r2', require: false
|
||||
gem 'rake'
|
||||
|
||||
gem 'thor', require: false
|
||||
@ -110,8 +125,20 @@ gem 'tilt', require: false
|
||||
|
||||
gem 'execjs', require: false
|
||||
gem 'mini_racer'
|
||||
|
||||
# TODO: determine why highline is being held back and upgrade to latest
|
||||
gem 'highline', '~> 1.7.0', require: false
|
||||
|
||||
# TODO: Upgrading breaks Sidekiq Web
|
||||
# This is a bit of a hornets nest cause in an ideal world we much prefer
|
||||
# if Sidekiq reused session and CSRF mitigation with Discourse on the
|
||||
# _forum_session cookie instead of a rack.session cookie
|
||||
gem 'rack', '2.0.8'
|
||||
|
||||
gem 'rack-protection' # security
|
||||
gem 'cbor', require: false
|
||||
gem 'cose', require: false
|
||||
gem 'addressable'
|
||||
|
||||
# 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
|
||||
@ -122,7 +149,7 @@ end
|
||||
|
||||
group :test do
|
||||
gem 'webmock', require: false
|
||||
gem 'fakeweb', '~> 1.3.0', require: false
|
||||
gem 'fakeweb', require: false
|
||||
gem 'minitest', require: false
|
||||
gem 'simplecov', require: false
|
||||
gem "test-prof"
|
||||
@ -133,32 +160,37 @@ group :test, :development do
|
||||
gem 'mock_redis'
|
||||
gem 'listen', require: false
|
||||
gem 'certified', require: false
|
||||
# later appears to break Fabricate(:topic, category: category)
|
||||
gem 'fabrication', require: false
|
||||
gem 'mocha', require: false
|
||||
|
||||
# TODO: upgrading to 1.10.1 cause it breaks our test suite.
|
||||
# We want our test suite fixed though to support this upgrade.
|
||||
gem 'mocha', '1.8.0', require: false
|
||||
|
||||
gem 'rb-fsevent', require: RUBY_PLATFORM =~ /darwin/i ? 'rb-fsevent' : false
|
||||
|
||||
# TODO determine if we can update this to 0.10, API changes happened
|
||||
# we would like to upgrade it if possible
|
||||
gem 'rb-inotify', '~> 0.9', require: RUBY_PLATFORM =~ /linux/i ? 'rb-inotify' : false
|
||||
gem 'rspec-rails', require: false
|
||||
gem 'shoulda-matchers', '~> 3.1', '>= 3.1.3', require: false
|
||||
|
||||
# TODO once 4.0.0 is released upgrade to it, at time of writing 3.9.0 is latest
|
||||
gem 'rspec-rails', '4.0.0.beta2', require: false
|
||||
|
||||
gem 'shoulda-matchers', require: false
|
||||
gem 'rspec-html-matchers'
|
||||
gem 'pry-nav'
|
||||
gem 'byebug', require: ENV['RM_INFO'].nil?
|
||||
gem 'byebug', require: ENV['RM_INFO'].nil?, platform: :mri
|
||||
gem 'rubocop', require: false
|
||||
gem "rubocop-discourse", require: false
|
||||
gem 'parallel_tests'
|
||||
end
|
||||
|
||||
group :development do
|
||||
gem 'ruby-prof', require: false
|
||||
gem 'ruby-prof', require: false, platform: :mri
|
||||
gem 'bullet', require: !!ENV['BULLET']
|
||||
gem 'better_errors'
|
||||
gem 'better_errors', platform: :mri
|
||||
gem 'binding_of_caller'
|
||||
|
||||
# waiting on 2.7.5 per: https://github.com/ctran/annotate_models/pull/595
|
||||
if rails_master?
|
||||
gem 'annotate', git: 'https://github.com/ctran/annotate_models.git'
|
||||
else
|
||||
gem 'annotate'
|
||||
end
|
||||
gem 'yaml-lint'
|
||||
gem 'annotate'
|
||||
end
|
||||
|
||||
# this is an optional gem, it provides a high performance replacement
|
||||
@ -196,24 +228,31 @@ gem 'logstash-event', require: false
|
||||
gem 'logstash-logger', require: false
|
||||
gem 'logster'
|
||||
|
||||
gem 'sassc', require: false
|
||||
# NOTE: later versions of sassc are causing a segfault, possibly dependent on processer architecture
|
||||
# and until resolved should be locked at 2.0.1
|
||||
gem 'sassc', '2.0.1', require: false
|
||||
gem "sassc-rails"
|
||||
|
||||
gem 'rotp'
|
||||
gem 'rotp', require: false
|
||||
gem 'rqrcode'
|
||||
|
||||
gem 'rubyzip', require: false
|
||||
|
||||
gem 'sshkey', require: false
|
||||
|
||||
gem 'rchardet', require: false
|
||||
gem 'lz4-ruby', require: false, platform: :mri
|
||||
|
||||
if ENV["IMPORT"] == "1"
|
||||
gem 'mysql2'
|
||||
gem 'redcarpet'
|
||||
|
||||
# NOTE: in import mode the version of sqlite can matter a lot, so we stick it to a specific one
|
||||
gem 'sqlite3', '~> 1.3', '>= 1.3.13'
|
||||
gem 'ruby-bbcode-to-md', git: 'https://github.com/nlalonde/ruby-bbcode-to-md'
|
||||
gem 'reverse_markdown'
|
||||
gem 'tiny_tds'
|
||||
gem 'csv', '~> 3.0'
|
||||
gem 'csv'
|
||||
end
|
||||
|
||||
gem 'webpush', require: false
|
||||
|
||||
401
Gemfile.lock
401
Gemfile.lock
@ -1,64 +1,65 @@
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
actionmailer (5.2.3)
|
||||
actionpack (= 5.2.3)
|
||||
actionview (= 5.2.3)
|
||||
activejob (= 5.2.3)
|
||||
actionmailer (6.0.1)
|
||||
actionpack (= 6.0.1)
|
||||
actionview (= 6.0.1)
|
||||
activejob (= 6.0.1)
|
||||
mail (~> 2.5, >= 2.5.4)
|
||||
rails-dom-testing (~> 2.0)
|
||||
actionpack (5.2.3)
|
||||
actionview (= 5.2.3)
|
||||
activesupport (= 5.2.3)
|
||||
actionpack (6.0.1)
|
||||
actionview (= 6.0.1)
|
||||
activesupport (= 6.0.1)
|
||||
rack (~> 2.0)
|
||||
rack-test (>= 0.6.3)
|
||||
rails-dom-testing (~> 2.0)
|
||||
rails-html-sanitizer (~> 1.0, >= 1.0.2)
|
||||
actionview (5.2.3)
|
||||
activesupport (= 5.2.3)
|
||||
rails-html-sanitizer (~> 1.0, >= 1.2.0)
|
||||
actionview (6.0.1)
|
||||
activesupport (= 6.0.1)
|
||||
builder (~> 3.1)
|
||||
erubi (~> 1.4)
|
||||
rails-dom-testing (~> 2.0)
|
||||
rails-html-sanitizer (~> 1.0, >= 1.0.3)
|
||||
rails-html-sanitizer (~> 1.1, >= 1.2.0)
|
||||
actionview_precompiler (0.2.2)
|
||||
actionview (>= 6.0.a)
|
||||
active_model_serializers (0.8.4)
|
||||
activemodel (>= 3.0)
|
||||
activejob (5.2.3)
|
||||
activesupport (= 5.2.3)
|
||||
activejob (6.0.1)
|
||||
activesupport (= 6.0.1)
|
||||
globalid (>= 0.3.6)
|
||||
activemodel (5.2.3)
|
||||
activesupport (= 5.2.3)
|
||||
activerecord (5.2.3)
|
||||
activemodel (= 5.2.3)
|
||||
activesupport (= 5.2.3)
|
||||
arel (>= 9.0)
|
||||
activesupport (5.2.3)
|
||||
activemodel (6.0.1)
|
||||
activesupport (= 6.0.1)
|
||||
activerecord (6.0.1)
|
||||
activemodel (= 6.0.1)
|
||||
activesupport (= 6.0.1)
|
||||
activesupport (6.0.1)
|
||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||
i18n (>= 0.7, < 2)
|
||||
minitest (~> 5.1)
|
||||
tzinfo (~> 1.1)
|
||||
addressable (2.5.2)
|
||||
public_suffix (>= 2.0.2, < 4.0)
|
||||
annotate (2.7.5)
|
||||
zeitwerk (~> 2.2)
|
||||
addressable (2.7.0)
|
||||
public_suffix (>= 2.0.2, < 5.0)
|
||||
annotate (3.1.0)
|
||||
activerecord (>= 3.2, < 7.0)
|
||||
rake (>= 10.4, < 13.0)
|
||||
arel (9.0.0)
|
||||
rake (>= 10.4, < 14.0)
|
||||
ast (2.4.0)
|
||||
aws-eventstream (1.0.3)
|
||||
aws-partitions (1.154.0)
|
||||
aws-sdk-core (3.48.6)
|
||||
aws-partitions (1.272.0)
|
||||
aws-sdk-core (3.89.1)
|
||||
aws-eventstream (~> 1.0, >= 1.0.2)
|
||||
aws-partitions (~> 1.0)
|
||||
aws-partitions (~> 1, >= 1.239.0)
|
||||
aws-sigv4 (~> 1.1)
|
||||
jmespath (~> 1.0)
|
||||
aws-sdk-kms (1.17.0)
|
||||
aws-sdk-core (~> 3, >= 3.48.2)
|
||||
aws-sdk-kms (1.29.0)
|
||||
aws-sdk-core (~> 3, >= 3.71.0)
|
||||
aws-sigv4 (~> 1.1)
|
||||
aws-sdk-s3 (1.36.1)
|
||||
aws-sdk-core (~> 3, >= 3.48.2)
|
||||
aws-sdk-s3 (1.60.2)
|
||||
aws-sdk-core (~> 3, >= 3.83.0)
|
||||
aws-sdk-kms (~> 1)
|
||||
aws-sigv4 (~> 1.0)
|
||||
aws-sdk-sns (1.13.0)
|
||||
aws-sdk-core (~> 3, >= 3.48.2)
|
||||
aws-sigv4 (~> 1.1)
|
||||
aws-sdk-sns (1.21.0)
|
||||
aws-sdk-core (~> 3, >= 3.71.0)
|
||||
aws-sigv4 (~> 1.1)
|
||||
aws-sigv4 (1.1.0)
|
||||
aws-eventstream (~> 1.0, >= 1.0.2)
|
||||
@ -71,34 +72,40 @@ GEM
|
||||
rack (>= 0.9.0)
|
||||
binding_of_caller (0.8.0)
|
||||
debug_inspector (>= 0.0.1)
|
||||
bootsnap (1.4.4)
|
||||
bootsnap (1.4.6)
|
||||
msgpack (~> 1.0)
|
||||
builder (3.2.3)
|
||||
bullet (6.0.0)
|
||||
builder (3.2.4)
|
||||
bullet (6.1.0)
|
||||
activesupport (>= 3.0.0)
|
||||
uniform_notifier (~> 1.11)
|
||||
byebug (11.0.1)
|
||||
byebug (11.1.1)
|
||||
cbor (0.5.9.6)
|
||||
certified (1.0.0)
|
||||
chunky_png (1.3.11)
|
||||
coderay (1.1.2)
|
||||
colored2 (3.1.2)
|
||||
concurrent-ruby (1.1.5)
|
||||
concurrent-ruby (1.1.6)
|
||||
connection_pool (2.2.2)
|
||||
cose (0.11.0)
|
||||
cbor (~> 0.5.9)
|
||||
openssl-signature_algorithm (~> 0.3.0)
|
||||
cppjieba_rb (0.3.3)
|
||||
crack (0.4.3)
|
||||
safe_yaml (~> 1.0.0)
|
||||
crass (1.0.4)
|
||||
crass (1.0.6)
|
||||
css_parser (1.7.1)
|
||||
addressable
|
||||
debug_inspector (0.0.3)
|
||||
diff-lcs (1.3)
|
||||
diffy (3.3.0)
|
||||
discourse-ember-source (3.8.0.1)
|
||||
discourse-ember-source (3.12.2.0)
|
||||
discourse_image_optim (0.26.2)
|
||||
exifr (~> 1.2, >= 1.2.2)
|
||||
fspath (~> 3.0)
|
||||
image_size (~> 1.5)
|
||||
in_threads (~> 1.3)
|
||||
progress (~> 3.0, >= 3.0.1)
|
||||
docile (1.3.1)
|
||||
docile (1.3.2)
|
||||
email_reply_trimmer (0.1.12)
|
||||
ember-data-source (3.0.2)
|
||||
ember-source (>= 2, < 3.0)
|
||||
@ -113,53 +120,50 @@ GEM
|
||||
jquery-rails (>= 1.0.17)
|
||||
railties (>= 3.1)
|
||||
ember-source (2.18.2)
|
||||
erubi (1.8.0)
|
||||
excon (0.64.0)
|
||||
erubi (1.9.0)
|
||||
excon (0.72.0)
|
||||
execjs (2.7.0)
|
||||
exifr (1.3.6)
|
||||
fabrication (2.20.1)
|
||||
fabrication (2.21.0)
|
||||
fakeweb (1.3.0)
|
||||
faraday (0.15.4)
|
||||
faraday (0.17.3)
|
||||
multipart-post (>= 1.2, < 3)
|
||||
fast_blank (1.0.0)
|
||||
fast_xor (1.1.3)
|
||||
rake
|
||||
rake-compiler
|
||||
fast_xs (0.8.0)
|
||||
fastimage (2.1.5)
|
||||
ffi (1.10.0)
|
||||
fastimage (2.1.7)
|
||||
ffi (1.12.2)
|
||||
flamegraph (0.9.5)
|
||||
fspath (3.1.0)
|
||||
fspath (3.1.2)
|
||||
gc_tracer (1.5.1)
|
||||
globalid (0.4.2)
|
||||
activesupport (>= 4.2.0)
|
||||
guess_html_encoding (0.0.11)
|
||||
hashdiff (0.3.9)
|
||||
hashdiff (1.0.0)
|
||||
hashie (3.6.0)
|
||||
highline (1.7.10)
|
||||
hiredis (0.6.3)
|
||||
hkdf (0.3.0)
|
||||
htmlentities (4.3.4)
|
||||
http_accept_language (2.0.5)
|
||||
i18n (1.6.0)
|
||||
http_accept_language (2.1.1)
|
||||
i18n (1.8.2)
|
||||
concurrent-ruby (~> 1.0)
|
||||
image_size (1.5.0)
|
||||
in_threads (1.5.1)
|
||||
jaro_winkler (1.5.2)
|
||||
in_threads (1.5.4)
|
||||
jaro_winkler (1.5.4)
|
||||
jmespath (1.4.0)
|
||||
jquery-rails (4.3.3)
|
||||
jquery-rails (4.3.5)
|
||||
rails-dom-testing (>= 1, < 3)
|
||||
railties (>= 4.2.0)
|
||||
thor (>= 0.14, < 2.0)
|
||||
json (2.2.0)
|
||||
jwt (2.2.1)
|
||||
kgio (2.11.2)
|
||||
kgio (2.11.3)
|
||||
libv8 (7.3.492.27.1)
|
||||
listen (3.1.5)
|
||||
rb-fsevent (~> 0.9, >= 0.9.4)
|
||||
rb-inotify (~> 0.9, >= 0.9.7)
|
||||
ruby_dep (~> 1.2)
|
||||
lograge (0.11.0)
|
||||
listen (3.2.1)
|
||||
rb-fsevent (~> 0.10, >= 0.10.3)
|
||||
rb-inotify (~> 0.9, >= 0.9.10)
|
||||
lograge (0.11.2)
|
||||
actionpack (>= 4)
|
||||
activesupport (>= 4)
|
||||
railties (>= 4)
|
||||
@ -167,62 +171,63 @@ GEM
|
||||
logstash-event (1.2.02)
|
||||
logstash-logger (0.26.1)
|
||||
logstash-event (~> 1.2)
|
||||
logster (2.3.2)
|
||||
loofah (2.2.3)
|
||||
logster (2.6.3)
|
||||
loofah (2.4.0)
|
||||
crass (~> 1.0.2)
|
||||
nokogiri (>= 1.5.9)
|
||||
lru_redux (1.1.0)
|
||||
lz4-ruby (0.3.3)
|
||||
mail (2.7.1)
|
||||
mini_mime (>= 0.1.1)
|
||||
maxminddb (0.1.22)
|
||||
memory_profiler (0.9.13)
|
||||
message_bus (2.2.0)
|
||||
memory_profiler (0.9.14)
|
||||
message_bus (2.2.3)
|
||||
rack (>= 1.1.3)
|
||||
metaclass (0.0.4)
|
||||
method_source (0.9.2)
|
||||
mini_mime (1.0.1)
|
||||
mini_mime (1.0.2)
|
||||
mini_portile2 (2.4.0)
|
||||
mini_racer (0.2.6)
|
||||
mini_racer (0.2.9)
|
||||
libv8 (>= 6.9.411)
|
||||
mini_scheduler (0.11.0)
|
||||
mini_scheduler (0.12.2)
|
||||
sidekiq
|
||||
mini_sql (0.2.2)
|
||||
mini_sql (0.2.4)
|
||||
mini_suffix (0.3.0)
|
||||
ffi (~> 1.9)
|
||||
minitest (5.11.3)
|
||||
minitest (5.14.0)
|
||||
mocha (1.8.0)
|
||||
metaclass (~> 0.0.1)
|
||||
mock_redis (0.19.0)
|
||||
moneta (1.1.1)
|
||||
msgpack (1.2.10)
|
||||
multi_json (1.13.1)
|
||||
mock_redis (0.22.0)
|
||||
msgpack (1.3.3)
|
||||
multi_json (1.14.1)
|
||||
multi_xml (0.6.0)
|
||||
multipart-post (2.1.1)
|
||||
mustache (1.1.0)
|
||||
nokogiri (1.10.3)
|
||||
mustache (1.1.1)
|
||||
nio4r (2.5.2)
|
||||
nokogiri (1.10.8)
|
||||
mini_portile2 (~> 2.4.0)
|
||||
nokogumbo (2.0.1)
|
||||
nokogumbo (2.0.2)
|
||||
nokogiri (~> 1.8, >= 1.8.4)
|
||||
oauth (0.5.4)
|
||||
oauth2 (1.4.1)
|
||||
faraday (>= 0.8, < 0.16.0)
|
||||
oauth2 (1.4.2)
|
||||
faraday (>= 0.8, < 2.0)
|
||||
jwt (>= 1.0, < 3.0)
|
||||
multi_json (~> 1.3)
|
||||
multi_xml (~> 0.5)
|
||||
rack (>= 1.2, < 3)
|
||||
oj (3.7.12)
|
||||
oj (3.10.2)
|
||||
omniauth (1.9.0)
|
||||
hashie (>= 3.4.6, < 3.7.0)
|
||||
rack (>= 1.6.2, < 3)
|
||||
omniauth-facebook (5.0.0)
|
||||
omniauth-facebook (6.0.0)
|
||||
omniauth-oauth2 (~> 1.2)
|
||||
omniauth-github (1.4.0)
|
||||
omniauth (~> 1.5)
|
||||
omniauth-oauth2 (>= 1.4.0, < 2.0)
|
||||
omniauth-google-oauth2 (0.7.0)
|
||||
omniauth-google-oauth2 (0.8.0)
|
||||
jwt (>= 2.0)
|
||||
omniauth (>= 1.1.1)
|
||||
omniauth-oauth2 (>= 1.5)
|
||||
omniauth-oauth2 (>= 1.6)
|
||||
omniauth-instagram (1.3.0)
|
||||
omniauth (~> 1)
|
||||
omniauth-oauth2 (~> 1)
|
||||
@ -232,30 +237,25 @@ GEM
|
||||
omniauth-oauth2 (1.6.0)
|
||||
oauth2 (~> 1.1)
|
||||
omniauth (~> 1.9)
|
||||
omniauth-openid (1.0.1)
|
||||
omniauth (~> 1.0)
|
||||
rack-openid (~> 1.3.1)
|
||||
omniauth-twitter (1.4.0)
|
||||
omniauth-oauth (~> 1.1)
|
||||
rack
|
||||
onebox (1.8.92)
|
||||
onebox (1.9.26)
|
||||
addressable (~> 2.7.0)
|
||||
htmlentities (~> 4.3)
|
||||
moneta (~> 1.0)
|
||||
multi_json (~> 1.11)
|
||||
mustache
|
||||
nokogiri (~> 1.7)
|
||||
sanitize
|
||||
openid-redis-store (0.0.2)
|
||||
redis
|
||||
ruby-openid
|
||||
openssl-signature_algorithm (0.3.0)
|
||||
optimist (3.0.0)
|
||||
parallel (1.17.0)
|
||||
parallel_tests (2.28.0)
|
||||
parallel (1.19.1)
|
||||
parallel_tests (2.31.0)
|
||||
parallel
|
||||
parser (2.6.3.0)
|
||||
parser (2.7.0.2)
|
||||
ast (~> 2.4.0)
|
||||
pg (1.1.4)
|
||||
progress (3.5.0)
|
||||
pg (1.2.2)
|
||||
progress (3.5.2)
|
||||
pry (0.12.2)
|
||||
coderay (~> 1.1.0)
|
||||
method_source (~> 0.9.0)
|
||||
@ -263,103 +263,107 @@ GEM
|
||||
pry (>= 0.9.10, < 0.13.0)
|
||||
pry-rails (0.3.9)
|
||||
pry (>= 0.10.4)
|
||||
public_suffix (3.0.3)
|
||||
puma (3.12.1)
|
||||
public_suffix (4.0.3)
|
||||
puma (4.3.1)
|
||||
nio4r (~> 2.0)
|
||||
r2 (0.2.7)
|
||||
rack (2.0.7)
|
||||
rack-mini-profiler (1.1.4)
|
||||
rack (2.0.8)
|
||||
rack-mini-profiler (1.1.6)
|
||||
rack (>= 1.2.0)
|
||||
rack-openid (1.3.1)
|
||||
rack (>= 1.1.0)
|
||||
ruby-openid (>= 2.1.8)
|
||||
rack-protection (2.0.5)
|
||||
rack-protection (2.0.8.1)
|
||||
rack
|
||||
rack-test (1.1.0)
|
||||
rack (>= 1.0, < 3)
|
||||
rails-dom-testing (2.0.3)
|
||||
activesupport (>= 4.2.0)
|
||||
nokogiri (>= 1.6)
|
||||
rails-html-sanitizer (1.0.4)
|
||||
loofah (~> 2.2, >= 2.2.2)
|
||||
rails-html-sanitizer (1.3.0)
|
||||
loofah (~> 2.3)
|
||||
rails_multisite (2.0.7)
|
||||
activerecord (> 4.2, < 7)
|
||||
railties (> 4.2, < 7)
|
||||
railties (5.2.3)
|
||||
actionpack (= 5.2.3)
|
||||
activesupport (= 5.2.3)
|
||||
railties (6.0.1)
|
||||
actionpack (= 6.0.1)
|
||||
activesupport (= 6.0.1)
|
||||
method_source
|
||||
rake (>= 0.8.7)
|
||||
thor (>= 0.19.0, < 2.0)
|
||||
thor (>= 0.20.3, < 2.0)
|
||||
rainbow (3.0.0)
|
||||
raindrops (0.19.0)
|
||||
rake (12.3.2)
|
||||
rake-compiler (1.0.7)
|
||||
raindrops (0.19.1)
|
||||
rake (13.0.1)
|
||||
rake-compiler (1.1.0)
|
||||
rake
|
||||
rb-fsevent (0.10.3)
|
||||
rb-inotify (0.10.0)
|
||||
rb-inotify (0.10.1)
|
||||
ffi (~> 1.0)
|
||||
rbtrace (0.4.11)
|
||||
ffi (>= 1.0.6)
|
||||
msgpack (>= 0.4.3)
|
||||
optimist (>= 3.0.0)
|
||||
rchardet (1.8.0)
|
||||
redis (4.0.1)
|
||||
redis-namespace (1.6.0)
|
||||
redis (4.1.3)
|
||||
redis-namespace (1.7.0)
|
||||
redis (>= 3.0.4)
|
||||
request_store (1.4.1)
|
||||
request_store (1.5.0)
|
||||
rack (>= 1.4)
|
||||
rexml (3.2.4)
|
||||
rinku (2.0.6)
|
||||
rotp (3.3.1)
|
||||
rqrcode (0.10.1)
|
||||
rotp (5.1.0)
|
||||
addressable (~> 2.5)
|
||||
rqrcode (1.1.2)
|
||||
chunky_png (~> 1.0)
|
||||
rspec (3.8.0)
|
||||
rspec-core (~> 3.8.0)
|
||||
rspec-expectations (~> 3.8.0)
|
||||
rspec-mocks (~> 3.8.0)
|
||||
rspec-core (3.8.0)
|
||||
rspec-support (~> 3.8.0)
|
||||
rspec-expectations (3.8.3)
|
||||
rqrcode_core (~> 0.1)
|
||||
rqrcode_core (0.1.1)
|
||||
rspec (3.9.0)
|
||||
rspec-core (~> 3.9.0)
|
||||
rspec-expectations (~> 3.9.0)
|
||||
rspec-mocks (~> 3.9.0)
|
||||
rspec-core (3.9.1)
|
||||
rspec-support (~> 3.9.1)
|
||||
rspec-expectations (3.9.0)
|
||||
diff-lcs (>= 1.2.0, < 2.0)
|
||||
rspec-support (~> 3.8.0)
|
||||
rspec-html-matchers (0.9.1)
|
||||
rspec-support (~> 3.9.0)
|
||||
rspec-html-matchers (0.9.2)
|
||||
nokogiri (~> 1)
|
||||
rspec (>= 3.0.0.a, < 4)
|
||||
rspec-mocks (3.8.0)
|
||||
rspec-mocks (3.9.1)
|
||||
diff-lcs (>= 1.2.0, < 2.0)
|
||||
rspec-support (~> 3.8.0)
|
||||
rspec-rails (3.8.2)
|
||||
actionpack (>= 3.0)
|
||||
activesupport (>= 3.0)
|
||||
railties (>= 3.0)
|
||||
rspec-core (~> 3.8.0)
|
||||
rspec-expectations (~> 3.8.0)
|
||||
rspec-mocks (~> 3.8.0)
|
||||
rspec-support (~> 3.8.0)
|
||||
rspec-support (3.8.0)
|
||||
rspec-support (~> 3.9.0)
|
||||
rspec-rails (4.0.0.beta2)
|
||||
actionpack (>= 4.2)
|
||||
activesupport (>= 4.2)
|
||||
railties (>= 4.2)
|
||||
rspec-core (~> 3.8)
|
||||
rspec-expectations (~> 3.8)
|
||||
rspec-mocks (~> 3.8)
|
||||
rspec-support (~> 3.8)
|
||||
rspec-support (3.9.2)
|
||||
rtlit (0.0.5)
|
||||
rubocop (0.69.0)
|
||||
rubocop (0.80.0)
|
||||
jaro_winkler (~> 1.5.1)
|
||||
parallel (~> 1.10)
|
||||
parser (>= 2.6)
|
||||
parser (>= 2.7.0.1)
|
||||
rainbow (>= 2.2.2, < 4.0)
|
||||
rexml
|
||||
ruby-progressbar (~> 1.7)
|
||||
unicode-display_width (>= 1.4.0, < 1.7)
|
||||
ruby-openid (2.7.0)
|
||||
ruby-prof (0.17.0)
|
||||
ruby-progressbar (1.10.0)
|
||||
rubocop-discourse (1.0.2)
|
||||
rubocop (>= 0.69.0)
|
||||
ruby-prof (1.3.0)
|
||||
ruby-progressbar (1.10.1)
|
||||
ruby-readability (0.7.0)
|
||||
guess_html_encoding (>= 0.0.4)
|
||||
nokogiri (>= 1.6.0)
|
||||
ruby_dep (1.5.0)
|
||||
rubyzip (2.2.0)
|
||||
safe_yaml (1.0.5)
|
||||
sanitize (5.0.0)
|
||||
sanitize (5.1.0)
|
||||
crass (~> 1.0.2)
|
||||
nokogiri (>= 1.8.0)
|
||||
nokogumbo (~> 2.0)
|
||||
sassc (2.0.1)
|
||||
ffi (~> 1.9)
|
||||
rake
|
||||
sassc-rails (2.1.1)
|
||||
sassc-rails (2.1.2)
|
||||
railties (>= 4.0.0)
|
||||
sassc (>= 2.0)
|
||||
sprockets (> 3.0)
|
||||
@ -368,18 +372,17 @@ GEM
|
||||
seed-fu (2.3.9)
|
||||
activerecord (>= 3.1)
|
||||
activesupport (>= 3.1)
|
||||
shoulda-matchers (3.1.3)
|
||||
activesupport (>= 4.0.0)
|
||||
sidekiq (5.2.7)
|
||||
connection_pool (~> 2.2, >= 2.2.2)
|
||||
rack (>= 1.5.0)
|
||||
rack-protection (>= 1.5.0)
|
||||
redis (>= 3.3.5, < 5)
|
||||
simplecov (0.16.1)
|
||||
shoulda-matchers (4.3.0)
|
||||
activesupport (>= 4.2.0)
|
||||
sidekiq (6.0.5)
|
||||
connection_pool (>= 2.2.2)
|
||||
rack (~> 2.0)
|
||||
rack-protection (>= 2.0.0)
|
||||
redis (>= 4.1.0)
|
||||
simplecov (0.18.3)
|
||||
docile (~> 1.1)
|
||||
json (>= 1.8, < 3)
|
||||
simplecov-html (~> 0.10.0)
|
||||
simplecov-html (0.10.2)
|
||||
simplecov-html (~> 0.11)
|
||||
simplecov-html (0.12.1)
|
||||
sprockets (3.7.2)
|
||||
concurrent-ruby (~> 1.0)
|
||||
rack (> 1, < 3)
|
||||
@ -388,42 +391,46 @@ GEM
|
||||
activesupport (>= 4.0)
|
||||
sprockets (>= 3.0.0)
|
||||
sshkey (2.0.0)
|
||||
stackprof (0.2.12)
|
||||
test-prof (0.9.0)
|
||||
thor (0.20.3)
|
||||
stackprof (0.2.15)
|
||||
test-prof (0.11.3)
|
||||
thor (1.0.1)
|
||||
thread_safe (0.3.6)
|
||||
tilt (2.0.9)
|
||||
tzinfo (1.2.5)
|
||||
tilt (2.0.10)
|
||||
tzinfo (1.2.6)
|
||||
thread_safe (~> 0.1)
|
||||
uglifier (4.1.20)
|
||||
uglifier (4.2.0)
|
||||
execjs (>= 0.3.0, < 3)
|
||||
unf (0.1.4)
|
||||
unf_ext
|
||||
unf_ext (0.0.7.6)
|
||||
unicode-display_width (1.6.0)
|
||||
unicorn (5.5.1)
|
||||
unicode-display_width (1.6.1)
|
||||
unicorn (5.5.3)
|
||||
kgio (~> 2.6)
|
||||
raindrops (~> 0.7)
|
||||
uniform_notifier (1.12.1)
|
||||
webmock (3.5.1)
|
||||
uniform_notifier (1.13.0)
|
||||
webmock (3.8.2)
|
||||
addressable (>= 2.3.6)
|
||||
crack (>= 0.3.2)
|
||||
hashdiff
|
||||
webpush (0.3.8)
|
||||
hashdiff (>= 0.4.0, < 2.0.0)
|
||||
webpush (1.0.0)
|
||||
hkdf (~> 0.2)
|
||||
jwt (~> 2.0)
|
||||
yaml-lint (0.0.10)
|
||||
zeitwerk (2.2.2)
|
||||
|
||||
PLATFORMS
|
||||
ruby
|
||||
|
||||
DEPENDENCIES
|
||||
actionmailer (= 5.2.3)
|
||||
actionpack (= 5.2.3)
|
||||
actionview (= 5.2.3)
|
||||
actionmailer (= 6.0.1)
|
||||
actionpack (= 6.0.1)
|
||||
actionview (= 6.0.1)
|
||||
actionview_precompiler
|
||||
active_model_serializers (~> 0.8.3)
|
||||
activemodel (= 5.2.3)
|
||||
activerecord (= 5.2.3)
|
||||
activesupport (= 5.2.3)
|
||||
activemodel (= 6.0.1)
|
||||
activerecord (= 6.0.1)
|
||||
activesupport (= 6.0.1)
|
||||
addressable
|
||||
annotate
|
||||
aws-sdk-s3
|
||||
aws-sdk-sns
|
||||
@ -433,19 +440,22 @@ DEPENDENCIES
|
||||
bootsnap
|
||||
bullet
|
||||
byebug
|
||||
cbor
|
||||
certified
|
||||
colored2
|
||||
cose
|
||||
cppjieba_rb
|
||||
css_parser
|
||||
diffy
|
||||
discourse-ember-source (~> 3.8.0)
|
||||
discourse-ember-source (~> 3.12.2)
|
||||
discourse_image_optim
|
||||
email_reply_trimmer (~> 0.1)
|
||||
email_reply_trimmer
|
||||
ember-handlebars-template (= 0.8.0)
|
||||
ember-rails (= 0.18.5)
|
||||
excon
|
||||
execjs
|
||||
fabrication
|
||||
fakeweb (~> 1.3.0)
|
||||
fakeweb
|
||||
fast_blank
|
||||
fast_xor
|
||||
fast_xs
|
||||
@ -453,15 +463,15 @@ DEPENDENCIES
|
||||
flamegraph
|
||||
gc_tracer
|
||||
highline (~> 1.7.0)
|
||||
hiredis
|
||||
htmlentities
|
||||
http_accept_language (~> 2.0.5)
|
||||
http_accept_language
|
||||
listen
|
||||
lograge
|
||||
logstash-event
|
||||
logstash-logger
|
||||
logster
|
||||
lru_redux
|
||||
lz4-ruby
|
||||
mail
|
||||
maxminddb
|
||||
memory_profiler
|
||||
@ -472,7 +482,7 @@ DEPENDENCIES
|
||||
mini_sql
|
||||
mini_suffix
|
||||
minitest
|
||||
mocha
|
||||
mocha (= 1.8.0)
|
||||
mock_redis
|
||||
multi_json
|
||||
mustache
|
||||
@ -484,44 +494,46 @@ DEPENDENCIES
|
||||
omniauth-google-oauth2
|
||||
omniauth-instagram
|
||||
omniauth-oauth2
|
||||
omniauth-openid
|
||||
omniauth-twitter
|
||||
onebox (= 1.8.92)
|
||||
openid-redis-store
|
||||
onebox
|
||||
parallel_tests
|
||||
pg
|
||||
pry-nav
|
||||
pry-rails
|
||||
puma
|
||||
r2 (~> 0.2.5)
|
||||
r2
|
||||
rack (= 2.0.8)
|
||||
rack-mini-profiler
|
||||
rack-protection
|
||||
rails_multisite
|
||||
railties (= 5.2.3)
|
||||
railties (= 6.0.1)
|
||||
rake
|
||||
rb-fsevent
|
||||
rb-inotify (~> 0.9)
|
||||
rbtrace
|
||||
rchardet
|
||||
redis (= 4.0.1)
|
||||
redis
|
||||
redis-namespace
|
||||
rinku
|
||||
rotp
|
||||
rqrcode
|
||||
rspec
|
||||
rspec-html-matchers
|
||||
rspec-rails
|
||||
rspec-rails (= 4.0.0.beta2)
|
||||
rtlit
|
||||
rubocop
|
||||
rubocop-discourse
|
||||
ruby-prof
|
||||
ruby-readability
|
||||
rubyzip
|
||||
sanitize
|
||||
sassc
|
||||
sassc (= 2.0.1)
|
||||
sassc-rails
|
||||
seed-fu
|
||||
shoulda-matchers (~> 3.1, >= 3.1.3)
|
||||
shoulda-matchers
|
||||
sidekiq
|
||||
simplecov
|
||||
sprockets (= 3.7.2)
|
||||
sprockets-rails
|
||||
sshkey
|
||||
stackprof
|
||||
@ -533,6 +545,7 @@ DEPENDENCIES
|
||||
unicorn
|
||||
webmock
|
||||
webpush
|
||||
yaml-lint
|
||||
|
||||
BUNDLED WITH
|
||||
2.1.1
|
||||
|
||||
12
README.md
12
README.md
@ -44,14 +44,16 @@ If you're looking for business class hosting, see [discourse.org/buy](https://ww
|
||||
|
||||
## Requirements
|
||||
|
||||
Discourse is built for the *next* 10 years of the Internet, so our requirements are high:
|
||||
Discourse is built for the *next* 10 years of the Internet, so our requirements are high.
|
||||
|
||||
Discourse supports the **latest, stable releases** of all major browsers and platforms:
|
||||
|
||||
| Browsers | Tablets | Phones |
|
||||
| --------------------- | ------------ | ------------ |
|
||||
| Safari 10+ | iPad 4+ | iOS 10+ |
|
||||
| Google Chrome 57+ | Android 4.4+ | Android 4.4+ |
|
||||
| Internet Explorer 11+ | | |
|
||||
| Firefox 52+ | | |
|
||||
| Apple Safari | iPadOS | iOS |
|
||||
| Google Chrome | Android | Android |
|
||||
| Microsoft Edge | | |
|
||||
| Mozilla Firefox | | |
|
||||
|
||||
## Built With
|
||||
|
||||
|
||||
11
app/assets/javascripts/admin/adapters/api-key.js.es6
Normal file
11
app/assets/javascripts/admin/adapters/api-key.js.es6
Normal file
@ -0,0 +1,11 @@
|
||||
import RESTAdapter from "discourse/adapters/rest";
|
||||
|
||||
export default RESTAdapter.extend({
|
||||
basePath() {
|
||||
return "/admin/api/";
|
||||
},
|
||||
|
||||
apiNameFor() {
|
||||
return "key";
|
||||
}
|
||||
});
|
||||
7
app/assets/javascripts/admin/adapters/email-style.js.es6
Normal file
7
app/assets/javascripts/admin/adapters/email-style.js.es6
Normal file
@ -0,0 +1,7 @@
|
||||
import RestAdapter from "discourse/adapters/rest";
|
||||
|
||||
export default RestAdapter.extend({
|
||||
pathFor() {
|
||||
return "/admin/customize/email_style";
|
||||
}
|
||||
});
|
||||
@ -0,0 +1,7 @@
|
||||
import RestAdapter from "discourse/adapters/rest";
|
||||
|
||||
export default RestAdapter.extend({
|
||||
basePath() {
|
||||
return "/admin/logs/";
|
||||
}
|
||||
});
|
||||
5
app/assets/javascripts/admin/adapters/tag-group.js.es6
Normal file
5
app/assets/javascripts/admin/adapters/tag-group.js.es6
Normal file
@ -0,0 +1,5 @@
|
||||
import RestAdapter from "discourse/adapters/rest";
|
||||
|
||||
export default RestAdapter.extend({
|
||||
jsonMode: true
|
||||
});
|
||||
@ -1,7 +1,9 @@
|
||||
import Component from "@ember/component";
|
||||
import loadScript from "discourse/lib/load-script";
|
||||
import { observes } from "ember-addons/ember-computed-decorators";
|
||||
import { observes } from "discourse-common/utils/decorators";
|
||||
import { on } from "@ember/object/evented";
|
||||
|
||||
export default Ember.Component.extend({
|
||||
export default Component.extend({
|
||||
mode: "css",
|
||||
classNames: ["ace-wrapper"],
|
||||
_editor: null,
|
||||
@ -17,8 +19,9 @@ export default Ember.Component.extend({
|
||||
|
||||
@observes("content")
|
||||
contentChanged() {
|
||||
if (this._editor && !this._skipContentChangeEvent && this.content) {
|
||||
this._editor.getSession().setValue(this.content);
|
||||
const content = this.content || "";
|
||||
if (this._editor && !this._skipContentChangeEvent) {
|
||||
this._editor.getSession().setValue(content);
|
||||
}
|
||||
},
|
||||
|
||||
@ -47,7 +50,7 @@ export default Ember.Component.extend({
|
||||
}
|
||||
},
|
||||
|
||||
_destroyEditor: function() {
|
||||
_destroyEditor: on("willDestroyElement", function() {
|
||||
if (this._editor) {
|
||||
this._editor.destroy();
|
||||
this._editor = null;
|
||||
@ -58,7 +61,7 @@ export default Ember.Component.extend({
|
||||
}
|
||||
|
||||
$(window).off("ace:resize");
|
||||
}.on("willDestroyElement"),
|
||||
}),
|
||||
|
||||
resize() {
|
||||
if (this._editor) {
|
||||
@ -74,7 +77,7 @@ export default Ember.Component.extend({
|
||||
if (!this.element || this.isDestroying || this.isDestroyed) {
|
||||
return;
|
||||
}
|
||||
const editor = loadedAce.edit(this.$(".ace")[0]);
|
||||
const editor = loadedAce.edit(this.element.querySelector(".ace"));
|
||||
|
||||
editor.setTheme("ace/theme/chrome");
|
||||
editor.setShowPrintMargin(false);
|
||||
@ -88,7 +91,7 @@ export default Ember.Component.extend({
|
||||
editor.$blockScrolling = Infinity;
|
||||
editor.renderer.setScrollMargin(10, 10);
|
||||
|
||||
this.$().data("editor", editor);
|
||||
this.element.setAttribute("data-editor", editor);
|
||||
this._editor = editor;
|
||||
this.changeDisabledState();
|
||||
|
||||
|
||||
@ -1,73 +1,73 @@
|
||||
import debounce from "discourse/lib/debounce";
|
||||
import { renderSpinner } from "discourse/helpers/loading-spinner";
|
||||
import { escapeExpression } from "discourse/lib/utilities";
|
||||
import { bufferedRender } from "discourse-common/lib/buffered-render";
|
||||
import { observes, on } from "ember-addons/ember-computed-decorators";
|
||||
import { scheduleOnce } from "@ember/runloop";
|
||||
import Component from "@ember/component";
|
||||
import discourseDebounce from "discourse/lib/debounce";
|
||||
import { observes, on } from "discourse-common/utils/decorators";
|
||||
|
||||
export default Ember.Component.extend(
|
||||
bufferedRender({
|
||||
classNames: ["admin-backups-logs"],
|
||||
export default Component.extend({
|
||||
classNames: ["admin-backups-logs"],
|
||||
showLoadingSpinner: false,
|
||||
hasFormattedLogs: false,
|
||||
noLogsMessage: I18n.t("admin.backups.logs.none"),
|
||||
|
||||
init() {
|
||||
this._super(...arguments);
|
||||
this._reset();
|
||||
},
|
||||
init() {
|
||||
this._super(...arguments);
|
||||
this._reset();
|
||||
},
|
||||
|
||||
_reset() {
|
||||
this.setProperties({ formattedLogs: "", index: 0 });
|
||||
},
|
||||
_reset() {
|
||||
this.setProperties({ formattedLogs: "", index: 0 });
|
||||
},
|
||||
|
||||
_scrollDown() {
|
||||
const $div = this.$()[0];
|
||||
$div.scrollTop = $div.scrollHeight;
|
||||
},
|
||||
_scrollDown() {
|
||||
const div = this.element;
|
||||
div.scrollTop = div.scrollHeight;
|
||||
},
|
||||
|
||||
@on("init")
|
||||
@observes("logs.[]")
|
||||
_resetFormattedLogs() {
|
||||
if (this.logs.length === 0) {
|
||||
this._reset(); // reset the cached logs whenever the model is reset
|
||||
this.rerenderBuffer();
|
||||
}
|
||||
},
|
||||
|
||||
@on("init")
|
||||
@observes("logs.[]")
|
||||
_updateFormattedLogs: debounce(function() {
|
||||
const logs = this.logs;
|
||||
if (logs.length === 0) return;
|
||||
|
||||
// do the log formatting only once for HELLish performance
|
||||
let formattedLogs = this.formattedLogs;
|
||||
for (let i = this.index, length = logs.length; i < length; i++) {
|
||||
const date = logs[i].get("timestamp"),
|
||||
message = escapeExpression(logs[i].get("message"));
|
||||
formattedLogs += "[" + date + "] " + message + "\n";
|
||||
}
|
||||
// update the formatted logs & cache index
|
||||
this.setProperties({
|
||||
formattedLogs: formattedLogs,
|
||||
index: logs.length
|
||||
});
|
||||
// force rerender
|
||||
this.rerenderBuffer();
|
||||
|
||||
Ember.run.scheduleOnce("afterRender", this, this._scrollDown);
|
||||
}, 150),
|
||||
|
||||
buildBuffer(buffer) {
|
||||
const formattedLogs = this.formattedLogs;
|
||||
if (formattedLogs && formattedLogs.length > 0) {
|
||||
buffer.push("<pre>");
|
||||
buffer.push(formattedLogs);
|
||||
buffer.push("</pre>");
|
||||
} else {
|
||||
buffer.push("<p>" + I18n.t("admin.backups.logs.none") + "</p>");
|
||||
}
|
||||
// add a loading indicator
|
||||
if (this.get("status.isOperationRunning")) {
|
||||
buffer.push(renderSpinner("small"));
|
||||
}
|
||||
@on("init")
|
||||
@observes("logs.[]")
|
||||
_resetFormattedLogs() {
|
||||
if (this.logs.length === 0) {
|
||||
this._reset(); // reset the cached logs whenever the model is reset
|
||||
this.renderLogs();
|
||||
}
|
||||
})
|
||||
);
|
||||
},
|
||||
|
||||
@on("init")
|
||||
@observes("logs.[]")
|
||||
_updateFormattedLogs: discourseDebounce(function() {
|
||||
const logs = this.logs;
|
||||
if (logs.length === 0) return;
|
||||
|
||||
// do the log formatting only once for HELLish performance
|
||||
let formattedLogs = this.formattedLogs;
|
||||
for (let i = this.index, length = logs.length; i < length; i++) {
|
||||
const date = logs[i].get("timestamp"),
|
||||
message = logs[i].get("message");
|
||||
formattedLogs += "[" + date + "] " + message + "\n";
|
||||
}
|
||||
// update the formatted logs & cache index
|
||||
this.setProperties({
|
||||
formattedLogs: formattedLogs,
|
||||
index: logs.length
|
||||
});
|
||||
// force rerender
|
||||
this.renderLogs();
|
||||
|
||||
scheduleOnce("afterRender", this, this._scrollDown);
|
||||
}, 150),
|
||||
|
||||
renderLogs() {
|
||||
const formattedLogs = this.formattedLogs;
|
||||
if (formattedLogs && formattedLogs.length > 0) {
|
||||
this.set("hasFormattedLogs", true);
|
||||
} else {
|
||||
this.set("hasFormattedLogs", false);
|
||||
}
|
||||
// add a loading indicator
|
||||
if (this.get("status.isOperationRunning")) {
|
||||
this.set("showLoadingSpinner", true);
|
||||
} else {
|
||||
this.set("showLoadingSpinner", false);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@ -1,35 +1,30 @@
|
||||
import Component from "@ember/component";
|
||||
import { iconHTML } from "discourse-common/lib/icon-library";
|
||||
import { bufferedRender } from "discourse-common/lib/buffered-render";
|
||||
|
||||
export default Ember.Component.extend(
|
||||
bufferedRender({
|
||||
tagName: "th",
|
||||
classNames: ["sortable"],
|
||||
rerenderTriggers: ["order", "ascending"],
|
||||
|
||||
buildBuffer(buffer) {
|
||||
const icon = this.icon;
|
||||
|
||||
if (icon) {
|
||||
buffer.push(iconHTML(icon));
|
||||
}
|
||||
|
||||
buffer.push(I18n.t(this.i18nKey));
|
||||
|
||||
if (this.field === this.order) {
|
||||
buffer.push(iconHTML(this.ascending ? "chevron-up" : "chevron-down"));
|
||||
}
|
||||
},
|
||||
|
||||
click() {
|
||||
const currentOrder = this.order;
|
||||
const field = this.field;
|
||||
|
||||
if (currentOrder === field) {
|
||||
this.set("ascending", this.ascending ? null : true);
|
||||
} else {
|
||||
this.setProperties({ order: field, ascending: null });
|
||||
}
|
||||
export default Component.extend({
|
||||
tagName: "th",
|
||||
classNames: ["sortable"],
|
||||
chevronIcon: null,
|
||||
toggleProperties() {
|
||||
if (this.order === this.field) {
|
||||
this.set("ascending", this.ascending ? null : true);
|
||||
} else {
|
||||
this.setProperties({ order: this.field, ascending: null });
|
||||
}
|
||||
})
|
||||
);
|
||||
},
|
||||
toggleChevron() {
|
||||
if (this.order === this.field) {
|
||||
let chevron = iconHTML(this.ascending ? "chevron-up" : "chevron-down");
|
||||
this.set("chevronIcon", `${chevron}`.htmlSafe());
|
||||
} else {
|
||||
this.set("chevronIcon", null);
|
||||
}
|
||||
},
|
||||
click() {
|
||||
this.toggleProperties();
|
||||
},
|
||||
didReceiveAttrs() {
|
||||
this._super(...arguments);
|
||||
this.toggleChevron();
|
||||
}
|
||||
});
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
export default Ember.Component.extend({
|
||||
import Component from "@ember/component";
|
||||
export default Component.extend({
|
||||
tagName: "",
|
||||
|
||||
buffer: "",
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
export default Ember.Component.extend({
|
||||
import Component from "@ember/component";
|
||||
export default Component.extend({
|
||||
classNames: ["row"]
|
||||
});
|
||||
|
||||
@ -1,11 +1,12 @@
|
||||
import Component from "@ember/component";
|
||||
import loadScript from "discourse/lib/load-script";
|
||||
|
||||
export default Ember.Component.extend({
|
||||
export default Component.extend({
|
||||
tagName: "canvas",
|
||||
type: "line",
|
||||
|
||||
refreshChart() {
|
||||
const ctx = this.$()[0].getContext("2d");
|
||||
const ctx = this.element.getContext("2d");
|
||||
const model = this.model;
|
||||
const rawData = this.get("model.data");
|
||||
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
export default Ember.Component.extend({
|
||||
import Component from "@ember/component";
|
||||
export default Component.extend({
|
||||
tagName: ""
|
||||
});
|
||||
|
||||
@ -1,7 +1,11 @@
|
||||
import { makeArray } from "discourse-common/lib/helpers";
|
||||
import { debounce } from "@ember/runloop";
|
||||
import { schedule } from "@ember/runloop";
|
||||
import Component from "@ember/component";
|
||||
import { number } from "discourse/lib/formatter";
|
||||
import loadScript from "discourse/lib/load-script";
|
||||
|
||||
export default Ember.Component.extend({
|
||||
export default Component.extend({
|
||||
classNames: ["admin-report-chart"],
|
||||
limit: 8,
|
||||
total: 0,
|
||||
@ -10,7 +14,7 @@ export default Ember.Component.extend({
|
||||
this._super(...arguments);
|
||||
|
||||
this.resizeHandler = () =>
|
||||
Ember.run.debounce(this, this._scheduleChartRendering, 500);
|
||||
debounce(this, this._scheduleChartRendering, 500);
|
||||
},
|
||||
|
||||
didInsertElement() {
|
||||
@ -30,23 +34,24 @@ export default Ember.Component.extend({
|
||||
didReceiveAttrs() {
|
||||
this._super(...arguments);
|
||||
|
||||
Ember.run.debounce(this, this._scheduleChartRendering, 100);
|
||||
debounce(this, this._scheduleChartRendering, 100);
|
||||
},
|
||||
|
||||
_scheduleChartRendering() {
|
||||
Ember.run.schedule("afterRender", () => {
|
||||
this._renderChart(this.model, this.$(".chart-canvas"));
|
||||
schedule("afterRender", () => {
|
||||
this._renderChart(
|
||||
this.model,
|
||||
this.element && this.element.querySelector(".chart-canvas")
|
||||
);
|
||||
});
|
||||
},
|
||||
|
||||
_renderChart(model, $chartCanvas) {
|
||||
if (!$chartCanvas || !$chartCanvas.length) return;
|
||||
_renderChart(model, chartCanvas) {
|
||||
if (!chartCanvas) return;
|
||||
|
||||
const context = $chartCanvas[0].getContext("2d");
|
||||
const chartData = Ember.makeArray(
|
||||
model.get("chartData") || model.get("data")
|
||||
);
|
||||
const prevChartData = Ember.makeArray(
|
||||
const context = chartCanvas.getContext("2d");
|
||||
const chartData = makeArray(model.get("chartData") || model.get("data"));
|
||||
const prevChartData = makeArray(
|
||||
model.get("prevChartData") || model.get("prev_data")
|
||||
);
|
||||
|
||||
@ -82,6 +87,11 @@ export default Ember.Component.extend({
|
||||
|
||||
loadScript("/javascripts/Chart.min.js").then(() => {
|
||||
this._resetChart();
|
||||
|
||||
if (!this.element) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._chart = new window.Chart(context, this._buildChartConfig(data));
|
||||
});
|
||||
},
|
||||
@ -102,6 +112,10 @@ export default Ember.Component.extend({
|
||||
},
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
responsiveAnimationDuration: 0,
|
||||
animation: {
|
||||
duration: 0
|
||||
},
|
||||
layout: {
|
||||
padding: {
|
||||
left: 0,
|
||||
@ -118,7 +132,10 @@ export default Ember.Component.extend({
|
||||
userCallback: label => {
|
||||
if (Math.floor(label) === label) return label;
|
||||
},
|
||||
callback: label => number(label)
|
||||
callback: label => number(label),
|
||||
sampleSize: 5,
|
||||
maxRotation: 25,
|
||||
minRotation: 25
|
||||
}
|
||||
}
|
||||
],
|
||||
@ -129,6 +146,11 @@ export default Ember.Component.extend({
|
||||
type: "time",
|
||||
time: {
|
||||
parser: "YYYY-MM-DD"
|
||||
},
|
||||
ticks: {
|
||||
sampleSize: 5,
|
||||
maxRotation: 50,
|
||||
minRotation: 50
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
export default Ember.Component.extend({
|
||||
import Component from "@ember/component";
|
||||
export default Component.extend({
|
||||
classNames: ["admin-report-counters"],
|
||||
|
||||
attributeBindings: ["model.description:title"]
|
||||
|
||||
@ -1,7 +1,9 @@
|
||||
export default Ember.Component.extend({
|
||||
import { match } from "@ember/object/computed";
|
||||
import Component from "@ember/component";
|
||||
export default Component.extend({
|
||||
allTime: true,
|
||||
tagName: "tr",
|
||||
reverseColors: Ember.computed.match(
|
||||
reverseColors: match(
|
||||
"report.type",
|
||||
/^(time_to_first_response|topics_with_no_response)$/
|
||||
),
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
export default Ember.Component.extend({
|
||||
import Component from "@ember/component";
|
||||
export default Component.extend({
|
||||
classNames: ["admin-report-inline-table"]
|
||||
});
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
export default Ember.Component.extend({
|
||||
import Component from "@ember/component";
|
||||
export default Component.extend({
|
||||
tagName: "tr"
|
||||
});
|
||||
|
||||
@ -1,14 +1,18 @@
|
||||
import { makeArray } from "discourse-common/lib/helpers";
|
||||
import { debounce } from "@ember/runloop";
|
||||
import { schedule } from "@ember/runloop";
|
||||
import Component from "@ember/component";
|
||||
import { number } from "discourse/lib/formatter";
|
||||
import loadScript from "discourse/lib/load-script";
|
||||
|
||||
export default Ember.Component.extend({
|
||||
export default Component.extend({
|
||||
classNames: ["admin-report-chart", "admin-report-stacked-chart"],
|
||||
|
||||
init() {
|
||||
this._super(...arguments);
|
||||
|
||||
this.resizeHandler = () =>
|
||||
Ember.run.debounce(this, this._scheduleChartRendering, 500);
|
||||
debounce(this, this._scheduleChartRendering, 500);
|
||||
},
|
||||
|
||||
didInsertElement() {
|
||||
@ -28,26 +32,31 @@ export default Ember.Component.extend({
|
||||
didReceiveAttrs() {
|
||||
this._super(...arguments);
|
||||
|
||||
Ember.run.debounce(this, this._scheduleChartRendering, 100);
|
||||
debounce(this, this._scheduleChartRendering, 100);
|
||||
},
|
||||
|
||||
_scheduleChartRendering() {
|
||||
Ember.run.schedule("afterRender", () => {
|
||||
this._renderChart(this.model, this.$(".chart-canvas"));
|
||||
schedule("afterRender", () => {
|
||||
if (!this.element) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._renderChart(
|
||||
this.model,
|
||||
this.element.querySelector(".chart-canvas")
|
||||
);
|
||||
});
|
||||
},
|
||||
|
||||
_renderChart(model, $chartCanvas) {
|
||||
if (!$chartCanvas || !$chartCanvas.length) return;
|
||||
_renderChart(model, chartCanvas) {
|
||||
if (!chartCanvas) return;
|
||||
|
||||
const context = $chartCanvas[0].getContext("2d");
|
||||
const context = chartCanvas.getContext("2d");
|
||||
|
||||
const chartData = Ember.makeArray(
|
||||
model.get("chartData") || model.get("data")
|
||||
);
|
||||
const chartData = makeArray(model.get("chartData") || model.get("data"));
|
||||
|
||||
const data = {
|
||||
labels: chartData[0].data.map(cd => cd.x),
|
||||
labels: chartData[0].data.mapBy("x"),
|
||||
datasets: chartData.map(cd => {
|
||||
return {
|
||||
label: cd.label,
|
||||
@ -60,6 +69,7 @@ export default Ember.Component.extend({
|
||||
|
||||
loadScript("/javascripts/Chart.min.js").then(() => {
|
||||
this._resetChart();
|
||||
|
||||
this._chart = new window.Chart(context, this._buildChartConfig(data));
|
||||
});
|
||||
},
|
||||
@ -71,7 +81,11 @@ export default Ember.Component.extend({
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
responsiveAnimationDuration: 0,
|
||||
hover: { mode: "index" },
|
||||
animation: {
|
||||
duration: 0
|
||||
},
|
||||
tooltips: {
|
||||
mode: "index",
|
||||
intersect: false,
|
||||
@ -87,7 +101,6 @@ export default Ember.Component.extend({
|
||||
moment(tooltipItem[0].xLabel, "YYYY-MM-DD").format("LL")
|
||||
}
|
||||
},
|
||||
legend: { display: false },
|
||||
layout: {
|
||||
padding: {
|
||||
left: 0,
|
||||
@ -105,7 +118,10 @@ export default Ember.Component.extend({
|
||||
userCallback: label => {
|
||||
if (Math.floor(label) === label) return label;
|
||||
},
|
||||
callback: label => number(label)
|
||||
callback: label => number(label),
|
||||
sampleSize: 5,
|
||||
maxRotation: 25,
|
||||
minRotation: 25
|
||||
}
|
||||
}
|
||||
],
|
||||
@ -118,6 +134,11 @@ export default Ember.Component.extend({
|
||||
time: {
|
||||
parser: "YYYY-MM-DD",
|
||||
minUnit: "day"
|
||||
},
|
||||
ticks: {
|
||||
sampleSize: 5,
|
||||
maxRotation: 50,
|
||||
minRotation: 50
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@ -1,39 +1,41 @@
|
||||
import discourseComputed from "discourse-common/utils/decorators";
|
||||
import { alias } from "@ember/object/computed";
|
||||
import Component from "@ember/component";
|
||||
import { setting } from "discourse/lib/computed";
|
||||
import computed from "ember-addons/ember-computed-decorators";
|
||||
|
||||
export default Ember.Component.extend({
|
||||
export default Component.extend({
|
||||
classNames: ["admin-report-storage-stats"],
|
||||
|
||||
backupLocation: setting("backup_location"),
|
||||
backupStats: Ember.computed.alias("model.data.backups"),
|
||||
uploadStats: Ember.computed.alias("model.data.uploads"),
|
||||
backupStats: alias("model.data.backups"),
|
||||
uploadStats: alias("model.data.uploads"),
|
||||
|
||||
@computed("backupStats")
|
||||
@discourseComputed("backupStats")
|
||||
showBackupStats(stats) {
|
||||
return stats && this.currentUser.admin;
|
||||
},
|
||||
|
||||
@computed("backupLocation")
|
||||
@discourseComputed("backupLocation")
|
||||
backupLocationName(backupLocation) {
|
||||
return I18n.t(`admin.backups.location.${backupLocation}`);
|
||||
},
|
||||
|
||||
@computed("backupStats.used_bytes")
|
||||
@discourseComputed("backupStats.used_bytes")
|
||||
usedBackupSpace(bytes) {
|
||||
return I18n.toHumanSize(bytes);
|
||||
},
|
||||
|
||||
@computed("backupStats.free_bytes")
|
||||
@discourseComputed("backupStats.free_bytes")
|
||||
freeBackupSpace(bytes) {
|
||||
return I18n.toHumanSize(bytes);
|
||||
},
|
||||
|
||||
@computed("uploadStats.used_bytes")
|
||||
@discourseComputed("uploadStats.used_bytes")
|
||||
usedUploadSpace(bytes) {
|
||||
return I18n.toHumanSize(bytes);
|
||||
},
|
||||
|
||||
@computed("uploadStats.free_bytes")
|
||||
@discourseComputed("uploadStats.free_bytes")
|
||||
freeUploadSpace(bytes) {
|
||||
return I18n.toHumanSize(bytes);
|
||||
}
|
||||
|
||||
@ -1,18 +1,20 @@
|
||||
import computed from "ember-addons/ember-computed-decorators";
|
||||
import discourseComputed from "discourse-common/utils/decorators";
|
||||
import { alias } from "@ember/object/computed";
|
||||
import Component from "@ember/component";
|
||||
|
||||
export default Ember.Component.extend({
|
||||
export default Component.extend({
|
||||
tagName: "td",
|
||||
classNames: ["admin-report-table-cell"],
|
||||
classNameBindings: ["type", "property"],
|
||||
options: null,
|
||||
|
||||
@computed("label", "data", "options")
|
||||
@discourseComputed("label", "data", "options")
|
||||
computedLabel(label, data, options) {
|
||||
return label.compute(data, options || {});
|
||||
},
|
||||
|
||||
type: Ember.computed.alias("label.type"),
|
||||
property: Ember.computed.alias("label.mainProperty"),
|
||||
formatedValue: Ember.computed.alias("computedLabel.formatedValue"),
|
||||
value: Ember.computed.alias("computedLabel.value")
|
||||
type: alias("label.type"),
|
||||
property: alias("label.mainProperty"),
|
||||
formatedValue: alias("computedLabel.formatedValue"),
|
||||
value: alias("computedLabel.value")
|
||||
});
|
||||
|
||||
@ -1,17 +1,18 @@
|
||||
import computed from "ember-addons/ember-computed-decorators";
|
||||
import discourseComputed from "discourse-common/utils/decorators";
|
||||
import Component from "@ember/component";
|
||||
|
||||
export default Ember.Component.extend({
|
||||
export default Component.extend({
|
||||
tagName: "th",
|
||||
classNames: ["admin-report-table-header"],
|
||||
classNameBindings: ["label.mainProperty", "label.type", "isCurrentSort"],
|
||||
attributeBindings: ["label.title:title"],
|
||||
|
||||
@computed("currentSortLabel.sortProperty", "label.sortProperty")
|
||||
@discourseComputed("currentSortLabel.sortProperty", "label.sortProperty")
|
||||
isCurrentSort(currentSortField, labelSortField) {
|
||||
return currentSortField === labelSortField;
|
||||
},
|
||||
|
||||
@computed("currentSortDirection")
|
||||
@discourseComputed("currentSortDirection")
|
||||
sortIcon(currentSortDirection) {
|
||||
return currentSortDirection === 1 ? "caret-up" : "caret-down";
|
||||
}
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
export default Ember.Component.extend({
|
||||
import Component from "@ember/component";
|
||||
export default Component.extend({
|
||||
tagName: "tr",
|
||||
classNames: ["admin-report-table-row"],
|
||||
options: null
|
||||
|
||||
@ -1,21 +1,28 @@
|
||||
import computed from "ember-addons/ember-computed-decorators";
|
||||
import discourseComputed from "discourse-common/utils/decorators";
|
||||
import { makeArray } from "discourse-common/lib/helpers";
|
||||
import { alias } from "@ember/object/computed";
|
||||
import Component from "@ember/component";
|
||||
|
||||
const PAGES_LIMIT = 8;
|
||||
|
||||
export default Ember.Component.extend({
|
||||
export default Component.extend({
|
||||
classNameBindings: ["sortable", "twoColumns"],
|
||||
classNames: ["admin-report-table"],
|
||||
sortable: false,
|
||||
sortDirection: 1,
|
||||
perPage: Ember.computed.alias("options.perPage"),
|
||||
perPage: alias("options.perPage"),
|
||||
page: 0,
|
||||
|
||||
@computed("model.computedLabels.length")
|
||||
@discourseComputed("model.computedLabels.length")
|
||||
twoColumns(labelsLength) {
|
||||
return labelsLength === 2;
|
||||
},
|
||||
|
||||
@computed("totalsForSample", "options.total", "model.dates_filtering")
|
||||
@discourseComputed(
|
||||
"totalsForSample",
|
||||
"options.total",
|
||||
"model.dates_filtering"
|
||||
)
|
||||
showTotalForSample(totalsForSample, total, datesFiltering) {
|
||||
// check if we have at least one cell which contains a value
|
||||
const sum = totalsForSample
|
||||
@ -26,12 +33,16 @@ export default Ember.Component.extend({
|
||||
return sum >= 1 && total && datesFiltering;
|
||||
},
|
||||
|
||||
@computed("model.total", "options.total", "twoColumns")
|
||||
@discourseComputed("model.total", "options.total", "twoColumns")
|
||||
showTotal(reportTotal, total, twoColumns) {
|
||||
return reportTotal && total && twoColumns;
|
||||
},
|
||||
|
||||
@computed("model.{average,data}", "totalsForSample.1.value", "twoColumns")
|
||||
@discourseComputed(
|
||||
"model.{average,data}",
|
||||
"totalsForSample.1.value",
|
||||
"twoColumns"
|
||||
)
|
||||
showAverage(model, sampleTotalValue, hasTwoColumns) {
|
||||
return (
|
||||
model.average &&
|
||||
@ -41,17 +52,17 @@ export default Ember.Component.extend({
|
||||
);
|
||||
},
|
||||
|
||||
@computed("totalsForSample.1.value", "model.data.length")
|
||||
@discourseComputed("totalsForSample.1.value", "model.data.length")
|
||||
averageForSample(totals, count) {
|
||||
return (totals / count).toFixed(0);
|
||||
},
|
||||
|
||||
@computed("model.data.length")
|
||||
@discourseComputed("model.data.length")
|
||||
showSortingUI(dataLength) {
|
||||
return dataLength >= 5;
|
||||
},
|
||||
|
||||
@computed("totalsForSampleRow", "model.computedLabels")
|
||||
@discourseComputed("totalsForSampleRow", "model.computedLabels")
|
||||
totalsForSample(row, labels) {
|
||||
return labels.map(label => {
|
||||
const computedLabel = label.compute(row);
|
||||
@ -61,7 +72,7 @@ export default Ember.Component.extend({
|
||||
});
|
||||
},
|
||||
|
||||
@computed("model.data", "model.computedLabels")
|
||||
@discourseComputed("model.data", "model.computedLabels")
|
||||
totalsForSampleRow(rows, labels) {
|
||||
if (!rows || !rows.length) return {};
|
||||
|
||||
@ -87,9 +98,9 @@ export default Ember.Component.extend({
|
||||
return totalsRow;
|
||||
},
|
||||
|
||||
@computed("sortLabel", "sortDirection", "model.data.[]")
|
||||
@discourseComputed("sortLabel", "sortDirection", "model.data.[]")
|
||||
sortedData(sortLabel, sortDirection, data) {
|
||||
data = Ember.makeArray(data);
|
||||
data = makeArray(data);
|
||||
|
||||
if (sortLabel) {
|
||||
const compare = (label, direction) => {
|
||||
@ -107,7 +118,7 @@ export default Ember.Component.extend({
|
||||
return data;
|
||||
},
|
||||
|
||||
@computed("sortedData.[]", "perPage", "page")
|
||||
@discourseComputed("sortedData.[]", "perPage", "page")
|
||||
paginatedData(data, perPage, page) {
|
||||
if (perPage < data.length) {
|
||||
const start = perPage * page;
|
||||
@ -117,7 +128,7 @@ export default Ember.Component.extend({
|
||||
return data;
|
||||
},
|
||||
|
||||
@computed("model.data", "perPage", "page")
|
||||
@discourseComputed("model.data", "perPage", "page")
|
||||
pages(data, perPage, page) {
|
||||
if (!data || data.length <= perPage) return [];
|
||||
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
export default Ember.Component.extend({
|
||||
import Component from "@ember/component";
|
||||
export default Component.extend({
|
||||
tagName: "tr"
|
||||
});
|
||||
|
||||
@ -1,9 +1,15 @@
|
||||
import discourseComputed from "discourse-common/utils/decorators";
|
||||
import { makeArray } from "discourse-common/lib/helpers";
|
||||
import { alias, or, and, reads, equal, notEmpty } from "@ember/object/computed";
|
||||
import EmberObject from "@ember/object";
|
||||
import { next } from "@ember/runloop";
|
||||
import Component from "@ember/component";
|
||||
import ReportLoader from "discourse/lib/reports-loader";
|
||||
import { exportEntity } from "discourse/lib/export-csv";
|
||||
import { outputExportResult } from "discourse/lib/export-result";
|
||||
import { isNumeric } from "discourse/lib/utilities";
|
||||
import { SCHEMA_VERSION, default as Report } from "admin/models/report";
|
||||
import computed from "ember-addons/ember-computed-decorators";
|
||||
import Report, { SCHEMA_VERSION } from "admin/models/report";
|
||||
import ENV from "discourse-common/config/environment";
|
||||
|
||||
const TABLE_OPTIONS = {
|
||||
perPage: 8,
|
||||
@ -34,7 +40,7 @@ function collapseWeekly(data, average) {
|
||||
return aggregate;
|
||||
}
|
||||
|
||||
export default Ember.Component.extend({
|
||||
export default Component.extend({
|
||||
classNameBindings: ["isEnabled", "isLoading", "dasherizedDataSourceName"],
|
||||
classNames: ["admin-report"],
|
||||
isEnabled: true,
|
||||
@ -54,13 +60,9 @@ export default Ember.Component.extend({
|
||||
showHeader: true,
|
||||
showTitle: true,
|
||||
showFilteringUI: false,
|
||||
showDatesOptions: Ember.computed.alias("model.dates_filtering"),
|
||||
showExport: Ember.computed.not("model.onlyTable"),
|
||||
showRefresh: Ember.computed.or(
|
||||
"showDatesOptions",
|
||||
"model.available_filters.length"
|
||||
),
|
||||
shouldDisplayTrend: Ember.computed.and("showTrend", "model.prev_period"),
|
||||
showDatesOptions: alias("model.dates_filtering"),
|
||||
showRefresh: or("showDatesOptions", "model.available_filters.length"),
|
||||
shouldDisplayTrend: and("showTrend", "model.prev_period"),
|
||||
|
||||
init() {
|
||||
this._super(...arguments);
|
||||
@ -68,8 +70,8 @@ export default Ember.Component.extend({
|
||||
this._reports = [];
|
||||
},
|
||||
|
||||
startDate: Ember.computed.reads("filters.startDate"),
|
||||
endDate: Ember.computed.reads("filters.endDate"),
|
||||
startDate: reads("filters.startDate"),
|
||||
endDate: reads("filters.endDate"),
|
||||
|
||||
didReceiveAttrs() {
|
||||
this._super(...arguments);
|
||||
@ -81,38 +83,34 @@ export default Ember.Component.extend({
|
||||
}
|
||||
},
|
||||
|
||||
showError: Ember.computed.or(
|
||||
"showTimeoutError",
|
||||
"showExceptionError",
|
||||
"showNotFoundError"
|
||||
),
|
||||
showNotFoundError: Ember.computed.equal("model.error", "not_found"),
|
||||
showTimeoutError: Ember.computed.equal("model.error", "timeout"),
|
||||
showExceptionError: Ember.computed.equal("model.error", "exception"),
|
||||
showError: or("showTimeoutError", "showExceptionError", "showNotFoundError"),
|
||||
showNotFoundError: equal("model.error", "not_found"),
|
||||
showTimeoutError: equal("model.error", "timeout"),
|
||||
showExceptionError: equal("model.error", "exception"),
|
||||
|
||||
hasData: Ember.computed.notEmpty("model.data"),
|
||||
hasData: notEmpty("model.data"),
|
||||
|
||||
@computed("dataSourceName", "model.type")
|
||||
@discourseComputed("dataSourceName", "model.type")
|
||||
dasherizedDataSourceName(dataSourceName, type) {
|
||||
return (dataSourceName || type || "undefined").replace(/_/g, "-");
|
||||
},
|
||||
|
||||
@computed("dataSourceName", "model.type")
|
||||
@discourseComputed("dataSourceName", "model.type")
|
||||
dataSource(dataSourceName, type) {
|
||||
dataSourceName = dataSourceName || type;
|
||||
return `/admin/reports/${dataSourceName}`;
|
||||
},
|
||||
|
||||
@computed("displayedModes.length")
|
||||
@discourseComputed("displayedModes.length")
|
||||
showModes(displayedModesLength) {
|
||||
return displayedModesLength > 1;
|
||||
},
|
||||
|
||||
@computed("currentMode", "model.modes", "forcedModes")
|
||||
@discourseComputed("currentMode", "model.modes", "forcedModes")
|
||||
displayedModes(currentMode, reportModes, forcedModes) {
|
||||
const modes = forcedModes ? forcedModes.split(",") : reportModes;
|
||||
|
||||
return Ember.makeArray(modes).map(mode => {
|
||||
return makeArray(modes).map(mode => {
|
||||
const base = `btn-default mode-btn ${mode}`;
|
||||
const cssClass = currentMode === mode ? `${base} is-current` : base;
|
||||
|
||||
@ -124,12 +122,12 @@ export default Ember.Component.extend({
|
||||
});
|
||||
},
|
||||
|
||||
@computed("currentMode")
|
||||
@discourseComputed("currentMode")
|
||||
modeComponent(currentMode) {
|
||||
return `admin-report-${currentMode}`;
|
||||
return `admin-report-${currentMode.replace(/_/g, "-")}`;
|
||||
},
|
||||
|
||||
@computed("startDate")
|
||||
@discourseComputed("startDate")
|
||||
normalizedStartDate(startDate) {
|
||||
return startDate && typeof startDate.isValid === "function"
|
||||
? moment
|
||||
@ -141,7 +139,7 @@ export default Ember.Component.extend({
|
||||
.format("YYYYMMDD");
|
||||
},
|
||||
|
||||
@computed("endDate")
|
||||
@discourseComputed("endDate")
|
||||
normalizedEndDate(endDate) {
|
||||
return endDate && typeof endDate.isValid === "function"
|
||||
? moment
|
||||
@ -153,7 +151,7 @@ export default Ember.Component.extend({
|
||||
.format("YYYYMMDD");
|
||||
},
|
||||
|
||||
@computed(
|
||||
@discourseComputed(
|
||||
"dataSourceName",
|
||||
"normalizedStartDate",
|
||||
"normalizedEndDate",
|
||||
@ -165,8 +163,8 @@ export default Ember.Component.extend({
|
||||
let reportKey = "reports:";
|
||||
reportKey += [
|
||||
dataSourceName,
|
||||
startDate.replace(/-/g, ""),
|
||||
endDate.replace(/-/g, ""),
|
||||
ENV.environment === "test" ? "start" : startDate.replace(/-/g, ""),
|
||||
ENV.environment === "test" ? "end" : endDate.replace(/-/g, ""),
|
||||
"[:prev_period]",
|
||||
this.get("reportOptions.table.limit"),
|
||||
customFilters
|
||||
@ -184,6 +182,32 @@ export default Ember.Component.extend({
|
||||
},
|
||||
|
||||
actions: {
|
||||
onChangeEndDate(date) {
|
||||
const startDate = moment(this.startDate);
|
||||
const newEndDate = moment(date).endOf("day");
|
||||
|
||||
if (newEndDate.isSameOrAfter(startDate)) {
|
||||
this.set("endDate", newEndDate.format("YYYY-MM-DD"));
|
||||
} else {
|
||||
this.set("endDate", startDate.endOf("day").format("YYYY-MM-DD"));
|
||||
}
|
||||
|
||||
this.send("refreshReport");
|
||||
},
|
||||
|
||||
onChangeStartDate(date) {
|
||||
const endDate = moment(this.endDate);
|
||||
const newStartDate = moment(date).startOf("day");
|
||||
|
||||
if (newStartDate.isSameOrBefore(endDate)) {
|
||||
this.set("startDate", newStartDate.format("YYYY-MM-DD"));
|
||||
} else {
|
||||
this.set("startDate", endDate.startOf("day").format("YYYY-MM-DD"));
|
||||
}
|
||||
|
||||
this.send("refreshReport");
|
||||
},
|
||||
|
||||
applyFilter(id, value) {
|
||||
let customFilters = this.get("filters.customFilters") || {};
|
||||
|
||||
@ -286,7 +310,7 @@ export default Ember.Component.extend({
|
||||
|
||||
this.setProperties({ isLoading: true, rateLimitationString: null });
|
||||
|
||||
Ember.run.next(() => {
|
||||
next(() => {
|
||||
let payload = this._buildPayload(["prev_period"]);
|
||||
|
||||
const callback = response => {
|
||||
@ -342,12 +366,12 @@ export default Ember.Component.extend({
|
||||
_buildOptions(mode) {
|
||||
if (mode === "table") {
|
||||
const tableOptions = JSON.parse(JSON.stringify(TABLE_OPTIONS));
|
||||
return Ember.Object.create(
|
||||
return EmberObject.create(
|
||||
Object.assign(tableOptions, this.get("reportOptions.table") || {})
|
||||
);
|
||||
} else {
|
||||
const chartOptions = JSON.parse(JSON.stringify(CHART_OPTIONS));
|
||||
return Ember.Object.create(
|
||||
return EmberObject.create(
|
||||
Object.assign(chartOptions, this.get("reportOptions.chart") || {})
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,8 +1,10 @@
|
||||
import { default as computed } from "ember-addons/ember-computed-decorators";
|
||||
import { next } from "@ember/runloop";
|
||||
import Component from "@ember/component";
|
||||
import discourseComputed from "discourse-common/utils/decorators";
|
||||
import { fmt } from "discourse/lib/computed";
|
||||
|
||||
export default Ember.Component.extend({
|
||||
@computed("theme.targets", "onlyOverridden", "showAdvanced")
|
||||
export default Component.extend({
|
||||
@discourseComputed("theme.targets", "onlyOverridden", "showAdvanced")
|
||||
visibleTargets(targets, onlyOverridden, showAdvanced) {
|
||||
return targets.filter(target => {
|
||||
if (target.advanced && !showAdvanced) {
|
||||
@ -15,7 +17,7 @@ export default Ember.Component.extend({
|
||||
});
|
||||
},
|
||||
|
||||
@computed("currentTargetName", "onlyOverridden", "theme.fields")
|
||||
@discourseComputed("currentTargetName", "onlyOverridden", "theme.fields")
|
||||
visibleFields(targetName, onlyOverridden, fields) {
|
||||
fields = fields[targetName];
|
||||
if (onlyOverridden) {
|
||||
@ -24,14 +26,14 @@ export default Ember.Component.extend({
|
||||
return fields;
|
||||
},
|
||||
|
||||
@computed("currentTargetName", "fieldName")
|
||||
@discourseComputed("currentTargetName", "fieldName")
|
||||
activeSectionMode(targetName, fieldName) {
|
||||
if (["settings", "translations"].includes(targetName)) return "yaml";
|
||||
if (["extra_scss"].includes(targetName)) return "scss";
|
||||
return fieldName && fieldName.indexOf("scss") > -1 ? "scss" : "html";
|
||||
},
|
||||
|
||||
@computed("fieldName", "currentTargetName", "theme")
|
||||
@discourseComputed("fieldName", "currentTargetName", "theme")
|
||||
activeSection: {
|
||||
get(fieldName, target, model) {
|
||||
return model.getField(target, fieldName);
|
||||
@ -44,17 +46,21 @@ export default Ember.Component.extend({
|
||||
|
||||
editorId: fmt("fieldName", "currentTargetName", "%@|%@"),
|
||||
|
||||
@computed("maximized")
|
||||
@discourseComputed("maximized")
|
||||
maximizeIcon(maximized) {
|
||||
return maximized ? "discourse-compress" : "discourse-expand";
|
||||
},
|
||||
|
||||
@computed("currentTargetName", "theme.targets")
|
||||
@discourseComputed("currentTargetName", "theme.targets")
|
||||
showAddField(currentTargetName, targets) {
|
||||
return targets.find(t => t.name === currentTargetName).customNames;
|
||||
},
|
||||
|
||||
@computed("currentTargetName", "fieldName", "theme.theme_fields.@each.error")
|
||||
@discourseComputed(
|
||||
"currentTargetName",
|
||||
"fieldName",
|
||||
"theme.theme_fields.@each.error"
|
||||
)
|
||||
error(target, fieldName) {
|
||||
return this.theme.getError(target, fieldName);
|
||||
},
|
||||
@ -82,7 +88,7 @@ export default Ember.Component.extend({
|
||||
|
||||
toggleMaximize: function() {
|
||||
this.toggleProperty("maximized");
|
||||
Ember.run.next(() => this.appEvents.trigger("ace:resize"));
|
||||
next(() => this.appEvents.trigger("ace:resize"));
|
||||
},
|
||||
|
||||
onlyOverriddenChanged(value) {
|
||||
|
||||
@ -1,16 +1,19 @@
|
||||
import { isEmpty } from "@ember/utils";
|
||||
import { empty } from "@ember/object/computed";
|
||||
import { scheduleOnce } from "@ember/runloop";
|
||||
import Component from "@ember/component";
|
||||
import UserField from "admin/models/user-field";
|
||||
import { bufferedProperty } from "discourse/mixins/buffered-content";
|
||||
import { popupAjaxError } from "discourse/lib/ajax-error";
|
||||
import { propertyEqual } from "discourse/lib/computed";
|
||||
import { i18n } from "discourse/lib/computed";
|
||||
import {
|
||||
default as computed,
|
||||
import discourseComputed, {
|
||||
observes,
|
||||
on
|
||||
} from "ember-addons/ember-computed-decorators";
|
||||
} from "discourse-common/utils/decorators";
|
||||
|
||||
export default Ember.Component.extend(bufferedProperty("userField"), {
|
||||
editing: Ember.computed.empty("userField.id"),
|
||||
export default Component.extend(bufferedProperty("userField"), {
|
||||
editing: empty("userField.id"),
|
||||
classNameBindings: [":user-field"],
|
||||
|
||||
cantMoveUp: propertyEqual("userField", "firstField"),
|
||||
@ -18,7 +21,7 @@ export default Ember.Component.extend(bufferedProperty("userField"), {
|
||||
|
||||
userFieldsDescription: i18n("admin.user_fields.description"),
|
||||
|
||||
@computed("buffered.field_type")
|
||||
@discourseComputed("buffered.field_type")
|
||||
bufferedFieldType(fieldType) {
|
||||
return UserField.fieldTypeById(fieldType);
|
||||
},
|
||||
@ -27,7 +30,7 @@ export default Ember.Component.extend(bufferedProperty("userField"), {
|
||||
@observes("editing")
|
||||
_focusOnEdit() {
|
||||
if (this.editing) {
|
||||
Ember.run.scheduleOnce("afterRender", this, "_focusName");
|
||||
scheduleOnce("afterRender", this, "_focusName");
|
||||
}
|
||||
},
|
||||
|
||||
@ -35,12 +38,12 @@ export default Ember.Component.extend(bufferedProperty("userField"), {
|
||||
$(".user-field-name").select();
|
||||
},
|
||||
|
||||
@computed("userField.field_type")
|
||||
@discourseComputed("userField.field_type")
|
||||
fieldName(fieldType) {
|
||||
return UserField.fieldTypeById(fieldType).get("name");
|
||||
},
|
||||
|
||||
@computed(
|
||||
@discourseComputed(
|
||||
"userField.editable",
|
||||
"userField.required",
|
||||
"userField.show_on_profile",
|
||||
@ -93,7 +96,7 @@ export default Ember.Component.extend(bufferedProperty("userField"), {
|
||||
|
||||
cancel() {
|
||||
const id = this.get("userField.id");
|
||||
if (Ember.isEmpty(id)) {
|
||||
if (isEmpty(id)) {
|
||||
this.destroyAction(this.userField);
|
||||
} else {
|
||||
this.rollbackBuffer();
|
||||
|
||||
@ -1,29 +1,28 @@
|
||||
import Component from "@ember/component";
|
||||
import { iconHTML } from "discourse-common/lib/icon-library";
|
||||
import { bufferedRender } from "discourse-common/lib/buffered-render";
|
||||
import { escapeExpression } from "discourse/lib/utilities";
|
||||
|
||||
export default Ember.Component.extend(
|
||||
bufferedRender({
|
||||
classNames: ["watched-word"],
|
||||
export default Component.extend({
|
||||
classNames: ["watched-word"],
|
||||
watchedWord: null,
|
||||
xIcon: iconHTML("times").htmlSafe(),
|
||||
|
||||
buildBuffer(buffer) {
|
||||
buffer.push(iconHTML("times"));
|
||||
buffer.push(` ${escapeExpression(this.get("word.word"))}`);
|
||||
},
|
||||
init() {
|
||||
this._super(...arguments);
|
||||
this.set("watchedWord", this.get("word.word"));
|
||||
},
|
||||
|
||||
click() {
|
||||
this.word
|
||||
.destroy()
|
||||
.then(() => {
|
||||
this.action(this.word);
|
||||
})
|
||||
.catch(e => {
|
||||
bootbox.alert(
|
||||
I18n.t("generic_error_with_reason", {
|
||||
error: `http: ${e.status} - ${e.body}`
|
||||
})
|
||||
);
|
||||
});
|
||||
}
|
||||
})
|
||||
);
|
||||
click() {
|
||||
this.word
|
||||
.destroy()
|
||||
.then(() => {
|
||||
this.action(this.word);
|
||||
})
|
||||
.catch(e => {
|
||||
bootbox.alert(
|
||||
I18n.t("generic_error_with_reason", {
|
||||
error: `http: ${e.status} - ${e.body}`
|
||||
})
|
||||
);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@ -1,25 +1,27 @@
|
||||
import computed from "ember-addons/ember-computed-decorators";
|
||||
import discourseComputed from "discourse-common/utils/decorators";
|
||||
import { alias } from "@ember/object/computed";
|
||||
import Component from "@ember/component";
|
||||
|
||||
export default Ember.Component.extend({
|
||||
export default Component.extend({
|
||||
classNames: ["hook-event"],
|
||||
typeName: Ember.computed.alias("type.name"),
|
||||
typeName: alias("type.name"),
|
||||
|
||||
@computed("typeName")
|
||||
@discourseComputed("typeName")
|
||||
name(typeName) {
|
||||
return I18n.t(`admin.web_hooks.${typeName}_event.name`);
|
||||
},
|
||||
|
||||
@computed("typeName")
|
||||
@discourseComputed("typeName")
|
||||
details(typeName) {
|
||||
return I18n.t(`admin.web_hooks.${typeName}_event.details`);
|
||||
},
|
||||
|
||||
@computed("model.[]", "typeName")
|
||||
@discourseComputed("model.[]", "typeName")
|
||||
eventTypeExists(eventTypes, typeName) {
|
||||
return eventTypes.any(event => event.name === typeName);
|
||||
},
|
||||
|
||||
@computed("eventTypeExists")
|
||||
@discourseComputed("eventTypeExists")
|
||||
enabled: {
|
||||
get(eventTypeExists) {
|
||||
return eventTypeExists;
|
||||
|
||||
@ -1,15 +1,16 @@
|
||||
import computed from "ember-addons/ember-computed-decorators";
|
||||
import discourseComputed from "discourse-common/utils/decorators";
|
||||
import Component from "@ember/component";
|
||||
import { ajax } from "discourse/lib/ajax";
|
||||
import { popupAjaxError } from "discourse/lib/ajax-error";
|
||||
import { ensureJSON, plainJSON, prettyJSON } from "discourse/lib/formatter";
|
||||
|
||||
export default Ember.Component.extend({
|
||||
export default Component.extend({
|
||||
tagName: "li",
|
||||
expandDetails: null,
|
||||
expandDetailsRequestKey: "request",
|
||||
expandDetailsResponseKey: "response",
|
||||
|
||||
@computed("model.status")
|
||||
@discourseComputed("model.status")
|
||||
statusColorClasses(status) {
|
||||
if (!status) return "";
|
||||
|
||||
@ -20,25 +21,25 @@ export default Ember.Component.extend({
|
||||
}
|
||||
},
|
||||
|
||||
@computed("model.created_at")
|
||||
@discourseComputed("model.created_at")
|
||||
createdAt(createdAt) {
|
||||
return moment(createdAt).format("YYYY-MM-DD HH:mm:ss");
|
||||
},
|
||||
|
||||
@computed("model.duration")
|
||||
@discourseComputed("model.duration")
|
||||
completion(duration) {
|
||||
const seconds = Math.floor(duration / 10.0) / 100.0;
|
||||
return I18n.t("admin.web_hooks.events.completed_in", { count: seconds });
|
||||
},
|
||||
|
||||
@computed("expandDetails")
|
||||
@discourseComputed("expandDetails")
|
||||
expandRequestIcon(expandDetails) {
|
||||
return expandDetails === this.expandDetailsRequestKey
|
||||
? "ellipsis-h"
|
||||
: "ellipsis-v";
|
||||
},
|
||||
|
||||
@computed("expandDetails")
|
||||
@discourseComputed("expandDetails")
|
||||
expandResponseIcon(expandDetails) {
|
||||
return expandDetails === this.expandDetailsResponseKey
|
||||
? "ellipsis-h"
|
||||
|
||||
@ -1,32 +1,37 @@
|
||||
import computed from "ember-addons/ember-computed-decorators";
|
||||
import discourseComputed from "discourse-common/utils/decorators";
|
||||
import Component from "@ember/component";
|
||||
import { iconHTML } from "discourse-common/lib/icon-library";
|
||||
import { bufferedRender } from "discourse-common/lib/buffered-render";
|
||||
|
||||
export default Ember.Component.extend(
|
||||
bufferedRender({
|
||||
classes: ["text-muted", "text-danger", "text-successful", "text-muted"],
|
||||
icons: ["circle-o", "times-circle", "circle", "circle"],
|
||||
export default Component.extend({
|
||||
classes: ["text-muted", "text-danger", "text-successful", "text-muted"],
|
||||
icons: ["far-circle", "times-circle", "circle", "circle"],
|
||||
circleIcon: null,
|
||||
deliveryStatus: null,
|
||||
|
||||
@computed("deliveryStatuses", "model.last_delivery_status")
|
||||
status(deliveryStatuses, lastDeliveryStatus) {
|
||||
return deliveryStatuses.find(s => s.id === lastDeliveryStatus);
|
||||
},
|
||||
@discourseComputed("deliveryStatuses", "model.last_delivery_status")
|
||||
status(deliveryStatuses, lastDeliveryStatus) {
|
||||
return deliveryStatuses.find(s => s.id === lastDeliveryStatus);
|
||||
},
|
||||
|
||||
@computed("status.id", "icons")
|
||||
icon(statusId, icons) {
|
||||
return icons[statusId - 1];
|
||||
},
|
||||
@discourseComputed("status.id", "icons")
|
||||
icon(statusId, icons) {
|
||||
return icons[statusId - 1];
|
||||
},
|
||||
|
||||
@computed("status.id", "classes")
|
||||
class(statusId, classes) {
|
||||
return classes[statusId - 1];
|
||||
},
|
||||
@discourseComputed("status.id", "classes")
|
||||
class(statusId, classes) {
|
||||
return classes[statusId - 1];
|
||||
},
|
||||
|
||||
buildBuffer(buffer) {
|
||||
buffer.push(iconHTML(this.icon, { class: this.class }));
|
||||
buffer.push(
|
||||
I18n.t(`admin.web_hooks.delivery_status.${this.get("status.name")}`)
|
||||
);
|
||||
}
|
||||
})
|
||||
);
|
||||
didReceiveAttrs() {
|
||||
this._super(...arguments);
|
||||
this.set(
|
||||
"circleIcon",
|
||||
iconHTML(this.icon, { class: this.class }).htmlSafe()
|
||||
);
|
||||
this.set(
|
||||
"deliveryStatus",
|
||||
I18n.t(`admin.web_hooks.delivery_status.${this.get("status.name")}`)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
export default Ember.Component.extend({
|
||||
import Component from "@ember/component";
|
||||
export default Component.extend({
|
||||
didInsertElement() {
|
||||
this._super(...arguments);
|
||||
$("body").addClass("admin-interface");
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
export default Ember.Component.extend({
|
||||
import Component from "@ember/component";
|
||||
export default Component.extend({
|
||||
tagName: ""
|
||||
});
|
||||
|
||||
@ -1,4 +1,7 @@
|
||||
import { default as loadScript, loadCSS } from "discourse/lib/load-script";
|
||||
import { schedule } from "@ember/runloop";
|
||||
import Component from "@ember/component";
|
||||
import loadScript, { loadCSS } from "discourse/lib/load-script";
|
||||
import { observes } from "discourse-common/utils/decorators";
|
||||
|
||||
/**
|
||||
An input field for a color.
|
||||
@ -7,14 +10,16 @@ import { default as loadScript, loadCSS } from "discourse/lib/load-script";
|
||||
@param brightnessValue is a number from 0 to 255 representing the brightness of the color. See ColorSchemeColor.
|
||||
@params valid is a boolean indicating if the input field is a valid color.
|
||||
**/
|
||||
export default Ember.Component.extend({
|
||||
export default Component.extend({
|
||||
classNames: ["color-picker"],
|
||||
|
||||
@observes("hexValue", "brightnessValue", "valid")
|
||||
hexValueChanged: function() {
|
||||
var hex = this.hexValue;
|
||||
let $text = this.$("input.hex-input");
|
||||
let text = this.element.querySelector("input.hex-input");
|
||||
|
||||
if (this.valid) {
|
||||
$text.attr(
|
||||
text.setAttribute(
|
||||
"style",
|
||||
"color: " +
|
||||
(this.brightnessValue > 125 ? "black" : "white") +
|
||||
@ -24,18 +29,20 @@ export default Ember.Component.extend({
|
||||
);
|
||||
|
||||
if (this.pickerLoaded) {
|
||||
this.$(".picker").spectrum({ color: "#" + this.hexValue });
|
||||
$(this.element.querySelector(".picker")).spectrum({
|
||||
color: "#" + this.hexValue
|
||||
});
|
||||
}
|
||||
} else {
|
||||
$text.attr("style", "");
|
||||
text.setAttribute("style", "");
|
||||
}
|
||||
}.observes("hexValue", "brightnessValue", "valid"),
|
||||
},
|
||||
|
||||
didInsertElement() {
|
||||
loadScript("/javascripts/spectrum.js").then(() => {
|
||||
loadCSS("/javascripts/spectrum.css").then(() => {
|
||||
Ember.run.schedule("afterRender", () => {
|
||||
this.$(".picker")
|
||||
schedule("afterRender", () => {
|
||||
$(this.element.querySelector(".picker"))
|
||||
.spectrum({ color: "#" + this.hexValue })
|
||||
.on("change.spectrum", (me, color) => {
|
||||
this.set("hexValue", color.toHexString().replace("#", ""));
|
||||
@ -44,7 +51,7 @@ export default Ember.Component.extend({
|
||||
});
|
||||
});
|
||||
});
|
||||
Ember.run.schedule("afterRender", () => {
|
||||
schedule("afterRender", () => {
|
||||
this.hexValueChanged();
|
||||
});
|
||||
}
|
||||
|
||||
@ -0,0 +1,52 @@
|
||||
import discourseComputed from "discourse-common/utils/decorators";
|
||||
import { reads } from "@ember/object/computed";
|
||||
import Component from "@ember/component";
|
||||
|
||||
export default Component.extend({
|
||||
editorId: reads("fieldName"),
|
||||
|
||||
@discourseComputed("fieldName")
|
||||
currentEditorMode(fieldName) {
|
||||
return fieldName === "css" ? "scss" : fieldName;
|
||||
},
|
||||
|
||||
@discourseComputed("fieldName", "styles.html", "styles.css")
|
||||
resetDisabled(fieldName) {
|
||||
return (
|
||||
this.get(`styles.${fieldName}`) ===
|
||||
this.get(`styles.default_${fieldName}`)
|
||||
);
|
||||
},
|
||||
|
||||
@discourseComputed("styles", "fieldName")
|
||||
editorContents: {
|
||||
get(styles, fieldName) {
|
||||
return styles[fieldName];
|
||||
},
|
||||
set(value, styles, fieldName) {
|
||||
styles.setField(fieldName, value);
|
||||
return value;
|
||||
}
|
||||
},
|
||||
|
||||
actions: {
|
||||
reset() {
|
||||
bootbox.confirm(
|
||||
I18n.t("admin.customize.email_style.reset_confirm", {
|
||||
fieldName: I18n.t(`admin.customize.email_style.${this.fieldName}`)
|
||||
}),
|
||||
I18n.t("no_value"),
|
||||
I18n.t("yes_value"),
|
||||
result => {
|
||||
if (result) {
|
||||
this.styles.setField(
|
||||
this.fieldName,
|
||||
this.styles.get(`default_${this.fieldName}`)
|
||||
);
|
||||
this.notifyPropertyChange("editorContents");
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -1,26 +1,31 @@
|
||||
import discourseComputed from "discourse-common/utils/decorators";
|
||||
import { isEmpty } from "@ember/utils";
|
||||
import { or } from "@ember/object/computed";
|
||||
import { schedule } from "@ember/runloop";
|
||||
import Component from "@ember/component";
|
||||
import { bufferedProperty } from "discourse/mixins/buffered-content";
|
||||
import computed from "ember-addons/ember-computed-decorators";
|
||||
import { on, observes } from "ember-addons/ember-computed-decorators";
|
||||
import { on, observes } from "discourse-common/utils/decorators";
|
||||
import { popupAjaxError } from "discourse/lib/ajax-error";
|
||||
import Category from "discourse/models/category";
|
||||
|
||||
export default Ember.Component.extend(bufferedProperty("host"), {
|
||||
export default Component.extend(bufferedProperty("host"), {
|
||||
editToggled: false,
|
||||
tagName: "tr",
|
||||
categoryId: null,
|
||||
|
||||
editing: Ember.computed.or("host.isNew", "editToggled"),
|
||||
editing: or("host.isNew", "editToggled"),
|
||||
|
||||
@on("didInsertElement")
|
||||
@observes("editing")
|
||||
_focusOnInput() {
|
||||
Ember.run.schedule("afterRender", () => {
|
||||
this.$(".host-name").focus();
|
||||
schedule("afterRender", () => {
|
||||
this.element.querySelector(".host-name").focus();
|
||||
});
|
||||
},
|
||||
|
||||
@computed("buffered.host", "host.isSaving")
|
||||
@discourseComputed("buffered.host", "host.isSaving")
|
||||
cantSave(host, isSaving) {
|
||||
return isSaving || Ember.isEmpty(host);
|
||||
return isSaving || isEmpty(host);
|
||||
},
|
||||
|
||||
actions: {
|
||||
@ -46,7 +51,7 @@ export default Ember.Component.extend(bufferedProperty("host"), {
|
||||
host
|
||||
.save(props)
|
||||
.then(() => {
|
||||
host.set("category", Discourse.Category.findById(this.categoryId));
|
||||
host.set("category", Category.findById(this.categoryId));
|
||||
this.set("editToggled", false);
|
||||
})
|
||||
.catch(popupAjaxError);
|
||||
|
||||
@ -1,24 +1,25 @@
|
||||
import computed from "ember-addons/ember-computed-decorators";
|
||||
import discourseComputed from "discourse-common/utils/decorators";
|
||||
import Component from "@ember/component";
|
||||
|
||||
export default Ember.Component.extend({
|
||||
export default Component.extend({
|
||||
classNames: ["embed-setting"],
|
||||
|
||||
@computed("field")
|
||||
@discourseComputed("field")
|
||||
inputId(field) {
|
||||
return field.dasherize();
|
||||
},
|
||||
|
||||
@computed("field")
|
||||
@discourseComputed("field")
|
||||
translationKey(field) {
|
||||
return `admin.embedding.${field}`;
|
||||
},
|
||||
|
||||
@computed("type")
|
||||
@discourseComputed("type")
|
||||
isCheckbox(type) {
|
||||
return type === "checkbox";
|
||||
},
|
||||
|
||||
@computed("value")
|
||||
@discourseComputed("value")
|
||||
checked: {
|
||||
get(value) {
|
||||
return !!value;
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
export default Ember.Component.extend({
|
||||
import Component from "@ember/component";
|
||||
export default Component.extend({
|
||||
classNames: ["flag-user-lists"]
|
||||
});
|
||||
|
||||
@ -1,10 +1,11 @@
|
||||
import { on, observes } from "ember-addons/ember-computed-decorators";
|
||||
import Component from "@ember/component";
|
||||
import { on, observes } from "discourse-common/utils/decorators";
|
||||
import highlightSyntax from "discourse/lib/highlight-syntax";
|
||||
|
||||
export default Ember.Component.extend({
|
||||
export default Component.extend({
|
||||
@on("didInsertElement")
|
||||
@observes("code")
|
||||
_refresh: function() {
|
||||
highlightSyntax(this.$());
|
||||
highlightSyntax($(this.element));
|
||||
}
|
||||
});
|
||||
|
||||
@ -1,9 +1,7 @@
|
||||
import {
|
||||
default as computed,
|
||||
observes
|
||||
} from "ember-addons/ember-computed-decorators";
|
||||
import Component from "@ember/component";
|
||||
import discourseComputed, { observes } from "discourse-common/utils/decorators";
|
||||
|
||||
export default Ember.Component.extend({
|
||||
export default Component.extend({
|
||||
classNames: ["inline-edit"],
|
||||
|
||||
checked: null,
|
||||
@ -20,12 +18,12 @@ export default Ember.Component.extend({
|
||||
this.set("checkedInternal", this.checked);
|
||||
},
|
||||
|
||||
@computed("labelKey")
|
||||
@discourseComputed("labelKey")
|
||||
label(key) {
|
||||
return I18n.t(key);
|
||||
},
|
||||
|
||||
@computed("checked", "checkedInternal")
|
||||
@discourseComputed("checked", "checkedInternal")
|
||||
changed(checked, checkedInternal) {
|
||||
return !!checked !== !!checkedInternal;
|
||||
},
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
export default Ember.Component.extend({
|
||||
import Component from "@ember/component";
|
||||
export default Component.extend({
|
||||
classNames: ["install-theme-item"]
|
||||
});
|
||||
|
||||
@ -1,12 +1,15 @@
|
||||
import { default as computed } from "ember-addons/ember-computed-decorators";
|
||||
import EmberObject from "@ember/object";
|
||||
import { later } from "@ember/runloop";
|
||||
import Component from "@ember/component";
|
||||
import discourseComputed from "discourse-common/utils/decorators";
|
||||
import { ajax } from "discourse/lib/ajax";
|
||||
import AdminUser from "admin/models/admin-user";
|
||||
import copyText from "discourse/lib/copy-text";
|
||||
|
||||
export default Ember.Component.extend({
|
||||
export default Component.extend({
|
||||
classNames: ["ip-lookup"],
|
||||
|
||||
@computed("other_accounts.length", "totalOthersWithSameIP")
|
||||
@discourseComputed("other_accounts.length", "totalOthersWithSameIP")
|
||||
otherAccountsToDelete(otherAccountsLength, totalOthersWithSameIP) {
|
||||
// can only delete up to 50 accounts at a time
|
||||
const total = Math.min(50, totalOthersWithSameIP || 0);
|
||||
@ -20,7 +23,7 @@ export default Ember.Component.extend({
|
||||
|
||||
if (!this.location) {
|
||||
ajax("/admin/users/ip-info", { data: { ip: this.ip } }).then(location =>
|
||||
this.set("location", Ember.Object.create(location))
|
||||
this.set("location", EmberObject.create(location))
|
||||
);
|
||||
}
|
||||
|
||||
@ -76,7 +79,7 @@ export default Ember.Component.extend({
|
||||
$(document.body).append($copyRange);
|
||||
if (copyText(text, $copyRange[0])) {
|
||||
this.set("copied", true);
|
||||
Ember.run.later(() => this.set("copied", false), 2000);
|
||||
later(() => this.set("copied", false), 2000);
|
||||
}
|
||||
$copyRange.remove();
|
||||
},
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
export default Ember.Component.extend({
|
||||
import Component from "@ember/component";
|
||||
export default Component.extend({
|
||||
tagName: "tr"
|
||||
});
|
||||
|
||||
@ -1,32 +1,33 @@
|
||||
import computed from "ember-addons/ember-computed-decorators";
|
||||
import discourseComputed from "discourse-common/utils/decorators";
|
||||
import { equal } from "@ember/object/computed";
|
||||
import { scheduleOnce } from "@ember/runloop";
|
||||
import Component from "@ember/component";
|
||||
|
||||
const ACTIONS = ["delete", "delete_replies", "edit", "none"];
|
||||
|
||||
export default Ember.Component.extend({
|
||||
export default Component.extend({
|
||||
postId: null,
|
||||
postAction: null,
|
||||
postEdit: null,
|
||||
|
||||
@computed
|
||||
@discourseComputed
|
||||
penaltyActions() {
|
||||
return ACTIONS.map(id => {
|
||||
return { id, name: I18n.t(`admin.user.penalty_post_${id}`) };
|
||||
});
|
||||
},
|
||||
|
||||
editing: Ember.computed.equal("postAction", "edit"),
|
||||
editing: equal("postAction", "edit"),
|
||||
|
||||
actions: {
|
||||
penaltyChanged() {
|
||||
let postAction = this.postAction;
|
||||
|
||||
// If we switch to edit mode, jump to the edit textarea
|
||||
if (postAction === "edit") {
|
||||
Ember.run.scheduleOnce("afterRender", () => {
|
||||
let $elem = this.$();
|
||||
let body = $elem.closest(".modal-body");
|
||||
if (this.postAction === "edit") {
|
||||
scheduleOnce("afterRender", () => {
|
||||
const elem = this.element;
|
||||
const body = elem.closest(".modal-body");
|
||||
body.scrollTop(body.height());
|
||||
$elem.find(".post-editor").focus();
|
||||
elem.querySelector(".post-editor").focus();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,14 +1,16 @@
|
||||
import { default as computed } from "ember-addons/ember-computed-decorators";
|
||||
import { schedule } from "@ember/runloop";
|
||||
import Component from "@ember/component";
|
||||
import discourseComputed from "discourse-common/utils/decorators";
|
||||
import { fmt } from "discourse/lib/computed";
|
||||
import Permalink from "admin/models/permalink";
|
||||
|
||||
export default Ember.Component.extend({
|
||||
export default Component.extend({
|
||||
classNames: ["permalink-form"],
|
||||
formSubmitted: false,
|
||||
permalinkType: "topic_id",
|
||||
permalinkTypePlaceholder: fmt("permalinkType", "admin.permalink.%@"),
|
||||
|
||||
@computed
|
||||
@discourseComputed
|
||||
permalinkTypes() {
|
||||
return [
|
||||
{ id: "topic_id", name: I18n.t("admin.permalink.topic_id") },
|
||||
@ -18,8 +20,23 @@ export default Ember.Component.extend({
|
||||
];
|
||||
},
|
||||
|
||||
didInsertElement() {
|
||||
this._super(...arguments);
|
||||
|
||||
schedule("afterRender", () => {
|
||||
$(this.element.querySelector(".external-url")).keydown(e => {
|
||||
// enter key
|
||||
if (e.keyCode === 13) {
|
||||
this.send("submit");
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
focusPermalink() {
|
||||
Ember.run.schedule("afterRender", () => this.$(".permalink-url").focus());
|
||||
schedule("afterRender", () =>
|
||||
this.element.querySelector(".permalink-url").focus()
|
||||
);
|
||||
},
|
||||
|
||||
actions: {
|
||||
@ -60,19 +77,10 @@ export default Ember.Component.extend({
|
||||
}
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
onChangePermalinkType(type) {
|
||||
this.set("permalinkType", type);
|
||||
}
|
||||
},
|
||||
|
||||
didInsertElement() {
|
||||
this._super(...arguments);
|
||||
|
||||
Ember.run.schedule("afterRender", () => {
|
||||
this.$(".external-url").keydown(e => {
|
||||
// enter key
|
||||
if (e.keyCode === 13) {
|
||||
this.send("submit");
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
import Category from "discourse/models/category";
|
||||
import { default as computed } from "ember-addons/ember-computed-decorators";
|
||||
import { readOnly } from "@ember/object/computed";
|
||||
import FilterComponent from "admin/components/report-filters/filter";
|
||||
|
||||
export default FilterComponent.extend({
|
||||
@ -7,14 +6,11 @@ export default FilterComponent.extend({
|
||||
|
||||
layoutName: "admin/templates/components/report-filters/category",
|
||||
|
||||
@computed("filter.default")
|
||||
category(categoryId) {
|
||||
return Category.findById(categoryId);
|
||||
},
|
||||
category: readOnly("filter.default"),
|
||||
|
||||
actions: {
|
||||
onDeselect() {
|
||||
this.applyFilter(this.get("filter.id"), undefined);
|
||||
onChange(categoryId) {
|
||||
this.applyFilter(this.get("filter.id"), categoryId || undefined);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
export default Ember.Component.extend({
|
||||
import Component from "@ember/component";
|
||||
export default Component.extend({
|
||||
actions: {
|
||||
onChange(value) {
|
||||
this.applyFilter(this.get("filter.id"), value);
|
||||
|
||||
@ -1,19 +1,19 @@
|
||||
import FilterComponent from "admin/components/report-filters/filter";
|
||||
import { default as computed } from "ember-addons/ember-computed-decorators";
|
||||
import discourseComputed from "discourse-common/utils/decorators";
|
||||
|
||||
export default FilterComponent.extend({
|
||||
classNames: ["group-filter"],
|
||||
|
||||
layoutName: "admin/templates/components/report-filters/group",
|
||||
|
||||
@computed()
|
||||
@discourseComputed()
|
||||
groupOptions() {
|
||||
return (this.site.groups || []).map(group => {
|
||||
return { name: group["name"], value: group["id"] };
|
||||
});
|
||||
},
|
||||
|
||||
@computed("filter.default")
|
||||
@discourseComputed("filter.default")
|
||||
groupId(filterDefault) {
|
||||
return filterDefault ? parseInt(filterDefault, 10) : null;
|
||||
}
|
||||
|
||||
@ -1,9 +1,8 @@
|
||||
import { schedule } from "@ember/runloop";
|
||||
import { later } from "@ember/runloop";
|
||||
import Component from "@ember/component";
|
||||
import { iconHTML } from "discourse-common/lib/icon-library";
|
||||
import { bufferedRender } from "discourse-common/lib/buffered-render";
|
||||
import {
|
||||
default as computed,
|
||||
on
|
||||
} from "ember-addons/ember-computed-decorators";
|
||||
import discourseComputed, { on } from "discourse-common/utils/decorators";
|
||||
|
||||
/*global Resumable:true */
|
||||
|
||||
@ -17,114 +16,124 @@ import {
|
||||
uploadText="UPLOAD"
|
||||
}}
|
||||
**/
|
||||
export default Ember.Component.extend(
|
||||
bufferedRender({
|
||||
tagName: "button",
|
||||
classNames: ["btn", "ru"],
|
||||
classNameBindings: ["isUploading"],
|
||||
attributeBindings: ["translatedTitle:title"],
|
||||
resumable: null,
|
||||
isUploading: false,
|
||||
progress: 0,
|
||||
rerenderTriggers: ["isUploading", "progress"],
|
||||
export default Component.extend({
|
||||
tagName: "button",
|
||||
classNames: ["btn", "ru"],
|
||||
classNameBindings: ["isUploading"],
|
||||
attributeBindings: ["translatedTitle:title"],
|
||||
resumable: null,
|
||||
isUploading: false,
|
||||
progress: 0,
|
||||
rerenderTriggers: ["isUploading", "progress"],
|
||||
uploadingIcon: null,
|
||||
progressBar: null,
|
||||
|
||||
@on("init")
|
||||
_initialize() {
|
||||
this.resumable = new Resumable({
|
||||
target: Discourse.getURL(this.target),
|
||||
maxFiles: 1, // only 1 file at a time
|
||||
headers: {
|
||||
"X-CSRF-Token": document.querySelector("meta[name='csrf-token']")
|
||||
.content
|
||||
}
|
||||
});
|
||||
|
||||
this.resumable.on("fileAdded", () => {
|
||||
// automatically upload the selected file
|
||||
this.resumable.upload();
|
||||
|
||||
// mark as uploading
|
||||
Ember.run.later(() => this.set("isUploading", true));
|
||||
});
|
||||
|
||||
this.resumable.on("fileProgress", file => {
|
||||
// update progress
|
||||
Ember.run.later(() =>
|
||||
this.set("progress", parseInt(file.progress() * 100, 10))
|
||||
);
|
||||
});
|
||||
|
||||
this.resumable.on("fileSuccess", file => {
|
||||
Ember.run.later(() => {
|
||||
// mark as not uploading anymore
|
||||
this._reset();
|
||||
|
||||
// fire an event to allow the parent route to reload its model
|
||||
this.success(file.fileName);
|
||||
});
|
||||
});
|
||||
|
||||
this.resumable.on("fileError", (file, message) => {
|
||||
Ember.run.later(() => {
|
||||
// mark as not uploading anymore
|
||||
this._reset();
|
||||
|
||||
// fire an event to allow the parent route to display the error message
|
||||
this.error(file.fileName, message);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
@on("didInsertElement")
|
||||
_assignBrowse() {
|
||||
Ember.run.schedule("afterRender", () =>
|
||||
this.resumable.assignBrowse($(this.element))
|
||||
);
|
||||
},
|
||||
|
||||
@on("willDestroyElement")
|
||||
_teardown() {
|
||||
if (this.resumable) {
|
||||
this.resumable.cancel();
|
||||
this.resumable = null;
|
||||
@on("init")
|
||||
_initialize() {
|
||||
this.resumable = new Resumable({
|
||||
target: Discourse.getURL(this.target),
|
||||
maxFiles: 1, // only 1 file at a time
|
||||
headers: {
|
||||
"X-CSRF-Token": document.querySelector("meta[name='csrf-token']")
|
||||
.content
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@computed("title", "text")
|
||||
translatedTitle(title, text) {
|
||||
return title ? I18n.t(title) : text;
|
||||
},
|
||||
this.resumable.on("fileAdded", () => {
|
||||
// automatically upload the selected file
|
||||
this.resumable.upload();
|
||||
|
||||
@computed("isUploading", "progress")
|
||||
text(isUploading, progress) {
|
||||
if (isUploading) {
|
||||
return progress + " %";
|
||||
} else {
|
||||
return this.uploadText;
|
||||
}
|
||||
},
|
||||
// mark as uploading
|
||||
later(() => {
|
||||
this.set("isUploading", true);
|
||||
this._updateIcon();
|
||||
});
|
||||
});
|
||||
|
||||
buildBuffer(buffer) {
|
||||
const icon = this.isUploading ? "times" : "upload";
|
||||
buffer.push(iconHTML(icon));
|
||||
buffer.push("<span class='ru-label'>" + this.text + "</span>");
|
||||
buffer.push(
|
||||
"<span class='ru-progress' style='width:" + this.progress + "%'></span>"
|
||||
);
|
||||
},
|
||||
this.resumable.on("fileProgress", file => {
|
||||
// update progress
|
||||
later(() => {
|
||||
this.set("progress", parseInt(file.progress() * 100, 10));
|
||||
this._updateProgressBar();
|
||||
});
|
||||
});
|
||||
|
||||
click() {
|
||||
if (this.isUploading) {
|
||||
this.resumable.cancel();
|
||||
Ember.run.later(() => this._reset());
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
},
|
||||
this.resumable.on("fileSuccess", file => {
|
||||
later(() => {
|
||||
// mark as not uploading anymore
|
||||
this._reset();
|
||||
|
||||
_reset() {
|
||||
this.setProperties({ isUploading: false, progress: 0 });
|
||||
// fire an event to allow the parent route to reload its model
|
||||
this.success(file.fileName);
|
||||
});
|
||||
});
|
||||
|
||||
this.resumable.on("fileError", (file, message) => {
|
||||
later(() => {
|
||||
// mark as not uploading anymore
|
||||
this._reset();
|
||||
|
||||
// fire an event to allow the parent route to display the error message
|
||||
this.error(file.fileName, message);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
@on("didInsertElement")
|
||||
_assignBrowse() {
|
||||
schedule("afterRender", () => this.resumable.assignBrowse($(this.element)));
|
||||
},
|
||||
|
||||
@on("willDestroyElement")
|
||||
_teardown() {
|
||||
if (this.resumable) {
|
||||
this.resumable.cancel();
|
||||
this.resumable = null;
|
||||
}
|
||||
})
|
||||
);
|
||||
},
|
||||
|
||||
@discourseComputed("title", "text")
|
||||
translatedTitle(title, text) {
|
||||
return title ? I18n.t(title) : text;
|
||||
},
|
||||
|
||||
@discourseComputed("isUploading", "progress")
|
||||
text(isUploading, progress) {
|
||||
if (isUploading) {
|
||||
return progress + " %";
|
||||
} else {
|
||||
return this.uploadText;
|
||||
}
|
||||
},
|
||||
|
||||
didReceiveAttrs() {
|
||||
this._super(...arguments);
|
||||
this._updateIcon();
|
||||
},
|
||||
|
||||
click() {
|
||||
if (this.isUploading) {
|
||||
this.resumable.cancel();
|
||||
later(() => this._reset());
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
},
|
||||
|
||||
_updateIcon() {
|
||||
const icon = this.isUploading ? "times" : "upload";
|
||||
this.set("uploadingIcon", `${iconHTML(icon)}`.htmlSafe());
|
||||
},
|
||||
|
||||
_updateProgressBar() {
|
||||
const pb = `${"width:" + this.progress + "%"}`.htmlSafe();
|
||||
this.set("progressBar", pb);
|
||||
},
|
||||
|
||||
_reset() {
|
||||
this.setProperties({ isUploading: false, progress: 0 });
|
||||
this._updateIcon();
|
||||
this._updateProgressBar();
|
||||
}
|
||||
});
|
||||
|
||||
@ -1,11 +1,13 @@
|
||||
import computed from "ember-addons/ember-computed-decorators";
|
||||
import discourseComputed from "discourse-common/utils/decorators";
|
||||
import { or } from "@ember/object/computed";
|
||||
import Component from "@ember/component";
|
||||
|
||||
export default Ember.Component.extend({
|
||||
export default Component.extend({
|
||||
classNames: ["controls"],
|
||||
|
||||
buttonDisabled: Ember.computed.or("model.isSaving", "saveDisabled"),
|
||||
buttonDisabled: or("model.isSaving", "saveDisabled"),
|
||||
|
||||
@computed("model.isSaving")
|
||||
@discourseComputed("model.isSaving")
|
||||
savingText(saving) {
|
||||
return saving ? "saving" : "save";
|
||||
}
|
||||
|
||||
@ -1,3 +1,6 @@
|
||||
import discourseComputed from "discourse-common/utils/decorators";
|
||||
import { schedule } from "@ember/runloop";
|
||||
import Component from "@ember/component";
|
||||
/**
|
||||
A form to create an IP address that will be blocked or whitelisted.
|
||||
Example usage:
|
||||
@ -10,20 +13,19 @@
|
||||
**/
|
||||
|
||||
import ScreenedIpAddress from "admin/models/screened-ip-address";
|
||||
import computed from "ember-addons/ember-computed-decorators";
|
||||
import { on } from "ember-addons/ember-computed-decorators";
|
||||
import { on } from "discourse-common/utils/decorators";
|
||||
|
||||
export default Ember.Component.extend({
|
||||
export default Component.extend({
|
||||
classNames: ["screened-ip-address-form"],
|
||||
formSubmitted: false,
|
||||
actionName: "block",
|
||||
|
||||
@computed
|
||||
@discourseComputed
|
||||
adminWhitelistEnabled() {
|
||||
return Discourse.SiteSettings.use_admin_ip_whitelist;
|
||||
},
|
||||
|
||||
@computed("adminWhitelistEnabled")
|
||||
@discourseComputed("adminWhitelistEnabled")
|
||||
actionNames(adminWhitelistEnabled) {
|
||||
if (adminWhitelistEnabled) {
|
||||
return [
|
||||
@ -61,8 +63,8 @@ export default Ember.Component.extend({
|
||||
.then(result => {
|
||||
this.setProperties({ ip_address: "", formSubmitted: false });
|
||||
this.action(ScreenedIpAddress.create(result.screened_ip_address));
|
||||
Ember.run.schedule("afterRender", () =>
|
||||
this.$(".ip-address-input").focus()
|
||||
schedule("afterRender", () =>
|
||||
this.element.querySelector(".ip-address-input").focus()
|
||||
);
|
||||
})
|
||||
.catch(e => {
|
||||
@ -73,7 +75,9 @@ export default Ember.Component.extend({
|
||||
error: e.jqXHR.responseJSON.errors.join(". ")
|
||||
})
|
||||
: I18n.t("generic_error");
|
||||
bootbox.alert(msg, () => this.$(".ip-address-input").focus());
|
||||
bootbox.alert(msg, () =>
|
||||
this.element.querySelector(".ip-address-input").focus()
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -81,8 +85,8 @@ export default Ember.Component.extend({
|
||||
|
||||
@on("didInsertElement")
|
||||
_init() {
|
||||
Ember.run.schedule("afterRender", () => {
|
||||
this.$(".ip-address-input").keydown(e => {
|
||||
schedule("afterRender", () => {
|
||||
$(this.element.querySelector(".ip-address-input")).keydown(e => {
|
||||
if (e.keyCode === 13) {
|
||||
this.send("submit");
|
||||
}
|
||||
|
||||
@ -1,6 +1,9 @@
|
||||
import { on } from "ember-addons/ember-computed-decorators";
|
||||
import { isEmpty } from "@ember/utils";
|
||||
import Component from "@ember/component";
|
||||
import { on } from "discourse-common/utils/decorators";
|
||||
import { set } from "@ember/object";
|
||||
|
||||
export default Ember.Component.extend({
|
||||
export default Component.extend({
|
||||
classNameBindings: [":value-list", ":secret-value-list"],
|
||||
inputDelimiter: null,
|
||||
collection: null,
|
||||
@ -42,7 +45,7 @@ export default Ember.Component.extend({
|
||||
_checkInvalidInput(inputs) {
|
||||
this.set("validationMessage", null);
|
||||
for (let input of inputs) {
|
||||
if (Ember.isEmpty(input) || input.includes("|")) {
|
||||
if (isEmpty(input) || input.includes("|")) {
|
||||
this.set(
|
||||
"validationMessage",
|
||||
I18n.t("admin.site_settings.secret_list.invalid_input")
|
||||
@ -65,7 +68,7 @@ export default Ember.Component.extend({
|
||||
|
||||
_replaceValue(index, newValue, keyName) {
|
||||
let item = this.collection[index];
|
||||
Ember.set(item, keyName, newValue);
|
||||
set(item, keyName, newValue);
|
||||
|
||||
this._saveValues();
|
||||
},
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
export default Ember.Component.extend({
|
||||
import Component from "@ember/component";
|
||||
export default Component.extend({
|
||||
tagName: ""
|
||||
});
|
||||
|
||||
@ -1,10 +1,15 @@
|
||||
import Component from "@ember/component";
|
||||
import BufferedContent from "discourse/mixins/buffered-content";
|
||||
import SiteSetting from "admin/models/site-setting";
|
||||
import SettingComponent from "admin/mixins/setting-component";
|
||||
|
||||
export default Ember.Component.extend(BufferedContent, SettingComponent, {
|
||||
export default Component.extend(BufferedContent, SettingComponent, {
|
||||
updateExistingUsers: null,
|
||||
|
||||
_save() {
|
||||
const setting = this.buffered;
|
||||
return SiteSetting.update(setting.get("setting"), setting.get("value"));
|
||||
return SiteSetting.update(setting.get("setting"), setting.get("value"), {
|
||||
updateExistingUsers: this.updateExistingUsers
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@ -1,10 +1,12 @@
|
||||
import computed from "ember-addons/ember-computed-decorators";
|
||||
import discourseComputed from "discourse-common/utils/decorators";
|
||||
import { isEmpty } from "@ember/utils";
|
||||
import Component from "@ember/component";
|
||||
|
||||
export default Ember.Component.extend({
|
||||
@computed("value")
|
||||
export default Component.extend({
|
||||
@discourseComputed("value")
|
||||
enabled: {
|
||||
get(value) {
|
||||
if (Ember.isEmpty(value)) {
|
||||
if (isEmpty(value)) {
|
||||
return false;
|
||||
}
|
||||
return value.toString() === "true";
|
||||
|
||||
@ -1,14 +1,15 @@
|
||||
import computed from "ember-addons/ember-computed-decorators";
|
||||
import Component from "@ember/component";
|
||||
import Category from "discourse/models/category";
|
||||
import { computed } from "@ember/object";
|
||||
|
||||
export default Ember.Component.extend({
|
||||
@computed("value")
|
||||
selectedCategories: {
|
||||
get(value) {
|
||||
return Discourse.Category.findByIds(value.split("|"));
|
||||
},
|
||||
set(value) {
|
||||
this.set("value", value.mapBy("id").join("|"));
|
||||
return value;
|
||||
export default Component.extend({
|
||||
selectedCategories: computed("value", function() {
|
||||
return Category.findByIds(this.value.split("|").filter(Boolean));
|
||||
}),
|
||||
|
||||
actions: {
|
||||
onChangeSelectedCategories(value) {
|
||||
this.set("value", (value || []).mapBy("id").join("|"));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@ -0,0 +1,43 @@
|
||||
import Component from "@ember/component";
|
||||
import { computed } from "@ember/object";
|
||||
import { makeArray } from "discourse-common/lib/helpers";
|
||||
|
||||
export default Component.extend({
|
||||
tokenSeparator: "|",
|
||||
|
||||
createdChoices: null,
|
||||
|
||||
settingValue: computed("value", function() {
|
||||
return this.value
|
||||
.toString()
|
||||
.split(this.tokenSeparator)
|
||||
.filter(Boolean);
|
||||
}),
|
||||
|
||||
settingChoices: computed(
|
||||
"settingValue",
|
||||
"setting.choices.[]",
|
||||
"createdChoices.[]",
|
||||
function() {
|
||||
return [
|
||||
...new Set([
|
||||
...makeArray(this.settingValue),
|
||||
...makeArray(this.setting.choices),
|
||||
...makeArray(this.createdChoices)
|
||||
])
|
||||
];
|
||||
}
|
||||
),
|
||||
|
||||
actions: {
|
||||
onChangeListSetting(value) {
|
||||
this.set("value", value.join(this.tokenSeparator));
|
||||
},
|
||||
|
||||
onChangeChoices(choices) {
|
||||
this.set("createdChoices", [
|
||||
...new Set([...makeArray(this.createdChoices), ...makeArray(choices)])
|
||||
]);
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -1,8 +1,25 @@
|
||||
import computed from "ember-addons/ember-computed-decorators";
|
||||
import { computed } from "@ember/object";
|
||||
import Component from "@ember/component";
|
||||
|
||||
export default Ember.Component.extend({
|
||||
@computed()
|
||||
groupChoices() {
|
||||
return this.site.get("groups").map(g => g.name);
|
||||
export default Component.extend({
|
||||
tokenSeparator: "|",
|
||||
|
||||
nameProperty: "name",
|
||||
valueProperty: "id",
|
||||
|
||||
groupChoices: computed("site.groups", function() {
|
||||
return (this.site.groups || []).map(g => {
|
||||
return { name: g.name, id: g.id.toString() };
|
||||
});
|
||||
}),
|
||||
|
||||
settingValue: computed("value", function() {
|
||||
return (this.value || "").split(this.tokenSeparator).filter(Boolean);
|
||||
}),
|
||||
|
||||
actions: {
|
||||
onChangeGroupListSetting(value) {
|
||||
this.set("value", value.join(this.tokenSeparator));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@ -0,0 +1,15 @@
|
||||
import discourseComputed from "discourse-common/utils/decorators";
|
||||
import Component from "@ember/component";
|
||||
|
||||
export default Component.extend({
|
||||
@discourseComputed("value")
|
||||
selectedTags: {
|
||||
get(value) {
|
||||
return value.split("|");
|
||||
},
|
||||
set(value) {
|
||||
this.set("value", value.join("|"));
|
||||
return value;
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -1,6 +1,7 @@
|
||||
import Component from "@ember/component";
|
||||
import showModal from "discourse/lib/show-modal";
|
||||
|
||||
export default Ember.Component.extend({
|
||||
export default Component.extend({
|
||||
actions: {
|
||||
showUploadModal({ value, setting }) {
|
||||
showModal("admin-uploaded-image-list", {
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { on } from "ember-addons/ember-computed-decorators";
|
||||
import Component from "@ember/component";
|
||||
import { on } from "discourse-common/utils/decorators";
|
||||
|
||||
export default Ember.Component.extend({
|
||||
export default Component.extend({
|
||||
classNames: ["site-text"],
|
||||
classNameBindings: ["siteText.overridden"],
|
||||
|
||||
@ -9,11 +10,13 @@ export default Ember.Component.extend({
|
||||
const term = this._searchTerm();
|
||||
|
||||
if (term) {
|
||||
this.$(".site-text-id, .site-text-value").highlight(term, {
|
||||
$(
|
||||
this.element.querySelector(".site-text-id, .site-text-value")
|
||||
).highlight(term, {
|
||||
className: "text-highlight"
|
||||
});
|
||||
}
|
||||
this.$(".site-text-value").ellipsis();
|
||||
$(this.element.querySelector(".site-text-value")).ellipsis();
|
||||
},
|
||||
|
||||
click() {
|
||||
|
||||
@ -1,22 +1,27 @@
|
||||
import Component from "@ember/component";
|
||||
import DiscourseURL from "discourse/lib/url";
|
||||
|
||||
export default Ember.Component.extend({
|
||||
export default Component.extend({
|
||||
classNames: ["table", "staff-actions"],
|
||||
|
||||
willDestroyElement() {
|
||||
this.$().off("click.discourse-staff-logs");
|
||||
$(this.element).off("click.discourse-staff-logs");
|
||||
},
|
||||
|
||||
didInsertElement() {
|
||||
this._super(...arguments);
|
||||
|
||||
this.$().on("click.discourse-staff-logs", "[data-link-post-id]", e => {
|
||||
let postId = $(e.target).attr("data-link-post-id");
|
||||
$(this.element).on(
|
||||
"click.discourse-staff-logs",
|
||||
"[data-link-post-id]",
|
||||
e => {
|
||||
let postId = $(e.target).attr("data-link-post-id");
|
||||
|
||||
this.store.find("post", postId).then(p => {
|
||||
DiscourseURL.routeTo(p.get("url"));
|
||||
});
|
||||
return false;
|
||||
});
|
||||
this.store.find("post", postId).then(p => {
|
||||
DiscourseURL.routeTo(p.get("url"));
|
||||
});
|
||||
return false;
|
||||
}
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
export default Ember.Component.extend({
|
||||
import Component from "@ember/component";
|
||||
export default Component.extend({
|
||||
tagName: ""
|
||||
});
|
||||
|
||||
@ -1,9 +1,11 @@
|
||||
import { alias } from "@ember/object/computed";
|
||||
import Component from "@ember/component";
|
||||
import UploadMixin from "discourse/mixins/upload";
|
||||
|
||||
export default Ember.Component.extend(UploadMixin, {
|
||||
export default Component.extend(UploadMixin, {
|
||||
type: "csv",
|
||||
uploadUrl: "/tags/upload",
|
||||
addDisabled: Ember.computed.alias("uploading"),
|
||||
addDisabled: alias("uploading"),
|
||||
elementId: "tag-uploader",
|
||||
|
||||
validateUploadedFilesOptions() {
|
||||
|
||||
@ -1,12 +1,20 @@
|
||||
import Component from "@ember/component";
|
||||
import BufferedContent from "discourse/mixins/buffered-content";
|
||||
import SettingComponent from "admin/mixins/setting-component";
|
||||
import { ajax } from "discourse/lib/ajax";
|
||||
import { url } from "discourse/lib/computed";
|
||||
|
||||
export default Ember.Component.extend(BufferedContent, SettingComponent, {
|
||||
export default Component.extend(BufferedContent, SettingComponent, {
|
||||
layoutName: "admin/templates/components/site-setting",
|
||||
updateUrl: url("model.id", "/admin/themes/%@/setting"),
|
||||
|
||||
_save() {
|
||||
return this.model.saveSettings(
|
||||
this.get("setting.setting"),
|
||||
this.get("buffered.value")
|
||||
);
|
||||
return ajax(this.updateUrl, {
|
||||
type: "PUT",
|
||||
data: {
|
||||
name: this.setting.setting,
|
||||
value: this.get("buffered.value")
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@ -0,0 +1,26 @@
|
||||
import Component from "@ember/component";
|
||||
import BufferedContent from "discourse/mixins/buffered-content";
|
||||
import SettingComponent from "admin/mixins/setting-component";
|
||||
|
||||
export default Component.extend(BufferedContent, SettingComponent, {
|
||||
layoutName: "admin/templates/components/site-setting",
|
||||
|
||||
_save() {
|
||||
return this.model
|
||||
.save({ [this.setting.setting]: this.convertNamesToIds() })
|
||||
.then(() => this.store.findAll("theme"));
|
||||
},
|
||||
|
||||
convertNamesToIds() {
|
||||
return this.get("buffered.value")
|
||||
.split("|")
|
||||
.filter(Boolean)
|
||||
.map(themeName => {
|
||||
if (themeName !== "") {
|
||||
return this.setting.allThemes.find(theme => theme.name === themeName)
|
||||
.id;
|
||||
}
|
||||
return themeName;
|
||||
});
|
||||
}
|
||||
});
|
||||
@ -1,11 +1,13 @@
|
||||
import { alias } from "@ember/object/computed";
|
||||
import Component from "@ember/component";
|
||||
import BufferedContent from "discourse/mixins/buffered-content";
|
||||
import SettingComponent from "admin/mixins/setting-component";
|
||||
|
||||
export default Ember.Component.extend(BufferedContent, SettingComponent, {
|
||||
export default Component.extend(BufferedContent, SettingComponent, {
|
||||
layoutName: "admin/templates/components/site-setting",
|
||||
setting: Ember.computed.alias("translation"),
|
||||
setting: alias("translation"),
|
||||
type: "string",
|
||||
settingName: Ember.computed.alias("translation.key"),
|
||||
settingName: alias("translation.key"),
|
||||
|
||||
_save() {
|
||||
return this.model.saveTranslation(
|
||||
|
||||
@ -1,17 +1,20 @@
|
||||
import {
|
||||
default as computed,
|
||||
observes
|
||||
} from "ember-addons/ember-computed-decorators";
|
||||
import { gt, and } from "@ember/object/computed";
|
||||
import { schedule } from "@ember/runloop";
|
||||
import Component from "@ember/component";
|
||||
import discourseComputed, { observes } from "discourse-common/utils/decorators";
|
||||
import { iconHTML } from "discourse-common/lib/icon-library";
|
||||
import { escape } from "pretty-text/sanitizer";
|
||||
import ENV from "discourse-common/config/environment";
|
||||
|
||||
const MAX_COMPONENTS = 4;
|
||||
|
||||
export default Ember.Component.extend({
|
||||
export default Component.extend({
|
||||
childrenExpanded: false,
|
||||
classNames: ["themes-list-item"],
|
||||
classNameBindings: ["theme.selected:selected"],
|
||||
hasComponents: Ember.computed.gt("children.length", 0),
|
||||
displayComponents: Ember.computed.and("hasComponents", "theme.isActive"),
|
||||
displayHasMore: Ember.computed.gt("theme.childThemes.length", MAX_COMPONENTS),
|
||||
hasComponents: gt("children.length", 0),
|
||||
displayComponents: and("hasComponents", "theme.isActive"),
|
||||
displayHasMore: gt("theme.childThemes.length", MAX_COMPONENTS),
|
||||
|
||||
click(e) {
|
||||
if (!$(e.target).hasClass("others-count")) {
|
||||
@ -30,15 +33,15 @@ export default Ember.Component.extend({
|
||||
},
|
||||
|
||||
scheduleAnimation() {
|
||||
Ember.run.schedule("afterRender", () => {
|
||||
schedule("afterRender", () => {
|
||||
this.animate(true);
|
||||
});
|
||||
},
|
||||
|
||||
animate(isInitial) {
|
||||
const $container = this.$();
|
||||
const $list = this.$(".components-list");
|
||||
if ($list.length === 0 || Ember.testing) {
|
||||
const $container = $(this.element);
|
||||
const $list = $(this.element.querySelector(".components-list"));
|
||||
if ($list.length === 0 || ENV.environment === "test") {
|
||||
return;
|
||||
}
|
||||
const duration = 300;
|
||||
@ -49,7 +52,7 @@ export default Ember.Component.extend({
|
||||
}
|
||||
},
|
||||
|
||||
@computed(
|
||||
@discourseComputed(
|
||||
"theme.component",
|
||||
"theme.childThemes.@each.name",
|
||||
"theme.childThemes.length",
|
||||
@ -64,15 +67,18 @@ export default Ember.Component.extend({
|
||||
children = this.childrenExpanded
|
||||
? children
|
||||
: children.slice(0, MAX_COMPONENTS);
|
||||
return children.map(t => t.get("name"));
|
||||
return children.map(t => {
|
||||
const name = escape(t.name);
|
||||
return t.enabled ? name : `${iconHTML("ban")} ${name}`;
|
||||
});
|
||||
},
|
||||
|
||||
@computed("children")
|
||||
@discourseComputed("children")
|
||||
childrenString(children) {
|
||||
return children.join(", ");
|
||||
},
|
||||
|
||||
@computed(
|
||||
@discourseComputed(
|
||||
"theme.childThemes.length",
|
||||
"theme.component",
|
||||
"childrenExpanded",
|
||||
|
||||
@ -1,20 +1,23 @@
|
||||
import { gt, equal } from "@ember/object/computed";
|
||||
import Component from "@ember/component";
|
||||
import { THEMES, COMPONENTS } from "admin/models/theme";
|
||||
import { default as computed } from "ember-addons/ember-computed-decorators";
|
||||
import discourseComputed from "discourse-common/utils/decorators";
|
||||
import { getOwner } from "@ember/application";
|
||||
|
||||
export default Ember.Component.extend({
|
||||
export default Component.extend({
|
||||
THEMES: THEMES,
|
||||
COMPONENTS: COMPONENTS,
|
||||
|
||||
classNames: ["themes-list"],
|
||||
|
||||
hasThemes: Ember.computed.gt("themesList.length", 0),
|
||||
hasActiveThemes: Ember.computed.gt("activeThemes.length", 0),
|
||||
hasInactiveThemes: Ember.computed.gt("inactiveThemes.length", 0),
|
||||
hasThemes: gt("themesList.length", 0),
|
||||
hasActiveThemes: gt("activeThemes.length", 0),
|
||||
hasInactiveThemes: gt("inactiveThemes.length", 0),
|
||||
|
||||
themesTabActive: Ember.computed.equal("currentTab", THEMES),
|
||||
componentsTabActive: Ember.computed.equal("currentTab", COMPONENTS),
|
||||
themesTabActive: equal("currentTab", THEMES),
|
||||
componentsTabActive: equal("currentTab", COMPONENTS),
|
||||
|
||||
@computed("themes", "components", "currentTab")
|
||||
@discourseComputed("themes", "components", "currentTab")
|
||||
themesList(themes, components) {
|
||||
if (this.themesTabActive) {
|
||||
return themes;
|
||||
@ -23,7 +26,7 @@ export default Ember.Component.extend({
|
||||
}
|
||||
},
|
||||
|
||||
@computed(
|
||||
@discourseComputed(
|
||||
"themesList",
|
||||
"currentTab",
|
||||
"themesList.@each.user_selectable",
|
||||
@ -38,7 +41,7 @@ export default Ember.Component.extend({
|
||||
);
|
||||
},
|
||||
|
||||
@computed(
|
||||
@discourseComputed(
|
||||
"themesList",
|
||||
"currentTab",
|
||||
"themesList.@each.user_selectable",
|
||||
@ -68,7 +71,7 @@ export default Ember.Component.extend({
|
||||
}
|
||||
},
|
||||
navigateToTheme(theme) {
|
||||
Ember.getOwner(this)
|
||||
getOwner(this)
|
||||
.lookup("router:main")
|
||||
.transitionTo("adminCustomizeThemes.show", theme);
|
||||
}
|
||||
|
||||
@ -1,17 +1,18 @@
|
||||
import { on } from "ember-addons/ember-computed-decorators";
|
||||
import computed from "ember-addons/ember-computed-decorators";
|
||||
import discourseComputed from "discourse-common/utils/decorators";
|
||||
import { makeArray } from "discourse-common/lib/helpers";
|
||||
import { empty, reads } from "@ember/object/computed";
|
||||
import Component from "@ember/component";
|
||||
import { on } from "discourse-common/utils/decorators";
|
||||
|
||||
export default Ember.Component.extend({
|
||||
export default Component.extend({
|
||||
classNameBindings: [":value-list"],
|
||||
|
||||
inputInvalid: Ember.computed.empty("newValue"),
|
||||
|
||||
inputInvalid: empty("newValue"),
|
||||
inputDelimiter: null,
|
||||
inputType: null,
|
||||
newValue: "",
|
||||
collection: null,
|
||||
values: null,
|
||||
noneKey: Ember.computed.alias("addKey"),
|
||||
noneKey: reads("addKey"),
|
||||
|
||||
@on("didReceiveAttrs")
|
||||
_setupCollection() {
|
||||
@ -27,9 +28,9 @@ export default Ember.Component.extend({
|
||||
);
|
||||
},
|
||||
|
||||
@computed("choices.[]", "collection.[]")
|
||||
@discourseComputed("choices.[]", "collection.[]")
|
||||
filteredChoices(choices, collection) {
|
||||
return Ember.makeArray(choices).filter(i => collection.indexOf(i) < 0);
|
||||
return makeArray(choices).filter(i => collection.indexOf(i) < 0);
|
||||
},
|
||||
|
||||
keyDown(event) {
|
||||
@ -44,7 +45,7 @@ export default Ember.Component.extend({
|
||||
addValue(newValue) {
|
||||
if (this.inputInvalid) return;
|
||||
|
||||
this.set("newValue", "");
|
||||
this.set("newValue", null);
|
||||
this._addValue(newValue);
|
||||
},
|
||||
|
||||
@ -59,12 +60,25 @@ export default Ember.Component.extend({
|
||||
|
||||
_addValue(value) {
|
||||
this.collection.addObject(value);
|
||||
|
||||
if (this.choices) {
|
||||
this.set("choices", this.choices.rejectBy("id", value));
|
||||
} else {
|
||||
this.set("choices", []);
|
||||
}
|
||||
|
||||
this._saveValues();
|
||||
},
|
||||
|
||||
_removeValue(value) {
|
||||
const collection = this.collection;
|
||||
collection.removeObject(value);
|
||||
this.collection.removeObject(value);
|
||||
|
||||
if (this.choices) {
|
||||
this.set("choices", this.choices.concat([value]).uniq());
|
||||
} else {
|
||||
this.set("choices", makeArray(value));
|
||||
}
|
||||
|
||||
this._saveValues();
|
||||
},
|
||||
|
||||
|
||||
@ -1,17 +1,19 @@
|
||||
import { isEmpty } from "@ember/utils";
|
||||
import { schedule } from "@ember/runloop";
|
||||
import Component from "@ember/component";
|
||||
import WatchedWord from "admin/models/watched-word";
|
||||
import {
|
||||
default as computed,
|
||||
import discourseComputed, {
|
||||
on,
|
||||
observes
|
||||
} from "ember-addons/ember-computed-decorators";
|
||||
} from "discourse-common/utils/decorators";
|
||||
|
||||
export default Ember.Component.extend({
|
||||
export default Component.extend({
|
||||
classNames: ["watched-word-form"],
|
||||
formSubmitted: false,
|
||||
actionKey: null,
|
||||
showMessage: false,
|
||||
|
||||
@computed("regularExpressions")
|
||||
@discourseComputed("regularExpressions")
|
||||
placeholderKey(regularExpressions) {
|
||||
return (
|
||||
"admin.watched_words.form.placeholder" +
|
||||
@ -21,12 +23,12 @@ export default Ember.Component.extend({
|
||||
|
||||
@observes("word")
|
||||
removeMessage() {
|
||||
if (this.showMessage && !Ember.isEmpty(this.word)) {
|
||||
if (this.showMessage && !isEmpty(this.word)) {
|
||||
this.set("showMessage", false);
|
||||
}
|
||||
},
|
||||
|
||||
@computed("word")
|
||||
@discourseComputed("word")
|
||||
isUniqueWord(word) {
|
||||
const words = this.filteredContent || [];
|
||||
const filtered = words.filter(content => content.action === this.actionKey);
|
||||
@ -63,8 +65,8 @@ export default Ember.Component.extend({
|
||||
message: I18n.t("admin.watched_words.form.success")
|
||||
});
|
||||
this.action(WatchedWord.create(result));
|
||||
Ember.run.schedule("afterRender", () =>
|
||||
this.$(".watched-word-input").focus()
|
||||
schedule("afterRender", () =>
|
||||
this.element.querySelector(".watched-word-input").focus()
|
||||
);
|
||||
})
|
||||
.catch(e => {
|
||||
@ -75,7 +77,9 @@ export default Ember.Component.extend({
|
||||
error: e.jqXHR.responseJSON.errors.join(". ")
|
||||
})
|
||||
: I18n.t("generic_error");
|
||||
bootbox.alert(msg, () => this.$(".watched-word-input").focus());
|
||||
bootbox.alert(msg, () =>
|
||||
this.element.querySelector(".watched-word-input").focus()
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -83,8 +87,8 @@ export default Ember.Component.extend({
|
||||
|
||||
@on("didInsertElement")
|
||||
_init() {
|
||||
Ember.run.schedule("afterRender", () => {
|
||||
this.$(".watched-word-input").keydown(e => {
|
||||
schedule("afterRender", () => {
|
||||
$(this.element.querySelector(".watched-word-input")).keydown(e => {
|
||||
if (e.keyCode === 13) {
|
||||
this.send("submit");
|
||||
}
|
||||
|
||||
@ -1,17 +1,19 @@
|
||||
import computed from "ember-addons/ember-computed-decorators";
|
||||
import discourseComputed from "discourse-common/utils/decorators";
|
||||
import { alias } from "@ember/object/computed";
|
||||
import Component from "@ember/component";
|
||||
import UploadMixin from "discourse/mixins/upload";
|
||||
|
||||
export default Ember.Component.extend(UploadMixin, {
|
||||
type: "csv",
|
||||
export default Component.extend(UploadMixin, {
|
||||
type: "txt",
|
||||
classNames: "watched-words-uploader",
|
||||
uploadUrl: "/admin/logs/watched_words/upload",
|
||||
addDisabled: Ember.computed.alias("uploading"),
|
||||
addDisabled: alias("uploading"),
|
||||
|
||||
validateUploadedFilesOptions() {
|
||||
return { csvOnly: true };
|
||||
return { skipValidation: true };
|
||||
},
|
||||
|
||||
@computed("actionKey")
|
||||
@discourseComputed("actionKey")
|
||||
data(actionKey) {
|
||||
return { action_key: actionKey };
|
||||
},
|
||||
|
||||
@ -0,0 +1,14 @@
|
||||
import { popupAjaxError } from "discourse/lib/ajax-error";
|
||||
import Controller from "@ember/controller";
|
||||
|
||||
export default Controller.extend({
|
||||
actions: {
|
||||
revokeKey(key) {
|
||||
key.revoke().catch(popupAjaxError);
|
||||
},
|
||||
|
||||
undoRevokeKey(key) {
|
||||
key.undoRevoke().catch(popupAjaxError);
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -0,0 +1,39 @@
|
||||
import discourseComputed from "discourse-common/utils/decorators";
|
||||
import Controller from "@ember/controller";
|
||||
import { popupAjaxError } from "discourse/lib/ajax-error";
|
||||
|
||||
export default Controller.extend({
|
||||
userModes: [
|
||||
{ id: "all", name: I18n.t("admin.api.all_users") },
|
||||
{ id: "single", name: I18n.t("admin.api.single_user") }
|
||||
],
|
||||
|
||||
@discourseComputed("userMode")
|
||||
showUserSelector(mode) {
|
||||
return mode === "single";
|
||||
},
|
||||
|
||||
@discourseComputed("model.description", "model.username", "userMode")
|
||||
saveDisabled(description, username, userMode) {
|
||||
if (Ember.isBlank(description)) return true;
|
||||
if (userMode === "single" && Ember.isBlank(username)) return true;
|
||||
return false;
|
||||
},
|
||||
|
||||
actions: {
|
||||
changeUserMode(value) {
|
||||
if (value === "all") {
|
||||
this.model.set("username", null);
|
||||
}
|
||||
this.set("userMode", value);
|
||||
},
|
||||
|
||||
save() {
|
||||
this.model.save().catch(popupAjaxError);
|
||||
},
|
||||
|
||||
continue() {
|
||||
this.transitionToRoute("adminApiKeys.show", this.model.id);
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -0,0 +1,56 @@
|
||||
import { bufferedProperty } from "discourse/mixins/buffered-content";
|
||||
import Controller from "@ember/controller";
|
||||
import { isEmpty } from "@ember/utils";
|
||||
import { popupAjaxError } from "discourse/lib/ajax-error";
|
||||
import { empty } from "@ember/object/computed";
|
||||
|
||||
export default Controller.extend(bufferedProperty("model"), {
|
||||
isNew: empty("model.id"),
|
||||
|
||||
actions: {
|
||||
saveDescription() {
|
||||
const buffered = this.buffered;
|
||||
const attrs = buffered.getProperties("description");
|
||||
|
||||
this.model
|
||||
.save(attrs)
|
||||
.then(() => {
|
||||
this.set("editingDescription", false);
|
||||
this.rollbackBuffer();
|
||||
})
|
||||
.catch(popupAjaxError);
|
||||
},
|
||||
|
||||
cancel() {
|
||||
const id = this.get("userField.id");
|
||||
if (isEmpty(id)) {
|
||||
this.destroyAction(this.userField);
|
||||
} else {
|
||||
this.rollbackBuffer();
|
||||
this.set("editing", false);
|
||||
}
|
||||
},
|
||||
|
||||
editDescription() {
|
||||
this.toggleProperty("editingDescription");
|
||||
if (!this.editingDescription) {
|
||||
this.rollbackBuffer();
|
||||
}
|
||||
},
|
||||
|
||||
revokeKey(key) {
|
||||
key.revoke().catch(popupAjaxError);
|
||||
},
|
||||
|
||||
deleteKey(key) {
|
||||
key
|
||||
.destroyRecord()
|
||||
.then(() => this.transitionToRoute("adminApiKeys.index"))
|
||||
.catch(popupAjaxError);
|
||||
},
|
||||
|
||||
undoRevokeKey(key) {
|
||||
key.undoRevoke().catch(popupAjaxError);
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -1,41 +0,0 @@
|
||||
import ApiKey from "admin/models/api-key";
|
||||
import { default as computed } from "ember-addons/ember-computed-decorators";
|
||||
|
||||
export default Ember.Controller.extend({
|
||||
@computed("model.[]")
|
||||
hasMasterKey(model) {
|
||||
return !!model.findBy("user", null);
|
||||
},
|
||||
|
||||
actions: {
|
||||
generateMasterKey() {
|
||||
ApiKey.generateMasterKey().then(key => this.model.pushObject(key));
|
||||
},
|
||||
|
||||
regenerateKey(key) {
|
||||
bootbox.confirm(
|
||||
I18n.t("admin.api.confirm_regen"),
|
||||
I18n.t("no_value"),
|
||||
I18n.t("yes_value"),
|
||||
result => {
|
||||
if (result) {
|
||||
key.regenerate();
|
||||
}
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
revokeKey(key) {
|
||||
bootbox.confirm(
|
||||
I18n.t("admin.api.confirm_revoke"),
|
||||
I18n.t("no_value"),
|
||||
I18n.t("yes_value"),
|
||||
result => {
|
||||
if (result) {
|
||||
key.revoke().then(() => this.model.removeObject(key));
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -1,15 +1,18 @@
|
||||
import { alias, equal } from "@ember/object/computed";
|
||||
import { inject } from "@ember/controller";
|
||||
import Controller from "@ember/controller";
|
||||
import { ajax } from "discourse/lib/ajax";
|
||||
import { default as computed } from "ember-addons/ember-computed-decorators";
|
||||
import discourseComputed from "discourse-common/utils/decorators";
|
||||
import { setting, i18n } from "discourse/lib/computed";
|
||||
|
||||
export default Ember.Controller.extend({
|
||||
adminBackups: Ember.inject.controller(),
|
||||
status: Ember.computed.alias("adminBackups.model"),
|
||||
export default Controller.extend({
|
||||
adminBackups: inject(),
|
||||
status: alias("adminBackups.model"),
|
||||
uploadLabel: i18n("admin.backups.upload.label"),
|
||||
backupLocation: setting("backup_location"),
|
||||
localBackupStorage: Ember.computed.equal("backupLocation", "local"),
|
||||
localBackupStorage: equal("backupLocation", "local"),
|
||||
|
||||
@computed("status.allowRestore", "status.isOperationRunning")
|
||||
@discourseComputed("status.allowRestore", "status.isOperationRunning")
|
||||
restoreTitle(allowRestore, isOperationRunning) {
|
||||
if (!allowRestore) {
|
||||
return "admin.backups.operations.restore.is_disabled";
|
||||
@ -29,7 +32,7 @@ export default Ember.Controller.extend({
|
||||
I18n.t("yes_value"),
|
||||
confirmed => {
|
||||
if (confirmed) {
|
||||
Discourse.User.currentProp("hideReadOnlyAlert", true);
|
||||
this.set("currentUser.hideReadOnlyAlert", true);
|
||||
this._toggleReadOnlyMode(true);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,9 @@
|
||||
export default Ember.Controller.extend({
|
||||
adminBackups: Ember.inject.controller(),
|
||||
status: Ember.computed.alias("adminBackups.model"),
|
||||
import { alias } from "@ember/object/computed";
|
||||
import { inject } from "@ember/controller";
|
||||
import Controller from "@ember/controller";
|
||||
export default Controller.extend({
|
||||
adminBackups: inject(),
|
||||
status: alias("adminBackups.model"),
|
||||
|
||||
init() {
|
||||
this._super(...arguments);
|
||||
|
||||
@ -1,9 +1,11 @@
|
||||
export default Ember.Controller.extend({
|
||||
noOperationIsRunning: Ember.computed.not("model.isOperationRunning"),
|
||||
rollbackEnabled: Ember.computed.and(
|
||||
import { not, and } from "@ember/object/computed";
|
||||
import Controller from "@ember/controller";
|
||||
export default Controller.extend({
|
||||
noOperationIsRunning: not("model.isOperationRunning"),
|
||||
rollbackEnabled: and(
|
||||
"model.canRollback",
|
||||
"model.restoreEnabled",
|
||||
"noOperationIsRunning"
|
||||
),
|
||||
rollbackDisabled: Ember.computed.not("rollbackEnabled")
|
||||
rollbackDisabled: not("rollbackEnabled")
|
||||
});
|
||||
|
||||
@ -0,0 +1,37 @@
|
||||
import Controller from "@ember/controller";
|
||||
import { ajax } from "discourse/lib/ajax";
|
||||
import { popupAjaxError } from "discourse/lib/ajax-error";
|
||||
|
||||
export default Controller.extend({
|
||||
saving: false,
|
||||
replaceBadgeOwners: false,
|
||||
|
||||
actions: {
|
||||
massAward() {
|
||||
const file = document.querySelector("#massAwardCSVUpload").files[0];
|
||||
|
||||
if (this.model && file) {
|
||||
const options = {
|
||||
type: "POST",
|
||||
processData: false,
|
||||
contentType: false,
|
||||
data: new FormData()
|
||||
};
|
||||
|
||||
options.data.append("file", file);
|
||||
options.data.append("replace_badge_owners", this.replaceBadgeOwners);
|
||||
|
||||
this.set("saving", true);
|
||||
|
||||
ajax(`/admin/badges/award/${this.model.id}`, options)
|
||||
.then(() => {
|
||||
bootbox.alert(I18n.t("admin.badges.mass_award.success"));
|
||||
})
|
||||
.catch(popupAjaxError)
|
||||
.finally(() => this.set("saving", false));
|
||||
} else {
|
||||
bootbox.alert(I18n.t("admin.badges.mass_award.aborted"));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -1,24 +1,52 @@
|
||||
import discourseComputed, { observes } from "discourse-common/utils/decorators";
|
||||
import { reads } from "@ember/object/computed";
|
||||
import Controller, { inject } from "@ember/controller";
|
||||
import { popupAjaxError } from "discourse/lib/ajax-error";
|
||||
import { bufferedProperty } from "discourse/mixins/buffered-content";
|
||||
import { propertyNotEqual } from "discourse/lib/computed";
|
||||
import computed from "ember-addons/ember-computed-decorators";
|
||||
import { run } from "@ember/runloop";
|
||||
|
||||
export default Ember.Controller.extend(bufferedProperty("model"), {
|
||||
adminBadges: Ember.inject.controller(),
|
||||
export default Controller.extend(bufferedProperty("model"), {
|
||||
adminBadges: inject(),
|
||||
saving: false,
|
||||
savingStatus: "",
|
||||
|
||||
badgeTypes: Ember.computed.alias("adminBadges.badgeTypes"),
|
||||
badgeGroupings: Ember.computed.alias("adminBadges.badgeGroupings"),
|
||||
badgeTriggers: Ember.computed.alias("adminBadges.badgeTriggers"),
|
||||
protectedSystemFields: Ember.computed.alias(
|
||||
"adminBadges.protectedSystemFields"
|
||||
),
|
||||
|
||||
readOnly: Ember.computed.alias("buffered.system"),
|
||||
badgeTypes: reads("adminBadges.badgeTypes"),
|
||||
badgeGroupings: reads("adminBadges.badgeGroupings"),
|
||||
badgeTriggers: reads("adminBadges.badgeTriggers"),
|
||||
protectedSystemFields: reads("adminBadges.protectedSystemFields"),
|
||||
readOnly: reads("buffered.system"),
|
||||
showDisplayName: propertyNotEqual("name", "displayName"),
|
||||
|
||||
@computed("model.query", "buffered.query")
|
||||
init() {
|
||||
this._super(...arguments);
|
||||
|
||||
// this is needed because the model doesnt have default values
|
||||
// and as we are using a bufferedProperty it's not accessible
|
||||
// in any other way
|
||||
run.next(() => {
|
||||
if (this.model) {
|
||||
if (!this.model.badge_type_id) {
|
||||
this.model.set(
|
||||
"badge_type_id",
|
||||
this.get("badgeTypes.firstObject.id")
|
||||
);
|
||||
}
|
||||
|
||||
if (!this.model.badge_grouping_id) {
|
||||
this.model.set(
|
||||
"badge_grouping_id",
|
||||
this.get("badgeGroupings.firstObject.id")
|
||||
);
|
||||
}
|
||||
|
||||
if (!this.model.trigger) {
|
||||
this.model.set("trigger", this.get("badgeTriggers.firstObject.id"));
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
@discourseComputed("model.query", "buffered.query")
|
||||
hasQuery(modelQuery, bufferedQuery) {
|
||||
if (bufferedQuery) {
|
||||
return bufferedQuery.trim().length > 0;
|
||||
@ -26,10 +54,11 @@ export default Ember.Controller.extend(bufferedProperty("model"), {
|
||||
return modelQuery && modelQuery.trim().length > 0;
|
||||
},
|
||||
|
||||
@observes("model.id")
|
||||
_resetSaving: function() {
|
||||
this.set("saving", false);
|
||||
this.set("savingStatus", "");
|
||||
}.observes("model.id"),
|
||||
},
|
||||
|
||||
actions: {
|
||||
save() {
|
||||
|
||||
@ -1 +1,18 @@
|
||||
export default Ember.Controller.extend();
|
||||
import Controller from "@ember/controller";
|
||||
import { inject as service } from "@ember/service";
|
||||
import discourseComputed from "discourse-common/utils/decorators";
|
||||
|
||||
export default Controller.extend({
|
||||
routing: service("-routing"),
|
||||
|
||||
@discourseComputed("routing.currentRouteName")
|
||||
selectedRoute() {
|
||||
const currentRoute = this.routing.currentRouteName;
|
||||
const indexRoute = "adminBadges.index";
|
||||
if (currentRoute === indexRoute) {
|
||||
return "adminBadges.show";
|
||||
} else {
|
||||
return this.routing.currentRouteName;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@ -1,7 +1,9 @@
|
||||
import computed from "ember-addons/ember-computed-decorators";
|
||||
import discourseComputed from "discourse-common/utils/decorators";
|
||||
import { later } from "@ember/runloop";
|
||||
import Controller from "@ember/controller";
|
||||
|
||||
export default Ember.Controller.extend({
|
||||
@computed("model.colors", "onlyOverridden")
|
||||
export default Controller.extend({
|
||||
@discourseComputed("model.colors", "onlyOverridden")
|
||||
colors(allColors, onlyOverridden) {
|
||||
if (onlyOverridden) {
|
||||
return allColors.filter(color => color.get("overridden"));
|
||||
@ -40,7 +42,7 @@ export default Ember.Controller.extend({
|
||||
);
|
||||
}
|
||||
|
||||
Ember.run.later(() => {
|
||||
later(() => {
|
||||
this.set("model.savingStatus", null);
|
||||
}, 2000);
|
||||
|
||||
|
||||
@ -1,20 +1,22 @@
|
||||
import EmberObject from "@ember/object";
|
||||
import Controller from "@ember/controller";
|
||||
import showModal from "discourse/lib/show-modal";
|
||||
import { default as computed } from "ember-addons/ember-computed-decorators";
|
||||
import discourseComputed from "discourse-common/utils/decorators";
|
||||
|
||||
export default Ember.Controller.extend({
|
||||
@computed("model.@each.id")
|
||||
export default Controller.extend({
|
||||
@discourseComputed("model.@each.id")
|
||||
baseColorScheme() {
|
||||
return this.model.findBy("is_base", true);
|
||||
},
|
||||
|
||||
@computed("model.@each.id")
|
||||
@discourseComputed("model.@each.id")
|
||||
baseColorSchemes() {
|
||||
return this.model.filterBy("is_base", true);
|
||||
},
|
||||
|
||||
@computed("baseColorScheme")
|
||||
@discourseComputed("baseColorScheme")
|
||||
baseColors(baseColorScheme) {
|
||||
const baseColorsHash = Ember.Object.create({});
|
||||
const baseColorsHash = EmberObject.create({});
|
||||
baseColorScheme.get("colors").forEach(color => {
|
||||
baseColorsHash.set(color.get("name"), color);
|
||||
});
|
||||
|
||||
@ -0,0 +1,34 @@
|
||||
import discourseComputed from "discourse-common/utils/decorators";
|
||||
import Controller from "@ember/controller";
|
||||
|
||||
export default Controller.extend({
|
||||
@discourseComputed("model.isSaving")
|
||||
saveButtonText(isSaving) {
|
||||
return isSaving ? I18n.t("saving") : I18n.t("admin.customize.save");
|
||||
},
|
||||
|
||||
@discourseComputed("model.changed", "model.isSaving")
|
||||
saveDisabled(changed, isSaving) {
|
||||
return !changed || isSaving;
|
||||
},
|
||||
|
||||
actions: {
|
||||
save() {
|
||||
if (!this.model.saving) {
|
||||
this.set("saving", true);
|
||||
this.model
|
||||
.update(this.model.getProperties("html", "css"))
|
||||
.catch(e => {
|
||||
const msg =
|
||||
e.jqXHR.responseJSON && e.jqXHR.responseJSON.errors
|
||||
? I18n.t("admin.customize.email_style.save_error_with_reason", {
|
||||
error: e.jqXHR.responseJSON.errors.join(". ")
|
||||
})
|
||||
: I18n.t("generic_error");
|
||||
bootbox.alert(msg);
|
||||
})
|
||||
.finally(() => this.set("model.changed", false));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -1,11 +1,19 @@
|
||||
import discourseComputed from "discourse-common/utils/decorators";
|
||||
import Controller from "@ember/controller";
|
||||
import { popupAjaxError } from "discourse/lib/ajax-error";
|
||||
import { bufferedProperty } from "discourse/mixins/buffered-content";
|
||||
import computed from "ember-addons/ember-computed-decorators";
|
||||
|
||||
export default Ember.Controller.extend(bufferedProperty("emailTemplate"), {
|
||||
export default Controller.extend(bufferedProperty("emailTemplate"), {
|
||||
saved: false,
|
||||
|
||||
@computed("buffered")
|
||||
@discourseComputed("buffered.body", "buffered.subject")
|
||||
saveDisabled(body, subject) {
|
||||
return (
|
||||
this.emailTemplate.body === body && this.emailTemplate.subject === subject
|
||||
);
|
||||
},
|
||||
|
||||
@discourseComputed("buffered")
|
||||
hasMultipleSubjects(buffered) {
|
||||
if (buffered.getProperties("subject")["subject"]) {
|
||||
return false;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user