diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 51fd199db1..c02b8d1947 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,5 +1,5 @@ name: CI - + on: push: branches: @@ -7,7 +7,7 @@ on: pull_request: branches-ignore: - 'tests-passed' - + jobs: build: name: "${{ matrix.target }}-${{ matrix.build_types }}" @@ -31,14 +31,14 @@ jobs: build_types: [ 'BACKEND', 'FRONTEND', 'LINT' ] target: [ 'PLUGINS', 'CORE' ] os: [ ubuntu-latest ] - ruby: [ '2.6.3' ] + ruby: [ '2.6' ] postgres: [ '10' ] redis: [ '4.x' ] services: postgres: image: postgres:${{ matrix.postgres }} - ports: + ports: - 5432:5432 env: POSTGRES_USER: discourse @@ -88,14 +88,14 @@ jobs: 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 @@ -113,7 +113,7 @@ jobs: run: bin/rake plugin:install_all_official - name: Create database - if: env.BUILD_TYPE != 'LINT' + if: env.BUILD_TYPE != 'LINT' run: bin/rake db:create && bin/rake db:migrate - name: Create parallel databases @@ -123,7 +123,7 @@ jobs: - 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 @@ -133,7 +133,7 @@ jobs: 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 @@ -146,14 +146,13 @@ jobs: 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 - diff --git a/.gitignore b/.gitignore index 5bc058976e..59ad3c0b6f 100644 --- a/.gitignore +++ b/.gitignore @@ -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 diff --git a/.ruby-version.sample b/.ruby-version.sample index 6a6a3d8e35..57cf282ebb 100644 --- a/.ruby-version.sample +++ b/.ruby-version.sample @@ -1 +1 @@ -2.6.1 +2.6.5 diff --git a/Gemfile b/Gemfile index 205de4ccbe..1c801d8f3f 100644 --- a/Gemfile +++ b/Gemfile @@ -131,6 +131,12 @@ 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 diff --git a/Gemfile.lock b/Gemfile.lock index 14190dfb35..3f7ddc6a69 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -45,16 +45,16 @@ GEM rake (>= 10.4, < 14.0) ast (2.4.0) aws-eventstream (1.0.3) - aws-partitions (1.256.0) - aws-sdk-core (3.86.0) + aws-partitions (1.266.0) + aws-sdk-core (3.89.1) aws-eventstream (~> 1.0, >= 1.0.2) aws-partitions (~> 1, >= 1.239.0) aws-sigv4 (~> 1.1) jmespath (~> 1.0) - aws-sdk-kms (1.27.0) + aws-sdk-kms (1.28.0) aws-sdk-core (~> 3, >= 3.71.0) aws-sigv4 (~> 1.1) - aws-sdk-s3 (1.59.1) + aws-sdk-s3 (1.60.1) aws-sdk-core (~> 3, >= 3.83.0) aws-sdk-kms (~> 1) aws-sigv4 (~> 1.1) @@ -75,10 +75,10 @@ GEM bootsnap (1.4.5) msgpack (~> 1.0) builder (3.2.4) - bullet (6.0.2) + bullet (6.1.0) activesupport (>= 3.0.0) uniform_notifier (~> 1.11) - byebug (11.0.1) + byebug (11.1.0) cbor (0.5.9.6) certified (1.0.0) chunky_png (1.3.11) @@ -86,12 +86,12 @@ GEM colored2 (3.1.2) concurrent-ruby (1.1.5) connection_pool (2.2.2) - cose (0.9.0) + cose (0.10.0) cbor (~> 0.5.9) cppjieba_rb (0.3.3) crack (0.4.3) safe_yaml (~> 1.0.0) - crass (1.0.5) + crass (1.0.6) css_parser (1.7.1) addressable debug_inspector (0.0.3) @@ -120,7 +120,7 @@ GEM railties (>= 3.1) ember-source (2.18.2) erubi (1.9.0) - excon (0.71.0) + excon (0.71.1) execjs (2.7.0) exifr (1.3.6) fabrication (2.21.0) @@ -133,7 +133,7 @@ GEM rake-compiler fast_xs (0.8.0) fastimage (2.1.7) - ffi (1.11.3) + ffi (1.12.1) flamegraph (0.9.5) fspath (3.1.2) gc_tracer (1.5.1) @@ -146,10 +146,10 @@ GEM hkdf (0.3.0) htmlentities (4.3.4) http_accept_language (2.1.1) - i18n (1.7.0) + i18n (1.8.2) concurrent-ruby (~> 1.0) image_size (1.5.0) - in_threads (1.5.3) + in_threads (1.5.4) jaro_winkler (1.5.4) jmespath (1.4.0) jquery-rails (4.3.5) @@ -187,14 +187,14 @@ GEM method_source (0.9.2) mini_mime (1.0.2) mini_portile2 (2.4.0) - mini_racer (0.2.8) + mini_racer (0.2.9) libv8 (>= 6.9.411) 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.13.0) + minitest (5.14.0) mocha (1.8.0) metaclass (~> 0.0.1) mock_redis (0.22.0) @@ -215,7 +215,7 @@ GEM multi_json (~> 1.3) multi_xml (~> 0.5) rack (>= 1.2, < 3) - oj (3.10.0) + oj (3.10.1) omniauth (1.9.0) hashie (>= 3.4.6, < 3.7.0) rack (>= 1.6.2, < 3) @@ -243,7 +243,8 @@ GEM omniauth-twitter (1.4.0) omniauth-oauth (~> 1.1) rack - onebox (1.9.24) + onebox (1.9.25) + addressable (~> 2.7.0) htmlentities (~> 4.3) multi_json (~> 1.11) mustache @@ -256,9 +257,9 @@ GEM parallel (1.19.1) parallel_tests (2.30.0) parallel - parser (2.6.5.0) + parser (2.7.0.2) ast (~> 2.4.0) - pg (1.1.4) + pg (1.2.2) progress (3.5.2) pry (0.12.2) coderay (~> 1.1.0) @@ -267,7 +268,7 @@ GEM pry (>= 0.9.10, < 0.13.0) pry-rails (0.3.9) pry (>= 0.10.4) - public_suffix (4.0.1) + public_suffix (4.0.3) puma (4.3.1) nio4r (~> 2.0) r2 (0.2.7) @@ -277,7 +278,7 @@ GEM rack-openid (1.3.1) rack (>= 1.1.0) ruby-openid (>= 2.1.8) - rack-protection (2.0.7) + rack-protection (2.0.8.1) rack rack-test (1.1.0) rack (>= 1.0, < 3) @@ -298,10 +299,10 @@ GEM rainbow (3.0.0) raindrops (0.19.0) rake (13.0.1) - rake-compiler (1.0.8) + 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) @@ -311,7 +312,7 @@ GEM 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) rinku (2.0.6) rotp (5.1.0) @@ -324,15 +325,15 @@ GEM rspec-core (~> 3.9.0) rspec-expectations (~> 3.9.0) rspec-mocks (~> 3.9.0) - rspec-core (3.9.0) - rspec-support (~> 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.9.0) rspec-html-matchers (0.9.2) nokogiri (~> 1) rspec (>= 3.0.0.a, < 4) - rspec-mocks (3.9.0) + rspec-mocks (3.9.1) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.9.0) rspec-rails (4.0.0.beta2) @@ -343,12 +344,12 @@ GEM rspec-expectations (~> 3.8) rspec-mocks (~> 3.8) rspec-support (~> 3.8) - rspec-support (3.9.0) + rspec-support (3.9.2) rtlit (0.0.5) - rubocop (0.77.0) + rubocop (0.79.0) jaro_winkler (~> 1.5.1) parallel (~> 1.10) - parser (>= 2.6) + parser (>= 2.7.0.1) rainbow (>= 2.2.2, < 4.0) ruby-progressbar (~> 1.7) unicode-display_width (>= 1.4.0, < 1.7) @@ -378,9 +379,9 @@ GEM seed-fu (2.3.9) activerecord (>= 3.1) activesupport (>= 3.1) - shoulda-matchers (4.1.2) + shoulda-matchers (4.2.0) activesupport (>= 4.2.0) - sidekiq (6.0.3) + sidekiq (6.0.4) connection_pool (>= 2.2.2) rack (>= 2.0.0) rack-protection (>= 2.0.0) @@ -398,20 +399,20 @@ GEM activesupport (>= 4.0) sprockets (>= 3.0.0) sshkey (2.0.0) - stackprof (0.2.14) - test-prof (0.10.1) + stackprof (0.2.15) + test-prof (0.10.2) thor (1.0.1) thread_safe (0.3.6) tilt (2.0.10) - tzinfo (1.2.5) + tzinfo (1.2.6) thread_safe (~> 0.1) 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.2) kgio (~> 2.6) raindrops (~> 0.7) uniform_notifier (1.13.0) @@ -511,6 +512,7 @@ DEPENDENCIES pry-rails puma r2 + rack (= 2.0.8) rack-mini-profiler rack-protection rails_multisite diff --git a/README.md b/README.md index 0714caf896..323a21e73b 100644 --- a/README.md +++ b/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 diff --git a/app/assets/javascripts/admin-login/admin-login.js.es6 b/app/assets/javascripts/admin-login/admin-login.js.es6 deleted file mode 100644 index e7404b386a..0000000000 --- a/app/assets/javascripts/admin-login/admin-login.js.es6 +++ /dev/null @@ -1,46 +0,0 @@ -import { getWebauthnCredential } from "discourse/lib/webauthn"; - -export default function() { - document.getElementById( - "activate-security-key-alternative" - ).onclick = function() { - document.getElementById("second-factor-forms").style.display = "block"; - document.getElementById("primary-security-key-form").style.display = "none"; - }; - - document.getElementById("submit-security-key").onclick = function(e) { - e.preventDefault(); - getWebauthnCredential( - document.getElementById("security-key-challenge").value, - document - .getElementById("security-key-allowed-credential-ids") - .value.split(","), - credentialData => { - document.getElementById( - "security-key-credential" - ).value = JSON.stringify(credentialData); - e.target.parentElement.submit(); - }, - errorMessage => { - document.getElementById("security-key-error").innerText = errorMessage; - } - ); - }; - - const useTotp = I18n.t("login.second_factor_toggle.totp"); - const useBackup = I18n.t("login.second_factor_toggle.backup_code"); - const backupForm = document.getElementById("backup-second-factor-form"); - const primaryForm = document.getElementById("primary-second-factor-form"); - document.getElementById("toggle-form").onclick = function(event) { - event.preventDefault(); - if (backupForm.style.display === "none") { - backupForm.style.display = "block"; - primaryForm.style.display = "none"; - document.getElementById("toggle-form").innerHTML = useTotp; - } else { - backupForm.style.display = "none"; - primaryForm.style.display = "block"; - document.getElementById("toggle-form").innerHTML = useBackup; - } - }; -} diff --git a/app/assets/javascripts/admin-login/admin-login.no-module.js.es6 b/app/assets/javascripts/admin-login/admin-login.no-module.js.es6 deleted file mode 100644 index 4de268433d..0000000000 --- a/app/assets/javascripts/admin-login/admin-login.no-module.js.es6 +++ /dev/null @@ -1 +0,0 @@ -require("admin-login/admin-login").default(); diff --git a/app/assets/javascripts/admin/components/admin-report.js.es6 b/app/assets/javascripts/admin/components/admin-report.js.es6 index cb70fc07ff..b7a27a9325 100644 --- a/app/assets/javascripts/admin/components/admin-report.js.es6 +++ b/app/assets/javascripts/admin/components/admin-report.js.es6 @@ -8,7 +8,7 @@ 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 Report, { SCHEMA_VERSION } from "admin/models/report"; import ENV from "discourse-common/config/environment"; const TABLE_OPTIONS = { diff --git a/app/assets/javascripts/admin/components/admin-theme-editor.js.es6 b/app/assets/javascripts/admin/components/admin-theme-editor.js.es6 index 381ac0ca47..0299c04715 100644 --- a/app/assets/javascripts/admin/components/admin-theme-editor.js.es6 +++ b/app/assets/javascripts/admin/components/admin-theme-editor.js.es6 @@ -1,6 +1,6 @@ import { next } from "@ember/runloop"; import Component from "@ember/component"; -import { default as discourseComputed } from "discourse-common/utils/decorators"; +import discourseComputed from "discourse-common/utils/decorators"; import { fmt } from "discourse/lib/computed"; export default Component.extend({ diff --git a/app/assets/javascripts/admin/components/admin-user-field-item.js.es6 b/app/assets/javascripts/admin/components/admin-user-field-item.js.es6 index a392196f8a..a02404de98 100644 --- a/app/assets/javascripts/admin/components/admin-user-field-item.js.es6 +++ b/app/assets/javascripts/admin/components/admin-user-field-item.js.es6 @@ -7,8 +7,7 @@ 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 discourseComputed, +import discourseComputed, { observes, on } from "discourse-common/utils/decorators"; diff --git a/app/assets/javascripts/admin/components/color-input.js.es6 b/app/assets/javascripts/admin/components/color-input.js.es6 index 6c02f78dd5..8648506bf9 100644 --- a/app/assets/javascripts/admin/components/color-input.js.es6 +++ b/app/assets/javascripts/admin/components/color-input.js.es6 @@ -1,6 +1,7 @@ import { schedule } from "@ember/runloop"; import Component from "@ember/component"; -import { default as loadScript, loadCSS } from "discourse/lib/load-script"; +import loadScript, { loadCSS } from "discourse/lib/load-script"; +import { observes } from "discourse-common/utils/decorators"; /** An input field for a color. @@ -11,6 +12,8 @@ import { default as loadScript, loadCSS } from "discourse/lib/load-script"; **/ export default Component.extend({ classNames: ["color-picker"], + + @observes("hexValue", "brightnessValue", "valid") hexValueChanged: function() { var hex = this.hexValue; let text = this.element.querySelector("input.hex-input"); @@ -33,7 +36,7 @@ export default Component.extend({ } else { text.setAttribute("style", ""); } - }.observes("hexValue", "brightnessValue", "valid"), + }, didInsertElement() { loadScript("/javascripts/spectrum.js").then(() => { diff --git a/app/assets/javascripts/admin/components/inline-edit-checkbox.js.es6 b/app/assets/javascripts/admin/components/inline-edit-checkbox.js.es6 index ff3cb98ffc..2eb2c3ee5a 100644 --- a/app/assets/javascripts/admin/components/inline-edit-checkbox.js.es6 +++ b/app/assets/javascripts/admin/components/inline-edit-checkbox.js.es6 @@ -1,8 +1,5 @@ import Component from "@ember/component"; -import { - default as discourseComputed, - observes -} from "discourse-common/utils/decorators"; +import discourseComputed, { observes } from "discourse-common/utils/decorators"; export default Component.extend({ classNames: ["inline-edit"], diff --git a/app/assets/javascripts/admin/components/ip-lookup.js.es6 b/app/assets/javascripts/admin/components/ip-lookup.js.es6 index 06421e2e90..b27191d617 100644 --- a/app/assets/javascripts/admin/components/ip-lookup.js.es6 +++ b/app/assets/javascripts/admin/components/ip-lookup.js.es6 @@ -1,7 +1,7 @@ import EmberObject from "@ember/object"; import { later } from "@ember/runloop"; import Component from "@ember/component"; -import { default as discourseComputed } from "discourse-common/utils/decorators"; +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"; diff --git a/app/assets/javascripts/admin/components/permalink-form.js.es6 b/app/assets/javascripts/admin/components/permalink-form.js.es6 index 5a90dd9913..34d222d30f 100644 --- a/app/assets/javascripts/admin/components/permalink-form.js.es6 +++ b/app/assets/javascripts/admin/components/permalink-form.js.es6 @@ -1,6 +1,6 @@ import { schedule } from "@ember/runloop"; import Component from "@ember/component"; -import { default as discourseComputed } from "discourse-common/utils/decorators"; +import discourseComputed from "discourse-common/utils/decorators"; import { fmt } from "discourse/lib/computed"; import Permalink from "admin/models/permalink"; diff --git a/app/assets/javascripts/admin/components/report-filters/category.js.es6 b/app/assets/javascripts/admin/components/report-filters/category.js.es6 index 60fb61be9b..7cc490cda4 100644 --- a/app/assets/javascripts/admin/components/report-filters/category.js.es6 +++ b/app/assets/javascripts/admin/components/report-filters/category.js.es6 @@ -1,5 +1,5 @@ import Category from "discourse/models/category"; -import { default as discourseComputed } from "discourse-common/utils/decorators"; +import discourseComputed from "discourse-common/utils/decorators"; import FilterComponent from "admin/components/report-filters/filter"; export default FilterComponent.extend({ diff --git a/app/assets/javascripts/admin/components/report-filters/group.js.es6 b/app/assets/javascripts/admin/components/report-filters/group.js.es6 index 0821cb084e..a6511e75f6 100644 --- a/app/assets/javascripts/admin/components/report-filters/group.js.es6 +++ b/app/assets/javascripts/admin/components/report-filters/group.js.es6 @@ -1,5 +1,5 @@ import FilterComponent from "admin/components/report-filters/filter"; -import { default as discourseComputed } from "discourse-common/utils/decorators"; +import discourseComputed from "discourse-common/utils/decorators"; export default FilterComponent.extend({ classNames: ["group-filter"], diff --git a/app/assets/javascripts/admin/components/resumable-upload.js.es6 b/app/assets/javascripts/admin/components/resumable-upload.js.es6 index a9c071f33f..830e560252 100644 --- a/app/assets/javascripts/admin/components/resumable-upload.js.es6 +++ b/app/assets/javascripts/admin/components/resumable-upload.js.es6 @@ -2,10 +2,7 @@ import { schedule } from "@ember/runloop"; import { later } from "@ember/runloop"; import Component from "@ember/component"; import { iconHTML } from "discourse-common/lib/icon-library"; -import { - default as discourseComputed, - on -} from "discourse-common/utils/decorators"; +import discourseComputed, { on } from "discourse-common/utils/decorators"; /*global Resumable:true */ diff --git a/app/assets/javascripts/admin/components/themes-list-item.js.es6 b/app/assets/javascripts/admin/components/themes-list-item.js.es6 index cfc1b63a36..c292b80f99 100644 --- a/app/assets/javascripts/admin/components/themes-list-item.js.es6 +++ b/app/assets/javascripts/admin/components/themes-list-item.js.es6 @@ -1,10 +1,7 @@ import { gt, and } from "@ember/object/computed"; import { schedule } from "@ember/runloop"; import Component from "@ember/component"; -import { - default as discourseComputed, - observes -} from "discourse-common/utils/decorators"; +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"; diff --git a/app/assets/javascripts/admin/components/watched-word-form.js.es6 b/app/assets/javascripts/admin/components/watched-word-form.js.es6 index 2359320ad6..fff5b86e26 100644 --- a/app/assets/javascripts/admin/components/watched-word-form.js.es6 +++ b/app/assets/javascripts/admin/components/watched-word-form.js.es6 @@ -2,8 +2,7 @@ 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 discourseComputed, +import discourseComputed, { on, observes } from "discourse-common/utils/decorators"; diff --git a/app/assets/javascripts/admin/controllers/admin-api-keys-new.js.es6 b/app/assets/javascripts/admin/controllers/admin-api-keys-new.js.es6 index e4f2869b21..7bb679f2d0 100644 --- a/app/assets/javascripts/admin/controllers/admin-api-keys-new.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-api-keys-new.js.es6 @@ -1,4 +1,4 @@ -import { default as discourseComputed } from "discourse-common/utils/decorators"; +import discourseComputed from "discourse-common/utils/decorators"; import { popupAjaxError } from "discourse/lib/ajax-error"; export default Ember.Controller.extend({ diff --git a/app/assets/javascripts/admin/controllers/admin-backups-index.js.es6 b/app/assets/javascripts/admin/controllers/admin-backups-index.js.es6 index e4e7c24d43..01103105e8 100644 --- a/app/assets/javascripts/admin/controllers/admin-backups-index.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-backups-index.js.es6 @@ -2,7 +2,7 @@ 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 discourseComputed } from "discourse-common/utils/decorators"; +import discourseComputed from "discourse-common/utils/decorators"; import { setting, i18n } from "discourse/lib/computed"; export default Controller.extend({ diff --git a/app/assets/javascripts/admin/controllers/admin-badges-award.js.es6 b/app/assets/javascripts/admin/controllers/admin-badges-award.js.es6 new file mode 100644 index 0000000000..af3afd528b --- /dev/null +++ b/app/assets/javascripts/admin/controllers/admin-badges-award.js.es6 @@ -0,0 +1,35 @@ +import Controller from "@ember/controller"; +import { ajax } from "discourse/lib/ajax"; +import { popupAjaxError } from "discourse/lib/ajax-error"; + +export default Controller.extend({ + saving: 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); + + 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")); + } + } + } +}); diff --git a/app/assets/javascripts/admin/controllers/admin-badges-show.js.es6 b/app/assets/javascripts/admin/controllers/admin-badges-show.js.es6 index 2511dcead1..140353e143 100644 --- a/app/assets/javascripts/admin/controllers/admin-badges-show.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-badges-show.js.es6 @@ -1,4 +1,4 @@ -import discourseComputed from "discourse-common/utils/decorators"; +import discourseComputed, { observes } from "discourse-common/utils/decorators"; import { alias } from "@ember/object/computed"; import { inject } from "@ember/controller"; import Controller from "@ember/controller"; @@ -27,10 +27,11 @@ export default 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() { diff --git a/app/assets/javascripts/admin/controllers/admin-badges.js.es6 b/app/assets/javascripts/admin/controllers/admin-badges.js.es6 index cf6c4e3aa2..4797fb2695 100644 --- a/app/assets/javascripts/admin/controllers/admin-badges.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-badges.js.es6 @@ -1,2 +1,18 @@ import Controller from "@ember/controller"; -export default Controller.extend(); +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; + } + } +}); diff --git a/app/assets/javascripts/admin/controllers/admin-customize-colors.js.es6 b/app/assets/javascripts/admin/controllers/admin-customize-colors.js.es6 index 4fc2cf34f0..869918bebb 100644 --- a/app/assets/javascripts/admin/controllers/admin-customize-colors.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-customize-colors.js.es6 @@ -1,7 +1,7 @@ import EmberObject from "@ember/object"; import Controller from "@ember/controller"; import showModal from "discourse/lib/show-modal"; -import { default as discourseComputed } from "discourse-common/utils/decorators"; +import discourseComputed from "discourse-common/utils/decorators"; export default Controller.extend({ @discourseComputed("model.@each.id") diff --git a/app/assets/javascripts/admin/controllers/admin-customize-themes-edit.js.es6 b/app/assets/javascripts/admin/controllers/admin-customize-themes-edit.js.es6 index 75d7a486f2..c3c0f4fc99 100644 --- a/app/assets/javascripts/admin/controllers/admin-customize-themes-edit.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-customize-themes-edit.js.es6 @@ -1,6 +1,6 @@ import Controller from "@ember/controller"; import { url } from "discourse/lib/computed"; -import { default as discourseComputed } from "discourse-common/utils/decorators"; +import discourseComputed from "discourse-common/utils/decorators"; export default Controller.extend({ section: null, diff --git a/app/assets/javascripts/admin/controllers/admin-customize-themes-show.js.es6 b/app/assets/javascripts/admin/controllers/admin-customize-themes-show.js.es6 index cebdb19235..350ec2dd4f 100644 --- a/app/assets/javascripts/admin/controllers/admin-customize-themes-show.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-customize-themes-show.js.es6 @@ -7,7 +7,7 @@ import { notEmpty } from "@ember/object/computed"; import Controller from "@ember/controller"; -import { default as discourseComputed } from "discourse-common/utils/decorators"; +import discourseComputed from "discourse-common/utils/decorators"; import { url } from "discourse/lib/computed"; import { popupAjaxError } from "discourse/lib/ajax-error"; import showModal from "discourse/lib/show-modal"; diff --git a/app/assets/javascripts/admin/controllers/admin-customize-themes.js.es6 b/app/assets/javascripts/admin/controllers/admin-customize-themes.js.es6 index f1d1f0d715..e99532bf03 100644 --- a/app/assets/javascripts/admin/controllers/admin-customize-themes.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-customize-themes.js.es6 @@ -1,5 +1,5 @@ import Controller from "@ember/controller"; -import { default as discourseComputed } from "discourse-common/utils/decorators"; +import discourseComputed from "discourse-common/utils/decorators"; import { THEMES } from "admin/models/theme"; export default Controller.extend({ diff --git a/app/assets/javascripts/admin/controllers/admin-email-bounced.js.es6 b/app/assets/javascripts/admin/controllers/admin-email-bounced.js.es6 index 17cb8ed04f..508bc559c5 100644 --- a/app/assets/javascripts/admin/controllers/admin-email-bounced.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-email-bounced.js.es6 @@ -1,8 +1,10 @@ import AdminEmailLogsController from "admin/controllers/admin-email-logs"; import discourseDebounce from "discourse/lib/debounce"; +import { observes } from "discourse-common/utils/decorators"; export default AdminEmailLogsController.extend({ + @observes("filter.{status,user,address,type}") filterEmailLogs: discourseDebounce(function() { this.loadLogs(); - }, 250).observes("filter.{status,user,address,type}") + }, 250) }); diff --git a/app/assets/javascripts/admin/controllers/admin-email-index.js.es6 b/app/assets/javascripts/admin/controllers/admin-email-index.js.es6 index 124c364af3..3433f6f55f 100644 --- a/app/assets/javascripts/admin/controllers/admin-email-index.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-email-index.js.es6 @@ -1,6 +1,8 @@ import { empty } from "@ember/object/computed"; import Controller from "@ember/controller"; import { ajax } from "discourse/lib/ajax"; +import { observes } from "discourse-common/utils/decorators"; + export default Controller.extend({ /** Is the "send test email" button disabled? @@ -14,9 +16,10 @@ export default Controller.extend({ @method testEmailAddressChanged **/ + @observes("testEmailAddress") testEmailAddressChanged: function() { this.set("sentTestEmail", false); - }.observes("testEmailAddress"), + }, actions: { /** diff --git a/app/assets/javascripts/admin/controllers/admin-email-received.js.es6 b/app/assets/javascripts/admin/controllers/admin-email-received.js.es6 index 7dc733e00c..4a3cc363df 100644 --- a/app/assets/javascripts/admin/controllers/admin-email-received.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-email-received.js.es6 @@ -1,11 +1,13 @@ import AdminEmailLogsController from "admin/controllers/admin-email-logs"; import discourseDebounce from "discourse/lib/debounce"; import IncomingEmail from "admin/models/incoming-email"; +import { observes } from "discourse-common/utils/decorators"; export default AdminEmailLogsController.extend({ + @observes("filter.{status,from,to,subject}") filterIncomingEmails: discourseDebounce(function() { this.loadLogs(IncomingEmail); - }, 250).observes("filter.{status,from,to,subject}"), + }, 250), actions: { loadMore() { diff --git a/app/assets/javascripts/admin/controllers/admin-email-rejected.js.es6 b/app/assets/javascripts/admin/controllers/admin-email-rejected.js.es6 index d70efb23ce..8c6f6767f4 100644 --- a/app/assets/javascripts/admin/controllers/admin-email-rejected.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-email-rejected.js.es6 @@ -1,11 +1,13 @@ import AdminEmailLogsController from "admin/controllers/admin-email-logs"; import discourseDebounce from "discourse/lib/debounce"; import IncomingEmail from "admin/models/incoming-email"; +import { observes } from "discourse-common/utils/decorators"; export default AdminEmailLogsController.extend({ + @observes("filter.{status,from,to,subject,error}") filterIncomingEmails: discourseDebounce(function() { this.loadLogs(IncomingEmail); - }, 250).observes("filter.{status,from,to,subject,error}"), + }, 250), actions: { loadMore() { diff --git a/app/assets/javascripts/admin/controllers/admin-email-sent.js.es6 b/app/assets/javascripts/admin/controllers/admin-email-sent.js.es6 index c7ddaa0043..f727c9d6f4 100644 --- a/app/assets/javascripts/admin/controllers/admin-email-sent.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-email-sent.js.es6 @@ -1,8 +1,10 @@ import AdminEmailLogsController from "admin/controllers/admin-email-logs"; import discourseDebounce from "discourse/lib/debounce"; +import { observes } from "discourse-common/utils/decorators"; export default AdminEmailLogsController.extend({ + @observes("filter.{status,user,address,type,reply_key}") filterEmailLogs: discourseDebounce(function() { this.loadLogs(); - }, 250).observes("filter.{status,user,address,type,reply_key}") + }, 250) }); diff --git a/app/assets/javascripts/admin/controllers/admin-email-skipped.js.es6 b/app/assets/javascripts/admin/controllers/admin-email-skipped.js.es6 index 17cb8ed04f..508bc559c5 100644 --- a/app/assets/javascripts/admin/controllers/admin-email-skipped.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-email-skipped.js.es6 @@ -1,8 +1,10 @@ import AdminEmailLogsController from "admin/controllers/admin-email-logs"; import discourseDebounce from "discourse/lib/debounce"; +import { observes } from "discourse-common/utils/decorators"; export default AdminEmailLogsController.extend({ + @observes("filter.{status,user,address,type}") filterEmailLogs: discourseDebounce(function() { this.loadLogs(); - }, 250).observes("filter.{status,user,address,type}") + }, 250) }); diff --git a/app/assets/javascripts/admin/controllers/admin-logs-screened-ip-addresses.js.es6 b/app/assets/javascripts/admin/controllers/admin-logs-screened-ip-addresses.js.es6 index e5da638f9a..baee177c02 100644 --- a/app/assets/javascripts/admin/controllers/admin-logs-screened-ip-addresses.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-logs-screened-ip-addresses.js.es6 @@ -3,18 +3,20 @@ import discourseDebounce from "discourse/lib/debounce"; import { outputExportResult } from "discourse/lib/export-result"; import { exportEntity } from "discourse/lib/export-csv"; import ScreenedIpAddress from "admin/models/screened-ip-address"; +import { observes } from "discourse-common/utils/decorators"; export default Controller.extend({ loading: false, filter: null, savedIpAddress: null, + @observes("filter") show: discourseDebounce(function() { this.set("loading", true); ScreenedIpAddress.findAll(this.filter).then(result => { this.setProperties({ model: result, loading: false }); }); - }, 250).observes("filter"), + }, 250), actions: { allow(record) { diff --git a/app/assets/javascripts/admin/controllers/admin-logs-staff-action-logs.js.es6 b/app/assets/javascripts/admin/controllers/admin-logs-staff-action-logs.js.es6 index e559bc846f..849861c921 100644 --- a/app/assets/javascripts/admin/controllers/admin-logs-staff-action-logs.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-logs-staff-action-logs.js.es6 @@ -4,10 +4,7 @@ import { scheduleOnce } from "@ember/runloop"; import Controller from "@ember/controller"; import { exportEntity } from "discourse/lib/export-csv"; import { outputExportResult } from "discourse/lib/export-result"; -import { - default as discourseComputed, - on -} from "discourse-common/utils/decorators"; +import discourseComputed, { on } from "discourse-common/utils/decorators"; export default Controller.extend({ model: null, diff --git a/app/assets/javascripts/admin/controllers/admin-permalinks.js.es6 b/app/assets/javascripts/admin/controllers/admin-permalinks.js.es6 index 29c076c30c..a45f8cc89a 100644 --- a/app/assets/javascripts/admin/controllers/admin-permalinks.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-permalinks.js.es6 @@ -1,17 +1,19 @@ import Controller from "@ember/controller"; import discourseDebounce from "discourse/lib/debounce"; import Permalink from "admin/models/permalink"; +import { observes } from "discourse-common/utils/decorators"; export default Controller.extend({ loading: false, filter: null, + @observes("filter") show: discourseDebounce(function() { Permalink.findAll(this.filter).then(result => { this.set("model", result); this.set("loading", false); }); - }, 250).observes("filter"), + }, 250), actions: { recordAdded(arg) { diff --git a/app/assets/javascripts/admin/controllers/admin-site-settings.js.es6 b/app/assets/javascripts/admin/controllers/admin-site-settings.js.es6 index 052f7c567d..28721de995 100644 --- a/app/assets/javascripts/admin/controllers/admin-site-settings.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-site-settings.js.es6 @@ -2,6 +2,7 @@ import { isEmpty } from "@ember/utils"; import { alias } from "@ember/object/computed"; import Controller from "@ember/controller"; import discourseDebounce from "discourse/lib/debounce"; +import { observes } from "discourse-common/utils/decorators"; export default Controller.extend({ filter: null, @@ -76,13 +77,14 @@ export default Controller.extend({ ); }, + @observes("filter", "onlyOverridden", "model") filterContent: discourseDebounce(function() { if (this._skipBounce) { this.set("_skipBounce", false); } else { this.filterContentNow(); } - }, 250).observes("filter", "onlyOverridden", "model"), + }, 250), actions: { clearFilter() { diff --git a/app/assets/javascripts/admin/controllers/admin-user-index.js.es6 b/app/assets/javascripts/admin/controllers/admin-user-index.js.es6 index 765e8d3686..51d53509ad 100644 --- a/app/assets/javascripts/admin/controllers/admin-user-index.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-user-index.js.es6 @@ -6,7 +6,7 @@ import CanCheckEmails from "discourse/mixins/can-check-emails"; import { propertyNotEqual, setting } from "discourse/lib/computed"; import { userPath } from "discourse/lib/url"; import { popupAjaxError } from "discourse/lib/ajax-error"; -import { default as discourseComputed } from "discourse-common/utils/decorators"; +import discourseComputed from "discourse-common/utils/decorators"; import { fmt } from "discourse/lib/computed"; import { htmlSafe } from "@ember/template"; diff --git a/app/assets/javascripts/admin/controllers/admin-users-list-show.js.es6 b/app/assets/javascripts/admin/controllers/admin-users-list-show.js.es6 index 12d4724862..8776fee466 100644 --- a/app/assets/javascripts/admin/controllers/admin-users-list-show.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-users-list-show.js.es6 @@ -1,4 +1,4 @@ -import discourseComputed from "discourse-common/utils/decorators"; +import discourseComputed, { observes } from "discourse-common/utils/decorators"; import Controller from "@ember/controller"; import discourseDebounce from "discourse/lib/debounce"; import { i18n } from "discourse/lib/computed"; @@ -29,9 +29,10 @@ export default Controller.extend(CanCheckEmails, { return I18n.t("admin.users.titles." + query); }, + @observes("listFilter") _filterUsers: discourseDebounce(function() { this.resetFilters(); - }, 250).observes("listFilter"), + }, 250), resetFilters() { this._page = 1; diff --git a/app/assets/javascripts/admin/controllers/admin-watched-words.js.es6 b/app/assets/javascripts/admin/controllers/admin-watched-words.js.es6 index 84a7ac7939..dbc2afe1dc 100644 --- a/app/assets/javascripts/admin/controllers/admin-watched-words.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-watched-words.js.es6 @@ -3,6 +3,7 @@ import { alias } from "@ember/object/computed"; import EmberObject from "@ember/object"; import Controller from "@ember/controller"; import discourseDebounce from "discourse/lib/debounce"; +import { observes } from "discourse-common/utils/decorators"; export default Controller.extend({ filter: null, @@ -43,10 +44,11 @@ export default Controller.extend({ this.set("model", matchesByAction); }, + @observes("filter") filterContent: discourseDebounce(function() { this.filterContentNow(); this.set("filtered", !isEmpty(this.filter)); - }, 250).observes("filter"), + }, 250), actions: { clearFilter() { diff --git a/app/assets/javascripts/admin/controllers/modals/admin-add-upload.js.es6 b/app/assets/javascripts/admin/controllers/modals/admin-add-upload.js.es6 index bd246d9371..c7d9447d8d 100644 --- a/app/assets/javascripts/admin/controllers/modals/admin-add-upload.js.es6 +++ b/app/assets/javascripts/admin/controllers/modals/admin-add-upload.js.es6 @@ -4,10 +4,7 @@ import { inject } from "@ember/controller"; import Controller from "@ember/controller"; import ModalFunctionality from "discourse/mixins/modal-functionality"; import { ajax } from "discourse/lib/ajax"; -import { - default as discourseComputed, - observes -} from "discourse-common/utils/decorators"; +import discourseComputed, { observes } from "discourse-common/utils/decorators"; import { popupAjaxError } from "discourse/lib/ajax-error"; const THEME_FIELD_VARIABLE_TYPE_IDS = [2, 3, 4]; diff --git a/app/assets/javascripts/admin/controllers/modals/admin-badge-preview.js.es6 b/app/assets/javascripts/admin/controllers/modals/admin-badge-preview.js.es6 index 67450978d7..84325f90d1 100644 --- a/app/assets/javascripts/admin/controllers/modals/admin-badge-preview.js.es6 +++ b/app/assets/javascripts/admin/controllers/modals/admin-badge-preview.js.es6 @@ -1,6 +1,6 @@ import { alias, map } from "@ember/object/computed"; import Controller from "@ember/controller"; -import { default as discourseComputed } from "discourse-common/utils/decorators"; +import discourseComputed from "discourse-common/utils/decorators"; import { escapeExpression } from "discourse/lib/utilities"; export default Controller.extend({ diff --git a/app/assets/javascripts/admin/controllers/modals/admin-install-theme.js.es6 b/app/assets/javascripts/admin/controllers/modals/admin-install-theme.js.es6 index 9aee44f72e..cfb61ccc8f 100644 --- a/app/assets/javascripts/admin/controllers/modals/admin-install-theme.js.es6 +++ b/app/assets/javascripts/admin/controllers/modals/admin-install-theme.js.es6 @@ -4,10 +4,7 @@ import Controller from "@ember/controller"; import ModalFunctionality from "discourse/mixins/modal-functionality"; import { ajax } from "discourse/lib/ajax"; import { popupAjaxError } from "discourse/lib/ajax-error"; -import { - default as discourseComputed, - observes -} from "discourse-common/utils/decorators"; +import discourseComputed, { observes } from "discourse-common/utils/decorators"; import { THEMES, COMPONENTS } from "admin/models/theme"; import { POPULAR_THEMES } from "discourse-common/helpers/popular-themes"; import { set } from "@ember/object"; diff --git a/app/assets/javascripts/admin/controllers/modals/admin-watched-word-test.js.es6 b/app/assets/javascripts/admin/controllers/modals/admin-watched-word-test.js.es6 index 9d9b73072d..a3de68d4cd 100644 --- a/app/assets/javascripts/admin/controllers/modals/admin-watched-word-test.js.es6 +++ b/app/assets/javascripts/admin/controllers/modals/admin-watched-word-test.js.es6 @@ -1,5 +1,5 @@ import Controller from "@ember/controller"; -import { default as discourseComputed } from "discourse-common/utils/decorators"; +import discourseComputed from "discourse-common/utils/decorators"; import ModalFunctionality from "discourse/mixins/modal-functionality"; export default Controller.extend(ModalFunctionality, { diff --git a/app/assets/javascripts/admin/models/color-scheme-color.js.es6 b/app/assets/javascripts/admin/models/color-scheme-color.js.es6 index 2e4907ef9f..f1aa235a4b 100644 --- a/app/assets/javascripts/admin/models/color-scheme-color.js.es6 +++ b/app/assets/javascripts/admin/models/color-scheme-color.js.es6 @@ -1,5 +1,4 @@ -import { - default as discourseComputed, +import discourseComputed, { observes, on } from "discourse-common/utils/decorators"; diff --git a/app/assets/javascripts/admin/models/report.js.es6 b/app/assets/javascripts/admin/models/report.js.es6 index 91a5f54002..bd7074ced2 100644 --- a/app/assets/javascripts/admin/models/report.js.es6 +++ b/app/assets/javascripts/admin/models/report.js.es6 @@ -22,11 +22,6 @@ const Report = EmberObject.extend({ percent: false, higher_is_better: true, - @discourseComputed("modes") - isTable(modes) { - return modes.some(mode => mode === "table"); - }, - @discourseComputed("type", "start_date", "end_date") reportUrl(type, start_date, end_date) { start_date = moment diff --git a/app/assets/javascripts/admin/models/theme.js.es6 b/app/assets/javascripts/admin/models/theme.js.es6 index d1528bcd06..7385aaf630 100644 --- a/app/assets/javascripts/admin/models/theme.js.es6 +++ b/app/assets/javascripts/admin/models/theme.js.es6 @@ -2,7 +2,7 @@ import { get } from "@ember/object"; import { isEmpty } from "@ember/utils"; import { or, gt } from "@ember/object/computed"; import RestModel from "discourse/models/rest"; -import { default as discourseComputed } from "discourse-common/utils/decorators"; +import discourseComputed from "discourse-common/utils/decorators"; import { popupAjaxError } from "discourse/lib/ajax-error"; import { ajax } from "discourse/lib/ajax"; import { escapeExpression } from "discourse/lib/utilities"; diff --git a/app/assets/javascripts/admin/models/web-hook.js.es6 b/app/assets/javascripts/admin/models/web-hook.js.es6 index 74fd93a5f3..b727ea59d1 100644 --- a/app/assets/javascripts/admin/models/web-hook.js.es6 +++ b/app/assets/javascripts/admin/models/web-hook.js.es6 @@ -2,10 +2,7 @@ import { isEmpty } from "@ember/utils"; import RestModel from "discourse/models/rest"; import Category from "discourse/models/category"; import Group from "discourse/models/group"; -import { - default as discourseComputed, - observes -} from "discourse-common/utils/decorators"; +import discourseComputed, { observes } from "discourse-common/utils/decorators"; import Site from "discourse/models/site"; export default RestModel.extend({ diff --git a/app/assets/javascripts/admin/routes/admin-badges-award.js.es6 b/app/assets/javascripts/admin/routes/admin-badges-award.js.es6 new file mode 100644 index 0000000000..90a4cba17f --- /dev/null +++ b/app/assets/javascripts/admin/routes/admin-badges-award.js.es6 @@ -0,0 +1,12 @@ +import Route from "discourse/routes/discourse"; + +export default Route.extend({ + model(params) { + if (params.badge_id !== "new") { + return this.modelFor("adminBadges").findBy( + "id", + parseInt(params.badge_id, 10) + ); + } + } +}); diff --git a/app/assets/javascripts/admin/routes/admin-email-preview-digest.js.es6 b/app/assets/javascripts/admin/routes/admin-email-preview-digest.js.es6 index 391ec6c75b..56464eaf8b 100644 --- a/app/assets/javascripts/admin/routes/admin-email-preview-digest.js.es6 +++ b/app/assets/javascripts/admin/routes/admin-email-preview-digest.js.es6 @@ -1,8 +1,5 @@ import DiscourseRoute from "discourse/routes/discourse"; -import { - default as EmailPreview, - oneWeekAgo -} from "admin/models/email-preview"; +import EmailPreview, { oneWeekAgo } from "admin/models/email-preview"; export default DiscourseRoute.extend({ model() { 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 f90b66f97a..8297380e05 100644 --- a/app/assets/javascripts/admin/routes/admin-route-map.js.es6 +++ b/app/assets/javascripts/admin/routes/admin-route-map.js.es6 @@ -190,6 +190,7 @@ export default function() { "adminBadges", { path: "/badges", resetNamespace: true }, function() { + this.route("award", { path: "/award/:badge_id" }); this.route("show", { path: "/:badge_id" }); } ); diff --git a/app/assets/javascripts/admin/templates/api-keys-show.hbs b/app/assets/javascripts/admin/templates/api-keys-show.hbs index 7639a438f9..098d727f82 100644 --- a/app/assets/javascripts/admin/templates/api-keys-show.hbs +++ b/app/assets/javascripts/admin/templates/api-keys-show.hbs @@ -64,7 +64,7 @@ label="admin.api.undo_revoke"}} {{d-button action=(action "deleteKey") - actionParam=model icon="trash" + actionParam=model icon="trash-alt" label="admin.api.delete" class="btn-danger"}} {{else}} @@ -77,4 +77,4 @@ {{/if}} {{/admin-form-row}} - \ No newline at end of file + diff --git a/app/assets/javascripts/admin/templates/badges-award.hbs b/app/assets/javascripts/admin/templates/badges-award.hbs new file mode 100644 index 0000000000..66ef70365a --- /dev/null +++ b/app/assets/javascripts/admin/templates/badges-award.hbs @@ -0,0 +1,32 @@ +{{#d-section class="award-badge"}} +

{{i18n 'admin.badges.mass_award.title'}}

+

{{i18n 'admin.badges.mass_award.description'}}

+ + {{#if model}} +
+
+ {{#if model}} + {{icon-or-image model}} + {{model.name}} + {{else}} + {{I18n 'admin.badges.mass_award.no_badge_selected'}} + {{/if}} +
+
+

{{I18n 'admin.badges.mass_award.upload_csv'}}

+ +
+ {{d-button + class="btn-primary" + action=(action 'massAward') + disabled=saving + label="admin.badges.mass_award.perform"}} + {{#link-to 'adminBadges.index' class="btn btn-danger"}} + {{d-icon "times"}} + {{i18n 'cancel'}} + {{/link-to}} +
+ {{else}} + {{I18n 'admin.badges.mass_award.no_badge_selected'}} + {{/if}} +{{/d-section}} diff --git a/app/assets/javascripts/admin/templates/badges.hbs b/app/assets/javascripts/admin/templates/badges.hbs index 398a5c08fc..f5ff6c7a0d 100644 --- a/app/assets/javascripts/admin/templates/badges.hbs +++ b/app/assets/javascripts/admin/templates/badges.hbs @@ -6,13 +6,18 @@ {{d-icon "plus"}} {{i18n 'admin.badges.new'}} {{/link-to}} + + {{#link-to 'adminBadges.award' 'new' class="btn"}} + {{d-icon "upload"}} + {{i18n 'admin.badges.mass_award.title'}} + {{/link-to}}
{{outlet}} - \ No newline at end of file + diff --git a/app/assets/javascripts/admin/templates/components/admin-report.hbs b/app/assets/javascripts/admin/templates/components/admin-report.hbs index 8d9729dc82..bdc3b5b12a 100644 --- a/app/assets/javascripts/admin/templates/components/admin-report.hbs +++ b/app/assets/javascripts/admin/templates/components/admin-report.hbs @@ -160,17 +160,15 @@ {{/each}} - {{#if model.isTable}} -
-
- {{d-button - class="btn-default export-csv-btn" - action=(action "exportCsv") - label="admin.export_csv.button_text" - icon="download"}} -
+
+
+ {{d-button + class="btn-default export-csv-btn" + action=(action "exportCsv") + label="admin.export_csv.button_text" + icon="download"}}
- {{/if}} +
{{#if showRefresh}}
diff --git a/app/assets/javascripts/admin/templates/modal/admin-badge-preview.hbs b/app/assets/javascripts/admin/templates/modal/admin-badge-preview.hbs index 7911b91ab8..ef36e4aa05 100644 --- a/app/assets/javascripts/admin/templates/modal/admin-badge-preview.hbs +++ b/app/assets/javascripts/admin/templates/modal/admin-badge-preview.hbs @@ -26,7 +26,7 @@ {{#if countWarning}}

- {{d-icon "warning"}} + {{d-icon "exclamation-triangle"}} {{i18n "admin.badges.preview.bad_count_warning.header"}}

diff --git a/app/assets/javascripts/confirm-new-email/confirm-new-email.js.es6 b/app/assets/javascripts/confirm-new-email/confirm-new-email.js.es6 new file mode 100644 index 0000000000..dbcfdd25c4 --- /dev/null +++ b/app/assets/javascripts/confirm-new-email/confirm-new-email.js.es6 @@ -0,0 +1,23 @@ +import { getWebauthnCredential } from "discourse/lib/webauthn"; + +document.getElementById("submit-security-key").onclick = function(e) { + e.preventDefault(); + getWebauthnCredential( + document.getElementById("security-key-challenge").value, + document + .getElementById("security-key-allowed-credential-ids") + .value.split(","), + credentialData => { + document.getElementById("security-key-credential").value = JSON.stringify( + credentialData + ); + + $(e.target) + .parents("form") + .submit(); + }, + errorMessage => { + document.getElementById("security-key-error").innerText = errorMessage; + } + ); +}; diff --git a/app/assets/javascripts/confirm-new-email/confirm-new-email.no-module.js.es6 b/app/assets/javascripts/confirm-new-email/confirm-new-email.no-module.js.es6 new file mode 100644 index 0000000000..0dd05dd8ca --- /dev/null +++ b/app/assets/javascripts/confirm-new-email/confirm-new-email.no-module.js.es6 @@ -0,0 +1 @@ +require("confirm-new-email/confirm-new-email").default(); diff --git a/app/assets/javascripts/discourse-common/lib/buffered-render.js.es6 b/app/assets/javascripts/discourse-common/lib/buffered-render.js.es6 deleted file mode 100644 index d3d301212a..0000000000 --- a/app/assets/javascripts/discourse-common/lib/buffered-render.js.es6 +++ /dev/null @@ -1,46 +0,0 @@ -import { scheduleOnce } from "@ember/runloop"; -// Ember 2.0 removes buffered rendering, but we can still implement it ourselves. -// In the long term we'll want to remove this. - -const Mixin = { - _customRender() { - if (!this.element || this.isDestroying || this.isDestroyed) { - return; - } - - const buffer = []; - this.buildBuffer(buffer); - this.element.innerHTML = buffer.join(""); - }, - - rerenderBuffer() { - scheduleOnce("render", this, this._customRender); - } -}; - -export function bufferedRender(obj) { - if (!obj.buildBuffer) { - Ember.warn("Missing `buildBuffer` method", { - id: "discourse.buffered-render.missing-build-buffer" - }); - return obj; - } - - const caller = {}; - - caller.didRender = function() { - this._super(...arguments); - this._customRender(); - }; - - const triggers = obj.rerenderTriggers; - if (triggers) { - caller.init = function() { - this._super(...arguments); - triggers.forEach(k => this.addObserver(k, this.rerenderBuffer)); - }; - } - delete obj.rerenderTriggers; - - return Ember.Mixin.create(Mixin, caller, obj); -} diff --git a/app/assets/javascripts/discourse-common/lib/helpers.js.es6 b/app/assets/javascripts/discourse-common/lib/helpers.js.es6 index 5b7d4fdc81..0f87040d95 100644 --- a/app/assets/javascripts/discourse-common/lib/helpers.js.es6 +++ b/app/assets/javascripts/discourse-common/lib/helpers.js.es6 @@ -1,5 +1,6 @@ import { get } from "@ember/object"; import Helper from "@ember/component/helper"; +import RawHandlebars from "discourse-common/lib/raw-handlebars"; export function makeArray(obj) { if (obj === null || obj === undefined) { @@ -88,5 +89,5 @@ export function registerUnbound(name, fn) { _helpers[name] = Helper.extend({ compute: (params, args) => fn(...params, args) }); - Handlebars.registerHelper(name, func); + RawHandlebars.registerHelper(name, func); } diff --git a/app/assets/javascripts/discourse-common/lib/icon-library.js.es6 b/app/assets/javascripts/discourse-common/lib/icon-library.js.es6 index 70e1ab152c..aa32589a3a 100644 --- a/app/assets/javascripts/discourse-common/lib/icon-library.js.es6 +++ b/app/assets/javascripts/discourse-common/lib/icon-library.js.es6 @@ -6,11 +6,11 @@ const SVG_NAMESPACE = "http://www.w3.org/2000/svg"; let _renderers = []; const REPLACEMENTS = { - "d-tracking": "circle", - "d-muted": "times-circle", - "d-regular": "far-circle", - "d-watching": "exclamation-circle", - "d-watching-first": "far-dot-circle", + "d-tracking": "bell", + "d-muted": "discourse-bell-slash", + "d-regular": "far-bell", + "d-watching": "discourse-bell-exclamation", + "d-watching-first": "discourse-bell-one", "d-drop-expanded": "caret-down", "d-drop-collapsed": "caret-right", "d-unliked": "far-heart", @@ -29,11 +29,11 @@ const REPLACEMENTS = { "notification.invited_to_private_message": "far-envelope", "notification.invited_to_topic": "hand-point-right", "notification.invitee_accepted": "user", - "notification.moved_post": "sign-out", + "notification.moved_post": "sign-out-alt", "notification.linked": "link", "notification.granted_badge": "certificate", "notification.topic_reminder": "far-clock", - "notification.watching_first_post": "far-dot-circle", + "notification.watching_first_post": "discourse-bell-one", "notification.group_message_summary": "users", "notification.post_approved": "check", "notification.membership_request_accepted": "user-plus", @@ -580,21 +580,25 @@ function warnIfMissing(id) { } } +const reportedIcons = []; + function warnIfDeprecated(oldId, newId) { deprecated( `Please replace all occurrences of "${oldId}"" with "${newId}". FontAwesome 4.7 icon names are now deprecated and will be removed in the next release.` ); - if (!Discourse.testing) { + if (!Discourse.testing && !reportedIcons.includes(oldId)) { const errorData = { message: `FA icon deprecation: replace "${oldId}"" with "${newId}".`, stacktrace: Error().stack }; Ember.$.ajax(`${Discourse.BaseUri}/logs/report_js_error`, { - errorData, + data: errorData, type: "POST", cache: false }); + + reportedIcons.push(oldId); } } diff --git a/app/assets/javascripts/discourse-common/lib/raw-handlebars-helpers.js.es6 b/app/assets/javascripts/discourse-common/lib/raw-handlebars-helpers.js.es6 index 432ce98853..ffaa721cf3 100644 --- a/app/assets/javascripts/discourse-common/lib/raw-handlebars-helpers.js.es6 +++ b/app/assets/javascripts/discourse-common/lib/raw-handlebars-helpers.js.es6 @@ -1,14 +1,23 @@ import { get } from "@ember/object"; export function registerRawHelpers(hbs, handlebarsClass) { - hbs.helper = function() {}; - hbs.helpers = Object.create(handlebarsClass.helpers); + if (!hbs.helpers) { + hbs.helpers = Object.create(handlebarsClass.helpers); + } hbs.helpers["get"] = function(context, options) { - var firstContext = options.contexts[0]; - var val = firstContext[context]; + if (!context || !options.contexts) { + return; + } - if (context.indexOf("controller.") === 0) { + if (typeof context !== "string") { + return context; + } + + let firstContext = options.contexts[0]; + let val = firstContext[context]; + + if (context.toString().indexOf("controller.") === 0) { context = context.slice(context.indexOf(".") + 1); } diff --git a/app/assets/javascripts/discourse.js.es6 b/app/assets/javascripts/discourse.js.es6 index 38ea5dd7bb..7b21f2bec6 100644 --- a/app/assets/javascripts/discourse.js.es6 +++ b/app/assets/javascripts/discourse.js.es6 @@ -1,9 +1,6 @@ /*global Mousetrap:true*/ import { buildResolver } from "discourse-common/resolver"; -import { - default as discourseComputed, - observes -} from "discourse-common/utils/decorators"; +import discourseComputed, { observes } from "discourse-common/utils/decorators"; import { computed } from "@ember/object"; import FocusEvent from "discourse-common/mixins/focus-event"; import EmberObject from "@ember/object"; diff --git a/app/assets/javascripts/discourse/adapters/tag-info.js.es6 b/app/assets/javascripts/discourse/adapters/tag-info.js.es6 index ca04b6d212..c2bf608c23 100644 --- a/app/assets/javascripts/discourse/adapters/tag-info.js.es6 +++ b/app/assets/javascripts/discourse/adapters/tag-info.js.es6 @@ -2,6 +2,6 @@ import RESTAdapter from "discourse/adapters/rest"; export default RESTAdapter.extend({ pathFor(store, type, id) { - return "/tags/" + id + "/info"; + return "/tag/" + id + "/info"; } }); diff --git a/app/assets/javascripts/discourse/adapters/tag-notification.js.es6 b/app/assets/javascripts/discourse/adapters/tag-notification.js.es6 index 6d4a5f4e47..2c0b0c0bf5 100644 --- a/app/assets/javascripts/discourse/adapters/tag-notification.js.es6 +++ b/app/assets/javascripts/discourse/adapters/tag-notification.js.es6 @@ -2,6 +2,6 @@ import RESTAdapter from "discourse/adapters/rest"; export default RESTAdapter.extend({ pathFor(store, type, id) { - return "/tags/" + id + "/notifications"; + return "/tag/" + id + "/notifications"; } }); diff --git a/app/assets/javascripts/discourse/components/badge-selector.js.es6 b/app/assets/javascripts/discourse/components/badge-selector.js.es6 index 3e02b0740e..0f5c22941b 100644 --- a/app/assets/javascripts/discourse/components/badge-selector.js.es6 +++ b/app/assets/javascripts/discourse/components/badge-selector.js.es6 @@ -1,8 +1,7 @@ import Component from "@ember/component"; -import { +import discourseComputed, { on, - observes, - default as discourseComputed + observes } from "discourse-common/utils/decorators"; import { findRawTemplate } from "discourse/lib/raw-templates"; const { makeArray } = Ember; diff --git a/app/assets/javascripts/discourse/components/basic-topic-list.js.es6 b/app/assets/javascripts/discourse/components/basic-topic-list.js.es6 index be4ec964c2..81c08adf61 100644 --- a/app/assets/javascripts/discourse/components/basic-topic-list.js.es6 +++ b/app/assets/javascripts/discourse/components/basic-topic-list.js.es6 @@ -1,4 +1,4 @@ -import discourseComputed from "discourse-common/utils/decorators"; +import discourseComputed, { observes } from "discourse-common/utils/decorators"; import { alias, not } from "@ember/object/computed"; import Component from "@ember/component"; @@ -16,9 +16,10 @@ export default Component.extend({ } }, + @observes("topicList.[]") _topicListChanged: function() { this._initFromTopicList(this.topicList); - }.observes("topicList.[]"), + }, _initFromTopicList(topicList) { if (topicList !== null) { diff --git a/app/assets/javascripts/discourse/components/bread-crumbs.js.es6 b/app/assets/javascripts/discourse/components/bread-crumbs.js.es6 index 9d00a25272..d8eeba3b01 100644 --- a/app/assets/javascripts/discourse/components/bread-crumbs.js.es6 +++ b/app/assets/javascripts/discourse/components/bread-crumbs.js.es6 @@ -1,6 +1,6 @@ import { filter } from "@ember/object/computed"; import Component from "@ember/component"; -import { default as discourseComputed } from "discourse-common/utils/decorators"; +import discourseComputed from "discourse-common/utils/decorators"; import deprecated from "discourse-common/lib/deprecated"; // A breadcrumb including category drop downs diff --git a/app/assets/javascripts/discourse/components/categories-boxes.js.es6 b/app/assets/javascripts/discourse/components/categories-boxes.js.es6 index b4dcfde7e8..c3aadd638c 100644 --- a/app/assets/javascripts/discourse/components/categories-boxes.js.es6 +++ b/app/assets/javascripts/discourse/components/categories-boxes.js.es6 @@ -1,7 +1,6 @@ import discourseComputed from "discourse-common/utils/decorators"; import { isEmpty } from "@ember/utils"; import Component from "@ember/component"; -import DiscourseURL from "discourse/lib/url"; export default Component.extend({ tagName: "section", @@ -19,16 +18,5 @@ export default Component.extend({ @discourseComputed("categories.[].subcategories") hasSubcategories() { return this.categories.any(c => !isEmpty(c.get("subcategories"))); - }, - - click(e) { - if (!$(e.target).is("a")) { - const url = $(e.target) - .closest(".category-box") - .data("url"); - if (url) { - DiscourseURL.routeTo(url); - } - } } }); diff --git a/app/assets/javascripts/discourse/components/composer-action-title.js.es6 b/app/assets/javascripts/discourse/components/composer-action-title.js.es6 index 996b409f6b..86a968154a 100644 --- a/app/assets/javascripts/discourse/components/composer-action-title.js.es6 +++ b/app/assets/javascripts/discourse/components/composer-action-title.js.es6 @@ -1,6 +1,6 @@ import { alias, equal } from "@ember/object/computed"; import Component from "@ember/component"; -import { default as discourseComputed } from "discourse-common/utils/decorators"; +import discourseComputed from "discourse-common/utils/decorators"; import { PRIVATE_MESSAGE, CREATE_TOPIC, diff --git a/app/assets/javascripts/discourse/components/composer-body.js.es6 b/app/assets/javascripts/discourse/components/composer-body.js.es6 index f2a6ae3bc8..9684e9f8db 100644 --- a/app/assets/javascripts/discourse/components/composer-body.js.es6 +++ b/app/assets/javascripts/discourse/components/composer-body.js.es6 @@ -4,10 +4,7 @@ import { cancel } from "@ember/runloop"; import { scheduleOnce } from "@ember/runloop"; import { later } from "@ember/runloop"; import Component from "@ember/component"; -import { - default as discourseComputed, - observes -} from "discourse-common/utils/decorators"; +import discourseComputed, { observes } from "discourse-common/utils/decorators"; import Composer from "discourse/models/composer"; import afterTransition from "discourse/lib/after-transition"; import positioningWorkaround from "discourse/lib/safari-hacks"; @@ -70,7 +67,7 @@ export default Component.extend(KeyEnterEscape, { return; } - const h = $("#reply-control").height() || 0; + const h = $("#reply-control:not(.saving)").height() || 0; this.movePanels(h); }); }, diff --git a/app/assets/javascripts/discourse/components/composer-editor.js.es6 b/app/assets/javascripts/discourse/components/composer-editor.js.es6 index 0881f577b9..3304b007cc 100644 --- a/app/assets/javascripts/discourse/components/composer-editor.js.es6 +++ b/app/assets/javascripts/discourse/components/composer-editor.js.es6 @@ -5,8 +5,7 @@ import { scheduleOnce } from "@ember/runloop"; import { later } from "@ember/runloop"; import Component from "@ember/component"; import userSearch from "discourse/lib/user-search"; -import { - default as discourseComputed, +import discourseComputed, { observes, on } from "discourse-common/utils/decorators"; diff --git a/app/assets/javascripts/discourse/components/composer-messages.js.es6 b/app/assets/javascripts/discourse/components/composer-messages.js.es6 index e9860973a2..cafae73058 100644 --- a/app/assets/javascripts/discourse/components/composer-messages.js.es6 +++ b/app/assets/javascripts/discourse/components/composer-messages.js.es6 @@ -97,16 +97,11 @@ export default Component.extend({ const composer = this.composer; if (composer.get("privateMessage")) { - let usernames = composer.get("targetUsernames"); - - if (usernames) { - usernames = usernames.split(","); - } + const recipients = composer.targetRecipientsArray; if ( - usernames && - usernames.length === 1 && - usernames[0] === this.currentUser.get("username") + recipients.length > 0 && + recipients.every(r => r.name === this.currentUser.get("username")) ) { const message = this._yourselfConfirm || diff --git a/app/assets/javascripts/discourse/components/composer-title.js.es6 b/app/assets/javascripts/discourse/components/composer-title.js.es6 index 2eb06cb071..bd559a59f1 100644 --- a/app/assets/javascripts/discourse/components/composer-title.js.es6 +++ b/app/assets/javascripts/discourse/components/composer-title.js.es6 @@ -3,10 +3,7 @@ import { next } from "@ember/runloop"; import { debounce } from "@ember/runloop"; import { schedule } from "@ember/runloop"; import Component from "@ember/component"; -import { - default as discourseComputed, - observes -} from "discourse-common/utils/decorators"; +import discourseComputed, { observes } from "discourse-common/utils/decorators"; import { load } from "pretty-text/oneboxer"; import { lookupCache } from "pretty-text/oneboxer-cache"; import { ajax } from "discourse/lib/ajax"; diff --git a/app/assets/javascripts/discourse/components/composer-user-selector.js.es6 b/app/assets/javascripts/discourse/components/composer-user-selector.js.es6 index 14ca7e3556..913a9356f7 100644 --- a/app/assets/javascripts/discourse/components/composer-user-selector.js.es6 +++ b/app/assets/javascripts/discourse/components/composer-user-selector.js.es6 @@ -1,9 +1,6 @@ import { schedule } from "@ember/runloop"; import Component from "@ember/component"; -import { - default as discourseComputed, - observes -} from "discourse-common/utils/decorators"; +import discourseComputed, { observes } from "discourse-common/utils/decorators"; export default Component.extend({ showSelector: true, diff --git a/app/assets/javascripts/discourse/components/d-button.js.es6 b/app/assets/javascripts/discourse/components/d-button.js.es6 index 1d0c9d04f8..1210432127 100644 --- a/app/assets/javascripts/discourse/components/d-button.js.es6 +++ b/app/assets/javascripts/discourse/components/d-button.js.es6 @@ -1,6 +1,6 @@ import { notEmpty, empty, equal } from "@ember/object/computed"; import Component from "@ember/component"; -import { default as discourseComputed } from "discourse-common/utils/decorators"; +import discourseComputed from "discourse-common/utils/decorators"; import DiscourseURL from "discourse/lib/url"; export default Component.extend({ diff --git a/app/assets/javascripts/discourse/components/d-editor.js.es6 b/app/assets/javascripts/discourse/components/d-editor.js.es6 index 7a26db929d..48c788c0bf 100644 --- a/app/assets/javascripts/discourse/components/d-editor.js.es6 +++ b/app/assets/javascripts/discourse/components/d-editor.js.es6 @@ -6,8 +6,7 @@ import { later } from "@ember/runloop"; import { inject as service } from "@ember/service"; import Component from "@ember/component"; /*global Mousetrap:true */ -import { - default as discourseComputed, +import discourseComputed, { on, observes } from "discourse-common/utils/decorators"; @@ -842,7 +841,7 @@ export default Component.extend({ } const isComposer = $("#reply-control .d-editor-input").is(":focus"); - let { clipboard, canPasteHtml } = clipboardData(e, isComposer); + let { clipboard, canPasteHtml, canUpload } = clipboardData(e, isComposer); let plainText = clipboard.getData("text/plain"); let html = clipboard.getData("text/html"); @@ -892,7 +891,7 @@ export default Component.extend({ } } - if (handled) { + if (handled || canUpload) { e.preventDefault(); } }, diff --git a/app/assets/javascripts/discourse/components/date-input.js.es6 b/app/assets/javascripts/discourse/components/date-input.js.es6 index 7d56b964a2..9da5a5e16c 100644 --- a/app/assets/javascripts/discourse/components/date-input.js.es6 +++ b/app/assets/javascripts/discourse/components/date-input.js.es6 @@ -2,10 +2,7 @@ import { next } from "@ember/runloop"; import Component from "@ember/component"; /* global Pikaday:true */ import loadScript from "discourse/lib/load-script"; -import { - default as discourseComputed, - on -} from "discourse-common/utils/decorators"; +import discourseComputed, { on } from "discourse-common/utils/decorators"; export default Component.extend({ classNames: ["d-date-input"], diff --git a/app/assets/javascripts/discourse/components/date-picker.js.es6 b/app/assets/javascripts/discourse/components/date-picker.js.es6 index 8ca644e952..4a32928ac0 100644 --- a/app/assets/javascripts/discourse/components/date-picker.js.es6 +++ b/app/assets/javascripts/discourse/components/date-picker.js.es6 @@ -2,10 +2,7 @@ import { next } from "@ember/runloop"; import Component from "@ember/component"; /* global Pikaday:true */ import loadScript from "discourse/lib/load-script"; -import { - default as discourseComputed, - on -} from "discourse-common/utils/decorators"; +import discourseComputed, { on } from "discourse-common/utils/decorators"; const DATE_FORMAT = "YYYY-MM-DD"; diff --git a/app/assets/javascripts/discourse/components/discourse-linked-text.js.es6 b/app/assets/javascripts/discourse/components/discourse-linked-text.js.es6 index 85d6fd3cb9..55240d4ee2 100644 --- a/app/assets/javascripts/discourse/components/discourse-linked-text.js.es6 +++ b/app/assets/javascripts/discourse/components/discourse-linked-text.js.es6 @@ -1,5 +1,5 @@ import Component from "@ember/component"; -import { default as discourseComputed } from "discourse-common/utils/decorators"; +import discourseComputed from "discourse-common/utils/decorators"; export default Component.extend({ tagName: "span", diff --git a/app/assets/javascripts/discourse/components/discourse-tag-bound.js.es6 b/app/assets/javascripts/discourse/components/discourse-tag-bound.js.es6 index 57e5c1841a..13c8798631 100644 --- a/app/assets/javascripts/discourse/components/discourse-tag-bound.js.es6 +++ b/app/assets/javascripts/discourse/components/discourse-tag-bound.js.es6 @@ -13,6 +13,6 @@ export default Component.extend({ @discourseComputed("tagRecord.id") href(tagRecordId) { - return Discourse.getURL("/tags/" + tagRecordId); + return Discourse.getURL("/tag/" + tagRecordId); } }); diff --git a/app/assets/javascripts/discourse/components/edit-category-images.js.es6 b/app/assets/javascripts/discourse/components/edit-category-images.js.es6 index 94956edf36..953a1a62e7 100644 --- a/app/assets/javascripts/discourse/components/edit-category-images.js.es6 +++ b/app/assets/javascripts/discourse/components/edit-category-images.js.es6 @@ -1,6 +1,6 @@ import EmberObject from "@ember/object"; import { buildCategoryPanel } from "discourse/components/edit-category-panel"; -import { default as discourseComputed } from "discourse-common/utils/decorators"; +import discourseComputed from "discourse-common/utils/decorators"; export default buildCategoryPanel("images").extend({ @discourseComputed("category.uploaded_background.url") diff --git a/app/assets/javascripts/discourse/components/edit-category-topic-template.js.es6 b/app/assets/javascripts/discourse/components/edit-category-topic-template.js.es6 index c3254cc3d6..9a5d7d7ab1 100644 --- a/app/assets/javascripts/discourse/components/edit-category-topic-template.js.es6 +++ b/app/assets/javascripts/discourse/components/edit-category-topic-template.js.es6 @@ -1,12 +1,14 @@ import { scheduleOnce } from "@ember/runloop"; import { buildCategoryPanel } from "discourse/components/edit-category-panel"; +import { observes } from "discourse-common/utils/decorators"; export default buildCategoryPanel("topic-template", { + @observes("activeTab") _activeTabChanged: function() { if (this.activeTab) { scheduleOnce("afterRender", () => this.element.querySelector(".d-editor-input").focus() ); } - }.observes("activeTab") + } }); diff --git a/app/assets/javascripts/discourse/components/edit-topic-timer-form.js.es6 b/app/assets/javascripts/discourse/components/edit-topic-timer-form.js.es6 index 00fa6b2bad..18f2ac04f0 100644 --- a/app/assets/javascripts/discourse/components/edit-topic-timer-form.js.es6 +++ b/app/assets/javascripts/discourse/components/edit-topic-timer-form.js.es6 @@ -2,8 +2,7 @@ import { isEmpty } from "@ember/utils"; import { alias, equal, or } from "@ember/object/computed"; import { schedule } from "@ember/runloop"; import Component from "@ember/component"; -import { - default as discourseComputed, +import discourseComputed, { observes, on } from "discourse-common/utils/decorators"; diff --git a/app/assets/javascripts/discourse/components/emoji-uploader.js.es6 b/app/assets/javascripts/discourse/components/emoji-uploader.js.es6 index fa7ee8fd10..77e2d5e9ff 100644 --- a/app/assets/javascripts/discourse/components/emoji-uploader.js.es6 +++ b/app/assets/javascripts/discourse/components/emoji-uploader.js.es6 @@ -1,6 +1,6 @@ import { notEmpty, not } from "@ember/object/computed"; import Component from "@ember/component"; -import { default as discourseComputed } from "discourse-common/utils/decorators"; +import discourseComputed from "discourse-common/utils/decorators"; import UploadMixin from "discourse/mixins/upload"; export default Component.extend(UploadMixin, { diff --git a/app/assets/javascripts/discourse/components/flat-button.js.es6 b/app/assets/javascripts/discourse/components/flat-button.js.es6 index af44422025..f2f38fdc27 100644 --- a/app/assets/javascripts/discourse/components/flat-button.js.es6 +++ b/app/assets/javascripts/discourse/components/flat-button.js.es6 @@ -1,5 +1,5 @@ import Component from "@ember/component"; -import { default as discourseComputed } from "discourse-common/utils/decorators"; +import discourseComputed from "discourse-common/utils/decorators"; export default Component.extend({ tagName: "button", diff --git a/app/assets/javascripts/discourse/components/future-date-input.js.es6 b/app/assets/javascripts/discourse/components/future-date-input.js.es6 index 05313f8ea0..2c1bb70b54 100644 --- a/app/assets/javascripts/discourse/components/future-date-input.js.es6 +++ b/app/assets/javascripts/discourse/components/future-date-input.js.es6 @@ -1,10 +1,7 @@ import { isEmpty } from "@ember/utils"; import { equal, and, empty } from "@ember/object/computed"; import Component from "@ember/component"; -import { - default as discourseComputed, - observes -} from "discourse-common/utils/decorators"; +import discourseComputed, { observes } from "discourse-common/utils/decorators"; import { FORMAT } from "select-kit/components/future-date-input-selector"; import { PUBLISH_TO_CATEGORY_STATUS_TYPE } from "discourse/controllers/edit-topic-timer"; diff --git a/app/assets/javascripts/discourse/components/global-notice.js.es6 b/app/assets/javascripts/discourse/components/global-notice.js.es6 index 7b80c97715..6096ebe1de 100644 --- a/app/assets/javascripts/discourse/components/global-notice.js.es6 +++ b/app/assets/javascripts/discourse/components/global-notice.js.es6 @@ -1,119 +1,233 @@ -import { bind } from "@ember/runloop"; +import { bind, cancel } from "@ember/runloop"; import Component from "@ember/component"; -import { on } from "discourse-common/utils/decorators"; -import { iconHTML } from "discourse-common/lib/icon-library"; import LogsNotice from "discourse/services/logs-notice"; -import { bufferedRender } from "discourse-common/lib/buffered-render"; +import EmberObject from "@ember/object"; -export default Component.extend( - bufferedRender({ - rerenderTriggers: ["site.isReadOnly", "siteSettings.disable_emails"], +const _pluginNotices = []; - buildBuffer(buffer) { +export function addGlobalNotice(text, id, options = {}) { + _pluginNotices.push(Notice.create({ text, id, options })); +} + +const GLOBAL_NOTICE_DISMISSED_PROMPT_KEY = "dismissed-global-notice-v2"; + +const Notice = EmberObject.extend({ + text: null, + id: null, + options: null, + + init() { + this._super(...arguments); + + const defaults = { + // can this banner be hidden + dismissable: false, + // prepend html content + html: null, + // will define the style of the banner, follows alerts styling + level: "info", + // should the banner be permanently hidden? + persistentDismiss: true, + // callback function when dismissing a banner + onDismiss: null, + // show/hide banner function, will take precedence over everything + visibility: null, + // how long before banner should show again, eg: moment.duration(1, "week") + dismissDuration: null + }; + + this.options = this.set( + "options", + Object.assign(defaults, this.options || {}) + ); + } +}); + +export default Component.extend({ + logNotice: null, + + init() { + this._super(...arguments); + + this._setupObservers(); + }, + + willDestroyElement() { + this._super(...arguments); + + this._tearDownObservers(); + }, + + notices: Ember.computed( + "site.isReadOnly", + "siteSettings.disable_emails", + "logNotice.{id,text,hidden}", + function() { let notices = []; if ($.cookie("dosp") === "1") { $.removeCookie("dosp", { path: "/" }); - notices.push([I18n.t("forced_anonymous"), "forced-anonymous"]); + notices.push( + Notice.create({ + text: I18n.t("forced_anonymous"), + id: "forced-anonymous" + }) + ); } - if (this.session.get("safe_mode")) { - notices.push([I18n.t("safe_mode.enabled"), "safe-mode"]); + if (this.session && this.session.safe_mode) { + notices.push( + Notice.create({ text: I18n.t("safe_mode.enabled"), id: "safe-mode" }) + ); } - if (this.site.get("isReadOnly")) { - notices.push([I18n.t("read_only_mode.enabled"), "alert-read-only"]); + if (this.site.isReadOnly) { + notices.push( + Notice.create({ + text: I18n.t("read_only_mode.enabled"), + id: "alert-read-only" + }) + ); } if ( this.siteSettings.disable_emails === "yes" || this.siteSettings.disable_emails === "non-staff" ) { - notices.push([I18n.t("emails_are_disabled"), "alert-emails-disabled"]); + notices.push( + Notice.create({ + text: I18n.t("emails_are_disabled"), + id: "alert-emails-disabled" + }) + ); } - if (this.site.get("wizard_required")) { + if (this.site.wizard_required) { const requiredText = I18n.t("wizard_required", { url: Discourse.getURL("/wizard") }); - notices.push([requiredText, "alert-wizard"]); + notices.push(Notice.create({ text: requiredText, id: "alert-wizard" })); } if ( - this.currentUser && - this.currentUser.get("staff") && + this.get("currentUser.staff") && this.siteSettings.bootstrap_mode_enabled ) { if (this.siteSettings.bootstrap_mode_min_users > 0) { - notices.push([ - I18n.t("bootstrap_mode_enabled", { - min_users: this.siteSettings.bootstrap_mode_min_users - }), - "alert-bootstrap-mode" - ]); + notices.push( + Notice.create({ + text: I18n.t("bootstrap_mode_enabled", { + min_users: this.siteSettings.bootstrap_mode_min_users + }), + id: "alert-bootstrap-mode" + }) + ); } else { - notices.push([ - I18n.t("bootstrap_mode_disabled"), - "alert-bootstrap-mode" - ]); + notices.push( + Notice.create({ + text: I18n.t("bootstrap_mode_disabled"), + id: "alert-bootstrap-mode" + }) + ); } } - if (!_.isEmpty(this.siteSettings.global_notice)) { - notices.push([this.siteSettings.global_notice, "alert-global-notice"]); - } - - if (!LogsNotice.currentProp("hidden")) { - notices.push([ - LogsNotice.currentProp("message"), - "alert-logs-notice", - `` - ]); - } - - if (notices.length > 0) { - buffer.push( - notices - .map(n => { - var html = `

`; - if (n[2]) html += n[2]; - html += `${n[0]}
`; - return html; - }) - .join("") + if ( + this.siteSettings.global_notice && + this.siteSettings.global_notice.length + ) { + notices.push( + Notice.create({ + text: this.siteSettings.global_notice, + id: "alert-global-notice" + }) ); } - }, - @on("didInsertElement") - _setupLogsNotice() { - this._boundRerenderBuffer = bind(this, this.rerenderBuffer); - LogsNotice.current().addObserver("hidden", this._boundRerenderBuffer); - - this._boundResetCurrentProp = bind(this, this._resetCurrentProp); - $(this.element).on( - "click.global-notice", - ".alert-logs-notice .close", - this._boundResetCurrentProp - ); - }, - - @on("willDestroyElement") - _teardownLogsNotice() { - if (this._boundResetCurrentProp) { - $(this.element).off("click.global-notice", this._boundResetCurrentProp); + if (this.logNotice) { + notices.push(this.logNotice); } - if (this._boundRerenderBuffer) { - LogsNotice.current().removeObserver( - "hidden", - this._boundRerenderBuffer - ); - } - }, + return notices.concat(_pluginNotices).filter(notice => { + if (notice.options.visibility) { + return notice.options.visibility(notice); + } else { + const key = `${GLOBAL_NOTICE_DISMISSED_PROMPT_KEY}-${notice.id}`; + const value = this.keyValueStore.get(key); - _resetCurrentProp() { - LogsNotice.currentProp("text", ""); + // banner has never been dismissed + if (!value) { + return true; + } + + // banner has no persistent dismiss and should always show on load + if (!notice.options.persistentDismiss) { + return true; + } + + if (notice.options.dismissDuration) { + const resetAt = moment(value).add(notice.options.dismissDuration); + return moment().isAfter(resetAt); + } else { + return false; + } + } + }); } - }) -); + ), + + actions: { + dismissNotice(notice) { + if (notice.options.onDismiss) { + notice.options.onDismiss(notice); + } + + if (notice.options.persistentDismiss) { + this.keyValueStore.set({ + key: `${GLOBAL_NOTICE_DISMISSED_PROMPT_KEY}-${notice.id}`, + value: moment().toISOString(true) + }); + } + + const alert = document.getElementById(`global-notice-${notice.id}`); + if (alert) alert.style.display = "none"; + } + }, + + _setupObservers() { + this._boundLogsNoticeHandler = bind(this, this._handleLogsNoticeUpdate); + LogsNotice.current().addObserver("hidden", this._boundLogsNoticeHandler); + LogsNotice.current().addObserver("text", this._boundLogsNoticeHandler); + }, + + _tearDownObservers() { + if (this._boundLogsNoticeHandler) { + LogsNotice.current().removeObserver("text", this._boundLogsNoticeHandler); + LogsNotice.current().removeObserver( + "hidden", + this._boundLogsNoticeHandler + ); + cancel(this._boundLogsNoticeHandler); + } + }, + + _handleLogsNoticeUpdate() { + const logNotice = Notice.create({ + text: LogsNotice.currentProp("message"), + id: "alert-logs-notice", + options: { + dismissable: true, + persistentDismiss: false, + visibility() { + return !LogsNotice.currentProp("hidden"); + }, + onDismiss() { + LogsNotice.currentProp("hidden", true); + LogsNotice.currentProp("text", ""); + } + } + }); + + this.set("logNotice", logNotice); + } +}); diff --git a/app/assets/javascripts/discourse/components/group-card-contents.js.es6 b/app/assets/javascripts/discourse/components/group-card-contents.js.es6 index 1838b32352..38565e2a76 100644 --- a/app/assets/javascripts/discourse/components/group-card-contents.js.es6 +++ b/app/assets/javascripts/discourse/components/group-card-contents.js.es6 @@ -1,7 +1,7 @@ import { alias, match, gt, or } from "@ember/object/computed"; import Component from "@ember/component"; import { setting } from "discourse/lib/computed"; -import { default as discourseComputed } from "discourse-common/utils/decorators"; +import discourseComputed from "discourse-common/utils/decorators"; import CardContentsBase from "discourse/mixins/card-contents-base"; import CleansUp from "discourse/mixins/cleans-up"; import { groupPath } from "discourse/lib/url"; diff --git a/app/assets/javascripts/discourse/components/group-membership-button.js.es6 b/app/assets/javascripts/discourse/components/group-membership-button.js.es6 index 7c4abeda18..7c808bc3eb 100644 --- a/app/assets/javascripts/discourse/components/group-membership-button.js.es6 +++ b/app/assets/javascripts/discourse/components/group-membership-button.js.es6 @@ -1,5 +1,5 @@ import Component from "@ember/component"; -import { default as discourseComputed } from "discourse-common/utils/decorators"; +import discourseComputed from "discourse-common/utils/decorators"; import { popupAjaxError } from "discourse/lib/ajax-error"; import showModal from "discourse/lib/show-modal"; @@ -31,6 +31,14 @@ export default Component.extend({ $.cookie("destination_url", window.location.href); }, + removeFromGroup() { + this.model + .removeMember(this.currentUser) + .then(() => this.model.set("is_group_user", false)) + .catch(popupAjaxError) + .finally(() => this.set("updatingMembership", false)); + }, + actions: { joinGroup() { if (this.currentUser) { @@ -53,17 +61,21 @@ export default Component.extend({ leaveGroup() { this.set("updatingMembership", true); - const model = this.model; - model - .removeMember(this.currentUser) - .then(() => { - model.set("is_group_user", false); - }) - .catch(popupAjaxError) - .finally(() => { - this.set("updatingMembership", false); - }); + if (this.model.public_admission) { + this.removeFromGroup(); + } else { + return bootbox.confirm( + I18n.t("groups.confirm_leave"), + I18n.t("no_value"), + I18n.t("yes_value"), + result => { + result + ? this.removeFromGroup() + : this.set("updatingMembership", false); + } + ); + } }, showRequestMembershipForm() { diff --git a/app/assets/javascripts/discourse/components/group-selector.js.es6 b/app/assets/javascripts/discourse/components/group-selector.js.es6 index 54b789664b..74c1c4b16f 100644 --- a/app/assets/javascripts/discourse/components/group-selector.js.es6 +++ b/app/assets/javascripts/discourse/components/group-selector.js.es6 @@ -1,9 +1,8 @@ import { isEmpty } from "@ember/utils"; import Component from "@ember/component"; -import { +import discourseComputed, { on, - observes, - default as discourseComputed + observes } from "discourse-common/utils/decorators"; import { findRawTemplate } from "discourse/lib/raw-templates"; diff --git a/app/assets/javascripts/discourse/components/groups-form-interaction-fields.js.es6 b/app/assets/javascripts/discourse/components/groups-form-interaction-fields.js.es6 index f3ba2d92a4..62a7d17ece 100644 --- a/app/assets/javascripts/discourse/components/groups-form-interaction-fields.js.es6 +++ b/app/assets/javascripts/discourse/components/groups-form-interaction-fields.js.es6 @@ -1,5 +1,5 @@ import Component from "@ember/component"; -import { default as discourseComputed } from "discourse-common/utils/decorators"; +import discourseComputed from "discourse-common/utils/decorators"; export default Component.extend({ init() { diff --git a/app/assets/javascripts/discourse/components/groups-form-profile-fields.js.es6 b/app/assets/javascripts/discourse/components/groups-form-profile-fields.js.es6 index fda6dd8ec6..988e4ac843 100644 --- a/app/assets/javascripts/discourse/components/groups-form-profile-fields.js.es6 +++ b/app/assets/javascripts/discourse/components/groups-form-profile-fields.js.es6 @@ -1,10 +1,7 @@ import { isEmpty } from "@ember/utils"; import { not } from "@ember/object/computed"; import Component from "@ember/component"; -import { - default as discourseComputed, - observes -} from "discourse-common/utils/decorators"; +import discourseComputed, { observes } from "discourse-common/utils/decorators"; import Group from "discourse/models/group"; import { popupAjaxError } from "discourse/lib/ajax-error"; import discourseDebounce from "discourse/lib/debounce"; diff --git a/app/assets/javascripts/discourse/components/input-tip.js.es6 b/app/assets/javascripts/discourse/components/input-tip.js.es6 index 1a12d8ebc0..b48a5df8cf 100644 --- a/app/assets/javascripts/discourse/components/input-tip.js.es6 +++ b/app/assets/javascripts/discourse/components/input-tip.js.es6 @@ -4,7 +4,6 @@ import { iconHTML } from "discourse-common/lib/icon-library"; export default Component.extend({ classNameBindings: [":tip", "good", "bad"], - rerenderTriggers: ["validation"], tipIcon: null, tipReason: null, diff --git a/app/assets/javascripts/discourse/components/navigation-bar.js.es6 b/app/assets/javascripts/discourse/components/navigation-bar.js.es6 index 6051726395..3da0001ad9 100644 --- a/app/assets/javascripts/discourse/components/navigation-bar.js.es6 +++ b/app/assets/javascripts/discourse/components/navigation-bar.js.es6 @@ -1,9 +1,6 @@ import { next } from "@ember/runloop"; import Component from "@ember/component"; -import { - default as discourseComputed, - observes -} from "discourse-common/utils/decorators"; +import discourseComputed, { observes } from "discourse-common/utils/decorators"; import DiscourseURL from "discourse/lib/url"; import { renderedConnectorsFor } from "discourse/lib/plugin-connectors"; import FilterModeMixin from "discourse/mixins/filter-mode"; diff --git a/app/assets/javascripts/discourse/components/notification-consent-banner.js.es6 b/app/assets/javascripts/discourse/components/notification-consent-banner.js.es6 index 1ffb99c0c2..97ee9b0aa7 100644 --- a/app/assets/javascripts/discourse/components/notification-consent-banner.js.es6 +++ b/app/assets/javascripts/discourse/components/notification-consent-banner.js.es6 @@ -1,6 +1,6 @@ -import { default as discourseComputed } from "discourse-common/utils/decorators"; +import discourseComputed from "discourse-common/utils/decorators"; import { keyValueStore as pushNotificationKeyValueStore } from "discourse/lib/push-notifications"; -import { default as DesktopNotificationConfig } from "discourse/components/desktop-notification-config"; +import DesktopNotificationConfig from "discourse/components/desktop-notification-config"; const userDismissedPromptKey = "dismissed-prompt"; diff --git a/app/assets/javascripts/discourse/components/popup-input-tip.js.es6 b/app/assets/javascripts/discourse/components/popup-input-tip.js.es6 index 6254fe2f1e..08f6cf39af 100644 --- a/app/assets/javascripts/discourse/components/popup-input-tip.js.es6 +++ b/app/assets/javascripts/discourse/components/popup-input-tip.js.es6 @@ -1,10 +1,7 @@ import { alias, not } from "@ember/object/computed"; import Component from "@ember/component"; import { iconHTML } from "discourse-common/lib/icon-library"; -import { - default as discourseComputed, - observes -} from "discourse-common/utils/decorators"; +import discourseComputed, { observes } from "discourse-common/utils/decorators"; export default Component.extend({ classNameBindings: [":popup-tip", "good", "bad", "lastShownAt::hide"], diff --git a/app/assets/javascripts/discourse/components/private-message-glyph.js.es6 b/app/assets/javascripts/discourse/components/private-message-glyph.js.es6 new file mode 100644 index 0000000000..26e636b05d --- /dev/null +++ b/app/assets/javascripts/discourse/components/private-message-glyph.js.es6 @@ -0,0 +1,8 @@ +import Component from "@ember/component"; + +export default Component.extend({ + tagName: null, + href: null, + title: null, + ariaLabel: null +}); diff --git a/app/assets/javascripts/discourse/components/pwa-install-banner.js.es6 b/app/assets/javascripts/discourse/components/pwa-install-banner.js.es6 index cb11ef38b7..1dd4bef396 100644 --- a/app/assets/javascripts/discourse/components/pwa-install-banner.js.es6 +++ b/app/assets/javascripts/discourse/components/pwa-install-banner.js.es6 @@ -1,9 +1,6 @@ import { bind } from "@ember/runloop"; import Component from "@ember/component"; -import { - default as discourseComputed, - on -} from "discourse-common/utils/decorators"; +import discourseComputed, { on } from "discourse-common/utils/decorators"; const USER_DISMISSED_PROMPT_KEY = "dismissed-pwa-install-banner"; diff --git a/app/assets/javascripts/discourse/components/related-messages.js.es6 b/app/assets/javascripts/discourse/components/related-messages.js.es6 index 5fec6f28ba..46732424f8 100644 --- a/app/assets/javascripts/discourse/components/related-messages.js.es6 +++ b/app/assets/javascripts/discourse/components/related-messages.js.es6 @@ -1,6 +1,5 @@ import discourseComputed from "discourse-common/utils/decorators"; import Component from "@ember/component"; -import { iconHTML } from "discourse-common/lib/icon-library"; export default Component.extend({ elementId: "related-messages", @@ -31,14 +30,7 @@ export default Component.extend({ }, @discourseComputed("topic") - relatedTitle(topic) { - const href = this.currentUser && this.currentUser.pmPath(topic); - return href - ? `${iconHTML("envelope", { - class: "private-message-glyph" - })}${I18n.t("related_messages.title")}` - : I18n.t("related_messages.title"); + relatedTitleLink(topic) { + return this.currentUser && this.currentUser.pmPath(topic); } }); diff --git a/app/assets/javascripts/discourse/components/reviewable-user.js.es6 b/app/assets/javascripts/discourse/components/reviewable-user.js.es6 index 3dd3043371..5feb5d84f2 100644 --- a/app/assets/javascripts/discourse/components/reviewable-user.js.es6 +++ b/app/assets/javascripts/discourse/components/reviewable-user.js.es6 @@ -1,5 +1,5 @@ import Component from "@ember/component"; -import { default as discourseComputed } from "discourse-common/utils/decorators"; +import discourseComputed from "discourse-common/utils/decorators"; export default Component.extend({ @discourseComputed("reviewable.user_fields") diff --git a/app/assets/javascripts/discourse/components/second-factor-input.js.es6 b/app/assets/javascripts/discourse/components/second-factor-input.js.es6 index 45ed1f4b9e..98ca98ac01 100644 --- a/app/assets/javascripts/discourse/components/second-factor-input.js.es6 +++ b/app/assets/javascripts/discourse/components/second-factor-input.js.es6 @@ -19,6 +19,6 @@ export default Component.extend({ @discourseComputed("secondFactorMethod") maxlength(secondFactorMethod) { if (secondFactorMethod === SECOND_FACTOR_METHODS.TOTP) return "6"; - if (secondFactorMethod === SECOND_FACTOR_METHODS.BACKUP_CODE) return "16"; + if (secondFactorMethod === SECOND_FACTOR_METHODS.BACKUP_CODE) return "32"; } }); diff --git a/app/assets/javascripts/discourse/components/share-panel.js.es6 b/app/assets/javascripts/discourse/components/share-panel.js.es6 index 97e09c225e..78bc76db23 100644 --- a/app/assets/javascripts/discourse/components/share-panel.js.es6 +++ b/app/assets/javascripts/discourse/components/share-panel.js.es6 @@ -3,7 +3,7 @@ import { alias } from "@ember/object/computed"; import { schedule } from "@ember/runloop"; import Component from "@ember/component"; import { escapeExpression } from "discourse/lib/utilities"; -import { default as discourseComputed } from "discourse-common/utils/decorators"; +import discourseComputed from "discourse-common/utils/decorators"; import Sharing from "discourse/lib/sharing"; export default Component.extend({ diff --git a/app/assets/javascripts/discourse/components/share-popup.js.es6 b/app/assets/javascripts/discourse/components/share-popup.js.es6 index c2174068bd..ba32b26585 100644 --- a/app/assets/javascripts/discourse/components/share-popup.js.es6 +++ b/app/assets/javascripts/discourse/components/share-popup.js.es6 @@ -4,10 +4,7 @@ import { scheduleOnce } from "@ember/runloop"; import Component from "@ember/component"; import { wantsNewWindow } from "discourse/lib/intercept-click"; import { longDateNoYear } from "discourse/lib/formatter"; -import { - default as discourseComputed, - on -} from "discourse-common/utils/decorators"; +import discourseComputed, { on } from "discourse-common/utils/decorators"; import Sharing from "discourse/lib/sharing"; import { nativeShare } from "discourse/lib/pwa-utils"; diff --git a/app/assets/javascripts/discourse/components/suggested-topics.js.es6 b/app/assets/javascripts/discourse/components/suggested-topics.js.es6 index c8ea62e827..d6010eba93 100644 --- a/app/assets/javascripts/discourse/components/suggested-topics.js.es6 +++ b/app/assets/javascripts/discourse/components/suggested-topics.js.es6 @@ -2,24 +2,20 @@ import discourseComputed from "discourse-common/utils/decorators"; import { get } from "@ember/object"; import Component from "@ember/component"; import { categoryBadgeHTML } from "discourse/helpers/category-link"; -import { iconHTML } from "discourse-common/lib/icon-library"; import Site from "discourse/models/site"; +import { computed } from "@ember/object"; export default Component.extend({ elementId: "suggested-topics", classNames: ["suggested-topics"], - @discourseComputed("topic") - suggestedTitle(topic) { - const href = this.currentUser && this.currentUser.pmPath(topic); - return topic.get("isPrivateMessage") && href - ? `${iconHTML("envelope", { - class: "private-message-glyph" - })}${I18n.t("suggested_topics.pm_title")}` - : I18n.t("suggested_topics.title"); - }, + suggestedTitleLabel: computed("topic", function() { + if (this.currentUser && this.currentUser.pmPath(this.topic)) { + return "suggested_topics.pm_title"; + } else { + return "suggested_topics.title"; + } + }), @discourseComputed("topic", "topicTrackingState.messageCount") browseMoreMessage(topic) { diff --git a/app/assets/javascripts/discourse/components/tag-drop-link.js.es6 b/app/assets/javascripts/discourse/components/tag-drop-link.js.es6 index e8cfada41a..d26c68d0b3 100644 --- a/app/assets/javascripts/discourse/components/tag-drop-link.js.es6 +++ b/app/assets/javascripts/discourse/components/tag-drop-link.js.es6 @@ -14,11 +14,11 @@ export default Component.extend({ @discourseComputed("tagId", "category") href(tagId, category) { - var url = "/tags"; if (category) { - url += category.url; + return "/tags" + category.url + "/" + tagId; + } else { + return "/tag/" + tagId; } - return url + "/" + tagId; }, @discourseComputed("tagId") diff --git a/app/assets/javascripts/discourse/components/tag-info.js.es6 b/app/assets/javascripts/discourse/components/tag-info.js.es6 index da1acd10a0..6421f47f59 100644 --- a/app/assets/javascripts/discourse/components/tag-info.js.es6 +++ b/app/assets/javascripts/discourse/components/tag-info.js.es6 @@ -1,7 +1,7 @@ import { ajax } from "discourse/lib/ajax"; import { popupAjaxError } from "discourse/lib/ajax-error"; import showModal from "discourse/lib/show-modal"; -import { default as discourseComputed } from "discourse-common/utils/decorators"; +import discourseComputed from "discourse-common/utils/decorators"; import Component from "@ember/component"; import { reads, and } from "@ember/object/computed"; import { isEmpty } from "@ember/utils"; @@ -76,7 +76,7 @@ export default Component.extend({ }, unlinkSynonym(tag) { - ajax(`/tags/${this.tagInfo.name}/synonyms/${tag.id}`, { + ajax(`/tag/${this.tagInfo.name}/synonyms/${tag.id}`, { type: "DELETE" }) .then(() => this.tagInfo.synonyms.removeObject(tag)) @@ -98,7 +98,7 @@ export default Component.extend({ }, addSynonyms() { - ajax(`/tags/${this.tagInfo.name}/synonyms`, { + ajax(`/tag/${this.tagInfo.name}/synonyms`, { type: "POST", data: { synonyms: this.newSynonyms diff --git a/app/assets/javascripts/discourse/components/tags-admin-dropdown.js.es6 b/app/assets/javascripts/discourse/components/tags-admin-dropdown.js.es6 index 85e8caf025..608d2e588c 100644 --- a/app/assets/javascripts/discourse/components/tags-admin-dropdown.js.es6 +++ b/app/assets/javascripts/discourse/components/tags-admin-dropdown.js.es6 @@ -35,7 +35,7 @@ export default DropdownSelectBoxComponent.extend({ id: "deleteUnusedTags", name: I18n.t("tagging.delete_unused"), description: I18n.t("tagging.delete_unused_description"), - icon: "trash", + icon: "trash-alt", __sk_row_type: "noopRow" } ]; diff --git a/app/assets/javascripts/discourse/components/text-overflow.js.es6 b/app/assets/javascripts/discourse/components/text-overflow.js.es6 index d5bc066fb0..90a9cc21a2 100644 --- a/app/assets/javascripts/discourse/components/text-overflow.js.es6 +++ b/app/assets/javascripts/discourse/components/text-overflow.js.es6 @@ -7,6 +7,7 @@ export default Component.extend({ const $this = $(this.element); if ($this) { + $this.find("br").replaceWith(" "); $this.find("hr").remove(); $this.ellipsis(); } diff --git a/app/assets/javascripts/discourse/components/topic-join-group-notice.js.es6 b/app/assets/javascripts/discourse/components/topic-join-group-notice.js.es6 index 4a55fb8886..07db8b0fa8 100644 --- a/app/assets/javascripts/discourse/components/topic-join-group-notice.js.es6 +++ b/app/assets/javascripts/discourse/components/topic-join-group-notice.js.es6 @@ -1,5 +1,5 @@ import Component from "@ember/component"; -import { default as discourseComputed } from "discourse-common/utils/decorators"; +import discourseComputed from "discourse-common/utils/decorators"; export default Component.extend({ classNames: ["topic-notice"], diff --git a/app/assets/javascripts/discourse/components/topic-list-item.js.es6 b/app/assets/javascripts/discourse/components/topic-list-item.js.es6 index 670d45b64c..7bc2d64603 100644 --- a/app/assets/javascripts/discourse/components/topic-list-item.js.es6 +++ b/app/assets/javascripts/discourse/components/topic-list-item.js.es6 @@ -1,8 +1,8 @@ -import discourseComputed from "discourse-common/utils/decorators"; +import discourseComputed, { observes } from "discourse-common/utils/decorators"; import { alias } from "@ember/object/computed"; import Component from "@ember/component"; +import { schedule } from "@ember/runloop"; import DiscourseURL from "discourse/lib/url"; -import { bufferedRender } from "discourse-common/lib/buffered-render"; import { findRawTemplate } from "discourse/lib/raw-templates"; import { wantsNewWindow } from "discourse/lib/intercept-click"; import { on } from "@ember/object/evented"; @@ -32,12 +32,25 @@ export function navigateToTopic(topic, href) { return false; } -export const ListItemDefaults = { +export default Component.extend({ tagName: "tr", classNameBindings: [":topic-list-item", "unboundClassNames", "topic.visited"], attributeBindings: ["data-topic-id"], "data-topic-id": alias("topic.id"), + didReceiveAttrs() { + this._super(...arguments); + this.renderTopicListItem(); + }, + + @observes("topic.pinned") + renderTopicListItem() { + const template = findRawTemplate("list/topic-list-item"); + if (template) { + this.set("topicListItemContents", template(this).htmlSafe()); + } + }, + didInsertElement() { this._super(...arguments); @@ -194,15 +207,29 @@ export const ListItemDefaults = { return this.unhandledRowClick(e, topic); }, + actions: { + toggleBookmark() { + this.topic.toggleBookmark().finally(() => this.renderTopicListItem()); + } + }, + + unhandledRowClick() {}, + navigateToTopic, highlight(opts = { isLastViewedTopic: false }) { - const $topic = $(this.element); - $topic - .addClass("highlighted") - .attr("data-islastviewedtopic", opts.isLastViewedTopic); + schedule("afterRender", () => { + if (!this.element || this.isDestroying || this.isDestroyed) { + return; + } - $topic.on("animationend", () => $topic.removeClass("highlighted")); + const $topic = $(this.element); + $topic + .addClass("highlighted") + .attr("data-islastviewedtopic", opts.isLastViewedTopic); + + $topic.on("animationend", () => $topic.removeClass("highlighted")); + }); }, _highlightIfNeeded: on("didInsertElement", function() { @@ -216,27 +243,4 @@ export const ListItemDefaults = { this.highlight(); } }) -}; - -export default Component.extend( - ListItemDefaults, - bufferedRender({ - rerenderTriggers: ["bulkSelectEnabled", "topic.pinned"], - - actions: { - toggleBookmark() { - this.topic.toggleBookmark().finally(() => this.rerenderBuffer()); - } - }, - - buildBuffer(buffer) { - const template = findRawTemplate("list/topic-list-item"); - if (template) { - buffer.push(template(this)); - } - }, - - // Can be overwritten by plugins to handle clicks on other parts of the row - unhandledRowClick() {} - }) -); +}); diff --git a/app/assets/javascripts/discourse/components/topic-list.js.es6 b/app/assets/javascripts/discourse/components/topic-list.js.es6 index d744b28a70..b4661eb68d 100644 --- a/app/assets/javascripts/discourse/components/topic-list.js.es6 +++ b/app/assets/javascripts/discourse/components/topic-list.js.es6 @@ -1,10 +1,7 @@ import { alias, reads } from "@ember/object/computed"; import { schedule } from "@ember/runloop"; import Component from "@ember/component"; -import { - default as discourseComputed, - observes -} from "discourse-common/utils/decorators"; +import discourseComputed, { observes } from "discourse-common/utils/decorators"; import LoadMore from "discourse/mixins/load-more"; import { on } from "@ember/object/evented"; diff --git a/app/assets/javascripts/discourse/components/topic-progress.js.es6 b/app/assets/javascripts/discourse/components/topic-progress.js.es6 index 3e055c96ed..61c4484be4 100644 --- a/app/assets/javascripts/discourse/components/topic-progress.js.es6 +++ b/app/assets/javascripts/discourse/components/topic-progress.js.es6 @@ -1,10 +1,7 @@ import { alias } from "@ember/object/computed"; import { scheduleOnce } from "@ember/runloop"; import Component from "@ember/component"; -import { - default as discourseComputed, - observes -} from "discourse-common/utils/decorators"; +import discourseComputed, { observes } from "discourse-common/utils/decorators"; export default Component.extend({ elementId: "topic-progress-wrapper", diff --git a/app/assets/javascripts/discourse/components/topic-timeline.js.es6 b/app/assets/javascripts/discourse/components/topic-timeline.js.es6 index 64f0dbc2d8..233155f2f3 100644 --- a/app/assets/javascripts/discourse/components/topic-timeline.js.es6 +++ b/app/assets/javascripts/discourse/components/topic-timeline.js.es6 @@ -102,5 +102,6 @@ export default MountWidget.extend(Docking, { } this.dispatch("topic:current-post-scrolled", "timeline-scrollarea"); + this.dispatch("topic:toggle-actions", "topic-admin-menu-button"); } }); diff --git a/app/assets/javascripts/discourse/components/track-selected.js.es6 b/app/assets/javascripts/discourse/components/track-selected.js.es6 index 241cb64c7c..e8d0ec3428 100644 --- a/app/assets/javascripts/discourse/components/track-selected.js.es6 +++ b/app/assets/javascripts/discourse/components/track-selected.js.es6 @@ -1,6 +1,10 @@ import Component from "@ember/component"; +import { observes } from "discourse-common/utils/decorators"; + export default Component.extend({ tagName: "span", + + @observes("selected") selectionChanged: function() { const selected = this.selected; const list = this.selectedList; @@ -11,5 +15,5 @@ export default Component.extend({ } else { list.removeObject(id); } - }.observes("selected") + } }); diff --git a/app/assets/javascripts/discourse/components/user-card-contents.js.es6 b/app/assets/javascripts/discourse/components/user-card-contents.js.es6 index 6607fe7961..6d76c15306 100644 --- a/app/assets/javascripts/discourse/components/user-card-contents.js.es6 +++ b/app/assets/javascripts/discourse/components/user-card-contents.js.es6 @@ -2,10 +2,7 @@ import { isEmpty } from "@ember/utils"; import { alias, gte, and, gt, not, or } from "@ember/object/computed"; import EmberObject from "@ember/object"; import Component from "@ember/component"; -import { - default as discourseComputed, - observes -} from "discourse-common/utils/decorators"; +import discourseComputed, { observes } from "discourse-common/utils/decorators"; import User from "discourse/models/user"; import { propertyNotEqual, setting } from "discourse/lib/computed"; import { durationTiny } from "discourse/lib/formatter"; diff --git a/app/assets/javascripts/discourse/components/user-selector.js.es6 b/app/assets/javascripts/discourse/components/user-selector.js.es6 index 85e5c61dd7..79de2262f3 100644 --- a/app/assets/javascripts/discourse/components/user-selector.js.es6 +++ b/app/assets/javascripts/discourse/components/user-selector.js.es6 @@ -62,14 +62,15 @@ export default TextField.extend({ allowEmails = bool("allowEmails"), fullWidthWrap = bool("fullWidthWrap"); - const excludedUsernames = () => { + const allExcludedUsernames = () => { // hack works around some issues with allowAny eventing - const usernames = single ? [] : selected; + let usernames = single ? [] : selected; if (currentUser && excludeCurrentUser) { - return usernames.concat([currentUser.username]); + usernames.concat([currentUser.username]); } - return usernames; + + return usernames.concat(this.excludedUsernames || []); }; this.element.addEventListener("paste", this._paste); @@ -90,7 +91,7 @@ export default TextField.extend({ return userSearch({ term, topicId: userSelectorComponent.topicId, - exclude: excludedUsernames(), + exclude: allExcludedUsernames(), includeGroups, allowedUsers, includeMentionableGroups, @@ -107,7 +108,7 @@ export default TextField.extend({ } return v.username || v.name; } else { - const excludes = excludedUsernames(); + const excludes = allExcludedUsernames(); return v.usernames.filter(item => excludes.indexOf(item) === -1); } }, @@ -158,7 +159,10 @@ export default TextField.extend({ (text || "").split(/[, \n]+/).forEach(val => { val = val.replace(/^@+/, "").trim(); - if (val.length > 0) { + if ( + val.length > 0 && + (!this.excludedUsernames || !this.excludedUsernames.includes(val)) + ) { usernames.push(val); } }); diff --git a/app/assets/javascripts/discourse/components/user-stream.js.es6 b/app/assets/javascripts/discourse/components/user-stream.js.es6 index 79543dd394..62d9fe7f0d 100644 --- a/app/assets/javascripts/discourse/components/user-stream.js.es6 +++ b/app/assets/javascripts/discourse/components/user-stream.js.es6 @@ -7,6 +7,7 @@ import DiscourseURL from "discourse/lib/url"; import Draft from "discourse/models/draft"; import { popupAjaxError } from "discourse/lib/ajax-error"; import { getOwner } from "discourse-common/lib/get-owner"; +import { observes } from "discourse-common/utils/decorators"; import { on } from "@ember/object/evented"; export default Component.extend(LoadMore, { @@ -24,9 +25,10 @@ export default Component.extend(LoadMore, { eyelineSelector: ".user-stream .item", classNames: ["user-stream"], + @observes("stream.user.id") _scrollTopOnModelChange: function() { schedule("afterRender", () => $(document).scrollTop(0)); - }.observes("stream.user.id"), + }, _inserted: on("didInsertElement", function() { this.bindScrolling({ name: "user-stream-view" }); diff --git a/app/assets/javascripts/discourse/controllers/badges/show.js.es6 b/app/assets/javascripts/discourse/controllers/badges/show.js.es6 index 807ea2b596..e9d70aa80f 100644 --- a/app/assets/javascripts/discourse/controllers/badges/show.js.es6 +++ b/app/assets/javascripts/discourse/controllers/badges/show.js.es6 @@ -3,10 +3,7 @@ import EmberObject from "@ember/object"; import Controller from "@ember/controller"; import Badge from "discourse/models/badge"; import UserBadge from "discourse/models/user-badge"; -import { - default as discourseComputed, - observes -} from "discourse-common/utils/decorators"; +import discourseComputed, { observes } from "discourse-common/utils/decorators"; export default Controller.extend({ queryParams: ["username"], diff --git a/app/assets/javascripts/discourse/controllers/bookmark.js.es6 b/app/assets/javascripts/discourse/controllers/bookmark.js.es6 index e46cf63c20..fd61ec759d 100644 --- a/app/assets/javascripts/discourse/controllers/bookmark.js.es6 +++ b/app/assets/javascripts/discourse/controllers/bookmark.js.es6 @@ -90,7 +90,7 @@ export default Controller.extend(ModalFunctionality, { nextWeekFormatted() { return htmlSafe( I18n.t("bookmarks.reminders.next_week", { - date: this.nextWeek().format(I18n.t("dates.month_day_time")) + date: this.nextWeek().format(I18n.t("dates.long_no_year")) }) ); }, @@ -99,7 +99,7 @@ export default Controller.extend(ModalFunctionality, { nextMonthFormatted() { return htmlSafe( I18n.t("bookmarks.reminders.next_month", { - date: this.nextMonth().format(I18n.t("dates.month_day_time")) + date: this.nextMonth().format(I18n.t("dates.long_no_year")) }) ); }, diff --git a/app/assets/javascripts/discourse/controllers/composer.js.es6 b/app/assets/javascripts/discourse/controllers/composer.js.es6 index a379d8f7e1..6e5f10f3f1 100644 --- a/app/assets/javascripts/discourse/controllers/composer.js.es6 +++ b/app/assets/javascripts/discourse/controllers/composer.js.es6 @@ -8,8 +8,7 @@ import DiscourseURL from "discourse/lib/url"; import Quote from "discourse/lib/quote"; import Draft from "discourse/models/draft"; import Composer from "discourse/models/composer"; -import { - default as discourseComputed, +import discourseComputed, { observes, on } from "discourse-common/utils/decorators"; @@ -25,6 +24,7 @@ import { SAVE_LABELS, SAVE_ICONS } from "discourse/models/composer"; import { Promise } from "rsvp"; import ENV from "discourse-common/config/environment"; import EmberObject, { computed } from "@ember/object"; +import deprecated from "discourse-common/lib/deprecated"; function loadDraft(store, opts) { opts = opts || {}; @@ -129,7 +129,7 @@ export default Controller.extend({ @discourseComputed( "model.replyingToTopic", "model.creatingPrivateMessage", - "model.targetUsernames", + "model.targetRecipients", "model.composeState" ) focusTarget(replyingToTopic, creatingPM, usernames, composeState) { @@ -246,7 +246,10 @@ export default Controller.extend({ }, _setupPopupMenuOption(callback) { - let option = callback(); + let option = callback(this); + if (typeof option === "undefined") { + return null; + } if (typeof option.condition === "undefined") { option.condition = true; @@ -287,14 +290,14 @@ export default Controller.extend({ ); return options.concat( - _popupMenuOptionsCallbacks.map(callback => - this._setupPopupMenuOption(callback) - ) + _popupMenuOptionsCallbacks + .map(callback => this._setupPopupMenuOption(callback)) + .filter(o => o) ); } }, - @discourseComputed("model.creatingPrivateMessage", "model.targetUsernames") + @discourseComputed("model.creatingPrivateMessage", "model.targetRecipients") showWarning(creatingPrivateMessage, usernames) { if (!this.get("currentUser.staff")) { return false; @@ -909,19 +912,24 @@ export default Controller.extend({ isWarning: false }); - if (opts.usernames && !this.get("model.targetUsernames")) { - this.set("model.targetUsernames", opts.usernames); + if (!this.model.targetRecipients) { + if (opts.usernames) { + deprecated("`usernames` is deprecated, use `recipients` instead."); + this.model.set("targetRecipients", opts.usernames); + } else if (opts.recipients) { + this.model.set("targetRecipients", opts.recipients); + } } if ( opts.topicTitle && opts.topicTitle.length <= this.siteSettings.max_topic_title_length ) { - this.set("model.title", opts.topicTitle); + this.model.set("title", opts.topicTitle); } if (opts.topicCategoryId) { - this.set("model.categoryId", opts.topicCategoryId); + this.model.set("categoryId", opts.topicCategoryId); } if (opts.topicTags && !this.site.mobileView && this.site.can_tag_topics) { @@ -934,11 +942,11 @@ export default Controller.extend({ (array[index] = tag.substring(0, this.siteSettings.max_tag_length)) ); - this.set("model.tags", tags); + this.model.set("tags", tags); } if (opts.topicBody) { - this.set("model.reply", opts.topicBody); + this.model.set("reply", opts.topicBody); } }, diff --git a/app/assets/javascripts/discourse/controllers/create-account.js.es6 b/app/assets/javascripts/discourse/controllers/create-account.js.es6 index 5bfe1ec139..84b3171cd6 100644 --- a/app/assets/javascripts/discourse/controllers/create-account.js.es6 +++ b/app/assets/javascripts/discourse/controllers/create-account.js.es6 @@ -5,8 +5,8 @@ import Controller from "@ember/controller"; import { ajax } from "discourse/lib/ajax"; import ModalFunctionality from "discourse/mixins/modal-functionality"; import { setting } from "discourse/lib/computed"; -import { - default as discourseComputed, +import discourseComputed, { + observes, on } from "discourse-common/utils/decorators"; import { emailValid } from "discourse/lib/utilities"; @@ -171,6 +171,7 @@ export default Controller.extend( : providerName; }, + @observes("emailValidation", "accountEmail") prefillUsername: function() { if (this.prefilledUsername) { // If username field has been filled automatically, and email field just changed, @@ -189,7 +190,7 @@ export default Controller.extend( // then look for a registered username that matches the email. this.fetchExistingUsername(); } - }.observes("emailValidation", "accountEmail"), + }, // Determines whether at least one login button is enabled @discourseComputed diff --git a/app/assets/javascripts/discourse/controllers/discovery.js.es6 b/app/assets/javascripts/discourse/controllers/discovery.js.es6 index 93237fa22b..fa374b4372 100644 --- a/app/assets/javascripts/discourse/controllers/discovery.js.es6 +++ b/app/assets/javascripts/discourse/controllers/discovery.js.es6 @@ -3,6 +3,7 @@ import { inject } from "@ember/controller"; import Controller from "@ember/controller"; import DiscourseURL from "discourse/lib/url"; import Category from "discourse/models/category"; +import { observes } from "discourse-common/utils/decorators"; export default Controller.extend({ discoveryTopics: inject("discovery/topics"), @@ -16,9 +17,10 @@ export default Controller.extend({ loadedAllItems: not("discoveryTopics.model.canLoadMore"), + @observes("loadedAllItems") _showFooter: function() { this.set("application.showFooter", this.loadedAllItems); - }.observes("loadedAllItems"), + }, showMoreUrl(period) { let url = "", diff --git a/app/assets/javascripts/discourse/controllers/discovery/topics.js.es6 b/app/assets/javascripts/discourse/controllers/discovery/topics.js.es6 index 14a6c597f8..047960ba8a 100644 --- a/app/assets/javascripts/discourse/controllers/discovery/topics.js.es6 +++ b/app/assets/javascripts/discourse/controllers/discovery/topics.js.es6 @@ -83,7 +83,6 @@ const controllerOpts = { }, resetNew() { - this.topicTrackingState.resetNew(); Topic.resetNew(this.category, !this.noSubcategories).then(() => this.send("refresh") ); diff --git a/app/assets/javascripts/discourse/controllers/edit-category.js.es6 b/app/assets/javascripts/discourse/controllers/edit-category.js.es6 index 36c961c7e1..e014cd1ea2 100644 --- a/app/assets/javascripts/discourse/controllers/edit-category.js.es6 +++ b/app/assets/javascripts/discourse/controllers/edit-category.js.es6 @@ -3,8 +3,7 @@ import Controller from "@ember/controller"; import ModalFunctionality from "discourse/mixins/modal-functionality"; import DiscourseURL from "discourse/lib/url"; import { extractError } from "discourse/lib/ajax-error"; -import { - default as discourseComputed, +import discourseComputed, { on, observes } from "discourse-common/utils/decorators"; diff --git a/app/assets/javascripts/discourse/controllers/edit-topic-timer.js.es6 b/app/assets/javascripts/discourse/controllers/edit-topic-timer.js.es6 index c1473509f2..2c04b1ca1c 100644 --- a/app/assets/javascripts/discourse/controllers/edit-topic-timer.js.es6 +++ b/app/assets/javascripts/discourse/controllers/edit-topic-timer.js.es6 @@ -1,6 +1,6 @@ import EmberObject from "@ember/object"; import Controller from "@ember/controller"; -import { default as discourseComputed } from "discourse-common/utils/decorators"; +import discourseComputed from "discourse-common/utils/decorators"; import ModalFunctionality from "discourse/mixins/modal-functionality"; import TopicTimer from "discourse/models/topic-timer"; import { popupAjaxError } from "discourse/lib/ajax-error"; diff --git a/app/assets/javascripts/discourse/controllers/email-login.js.es6 b/app/assets/javascripts/discourse/controllers/email-login.js.es6 index 01612c766f..4eab7fc1ba 100644 --- a/app/assets/javascripts/discourse/controllers/email-login.js.es6 +++ b/app/assets/javascripts/discourse/controllers/email-login.js.es6 @@ -23,15 +23,13 @@ export default Controller.extend({ actions: { finishLogin() { - let data = {}; + let data = { second_factor_method: this.secondFactorMethod }; if (this.securityKeyCredential) { - data = { security_key_credential: this.securityKeyCredential }; + data.second_factor_token = this.securityKeyCredential; } else { - data = { - second_factor_token: this.secondFactorToken, - second_factor_method: this.secondFactorMethod - }; + data.second_factor_token = this.secondFactorToken; } + ajax({ url: `/session/email-login/${this.model.token}`, type: "POST", diff --git a/app/assets/javascripts/discourse/controllers/exception.js.es6 b/app/assets/javascripts/discourse/controllers/exception.js.es6 index f53eff91fd..e9a5f10d16 100644 --- a/app/assets/javascripts/discourse/controllers/exception.js.es6 +++ b/app/assets/javascripts/discourse/controllers/exception.js.es6 @@ -1,10 +1,7 @@ import { equal, gte, none, alias } from "@ember/object/computed"; import { schedule } from "@ember/runloop"; import Controller from "@ember/controller"; -import { - on, - default as discourseComputed -} from "discourse-common/utils/decorators"; +import discourseComputed, { on } from "discourse-common/utils/decorators"; const ButtonBackBright = { classes: "btn-primary", diff --git a/app/assets/javascripts/discourse/controllers/feature-topic-on-profile.js.es6 b/app/assets/javascripts/discourse/controllers/feature-topic-on-profile.js.es6 index 4a0db76713..39c89b5f79 100644 --- a/app/assets/javascripts/discourse/controllers/feature-topic-on-profile.js.es6 +++ b/app/assets/javascripts/discourse/controllers/feature-topic-on-profile.js.es6 @@ -13,6 +13,10 @@ export default Controller.extend(ModalFunctionality, { this.set("newFeaturedTopic", null); }, + onShow() { + this.set("modal.modalClass", "choose-topic-modal"); + }, + actions: { save() { return ajax(`/u/${this.model.username}/feature-topic`, { diff --git a/app/assets/javascripts/discourse/controllers/full-page-search.js.es6 b/app/assets/javascripts/discourse/controllers/full-page-search.js.es6 index d62a2a8177..a143203720 100644 --- a/app/assets/javascripts/discourse/controllers/full-page-search.js.es6 +++ b/app/assets/javascripts/discourse/controllers/full-page-search.js.es6 @@ -9,10 +9,7 @@ import { getSearchKey, isValidSearchTerm } from "discourse/lib/search"; -import { - default as discourseComputed, - observes -} from "discourse-common/utils/decorators"; +import discourseComputed, { observes } from "discourse-common/utils/decorators"; import Category from "discourse/models/category"; import { escapeExpression } from "discourse/lib/utilities"; import { setTransient } from "discourse/lib/page-tracker"; diff --git a/app/assets/javascripts/discourse/controllers/group-index.js.es6 b/app/assets/javascripts/discourse/controllers/group-index.js.es6 index 887d5dbfcf..dcf5e07f3d 100644 --- a/app/assets/javascripts/discourse/controllers/group-index.js.es6 +++ b/app/assets/javascripts/discourse/controllers/group-index.js.es6 @@ -1,9 +1,6 @@ import Controller, { inject } from "@ember/controller"; import { alias } from "@ember/object/computed"; -import { - default as discourseComputed, - observes -} from "discourse-common/utils/decorators"; +import discourseComputed, { observes } from "discourse-common/utils/decorators"; import { popupAjaxError } from "discourse/lib/ajax-error"; import discourseDebounce from "discourse/lib/debounce"; diff --git a/app/assets/javascripts/discourse/controllers/group-manage-logs.js.es6 b/app/assets/javascripts/discourse/controllers/group-manage-logs.js.es6 index 3aaf3fdb87..41a12c2c98 100644 --- a/app/assets/javascripts/discourse/controllers/group-manage-logs.js.es6 +++ b/app/assets/javascripts/discourse/controllers/group-manage-logs.js.es6 @@ -1,10 +1,7 @@ import { inject } from "@ember/controller"; import EmberObject from "@ember/object"; import Controller from "@ember/controller"; -import { - default as discourseComputed, - observes -} from "discourse-common/utils/decorators"; +import discourseComputed, { observes } from "discourse-common/utils/decorators"; export default Controller.extend({ group: inject(), diff --git a/app/assets/javascripts/discourse/controllers/group-manage.js.es6 b/app/assets/javascripts/discourse/controllers/group-manage.js.es6 index b99f8a70f9..7a9e7859eb 100644 --- a/app/assets/javascripts/discourse/controllers/group-manage.js.es6 +++ b/app/assets/javascripts/discourse/controllers/group-manage.js.es6 @@ -1,6 +1,6 @@ import { inject as service } from "@ember/service"; import Controller from "@ember/controller"; -import { default as discourseComputed } from "discourse-common/utils/decorators"; +import discourseComputed from "discourse-common/utils/decorators"; export default Controller.extend({ router: service(), diff --git a/app/assets/javascripts/discourse/controllers/group-requests.js.es6 b/app/assets/javascripts/discourse/controllers/group-requests.js.es6 index 013c44f6d8..c1664f0c32 100644 --- a/app/assets/javascripts/discourse/controllers/group-requests.js.es6 +++ b/app/assets/javascripts/discourse/controllers/group-requests.js.es6 @@ -1,8 +1,5 @@ import Controller, { inject } from "@ember/controller"; -import { - default as discourseComputed, - observes -} from "discourse-common/utils/decorators"; +import discourseComputed, { observes } from "discourse-common/utils/decorators"; import { ajax } from "discourse/lib/ajax"; import { popupAjaxError } from "discourse/lib/ajax-error"; import discourseDebounce from "discourse/lib/debounce"; diff --git a/app/assets/javascripts/discourse/controllers/group.js.es6 b/app/assets/javascripts/discourse/controllers/group.js.es6 index b6e9a7ab8f..71354f58d3 100644 --- a/app/assets/javascripts/discourse/controllers/group.js.es6 +++ b/app/assets/javascripts/discourse/controllers/group.js.es6 @@ -1,7 +1,9 @@ import EmberObject from "@ember/object"; import { inject } from "@ember/controller"; import Controller from "@ember/controller"; -import { default as discourseComputed } from "discourse-common/utils/decorators"; +import discourseComputed from "discourse-common/utils/decorators"; +import { inject as service } from "@ember/service"; +import { readOnly } from "@ember/object/computed"; const Tab = EmberObject.extend({ init() { @@ -17,6 +19,8 @@ export default Controller.extend({ counts: null, showing: "members", destroying: null, + router: service(), + currentPath: readOnly("router._router.currentPath"), @discourseComputed( "showMessages", diff --git a/app/assets/javascripts/discourse/controllers/groups-index.js.es6 b/app/assets/javascripts/discourse/controllers/groups-index.js.es6 index 0380bdffd8..00276d0215 100644 --- a/app/assets/javascripts/discourse/controllers/groups-index.js.es6 +++ b/app/assets/javascripts/discourse/controllers/groups-index.js.es6 @@ -1,10 +1,7 @@ import { inject } from "@ember/controller"; import Controller from "@ember/controller"; import discourseDebounce from "discourse/lib/debounce"; -import { - default as discourseComputed, - observes -} from "discourse-common/utils/decorators"; +import discourseComputed, { observes } from "discourse-common/utils/decorators"; export default Controller.extend({ application: inject(), diff --git a/app/assets/javascripts/discourse/controllers/invites-show.js.es6 b/app/assets/javascripts/discourse/controllers/invites-show.js.es6 index 9be1de123e..58b7ca3685 100644 --- a/app/assets/javascripts/discourse/controllers/invites-show.js.es6 +++ b/app/assets/javascripts/discourse/controllers/invites-show.js.es6 @@ -1,7 +1,7 @@ import { isEmpty } from "@ember/utils"; import { alias, notEmpty } from "@ember/object/computed"; import Controller from "@ember/controller"; -import { default as discourseComputed } from "discourse-common/utils/decorators"; +import discourseComputed from "discourse-common/utils/decorators"; import getUrl from "discourse-common/lib/get-url"; import DiscourseURL from "discourse/lib/url"; import { ajax } from "discourse/lib/ajax"; diff --git a/app/assets/javascripts/discourse/controllers/keyboard-shortcuts-help.js.es6 b/app/assets/javascripts/discourse/controllers/keyboard-shortcuts-help.js.es6 index 9e1c216e13..7b3fbdcb5d 100644 --- a/app/assets/javascripts/discourse/controllers/keyboard-shortcuts-help.js.es6 +++ b/app/assets/javascripts/discourse/controllers/keyboard-shortcuts-help.js.es6 @@ -167,6 +167,10 @@ export default Controller.extend(ModalFunctionality, { defer: buildShortcut("actions.defer", { keys1: [SHIFT, "u"], keysDelimiter: PLUS + }), + topic_admin_actions: buildShortcut("actions.topic_admin_actions", { + keys1: [SHIFT, "a"], + keysDelimiter: PLUS }) } } diff --git a/app/assets/javascripts/discourse/controllers/login.js.es6 b/app/assets/javascripts/discourse/controllers/login.js.es6 index 5eabad52fb..fe57a856ec 100644 --- a/app/assets/javascripts/discourse/controllers/login.js.es6 +++ b/app/assets/javascripts/discourse/controllers/login.js.es6 @@ -120,9 +120,9 @@ export default Controller.extend(ModalFunctionality, { data: { login: this.loginName, password: this.loginPassword, - second_factor_token: this.secondFactorToken, + second_factor_token: + this.securityKeyCredential || this.secondFactorToken, second_factor_method: this.secondFactorMethod, - security_key_credential: this.securityKeyCredential, timezone: moment.tz.guess() } }).then( @@ -130,12 +130,9 @@ export default Controller.extend(ModalFunctionality, { // Successful login if (result && result.error) { this.set("loggingIn", false); - const invalidSecurityKey = result.reason === "invalid_security_key"; - const invalidSecondFactor = - result.reason === "invalid_second_factor"; if ( - (invalidSecondFactor || invalidSecurityKey) && + (result.security_key_enabled || result.totp_enabled) && !this.secondFactorRequired ) { document.getElementById("modal-alert").style.display = "none"; @@ -145,9 +142,9 @@ export default Controller.extend(ModalFunctionality, { secondFactorRequired: true, showLoginButtons: false, backupEnabled: result.backup_enabled, - showSecondFactor: invalidSecondFactor, - showSecurityKey: invalidSecurityKey, - secondFactorMethod: invalidSecurityKey + showSecondFactor: result.totp_enabled, + showSecurityKey: result.security_key_enabled, + secondFactorMethod: result.security_key_enabled ? SECOND_FACTOR_METHODS.SECURITY_KEY : SECOND_FACTOR_METHODS.TOTP, securityKeyChallenge: result.challenge, diff --git a/app/assets/javascripts/discourse/controllers/move-to-topic.js.es6 b/app/assets/javascripts/discourse/controllers/move-to-topic.js.es6 index f80b659baa..b2b7dd7f10 100644 --- a/app/assets/javascripts/discourse/controllers/move-to-topic.js.es6 +++ b/app/assets/javascripts/discourse/controllers/move-to-topic.js.es6 @@ -6,7 +6,7 @@ import Controller from "@ember/controller"; import ModalFunctionality from "discourse/mixins/modal-functionality"; import { movePosts, mergeTopic } from "discourse/models/topic"; import DiscourseURL from "discourse/lib/url"; -import { default as discourseComputed } from "discourse-common/utils/decorators"; +import discourseComputed from "discourse-common/utils/decorators"; import { extractError } from "discourse/lib/ajax-error"; export default Controller.extend(ModalFunctionality, { @@ -74,7 +74,7 @@ export default Controller.extend(ModalFunctionality, { onShow() { this.setProperties({ - "modal.modalClass": "move-to-modal", + "modal.modalClass": "choose-topic-modal", saving: false, selection: "new_topic", categoryId: null, diff --git a/app/assets/javascripts/discourse/controllers/password-reset.js.es6 b/app/assets/javascripts/discourse/controllers/password-reset.js.es6 index 7f3718482e..dcb1931ac0 100644 --- a/app/assets/javascripts/discourse/controllers/password-reset.js.es6 +++ b/app/assets/javascripts/discourse/controllers/password-reset.js.es6 @@ -1,6 +1,6 @@ -import { alias, or } from "@ember/object/computed"; +import { alias, or, readOnly } from "@ember/object/computed"; import Controller from "@ember/controller"; -import { default as discourseComputed } from "discourse-common/utils/decorators"; +import discourseComputed from "discourse-common/utils/decorators"; import DiscourseURL from "discourse/lib/url"; import { ajax } from "discourse/lib/ajax"; import PasswordValidation from "discourse/mixins/password-validation"; @@ -18,6 +18,7 @@ export default Controller.extend(PasswordValidation, { "model.second_factor_required", "model.security_key_required" ), + otherMethodAllowed: readOnly("model.multiple_second_factor_methods"), @discourseComputed("model.security_key_required") secondFactorMethod(security_key_required) { return security_key_required @@ -51,9 +52,9 @@ export default Controller.extend(PasswordValidation, { type: "PUT", data: { password: this.accountPassword, - second_factor_token: this.secondFactorToken, - second_factor_method: this.secondFactorMethod, - security_key_credential: this.securityKeyCredential + second_factor_token: + this.securityKeyCredential || this.secondFactorToken, + second_factor_method: this.secondFactorMethod } }) .then(result => { diff --git a/app/assets/javascripts/discourse/controllers/preferences/account.js.es6 b/app/assets/javascripts/discourse/controllers/preferences/account.js.es6 index f09f94ae89..6cac06c1aa 100644 --- a/app/assets/javascripts/discourse/controllers/preferences/account.js.es6 +++ b/app/assets/javascripts/discourse/controllers/preferences/account.js.es6 @@ -2,7 +2,7 @@ import { not, or, gt } from "@ember/object/computed"; import Controller from "@ember/controller"; import { iconHTML } from "discourse-common/lib/icon-library"; import CanCheckEmails from "discourse/mixins/can-check-emails"; -import { default as discourseComputed } from "discourse-common/utils/decorators"; +import discourseComputed from "discourse-common/utils/decorators"; import PreferencesTabController from "discourse/mixins/preferences-tab-controller"; import { propertyNotEqual, setting } from "discourse/lib/computed"; import { popupAjaxError } from "discourse/lib/ajax-error"; diff --git a/app/assets/javascripts/discourse/controllers/preferences/emails.js.es6 b/app/assets/javascripts/discourse/controllers/preferences/emails.js.es6 index 008ed9c8cd..2f7d0832c9 100644 --- a/app/assets/javascripts/discourse/controllers/preferences/emails.js.es6 +++ b/app/assets/javascripts/discourse/controllers/preferences/emails.js.es6 @@ -1,7 +1,7 @@ import { equal } from "@ember/object/computed"; import Controller from "@ember/controller"; import PreferencesTabController from "discourse/mixins/preferences-tab-controller"; -import { default as discourseComputed } from "discourse-common/utils/decorators"; +import discourseComputed from "discourse-common/utils/decorators"; import { popupAjaxError } from "discourse/lib/ajax-error"; const EMAIL_LEVELS = { diff --git a/app/assets/javascripts/discourse/controllers/preferences/interface.js.es6 b/app/assets/javascripts/discourse/controllers/preferences/interface.js.es6 index 370d5b9a4d..086e5cc0b6 100644 --- a/app/assets/javascripts/discourse/controllers/preferences/interface.js.es6 +++ b/app/assets/javascripts/discourse/controllers/preferences/interface.js.es6 @@ -2,10 +2,7 @@ import { inject } from "@ember/controller"; import Controller from "@ember/controller"; import PreferencesTabController from "discourse/mixins/preferences-tab-controller"; import { setDefaultHomepage } from "discourse/lib/utilities"; -import { - default as discourseComputed, - observes -} from "discourse-common/utils/decorators"; +import discourseComputed, { observes } from "discourse-common/utils/decorators"; import { listThemes, previewTheme, diff --git a/app/assets/javascripts/discourse/controllers/preferences/profile.js.es6 b/app/assets/javascripts/discourse/controllers/preferences/profile.js.es6 index 017035323c..5d2e65f045 100644 --- a/app/assets/javascripts/discourse/controllers/preferences/profile.js.es6 +++ b/app/assets/javascripts/discourse/controllers/preferences/profile.js.es6 @@ -1,7 +1,7 @@ import { isEmpty } from "@ember/utils"; import EmberObject from "@ember/object"; import Controller from "@ember/controller"; -import { default as discourseComputed } from "discourse-common/utils/decorators"; +import discourseComputed from "discourse-common/utils/decorators"; import PreferencesTabController from "discourse/mixins/preferences-tab-controller"; import { popupAjaxError } from "discourse/lib/ajax-error"; import { cookAsync } from "discourse/lib/text"; diff --git a/app/assets/javascripts/discourse/controllers/preferences/second-factor.js.es6 b/app/assets/javascripts/discourse/controllers/preferences/second-factor.js.es6 index 21e9a55f38..67c7b58c83 100644 --- a/app/assets/javascripts/discourse/controllers/preferences/second-factor.js.es6 +++ b/app/assets/javascripts/discourse/controllers/preferences/second-factor.js.es6 @@ -1,8 +1,8 @@ import { alias, and } from "@ember/object/computed"; import Controller from "@ember/controller"; -import { default as discourseComputed } from "discourse-common/utils/decorators"; +import discourseComputed from "discourse-common/utils/decorators"; import CanCheckEmails from "discourse/mixins/can-check-emails"; -import { default as DiscourseURL, userPath } from "discourse/lib/url"; +import DiscourseURL, { userPath } from "discourse/lib/url"; import { popupAjaxError } from "discourse/lib/ajax-error"; import { findAll } from "discourse/models/login-method"; import { SECOND_FACTOR_METHODS } from "discourse/models/user"; diff --git a/app/assets/javascripts/discourse/controllers/preferences/username.js.es6 b/app/assets/javascripts/discourse/controllers/preferences/username.js.es6 index 530d1a616e..3b5cbfc477 100644 --- a/app/assets/javascripts/discourse/controllers/preferences/username.js.es6 +++ b/app/assets/javascripts/discourse/controllers/preferences/username.js.es6 @@ -1,10 +1,7 @@ import { isEmpty } from "@ember/utils"; import { empty, or } from "@ember/object/computed"; import Controller from "@ember/controller"; -import { - default as discourseComputed, - observes -} from "discourse-common/utils/decorators"; +import discourseComputed, { observes } from "discourse-common/utils/decorators"; import { setting, propertyEqual } from "discourse/lib/computed"; import DiscourseURL from "discourse/lib/url"; import { userPath } from "discourse/lib/url"; diff --git a/app/assets/javascripts/discourse/controllers/reorder-categories.js.es6 b/app/assets/javascripts/discourse/controllers/reorder-categories.js.es6 index a858d605e9..db32c7c367 100644 --- a/app/assets/javascripts/discourse/controllers/reorder-categories.js.es6 +++ b/app/assets/javascripts/discourse/controllers/reorder-categories.js.es6 @@ -5,10 +5,7 @@ import { ajax } from "discourse/lib/ajax"; import ModalFunctionality from "discourse/mixins/modal-functionality"; const BufferedProxy = window.BufferedProxy; // import BufferedProxy from 'ember-buffered-proxy/proxy'; import { popupAjaxError } from "discourse/lib/ajax-error"; -import { - on, - default as discourseComputed -} from "discourse-common/utils/decorators"; +import discourseComputed, { on } from "discourse-common/utils/decorators"; import Ember from "ember"; export default Controller.extend(ModalFunctionality, Ember.Evented, { diff --git a/app/assets/javascripts/discourse/controllers/second-factor-backup-edit.js.es6 b/app/assets/javascripts/discourse/controllers/second-factor-backup-edit.js.es6 index 35b24b07df..aa3e0c1143 100644 --- a/app/assets/javascripts/discourse/controllers/second-factor-backup-edit.js.es6 +++ b/app/assets/javascripts/discourse/controllers/second-factor-backup-edit.js.es6 @@ -1,7 +1,7 @@ import { alias } from "@ember/object/computed"; import { later } from "@ember/runloop"; import Controller from "@ember/controller"; -import { default as discourseComputed } from "discourse-common/utils/decorators"; +import discourseComputed from "discourse-common/utils/decorators"; import { SECOND_FACTOR_METHODS } from "discourse/models/user"; import ModalFunctionality from "discourse/mixins/modal-functionality"; diff --git a/app/assets/javascripts/discourse/controllers/tags-show.js.es6 b/app/assets/javascripts/discourse/controllers/tags-show.js.es6 index a34a368a1a..bc26a7febc 100644 --- a/app/assets/javascripts/discourse/controllers/tags-show.js.es6 +++ b/app/assets/javascripts/discourse/controllers/tags-show.js.es6 @@ -1,12 +1,9 @@ import { alias } from "@ember/object/computed"; import { inject } from "@ember/controller"; import Controller from "@ember/controller"; -import { - default as discourseComputed, - observes -} from "discourse-common/utils/decorators"; +import discourseComputed, { observes } from "discourse-common/utils/decorators"; import BulkTopicSelection from "discourse/mixins/bulk-topic-selection"; -import { default as NavItem } from "discourse/models/nav-item"; +import NavItem from "discourse/models/nav-item"; import FilterModeMixin from "discourse/mixins/filter-mode"; export default Controller.extend(BulkTopicSelection, FilterModeMixin, { diff --git a/app/assets/javascripts/discourse/controllers/topic.js.es6 b/app/assets/javascripts/discourse/controllers/topic.js.es6 index 7b523ee768..cc0725a697 100644 --- a/app/assets/javascripts/discourse/controllers/topic.js.es6 +++ b/app/assets/javascripts/discourse/controllers/topic.js.es6 @@ -15,10 +15,7 @@ import Topic from "discourse/models/topic"; import discourseDebounce from "discourse/lib/debounce"; import isElementInViewport from "discourse/lib/is-element-in-viewport"; import { ajax } from "discourse/lib/ajax"; -import { - default as discourseComputed, - observes -} from "discourse-common/utils/decorators"; +import discourseComputed, { observes } from "discourse-common/utils/decorators"; import { extractLinkMeta } from "discourse/lib/render-topic-featured-link"; import { popupAjaxError } from "discourse/lib/ajax-error"; import { spinnerHTML } from "discourse/helpers/loading-spinner"; @@ -727,6 +724,10 @@ export default Controller.extend(bufferedProperty("model"), { }, jumpEnd() { + this.appEvents.trigger( + "topic:jump-to-post", + this.get("model.highest_post_number") + ); DiscourseURL.routeTo(this.get("model.lastPostUrl"), { jumpEnd: true }); diff --git a/app/assets/javascripts/discourse/controllers/upload-selector.js.es6 b/app/assets/javascripts/discourse/controllers/upload-selector.js.es6 index a8e61d86f5..eae9a3a2bb 100644 --- a/app/assets/javascripts/discourse/controllers/upload-selector.js.es6 +++ b/app/assets/javascripts/discourse/controllers/upload-selector.js.es6 @@ -1,7 +1,7 @@ import { equal } from "@ember/object/computed"; import Controller from "@ember/controller"; import ModalFunctionality from "discourse/mixins/modal-functionality"; -import { default as discourseComputed } from "discourse-common/utils/decorators"; +import discourseComputed from "discourse-common/utils/decorators"; import { allowsAttachments, authorizedExtensions, diff --git a/app/assets/javascripts/discourse/controllers/user-activity.js.es6 b/app/assets/javascripts/discourse/controllers/user-activity.js.es6 index 596125d221..473f24c753 100644 --- a/app/assets/javascripts/discourse/controllers/user-activity.js.es6 +++ b/app/assets/javascripts/discourse/controllers/user-activity.js.es6 @@ -3,6 +3,7 @@ import { inject as service } from "@ember/service"; import { inject } from "@ember/controller"; import Controller from "@ember/controller"; import { exportUserArchive } from "discourse/lib/export-csv"; +import { observes } from "discourse-common/utils/decorators"; export default Controller.extend({ application: inject(), @@ -12,6 +13,7 @@ export default Controller.extend({ canDownloadPosts: alias("user.viewingSelf"), + @observes("userActionType", "model.stream.itemsLoaded") _showFooter: function() { var showFooter; if (this.userActionType) { @@ -25,7 +27,7 @@ export default Controller.extend({ this.get("model.stream.itemsLoaded"); } this.set("application.showFooter", showFooter); - }.observes("userActionType", "model.stream.itemsLoaded"), + }, actions: { exportUserArchive() { diff --git a/app/assets/javascripts/discourse/controllers/user-card.js.es6 b/app/assets/javascripts/discourse/controllers/user-card.js.es6 index d0fa3abf1c..862fc92e73 100644 --- a/app/assets/javascripts/discourse/controllers/user-card.js.es6 +++ b/app/assets/javascripts/discourse/controllers/user-card.js.es6 @@ -1,11 +1,7 @@ import { inject as service } from "@ember/service"; import { inject } from "@ember/controller"; import Controller from "@ember/controller"; -import { - default as DiscourseURL, - userPath, - groupPath -} from "discourse/lib/url"; +import DiscourseURL, { userPath, groupPath } from "discourse/lib/url"; export default Controller.extend({ topic: inject(), diff --git a/app/assets/javascripts/discourse/controllers/user-invited-show.js.es6 b/app/assets/javascripts/discourse/controllers/user-invited-show.js.es6 index 1fd6ec0bdf..9282265e78 100644 --- a/app/assets/javascripts/discourse/controllers/user-invited-show.js.es6 +++ b/app/assets/javascripts/discourse/controllers/user-invited-show.js.es6 @@ -3,10 +3,7 @@ import Controller from "@ember/controller"; import Invite from "discourse/models/invite"; import discourseDebounce from "discourse/lib/debounce"; import { popupAjaxError } from "discourse/lib/ajax-error"; -import { - default as discourseComputed, - observes -} from "discourse-common/utils/decorators"; +import discourseComputed, { observes } from "discourse-common/utils/decorators"; export default Controller.extend({ user: null, diff --git a/app/assets/javascripts/discourse/controllers/user-notifications.js.es6 b/app/assets/javascripts/discourse/controllers/user-notifications.js.es6 index 24e61aa73a..23efcfc214 100644 --- a/app/assets/javascripts/discourse/controllers/user-notifications.js.es6 +++ b/app/assets/javascripts/discourse/controllers/user-notifications.js.es6 @@ -1,13 +1,14 @@ import { inject } from "@ember/controller"; import Controller from "@ember/controller"; import { ajax } from "discourse/lib/ajax"; -import { - default as discourseComputed, - observes -} from "discourse-common/utils/decorators"; +import discourseComputed, { observes } from "discourse-common/utils/decorators"; +import { readOnly } from "@ember/object/computed"; +import { inject as service } from "@ember/service"; export default Controller.extend({ application: inject(), + router: service(), + currentPath: readOnly("router._router.currentPath"), @observes("model.canLoadMore") _showFooter() { diff --git a/app/assets/javascripts/discourse/controllers/user-posts.js.es6 b/app/assets/javascripts/discourse/controllers/user-posts.js.es6 index fe001ad1c1..e5de7e9995 100644 --- a/app/assets/javascripts/discourse/controllers/user-posts.js.es6 +++ b/app/assets/javascripts/discourse/controllers/user-posts.js.es6 @@ -1,9 +1,12 @@ import { inject } from "@ember/controller"; import Controller from "@ember/controller"; +import { observes } from "discourse-common/utils/decorators"; + export default Controller.extend({ application: inject(), + @observes("model.canLoadMore") _showFooter: function() { this.set("application.showFooter", !this.get("model.canLoadMore")); - }.observes("model.canLoadMore") + } }); diff --git a/app/assets/javascripts/discourse/controllers/user-topics-list.js.es6 b/app/assets/javascripts/discourse/controllers/user-topics-list.js.es6 index 6c09d2e36b..f541ac01ca 100644 --- a/app/assets/javascripts/discourse/controllers/user-topics-list.js.es6 +++ b/app/assets/javascripts/discourse/controllers/user-topics-list.js.es6 @@ -1,4 +1,4 @@ -import discourseComputed from "discourse-common/utils/decorators"; +import discourseComputed, { observes } from "discourse-common/utils/decorators"; import { inject } from "@ember/controller"; import Controller from "@ember/controller"; @@ -22,9 +22,10 @@ export default Controller.extend({ this.session.set("topicListScrollPosition", $(window).scrollTop()); }, + @observes("model.canLoadMore") _showFooter: function() { this.set("application.showFooter", !this.get("model.canLoadMore")); - }.observes("model.canLoadMore"), + }, @discourseComputed("incomingCount") hasIncoming(incomingCount) { diff --git a/app/assets/javascripts/discourse/controllers/users.js.es6 b/app/assets/javascripts/discourse/controllers/users.js.es6 index 8fd3b3e157..820e50b0af 100644 --- a/app/assets/javascripts/discourse/controllers/users.js.es6 +++ b/app/assets/javascripts/discourse/controllers/users.js.es6 @@ -2,6 +2,7 @@ import { equal } from "@ember/object/computed"; import { inject } from "@ember/controller"; import Controller from "@ember/controller"; import discourseDebounce from "discourse/lib/debounce"; +import { observes } from "discourse-common/utils/decorators"; export default Controller.extend({ application: inject(), @@ -15,13 +16,15 @@ export default Controller.extend({ showTimeRead: equal("period", "all"), + @observes("nameInput") _setName: discourseDebounce(function() { this.set("name", this.nameInput); - }, 500).observes("nameInput"), + }, 500), + @observes("model.canLoadMore") _showFooter: function() { this.set("application.showFooter", !this.get("model.canLoadMore")); - }.observes("model.canLoadMore"), + }, actions: { loadMore() { diff --git a/app/assets/javascripts/discourse/helpers/category-link.js.es6 b/app/assets/javascripts/discourse/helpers/category-link.js.es6 index 3cdb03c8f0..b32c0c9452 100644 --- a/app/assets/javascripts/discourse/helpers/category-link.js.es6 +++ b/app/assets/javascripts/discourse/helpers/category-link.js.es6 @@ -25,7 +25,9 @@ function categoryStripe(color, classes) { @param {String} [opts.url] The url that we want the category badge to link to. @param {Boolean} [opts.allowUncategorized] If false, returns an empty string for the uncategorized category. @param {Boolean} [opts.link] If false, the category badge will not be a link. - @param {Boolean} [opts.hideParaent] If true, parent category will be hidden in the badge. + @param {Boolean} [opts.hideParent] If true, parent category will be hidden in the badge. + @param {Boolean} [opts.recursive] If true, the function will be called recursively for all parent categories + @param {Number} [opts.depth] Current category depth, used for limiting recursive calls **/ export function categoryBadgeHTML(category, opts) { opts = opts || {}; @@ -38,6 +40,13 @@ export function categoryBadgeHTML(category, opts) { ) return ""; + const depth = (opts.depth || 1) + 1; + if (opts.recursive && depth <= Discourse.SiteSettings.max_category_nesting) { + const parentCategory = Category.findById(category.parent_category_id); + opts.depth = depth; + return categoryBadgeHTML(parentCategory, opts) + _renderer(category, opts); + } + return _renderer(category, opts); } diff --git a/app/assets/javascripts/discourse/helpers/raw-plugin-outlet.js.es6 b/app/assets/javascripts/discourse/helpers/raw-plugin-outlet.js.es6 index 62de97d18c..4aa8484a55 100644 --- a/app/assets/javascripts/discourse/helpers/raw-plugin-outlet.js.es6 +++ b/app/assets/javascripts/discourse/helpers/raw-plugin-outlet.js.es6 @@ -1,6 +1,7 @@ import { rawConnectorsFor } from "discourse/lib/plugin-connectors"; +import RawHandlebars from "discourse-common/lib/raw-handlebars"; -Handlebars.registerHelper("raw-plugin-outlet", function(args) { +RawHandlebars.registerHelper("raw-plugin-outlet", function(args) { const connectors = rawConnectorsFor(args.hash.name); if (connectors.length) { const output = connectors.map(c => c.template({ context: this })); diff --git a/app/assets/javascripts/discourse/helpers/reviewable-status.js.es6 b/app/assets/javascripts/discourse/helpers/reviewable-status.js.es6 index b024500a0c..4dcc12d3a4 100644 --- a/app/assets/javascripts/discourse/helpers/reviewable-status.js.es6 +++ b/app/assets/javascripts/discourse/helpers/reviewable-status.js.es6 @@ -19,7 +19,7 @@ function dataFor(status) { case IGNORED: return { icon: "external-link-alt", name: "ignored" }; case DELETED: - return { icon: "trash", name: "deleted" }; + return { icon: "trash-alt", name: "deleted" }; } } diff --git a/app/assets/javascripts/discourse/initializers/badging.js.es6 b/app/assets/javascripts/discourse/initializers/badging.js.es6 index 004764056e..f406604dfa 100644 --- a/app/assets/javascripts/discourse/initializers/badging.js.es6 +++ b/app/assets/javascripts/discourse/initializers/badging.js.es6 @@ -4,7 +4,7 @@ export default { after: "message-bus", initialize(container) { - if (!window.ExperimentalBadge) return; // must have the Badging API + if (!navigator.setAppBadge) return; // must have the Badging API const user = container.lookup("current-user:main"); if (!user) return; // must be logged in @@ -18,6 +18,6 @@ export default { }, _updateBadge() { - window.ExperimentalBadge.set(this.notifications); + navigator.setAppBadge(this.notifications); } }; diff --git a/app/assets/javascripts/discourse/lib/click-track.js.es6 b/app/assets/javascripts/discourse/lib/click-track.js.es6 index c3186937b0..534c04a6c4 100644 --- a/app/assets/javascripts/discourse/lib/click-track.js.es6 +++ b/app/assets/javascripts/discourse/lib/click-track.js.es6 @@ -8,21 +8,13 @@ import ENV from "discourse-common/config/environment"; import User from "discourse/models/user"; export function isValidLink($link) { - // Do not track: - // - lightboxes - // - links with disabled tracking - // - category links - // - quote back button + // .hashtag == category/tag link + // .back == quote back ^ button if ($link.is(".lightbox, .no-track-link, .hashtag, .back")) { return false; } - // Do not track links in quotes or in elided part - if ($link.parents("aside.quote, .elided").length !== 0) { - return false; - } - - if ($link.parents(".expanded-embed").length !== 0) { + if ($link.parents("aside.quote, .elided, .expanded-embed").length !== 0) { return false; } diff --git a/app/assets/javascripts/discourse/lib/export-csv.js.es6 b/app/assets/javascripts/discourse/lib/export-csv.js.es6 index 9f604f6e45..39b19771f3 100644 --- a/app/assets/javascripts/discourse/lib/export-csv.js.es6 +++ b/app/assets/javascripts/discourse/lib/export-csv.js.es6 @@ -1,4 +1,6 @@ import { ajax } from "discourse/lib/ajax"; +import { popupAjaxError } from "discourse/lib/ajax-error"; + function exportEntityByType(type, entity, args) { return ajax("/export_csv/export_entity.json", { method: "POST", @@ -11,9 +13,7 @@ export function exportUserArchive() { .then(function() { bootbox.alert(I18n.t("user.download_archive.success")); }) - .catch(function() { - bootbox.alert(I18n.t("user.download_archive.rate_limit_error")); - }); + .catch(popupAjaxError); } export function exportEntity(entity, args) { diff --git a/app/assets/javascripts/discourse/lib/keyboard-shortcuts.js.es6 b/app/assets/javascripts/discourse/lib/keyboard-shortcuts.js.es6 index 991644737a..409ebc3190 100644 --- a/app/assets/javascripts/discourse/lib/keyboard-shortcuts.js.es6 +++ b/app/assets/javascripts/discourse/lib/keyboard-shortcuts.js.es6 @@ -71,6 +71,7 @@ const bindings = { "shift+z shift+z": { handler: "logout" }, "shift+f11": { handler: "fullscreenComposer", global: true }, "shift+u": { handler: "deferTopic" }, + "shift+a": { handler: "toggleAdminActions" }, t: { postAction: "replyAsNewTopic" }, u: { handler: "goBack", anonymous: true }, "x r": { @@ -638,5 +639,9 @@ export default { deferTopic() { this.container.lookup("controller:topic").send("deferTopic"); + }, + + toggleAdminActions() { + this.appEvents.trigger("topic:toggle-actions"); } }; diff --git a/app/assets/javascripts/discourse/lib/lazy-load-images.js.es6 b/app/assets/javascripts/discourse/lib/lazy-load-images.js.es6 index 284c5a3be4..a8f30a3998 100644 --- a/app/assets/javascripts/discourse/lib/lazy-load-images.js.es6 +++ b/app/assets/javascripts/discourse/lib/lazy-load-images.js.es6 @@ -40,7 +40,18 @@ function show(image) { image.srcset = copyImg.srcset; } image.classList.remove("d-lazyload-hidden"); - image.parentNode.removeChild(copyImg); + + if (image.onload) { + // don't bother fighting with existing handler + // this can mean a slight flash on mobile + image.parentNode.removeChild(copyImg); + } else { + image.onload = () => { + image.parentNode.removeChild(copyImg); + image.onload = null; + }; + } + copyImg.onload = null; }; @@ -50,43 +61,23 @@ function show(image) { copyImg.srcset = imageData.srcset; } + // width of image may not match, use computed style which + // is the actual size of the image + const computedStyle = window.getComputedStyle(image); + const actualWidth = parseInt(computedStyle.width, 10); + const actualHeight = parseInt(computedStyle.height, 10); + copyImg.style.position = "absolute"; copyImg.style.top = `${image.offsetTop}px`; copyImg.style.left = `${image.offsetLeft}px`; + copyImg.style.width = `${actualWidth}px`; + copyImg.style.height = `${actualHeight}px`; + copyImg.className = imageData.className; - let inOnebox = false; - let inQuote = false; - for (let element = image; element; element = element.parentElement) { - if (element.tagName === "ARTICLE" && element.dataset.postId) { - break; - } - if (element.classList.contains("onebox")) { - inOnebox = true; - } - if (element.tagName === "BLOCKQUOTE") { - inQuote = true; - } - } - - if (!inOnebox) { - copyImg.style.width = `${imageData.width}px`; - copyImg.style.height = `${imageData.height}px`; - } - - if (inQuote && imageData.width && imageData.height) { - const computedStyle = window.getComputedStyle(image); - const width = parseInt(computedStyle.width, 10); - const height = width * (imageData.height / imageData.width); - - image.width = width; - image.height = height; - - copyImg.style.width = `${width}px`; - copyImg.style.height = `${height}px`; - } - - image.parentNode.insertBefore(copyImg, image); + // insert after the current element so styling still will + // apply to original image firstChild selectors + image.parentNode.insertBefore(copyImg, image.nextSibling); } else { image.classList.remove("d-lazyload-hidden"); } diff --git a/app/assets/javascripts/discourse/lib/plugin-api.js.es6 b/app/assets/javascripts/discourse/lib/plugin-api.js.es6 index e4f1362c86..09ebe31f43 100644 --- a/app/assets/javascripts/discourse/lib/plugin-api.js.es6 +++ b/app/assets/javascripts/discourse/lib/plugin-api.js.es6 @@ -8,6 +8,7 @@ import { includeAttributes } from "discourse/lib/transform-post"; import { registerHighlightJSLanguage } from "discourse/lib/highlight-syntax"; import { addToolbarCallback } from "discourse/components/d-editor"; import { addWidgetCleanCallback } from "discourse/components/mount-widget"; +import { addGlobalNotice } from "discourse/components/global-notice"; import { createWidget, reopenWidget, @@ -47,9 +48,10 @@ import { import { addCategorySortCriteria } from "discourse/components/edit-category-settings"; import { queryRegistry } from "discourse/widgets/widget"; import Composer from "discourse/models/composer"; +import { on } from "@ember/object/evented"; // If you add any methods to the API ensure you bump up this number -const PLUGIN_API_VERSION = "0.8.36"; +const PLUGIN_API_VERSION = "0.8.37"; class PluginApi { constructor(version, container) { @@ -969,6 +971,18 @@ class PluginApi { registerHighlightJSLanguage(name, fn) { registerHighlightJSLanguage(name, fn); } + + /** + * Adds global notices to display. + * + * Example: + * + * api.addGlobalNotice("text", "foo", { html: "

bar

" }) + * + **/ + addGlobalNotice(id, text, options) { + addGlobalNotice(id, text, options); + } } let _pluginv01; @@ -1046,12 +1060,12 @@ function decorate(klass, evt, cb, id) { } const mixin = {}; - mixin["_decorate_" + _decorateId++] = function($elem) { + mixin["_decorate_" + _decorateId++] = on(evt, function($elem) { $elem = $elem || $(this.element); if ($elem) { cb($elem); } - }.on(evt); + }); klass.reopen(mixin); } diff --git a/app/assets/javascripts/discourse/lib/posts-with-placeholders.js.es6 b/app/assets/javascripts/discourse/lib/posts-with-placeholders.js.es6 index 3bf8c2457c..449c70ab79 100644 --- a/app/assets/javascripts/discourse/lib/posts-with-placeholders.js.es6 +++ b/app/assets/javascripts/discourse/lib/posts-with-placeholders.js.es6 @@ -1,5 +1,5 @@ import EmberObject from "@ember/object"; -import { default as discourseComputed } from "discourse-common/utils/decorators"; +import discourseComputed from "discourse-common/utils/decorators"; export function Placeholder(viewName) { this.viewName = viewName; diff --git a/app/assets/javascripts/discourse/lib/render-tag.js.es6 b/app/assets/javascripts/discourse/lib/render-tag.js.es6 index 5206e2c900..eed6efddd6 100644 --- a/app/assets/javascripts/discourse/lib/render-tag.js.es6 +++ b/app/assets/javascripts/discourse/lib/render-tag.js.es6 @@ -20,7 +20,7 @@ function defaultRenderTag(tag, params) { : User.current().username; path = `/u/${username}/messages/tags/${tag}`; } else { - path = `/tags/${tag}`; + path = `/tag/${tag}`; } } const href = path ? ` href='${Discourse.getURL(path)}' ` : ""; diff --git a/app/assets/javascripts/discourse/lib/search.js.es6 b/app/assets/javascripts/discourse/lib/search.js.es6 index 426a630483..d6c76dd56b 100644 --- a/app/assets/javascripts/discourse/lib/search.js.es6 +++ b/app/assets/javascripts/discourse/lib/search.js.es6 @@ -79,7 +79,7 @@ export function translateResults(results, opts) { const tagName = Handlebars.Utils.escapeExpression(tag.name); return EmberObject.create({ id: tagName, - url: Discourse.getURL("/tags/" + tagName) + url: Discourse.getURL("/tag/" + tagName) }); }) .compact(); diff --git a/app/assets/javascripts/discourse/lib/static-route-builder.js.es6 b/app/assets/javascripts/discourse/lib/static-route-builder.js.es6 index 8baa97fb4d..91cda0f0be 100644 --- a/app/assets/javascripts/discourse/lib/static-route-builder.js.es6 +++ b/app/assets/javascripts/discourse/lib/static-route-builder.js.es6 @@ -1,6 +1,6 @@ import DiscourseRoute from "discourse/routes/discourse"; import StaticPage from "discourse/models/static-page"; -import { default as DiscourseURL, jumpToElement } from "discourse/lib/url"; +import DiscourseURL, { jumpToElement } from "discourse/lib/url"; const configs = { faq: "faq_url", diff --git a/app/assets/javascripts/discourse/lib/text.js.es6 b/app/assets/javascripts/discourse/lib/text.js.es6 index 7862ce53e0..7f75cb497d 100644 --- a/app/assets/javascripts/discourse/lib/text.js.es6 +++ b/app/assets/javascripts/discourse/lib/text.js.es6 @@ -1,4 +1,4 @@ -import { default as PrettyText, buildOptions } from "pretty-text/pretty-text"; +import PrettyText, { buildOptions } from "pretty-text/pretty-text"; import { performEmojiUnescape, buildEmojiUrl } from "pretty-text/emoji"; import WhiteLister from "pretty-text/white-lister"; import { sanitize as textSanitize } from "pretty-text/sanitizer"; diff --git a/app/assets/javascripts/discourse/lib/to-markdown.js.es6 b/app/assets/javascripts/discourse/lib/to-markdown.js.es6 index c7ba652984..163c895f83 100644 --- a/app/assets/javascripts/discourse/lib/to-markdown.js.es6 +++ b/app/assets/javascripts/discourse/lib/to-markdown.js.es6 @@ -102,6 +102,10 @@ export class Tag { ]; } + static whitelists() { + return ["ins", "del", "small", "big", "kbd", "ruby", "rt", "rb", "rp"]; + } + static block(name, prefix, suffix) { return class extends Tag { constructor() { @@ -149,7 +153,7 @@ export class Tag { }; } - static keep(name) { + static whitelist(name) { return class extends Tag { constructor() { super(name, `<${name}>`, ``); @@ -479,18 +483,12 @@ function tags() { ...Tag.headings().map((h, i) => Tag.heading(h, i + 1)), ...Tag.slices().map(s => Tag.slice(s, "\n")), ...Tag.emphases().map(e => Tag.emphasis(e[0], e[1])), + ...Tag.whitelists().map(t => Tag.whitelist(t)), Tag.cell("td"), Tag.cell("th"), Tag.replace("br", "\n"), Tag.replace("hr", "\n---\n"), Tag.replace("head", ""), - Tag.keep("ins"), - Tag.keep("del"), - Tag.keep("small"), - Tag.keep("big"), - Tag.keep("kbd"), - Tag.keep("ruby"), - Tag.keep("rt"), Tag.li(), Tag.link(), Tag.image(), diff --git a/app/assets/javascripts/discourse/lib/uploads.js.es6 b/app/assets/javascripts/discourse/lib/uploads.js.es6 index 40840647f8..5a0e81779b 100644 --- a/app/assets/javascripts/discourse/lib/uploads.js.es6 +++ b/app/assets/javascripts/discourse/lib/uploads.js.es6 @@ -225,7 +225,10 @@ function uploadLocation(url) { url = Discourse.getURLWithCDN(url); return /^\/\//.test(url) ? "http:" + url : url; } else if (Discourse.S3BaseUrl) { - return "https:" + url; + if (url.indexOf("secure-media-uploads") === -1) { + return "https:" + url; + } + return window.location.protocol + url; } else { var protocol = window.location.protocol + "//", hostname = window.location.hostname, diff --git a/app/assets/javascripts/discourse/lib/url.js.es6 b/app/assets/javascripts/discourse/lib/url.js.es6 index ae252facf8..6d7fa31966 100644 --- a/app/assets/javascripts/discourse/lib/url.js.es6 +++ b/app/assets/javascripts/discourse/lib/url.js.es6 @@ -99,9 +99,21 @@ const DiscourseURL = EmberObject.extend({ let holder; if (opts.jumpEnd) { - $(window).scrollTop($(document).height() - $(window).height()); - _transitioning = false; - return; + let $holder = $(holderId); + let holderHeight = $holder.height(); + let windowHeight = $(window).height() - offsetCalculator(); + + // scroll to the bottom of the post and if the post is yuge we go back up the + // timeline by a small % of the post height so we can see the bottom of the text. + // + // otherwise just jump to the top of the post using the lock & holder method. + if (holderHeight > windowHeight) { + $(window).scrollTop( + $holder.offset().top + (holderHeight - holderHeight / 10) + ); + _transitioning = false; + return; + } } if (postNumber === 1 && !opts.anchor) { diff --git a/app/assets/javascripts/discourse/mixins/card-contents-base.js.es6 b/app/assets/javascripts/discourse/mixins/card-contents-base.js.es6 index e9c0c0df56..c5cdcc4a16 100644 --- a/app/assets/javascripts/discourse/mixins/card-contents-base.js.es6 +++ b/app/assets/javascripts/discourse/mixins/card-contents-base.js.es6 @@ -126,7 +126,7 @@ export default Mixin.create({ if (wantsNewWindow(e)) { return; } - const $target = $(e.target); + const $target = $(e.currentTarget); return this._show($target.text().replace(/^@/, ""), $target); }); diff --git a/app/assets/javascripts/discourse/mixins/name-validation.js.es6 b/app/assets/javascripts/discourse/mixins/name-validation.js.es6 index b2594c97fe..3b377f095b 100644 --- a/app/assets/javascripts/discourse/mixins/name-validation.js.es6 +++ b/app/assets/javascripts/discourse/mixins/name-validation.js.es6 @@ -1,5 +1,5 @@ import { isEmpty } from "@ember/utils"; -import { default as discourseComputed } from "discourse-common/utils/decorators"; +import discourseComputed from "discourse-common/utils/decorators"; import Mixin from "@ember/object/mixin"; import EmberObject from "@ember/object"; diff --git a/app/assets/javascripts/discourse/mixins/open-composer.js.es6 b/app/assets/javascripts/discourse/mixins/open-composer.js.es6 index 136cca6d50..480eae5783 100644 --- a/app/assets/javascripts/discourse/mixins/open-composer.js.es6 +++ b/app/assets/javascripts/discourse/mixins/open-composer.js.es6 @@ -39,10 +39,10 @@ export default Mixin.create({ }); }, - openComposerWithMessageParams(usernames, topicTitle, topicBody) { + openComposerWithMessageParams(recipients, topicTitle, topicBody) { this.controllerFor("composer").open({ action: Composer.PRIVATE_MESSAGE, - usernames, + recipients, topicTitle, topicBody, archetypeId: "private_message", diff --git a/app/assets/javascripts/discourse/mixins/password-validation.js.es6 b/app/assets/javascripts/discourse/mixins/password-validation.js.es6 index b313368f52..d660f5208b 100644 --- a/app/assets/javascripts/discourse/mixins/password-validation.js.es6 +++ b/app/assets/javascripts/discourse/mixins/password-validation.js.es6 @@ -1,5 +1,5 @@ import { isEmpty } from "@ember/utils"; -import { default as discourseComputed } from "discourse-common/utils/decorators"; +import discourseComputed from "discourse-common/utils/decorators"; import Mixin from "@ember/object/mixin"; import EmberObject from "@ember/object"; diff --git a/app/assets/javascripts/discourse/mixins/preferences-tab-controller.js.es6 b/app/assets/javascripts/discourse/mixins/preferences-tab-controller.js.es6 index 2e8763097c..4dabeb3fa0 100644 --- a/app/assets/javascripts/discourse/mixins/preferences-tab-controller.js.es6 +++ b/app/assets/javascripts/discourse/mixins/preferences-tab-controller.js.es6 @@ -1,4 +1,4 @@ -import { default as discourseComputed } from "discourse-common/utils/decorators"; +import discourseComputed from "discourse-common/utils/decorators"; import Mixin from "@ember/object/mixin"; export default Mixin.create({ diff --git a/app/assets/javascripts/discourse/mixins/user-fields-validation.js.es6 b/app/assets/javascripts/discourse/mixins/user-fields-validation.js.es6 index 782ceca03d..5a7ff18808 100644 --- a/app/assets/javascripts/discourse/mixins/user-fields-validation.js.es6 +++ b/app/assets/javascripts/discourse/mixins/user-fields-validation.js.es6 @@ -1,9 +1,6 @@ import { isEmpty } from "@ember/utils"; import EmberObject from "@ember/object"; -import { - on, - default as discourseComputed -} from "discourse-common/utils/decorators"; +import discourseComputed, { on } from "discourse-common/utils/decorators"; import Mixin from "@ember/object/mixin"; export default Mixin.create({ diff --git a/app/assets/javascripts/discourse/mixins/username-validation.js.es6 b/app/assets/javascripts/discourse/mixins/username-validation.js.es6 index 3e18f815c5..fcd0ea89ba 100644 --- a/app/assets/javascripts/discourse/mixins/username-validation.js.es6 +++ b/app/assets/javascripts/discourse/mixins/username-validation.js.es6 @@ -1,7 +1,7 @@ import { isEmpty } from "@ember/utils"; import discourseDebounce from "discourse/lib/debounce"; import { setting } from "discourse/lib/computed"; -import { default as discourseComputed } from "discourse-common/utils/decorators"; +import discourseComputed from "discourse-common/utils/decorators"; import Mixin from "@ember/object/mixin"; import EmberObject from "@ember/object"; import User from "discourse/models/user"; diff --git a/app/assets/javascripts/discourse/models/category.js.es6 b/app/assets/javascripts/discourse/models/category.js.es6 index 3847a9ea10..738819bc60 100644 --- a/app/assets/javascripts/discourse/models/category.js.es6 +++ b/app/assets/javascripts/discourse/models/category.js.es6 @@ -308,11 +308,10 @@ Category.reopenClass({ }, findBySlugAndParent(slug, parentCategory) { + if (Discourse.SiteSettings.slug_generation_method === "encoded") { + slug = encodeURI(slug); + } return Category.list().find(category => { - if (Discourse.SiteSettings.slug_generation_method === "encoded") { - slug = encodeURI(slug); - } - return ( category.slug === slug && (category.parentCategory || null) === parentCategory @@ -335,7 +334,11 @@ Category.reopenClass({ }, findBySlugPathWithID(slugPathWithID) { - const parts = slugPathWithID.split("/"); + let parts = slugPathWithID.split("/").filter(Boolean); + // slugs found by star/glob pathing in emeber do not automatically url decode - ensure that these are decoded + if (Discourse.SiteSettings.slug_generation_method === "encoded") { + parts = parts.map(urlPart => decodeURI(urlPart)); + } let category = null; if (parts.length > 0 && parts[parts.length - 1].match(/^\d+$/)) { diff --git a/app/assets/javascripts/discourse/models/composer.js.es6 b/app/assets/javascripts/discourse/models/composer.js.es6 index a630f521c4..991e580af2 100644 --- a/app/assets/javascripts/discourse/models/composer.js.es6 +++ b/app/assets/javascripts/discourse/models/composer.js.es6 @@ -9,18 +9,22 @@ import Topic from "discourse/models/topic"; import { throwAjaxError } from "discourse/lib/ajax-error"; import Quote from "discourse/lib/quote"; import Draft from "discourse/models/draft"; -import { - default as discourseComputed, +import discourseComputed, { observes, on } from "discourse-common/utils/decorators"; -import { escapeExpression, tinyAvatar } from "discourse/lib/utilities"; +import { + escapeExpression, + tinyAvatar, + emailValid +} from "discourse/lib/utilities"; import { propertyNotEqual } from "discourse/lib/computed"; import { throttle } from "@ember/runloop"; import { Promise } from "rsvp"; import { set } from "@ember/object"; import Site from "discourse/models/site"; import User from "discourse/models/user"; +import deprecated from "discourse-common/lib/deprecated"; // The actions the composer can take export const CREATE_TOPIC = "createTopic", @@ -51,7 +55,7 @@ const CLOSED = "closed", is_warning: "isWarning", whisper: "whisper", archetype: "archetypeId", - target_usernames: "targetUsernames", + target_recipients: "targetRecipients", typing_duration_msecs: "typingTime", composer_open_duration_msecs: "composerTime", tags: "tags", @@ -77,7 +81,9 @@ const CLOSED = "closed", composerTime: "composerTime", typingTime: "typingTime", postId: "post.id", - usernames: "targetUsernames" + // TODO remove together with 'targetUsername' deprecations + usernames: "targetUsernames", + recipients: "targetRecipients" }, _add_draft_fields = {}, FAST_REPLY_LENGTH_THRESHOLD = 10000; @@ -340,11 +346,36 @@ const Composer = RestModel.extend({ return options; }, + @discourseComputed("targetRecipients") + targetUsernames(targetRecipients) { + deprecated( + "`targetUsernames` is deprecated, use `targetRecipients` instead." + ); + return targetRecipients; + }, + + @discourseComputed("targetRecipients") + targetRecipientsArray(targetRecipients) { + const recipients = targetRecipients ? targetRecipients.split(",") : []; + const groups = new Set(this.site.groups.map(g => g.name)); + + return recipients.map(item => { + if (groups.has(item)) { + return { type: "group", name: item }; + } else if (emailValid(item)) { + return { type: "email", name: item }; + } else { + return { type: "user", name: item }; + } + }); + }, + @discourseComputed( "loading", "canEditTitle", "titleLength", - "targetUsernames", + "targetRecipients", + "targetRecipientsArray", "replyLength", "categoryId", "missingReplyCharacters", @@ -357,7 +388,8 @@ const Composer = RestModel.extend({ loading, canEditTitle, titleLength, - targetUsernames, + targetRecipients, + targetRecipientsArray, replyLength, categoryId, missingReplyCharacters, @@ -402,9 +434,7 @@ const Composer = RestModel.extend({ if (this.privateMessage) { // need at least one user when sending a PM - return ( - targetUsernames && (targetUsernames.trim() + ",").indexOf(",") === 0 - ); + return targetRecipients && targetRecipientsArray.length === 0; } else { // has a category? (when needed) return this.requiredCategoryMissing; @@ -667,13 +697,17 @@ const Composer = RestModel.extend({ throw new Error("draft sequence is required"); } + if (opts.usernames) { + deprecated("`usernames` is deprecated, use `recipients` instead."); + } + this.setProperties({ draftKey: opts.draftKey, draftSequence: opts.draftSequence, composeState: opts.composerState || OPEN, action: opts.action, topic: opts.topic, - targetUsernames: opts.usernames, + targetRecipients: opts.usernames || opts.recipients, composerTotalOpened: opts.composerTime, typingTime: opts.typingTime, whisper: opts.whisper, diff --git a/app/assets/javascripts/discourse/models/group.js.es6 b/app/assets/javascripts/discourse/models/group.js.es6 index 284719579a..c799f78314 100644 --- a/app/assets/javascripts/discourse/models/group.js.es6 +++ b/app/assets/javascripts/discourse/models/group.js.es6 @@ -1,10 +1,7 @@ import EmberObject from "@ember/object"; import { equal } from "@ember/object/computed"; import { isEmpty } from "@ember/utils"; -import { - default as discourseComputed, - observes -} from "discourse-common/utils/decorators"; +import discourseComputed, { observes } from "discourse-common/utils/decorators"; import { ajax } from "discourse/lib/ajax"; import Category from "discourse/models/category"; import GroupHistory from "discourse/models/group-history"; diff --git a/app/assets/javascripts/discourse/models/login-method.js.es6 b/app/assets/javascripts/discourse/models/login-method.js.es6 index 84b4cf4a52..2a3ab489c4 100644 --- a/app/assets/javascripts/discourse/models/login-method.js.es6 +++ b/app/assets/javascripts/discourse/models/login-method.js.es6 @@ -21,7 +21,7 @@ const LoginMethod = EmberObject.extend({ return this.message_override || I18n.t(`login.${this.name}.message`); }, - doLogin({ reconnect = false } = {}) { + doLogin({ reconnect = false, params = {} } = {}) { if (this.customLogin) { this.customLogin(); return Promise.resolve(); @@ -35,7 +35,15 @@ const LoginMethod = EmberObject.extend({ let authUrl = Discourse.getURL(`/auth/${this.name}`); if (reconnect) { - authUrl += "?reconnect=true"; + params["reconnect"] = true; + } + + const paramKeys = Object.keys(params); + if (paramKeys.length > 0) { + authUrl += "?"; + authUrl += paramKeys + .map(k => `${encodeURIComponent(k)}=${encodeURIComponent(params[k])}`) + .join("&"); } return LoginMethod.buildPostForm(authUrl).then(form => form.submit()); diff --git a/app/assets/javascripts/discourse/models/post-stream.js.es6 b/app/assets/javascripts/discourse/models/post-stream.js.es6 index 614db16332..4accbbe268 100644 --- a/app/assets/javascripts/discourse/models/post-stream.js.es6 +++ b/app/assets/javascripts/discourse/models/post-stream.js.es6 @@ -5,7 +5,7 @@ import { ajax } from "discourse/lib/ajax"; import DiscourseURL from "discourse/lib/url"; import RestModel from "discourse/models/rest"; import PostsWithPlaceholders from "discourse/lib/posts-with-placeholders"; -import { default as discourseComputed } from "discourse-common/utils/decorators"; +import discourseComputed from "discourse-common/utils/decorators"; import { loadTopicView } from "discourse/models/topic"; import { Promise } from "rsvp"; import User from "discourse/models/user"; diff --git a/app/assets/javascripts/discourse/models/topic-tracking-state.js.es6 b/app/assets/javascripts/discourse/models/topic-tracking-state.js.es6 index 82fc8d6f03..8f79703ef7 100644 --- a/app/assets/javascripts/discourse/models/topic-tracking-state.js.es6 +++ b/app/assets/javascripts/discourse/models/topic-tracking-state.js.es6 @@ -1,10 +1,7 @@ import { get } from "@ember/object"; import { isEmpty } from "@ember/utils"; import { NotificationLevels } from "discourse/lib/notification-levels"; -import { - default as discourseComputed, - on -} from "discourse-common/utils/decorators"; +import discourseComputed, { on } from "discourse-common/utils/decorators"; import PreloadStore from "preload-store"; import Category from "discourse/models/category"; import EmberObject from "@ember/object"; @@ -79,15 +76,6 @@ const TopicTrackingState = EmberObject.extend({ } } - // fill parent_category_id we need it for counting new/unread - if (data.payload && data.payload.category_id) { - var category = Category.findById(data.payload.category_id); - - if (category && category.parent_category_id) { - data.payload.parent_category_id = category.parent_category_id; - } - } - if (data.message_type === "latest") { tracker.notify(data); } @@ -367,38 +355,43 @@ const TopicTrackingState = EmberObject.extend({ this.incrementProperty("messageCount"); }, - countNew(category_id) { + getSubCategoryIds(categoryId) { + const result = [categoryId]; + const categories = Category.list(); + + for (let i = 0; i < result.length; ++i) { + for (let j = 0; j < categories.length; ++j) { + if (result[i] === categories[j].parent_category_id) { + result[result.length] = categories[j].id; + } + } + } + + return new Set(result); + }, + + countNew(categoryId) { + const subcategoryIds = this.getSubCategoryIds(categoryId); return _.chain(this.states) .filter(isNew) .filter( topic => topic.archetype !== "private_message" && !topic.deleted && - (topic.category_id === category_id || - topic.parent_category_id === category_id || - !category_id) + (!categoryId || subcategoryIds.has(topic.category_id)) ) .value().length; }, - resetNew() { - Object.keys(this.states).forEach(id => { - if (this.states[id].last_read_post_number === null) { - delete this.states[id]; - } - }); - }, - - countUnread(category_id) { + countUnread(categoryId) { + const subcategoryIds = this.getSubCategoryIds(categoryId); return _.chain(this.states) .filter(isUnread) .filter( topic => topic.archetype !== "private_message" && !topic.deleted && - (topic.category_id === category_id || - topic.parent_category_id === category_id || - !category_id) + (!categoryId || subcategoryIds.has(topic.category_id)) ) .value().length; }, @@ -445,10 +438,6 @@ const TopicTrackingState = EmberObject.extend({ // I am taking some shortcuts here to avoid 500 gets for a large list if (data) { data.forEach(topic => { - let category = Category.findById(topic.category_id); - if (category && category.parent_category_id) { - topic.parent_category_id = category.parent_category_id; - } states["t" + topic.topic_id] = topic; }); } diff --git a/app/assets/javascripts/discourse/models/topic.js.es6 b/app/assets/javascripts/discourse/models/topic.js.es6 index c392bc20ef..514c3564e6 100644 --- a/app/assets/javascripts/discourse/models/topic.js.es6 +++ b/app/assets/javascripts/discourse/models/topic.js.es6 @@ -13,8 +13,7 @@ import { censor } from "pretty-text/censored-words"; import { emojiUnescape } from "discourse/lib/text"; import PreloadStore from "preload-store"; import { userPath } from "discourse/lib/url"; -import { - default as discourseComputed, +import discourseComputed, { observes, on } from "discourse-common/utils/decorators"; @@ -467,16 +466,19 @@ const Topic = RestModel.extend({ // Delete this topic destroy(deleted_by) { - this.setProperties({ - deleted_at: new Date(), - deleted_by: deleted_by, - "details.can_delete": false, - "details.can_recover": true - }); return ajax(`/t/${this.id}`, { data: { context: window.location.pathname }, type: "DELETE" - }); + }) + .then(() => { + this.setProperties({ + deleted_at: new Date(), + deleted_by: deleted_by, + "details.can_delete": false, + "details.can_recover": true + }); + }) + .catch(popupAjaxError); }, // Recover this topic if deleted diff --git a/app/assets/javascripts/discourse/models/user-stream.js.es6 b/app/assets/javascripts/discourse/models/user-stream.js.es6 index 19e3d2ee62..81af6b30f8 100644 --- a/app/assets/javascripts/discourse/models/user-stream.js.es6 +++ b/app/assets/javascripts/discourse/models/user-stream.js.es6 @@ -4,10 +4,7 @@ import RestModel from "discourse/models/rest"; import UserAction from "discourse/models/user-action"; import { emojiUnescape } from "discourse/lib/text"; import { Promise } from "rsvp"; -import { - default as discourseComputed, - on -} from "discourse-common/utils/decorators"; +import discourseComputed, { on } from "discourse-common/utils/decorators"; export default RestModel.extend({ loaded: false, diff --git a/app/assets/javascripts/discourse/models/user.js.es6 b/app/assets/javascripts/discourse/models/user.js.es6 index 9b92f4bd03..1607ad1804 100644 --- a/app/assets/javascripts/discourse/models/user.js.es6 +++ b/app/assets/javascripts/discourse/models/user.js.es6 @@ -8,10 +8,7 @@ import UserStream from "discourse/models/user-stream"; import UserPostsStream from "discourse/models/user-posts-stream"; import Singleton from "discourse/mixins/singleton"; import { longDate } from "discourse/lib/formatter"; -import { - default as discourseComputed, - observes -} from "discourse-common/utils/decorators"; +import discourseComputed, { observes } from "discourse-common/utils/decorators"; import Badge from "discourse/models/badge"; import UserBadge from "discourse/models/user-badge"; import UserActionStat from "discourse/models/user-action-stat"; diff --git a/app/assets/javascripts/discourse/pre-initializers/inject-discourse-objects.js.es6 b/app/assets/javascripts/discourse/pre-initializers/inject-discourse-objects.js.es6 index eab9eb988c..924e6f6a50 100644 --- a/app/assets/javascripts/discourse/pre-initializers/inject-discourse-objects.js.es6 +++ b/app/assets/javascripts/discourse/pre-initializers/inject-discourse-objects.js.es6 @@ -4,9 +4,8 @@ import Store from "discourse/models/store"; import DiscourseLocation from "discourse/lib/discourse-location"; import Discourse from "discourse"; import SearchService from "discourse/services/search"; -import { - startTracking, - default as TopicTrackingState +import TopicTrackingState, { + startTracking } from "discourse/models/topic-tracking-state"; import ScreenTrack from "discourse/lib/screen-track"; import Site from "discourse/models/site"; diff --git a/app/assets/javascripts/discourse/raw-views/list/post-count-or-badges.js.es6 b/app/assets/javascripts/discourse/raw-views/list/post-count-or-badges.js.es6 index 20b15a3ebd..dd301f8b72 100644 --- a/app/assets/javascripts/discourse/raw-views/list/post-count-or-badges.js.es6 +++ b/app/assets/javascripts/discourse/raw-views/list/post-count-or-badges.js.es6 @@ -1,6 +1,6 @@ import { or, and } from "@ember/object/computed"; import EmberObject from "@ember/object"; -import { default as discourseComputed } from "discourse-common/utils/decorators"; +import discourseComputed from "discourse-common/utils/decorators"; export default EmberObject.extend({ postCountsPresent: or("topic.unread", "topic.displayNewPosts"), diff --git a/app/assets/javascripts/discourse/raw-views/topic-list-header-column.js.es6 b/app/assets/javascripts/discourse/raw-views/topic-list-header-column.js.es6 index e5b44af68e..dfa6e7039c 100644 --- a/app/assets/javascripts/discourse/raw-views/topic-list-header-column.js.es6 +++ b/app/assets/javascripts/discourse/raw-views/topic-list-header-column.js.es6 @@ -1,5 +1,5 @@ import EmberObject from "@ember/object"; -import { default as discourseComputed } from "discourse-common/utils/decorators"; +import discourseComputed from "discourse-common/utils/decorators"; export default EmberObject.extend({ @discourseComputed diff --git a/app/assets/javascripts/discourse/routes/application.js.es6 b/app/assets/javascripts/discourse/routes/application.js.es6 index 793c8a6492..e877d8ceff 100644 --- a/app/assets/javascripts/discourse/routes/application.js.es6 +++ b/app/assets/javascripts/discourse/routes/application.js.es6 @@ -71,20 +71,20 @@ const ApplicationRoute = DiscourseRoute.extend(OpenComposer, { }, composePrivateMessage(user, post) { - const recipient = user ? user.get("username") : "", - reply = post - ? `${window.location.protocol}//${window.location.host}${post.url}` - : null, - title = post - ? I18n.t("composer.reference_topic_title", { - title: post.topic.title - }) - : null; + const recipients = user ? user.get("username") : ""; + const reply = post + ? `${window.location.protocol}//${window.location.host}${post.url}` + : null; + const title = post + ? I18n.t("composer.reference_topic_title", { + title: post.topic.title + }) + : null; // used only once, one less dependency return this.controllerFor("composer").open({ action: Composer.PRIVATE_MESSAGE, - usernames: recipient, + recipients, archetypeId: "private_message", draftKey: Composer.NEW_PRIVATE_MESSAGE_KEY, reply, @@ -221,8 +221,8 @@ const ApplicationRoute = DiscourseRoute.extend(OpenComposer, { ); }, - createNewMessageViaParams(username, title, body) { - this.openComposerWithMessageParams(username, title, body); + createNewMessageViaParams(recipients, title, body) { + this.openComposerWithMessageParams(recipients, title, body); } }, diff --git a/app/assets/javascripts/discourse/routes/preferences-profile.js.es6 b/app/assets/javascripts/discourse/routes/preferences-profile.js.es6 index 7b794ef681..b56f1d7d78 100644 --- a/app/assets/javascripts/discourse/routes/preferences-profile.js.es6 +++ b/app/assets/javascripts/discourse/routes/preferences-profile.js.es6 @@ -3,8 +3,10 @@ import RestrictedUserRoute from "discourse/routes/restricted-user"; export default RestrictedUserRoute.extend({ showFooter: true, setupController(controller, model) { - model.user_option.timezone = - model.user_option.timezone || moment.tz.guess(); + if (!model.user_option.timezone) { + Ember.set(model, "user_option.timezone", moment.tz.guess()); + } + controller.set("model", model); } }); diff --git a/app/assets/javascripts/discourse/routes/tags-show.js.es6 b/app/assets/javascripts/discourse/routes/tags-show.js.es6 index 44b6bc6e1d..08fe0b24d2 100644 --- a/app/assets/javascripts/discourse/routes/tags-show.js.es6 +++ b/app/assets/javascripts/discourse/routes/tags-show.js.es6 @@ -84,7 +84,7 @@ export default DiscourseRoute.extend(FilterModeMixin, { filter = `tags/intersection/${tagId}/${this.additionalTags.join("/")}`; } else { this.set("category", null); - filter = `tags/${tagId}/l/${topicFilter}`; + filter = `tag/${tagId}/l/${topicFilter}`; } return findTopicList(this.store, this.topicTrackingState, filter, params, { diff --git a/app/assets/javascripts/discourse/routes/topic-by-slug-or-id.js.es6 b/app/assets/javascripts/discourse/routes/topic-by-slug-or-id.js.es6 index 74556b57fc..62339b6519 100644 --- a/app/assets/javascripts/discourse/routes/topic-by-slug-or-id.js.es6 +++ b/app/assets/javascripts/discourse/routes/topic-by-slug-or-id.js.es6 @@ -1,5 +1,5 @@ import DiscourseRoute from "discourse/routes/discourse"; -import { default as Topic, ID_CONSTRAINT } from "discourse/models/topic"; +import Topic, { ID_CONSTRAINT } from "discourse/models/topic"; import DiscourseURL from "discourse/lib/url"; export default DiscourseRoute.extend({ diff --git a/app/assets/javascripts/discourse/routes/user-activity-drafts.js.es6 b/app/assets/javascripts/discourse/routes/user-activity-drafts.js.es6 index 392b9a6255..a004e6631e 100644 --- a/app/assets/javascripts/discourse/routes/user-activity-drafts.js.es6 +++ b/app/assets/javascripts/discourse/routes/user-activity-drafts.js.es6 @@ -12,9 +12,16 @@ export default DiscourseRoute.extend({ setupController(controller, model) { controller.set("model", model); + }, + + activate() { this.appEvents.on("draft:destroyed", this, this.refresh); }, + deactivate() { + this.appEvents.off("draft:destroyed", this, this.refresh); + }, + actions: { didTransition() { this.controllerFor("user-activity")._showFooter(); diff --git a/app/assets/javascripts/discourse/services/logs-notice.js.es6 b/app/assets/javascripts/discourse/services/logs-notice.js.es6 index 9d124edf0b..7292e4465f 100644 --- a/app/assets/javascripts/discourse/services/logs-notice.js.es6 +++ b/app/assets/javascripts/discourse/services/logs-notice.js.es6 @@ -1,7 +1,6 @@ import { isEmpty } from "@ember/utils"; import EmberObject from "@ember/object"; -import { - default as discourseComputed, +import discourseComputed, { on, observes } from "discourse-common/utils/decorators"; diff --git a/app/assets/javascripts/discourse/services/search.js.es6 b/app/assets/javascripts/discourse/services/search.js.es6 index c9e51f43c8..d7a8edcd10 100644 --- a/app/assets/javascripts/discourse/services/search.js.es6 +++ b/app/assets/javascripts/discourse/services/search.js.es6 @@ -1,9 +1,6 @@ import { get } from "@ember/object"; import EmberObject from "@ember/object"; -import { - default as discourseComputed, - observes -} from "discourse-common/utils/decorators"; +import discourseComputed, { observes } from "discourse-common/utils/decorators"; export default EmberObject.extend({ searchContextEnabled: false, // checkbox to scope search diff --git a/app/assets/javascripts/discourse/templates/components/backup-codes.hbs b/app/assets/javascripts/discourse/templates/components/backup-codes.hbs index c8ddb725be..bd591ee6c3 100644 --- a/app/assets/javascripts/discourse/templates/components/backup-codes.hbs +++ b/app/assets/javascripts/discourse/templates/components/backup-codes.hbs @@ -1,5 +1,5 @@
- + {{d-button action=(action "copyToClipboard") diff --git a/app/assets/javascripts/discourse/templates/components/categories-boxes.hbs b/app/assets/javascripts/discourse/templates/components/categories-boxes.hbs index 8ae095c267..72ae9fd328 100644 --- a/app/assets/javascripts/discourse/templates/components/categories-boxes.hbs +++ b/app/assets/javascripts/discourse/templates/components/categories-boxes.hbs @@ -13,7 +13,7 @@
-{{/each}} \ No newline at end of file +{{/each}} diff --git a/app/assets/javascripts/discourse/templates/components/categories-only.hbs b/app/assets/javascripts/discourse/templates/components/categories-only.hbs index 852745c80f..e918461082 100644 --- a/app/assets/javascripts/discourse/templates/components/categories-only.hbs +++ b/app/assets/javascripts/discourse/templates/components/categories-only.hbs @@ -14,17 +14,22 @@ {{category-title-link category=c}} -
- {{{dir-span c.description_excerpt}}} -
- + {{#if c.description_excerpt}} +
+ {{{dir-span c.description_excerpt}}} +
+ {{/if}} {{#if c.isGrandParent}} - +
{{#each c.subcategories as |subcategory|}} diff --git a/app/assets/javascripts/discourse/templates/components/edit-category-general.hbs b/app/assets/javascripts/discourse/templates/components/edit-category-general.hbs index c157d22f0c..95e681f396 100644 --- a/app/assets/javascripts/discourse/templates/components/edit-category-general.hbs +++ b/app/assets/javascripts/discourse/templates/components/edit-category-general.hbs @@ -1,6 +1,6 @@ {{#if category.isUncategorizedCategory}}

- {{d-icon "warning"}} + {{d-icon "exclamation-triangle"}} {{{i18n 'category.uncategorized_general_warning' settingLink=uncategorizedSiteSettingLink customizeLink=customizeTextContentLink}}}

{{/if}} @@ -12,7 +12,7 @@
{{category-chooser - none="category.none" + rootNone=true value=category.parent_category_id excludeCategoryId=category.id categories=parentCategories diff --git a/app/assets/javascripts/discourse/templates/components/flag-action-type.hbs b/app/assets/javascripts/discourse/templates/components/flag-action-type.hbs index 8802d1c9b2..4d7cf58049 100644 --- a/app/assets/javascripts/discourse/templates/components/flag-action-type.hbs +++ b/app/assets/javascripts/discourse/templates/components/flag-action-type.hbs @@ -18,7 +18,7 @@

{{i18n 'flagging.notify_staff'}}

{{/if}} {{else}} -
+
{{category-title-link tagName="h4" category=subcategory}} - + {{#if subcategory.description_excerpt}} +
+ {{{dir-span subcategory.description_excerpt}}} +
+ {{/if}} {{#if subcategory.subcategories}}
{{#each subcategory.subcategories as |subsubcategory|}} @@ -37,9 +42,11 @@ {{/each}}
{{else}} -
- {{{dir-span subcategory.description_excerpt}}} -
+ {{#if subcategory.description_excerpt}} +
+ {{{dir-span subcategory.description_excerpt}}} +
+ {{/if}} {{/if}}