diff --git a/.image_optim.yml b/.image_optim.yml deleted file mode 100644 index 746b85dc3b..0000000000 --- a/.image_optim.yml +++ /dev/null @@ -1,12 +0,0 @@ -skip_missing_workers: true -allow_lossy: false -# PNG -advpng: false -optipng: - level: 2 -pngcrush: false -pngout: false -pngquant: false -# JPG -jpegrecompress: false -timeout: 15 diff --git a/.rubocop.yml b/.rubocop.yml index 792d6e22e1..cd2bcd9762 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,14 +1,108 @@ AllCops: - TargetRubyVersion: 2.3 + TargetRubyVersion: 2.4 + DisabledByDefault: true + Exclude: + - 'bundle/**/*' + - 'vendor/**/*' + - 'node_modules/**/*' -Metrics/LineLength: - Max: 120 +# Prefer &&/|| over and/or. +Style/AndOr: + Enabled: true -Metrics/MethodLength: +# Do not use braces for hash literals when they are the last argument of a +# method call. +Style/BracesAroundHashParameters: + Enabled: true + +# Align `when` with `case`. +Layout/CaseIndentation: + Enabled: true + +# Align comments with method definitions. +Layout/CommentIndentation: + Enabled: true + +# No extra empty lines. +Layout/EmptyLines: + Enabled: true + +# Use Ruby >= 1.9 syntax for hashes. Prefer { a: :b } over { :a => :b }. +Style/HashSyntax: + Enabled: true + +# Two spaces, no tabs (for indentation). +Layout/IndentationWidth: + Enabled: true + +Layout/SpaceAfterColon: + Enabled: true + +Layout/SpaceAfterComma: + Enabled: true + +Layout/SpaceAroundEqualsInParameterDefault: + Enabled: true + +Layout/SpaceAroundKeyword: + Enabled: true + +Layout/SpaceAroundOperators: + Enabled: true + +Layout/SpaceBeforeFirstArg: + Enabled: true + +# Defining a method with parameters needs parentheses. +Style/MethodDefParentheses: + Enabled: true + +# Use `foo {}` not `foo{}`. +Layout/SpaceBeforeBlockBraces: + Enabled: true + +# Use `foo { bar }` not `foo {bar}`. +Layout/SpaceInsideBlockBraces: + Enabled: true + +# Use `{ a: 1 }` not `{a:1}`. +Layout/SpaceInsideHashLiteralBraces: + Enabled: true + +Layout/SpaceInsideParens: + Enabled: true + +# Detect hard tabs, no hard tabs. +Layout/Tab: + Enabled: true + +# Blank lines should not have any spaces. +Layout/TrailingBlankLines: + Enabled: true + +# No trailing whitespace. +Layout/TrailingWhitespace: + Enabled: true + +Lint/BlockAlignment: + Enabled: true + +# Align `end` with the matching keyword or starting expression except for +# assignments, where it should be aligned with the LHS. +Lint/EndAlignment: + Enabled: true + EnforcedStyleAlignWith: variable + +# Use my_method(my_arg) not my_method( my_arg ) or my_method my_arg. +Lint/RequireParentheses: + Enabled: true + +Layout/MultilineMethodCallIndentation: + Enabled: true + EnforcedStyle: indented + +Layout/AlignHash: + Enabled: true + +Bundler/OrderedGems: Enabled: false - -Style/Documentation: - Enabled: false - -Style/FrozenStringLiteralComment: - Enabled: False diff --git a/.travis.yml b/.travis.yml index 5eaaec0902..dc66517428 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,9 +6,7 @@ env: - RUBY_GC_MALLOC_LIMIT=50000000 matrix: - "RAILS_MASTER=0 QUNIT_RUN=0" - - "RAILS_MASTER=1 QUNIT_RUN=0" - "RAILS_MASTER=0 QUNIT_RUN=1" - - "RAILS_MASTER=1 QUNIT_RUN=1" addons: postgresql: 9.5 @@ -20,9 +18,6 @@ addons: - jhead matrix: - allow_failures: - - env: "RAILS_MASTER=1 QUNIT_RUN=0" - - env: "RAILS_MASTER=1 QUNIT_RUN=1" fast_finish: true rvm: @@ -41,7 +36,7 @@ cache: - vendor/bundle before_install: - - gem install bundler + - gem install bundler rubocop - git clone --depth=1 https://github.com/discourse/discourse-backup-uploads-to-s3.git plugins/discourse-backup-uploads-to-s3 - git clone --depth=1 https://github.com/discourse/discourse-spoiler-alert.git plugins/discourse-spoiler-alert - git clone --depth=1 https://github.com/discourse/discourse-cakeday.git plugins/discourse-cakeday @@ -53,6 +48,7 @@ before_install: - eslint --ext .es6 test/javascripts - eslint --ext .es6 plugins/**/assets/javascripts - eslint test/javascripts + - rubocop --parallel before_script: - bundle exec rake db:create db:migrate @@ -62,4 +58,4 @@ install: - bash -c "if [ '$RAILS_MASTER' == '0' ]; then bundle install --without development --deployment --retry=3 --jobs=3; fi" script: - - bash -c "if [ '$QUNIT_RUN' == '0' ]; then bundle exec rspec && bundle exec rake plugin:spec; else bundle exec rake qunit:test['200000']; fi" + - bash -c "if [ '$QUNIT_RUN' == '0' ]; then bundle exec rspec && bundle exec rake plugin:spec; else LOAD_PLUGINS=1 bundle exec rake qunit:test['300000']; fi" diff --git a/Gemfile b/Gemfile index 03dbe0a17b..ba6165e350 100644 --- a/Gemfile +++ b/Gemfile @@ -66,7 +66,7 @@ gem 'aws-sdk', require: false gem 'excon', require: false gem 'unf', require: false -gem 'email_reply_trimmer', '0.1.6' +gem 'email_reply_trimmer', '0.1.7' # TODO Use official image_optim gem once https://github.com/toy/image_optim/pull/149 # is merged. @@ -122,7 +122,6 @@ group :test do gem 'webmock', require: false gem 'fakeweb', '~> 1.3.0', require: false gem 'minitest', require: false - gem 'timecop' # TODO: Remove once we upgrade to Rails 5. gem 'test_after_commit' end @@ -152,6 +151,7 @@ group :development do gem 'binding_of_caller' gem 'annotate' gem 'foreman', require: false + gem 'rubocop', require: false end # this is an optional gem, it provides a high performance replacement @@ -184,13 +184,12 @@ gem 'simple-rss', require: false gem 'stackprof', require: false, platform: :mri gem 'memory_profiler', require: false, platform: :mri -gem 'rmmseg-cpp', require: false +gem 'cppjieba_rb', require: false gem 'logster' gem 'sassc', require: false - if ENV["IMPORT"] == "1" gem 'mysql2' gem 'redcarpet' diff --git a/Gemfile.lock b/Gemfile.lock index c84ad60a34..b15fe8ddc6 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,38 +1,38 @@ GEM remote: https://rubygems.org/ specs: - actionmailer (4.2.8) - actionpack (= 4.2.8) - actionview (= 4.2.8) - activejob (= 4.2.8) + actionmailer (4.2.9) + actionpack (= 4.2.9) + actionview (= 4.2.9) + activejob (= 4.2.9) mail (~> 2.5, >= 2.5.4) rails-dom-testing (~> 1.0, >= 1.0.5) - actionpack (4.2.8) - actionview (= 4.2.8) - activesupport (= 4.2.8) + actionpack (4.2.9) + actionview (= 4.2.9) + activesupport (= 4.2.9) rack (~> 1.6) rack-test (~> 0.6.2) rails-dom-testing (~> 1.0, >= 1.0.5) rails-html-sanitizer (~> 1.0, >= 1.0.2) - actionview (4.2.8) - activesupport (= 4.2.8) + actionview (4.2.9) + activesupport (= 4.2.9) builder (~> 3.1) erubis (~> 2.7.0) rails-dom-testing (~> 1.0, >= 1.0.5) rails-html-sanitizer (~> 1.0, >= 1.0.3) active_model_serializers (0.8.3) activemodel (>= 3.0) - activejob (4.2.8) - activesupport (= 4.2.8) + activejob (4.2.9) + activesupport (= 4.2.9) globalid (>= 0.3.0) - activemodel (4.2.8) - activesupport (= 4.2.8) + activemodel (4.2.9) + activesupport (= 4.2.9) builder (~> 3.1) - activerecord (4.2.8) - activemodel (= 4.2.8) - activesupport (= 4.2.8) + activerecord (4.2.9) + activemodel (= 4.2.9) + activesupport (= 4.2.9) arel (~> 6.0) - activesupport (4.2.8) + activesupport (4.2.9) i18n (~> 0.7) minitest (~> 5.1) thread_safe (~> 0.3, >= 0.3.4) @@ -71,6 +71,7 @@ GEM coderay (1.1.1) concurrent-ruby (1.0.5) connection_pool (2.2.1) + cppjieba_rb (0.3.0) crack (0.4.3) safe_yaml (~> 1.0.0) crass (1.0.2) @@ -84,7 +85,7 @@ GEM image_size (~> 1.5) in_threads (~> 1.3) progress (~> 3.0, >= 3.0.1) - email_reply_trimmer (0.1.6) + email_reply_trimmer (0.1.7) ember-data-source (2.2.1) ember-source (>= 1.8, < 3.0) ember-handlebars-template (0.7.5) @@ -127,7 +128,7 @@ GEM hiredis (0.6.1) htmlentities (4.3.4) http_accept_language (2.0.5) - i18n (0.8.4) + i18n (0.8.6) image_size (1.5.0) in_threads (1.4.0) jmespath (1.3.1) @@ -137,7 +138,7 @@ GEM thor (>= 0.14, < 2.0) jwt (1.5.6) kgio (2.11.0) - libv8 (5.3.332.38.5) + libv8 (5.9.211.38.1) listen (3.1.5) rb-fsevent (~> 0.9, >= 0.9.4) rb-inotify (~> 0.9, >= 0.9.7) @@ -153,12 +154,14 @@ GEM rack (>= 1.1.3) metaclass (0.0.4) method_source (0.8.2) - mime-types (2.99.3) + mime-types (3.1) + mime-types-data (~> 3.2015) + mime-types-data (3.2016.0521) mini_mime (0.1.3) mini_portile2 (2.2.0) - mini_racer (0.1.9) - libv8 (~> 5.3) - minitest (5.10.2) + mini_racer (0.1.11) + libv8 (~> 5.7) + minitest (5.10.3) mocha (1.2.1) metaclass (~> 0.0.1) mock_redis (0.17.3) @@ -211,7 +214,7 @@ GEM omniauth-twitter (1.3.0) omniauth-oauth (~> 1.1) rack - onebox (1.8.14) + onebox (1.8.16) fast_blank (>= 1.0.0) htmlentities (~> 4.3) moneta (~> 1.0) @@ -222,7 +225,11 @@ GEM openid-redis-store (0.0.2) redis ruby-openid + parallel (1.11.2) + parser (2.4.0.0) + ast (~> 2.2) pg (0.20.0) + powerpack (0.1.1) progress (3.3.1) pry (0.10.4) coderay (~> 1.1.0) @@ -245,16 +252,16 @@ GEM rack rack-test (0.6.3) rack (>= 1.0) - rails (4.2.8) - actionmailer (= 4.2.8) - actionpack (= 4.2.8) - actionview (= 4.2.8) - activejob (= 4.2.8) - activemodel (= 4.2.8) - activerecord (= 4.2.8) - activesupport (= 4.2.8) + rails (4.2.9) + actionmailer (= 4.2.9) + actionpack (= 4.2.9) + actionview (= 4.2.9) + activejob (= 4.2.9) + activemodel (= 4.2.9) + activerecord (= 4.2.9) + activesupport (= 4.2.9) bundler (>= 1.3.0, < 2.0) - railties (= 4.2.8) + railties (= 4.2.9) sprockets-rails rails-deprecated_sanitizer (1.0.3) activesupport (>= 4.2.0.alpha) @@ -266,11 +273,13 @@ GEM loofah (~> 2.0) rails_multisite (1.0.6) rails (> 4.2, < 5) - railties (4.2.8) - actionpack (= 4.2.8) - activesupport (= 4.2.8) + railties (4.2.9) + actionpack (= 4.2.9) + activesupport (= 4.2.9) rake (>= 0.8.7) thor (>= 0.18.1, < 2.0) + rainbow (2.2.2) + rake raindrops (0.18.0) rake (12.0.0) rake-compiler (1.0.4) @@ -286,7 +295,6 @@ GEM redis-namespace (1.5.3) redis (~> 3.0, >= 3.0.4) rinku (2.0.2) - rmmseg-cpp (0.2.9) rspec (3.6.0) rspec-core (~> 3.6.0) rspec-expectations (~> 3.6.0) @@ -312,10 +320,18 @@ GEM rspec-support (~> 3.6.0) rspec-support (3.6.0) rtlit (0.0.5) + rubocop (0.49.1) + parallel (~> 1.10) + parser (>= 2.3.3.1, < 3.0) + powerpack (~> 0.1) + rainbow (>= 1.99.1, < 3.0) + ruby-progressbar (~> 1.7) + unicode-display_width (~> 1.0, >= 1.0.1) ruby-ll (2.1.2) ansi ast ruby-openid (2.7.0) + ruby-progressbar (1.8.1) ruby-readability (0.7.0) guess_html_encoding (>= 0.0.4) nokogiri (>= 1.6.0) @@ -363,7 +379,6 @@ GEM thor (0.19.4) thread_safe (0.3.6) tilt (2.0.7) - timecop (0.8.1) trollop (2.1.2) tzinfo (1.2.3) thread_safe (~> 0.1) @@ -372,6 +387,7 @@ GEM unf (0.1.4) unf_ext unf_ext (0.0.7.4) + unicode-display_width (1.3.0) unicorn (5.3.0) kgio (~> 2.6) raindrops (~> 0.7) @@ -395,9 +411,10 @@ DEPENDENCIES bullet byebug certified + cppjieba_rb discourse-qunit-rails discourse_image_optim - email_reply_trimmer (= 0.1.6) + email_reply_trimmer (= 0.1.7) ember-handlebars-template (= 0.7.5) ember-rails (= 0.18.5) ember-source @@ -459,11 +476,11 @@ DEPENDENCIES redis redis-namespace rinku - rmmseg-cpp rspec rspec-html-matchers rspec-rails rtlit + rubocop ruby-readability sanitize sassc @@ -476,11 +493,10 @@ DEPENDENCIES test_after_commit thor tilt - timecop uglifier unf unicorn webmock BUNDLED WITH - 1.15.1 + 1.15.3 diff --git a/Rakefile b/Rakefile index 179ba560eb..7a7f9ee44b 100755 --- a/Rakefile +++ b/Rakefile @@ -9,4 +9,3 @@ Discourse::Application.load_tasks # this prevents crashes when migrating a database in production in certain # PostgreSQL configuations when trying to create structure.sql Rake::Task["db:structure:dump"].clear if Rails.env.production? - diff --git a/app/assets/javascripts/admin/components/admin-directory-toggle.js.es6 b/app/assets/javascripts/admin/components/admin-directory-toggle.js.es6 index e4d19613b5..8d43474a9d 100644 --- a/app/assets/javascripts/admin/components/admin-directory-toggle.js.es6 +++ b/app/assets/javascripts/admin/components/admin-directory-toggle.js.es6 @@ -1,4 +1,4 @@ -import { iconHTML } from 'discourse-common/helpers/fa-icon'; +import { iconHTML } from 'discourse-common/lib/icon-library'; import { bufferedRender } from 'discourse-common/lib/buffered-render'; export default Ember.Component.extend(bufferedRender({ diff --git a/app/assets/javascripts/admin/components/admin-watched-word.js.es6 b/app/assets/javascripts/admin/components/admin-watched-word.js.es6 new file mode 100644 index 0000000000..3c78becb49 --- /dev/null +++ b/app/assets/javascripts/admin/components/admin-watched-word.js.es6 @@ -0,0 +1,19 @@ +import { iconHTML } from 'discourse-common/lib/icon-library'; +import { bufferedRender } from 'discourse-common/lib/buffered-render'; + +export default Ember.Component.extend(bufferedRender({ + classNames: ['watched-word'], + + buildBuffer(buffer) { + buffer.push(iconHTML('times')); + buffer.push(' ' + this.get('word.word')); + }, + + click() { + this.get('word').destroy().then(() => { + this.sendAction('action', this.get('word')); + }).catch(e => { + bootbox.alert(I18n.t("generic_error_with_reason", {error: `http: ${e.status} - ${e.body}`})); + });; + } +})); diff --git a/app/assets/javascripts/admin/components/admin-web-hook-status.js.es6 b/app/assets/javascripts/admin/components/admin-web-hook-status.js.es6 index d176b5a40a..c5f1a4a244 100644 --- a/app/assets/javascripts/admin/components/admin-web-hook-status.js.es6 +++ b/app/assets/javascripts/admin/components/admin-web-hook-status.js.es6 @@ -1,5 +1,5 @@ import computed from 'ember-addons/ember-computed-decorators'; -import { iconHTML } from 'discourse-common/helpers/fa-icon'; +import { iconHTML } from 'discourse-common/lib/icon-library'; import { bufferedRender } from 'discourse-common/lib/buffered-render'; export default Ember.Component.extend(bufferedRender({ diff --git a/app/assets/javascripts/admin/components/resumable-upload.js.es6 b/app/assets/javascripts/admin/components/resumable-upload.js.es6 index 73a46eded3..36f420d100 100644 --- a/app/assets/javascripts/admin/components/resumable-upload.js.es6 +++ b/app/assets/javascripts/admin/components/resumable-upload.js.es6 @@ -1,3 +1,4 @@ +import { iconHTML } from 'discourse-common/lib/icon-library'; import { bufferedRender } from 'discourse-common/lib/buffered-render'; /*global Resumable:true */ @@ -40,7 +41,7 @@ export default Ember.Component.extend(bufferedRender({ buildBuffer(buffer) { const icon = this.get("isUploading") ? "times" : "upload"; - buffer.push(``); + buffer.push(iconHTML(icon)); buffer.push("" + this.get("text") + ""); buffer.push(""); }, diff --git a/app/assets/javascripts/admin/components/watched-word-form.js.es6 b/app/assets/javascripts/admin/components/watched-word-form.js.es6 new file mode 100644 index 0000000000..95f859abf4 --- /dev/null +++ b/app/assets/javascripts/admin/components/watched-word-form.js.es6 @@ -0,0 +1,49 @@ +import WatchedWord from 'admin/models/watched-word'; +import { on, observes } from 'ember-addons/ember-computed-decorators'; + +export default Ember.Component.extend({ + classNames: ['watched-word-form'], + formSubmitted: false, + actionKey: null, + showSuccessMessage: false, + + @observes('word') + removeSuccessMessage() { + if (this.get('showSuccessMessage') && !Ember.isEmpty(this.get('word'))) { + this.set('showSuccessMessage', false); + } + }, + + actions: { + submit() { + if (!this.get('formSubmitted')) { + this.set('formSubmitted', true); + + const watchedWord = WatchedWord.create({ word: this.get('word'), action: this.get('actionKey') }); + + watchedWord.save().then(result => { + this.setProperties({ word: '', formSubmitted: false, showSuccessMessage: true }); + this.sendAction('action', WatchedWord.create(result)); + Ember.run.schedule('afterRender', () => this.$('.watched-word-input').focus()); + }).catch(e => { + this.set('formSubmitted', false); + const msg = (e.responseJSON && e.responseJSON.errors) ? + I18n.t("generic_error_with_reason", {error: e.responseJSON.errors.join('. ')}) : + I18n.t("generic_error"); + bootbox.alert(msg, () => this.$('.watched-word-input').focus()); + }); + } + } + }, + + @on("didInsertElement") + _init() { + Ember.run.schedule('afterRender', () => { + this.$('.watched-word-input').keydown(e => { + if (e.keyCode === 13) { + this.send('submit'); + } + }); + }); + } +}); diff --git a/app/assets/javascripts/admin/components/watched-word-uploader.js.es6 b/app/assets/javascripts/admin/components/watched-word-uploader.js.es6 new file mode 100644 index 0000000000..8d9e6ba0a9 --- /dev/null +++ b/app/assets/javascripts/admin/components/watched-word-uploader.js.es6 @@ -0,0 +1,25 @@ +import computed from "ember-addons/ember-computed-decorators"; +import UploadMixin from "discourse/mixins/upload"; + +export default Em.Component.extend(UploadMixin, { + type: 'csv', + classNames: 'watched-words-uploader', + uploadUrl: '/admin/logs/watched_words/upload', + addDisabled: Em.computed.alias("uploading"), + + validateUploadedFilesOptions() { + return { csvOnly: true }; + }, + + @computed('actionKey') + data(actionKey) { + return { action_key: actionKey }; + }, + + uploadDone() { + if (this) { + bootbox.alert(I18n.t("admin.watched_words.form.upload_successful")); + this.sendAction("done"); + } + } +}); diff --git a/app/assets/javascripts/admin/controllers/admin-group.js.es6 b/app/assets/javascripts/admin/controllers/admin-group.js.es6 index 52dbc07791..cab20f908f 100644 --- a/app/assets/javascripts/admin/controllers/admin-group.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-group.js.es6 @@ -31,10 +31,10 @@ export default Ember.Controller.extend({ ]; }.property(), - @computed('model.visibility_level', 'model.public') - disableMembershipRequestSetting(visibility_level, publicGroup) { + @computed('model.visibility_level', 'model.public_admission') + disableMembershipRequestSetting(visibility_level, publicAdmission) { visibility_level = parseInt(visibility_level); - return (visibility_level !== 0) || publicGroup; + return (visibility_level !== 0) || publicAdmission; }, @computed('model.visibility_level', 'model.allow_membership_requests') diff --git a/app/assets/javascripts/admin/controllers/admin-watched-words-action.js.es6 b/app/assets/javascripts/admin/controllers/admin-watched-words-action.js.es6 new file mode 100644 index 0000000000..bd11f15fcf --- /dev/null +++ b/app/assets/javascripts/admin/controllers/admin-watched-words-action.js.es6 @@ -0,0 +1,65 @@ +import computed from 'ember-addons/ember-computed-decorators'; +import WatchedWord from 'admin/models/watched-word'; + +export default Ember.Controller.extend({ + actionNameKey: null, + adminWatchedWords: Ember.inject.controller(), + showWordsList: Ember.computed.or('adminWatchedWords.filtered', 'adminWatchedWords.showWords'), + + findAction(actionName) { + return (this.get('adminWatchedWords.model') || []).findBy('nameKey', actionName); + }, + + @computed('adminWatchedWords.model', 'actionNameKey') + filteredContent() { + if (!this.get('actionNameKey')) { return []; } + + const a = this.findAction(this.get('actionNameKey')); + return a ? a.words : []; + }, + + @computed('actionNameKey') + actionDescription(actionNameKey) { + return I18n.t('admin.watched_words.action_descriptions.' + actionNameKey); + }, + + actions: { + recordAdded(arg) { + const a = this.findAction(this.get('actionNameKey')); + if (a) { + a.words.unshiftObject(arg); + a.incrementProperty('count'); + Em.run.schedule('afterRender', () => { + // remove from other actions lists + let match = null; + this.get('adminWatchedWords.model').forEach(action => { + if (match) return; + + if (action.nameKey !== this.get('actionNameKey')) { + match = action.words.findBy('id', arg.id); + if (match) { + action.words.removeObject(match); + action.decrementProperty('count'); + } + } + }); + }); + } + }, + + recordRemoved(arg) { + const a = this.findAction(this.get('actionNameKey')); + if (a) { + a.words.removeObject(arg); + a.decrementProperty('count'); + } + }, + + uploadComplete() { + WatchedWord.findAll().then(data => { + this.set('adminWatchedWords.model', data); + }); + } + } + +}); diff --git a/app/assets/javascripts/admin/controllers/admin-watched-words.js.es6 b/app/assets/javascripts/admin/controllers/admin-watched-words.js.es6 new file mode 100644 index 0000000000..07d53fd230 --- /dev/null +++ b/app/assets/javascripts/admin/controllers/admin-watched-words.js.es6 @@ -0,0 +1,51 @@ +import debounce from 'discourse/lib/debounce'; + +export default Ember.Controller.extend({ + filter: null, + filtered: false, + showWords: false, + disableShowWords: Ember.computed.alias('filtered'), + + filterContentNow() { + + if (!!Ember.isEmpty(this.get('allWatchedWords'))) return; + + let filter; + if (this.get('filter')) { + filter = this.get('filter').toLowerCase(); + } + + if (filter === undefined || filter.length < 1) { + this.set('model', this.get('allWatchedWords')); + return; + } + + const matchesByAction = []; + + this.get('allWatchedWords').forEach(wordsForAction => { + const wordRecords = wordsForAction.words.filter(wordRecord => { + return (wordRecord.word.indexOf(filter) > -1); + }); + matchesByAction.pushObject( Ember.Object.create({ + nameKey: wordsForAction.nameKey, + name: wordsForAction.name, + words: wordRecords, + count: wordRecords.length + }) ); + }); + + this.set('model', matchesByAction); + }, + + filterContent: debounce(function() { + this.filterContentNow(); + this.set('filtered', !Ember.isEmpty(this.get('filter'))); + }, 250).observes('filter'), + + actions: { + clearFilter() { + this.setProperties({ filter: '' }); + } + } + +}); diff --git a/app/assets/javascripts/admin/helpers/check-icon.js.es6 b/app/assets/javascripts/admin/helpers/check-icon.js.es6 new file mode 100644 index 0000000000..0a68c0be0b --- /dev/null +++ b/app/assets/javascripts/admin/helpers/check-icon.js.es6 @@ -0,0 +1,7 @@ +import { registerUnbound } from 'discourse-common/lib/helpers'; +import { renderIcon } from 'discourse-common/lib/icon-library'; + +registerUnbound('check-icon', function(value) { + let icon = value ? "check" : "times"; + return new Handlebars.SafeString(renderIcon('string', icon)); +}); diff --git a/app/assets/javascripts/admin/models/admin-user.js.es6 b/app/assets/javascripts/admin/models/admin-user.js.es6 index 0720976e69..662f0b046c 100644 --- a/app/assets/javascripts/admin/models/admin-user.js.es6 +++ b/app/assets/javascripts/admin/models/admin-user.js.es6 @@ -1,3 +1,4 @@ +import { iconHTML } from 'discourse-common/lib/icon-library'; import { ajax } from 'discourse/lib/ajax'; import computed from 'ember-addons/ember-computed-decorators'; import { propertyNotEqual } from 'discourse/lib/computed'; @@ -108,7 +109,7 @@ const AdminUser = Discourse.User.extend({ "class": "cancel-inline", "link": true }, { - "label": ' ' + I18n.t("admin.user.delete_all_posts"), + "label": `${iconHTML('exclamation-triangle')} ` + I18n.t("admin.user.delete_all_posts"), "class": "btn btn-danger", "callback": function() { ajax("/admin/users/" + user.get('id') + "/delete_all_posts", { @@ -337,7 +338,7 @@ const AdminUser = Discourse.User.extend({ "class": "cancel", "link": true }, { - "label": '' + I18n.t('admin.user.block_accept'), + "label": `${iconHTML('exclamation-triangle')} ` + I18n.t('admin.user.block_accept'), "class": "btn btn-danger", "callback": function() { performBlock(); } }]; @@ -386,7 +387,7 @@ const AdminUser = Discourse.User.extend({ "class": "cancel", "link": true }, { - "label": '' + I18n.t('admin.user.anonymize_yes'), + "label": `${iconHTML('exclamation-triangle')} ` + I18n.t('admin.user.anonymize_yes'), "class": "btn btn-danger", "callback": function() { performAnonymize(); } }]; @@ -450,7 +451,7 @@ const AdminUser = Discourse.User.extend({ "class": "btn", "link": true }, { - "label": '' + I18n.t('admin.user.delete_and_block'), + "label": `${iconHTML('exclamation-triangle')} ` + I18n.t('admin.user.delete_and_block'), "class": "btn btn-danger", "callback": function(){ performDestroy(true); } }, { @@ -479,7 +480,7 @@ const AdminUser = Discourse.User.extend({ "class": "cancel-inline", "link": true }, { - "label": ' ' + I18n.t("flagging.yes_delete_spammer"), + "label": `${iconHTML('exclamation-triangle')} ` + I18n.t("flagging.yes_delete_spammer"), "class": "btn btn-danger", "callback": function() { return ajax("/admin/users/" + user.get('id') + '.json', { diff --git a/app/assets/javascripts/admin/models/flagged-post.js.es6 b/app/assets/javascripts/admin/models/flagged-post.js.es6 index c7a654608f..7a627f1569 100644 --- a/app/assets/javascripts/admin/models/flagged-post.js.es6 +++ b/app/assets/javascripts/admin/models/flagged-post.js.es6 @@ -2,7 +2,7 @@ import { ajax } from 'discourse/lib/ajax'; import AdminUser from 'admin/models/admin-user'; import Topic from 'discourse/models/topic'; import Post from 'discourse/models/post'; - +import { iconHTML } from 'discourse-common/lib/icon-library'; const FlaggedPost = Post.extend({ @@ -35,13 +35,14 @@ const FlaggedPost = Post.extend({ dispositionIcon: function (disposition) { if (!disposition) { return null; } - var icon, title = I18n.t('admin.flags.dispositions.' + disposition); + let icon; + let title = 'admin.flags.dispositions.' + disposition; switch (disposition) { - case "deferred": { icon = "fa-external-link"; break; } - case "agreed": { icon = "fa-thumbs-o-up"; break; } - case "disagreed": { icon = "fa-thumbs-o-down"; break; } + case "deferred": { icon = "external-link"; break; } + case "agreed": { icon = "thumbs-o-up"; break; } + case "disagreed": { icon = "thumbs-o-down"; break; } } - return ""; + return iconHTML(icon, { title }); }, wasEdited: function () { diff --git a/app/assets/javascripts/admin/models/watched-word.js.es6 b/app/assets/javascripts/admin/models/watched-word.js.es6 new file mode 100644 index 0000000000..c0418aef97 --- /dev/null +++ b/app/assets/javascripts/admin/models/watched-word.js.es6 @@ -0,0 +1,37 @@ +import { ajax } from 'discourse/lib/ajax'; + +const WatchedWord = Discourse.Model.extend({ + save() { + return ajax("/admin/logs/watched_words" + (this.id ? '/' + this.id : '') + ".json", { + type: this.id ? 'PUT' : 'POST', + data: {word: this.get('word'), action_key: this.get('action')}, + dataType: 'json' + }); + }, + + destroy() { + return ajax("/admin/logs/watched_words/" + this.get('id') + ".json", {type: 'DELETE'}); + } +}); + +WatchedWord.reopenClass({ + findAll() { + return ajax("/admin/logs/watched_words").then(function (list) { + const actions = {}; + list.words.forEach(s => { + if (!actions[s.action]) { actions[s.action] = []; } + actions[s.action].pushObject(WatchedWord.create(s)); + }); + + list.actions.forEach(a => { + if (!actions[a]) { actions[a] = []; } + }); + + return Object.keys(actions).map(function(n) { + return Ember.Object.create({nameKey: n, name: I18n.t('admin.watched_words.actions.' + n), words: actions[n], count: actions[n].length}); + }); + }); + } +}); + +export default WatchedWord; diff --git a/app/assets/javascripts/admin/models/web-hook.js.es6 b/app/assets/javascripts/admin/models/web-hook.js.es6 index 325d931045..27d72878b0 100644 --- a/app/assets/javascripts/admin/models/web-hook.js.es6 +++ b/app/assets/javascripts/admin/models/web-hook.js.es6 @@ -37,7 +37,7 @@ export default RestModel.extend({ }, groupFinder(term) { - return Group.findAll({search: term, ignore_automatic: false}); + return Group.findAll({ term: term, ignore_automatic: false }); }, @computed('wildcard_web_hook', 'web_hook_event_types.[]') @@ -82,4 +82,3 @@ export default RestModel.extend({ return this.createProperties(); } }); - diff --git a/app/assets/javascripts/admin/routes/admin-route-map.js.es6 b/app/assets/javascripts/admin/routes/admin-route-map.js.es6 index dd87207156..34e55914ad 100644 --- a/app/assets/javascripts/admin/routes/admin-route-map.js.es6 +++ b/app/assets/javascripts/admin/routes/admin-route-map.js.es6 @@ -62,6 +62,10 @@ export default function() { this.route('screenedEmails', { path: '/screened_emails' }); this.route('screenedIpAddresses', { path: '/screened_ip_addresses' }); this.route('screenedUrls', { path: '/screened_urls' }); + this.route('adminWatchedWords', { path: '/watched_words', resetNamespace: true}, function() { + this.route('index', { path: '/' } ); + this.route('action', { path: '/action/:action_id' } ); + }); }); this.route('adminGroups', { path: '/groups', resetNamespace: true }, function() { diff --git a/app/assets/javascripts/admin/routes/admin-watched-words-action.js.es6 b/app/assets/javascripts/admin/routes/admin-watched-words-action.js.es6 new file mode 100644 index 0000000000..123884d6fc --- /dev/null +++ b/app/assets/javascripts/admin/routes/admin-watched-words-action.js.es6 @@ -0,0 +1,11 @@ +export default Discourse.Route.extend({ + model(params) { + this.controllerFor('adminWatchedWordsAction').set('actionNameKey', params.action_id); + let filteredContent = this.controllerFor('adminWatchedWordsAction').get('filteredContent'); + return Ember.Object.create({ + nameKey: params.action_id, + name: I18n.t('admin.watched_words.actions.' + params.action_id), + words: filteredContent + }); + } +}); diff --git a/app/assets/javascripts/admin/routes/admin-watched-words-index.js.es6 b/app/assets/javascripts/admin/routes/admin-watched-words-index.js.es6 new file mode 100644 index 0000000000..0103744b52 --- /dev/null +++ b/app/assets/javascripts/admin/routes/admin-watched-words-index.js.es6 @@ -0,0 +1,5 @@ +export default Discourse.Route.extend({ + beforeModel() { + this.replaceWith('adminWatchedWords.action', this.modelFor('adminWatchedWords')[0].nameKey); + } +}); diff --git a/app/assets/javascripts/admin/routes/admin-watched-words.js.es6 b/app/assets/javascripts/admin/routes/admin-watched-words.js.es6 new file mode 100644 index 0000000000..77f9082b57 --- /dev/null +++ b/app/assets/javascripts/admin/routes/admin-watched-words.js.es6 @@ -0,0 +1,15 @@ +import WatchedWord from 'admin/models/watched-word'; + +export default Discourse.Route.extend({ + queryParams: { + filter: { replace: true } + }, + + model() { + return WatchedWord.findAll(); + }, + + afterModel(watchedWordsList) { + this.controllerFor('adminWatchedWords').set('allWatchedWords', watchedWordsList); + } +}); diff --git a/app/assets/javascripts/admin/templates/api-keys.hbs b/app/assets/javascripts/admin/templates/api-keys.hbs index 60449052bb..b25eebd69a 100644 --- a/app/assets/javascripts/admin/templates/api-keys.hbs +++ b/app/assets/javascripts/admin/templates/api-keys.hbs @@ -29,5 +29,5 @@ {{/if}} {{#unless hasMasterKey}} - + {{/unless}} diff --git a/app/assets/javascripts/admin/templates/badges-index.hbs b/app/assets/javascripts/admin/templates/badges-index.hbs index c94f59fe10..674eae8de2 100644 --- a/app/assets/javascripts/admin/templates/badges-index.hbs +++ b/app/assets/javascripts/admin/templates/badges-index.hbs @@ -3,7 +3,7 @@
- {{i18n "admin.customize.theme.license"}} {{fa-icon "copyright"}} + {{i18n "admin.customize.theme.license"}} {{d-icon "copyright"}}
{{/if}} {{/if}} @@ -133,8 +133,8 @@ {{/if}} {{/if}} - {{fa-icon 'desktop'}}{{i18n 'admin.customize.theme.preview'}} - {{fa-icon "download"}} {{i18n 'admin.export_json.button_text'}} + {{d-icon 'desktop'}}{{i18n 'admin.customize.theme.preview'}} + {{d-icon "download"}} {{i18n 'admin.export_json.button_text'}} {{d-button action="destroy" label="admin.customize.delete" icon="trash" class="btn-danger"}} diff --git a/app/assets/javascripts/admin/templates/customize-themes.hbs b/app/assets/javascripts/admin/templates/customize-themes.hbs index 43129006a5..c9474917ef 100644 --- a/app/assets/javascripts/admin/templates/customize-themes.hbs +++ b/app/assets/javascripts/admin/templates/customize-themes.hbs @@ -7,10 +7,10 @@ {{#link-to 'adminCustomizeThemes.show' theme replace=true}} {{theme.name}} {{#if theme.user_selectable}} - {{fa-icon "user"}} + {{d-icon "user"}} {{/if}} {{#if theme.default}} - {{fa-icon "asterisk"}} + {{d-icon "asterisk"}} {{/if}} {{/link-to}} diff --git a/app/assets/javascripts/admin/templates/dashboard.hbs b/app/assets/javascripts/admin/templates/dashboard.hbs index c6c4487009..b8c0297d48 100644 --- a/app/assets/javascripts/admin/templates/dashboard.hbs +++ b/app/assets/javascripts/admin/templates/dashboard.hbs @@ -29,15 +29,15 @@| {{fa-icon "shield"}} {{i18n 'admin.dashboard.admins'}} | +{{d-icon "shield"}} {{i18n 'admin.dashboard.admins'}} | {{#link-to 'adminUsersList.show' 'admins'}}{{admins}}{{/link-to}} | -{{fa-icon "ban"}} {{i18n 'admin.dashboard.suspended'}} | +{{d-icon "ban"}} {{i18n 'admin.dashboard.suspended'}} | {{#link-to 'adminUsersList.show' 'suspended'}}{{suspended}}{{/link-to}} |
| {{fa-icon "shield"}} {{i18n 'admin.dashboard.moderators'}} | +{{d-icon "shield"}} {{i18n 'admin.dashboard.moderators'}} | {{#link-to 'adminUsersList.show' 'moderators'}}{{moderators}}{{/link-to}} | -{{fa-icon "ban"}} {{i18n 'admin.dashboard.blocked'}} | +{{d-icon "ban"}} {{i18n 'admin.dashboard.blocked'}} | {{#link-to 'adminUsersList.show' 'blocked'}}{{blocked}}{{/link-to}} |