diff --git a/.github/workflows/ember.yml b/.github/workflows/ember.yml index 538b16b316..b906767d01 100644 --- a/.github/workflows/ember.yml +++ b/.github/workflows/ember.yml @@ -1,4 +1,4 @@ -name: Ember CLI tests +name: (experimental) Ember CLI tests (core) on: pull_request: @@ -9,10 +9,14 @@ on: jobs: build: name: run - if: true runs-on: ubuntu-latest container: discourse/discourse_test:release - timeout-minutes: 40 + timeout-minutes: 60 + + strategy: + fail-fast: false + matrix: + browser: ["Chrome", "Firefox", "Headless Firefox"] steps: - uses: actions/checkout@master @@ -43,5 +47,5 @@ jobs: - name: Core QUnit working-directory: ./app/assets/javascripts/discourse - run: yarn ember test - timeout-minutes: 30 + run: sudo -E -u discourse -H yarn ember test --launch "${{ matrix.browser }}" + timeout-minutes: 60 diff --git a/.github/workflows/ember_with_plugins.yml b/.github/workflows/ember_with_plugins.yml new file mode 100644 index 0000000000..ec355c0143 --- /dev/null +++ b/.github/workflows/ember_with_plugins.yml @@ -0,0 +1,49 @@ +name: (experimental) Ember CLI tests (plugins) + +on: + schedule: + - cron: "0 0 * * *" + +jobs: + build: + name: run + runs-on: ubuntu-latest + container: discourse/discourse_test:release + timeout-minutes: 60 + + steps: + - uses: actions/checkout@master + with: + fetch-depth: 1 + + - name: Setup Git + run: | + git config --global user.email "ci@ci.invalid" + git config --global user.name "Discourse CI" + + - name: Get yarn cache directory + id: yarn-cache-dir + run: echo "::set-output name=dir::$(yarn cache dir)" + + - name: Yarn cache + uses: actions/cache@v2 + id: yarn-cache + with: + path: ${{ steps.yarn-cache-dir.outputs.dir }} + key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} + restore-keys: | + ${{ runner.os }}-yarn- + + - name: Official Plugins Install + run: | + bundle config --local path vendor/bundle + bundle config --local deployment true + bundle config --local without development + bundle install --jobs 4 + bundle clean + bundle exec rake plugin:install_all_official + + - name: QUnit + working-directory: ./app/assets/javascripts/discourse + run: QUNIT_EMBER_CLI=1 sudo -E -u discourse -H rake plugin:qunit + timeout-minutes: 60 diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 1f47223bdf..21234f82ad 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -28,7 +28,6 @@ jobs: build_type: [backend, frontend, annotations] target: [core, plugins] postgres: ["13"] - redis: ["6.x"] exclude: - build_type: annotations target: plugins @@ -59,10 +58,9 @@ jobs: git config --global user.email "ci@ci.invalid" git config --global user.name "Discourse CI" - - name: Setup redis - uses: shogo82148/actions-setup-redis@v1 - with: - redis-version: ${{ matrix.redis }} + - name: Start redis + run: | + redis-server /etc/redis/redis.conf & - name: Bundler cache uses: actions/cache@v2 diff --git a/Gemfile b/Gemfile index 9c2ff7cdbb..e09839e88a 100644 --- a/Gemfile +++ b/Gemfile @@ -210,6 +210,9 @@ gem 'gc_tracer', require: false, platform: :mri # required for feed importing and embedding gem 'ruby-readability', require: false +# rss gem is a bundled gem from Ruby 3 onwards +gem 'rss', require: false + gem 'stackprof', require: false, platform: :mri gem 'memory_profiler', require: false, platform: :mri diff --git a/Gemfile.lock b/Gemfile.lock index 152aabb99e..0e75936277 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -53,8 +53,8 @@ GEM rake (>= 10.4, < 14.0) ast (2.4.2) aws-eventstream (1.2.0) - aws-partitions (1.432.0) - aws-sdk-core (3.112.1) + aws-partitions (1.516.0) + aws-sdk-core (3.121.2) aws-eventstream (~> 1, >= 1.0.2) aws-partitions (~> 1, >= 1.239.0) aws-sigv4 (~> 1.1) @@ -66,10 +66,10 @@ GEM aws-sdk-core (~> 3, >= 3.112.0) aws-sdk-kms (~> 1) aws-sigv4 (~> 1.1) - aws-sdk-sns (1.38.0) - aws-sdk-core (~> 3, >= 3.112.0) + aws-sdk-sns (1.46.0) + aws-sdk-core (~> 3, >= 3.121.2) aws-sigv4 (~> 1.1) - aws-sigv4 (1.2.3) + aws-sigv4 (1.4.0) aws-eventstream (~> 1, >= 1.0.2) barber (0.12.2) ember-source (>= 1.0, < 3.1) @@ -80,7 +80,7 @@ GEM rack (>= 0.9.0) binding_of_caller (1.0.0) debug_inspector (>= 0.0.1) - bootsnap (1.8.1) + bootsnap (1.9.1) msgpack (~> 1.0) builder (3.2.4) bullet (6.1.5) @@ -129,14 +129,14 @@ GEM sprockets (>= 3.3, < 4.1) ember-source (2.18.2) erubi (1.10.0) - excon (0.85.0) + excon (0.87.0) execjs (2.8.1) exifr (1.3.9) fabrication (2.22.0) faker (2.19.0) i18n (>= 1.6, < 2) fakeweb (1.3.0) - faraday (1.7.1) + faraday (1.8.0) faraday-em_http (~> 1.0) faraday-em_synchrony (~> 1.0) faraday-excon (~> 1.1) @@ -173,7 +173,7 @@ GEM http_accept_language (2.1.1) i18n (1.8.10) concurrent-ruby (~> 1.0) - image_optim (0.30.0) + image_optim (0.31.0) exifr (~> 1.2, >= 1.2.2) fspath (~> 3.0) image_size (>= 1.5, < 3) @@ -181,12 +181,13 @@ GEM progress (~> 3.0, >= 3.0.1) image_size (2.1.2) in_threads (1.5.4) + ipaddr (1.2.2) jmespath (1.4.0) jquery-rails (4.4.0) rails-dom-testing (>= 1, < 3) railties (>= 4.2.0) thor (>= 0.14, < 2.0) - json (2.5.1) + json (2.6.0) json-schema (2.8.1) addressable (>= 2.4) json_schemer (0.2.18) @@ -194,7 +195,7 @@ GEM hana (~> 1.3) regexp_parser (~> 2.0) uri_template (~> 0.7) - jwt (2.2.3) + jwt (2.3.0) kgio (2.11.4) libv8-node (15.14.0.1) libv8-node (15.14.0.1-arm64-darwin-20) @@ -225,7 +226,7 @@ GEM message_bus (3.3.6) rack (>= 1.1.3) method_source (1.0.0) - mini_mime (1.1.1) + mini_mime (1.1.2) mini_portile2 (2.6.1) mini_racer (0.4.0) libv8-node (~> 15.14.0.0) @@ -244,14 +245,14 @@ GEM multipart-post (2.1.1) mustache (1.1.1) nio4r (2.5.8) - nokogiri (1.12.4) + nokogiri (1.12.5) mini_portile2 (~> 2.6.1) racc (~> 1.4) - nokogiri (1.12.4-arm64-darwin) + nokogiri (1.12.5-arm64-darwin) racc (~> 1.4) - nokogiri (1.12.4-x86_64-darwin) + nokogiri (1.12.5-x86_64-darwin) racc (~> 1.4) - nokogiri (1.12.4-x86_64-linux) + nokogiri (1.12.5-x86_64-linux) racc (~> 1.4) oauth (0.5.6) oauth2 (1.4.7) @@ -283,12 +284,13 @@ GEM omniauth-twitter (1.4.0) omniauth-oauth (~> 1.1) rack - openssl (2.2.0) + openssl (2.2.1) + ipaddr openssl-signature_algorithm (1.1.1) openssl (~> 2.0) optimist (3.0.1) - parallel (1.20.1) - parallel_tests (3.7.1) + parallel (1.21.0) + parallel_tests (3.7.3) parallel parser (3.0.2.0) ast (~> 2.4.1) @@ -303,10 +305,10 @@ GEM pry-rails (0.3.9) pry (>= 0.10.4) public_suffix (4.0.6) - puma (5.4.0) + puma (5.5.2) nio4r (~> 2.0) r2 (0.2.7) - racc (1.5.2) + racc (1.6.0) rack (2.2.3) rack-mini-profiler (2.3.3) rack (>= 1.2.0) @@ -323,7 +325,7 @@ GEM activerecord (~> 6.0) concurrent-ruby railties (~> 6.0) - rails_multisite (3.0.0) + rails_multisite (3.1.0) activerecord (> 5.0, < 7) railties (> 5.0, < 7) railties (6.1.4.1) @@ -343,7 +345,7 @@ GEM msgpack (>= 0.4.3) optimist (>= 3.0.0) rchardet (1.8.0) - redis (4.4.0) + redis (4.5.1) redis-namespace (1.8.1) redis (>= 3.0.4) regexp_parser (2.1.1) @@ -380,28 +382,29 @@ GEM rspec-mocks (~> 3.10) rspec-support (~> 3.10) rspec-support (3.10.2) + rss (0.2.9) + rexml rswag-specs (2.4.0) activesupport (>= 3.1, < 7.0) json-schema (~> 2.2) railties (>= 3.1, < 7.0) rtlit (0.0.5) - rubocop (1.20.0) + rubocop (1.22.1) parallel (~> 1.10) parser (>= 3.0.0.0) rainbow (>= 2.2.2, < 4.0) regexp_parser (>= 1.8, < 3.0) rexml - rubocop-ast (>= 1.9.1, < 2.0) + rubocop-ast (>= 1.12.0, < 2.0) ruby-progressbar (~> 1.7) unicode-display_width (>= 1.4.0, < 3.0) - rubocop-ast (1.11.0) + rubocop-ast (1.12.0) parser (>= 3.0.1.1) rubocop-discourse (2.4.2) rubocop (>= 1.1.0) rubocop-rspec (>= 2.0.0) - rubocop-rspec (2.4.0) - rubocop (~> 1.0) - rubocop-ast (>= 1.1.0) + rubocop-rspec (2.5.0) + rubocop (~> 1.19) ruby-prof (1.4.3) ruby-progressbar (1.11.0) ruby-readability (0.7.0) @@ -454,8 +457,8 @@ GEM execjs (>= 0.3.0, < 3) unf (0.1.4) unf_ext - unf_ext (0.0.7.7) - unicode-display_width (2.0.0) + unf_ext (0.0.8) + unicode-display_width (2.1.0) unicorn (6.0.0) kgio (~> 2.6) raindrops (~> 0.7) @@ -581,6 +584,7 @@ DEPENDENCIES rspec rspec-html-matchers rspec-rails + rss rswag-specs rtlit rubocop-discourse diff --git a/README.md b/README.md index 1410ab5057..cdd241f27d 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,6 @@ To learn more about the philosophy and goals of the project, [visit **discourse. Boing Boing - diff --git a/app/assets/javascripts/admin/addon/components/admin-report-chart.js b/app/assets/javascripts/admin/addon/components/admin-report-chart.js index 7711ca8d3d..1c4ad1caa3 100644 --- a/app/assets/javascripts/admin/addon/components/admin-report-chart.js +++ b/app/assets/javascripts/admin/addon/components/admin-report-chart.js @@ -5,6 +5,7 @@ import loadScript from "discourse/lib/load-script"; import { makeArray } from "discourse-common/lib/helpers"; import { number } from "discourse/lib/formatter"; import { schedule } from "@ember/runloop"; +import { bind } from "discourse-common/utils/decorators"; export default Component.extend({ classNames: ["admin-report-chart"], @@ -12,23 +13,16 @@ export default Component.extend({ total: 0, options: null, - init() { - this._super(...arguments); - - this.resizeHandler = () => - discourseDebounce(this, this._scheduleChartRendering, 500); - }, - didInsertElement() { this._super(...arguments); - $(window).on("resize.chart", this.resizeHandler); + window.addEventListener("resize", this._resizeHandler); }, willDestroyElement() { this._super(...arguments); - $(window).off("resize.chart", this.resizeHandler); + window.removeEventListener("resize", this._resizeHandler); this._resetChart(); }, @@ -179,4 +173,9 @@ export default Component.extend({ _applyChartGrouping(model, data, options) { return Report.collapse(model, data, options.chartGrouping); }, + + @bind + _resizeHandler() { + discourseDebounce(this, this._scheduleChartRendering, 500); + }, }); diff --git a/app/assets/javascripts/admin/addon/components/embeddable-host.js b/app/assets/javascripts/admin/addon/components/embeddable-host.js index a0f546227b..328ad06e1e 100644 --- a/app/assets/javascripts/admin/addon/components/embeddable-host.js +++ b/app/assets/javascripts/admin/addon/components/embeddable-host.js @@ -12,9 +12,20 @@ export default Component.extend(bufferedProperty("host"), { editToggled: false, tagName: "tr", categoryId: null, + category: null, editing: or("host.isNew", "editToggled"), + init() { + this._super(...arguments); + + const host = this.host; + const categoryId = host.category_id || this.site.uncategorized_category_id; + const category = Category.findById(categoryId); + + host.set("category", category); + }, + @discourseComputed("buffered.host", "host.isSaving") cantSave(host, isSaving) { return isSaving || isEmpty(host); diff --git a/app/assets/javascripts/admin/addon/components/screened-ip-address-form.js b/app/assets/javascripts/admin/addon/components/screened-ip-address-form.js index 5636c302db..95ed9edfe8 100644 --- a/app/assets/javascripts/admin/addon/components/screened-ip-address-form.js +++ b/app/assets/javascripts/admin/addon/components/screened-ip-address-form.js @@ -17,7 +17,7 @@ import { schedule } from "@ember/runloop"; **/ export default Component.extend({ - classNames: ["screened-ip-address-form"], + classNames: ["screened-ip-address-form", "inline-form"], formSubmitted: false, actionName: "block", diff --git a/app/assets/javascripts/admin/addon/components/site-settings-image-uploader.js b/app/assets/javascripts/admin/addon/components/site-settings-image-uploader.js deleted file mode 100644 index 3e19fa8f03..0000000000 --- a/app/assets/javascripts/admin/addon/components/site-settings-image-uploader.js +++ /dev/null @@ -1,6 +0,0 @@ -import ImageUploader from "discourse/components/image-uploader"; - -export default ImageUploader.extend({ - layoutName: "components/image-uploader", - uploadUrlParams: "&for_site_setting=true", -}); diff --git a/app/assets/javascripts/admin/addon/controllers/admin-api-keys-new.js b/app/assets/javascripts/admin/addon/controllers/admin-api-keys-new.js index c90316369a..961c1a5c4b 100644 --- a/app/assets/javascripts/admin/addon/controllers/admin-api-keys-new.js +++ b/app/assets/javascripts/admin/addon/controllers/admin-api-keys-new.js @@ -3,70 +3,83 @@ import I18n from "I18n"; import discourseComputed from "discourse-common/utils/decorators"; import { isBlank } from "@ember/utils"; import { popupAjaxError } from "discourse/lib/ajax-error"; -import { get } from "@ember/object"; +import { action, get } from "@ember/object"; +import { equal } from "@ember/object/computed"; import showModal from "discourse/lib/show-modal"; +import { ajax } from "discourse/lib/ajax"; export default Controller.extend({ - userModes: [ - { id: "all", name: I18n.t("admin.api.all_users") }, - { id: "single", name: I18n.t("admin.api.single_user") }, - ], + userModes: null, useGlobalKey: false, scopes: null, - @discourseComputed("userMode") - showUserSelector(mode) { - return mode === "single"; + init() { + this._super(...arguments); + + this.set("userModes", [ + { id: "all", name: I18n.t("admin.api.all_users") }, + { id: "single", name: I18n.t("admin.api.single_user") }, + ]); + this._loadScopes(); }, - @discourseComputed("model.description", "model.username", "userMode") - saveDisabled(description, username, userMode) { - if (isBlank(description)) { + showUserSelector: equal("userMode", "single"), + + @discourseComputed("model.{description,username}", "showUserSelector") + saveDisabled(model, showUserSelector) { + if (isBlank(model.description)) { return true; } - if (userMode === "single" && isBlank(username)) { + if (showUserSelector && isBlank(model.username)) { return true; } return false; }, - actions: { - updateUsername(selected) { - this.set("model.username", get(selected, "firstObject")); - }, + @action + updateUsername(selected) { + this.set("model.username", get(selected, "firstObject")); + }, - changeUserMode(value) { - if (value === "all") { - this.model.set("username", null); - } - this.set("userMode", value); - }, + @action + changeUserMode(userMode) { + if (userMode === "all") { + this.model.set("username", null); + } + this.set("userMode", userMode); + }, - save() { - if (!this.useGlobalKey) { - const selectedScopes = Object.values(this.scopes) - .flat() - .filter((action) => { - return action.selected; - }); + @action + save() { + if (!this.useGlobalKey) { + const selectedScopes = Object.values(this.scopes) + .flat() + .filterBy("selected"); - this.model.set("scopes", selectedScopes); - } + this.model.set("scopes", selectedScopes); + } - this.model.save().catch(popupAjaxError); - }, + return this.model.save().catch(popupAjaxError); + }, - continue() { - this.transitionToRoute("adminApiKeys.show", this.model.id); - }, + @action + continue() { + this.transitionToRoute("adminApiKeys.show", this.model.id); + }, - showURLs(urls) { - return showModal("admin-api-key-urls", { - admin: true, - model: { - urls, - }, - }); - }, + @action + showURLs(urls) { + return showModal("admin-api-key-urls", { + admin: true, + model: { urls }, + }); + }, + + _loadScopes() { + return ajax("/admin/api/keys/scopes.json") + .then((data) => { + this.set("scopes", data.scopes); + }) + .catch(popupAjaxError); }, }); diff --git a/app/assets/javascripts/admin/addon/controllers/admin-customize-themes-show.js b/app/assets/javascripts/admin/addon/controllers/admin-customize-themes-show.js index 000e1d3fc8..959151d96e 100644 --- a/app/assets/javascripts/admin/addon/controllers/admin-customize-themes-show.js +++ b/app/assets/javascripts/admin/addon/controllers/admin-customize-themes-show.js @@ -146,7 +146,9 @@ export default Controller.extend({ @discourseComputed("model.translations") translations(translations) { - return translations.map((setting) => ThemeSettings.create(setting)); + return translations.map((setting) => + ThemeSettings.create({ ...setting, textarea: true }) + ); }, hasTranslations: notEmpty("translations"), diff --git a/app/assets/javascripts/admin/addon/controllers/admin-user-index.js b/app/assets/javascripts/admin/addon/controllers/admin-user-index.js index 93ed9c306f..22345e8b82 100644 --- a/app/assets/javascripts/admin/addon/controllers/admin-user-index.js +++ b/app/assets/javascripts/admin/addon/controllers/admin-user-index.js @@ -218,8 +218,15 @@ export default Controller.extend(CanCheckEmails, { grantAdmin() { return this.model .grantAdmin() - .then(() => { - bootbox.alert(I18n.t("admin.user.grant_admin_confirm")); + .then((result) => { + if (result.email_confirmation_required) { + bootbox.alert(I18n.t("admin.user.grant_admin_confirm")); + } else { + const controller = showModal("grant-admin-second-factor", { + model: this.model, + }); + controller.setResult(result); + } }) .catch(popupAjaxError); }, diff --git a/app/assets/javascripts/admin/addon/mixins/setting-component.js b/app/assets/javascripts/admin/addon/mixins/setting-component.js index b55c045593..9585168210 100644 --- a/app/assets/javascripts/admin/addon/mixins/setting-component.js +++ b/app/assets/javascripts/admin/addon/mixins/setting-component.js @@ -1,14 +1,18 @@ +import { isNone } from "@ember/utils"; +import { fmt, propertyNotEqual } from "discourse/lib/computed"; import { alias, oneWay } from "@ember/object/computed"; import I18n from "I18n"; import Mixin from "@ember/object/mixin"; import { Promise } from "rsvp"; import { ajax } from "discourse/lib/ajax"; import { categoryLinkHTML } from "discourse/helpers/category-link"; -import discourseComputed from "discourse-common/utils/decorators"; +import discourseComputed, { bind } from "discourse-common/utils/decorators"; import { htmlSafe } from "@ember/template"; import { on } from "@ember/object/evented"; import showModal from "discourse/lib/show-modal"; import { warn } from "@ember/debug"; +import { action } from "@ember/object"; +import { splitString } from "discourse/lib/utilities"; const CUSTOM_TYPES = [ "bool", @@ -32,26 +36,20 @@ const CUSTOM_TYPES = [ const AUTO_REFRESH_ON_SAVE = ["logo", "logo_small", "large_icon"]; -function splitPipes(str) { - if (typeof str === "string") { - return str.split("|").filter(Boolean); - } else { - return []; - } -} - export default Mixin.create({ classNameBindings: [":row", ":setting", "overridden", "typeClass"], content: alias("setting"), validationMessage: null, isSecret: oneWay("setting.secret"), + setting: null, @discourseComputed("buffered.value", "setting.value") dirty(bufferVal, settingVal) { - if (bufferVal === null || bufferVal === undefined) { + if (isNone(bufferVal)) { bufferVal = ""; } - if (settingVal === null || settingVal === undefined) { + + if (isNone(settingVal)) { settingVal = ""; } @@ -61,21 +59,17 @@ export default Mixin.create({ @discourseComputed("setting", "buffered.value") preview(setting, value) { // A bit hacky, but allows us to use helpers - if (setting.get("setting") === "category_style") { - let category = this.site.get("categories.firstObject"); + if (setting.setting === "category_style") { + const category = this.site.get("categories.firstObject"); if (category) { - return categoryLinkHTML(category, { - categoryStyle: value, - }); + return categoryLinkHTML(category, { categoryStyle: value }); } } - let preview = setting.get("preview"); + + const preview = setting.preview; if (preview) { - return htmlSafe( - "
" + - preview.replace(/\{\{value\}\}/g, value) + - "
" - ); + const escapedValue = preview.replace(/\{\{value\}\}/g, value); + return htmlSafe(`
${escapedValue}
`); } }, @@ -103,51 +97,156 @@ export default Mixin.create({ return setting.type; }, - @discourseComputed("typeClass") - componentName(typeClass) { - return "site-settings/" + typeClass; - }, + componentName: fmt("typeClass", "site-settings/%@"), @discourseComputed("setting.anyValue") allowAny(anyValue) { return anyValue !== false; }, - @discourseComputed("setting.default", "buffered.value") - overridden(settingDefault, bufferedValue) { - return settingDefault !== bufferedValue; - }, + overridden: propertyNotEqual("setting.default", "buffered.value"), @discourseComputed("buffered.value") - bufferedValues: splitPipes, + bufferedValues(value) { + return splitString(value, "|"); + }, @discourseComputed("setting.defaultValues") - defaultValues: splitPipes, + defaultValues(value) { + return splitString(value, "|"); + }, @discourseComputed("defaultValues", "bufferedValues") defaultIsAvailable(defaultValues, bufferedValues) { return ( - defaultValues && defaultValues.length > 0 && !defaultValues.every((value) => bufferedValues.includes(value)) ); }, - _watchEnterKey: on("didInsertElement", function () { - $(this.element).on( - "keydown.setting-enter", - ".input-setting-string", - (e) => { - if (e.key === "Enter") { - // enter key - this.send("save"); + @action + update() { + const defaultUserPreferences = [ + "default_email_digest_frequency", + "default_include_tl0_in_digests", + "default_email_level", + "default_email_messages_level", + "default_email_mailing_list_mode", + "default_email_mailing_list_mode_frequency", + "default_email_previous_replies", + "default_email_in_reply_to", + "default_other_new_topic_duration_minutes", + "default_other_auto_track_topics_after_msecs", + "default_other_notification_level_when_replying", + "default_other_external_links_in_new_tab", + "default_other_enable_quoting", + "default_other_enable_defer", + "default_other_dynamic_favicon", + "default_other_like_notification_frequency", + "default_other_skip_new_user_tips", + "default_topics_automatic_unpin", + "default_categories_watching", + "default_categories_tracking", + "default_categories_muted", + "default_categories_watching_first_post", + "default_categories_regular", + "default_tags_watching", + "default_tags_tracking", + "default_tags_muted", + "default_tags_watching_first_post", + "default_text_size", + "default_title_count_mode", + ]; + const key = this.buffered.get("setting"); + + if (defaultUserPreferences.includes(key)) { + const data = {}; + data[key] = this.buffered.get("value"); + + ajax(`/admin/site_settings/${key}/user_count.json`, { + type: "PUT", + data, + }).then((result) => { + const count = result.user_count; + + if (count > 0) { + const controller = showModal("site-setting-default-categories", { + model: { count, key: key.replaceAll("_", " ") }, + admin: true, + }); + + controller.set("onClose", () => { + this.updateExistingUsers = controller.updateExistingUsers; + this.save(); + }); + } else { + this.save(); } - } + }); + } else { + this.save(); + } + }, + + @action + save() { + this._save() + .then(() => { + this.set("validationMessage", null); + this.commitBuffer(); + if (AUTO_REFRESH_ON_SAVE.includes(this.setting.setting)) { + this.afterSave(); + } + }) + .catch((e) => { + if (e.jqXHR?.responseJSON?.errors) { + this.set("validationMessage", e.jqXHR.responseJSON.errors[0]); + } else { + this.set("validationMessage", I18n.t("generic_error")); + } + }); + }, + + @action + cancel() { + this.rollbackBuffer(); + }, + + @action + resetDefault() { + this.set("buffered.value", this.get("setting.default")); + }, + + @action + toggleSecret() { + this.toggleProperty("isSecret"); + }, + + @action + setDefaultValues() { + this.set( + "buffered.value", + this.bufferedValues.concat(this.defaultValues).uniq().join("|") ); + return false; + }, + + @bind + _handleKeydown(event) { + if ( + event.key === "Enter" && + event.target.classList.contains("input-setting-string") + ) { + this.save(); + } + }, + + _watchEnterKey: on("didInsertElement", function () { + this.element.addEventListener("keydown", this._handleKeydown); }), _removeBindings: on("willDestroyElement", function () { - $(this.element).off("keydown.setting-enter"); + this.element.removeEventListener("keydown", this._handleKeydown); }), _save() { @@ -156,110 +255,4 @@ export default Mixin.create({ }); return Promise.resolve(); }, - - actions: { - update() { - const defaultUserPreferences = [ - "default_email_digest_frequency", - "default_include_tl0_in_digests", - "default_email_level", - "default_email_messages_level", - "default_email_mailing_list_mode", - "default_email_mailing_list_mode_frequency", - "default_email_previous_replies", - "default_email_in_reply_to", - "default_other_new_topic_duration_minutes", - "default_other_auto_track_topics_after_msecs", - "default_other_notification_level_when_replying", - "default_other_external_links_in_new_tab", - "default_other_enable_quoting", - "default_other_enable_defer", - "default_other_dynamic_favicon", - "default_other_like_notification_frequency", - "default_other_skip_new_user_tips", - "default_topics_automatic_unpin", - "default_categories_watching", - "default_categories_tracking", - "default_categories_muted", - "default_categories_watching_first_post", - "default_categories_regular", - "default_tags_watching", - "default_tags_tracking", - "default_tags_muted", - "default_tags_watching_first_post", - "default_text_size", - "default_title_count_mode", - ]; - const key = this.buffered.get("setting"); - - if (defaultUserPreferences.includes(key)) { - const data = {}; - data[key] = this.buffered.get("value"); - - ajax(`/admin/site_settings/${key}/user_count.json`, { - type: "PUT", - data, - }).then((result) => { - const count = result.user_count; - - if (count > 0) { - const controller = showModal("site-setting-default-categories", { - model: { - count: result.user_count, - key: key.replace(/_/g, " "), - }, - admin: true, - }); - - controller.set("onClose", () => { - this.updateExistingUsers = controller.updateExistingUsers; - this.send("save"); - }); - } else { - this.send("save"); - } - }); - } else { - this.send("save"); - } - }, - - save() { - this._save() - .then(() => { - this.set("validationMessage", null); - this.commitBuffer(); - if (AUTO_REFRESH_ON_SAVE.includes(this.setting.setting)) { - this.afterSave(); - } - }) - .catch((e) => { - if (e.jqXHR.responseJSON && e.jqXHR.responseJSON.errors) { - this.set("validationMessage", e.jqXHR.responseJSON.errors[0]); - } else { - this.set("validationMessage", I18n.t("generic_error")); - } - }); - }, - - cancel() { - this.rollbackBuffer(); - }, - - resetDefault() { - this.set("buffered.value", this.get("setting.default")); - }, - - toggleSecret() { - this.toggleProperty("isSecret"); - }, - - setDefaultValues() { - this.set( - "buffered.value", - this.bufferedValues.concat(this.defaultValues).uniq().join("|") - ); - return false; - }, - }, }); diff --git a/app/assets/javascripts/admin/addon/models/admin-user.js b/app/assets/javascripts/admin/addon/models/admin-user.js index cfd34b87e3..3fb3e28dad 100644 --- a/app/assets/javascripts/admin/addon/models/admin-user.js +++ b/app/assets/javascripts/admin/addon/models/admin-user.js @@ -99,9 +99,20 @@ const AdminUser = User.extend({ }); }, - grantAdmin() { + grantAdmin(data) { return ajax(`/admin/users/${this.id}/grant_admin`, { type: "PUT", + data, + }).then((resp) => { + if (resp.success && !resp.email_confirmation_required) { + this.setProperties({ + admin: true, + can_grant_admin: false, + can_revoke_admin: true, + }); + } + + return resp; }); }, diff --git a/app/assets/javascripts/admin/addon/routes/admin-api-keys-new.js b/app/assets/javascripts/admin/addon/routes/admin-api-keys-new.js index af26a30848..cc375b0cc3 100644 --- a/app/assets/javascripts/admin/addon/routes/admin-api-keys-new.js +++ b/app/assets/javascripts/admin/addon/routes/admin-api-keys-new.js @@ -1,17 +1,7 @@ import Route from "@ember/routing/route"; -import { ajax } from "discourse/lib/ajax"; export default Route.extend({ model() { return this.store.createRecord("api-key"); }, - - setupController(controller, model) { - ajax("/admin/api/keys/scopes.json").then((data) => { - controller.setProperties({ - scopes: data.scopes, - model, - }); - }); - }, }); diff --git a/app/assets/javascripts/admin/addon/routes/admin-user-index.js b/app/assets/javascripts/admin/addon/routes/admin-user-index.js index d3a426bd60..a6b329ac16 100644 --- a/app/assets/javascripts/admin/addon/routes/admin-user-index.js +++ b/app/assets/javascripts/admin/addon/routes/admin-user-index.js @@ -20,6 +20,8 @@ export default DiscourseRoute.extend({ originalPrimaryGroupId: model.primary_group_id, availableGroups: this._availableGroups, customGroupIdsBuffer: model.customGroups.mapBy("id"), + ssoExternalEmail: null, + ssoLastPayload: null, model, }); }, diff --git a/app/assets/javascripts/admin/addon/templates/api-keys-new.hbs b/app/assets/javascripts/admin/addon/templates/api-keys-new.hbs index a8957262df..2b9e40cf80 100644 --- a/app/assets/javascripts/admin/addon/templates/api-keys-new.hbs +++ b/app/assets/javascripts/admin/addon/templates/api-keys-new.hbs @@ -1,6 +1,6 @@ {{#link-to "adminApiKeys.index" class="go-back"}} {{d-icon "arrow-left"}} - {{i18n "admin.api.all_api_keys"}} + {{i18n "admin.api.all_api_keys"}} {{/link-to}}
diff --git a/app/assets/javascripts/admin/addon/templates/badges-award.hbs b/app/assets/javascripts/admin/addon/templates/badges-award.hbs index 52bed37623..1020f6369d 100644 --- a/app/assets/javascripts/admin/addon/templates/badges-award.hbs +++ b/app/assets/javascripts/admin/addon/templates/badges-award.hbs @@ -4,7 +4,7 @@ {{#if model}}
-
+
{{#if model}} {{icon-or-image model}} {{model.name}} @@ -12,11 +12,11 @@ {{i18n "admin.badges.mass_award.no_badge_selected"}} {{/if}}
-
+

{{i18n "admin.badges.mass_award.upload_csv"}}

-
+