<%= t('login.second_factor_title') %>
-- <%=form_tag({}, method: :put) do %> - <%= label_tag(:second_factor_token, t('login.second_factor_description')) %> -
diff --git a/.rubocop.yml b/.rubocop.yml index 55f8d548a8..eebd794151 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,13 +1,16 @@ +require: + - rubocop-discourse + AllCops: TargetRubyVersion: 2.4 DisabledByDefault: true Exclude: - - 'db/schema.rb' - - 'bundle/**/*' - - 'vendor/**/*' - - 'node_modules/**/*' - - 'public/**/*' - - 'plugins/**/gems/**/*' + - "db/schema.rb" + - "bundle/**/*" + - "vendor/**/*" + - "node_modules/**/*" + - "public/**/*" + - "plugins/**/gems/**/*" # Prefer &&/|| over and/or. Style/AndOr: @@ -57,7 +60,7 @@ Layout/SpaceAroundOperators: Enabled: true Layout/SpaceBeforeFirstArg: - Enabled: true + Enabled: true # Defining a method with parameters needs parentheses. Style/MethodDefParentheses: @@ -126,6 +129,15 @@ Style/Semicolon: Enabled: true AllowAsExpressionSeparator: true +Style/RedundantReturn: + Enabled: true + +DiscourseCops/NoChdir: + Enabled: true + Exclude: + - 'spec/**/*' # Specs are run sequentially, so chdir can be used + - 'plugins/*/spec/**/*' + Style/GlobalVars: Enabled: true Severity: warning diff --git a/Gemfile b/Gemfile index f728eef550..5a77669c3c 100644 --- a/Gemfile +++ b/Gemfile @@ -16,13 +16,13 @@ if rails_master? else # until rubygems gives us optional dependencies we are stuck with this # bundle update actionmailer actionpack actionview activemodel activerecord activesupport railties - gem 'actionmailer', '6.0.0' - gem 'actionpack', '6.0.0' - gem 'actionview', '6.0.0' - gem 'activemodel', '6.0.0' - gem 'activerecord', '6.0.0' - gem 'activesupport', '6.0.0' - gem 'railties', '6.0.0' + gem 'actionmailer', '6.0.1' + gem 'actionpack', '6.0.1' + gem 'actionview', '6.0.1' + gem 'activemodel', '6.0.1' + gem 'activerecord', '6.0.1' + gem 'activesupport', '6.0.1' + gem 'railties', '6.0.1' gem 'sprockets-rails' end @@ -41,7 +41,7 @@ gem 'redis-namespace' gem 'active_model_serializers', '~> 0.8.3' -gem 'onebox', '1.9.21' +gem 'onebox', '1.9.24' gem 'http_accept_language', '~>2.0.5', require: false @@ -143,6 +143,7 @@ group :test, :development do gem 'pry-nav' gem 'byebug', require: ENV['RM_INFO'].nil? gem 'rubocop', require: false + gem "rubocop-discourse", require: false gem 'parallel_tests' end diff --git a/Gemfile.lock b/Gemfile.lock index 4176a30f6e..213187d108 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,21 +1,21 @@ GEM remote: https://rubygems.org/ specs: - actionmailer (6.0.0) - actionpack (= 6.0.0) - actionview (= 6.0.0) - activejob (= 6.0.0) + actionmailer (6.0.1) + actionpack (= 6.0.1) + actionview (= 6.0.1) + activejob (= 6.0.1) mail (~> 2.5, >= 2.5.4) rails-dom-testing (~> 2.0) - actionpack (6.0.0) - actionview (= 6.0.0) - activesupport (= 6.0.0) + actionpack (6.0.1) + actionview (= 6.0.1) + activesupport (= 6.0.1) rack (~> 2.0) rack-test (>= 0.6.3) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.0, >= 1.2.0) - actionview (6.0.0) - activesupport (= 6.0.0) + actionview (6.0.1) + activesupport (= 6.0.1) builder (~> 3.1) erubi (~> 1.4) rails-dom-testing (~> 2.0) @@ -24,20 +24,20 @@ GEM actionview (>= 6.0.a) active_model_serializers (0.8.4) activemodel (>= 3.0) - activejob (6.0.0) - activesupport (= 6.0.0) + activejob (6.0.1) + activesupport (= 6.0.1) globalid (>= 0.3.6) - activemodel (6.0.0) - activesupport (= 6.0.0) - activerecord (6.0.0) - activemodel (= 6.0.0) - activesupport (= 6.0.0) - activesupport (6.0.0) + activemodel (6.0.1) + activesupport (= 6.0.1) + activerecord (6.0.1) + activemodel (= 6.0.1) + activesupport (= 6.0.1) + activesupport (6.0.1) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (>= 0.7, < 2) minitest (~> 5.1) tzinfo (~> 1.1) - zeitwerk (~> 2.1, >= 2.1.8) + zeitwerk (~> 2.2) addressable (2.5.2) public_suffix (>= 2.0.2, < 4.0) annotate (2.7.5) @@ -72,7 +72,7 @@ GEM rack (>= 0.9.0) binding_of_caller (0.8.0) debug_inspector (>= 0.0.1) - bootsnap (1.4.4) + bootsnap (1.4.5) msgpack (~> 1.0) builder (3.2.3) bullet (6.0.0) @@ -91,7 +91,7 @@ GEM cppjieba_rb (0.3.3) crack (0.4.3) safe_yaml (~> 1.0.0) - crass (1.0.4) + crass (1.0.5) css_parser (1.7.0) addressable debug_inspector (0.0.3) @@ -119,7 +119,7 @@ GEM jquery-rails (>= 1.0.17) railties (>= 3.1) ember-source (2.18.2) - erubi (1.8.0) + erubi (1.9.0) excon (0.64.0) execjs (2.7.0) exifr (1.3.6) @@ -146,7 +146,7 @@ GEM hkdf (0.3.0) htmlentities (4.3.4) http_accept_language (2.0.5) - i18n (1.6.0) + i18n (1.7.0) concurrent-ruby (~> 1.0) image_size (1.5.0) in_threads (1.5.1) @@ -173,7 +173,7 @@ GEM logstash-logger (0.26.1) logstash-event (~> 1.2) logster (2.4.1) - loofah (2.2.3) + loofah (2.3.1) crass (~> 1.0.2) nokogiri (>= 1.5.9) lru_redux (1.1.0) @@ -188,23 +188,23 @@ GEM method_source (0.9.2) mini_mime (1.0.2) mini_portile2 (2.4.0) - mini_racer (0.2.6) + mini_racer (0.2.8) libv8 (>= 6.9.411) mini_scheduler (0.12.2) sidekiq mini_sql (0.2.2) mini_suffix (0.3.0) ffi (~> 1.9) - minitest (5.11.3) + minitest (5.13.0) mocha (1.8.0) metaclass (~> 0.0.1) mock_redis (0.19.0) - msgpack (1.2.10) + msgpack (1.3.1) multi_json (1.13.1) multi_xml (0.6.0) multipart-post (2.1.1) mustache (1.1.0) - nokogiri (1.10.4) + nokogiri (1.10.5) mini_portile2 (~> 2.4.0) nokogumbo (2.0.1) nokogiri (~> 1.8, >= 1.8.4) @@ -243,7 +243,7 @@ GEM omniauth-twitter (1.4.0) omniauth-oauth (~> 1.1) rack - onebox (1.9.21) + onebox (1.9.24) htmlentities (~> 4.3) multi_json (~> 1.11) mustache @@ -283,14 +283,14 @@ GEM rails-dom-testing (2.0.3) activesupport (>= 4.2.0) nokogiri (>= 1.6) - rails-html-sanitizer (1.2.0) - loofah (~> 2.2, >= 2.2.2) + rails-html-sanitizer (1.3.0) + loofah (~> 2.3) rails_multisite (2.0.7) activerecord (> 4.2, < 7) railties (> 4.2, < 7) - railties (6.0.0) - actionpack (= 6.0.0) - activesupport (= 6.0.0) + railties (6.0.1) + actionpack (= 6.0.1) + activesupport (= 6.0.1) method_source rake (>= 0.8.7) thor (>= 0.20.3, < 2.0) @@ -348,6 +348,8 @@ GEM rainbow (>= 2.2.2, < 4.0) ruby-progressbar (~> 1.7) unicode-display_width (>= 1.4.0, < 1.7) + rubocop-discourse (1.0.1) + rubocop (>= 0.69.0) ruby-openid (2.7.0) ruby-prof (0.17.0) ruby-progressbar (1.10.0) @@ -418,20 +420,20 @@ GEM hkdf (~> 0.2) jwt (~> 2.0) yaml-lint (0.0.10) - zeitwerk (2.1.10) + zeitwerk (2.2.1) PLATFORMS ruby DEPENDENCIES - actionmailer (= 6.0.0) - actionpack (= 6.0.0) - actionview (= 6.0.0) + actionmailer (= 6.0.1) + actionpack (= 6.0.1) + actionview (= 6.0.1) actionview_precompiler active_model_serializers (~> 0.8.3) - activemodel (= 6.0.0) - activerecord (= 6.0.0) - activesupport (= 6.0.0) + activemodel (= 6.0.1) + activerecord (= 6.0.1) + activesupport (= 6.0.1) annotate aws-sdk-s3 aws-sdk-sns @@ -497,7 +499,7 @@ DEPENDENCIES omniauth-oauth2 omniauth-openid omniauth-twitter - onebox (= 1.9.21) + onebox (= 1.9.24) openid-redis-store parallel_tests pg @@ -508,7 +510,7 @@ DEPENDENCIES rack-mini-profiler rack-protection rails_multisite - railties (= 6.0.0) + railties (= 6.0.1) rake rb-fsevent rb-inotify (~> 0.9) @@ -524,6 +526,7 @@ DEPENDENCIES rspec-rails (= 4.0.0.beta2) rtlit rubocop + rubocop-discourse ruby-prof ruby-readability rubyzip diff --git a/app/assets/javascripts/admin/components/ace-editor.js.es6 b/app/assets/javascripts/admin/components/ace-editor.js.es6 index 60498a0bdb..4fc881dae4 100644 --- a/app/assets/javascripts/admin/components/ace-editor.js.es6 +++ b/app/assets/javascripts/admin/components/ace-editor.js.es6 @@ -1,6 +1,6 @@ import Component from "@ember/component"; import loadScript from "discourse/lib/load-script"; -import { observes } from "ember-addons/ember-computed-decorators"; +import { observes } from "discourse-common/utils/decorators"; import { on } from "@ember/object/evented"; export default Component.extend({ diff --git a/app/assets/javascripts/admin/components/admin-backups-logs.js.es6 b/app/assets/javascripts/admin/components/admin-backups-logs.js.es6 index ccacb2e705..316597e0f7 100644 --- a/app/assets/javascripts/admin/components/admin-backups-logs.js.es6 +++ b/app/assets/javascripts/admin/components/admin-backups-logs.js.es6 @@ -1,10 +1,10 @@ import { scheduleOnce } from "@ember/runloop"; import Component from "@ember/component"; -import debounce from "discourse/lib/debounce"; +import discourseDebounce from "discourse/lib/debounce"; import { renderSpinner } from "discourse/helpers/loading-spinner"; import { escapeExpression } from "discourse/lib/utilities"; import { bufferedRender } from "discourse-common/lib/buffered-render"; -import { observes, on } from "ember-addons/ember-computed-decorators"; +import { observes, on } from "discourse-common/utils/decorators"; export default Component.extend( bufferedRender({ @@ -35,7 +35,7 @@ export default Component.extend( @on("init") @observes("logs.[]") - _updateFormattedLogs: debounce(function() { + _updateFormattedLogs: discourseDebounce(function() { const logs = this.logs; if (logs.length === 0) return; diff --git a/app/assets/javascripts/admin/components/admin-directory-toggle.js.es6 b/app/assets/javascripts/admin/components/admin-directory-toggle.js.es6 index f110293a74..093f29826b 100644 --- a/app/assets/javascripts/admin/components/admin-directory-toggle.js.es6 +++ b/app/assets/javascripts/admin/components/admin-directory-toggle.js.es6 @@ -1,36 +1,30 @@ import Component from "@ember/component"; import { iconHTML } from "discourse-common/lib/icon-library"; -import { bufferedRender } from "discourse-common/lib/buffered-render"; -export default Component.extend( - bufferedRender({ - tagName: "th", - classNames: ["sortable"], - rerenderTriggers: ["order", "ascending"], - - buildBuffer(buffer) { - const icon = this.icon; - - if (icon) { - buffer.push(iconHTML(icon)); - } - - buffer.push(I18n.t(this.i18nKey)); - - if (this.field === this.order) { - buffer.push(iconHTML(this.ascending ? "chevron-up" : "chevron-down")); - } - }, - - click() { - const currentOrder = this.order; - const field = this.field; - - if (currentOrder === field) { - this.set("ascending", this.ascending ? null : true); - } else { - this.setProperties({ order: field, ascending: null }); - } +export default Component.extend({ + tagName: "th", + classNames: ["sortable"], + chevronIcon: null, + toggleProperties() { + if (this.order === this.field) { + this.set("ascending", this.ascending ? null : true); + } else { + this.setProperties({ order: this.field, ascending: null }); } - }) -); + }, + toggleChevron() { + if (this.order === this.field) { + let chevron = iconHTML(this.ascending ? "chevron-up" : "chevron-down"); + this.set("chevronIcon", `${chevron}`.htmlSafe()); + } else { + this.set("chevronIcon", null); + } + }, + click() { + this.toggleProperties(); + }, + didReceiveAttrs() { + this._super(...arguments); + this.toggleChevron(); + } +}); diff --git a/app/assets/javascripts/admin/components/admin-report-storage-stats.js.es6 b/app/assets/javascripts/admin/components/admin-report-storage-stats.js.es6 index 948ecf1e87..61629c626e 100644 --- a/app/assets/javascripts/admin/components/admin-report-storage-stats.js.es6 +++ b/app/assets/javascripts/admin/components/admin-report-storage-stats.js.es6 @@ -1,7 +1,7 @@ +import discourseComputed from "discourse-common/utils/decorators"; import { alias } from "@ember/object/computed"; import Component from "@ember/component"; import { setting } from "discourse/lib/computed"; -import computed from "ember-addons/ember-computed-decorators"; export default Component.extend({ classNames: ["admin-report-storage-stats"], @@ -10,32 +10,32 @@ export default Component.extend({ backupStats: alias("model.data.backups"), uploadStats: alias("model.data.uploads"), - @computed("backupStats") + @discourseComputed("backupStats") showBackupStats(stats) { return stats && this.currentUser.admin; }, - @computed("backupLocation") + @discourseComputed("backupLocation") backupLocationName(backupLocation) { return I18n.t(`admin.backups.location.${backupLocation}`); }, - @computed("backupStats.used_bytes") + @discourseComputed("backupStats.used_bytes") usedBackupSpace(bytes) { return I18n.toHumanSize(bytes); }, - @computed("backupStats.free_bytes") + @discourseComputed("backupStats.free_bytes") freeBackupSpace(bytes) { return I18n.toHumanSize(bytes); }, - @computed("uploadStats.used_bytes") + @discourseComputed("uploadStats.used_bytes") usedUploadSpace(bytes) { return I18n.toHumanSize(bytes); }, - @computed("uploadStats.free_bytes") + @discourseComputed("uploadStats.free_bytes") freeUploadSpace(bytes) { return I18n.toHumanSize(bytes); } diff --git a/app/assets/javascripts/admin/components/admin-report-table-cell.js.es6 b/app/assets/javascripts/admin/components/admin-report-table-cell.js.es6 index f83a33dbfb..e7bf688f2f 100644 --- a/app/assets/javascripts/admin/components/admin-report-table-cell.js.es6 +++ b/app/assets/javascripts/admin/components/admin-report-table-cell.js.es6 @@ -1,6 +1,6 @@ +import discourseComputed from "discourse-common/utils/decorators"; import { alias } from "@ember/object/computed"; import Component from "@ember/component"; -import computed from "ember-addons/ember-computed-decorators"; export default Component.extend({ tagName: "td", @@ -8,7 +8,7 @@ export default Component.extend({ classNameBindings: ["type", "property"], options: null, - @computed("label", "data", "options") + @discourseComputed("label", "data", "options") computedLabel(label, data, options) { return label.compute(data, options || {}); }, diff --git a/app/assets/javascripts/admin/components/admin-report-table-header.js.es6 b/app/assets/javascripts/admin/components/admin-report-table-header.js.es6 index 9317ef1f66..bc5633b21d 100644 --- a/app/assets/javascripts/admin/components/admin-report-table-header.js.es6 +++ b/app/assets/javascripts/admin/components/admin-report-table-header.js.es6 @@ -1,5 +1,5 @@ +import discourseComputed from "discourse-common/utils/decorators"; import Component from "@ember/component"; -import computed from "ember-addons/ember-computed-decorators"; export default Component.extend({ tagName: "th", @@ -7,12 +7,12 @@ export default Component.extend({ classNameBindings: ["label.mainProperty", "label.type", "isCurrentSort"], attributeBindings: ["label.title:title"], - @computed("currentSortLabel.sortProperty", "label.sortProperty") + @discourseComputed("currentSortLabel.sortProperty", "label.sortProperty") isCurrentSort(currentSortField, labelSortField) { return currentSortField === labelSortField; }, - @computed("currentSortDirection") + @discourseComputed("currentSortDirection") sortIcon(currentSortDirection) { return currentSortDirection === 1 ? "caret-up" : "caret-down"; } diff --git a/app/assets/javascripts/admin/components/admin-report-table.js.es6 b/app/assets/javascripts/admin/components/admin-report-table.js.es6 index 38e00c8ab8..aa636224d4 100644 --- a/app/assets/javascripts/admin/components/admin-report-table.js.es6 +++ b/app/assets/javascripts/admin/components/admin-report-table.js.es6 @@ -1,7 +1,7 @@ +import discourseComputed from "discourse-common/utils/decorators"; import { makeArray } from "discourse-common/lib/helpers"; import { alias } from "@ember/object/computed"; import Component from "@ember/component"; -import computed from "ember-addons/ember-computed-decorators"; const PAGES_LIMIT = 8; @@ -13,12 +13,16 @@ export default Component.extend({ perPage: alias("options.perPage"), page: 0, - @computed("model.computedLabels.length") + @discourseComputed("model.computedLabels.length") twoColumns(labelsLength) { return labelsLength === 2; }, - @computed("totalsForSample", "options.total", "model.dates_filtering") + @discourseComputed( + "totalsForSample", + "options.total", + "model.dates_filtering" + ) showTotalForSample(totalsForSample, total, datesFiltering) { // check if we have at least one cell which contains a value const sum = totalsForSample @@ -29,12 +33,16 @@ export default Component.extend({ return sum >= 1 && total && datesFiltering; }, - @computed("model.total", "options.total", "twoColumns") + @discourseComputed("model.total", "options.total", "twoColumns") showTotal(reportTotal, total, twoColumns) { return reportTotal && total && twoColumns; }, - @computed("model.{average,data}", "totalsForSample.1.value", "twoColumns") + @discourseComputed( + "model.{average,data}", + "totalsForSample.1.value", + "twoColumns" + ) showAverage(model, sampleTotalValue, hasTwoColumns) { return ( model.average && @@ -44,17 +52,17 @@ export default Component.extend({ ); }, - @computed("totalsForSample.1.value", "model.data.length") + @discourseComputed("totalsForSample.1.value", "model.data.length") averageForSample(totals, count) { return (totals / count).toFixed(0); }, - @computed("model.data.length") + @discourseComputed("model.data.length") showSortingUI(dataLength) { return dataLength >= 5; }, - @computed("totalsForSampleRow", "model.computedLabels") + @discourseComputed("totalsForSampleRow", "model.computedLabels") totalsForSample(row, labels) { return labels.map(label => { const computedLabel = label.compute(row); @@ -64,7 +72,7 @@ export default Component.extend({ }); }, - @computed("model.data", "model.computedLabels") + @discourseComputed("model.data", "model.computedLabels") totalsForSampleRow(rows, labels) { if (!rows || !rows.length) return {}; @@ -90,7 +98,7 @@ export default Component.extend({ return totalsRow; }, - @computed("sortLabel", "sortDirection", "model.data.[]") + @discourseComputed("sortLabel", "sortDirection", "model.data.[]") sortedData(sortLabel, sortDirection, data) { data = makeArray(data); @@ -110,7 +118,7 @@ export default Component.extend({ return data; }, - @computed("sortedData.[]", "perPage", "page") + @discourseComputed("sortedData.[]", "perPage", "page") paginatedData(data, perPage, page) { if (perPage < data.length) { const start = perPage * page; @@ -120,7 +128,7 @@ export default Component.extend({ return data; }, - @computed("model.data", "perPage", "page") + @discourseComputed("model.data", "perPage", "page") pages(data, perPage, page) { if (!data || data.length <= perPage) return []; diff --git a/app/assets/javascripts/admin/components/admin-report.js.es6 b/app/assets/javascripts/admin/components/admin-report.js.es6 index db06feecfc..cb70fc07ff 100644 --- a/app/assets/javascripts/admin/components/admin-report.js.es6 +++ b/app/assets/javascripts/admin/components/admin-report.js.es6 @@ -1,3 +1,4 @@ +import discourseComputed from "discourse-common/utils/decorators"; import { makeArray } from "discourse-common/lib/helpers"; import { alias, or, and, reads, equal, notEmpty } from "@ember/object/computed"; import EmberObject from "@ember/object"; @@ -8,7 +9,7 @@ import { exportEntity } from "discourse/lib/export-csv"; import { outputExportResult } from "discourse/lib/export-result"; import { isNumeric } from "discourse/lib/utilities"; import { SCHEMA_VERSION, default as Report } from "admin/models/report"; -import computed from "ember-addons/ember-computed-decorators"; +import ENV from "discourse-common/config/environment"; const TABLE_OPTIONS = { perPage: 8, @@ -89,23 +90,23 @@ export default Component.extend({ hasData: notEmpty("model.data"), - @computed("dataSourceName", "model.type") + @discourseComputed("dataSourceName", "model.type") dasherizedDataSourceName(dataSourceName, type) { return (dataSourceName || type || "undefined").replace(/_/g, "-"); }, - @computed("dataSourceName", "model.type") + @discourseComputed("dataSourceName", "model.type") dataSource(dataSourceName, type) { dataSourceName = dataSourceName || type; return `/admin/reports/${dataSourceName}`; }, - @computed("displayedModes.length") + @discourseComputed("displayedModes.length") showModes(displayedModesLength) { return displayedModesLength > 1; }, - @computed("currentMode", "model.modes", "forcedModes") + @discourseComputed("currentMode", "model.modes", "forcedModes") displayedModes(currentMode, reportModes, forcedModes) { const modes = forcedModes ? forcedModes.split(",") : reportModes; @@ -121,12 +122,12 @@ export default Component.extend({ }); }, - @computed("currentMode") + @discourseComputed("currentMode") modeComponent(currentMode) { return `admin-report-${currentMode}`; }, - @computed("startDate") + @discourseComputed("startDate") normalizedStartDate(startDate) { return startDate && typeof startDate.isValid === "function" ? moment @@ -138,7 +139,7 @@ export default Component.extend({ .format("YYYYMMDD"); }, - @computed("endDate") + @discourseComputed("endDate") normalizedEndDate(endDate) { return endDate && typeof endDate.isValid === "function" ? moment @@ -150,7 +151,7 @@ export default Component.extend({ .format("YYYYMMDD"); }, - @computed( + @discourseComputed( "dataSourceName", "normalizedStartDate", "normalizedEndDate", @@ -162,8 +163,8 @@ export default Component.extend({ let reportKey = "reports:"; reportKey += [ dataSourceName, - Ember.testing ? "start" : startDate.replace(/-/g, ""), - Ember.testing ? "end" : endDate.replace(/-/g, ""), + ENV.environment === "test" ? "start" : startDate.replace(/-/g, ""), + ENV.environment === "test" ? "end" : endDate.replace(/-/g, ""), "[:prev_period]", this.get("reportOptions.table.limit"), customFilters 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 1dec9fce37..381ac0ca47 100644 --- a/app/assets/javascripts/admin/components/admin-theme-editor.js.es6 +++ b/app/assets/javascripts/admin/components/admin-theme-editor.js.es6 @@ -1,10 +1,10 @@ import { next } from "@ember/runloop"; import Component from "@ember/component"; -import { default as computed } from "ember-addons/ember-computed-decorators"; +import { default as discourseComputed } from "discourse-common/utils/decorators"; import { fmt } from "discourse/lib/computed"; export default Component.extend({ - @computed("theme.targets", "onlyOverridden", "showAdvanced") + @discourseComputed("theme.targets", "onlyOverridden", "showAdvanced") visibleTargets(targets, onlyOverridden, showAdvanced) { return targets.filter(target => { if (target.advanced && !showAdvanced) { @@ -17,7 +17,7 @@ export default Component.extend({ }); }, - @computed("currentTargetName", "onlyOverridden", "theme.fields") + @discourseComputed("currentTargetName", "onlyOverridden", "theme.fields") visibleFields(targetName, onlyOverridden, fields) { fields = fields[targetName]; if (onlyOverridden) { @@ -26,14 +26,14 @@ export default Component.extend({ return fields; }, - @computed("currentTargetName", "fieldName") + @discourseComputed("currentTargetName", "fieldName") activeSectionMode(targetName, fieldName) { if (["settings", "translations"].includes(targetName)) return "yaml"; if (["extra_scss"].includes(targetName)) return "scss"; return fieldName && fieldName.indexOf("scss") > -1 ? "scss" : "html"; }, - @computed("fieldName", "currentTargetName", "theme") + @discourseComputed("fieldName", "currentTargetName", "theme") activeSection: { get(fieldName, target, model) { return model.getField(target, fieldName); @@ -46,17 +46,21 @@ export default Component.extend({ editorId: fmt("fieldName", "currentTargetName", "%@|%@"), - @computed("maximized") + @discourseComputed("maximized") maximizeIcon(maximized) { return maximized ? "discourse-compress" : "discourse-expand"; }, - @computed("currentTargetName", "theme.targets") + @discourseComputed("currentTargetName", "theme.targets") showAddField(currentTargetName, targets) { return targets.find(t => t.name === currentTargetName).customNames; }, - @computed("currentTargetName", "fieldName", "theme.theme_fields.@each.error") + @discourseComputed( + "currentTargetName", + "fieldName", + "theme.theme_fields.@each.error" + ) error(target, fieldName) { return this.theme.getError(target, fieldName); }, 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 23bfac19b4..a392196f8a 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 @@ -8,10 +8,10 @@ import { popupAjaxError } from "discourse/lib/ajax-error"; import { propertyEqual } from "discourse/lib/computed"; import { i18n } from "discourse/lib/computed"; import { - default as computed, + default as discourseComputed, observes, on -} from "ember-addons/ember-computed-decorators"; +} from "discourse-common/utils/decorators"; export default Component.extend(bufferedProperty("userField"), { editing: empty("userField.id"), @@ -22,7 +22,7 @@ export default Component.extend(bufferedProperty("userField"), { userFieldsDescription: i18n("admin.user_fields.description"), - @computed("buffered.field_type") + @discourseComputed("buffered.field_type") bufferedFieldType(fieldType) { return UserField.fieldTypeById(fieldType); }, @@ -39,12 +39,12 @@ export default Component.extend(bufferedProperty("userField"), { $(".user-field-name").select(); }, - @computed("userField.field_type") + @discourseComputed("userField.field_type") fieldName(fieldType) { return UserField.fieldTypeById(fieldType).get("name"); }, - @computed( + @discourseComputed( "userField.editable", "userField.required", "userField.show_on_profile", diff --git a/app/assets/javascripts/admin/components/admin-watched-word.js.es6 b/app/assets/javascripts/admin/components/admin-watched-word.js.es6 index 8a408cf166..d4f5108c0f 100644 --- a/app/assets/javascripts/admin/components/admin-watched-word.js.es6 +++ b/app/assets/javascripts/admin/components/admin-watched-word.js.es6 @@ -1,30 +1,28 @@ import Component from "@ember/component"; import { iconHTML } from "discourse-common/lib/icon-library"; -import { bufferedRender } from "discourse-common/lib/buffered-render"; -import { escapeExpression } from "discourse/lib/utilities"; -export default Component.extend( - bufferedRender({ - classNames: ["watched-word"], +export default Component.extend({ + classNames: ["watched-word"], + watchedWord: null, + xIcon: iconHTML("times").htmlSafe(), - buildBuffer(buffer) { - buffer.push(iconHTML("times")); - buffer.push(` ${escapeExpression(this.get("word.word"))}`); - }, + init() { + this._super(...arguments); + this.set("watchedWord", this.get("word.word")); + }, - click() { - this.word - .destroy() - .then(() => { - this.action(this.word); - }) - .catch(e => { - bootbox.alert( - I18n.t("generic_error_with_reason", { - error: `http: ${e.status} - ${e.body}` - }) - ); - }); - } - }) -); + click() { + this.word + .destroy() + .then(() => { + this.action(this.word); + }) + .catch(e => { + bootbox.alert( + I18n.t("generic_error_with_reason", { + error: `http: ${e.status} - ${e.body}` + }) + ); + }); + } +}); diff --git a/app/assets/javascripts/admin/components/admin-web-hook-event-chooser.js.es6 b/app/assets/javascripts/admin/components/admin-web-hook-event-chooser.js.es6 index 1c7f6f05a0..a38695c735 100644 --- a/app/assets/javascripts/admin/components/admin-web-hook-event-chooser.js.es6 +++ b/app/assets/javascripts/admin/components/admin-web-hook-event-chooser.js.es6 @@ -1,27 +1,27 @@ +import discourseComputed from "discourse-common/utils/decorators"; import { alias } from "@ember/object/computed"; import Component from "@ember/component"; -import computed from "ember-addons/ember-computed-decorators"; export default Component.extend({ classNames: ["hook-event"], typeName: alias("type.name"), - @computed("typeName") + @discourseComputed("typeName") name(typeName) { return I18n.t(`admin.web_hooks.${typeName}_event.name`); }, - @computed("typeName") + @discourseComputed("typeName") details(typeName) { return I18n.t(`admin.web_hooks.${typeName}_event.details`); }, - @computed("model.[]", "typeName") + @discourseComputed("model.[]", "typeName") eventTypeExists(eventTypes, typeName) { return eventTypes.any(event => event.name === typeName); }, - @computed("eventTypeExists") + @discourseComputed("eventTypeExists") enabled: { get(eventTypeExists) { return eventTypeExists; diff --git a/app/assets/javascripts/admin/components/admin-web-hook-event.js.es6 b/app/assets/javascripts/admin/components/admin-web-hook-event.js.es6 index 693e6502ff..365e22aa67 100644 --- a/app/assets/javascripts/admin/components/admin-web-hook-event.js.es6 +++ b/app/assets/javascripts/admin/components/admin-web-hook-event.js.es6 @@ -1,5 +1,5 @@ +import discourseComputed from "discourse-common/utils/decorators"; import Component from "@ember/component"; -import computed from "ember-addons/ember-computed-decorators"; import { ajax } from "discourse/lib/ajax"; import { popupAjaxError } from "discourse/lib/ajax-error"; import { ensureJSON, plainJSON, prettyJSON } from "discourse/lib/formatter"; @@ -10,7 +10,7 @@ export default Component.extend({ expandDetailsRequestKey: "request", expandDetailsResponseKey: "response", - @computed("model.status") + @discourseComputed("model.status") statusColorClasses(status) { if (!status) return ""; @@ -21,25 +21,25 @@ export default Component.extend({ } }, - @computed("model.created_at") + @discourseComputed("model.created_at") createdAt(createdAt) { return moment(createdAt).format("YYYY-MM-DD HH:mm:ss"); }, - @computed("model.duration") + @discourseComputed("model.duration") completion(duration) { const seconds = Math.floor(duration / 10.0) / 100.0; return I18n.t("admin.web_hooks.events.completed_in", { count: seconds }); }, - @computed("expandDetails") + @discourseComputed("expandDetails") expandRequestIcon(expandDetails) { return expandDetails === this.expandDetailsRequestKey ? "ellipsis-h" : "ellipsis-v"; }, - @computed("expandDetails") + @discourseComputed("expandDetails") expandResponseIcon(expandDetails) { return expandDetails === this.expandDetailsResponseKey ? "ellipsis-h" diff --git a/app/assets/javascripts/admin/components/admin-web-hook-status.js.es6 b/app/assets/javascripts/admin/components/admin-web-hook-status.js.es6 index 0d8e80cc81..0c24edc9d6 100644 --- a/app/assets/javascripts/admin/components/admin-web-hook-status.js.es6 +++ b/app/assets/javascripts/admin/components/admin-web-hook-status.js.es6 @@ -1,33 +1,37 @@ +import discourseComputed from "discourse-common/utils/decorators"; import Component from "@ember/component"; -import computed from "ember-addons/ember-computed-decorators"; import { iconHTML } from "discourse-common/lib/icon-library"; -import { bufferedRender } from "discourse-common/lib/buffered-render"; -export default Component.extend( - bufferedRender({ - classes: ["text-muted", "text-danger", "text-successful", "text-muted"], - icons: ["far-circle", "times-circle", "circle", "circle"], +export default Component.extend({ + classes: ["text-muted", "text-danger", "text-successful", "text-muted"], + icons: ["far-circle", "times-circle", "circle", "circle"], + circleIcon: null, + deliveryStatus: null, - @computed("deliveryStatuses", "model.last_delivery_status") - status(deliveryStatuses, lastDeliveryStatus) { - return deliveryStatuses.find(s => s.id === lastDeliveryStatus); - }, + @discourseComputed("deliveryStatuses", "model.last_delivery_status") + status(deliveryStatuses, lastDeliveryStatus) { + return deliveryStatuses.find(s => s.id === lastDeliveryStatus); + }, - @computed("status.id", "icons") - icon(statusId, icons) { - return icons[statusId - 1]; - }, + @discourseComputed("status.id", "icons") + icon(statusId, icons) { + return icons[statusId - 1]; + }, - @computed("status.id", "classes") - class(statusId, classes) { - return classes[statusId - 1]; - }, + @discourseComputed("status.id", "classes") + class(statusId, classes) { + return classes[statusId - 1]; + }, - buildBuffer(buffer) { - buffer.push(iconHTML(this.icon, { class: this.class })); - buffer.push( - I18n.t(`admin.web_hooks.delivery_status.${this.get("status.name")}`) - ); - } - }) -); + didReceiveAttrs() { + this._super(...arguments); + this.set( + "circleIcon", + iconHTML(this.icon, { class: this.class }).htmlSafe() + ); + this.set( + "deliveryStatus", + I18n.t(`admin.web_hooks.delivery_status.${this.get("status.name")}`) + ); + } +}); diff --git a/app/assets/javascripts/admin/components/email-styles-editor.js.es6 b/app/assets/javascripts/admin/components/email-styles-editor.js.es6 index e465c04cba..ef5cdb077e 100644 --- a/app/assets/javascripts/admin/components/email-styles-editor.js.es6 +++ b/app/assets/javascripts/admin/components/email-styles-editor.js.es6 @@ -1,16 +1,16 @@ +import discourseComputed from "discourse-common/utils/decorators"; import { reads } from "@ember/object/computed"; import Component from "@ember/component"; -import computed from "ember-addons/ember-computed-decorators"; export default Component.extend({ editorId: reads("fieldName"), - @computed("fieldName") + @discourseComputed("fieldName") currentEditorMode(fieldName) { return fieldName === "css" ? "scss" : fieldName; }, - @computed("fieldName", "styles.html", "styles.css") + @discourseComputed("fieldName", "styles.html", "styles.css") resetDisabled(fieldName) { return ( this.get(`styles.${fieldName}`) === @@ -18,7 +18,7 @@ export default Component.extend({ ); }, - @computed("styles", "fieldName") + @discourseComputed("styles", "fieldName") editorContents: { get(styles, fieldName) { return styles[fieldName]; diff --git a/app/assets/javascripts/admin/components/embeddable-host.js.es6 b/app/assets/javascripts/admin/components/embeddable-host.js.es6 index 57829b45fb..1d853b8986 100644 --- a/app/assets/javascripts/admin/components/embeddable-host.js.es6 +++ b/app/assets/javascripts/admin/components/embeddable-host.js.es6 @@ -1,11 +1,12 @@ +import discourseComputed from "discourse-common/utils/decorators"; import { isEmpty } from "@ember/utils"; import { or } from "@ember/object/computed"; import { schedule } from "@ember/runloop"; import Component from "@ember/component"; import { bufferedProperty } from "discourse/mixins/buffered-content"; -import computed from "ember-addons/ember-computed-decorators"; -import { on, observes } from "ember-addons/ember-computed-decorators"; +import { on, observes } from "discourse-common/utils/decorators"; import { popupAjaxError } from "discourse/lib/ajax-error"; +import Category from "discourse/models/category"; export default Component.extend(bufferedProperty("host"), { editToggled: false, @@ -22,7 +23,7 @@ export default Component.extend(bufferedProperty("host"), { }); }, - @computed("buffered.host", "host.isSaving") + @discourseComputed("buffered.host", "host.isSaving") cantSave(host, isSaving) { return isSaving || isEmpty(host); }, @@ -50,7 +51,7 @@ export default Component.extend(bufferedProperty("host"), { host .save(props) .then(() => { - host.set("category", Discourse.Category.findById(this.categoryId)); + host.set("category", Category.findById(this.categoryId)); this.set("editToggled", false); }) .catch(popupAjaxError); diff --git a/app/assets/javascripts/admin/components/embedding-setting.js.es6 b/app/assets/javascripts/admin/components/embedding-setting.js.es6 index da7f9abb7f..517e37f4f9 100644 --- a/app/assets/javascripts/admin/components/embedding-setting.js.es6 +++ b/app/assets/javascripts/admin/components/embedding-setting.js.es6 @@ -1,25 +1,25 @@ +import discourseComputed from "discourse-common/utils/decorators"; import Component from "@ember/component"; -import computed from "ember-addons/ember-computed-decorators"; export default Component.extend({ classNames: ["embed-setting"], - @computed("field") + @discourseComputed("field") inputId(field) { return field.dasherize(); }, - @computed("field") + @discourseComputed("field") translationKey(field) { return `admin.embedding.${field}`; }, - @computed("type") + @discourseComputed("type") isCheckbox(type) { return type === "checkbox"; }, - @computed("value") + @discourseComputed("value") checked: { get(value) { return !!value; diff --git a/app/assets/javascripts/admin/components/highlighted-code.js.es6 b/app/assets/javascripts/admin/components/highlighted-code.js.es6 index d182d7e2a1..9159bb574a 100644 --- a/app/assets/javascripts/admin/components/highlighted-code.js.es6 +++ b/app/assets/javascripts/admin/components/highlighted-code.js.es6 @@ -1,5 +1,5 @@ import Component from "@ember/component"; -import { on, observes } from "ember-addons/ember-computed-decorators"; +import { on, observes } from "discourse-common/utils/decorators"; import highlightSyntax from "discourse/lib/highlight-syntax"; export default Component.extend({ 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 e88c2bc3b7..ff3cb98ffc 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,8 @@ import Component from "@ember/component"; import { - default as computed, + default as discourseComputed, observes -} from "ember-addons/ember-computed-decorators"; +} from "discourse-common/utils/decorators"; export default Component.extend({ classNames: ["inline-edit"], @@ -21,12 +21,12 @@ export default Component.extend({ this.set("checkedInternal", this.checked); }, - @computed("labelKey") + @discourseComputed("labelKey") label(key) { return I18n.t(key); }, - @computed("checked", "checkedInternal") + @discourseComputed("checked", "checkedInternal") changed(checked, checkedInternal) { return !!checked !== !!checkedInternal; }, diff --git a/app/assets/javascripts/admin/components/ip-lookup.js.es6 b/app/assets/javascripts/admin/components/ip-lookup.js.es6 index 8438a4ee5c..06421e2e90 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 computed } from "ember-addons/ember-computed-decorators"; +import { default as 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"; @@ -9,7 +9,7 @@ import copyText from "discourse/lib/copy-text"; export default Component.extend({ classNames: ["ip-lookup"], - @computed("other_accounts.length", "totalOthersWithSameIP") + @discourseComputed("other_accounts.length", "totalOthersWithSameIP") otherAccountsToDelete(otherAccountsLength, totalOthersWithSameIP) { // can only delete up to 50 accounts at a time const total = Math.min(50, totalOthersWithSameIP || 0); diff --git a/app/assets/javascripts/admin/components/penalty-post-action.js.es6 b/app/assets/javascripts/admin/components/penalty-post-action.js.es6 index 8af1f83b16..6a703105fb 100644 --- a/app/assets/javascripts/admin/components/penalty-post-action.js.es6 +++ b/app/assets/javascripts/admin/components/penalty-post-action.js.es6 @@ -1,7 +1,7 @@ +import discourseComputed from "discourse-common/utils/decorators"; import { equal } from "@ember/object/computed"; import { scheduleOnce } from "@ember/runloop"; import Component from "@ember/component"; -import computed from "ember-addons/ember-computed-decorators"; const ACTIONS = ["delete", "delete_replies", "edit", "none"]; @@ -10,7 +10,7 @@ export default Component.extend({ postAction: null, postEdit: null, - @computed + @discourseComputed penaltyActions() { return ACTIONS.map(id => { return { id, name: I18n.t(`admin.user.penalty_post_${id}`) }; diff --git a/app/assets/javascripts/admin/components/permalink-form.js.es6 b/app/assets/javascripts/admin/components/permalink-form.js.es6 index b1f82ea295..5a90dd9913 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 computed } from "ember-addons/ember-computed-decorators"; +import { default as discourseComputed } from "discourse-common/utils/decorators"; import { fmt } from "discourse/lib/computed"; import Permalink from "admin/models/permalink"; @@ -10,7 +10,7 @@ export default Component.extend({ permalinkType: "topic_id", permalinkTypePlaceholder: fmt("permalinkType", "admin.permalink.%@"), - @computed + @discourseComputed permalinkTypes() { return [ { id: "topic_id", name: I18n.t("admin.permalink.topic_id") }, 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 7efdd4a465..60fb61be9b 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 computed } from "ember-addons/ember-computed-decorators"; +import { default as discourseComputed } from "discourse-common/utils/decorators"; import FilterComponent from "admin/components/report-filters/filter"; export default FilterComponent.extend({ @@ -7,7 +7,7 @@ export default FilterComponent.extend({ layoutName: "admin/templates/components/report-filters/category", - @computed("filter.default") + @discourseComputed("filter.default") category(categoryId) { return Category.findById(categoryId); }, 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 54523f9446..0821cb084e 100644 --- a/app/assets/javascripts/admin/components/report-filters/group.js.es6 +++ b/app/assets/javascripts/admin/components/report-filters/group.js.es6 @@ -1,19 +1,19 @@ import FilterComponent from "admin/components/report-filters/filter"; -import { default as computed } from "ember-addons/ember-computed-decorators"; +import { default as discourseComputed } from "discourse-common/utils/decorators"; export default FilterComponent.extend({ classNames: ["group-filter"], layoutName: "admin/templates/components/report-filters/group", - @computed() + @discourseComputed() groupOptions() { return (this.site.groups || []).map(group => { return { name: group["name"], value: group["id"] }; }); }, - @computed("filter.default") + @discourseComputed("filter.default") groupId(filterDefault) { return filterDefault ? parseInt(filterDefault, 10) : null; } diff --git a/app/assets/javascripts/admin/components/resumable-upload.js.es6 b/app/assets/javascripts/admin/components/resumable-upload.js.es6 index d63a1a651d..925bdf0de9 100644 --- a/app/assets/javascripts/admin/components/resumable-upload.js.es6 +++ b/app/assets/javascripts/admin/components/resumable-upload.js.es6 @@ -4,9 +4,9 @@ import Component from "@ember/component"; import { iconHTML } from "discourse-common/lib/icon-library"; import { bufferedRender } from "discourse-common/lib/buffered-render"; import { - default as computed, + default as discourseComputed, on -} from "ember-addons/ember-computed-decorators"; +} from "discourse-common/utils/decorators"; /*global Resumable:true */ @@ -91,12 +91,12 @@ export default Component.extend( } }, - @computed("title", "text") + @discourseComputed("title", "text") translatedTitle(title, text) { return title ? I18n.t(title) : text; }, - @computed("isUploading", "progress") + @discourseComputed("isUploading", "progress") text(isUploading, progress) { if (isUploading) { return progress + " %"; diff --git a/app/assets/javascripts/admin/components/save-controls.js.es6 b/app/assets/javascripts/admin/components/save-controls.js.es6 index b039b0158c..9da4e49fe2 100644 --- a/app/assets/javascripts/admin/components/save-controls.js.es6 +++ b/app/assets/javascripts/admin/components/save-controls.js.es6 @@ -1,13 +1,13 @@ +import discourseComputed from "discourse-common/utils/decorators"; import { or } from "@ember/object/computed"; import Component from "@ember/component"; -import computed from "ember-addons/ember-computed-decorators"; export default Component.extend({ classNames: ["controls"], buttonDisabled: or("model.isSaving", "saveDisabled"), - @computed("model.isSaving") + @discourseComputed("model.isSaving") savingText(saving) { return saving ? "saving" : "save"; } diff --git a/app/assets/javascripts/admin/components/screened-ip-address-form.js.es6 b/app/assets/javascripts/admin/components/screened-ip-address-form.js.es6 index 8b6db57764..48b92641b4 100644 --- a/app/assets/javascripts/admin/components/screened-ip-address-form.js.es6 +++ b/app/assets/javascripts/admin/components/screened-ip-address-form.js.es6 @@ -1,3 +1,4 @@ +import discourseComputed from "discourse-common/utils/decorators"; import { schedule } from "@ember/runloop"; import Component from "@ember/component"; /** @@ -12,20 +13,19 @@ import Component from "@ember/component"; **/ import ScreenedIpAddress from "admin/models/screened-ip-address"; -import computed from "ember-addons/ember-computed-decorators"; -import { on } from "ember-addons/ember-computed-decorators"; +import { on } from "discourse-common/utils/decorators"; export default Component.extend({ classNames: ["screened-ip-address-form"], formSubmitted: false, actionName: "block", - @computed + @discourseComputed adminWhitelistEnabled() { return Discourse.SiteSettings.use_admin_ip_whitelist; }, - @computed("adminWhitelistEnabled") + @discourseComputed("adminWhitelistEnabled") actionNames(adminWhitelistEnabled) { if (adminWhitelistEnabled) { return [ diff --git a/app/assets/javascripts/admin/components/secret-value-list.js.es6 b/app/assets/javascripts/admin/components/secret-value-list.js.es6 index 4327f62f80..ea4ecf792c 100644 --- a/app/assets/javascripts/admin/components/secret-value-list.js.es6 +++ b/app/assets/javascripts/admin/components/secret-value-list.js.es6 @@ -1,6 +1,6 @@ import { isEmpty } from "@ember/utils"; import Component from "@ember/component"; -import { on } from "ember-addons/ember-computed-decorators"; +import { on } from "discourse-common/utils/decorators"; import { set } from "@ember/object"; export default Component.extend({ diff --git a/app/assets/javascripts/admin/components/site-settings/bool.js.es6 b/app/assets/javascripts/admin/components/site-settings/bool.js.es6 index 2b2fdaca8e..88f4387601 100644 --- a/app/assets/javascripts/admin/components/site-settings/bool.js.es6 +++ b/app/assets/javascripts/admin/components/site-settings/bool.js.es6 @@ -1,9 +1,9 @@ +import discourseComputed from "discourse-common/utils/decorators"; import { isEmpty } from "@ember/utils"; import Component from "@ember/component"; -import computed from "ember-addons/ember-computed-decorators"; export default Component.extend({ - @computed("value") + @discourseComputed("value") enabled: { get(value) { if (isEmpty(value)) { diff --git a/app/assets/javascripts/admin/components/site-settings/category-list.js.es6 b/app/assets/javascripts/admin/components/site-settings/category-list.js.es6 index d4476ddf13..ff83e13e52 100644 --- a/app/assets/javascripts/admin/components/site-settings/category-list.js.es6 +++ b/app/assets/javascripts/admin/components/site-settings/category-list.js.es6 @@ -1,11 +1,12 @@ +import discourseComputed from "discourse-common/utils/decorators"; import Component from "@ember/component"; -import computed from "ember-addons/ember-computed-decorators"; +import Category from "discourse/models/category"; export default Component.extend({ - @computed("value") + @discourseComputed("value") selectedCategories: { get(value) { - return Discourse.Category.findByIds(value.split("|")); + return Category.findByIds(value.split("|")); }, set(value) { this.set("value", value.mapBy("id").join("|")); diff --git a/app/assets/javascripts/admin/components/site-settings/group-list.js.es6 b/app/assets/javascripts/admin/components/site-settings/group-list.js.es6 index 21af030269..97736c36ca 100644 --- a/app/assets/javascripts/admin/components/site-settings/group-list.js.es6 +++ b/app/assets/javascripts/admin/components/site-settings/group-list.js.es6 @@ -1,8 +1,8 @@ +import discourseComputed from "discourse-common/utils/decorators"; import Component from "@ember/component"; -import computed from "ember-addons/ember-computed-decorators"; export default Component.extend({ - @computed() + @discourseComputed() groupChoices() { return this.site.get("groups").map(g => { return { name: g.name, id: g.id.toString() }; diff --git a/app/assets/javascripts/admin/components/site-settings/tag-list.js.es6 b/app/assets/javascripts/admin/components/site-settings/tag-list.js.es6 index 417ad622cb..c8a8e0a06f 100644 --- a/app/assets/javascripts/admin/components/site-settings/tag-list.js.es6 +++ b/app/assets/javascripts/admin/components/site-settings/tag-list.js.es6 @@ -1,8 +1,8 @@ +import discourseComputed from "discourse-common/utils/decorators"; import Component from "@ember/component"; -import computed from "ember-addons/ember-computed-decorators"; export default Component.extend({ - @computed("value") + @discourseComputed("value") selectedTags: { get(value) { return value.split("|"); diff --git a/app/assets/javascripts/admin/components/site-text-summary.js.es6 b/app/assets/javascripts/admin/components/site-text-summary.js.es6 index 4467a092d1..11c6bc45eb 100644 --- a/app/assets/javascripts/admin/components/site-text-summary.js.es6 +++ b/app/assets/javascripts/admin/components/site-text-summary.js.es6 @@ -1,5 +1,5 @@ import Component from "@ember/component"; -import { on } from "ember-addons/ember-computed-decorators"; +import { on } from "discourse-common/utils/decorators"; export default Component.extend({ classNames: ["site-text"], diff --git a/app/assets/javascripts/admin/components/theme-setting-relatives-selector.js.es6 b/app/assets/javascripts/admin/components/theme-setting-relatives-selector.js.es6 new file mode 100644 index 0000000000..8ba638076c --- /dev/null +++ b/app/assets/javascripts/admin/components/theme-setting-relatives-selector.js.es6 @@ -0,0 +1,26 @@ +import Component from "@ember/component"; +import BufferedContent from "discourse/mixins/buffered-content"; +import SettingComponent from "admin/mixins/setting-component"; + +export default Component.extend(BufferedContent, SettingComponent, { + layoutName: "admin/templates/components/site-setting", + + _save() { + return this.model + .save({ [this.setting.setting]: this.convertNamesToIds() }) + .then(() => this.store.findAll("theme")); + }, + + convertNamesToIds() { + return this.get("buffered.value") + .split("|") + .filter(Boolean) + .map(themeName => { + if (themeName !== "") { + return this.setting.allThemes.find(theme => theme.name === themeName) + .id; + } + return themeName; + }); + } +}); 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 af30e5a398..cfc1b63a36 100644 --- a/app/assets/javascripts/admin/components/themes-list-item.js.es6 +++ b/app/assets/javascripts/admin/components/themes-list-item.js.es6 @@ -2,11 +2,12 @@ import { gt, and } from "@ember/object/computed"; import { schedule } from "@ember/runloop"; import Component from "@ember/component"; import { - default as computed, + default as discourseComputed, observes -} from "ember-addons/ember-computed-decorators"; +} from "discourse-common/utils/decorators"; import { iconHTML } from "discourse-common/lib/icon-library"; import { escape } from "pretty-text/sanitizer"; +import ENV from "discourse-common/config/environment"; const MAX_COMPONENTS = 4; @@ -43,7 +44,7 @@ export default Component.extend({ animate(isInitial) { const $container = $(this.element); const $list = $(this.element.querySelector(".components-list")); - if ($list.length === 0 || Ember.testing) { + if ($list.length === 0 || ENV.environment === "test") { return; } const duration = 300; @@ -54,7 +55,7 @@ export default Component.extend({ } }, - @computed( + @discourseComputed( "theme.component", "theme.childThemes.@each.name", "theme.childThemes.length", @@ -75,12 +76,12 @@ export default Component.extend({ }); }, - @computed("children") + @discourseComputed("children") childrenString(children) { return children.join(", "); }, - @computed( + @discourseComputed( "theme.childThemes.length", "theme.component", "childrenExpanded", diff --git a/app/assets/javascripts/admin/components/themes-list.js.es6 b/app/assets/javascripts/admin/components/themes-list.js.es6 index 357e4f756c..ae883a115d 100644 --- a/app/assets/javascripts/admin/components/themes-list.js.es6 +++ b/app/assets/javascripts/admin/components/themes-list.js.es6 @@ -1,7 +1,8 @@ import { gt, equal } from "@ember/object/computed"; import Component from "@ember/component"; import { THEMES, COMPONENTS } from "admin/models/theme"; -import { default as computed } from "ember-addons/ember-computed-decorators"; +import discourseComputed from "discourse-common/utils/decorators"; +import { getOwner } from "@ember/application"; export default Component.extend({ THEMES: THEMES, @@ -16,7 +17,7 @@ export default Component.extend({ themesTabActive: equal("currentTab", THEMES), componentsTabActive: equal("currentTab", COMPONENTS), - @computed("themes", "components", "currentTab") + @discourseComputed("themes", "components", "currentTab") themesList(themes, components) { if (this.themesTabActive) { return themes; @@ -25,7 +26,7 @@ export default Component.extend({ } }, - @computed( + @discourseComputed( "themesList", "currentTab", "themesList.@each.user_selectable", @@ -40,7 +41,7 @@ export default Component.extend({ ); }, - @computed( + @discourseComputed( "themesList", "currentTab", "themesList.@each.user_selectable", @@ -70,7 +71,7 @@ export default Component.extend({ } }, navigateToTheme(theme) { - Ember.getOwner(this) + getOwner(this) .lookup("router:main") .transitionTo("adminCustomizeThemes.show", theme); } diff --git a/app/assets/javascripts/admin/components/value-list.js.es6 b/app/assets/javascripts/admin/components/value-list.js.es6 index 31ef93932e..6582f20e7f 100644 --- a/app/assets/javascripts/admin/components/value-list.js.es6 +++ b/app/assets/javascripts/admin/components/value-list.js.es6 @@ -1,8 +1,8 @@ +import discourseComputed from "discourse-common/utils/decorators"; import { makeArray } from "discourse-common/lib/helpers"; import { empty, alias } from "@ember/object/computed"; import Component from "@ember/component"; -import { on } from "ember-addons/ember-computed-decorators"; -import computed from "ember-addons/ember-computed-decorators"; +import { on } from "discourse-common/utils/decorators"; export default Component.extend({ classNameBindings: [":value-list"], @@ -30,7 +30,7 @@ export default Component.extend({ ); }, - @computed("choices.[]", "collection.[]") + @discourseComputed("choices.[]", "collection.[]") filteredChoices(choices, collection) { return makeArray(choices).filter(i => collection.indexOf(i) < 0); }, 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 629c5a2b6a..2359320ad6 100644 --- a/app/assets/javascripts/admin/components/watched-word-form.js.es6 +++ b/app/assets/javascripts/admin/components/watched-word-form.js.es6 @@ -3,10 +3,10 @@ import { schedule } from "@ember/runloop"; import Component from "@ember/component"; import WatchedWord from "admin/models/watched-word"; import { - default as computed, + default as discourseComputed, on, observes -} from "ember-addons/ember-computed-decorators"; +} from "discourse-common/utils/decorators"; export default Component.extend({ classNames: ["watched-word-form"], @@ -14,7 +14,7 @@ export default Component.extend({ actionKey: null, showMessage: false, - @computed("regularExpressions") + @discourseComputed("regularExpressions") placeholderKey(regularExpressions) { return ( "admin.watched_words.form.placeholder" + @@ -29,7 +29,7 @@ export default Component.extend({ } }, - @computed("word") + @discourseComputed("word") isUniqueWord(word) { const words = this.filteredContent || []; const filtered = words.filter(content => content.action === this.actionKey); diff --git a/app/assets/javascripts/admin/components/watched-word-uploader.js.es6 b/app/assets/javascripts/admin/components/watched-word-uploader.js.es6 index 417f3d5bbf..05dc41c207 100644 --- a/app/assets/javascripts/admin/components/watched-word-uploader.js.es6 +++ b/app/assets/javascripts/admin/components/watched-word-uploader.js.es6 @@ -1,6 +1,6 @@ +import discourseComputed from "discourse-common/utils/decorators"; import { alias } from "@ember/object/computed"; import Component from "@ember/component"; -import computed from "ember-addons/ember-computed-decorators"; import UploadMixin from "discourse/mixins/upload"; export default Component.extend(UploadMixin, { @@ -13,7 +13,7 @@ export default Component.extend(UploadMixin, { return { skipValidation: true }; }, - @computed("actionKey") + @discourseComputed("actionKey") data(actionKey) { return { action_key: actionKey }; }, 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 f4d56c0a05..c04e6abec9 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 computed } from "ember-addons/ember-computed-decorators"; +import { default as discourseComputed } from "discourse-common/utils/decorators"; import { popupAjaxError } from "discourse/lib/ajax-error"; export default Ember.Controller.extend({ @@ -7,12 +7,12 @@ export default Ember.Controller.extend({ { id: "single", name: I18n.t("admin.api.single_user") } ], - @computed("userMode") + @discourseComputed("userMode") showUserSelector(mode) { return mode === "single"; }, - @computed("model.description", "model.username", "userMode") + @discourseComputed("model.description", "model.username", "userMode") saveDisabled(description, username, userMode) { if (Ember.isBlank(description)) return true; if (userMode === "single" && Ember.isBlank(username)) return true; 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 6b69eaea72..e4e7c24d43 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 computed } from "ember-addons/ember-computed-decorators"; +import { default as discourseComputed } from "discourse-common/utils/decorators"; import { setting, i18n } from "discourse/lib/computed"; export default Controller.extend({ @@ -12,7 +12,7 @@ export default Controller.extend({ backupLocation: setting("backup_location"), localBackupStorage: equal("backupLocation", "local"), - @computed("status.allowRestore", "status.isOperationRunning") + @discourseComputed("status.allowRestore", "status.isOperationRunning") restoreTitle(allowRestore, isOperationRunning) { if (!allowRestore) { return "admin.backups.operations.restore.is_disabled"; 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 733f4589fe..2511dcead1 100644 --- a/app/assets/javascripts/admin/controllers/admin-badges-show.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-badges-show.js.es6 @@ -1,10 +1,10 @@ +import discourseComputed from "discourse-common/utils/decorators"; import { alias } from "@ember/object/computed"; import { inject } from "@ember/controller"; import Controller from "@ember/controller"; import { popupAjaxError } from "discourse/lib/ajax-error"; import { bufferedProperty } from "discourse/mixins/buffered-content"; import { propertyNotEqual } from "discourse/lib/computed"; -import computed from "ember-addons/ember-computed-decorators"; export default Controller.extend(bufferedProperty("model"), { adminBadges: inject(), @@ -19,7 +19,7 @@ export default Controller.extend(bufferedProperty("model"), { readOnly: alias("buffered.system"), showDisplayName: propertyNotEqual("name", "displayName"), - @computed("model.query", "buffered.query") + @discourseComputed("model.query", "buffered.query") hasQuery(modelQuery, bufferedQuery) { if (bufferedQuery) { return bufferedQuery.trim().length > 0; diff --git a/app/assets/javascripts/admin/controllers/admin-customize-colors-show.js.es6 b/app/assets/javascripts/admin/controllers/admin-customize-colors-show.js.es6 index ce99c2cfea..9cdca3f098 100644 --- a/app/assets/javascripts/admin/controllers/admin-customize-colors-show.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-customize-colors-show.js.es6 @@ -1,9 +1,9 @@ +import discourseComputed from "discourse-common/utils/decorators"; import { later } from "@ember/runloop"; import Controller from "@ember/controller"; -import computed from "ember-addons/ember-computed-decorators"; export default Controller.extend({ - @computed("model.colors", "onlyOverridden") + @discourseComputed("model.colors", "onlyOverridden") colors(allColors, onlyOverridden) { if (onlyOverridden) { return allColors.filter(color => color.get("overridden")); 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 21c628be24..4fc2cf34f0 100644 --- a/app/assets/javascripts/admin/controllers/admin-customize-colors.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-customize-colors.js.es6 @@ -1,20 +1,20 @@ import EmberObject from "@ember/object"; import Controller from "@ember/controller"; import showModal from "discourse/lib/show-modal"; -import { default as computed } from "ember-addons/ember-computed-decorators"; +import { default as discourseComputed } from "discourse-common/utils/decorators"; export default Controller.extend({ - @computed("model.@each.id") + @discourseComputed("model.@each.id") baseColorScheme() { return this.model.findBy("is_base", true); }, - @computed("model.@each.id") + @discourseComputed("model.@each.id") baseColorSchemes() { return this.model.filterBy("is_base", true); }, - @computed("baseColorScheme") + @discourseComputed("baseColorScheme") baseColors(baseColorScheme) { const baseColorsHash = EmberObject.create({}); baseColorScheme.get("colors").forEach(color => { diff --git a/app/assets/javascripts/admin/controllers/admin-customize-email-style-edit.js.es6 b/app/assets/javascripts/admin/controllers/admin-customize-email-style-edit.js.es6 index f48f46eff0..d534792b00 100644 --- a/app/assets/javascripts/admin/controllers/admin-customize-email-style-edit.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-customize-email-style-edit.js.es6 @@ -1,13 +1,13 @@ +import discourseComputed from "discourse-common/utils/decorators"; import Controller from "@ember/controller"; -import computed from "ember-addons/ember-computed-decorators"; export default Controller.extend({ - @computed("model.isSaving") + @discourseComputed("model.isSaving") saveButtonText(isSaving) { return isSaving ? I18n.t("saving") : I18n.t("admin.customize.save"); }, - @computed("model.changed", "model.isSaving") + @discourseComputed("model.changed", "model.isSaving") saveDisabled(changed, isSaving) { return !changed || isSaving; }, diff --git a/app/assets/javascripts/admin/controllers/admin-customize-email-templates-edit.js.es6 b/app/assets/javascripts/admin/controllers/admin-customize-email-templates-edit.js.es6 index 5ec405594a..3617edca75 100644 --- a/app/assets/javascripts/admin/controllers/admin-customize-email-templates-edit.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-customize-email-templates-edit.js.es6 @@ -1,19 +1,19 @@ +import discourseComputed from "discourse-common/utils/decorators"; import Controller from "@ember/controller"; import { popupAjaxError } from "discourse/lib/ajax-error"; import { bufferedProperty } from "discourse/mixins/buffered-content"; -import computed from "ember-addons/ember-computed-decorators"; export default Controller.extend(bufferedProperty("emailTemplate"), { saved: false, - @computed("buffered.body", "buffered.subject") + @discourseComputed("buffered.body", "buffered.subject") saveDisabled(body, subject) { return ( this.emailTemplate.body === body && this.emailTemplate.subject === subject ); }, - @computed("buffered") + @discourseComputed("buffered") hasMultipleSubjects(buffered) { if (buffered.getProperties("subject")["subject"]) { return false; 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 a5a286bf80..75d7a486f2 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 computed } from "ember-addons/ember-computed-decorators"; +import { default as discourseComputed } from "discourse-common/utils/decorators"; export default Controller.extend({ section: null, @@ -16,7 +16,7 @@ export default Controller.extend({ this.set("currentTarget", target && target.id); }, - @computed("currentTarget") + @discourseComputed("currentTarget") currentTargetName(id) { const target = this.get("model.targets").find( t => t.id === parseInt(id, 10) @@ -24,12 +24,12 @@ export default Controller.extend({ return target && target.name; }, - @computed("model.isSaving") + @discourseComputed("model.isSaving") saveButtonText(isSaving) { return isSaving ? I18n.t("saving") : I18n.t("admin.customize.save"); }, - @computed("model.changed", "model.isSaving") + @discourseComputed("model.changed", "model.isSaving") saveDisabled(changed, isSaving) { return !changed || isSaving; }, 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 d4622e21d8..b9712761e5 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 @@ -1,7 +1,13 @@ import { makeArray } from "discourse-common/lib/helpers"; -import { empty, notEmpty, match } from "@ember/object/computed"; +import { + empty, + filterBy, + match, + mapBy, + notEmpty +} from "@ember/object/computed"; import Controller from "@ember/controller"; -import { default as computed } from "ember-addons/ember-computed-decorators"; +import { default as 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"; @@ -15,8 +21,17 @@ export default Controller.extend({ previewUrl: url("model.id", "/admin/themes/%@/preview"), addButtonDisabled: empty("selectedChildThemeId"), editRouteName: "adminCustomizeThemes.edit", + parentThemesNames: mapBy("model.parentThemes", "name"), + availableParentThemes: filterBy("allThemes", "component", false), + availableActiveParentThemes: filterBy("availableParentThemes", "isActive"), + availableThemesNames: mapBy("availableParentThemes", "name"), + availableActiveThemesNames: mapBy("availableActiveParentThemes", "name"), + availableActiveChildThemes: filterBy("availableChildThemes", "hasParents"), + availableComponentsNames: mapBy("availableChildThemes", "name"), + availableActiveComponentsNames: mapBy("availableActiveChildThemes", "name"), + childThemesNames: mapBy("model.childThemes", "name"), - @computed("model.editedFields") + @discourseComputed("model.editedFields") editedFieldsFormatted() { const descriptions = []; ["common", "desktop", "mobile"].forEach(target => { @@ -34,13 +49,13 @@ export default Controller.extend({ return descriptions; }, - @computed("colorSchemeId", "model.color_scheme_id") + @discourseComputed("colorSchemeId", "model.color_scheme_id") colorSchemeChanged(colorSchemeId, existingId) { - colorSchemeId = colorSchemeId === null ? null : parseInt(colorSchemeId); + colorSchemeId = colorSchemeId === null ? null : parseInt(colorSchemeId, 10); return colorSchemeId !== existingId; }, - @computed("availableChildThemes", "model.childThemes.[]", "model") + @discourseComputed("availableChildThemes", "model.childThemes.[]", "model") selectableChildThemes(available, childThemes) { if (available) { const themes = !childThemes @@ -50,7 +65,43 @@ export default Controller.extend({ } }, - @computed("allThemes", "model.component", "model") + @discourseComputed("model.parentThemes.[]") + relativesSelectorSettingsForComponent() { + return Ember.Object.create({ + list_type: "compact", + type: "list", + preview: null, + anyValue: false, + setting: "parent_theme_ids", + label: I18n.t("admin.customize.theme.component_on_themes"), + choices: this.availableThemesNames, + default: this.parentThemesNames.join("|"), + value: this.parentThemesNames.join("|"), + defaultValues: this.availableActiveThemesNames.join("|"), + allThemes: this.allThemes, + setDefaultValuesLabel: I18n.t("admin.customize.theme.add_all_themes") + }); + }, + + @discourseComputed("model.parentThemes.[]") + relativesSelectorSettingsForTheme() { + return Ember.Object.create({ + list_type: "compact", + type: "list", + preview: null, + anyValue: false, + setting: "child_theme_ids", + label: I18n.t("admin.customize.theme.included_components"), + choices: this.availableComponentsNames, + default: this.childThemesNames.join("|"), + value: this.childThemesNames.join("|"), + defaultValues: this.availableActiveComponentsNames.join("|"), + allThemes: this.allThemes, + setDefaultValuesLabel: I18n.t("admin.customize.theme.add_all") + }); + }, + + @discourseComputed("allThemes", "model.component", "model") availableChildThemes(allThemes) { if (!this.get("model.component")) { const themeId = this.get("model.id"); @@ -60,38 +111,38 @@ export default Controller.extend({ } }, - @computed("model.component") + @discourseComputed("model.component") convertKey(component) { const type = component ? "component" : "theme"; return `admin.customize.theme.convert_${type}`; }, - @computed("model.component") + @discourseComputed("model.component") convertIcon(component) { return component ? "cube" : ""; }, - @computed("model.component") + @discourseComputed("model.component") convertTooltip(component) { const type = component ? "component" : "theme"; return `admin.customize.theme.convert_${type}_tooltip`; }, - @computed("model.settings") + @discourseComputed("model.settings") settings(settings) { return settings.map(setting => ThemeSettings.create(setting)); }, hasSettings: notEmpty("settings"), - @computed("model.translations") + @discourseComputed("model.translations") translations(translations) { return translations.map(setting => ThemeSettings.create(setting)); }, hasTranslations: notEmpty("translations"), - @computed("model.remoteError", "updatingRemote") + @discourseComputed("model.remoteError", "updatingRemote") showRemoteError(errorMessage, updating) { return errorMessage && !updating; }, @@ -189,7 +240,7 @@ export default Controller.extend({ let schemeId = this.colorSchemeId; this.set( "model.color_scheme_id", - schemeId === null ? null : parseInt(schemeId) + schemeId === null ? null : parseInt(schemeId, 10) ); this.model.saveChanges("color_scheme_id"); }, @@ -239,9 +290,9 @@ export default Controller.extend({ }, addChildTheme() { - let themeId = parseInt(this.selectedChildThemeId); + let themeId = parseInt(this.selectedChildThemeId, 10); let theme = this.allThemes.findBy("id", themeId); - this.model.addChildTheme(theme); + this.model.addChildTheme(theme).then(() => this.store.findAll("theme")); }, removeUpload(upload) { @@ -258,7 +309,9 @@ export default Controller.extend({ }, removeChildTheme(theme) { - this.model.removeChildTheme(theme); + this.model + .removeChildTheme(theme) + .then(() => this.store.findAll("theme")); }, destroy() { 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 6727df97f3..f1d1f0d715 100644 --- a/app/assets/javascripts/admin/controllers/admin-customize-themes.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-customize-themes.js.es6 @@ -1,21 +1,21 @@ import Controller from "@ember/controller"; -import { default as computed } from "ember-addons/ember-computed-decorators"; +import { default as discourseComputed } from "discourse-common/utils/decorators"; import { THEMES } from "admin/models/theme"; export default Controller.extend({ currentTab: THEMES, - @computed("model", "model.@each.component") + @discourseComputed("model", "model.@each.component") fullThemes(themes) { return themes.filter(t => !t.get("component")); }, - @computed("model", "model.@each.component") + @discourseComputed("model", "model.@each.component") childThemes(themes) { return themes.filter(t => t.get("component")); }, - @computed("model", "model.@each.component") + @discourseComputed("model", "model.@each.component") installedThemes(themes) { return themes.map(t => t.name); } diff --git a/app/assets/javascripts/admin/controllers/admin-dashboard-general.js.es6 b/app/assets/javascripts/admin/controllers/admin-dashboard-general.js.es6 index 24884b0ba6..b77e3e0288 100644 --- a/app/assets/javascripts/admin/controllers/admin-dashboard-general.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-dashboard-general.js.es6 @@ -1,14 +1,15 @@ +import discourseComputed from "discourse-common/utils/decorators"; import { makeArray } from "discourse-common/lib/helpers"; import { inject } from "@ember/controller"; import Controller from "@ember/controller"; import { setting } from "discourse/lib/computed"; -import computed from "ember-addons/ember-computed-decorators"; import AdminDashboard from "admin/models/admin-dashboard"; import Report from "admin/models/report"; import PeriodComputationMixin from "admin/mixins/period-computation"; +import { computed } from "@ember/object"; function staticReport(reportType) { - return Ember.computed("reports.[]", function() { + return computed("reports.[]", function() { return makeArray(this.reports).find(report => report.type === reportType); }); } @@ -20,12 +21,12 @@ export default Controller.extend(PeriodComputationMixin, { logSearchQueriesEnabled: setting("log_search_queries"), basePath: Discourse.BaseUri, - @computed("siteSettings.dashboard_general_tab_activity_metrics") + @discourseComputed("siteSettings.dashboard_general_tab_activity_metrics") activityMetrics(metrics) { return (metrics || "").split("|").filter(m => m); }, - @computed + @discourseComputed activityMetricsFilters() { return { startDate: this.lastMonth, @@ -33,14 +34,14 @@ export default Controller.extend(PeriodComputationMixin, { }; }, - @computed + @discourseComputed topReferredTopicsOptions() { return { table: { total: false, limit: 8 } }; }, - @computed + @discourseComputed topReferredTopicsFilters() { return { startDate: moment() @@ -50,7 +51,7 @@ export default Controller.extend(PeriodComputationMixin, { }; }, - @computed + @discourseComputed trendingSearchFilters() { return { startDate: moment() @@ -60,14 +61,14 @@ export default Controller.extend(PeriodComputationMixin, { }; }, - @computed + @discourseComputed trendingSearchOptions() { return { table: { total: false, limit: 8 } }; }, - @computed + @discourseComputed trendingSearchDisabledLabel() { return I18n.t("admin.dashboard.reports.trending_search.disabled", { basePath: Discourse.BaseUri @@ -107,7 +108,7 @@ export default Controller.extend(PeriodComputationMixin, { } }, - @computed("startDate", "endDate") + @discourseComputed("startDate", "endDate") filters(startDate, endDate) { return { startDate, endDate }; }, diff --git a/app/assets/javascripts/admin/controllers/admin-dashboard-moderation.js.es6 b/app/assets/javascripts/admin/controllers/admin-dashboard-moderation.js.es6 index e5d8dae25a..8925825fba 100644 --- a/app/assets/javascripts/admin/controllers/admin-dashboard-moderation.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-dashboard-moderation.js.es6 @@ -1,9 +1,9 @@ +import discourseComputed from "discourse-common/utils/decorators"; import Controller from "@ember/controller"; -import computed from "ember-addons/ember-computed-decorators"; import PeriodComputationMixin from "admin/mixins/period-computation"; export default Controller.extend(PeriodComputationMixin, { - @computed + @discourseComputed flagsStatusOptions() { return { table: { @@ -13,7 +13,7 @@ export default Controller.extend(PeriodComputationMixin, { }; }, - @computed + @discourseComputed userFlaggingRatioOptions() { return { table: { @@ -23,12 +23,12 @@ export default Controller.extend(PeriodComputationMixin, { }; }, - @computed("startDate", "endDate") + @discourseComputed("startDate", "endDate") filters(startDate, endDate) { return { startDate, endDate }; }, - @computed("lastWeek", "endDate") + @discourseComputed("lastWeek", "endDate") lastWeekfilters(startDate, endDate) { return { startDate, endDate }; }, diff --git a/app/assets/javascripts/admin/controllers/admin-dashboard-reports.js.es6 b/app/assets/javascripts/admin/controllers/admin-dashboard-reports.js.es6 index b582f733aa..9a57b9cf32 100644 --- a/app/assets/javascripts/admin/controllers/admin-dashboard-reports.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-dashboard-reports.js.es6 @@ -1,12 +1,13 @@ +import discourseComputed from "discourse-common/utils/decorators"; import { debounce } from "@ember/runloop"; import Controller from "@ember/controller"; -import computed from "ember-addons/ember-computed-decorators"; + const { get } = Ember; export default Controller.extend({ filter: null, - @computed("model.[]", "filter") + @discourseComputed("model.[]", "filter") filterReports(reports, filter) { if (filter) { filter = filter.toLowerCase(); diff --git a/app/assets/javascripts/admin/controllers/admin-dashboard.js.es6 b/app/assets/javascripts/admin/controllers/admin-dashboard.js.es6 index 0f214d6f2d..bd8561abc1 100644 --- a/app/assets/javascripts/admin/controllers/admin-dashboard.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-dashboard.js.es6 @@ -1,7 +1,7 @@ +import discourseComputed from "discourse-common/utils/decorators"; import { inject } from "@ember/controller"; import Controller from "@ember/controller"; import { setting } from "discourse/lib/computed"; -import computed from "ember-addons/ember-computed-decorators"; import AdminDashboard from "admin/models/admin-dashboard"; import VersionCheck from "admin/models/version-check"; @@ -13,7 +13,7 @@ export default Controller.extend({ exceptionController: inject("exception"), showVersionChecks: setting("version_checks"), - @computed("problems.length") + @discourseComputed("problems.length") foundProblems(problemsLength) { return this.currentUser.get("admin") && (problemsLength || 0) > 0; }, @@ -77,7 +77,7 @@ export default Controller.extend({ .finally(() => this.set("loadingProblems", false)); }, - @computed("problemsFetchedAt") + @discourseComputed("problemsFetchedAt") problemsTimestamp(problemsFetchedAt) { return moment(problemsFetchedAt) .locale("en") 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 535fa4bca1..17cb8ed04f 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,8 @@ import AdminEmailLogsController from "admin/controllers/admin-email-logs"; -import debounce from "discourse/lib/debounce"; +import discourseDebounce from "discourse/lib/debounce"; export default AdminEmailLogsController.extend({ - filterEmailLogs: debounce(function() { + filterEmailLogs: discourseDebounce(function() { this.loadLogs(); }, 250).observes("filter.{status,user,address,type}") }); 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 7659e61edd..7dc733e00c 100644 --- a/app/assets/javascripts/admin/controllers/admin-email-received.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-email-received.js.es6 @@ -1,9 +1,9 @@ import AdminEmailLogsController from "admin/controllers/admin-email-logs"; -import debounce from "discourse/lib/debounce"; +import discourseDebounce from "discourse/lib/debounce"; import IncomingEmail from "admin/models/incoming-email"; export default AdminEmailLogsController.extend({ - filterIncomingEmails: debounce(function() { + filterIncomingEmails: discourseDebounce(function() { this.loadLogs(IncomingEmail); }, 250).observes("filter.{status,from,to,subject}"), 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 602bb052ce..d70efb23ce 100644 --- a/app/assets/javascripts/admin/controllers/admin-email-rejected.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-email-rejected.js.es6 @@ -1,9 +1,9 @@ import AdminEmailLogsController from "admin/controllers/admin-email-logs"; -import debounce from "discourse/lib/debounce"; +import discourseDebounce from "discourse/lib/debounce"; import IncomingEmail from "admin/models/incoming-email"; export default AdminEmailLogsController.extend({ - filterIncomingEmails: debounce(function() { + filterIncomingEmails: discourseDebounce(function() { this.loadLogs(IncomingEmail); }, 250).observes("filter.{status,from,to,subject,error}"), 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 83f52d3510..c7ddaa0043 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,8 @@ import AdminEmailLogsController from "admin/controllers/admin-email-logs"; -import debounce from "discourse/lib/debounce"; +import discourseDebounce from "discourse/lib/debounce"; export default AdminEmailLogsController.extend({ - filterEmailLogs: debounce(function() { + filterEmailLogs: discourseDebounce(function() { this.loadLogs(); }, 250).observes("filter.{status,user,address,type,reply_key}") }); 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 535fa4bca1..17cb8ed04f 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,8 @@ import AdminEmailLogsController from "admin/controllers/admin-email-logs"; -import debounce from "discourse/lib/debounce"; +import discourseDebounce from "discourse/lib/debounce"; export default AdminEmailLogsController.extend({ - filterEmailLogs: debounce(function() { + filterEmailLogs: discourseDebounce(function() { this.loadLogs(); }, 250).observes("filter.{status,user,address,type}") }); diff --git a/app/assets/javascripts/admin/controllers/admin-embedding.js.es6 b/app/assets/javascripts/admin/controllers/admin-embedding.js.es6 index 7e185e9d12..b71c173e36 100644 --- a/app/assets/javascripts/admin/controllers/admin-embedding.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-embedding.js.es6 @@ -1,5 +1,5 @@ +import discourseComputed from "discourse-common/utils/decorators"; import Controller from "@ember/controller"; -import computed from "ember-addons/ember-computed-decorators"; import { popupAjaxError } from "discourse/lib/ajax-error"; export default Controller.extend({ @@ -7,13 +7,13 @@ export default Controller.extend({ embedding: null, // show settings if we have at least one created host - @computed("embedding.embeddable_hosts.@each.isCreated") + @discourseComputed("embedding.embeddable_hosts.@each.isCreated") showSecondary() { const hosts = this.get("embedding.embeddable_hosts"); return hosts.length && hosts.findBy("isCreated"); }, - @computed("embedding.base_url") + @discourseComputed("embedding.base_url") embeddingCode(baseUrl) { const html = `
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 2e404eb29d..e5da638f9a 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 @@ -1,5 +1,5 @@ import Controller from "@ember/controller"; -import debounce from "discourse/lib/debounce"; +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"; @@ -9,7 +9,7 @@ export default Controller.extend({ filter: null, savedIpAddress: null, - show: debounce(function() { + show: discourseDebounce(function() { this.set("loading", true); ScreenedIpAddress.findAll(this.filter).then(result => { this.setProperties({ model: result, loading: false }); 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 a375379fc0..e559bc846f 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 @@ -5,9 +5,9 @@ import Controller from "@ember/controller"; import { exportEntity } from "discourse/lib/export-csv"; import { outputExportResult } from "discourse/lib/export-result"; import { - default as computed, + default as discourseComputed, on -} from "ember-addons/ember-computed-decorators"; +} from "discourse-common/utils/decorators"; export default Controller.extend({ model: null, @@ -15,7 +15,7 @@ export default Controller.extend({ filtersExists: gt("filterCount", 0), userHistoryActions: null, - @computed("filters.action_name") + @discourseComputed("filters.action_name") actionFilter(name) { return name ? I18n.t("admin.logs.staff_actions.actions." + name) : null; }, diff --git a/app/assets/javascripts/admin/controllers/admin-permalinks.js.es6 b/app/assets/javascripts/admin/controllers/admin-permalinks.js.es6 index d024c83051..29c076c30c 100644 --- a/app/assets/javascripts/admin/controllers/admin-permalinks.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-permalinks.js.es6 @@ -1,12 +1,12 @@ import Controller from "@ember/controller"; -import debounce from "discourse/lib/debounce"; +import discourseDebounce from "discourse/lib/debounce"; import Permalink from "admin/models/permalink"; export default Controller.extend({ loading: false, filter: null, - show: debounce(function() { + show: discourseDebounce(function() { Permalink.findAll(this.filter).then(result => { this.set("model", result); this.set("loading", false); diff --git a/app/assets/javascripts/admin/controllers/admin-plugins.js.es6 b/app/assets/javascripts/admin/controllers/admin-plugins.js.es6 index c0322317e5..f9b34e70a4 100644 --- a/app/assets/javascripts/admin/controllers/admin-plugins.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-plugins.js.es6 @@ -1,8 +1,8 @@ +import discourseComputed from "discourse-common/utils/decorators"; import Controller from "@ember/controller"; -import computed from "ember-addons/ember-computed-decorators"; export default Controller.extend({ - @computed + @discourseComputed adminRoutes: function() { return this.model .map(p => { diff --git a/app/assets/javascripts/admin/controllers/admin-reports-show.js.es6 b/app/assets/javascripts/admin/controllers/admin-reports-show.js.es6 index 359be15f1d..6d302204ce 100644 --- a/app/assets/javascripts/admin/controllers/admin-reports-show.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-reports-show.js.es6 @@ -1,5 +1,5 @@ +import discourseComputed from "discourse-common/utils/decorators"; import Controller from "@ember/controller"; -import computed from "ember-addons/ember-computed-decorators"; export default Controller.extend({ queryParams: ["start_date", "end_date", "filters"], @@ -7,7 +7,7 @@ export default Controller.extend({ end_date: null, filters: null, - @computed("model.type") + @discourseComputed("model.type") reportOptions(type) { let options = { table: { perPage: 50, limit: 50, formatNumbers: false } }; diff --git a/app/assets/javascripts/admin/controllers/admin-site-settings-category.js.es6 b/app/assets/javascripts/admin/controllers/admin-site-settings-category.js.es6 index 3fd10f15d1..bfd727e6ea 100644 --- a/app/assets/javascripts/admin/controllers/admin-site-settings-category.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-site-settings-category.js.es6 @@ -1,17 +1,17 @@ +import discourseComputed from "discourse-common/utils/decorators"; import { inject } from "@ember/controller"; import Controller from "@ember/controller"; -import computed from "ember-addons/ember-computed-decorators"; export default Controller.extend({ categoryNameKey: null, adminSiteSettings: inject(), - @computed("adminSiteSettings.visibleSiteSettings", "categoryNameKey") + @discourseComputed("adminSiteSettings.visibleSiteSettings", "categoryNameKey") category(categories, nameKey) { return (categories || []).findBy("nameKey", nameKey); }, - @computed("category") + @discourseComputed("category") filteredContent(category) { return category ? category.siteSettings : []; } 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 fe35885617..052f7c567d 100644 --- a/app/assets/javascripts/admin/controllers/admin-site-settings.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-site-settings.js.es6 @@ -1,7 +1,7 @@ import { isEmpty } from "@ember/utils"; import { alias } from "@ember/object/computed"; import Controller from "@ember/controller"; -import debounce from "discourse/lib/debounce"; +import discourseDebounce from "discourse/lib/debounce"; export default Controller.extend({ filter: null, @@ -76,7 +76,7 @@ export default Controller.extend({ ); }, - filterContent: debounce(function() { + filterContent: discourseDebounce(function() { if (this._skipBounce) { this.set("_skipBounce", false); } else { diff --git a/app/assets/javascripts/admin/controllers/admin-site-text-edit.js.es6 b/app/assets/javascripts/admin/controllers/admin-site-text-edit.js.es6 index cd815b9ae8..d24a172910 100644 --- a/app/assets/javascripts/admin/controllers/admin-site-text-edit.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-site-text-edit.js.es6 @@ -1,12 +1,12 @@ +import discourseComputed from "discourse-common/utils/decorators"; import Controller from "@ember/controller"; import { popupAjaxError } from "discourse/lib/ajax-error"; import { bufferedProperty } from "discourse/mixins/buffered-content"; -import computed from "ember-addons/ember-computed-decorators"; export default Controller.extend(bufferedProperty("siteText"), { saved: false, - @computed("buffered.value") + @discourseComputed("buffered.value") saveDisabled(value) { return this.siteText.value === value; }, diff --git a/app/assets/javascripts/admin/controllers/admin-user-badges.js.es6 b/app/assets/javascripts/admin/controllers/admin-user-badges.js.es6 index fac3436c6e..3bedafb45c 100644 --- a/app/assets/javascripts/admin/controllers/admin-user-badges.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-user-badges.js.es6 @@ -1,10 +1,10 @@ +import discourseComputed from "discourse-common/utils/decorators"; import { alias, sort } from "@ember/object/computed"; import { next } from "@ember/runloop"; import { inject } from "@ember/controller"; import Controller from "@ember/controller"; import GrantBadgeController from "discourse/mixins/grant-badge-controller"; import { popupAjaxError } from "discourse/lib/ajax-error"; -import computed from "ember-addons/ember-computed-decorators"; export default Controller.extend(GrantBadgeController, { adminUser: inject(), @@ -19,7 +19,7 @@ export default Controller.extend(GrantBadgeController, { this.badgeSortOrder = ["granted_at:desc"]; }, - @computed("model", "model.[]", "model.expandedBadges.[]") + @discourseComputed("model", "model.[]", "model.expandedBadges.[]") groupedBadges() { const allBadges = this.model; 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 de2e37668f..765e8d3686 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 computed } from "ember-addons/ember-computed-decorators"; +import { default as discourseComputed } from "discourse-common/utils/decorators"; import { fmt } from "discourse/lib/computed"; import { htmlSafe } from "@ember/template"; @@ -30,12 +30,12 @@ export default Controller.extend(CanCheckEmails, { "model.can_disable_second_factor" ), - @computed("model.customGroups") + @discourseComputed("model.customGroups") customGroupIds(customGroups) { return customGroups.mapBy("id"); }, - @computed("customGroupIdsBuffer", "customGroupIds") + @discourseComputed("customGroupIdsBuffer", "customGroupIds") customGroupsDirty(buffer, original) { if (buffer === null) return false; @@ -44,7 +44,7 @@ export default Controller.extend(CanCheckEmails, { : true; }, - @computed("model.automaticGroups") + @discourseComputed("model.automaticGroups") automaticGroups(automaticGroups) { return automaticGroups .map(group => { @@ -54,26 +54,30 @@ export default Controller.extend(CanCheckEmails, { .join(", "); }, - @computed("model.associated_accounts") + @discourseComputed("model.associated_accounts") associatedAccountsLoaded(associatedAccounts) { return typeof associatedAccounts !== "undefined"; }, - @computed("model.associated_accounts") + @discourseComputed("model.associated_accounts") associatedAccounts(associatedAccounts) { return associatedAccounts .map(provider => `${provider.name} (${provider.description})`) .join(", "); }, - @computed("model.user_fields.[]") + @discourseComputed("model.user_fields.[]") userFields(userFields) { return this.site.collectUserFields(userFields); }, preferencesPath: fmt("model.username_lower", userPath("%@/preferences")), - @computed("model.can_delete_all_posts", "model.staff", "model.post_count") + @discourseComputed( + "model.can_delete_all_posts", + "model.staff", + "model.post_count" + ) deleteAllPostsExplanation(canDeleteAllPosts, staff, postCount) { if (canDeleteAllPosts) { return null; @@ -93,7 +97,7 @@ export default Controller.extend(CanCheckEmails, { } }, - @computed("model.canBeDeleted", "model.staff") + @discourseComputed("model.canBeDeleted", "model.staff") deleteExplanation(canBeDeleted, staff) { if (canBeDeleted) { return null; 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 944b0d9485..12d4724862 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,9 +1,9 @@ +import discourseComputed from "discourse-common/utils/decorators"; import Controller from "@ember/controller"; -import debounce from "discourse/lib/debounce"; +import discourseDebounce from "discourse/lib/debounce"; import { i18n } from "discourse/lib/computed"; import AdminUser from "admin/models/admin-user"; import CanCheckEmails from "discourse/mixins/can-check-emails"; -import computed from "ember-addons/ember-computed-decorators"; export default Controller.extend(CanCheckEmails, { model: null, @@ -24,12 +24,12 @@ export default Controller.extend(CanCheckEmails, { this._canLoadMore = true; }, - @computed("query") + @discourseComputed("query") title(query) { return I18n.t("admin.users.titles." + query); }, - _filterUsers: debounce(function() { + _filterUsers: discourseDebounce(function() { this.resetFilters(); }, 250).observes("listFilter"), diff --git a/app/assets/javascripts/admin/controllers/admin-watched-words-action.js.es6 b/app/assets/javascripts/admin/controllers/admin-watched-words-action.js.es6 index 1726446d8c..d6f52c13fd 100644 --- a/app/assets/javascripts/admin/controllers/admin-watched-words-action.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-watched-words-action.js.es6 @@ -1,8 +1,8 @@ +import discourseComputed from "discourse-common/utils/decorators"; import { or } from "@ember/object/computed"; import { schedule } from "@ember/runloop"; import { inject } from "@ember/controller"; import Controller from "@ember/controller"; -import computed from "ember-addons/ember-computed-decorators"; import WatchedWord from "admin/models/watched-word"; import { ajax } from "discourse/lib/ajax"; import { fmt } from "discourse/lib/computed"; @@ -27,22 +27,22 @@ export default Controller.extend({ ); }, - @computed("actionNameKey", "adminWatchedWords.model") + @discourseComputed("actionNameKey", "adminWatchedWords.model") currentAction(actionName) { return this.findAction(actionName); }, - @computed("currentAction.words.[]", "adminWatchedWords.model") + @discourseComputed("currentAction.words.[]", "adminWatchedWords.model") filteredContent(words) { return words || []; }, - @computed("actionNameKey") + @discourseComputed("actionNameKey") actionDescription(actionNameKey) { return I18n.t("admin.watched_words.action_descriptions." + actionNameKey); }, - @computed("currentAction.count") + @discourseComputed("currentAction.count") wordCount(count) { return count || 0; }, 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 397c5b030e..84a7ac7939 100644 --- a/app/assets/javascripts/admin/controllers/admin-watched-words.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-watched-words.js.es6 @@ -2,7 +2,7 @@ import { isEmpty } from "@ember/utils"; import { alias } from "@ember/object/computed"; import EmberObject from "@ember/object"; import Controller from "@ember/controller"; -import debounce from "discourse/lib/debounce"; +import discourseDebounce from "discourse/lib/debounce"; export default Controller.extend({ filter: null, @@ -43,7 +43,7 @@ export default Controller.extend({ this.set("model", matchesByAction); }, - filterContent: debounce(function() { + filterContent: discourseDebounce(function() { this.filterContentNow(); this.set("filtered", !isEmpty(this.filter)); }, 250).observes("filter"), diff --git a/app/assets/javascripts/admin/controllers/admin-web-hooks-show-events.js.es6 b/app/assets/javascripts/admin/controllers/admin-web-hooks-show-events.js.es6 index e550a79069..6cd94efb68 100644 --- a/app/assets/javascripts/admin/controllers/admin-web-hooks-show-events.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-web-hooks-show-events.js.es6 @@ -1,8 +1,8 @@ +import discourseComputed from "discourse-common/utils/decorators"; import { alias } from "@ember/object/computed"; import Controller from "@ember/controller"; import { ajax } from "discourse/lib/ajax"; import { popupAjaxError } from "discourse/lib/ajax-error"; -import computed from "ember-addons/ember-computed-decorators"; export default Controller.extend({ pingDisabled: false, @@ -14,7 +14,7 @@ export default Controller.extend({ this.incomingEventIds = []; }, - @computed("incomingCount") + @discourseComputed("incomingCount") hasIncoming(incomingCount) { return incomingCount > 0; }, diff --git a/app/assets/javascripts/admin/controllers/admin-web-hooks-show.js.es6 b/app/assets/javascripts/admin/controllers/admin-web-hooks-show.js.es6 index 83de79e1f0..4ba34034f3 100644 --- a/app/assets/javascripts/admin/controllers/admin-web-hooks-show.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-web-hooks-show.js.es6 @@ -1,11 +1,11 @@ +import discourseComputed from "discourse-common/utils/decorators"; import { isEmpty } from "@ember/utils"; import { alias } from "@ember/object/computed"; import { inject } from "@ember/controller"; import Controller from "@ember/controller"; import { popupAjaxError } from "discourse/lib/ajax-error"; import { extractDomainFromUrl } from "discourse/lib/utilities"; -import computed from "ember-addons/ember-computed-decorators"; -import InputValidation from "discourse/models/input-validation"; +import EmberObject from "@ember/object"; export default Controller.extend({ adminWebHooks: inject(), @@ -13,12 +13,12 @@ export default Controller.extend({ defaultEventTypes: alias("adminWebHooks.defaultEventTypes"), contentTypes: alias("adminWebHooks.contentTypes"), - @computed + @discourseComputed showTagsFilter() { return this.siteSettings.tagging_enabled; }, - @computed("model.isSaving", "saved", "saveButtonDisabled") + @discourseComputed("model.isSaving", "saved", "saveButtonDisabled") savingStatus(isSaving, saved, saveButtonDisabled) { if (isSaving) { return I18n.t("saving"); @@ -30,25 +30,25 @@ export default Controller.extend({ return ""; }, - @computed("model.isNew") + @discourseComputed("model.isNew") saveButtonText(isNew) { return isNew ? I18n.t("admin.web_hooks.create") : I18n.t("admin.web_hooks.save"); }, - @computed("model.secret") + @discourseComputed("model.secret") secretValidation(secret) { if (!isEmpty(secret)) { if (secret.indexOf(" ") !== -1) { - return InputValidation.create({ + return EmberObject.create({ failed: true, reason: I18n.t("admin.web_hooks.secret_invalid") }); } if (secret.length < 12) { - return InputValidation.create({ + return EmberObject.create({ failed: true, reason: I18n.t("admin.web_hooks.secret_too_short") }); @@ -56,17 +56,17 @@ export default Controller.extend({ } }, - @computed("model.wildcard_web_hook", "model.web_hook_event_types.[]") + @discourseComputed("model.wildcard_web_hook", "model.web_hook_event_types.[]") eventTypeValidation(isWildcard, eventTypes) { if (!isWildcard && isEmpty(eventTypes)) { - return InputValidation.create({ + return EmberObject.create({ failed: true, reason: I18n.t("admin.web_hooks.event_type_missing") }); } }, - @computed( + @discourseComputed( "model.isSaving", "secretValidation", "eventTypeValidation", diff --git a/app/assets/javascripts/admin/controllers/admin.js.es6 b/app/assets/javascripts/admin/controllers/admin.js.es6 index f01a898b0c..641643d573 100644 --- a/app/assets/javascripts/admin/controllers/admin.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin.js.es6 @@ -1,22 +1,22 @@ +import discourseComputed from "discourse-common/utils/decorators"; import { inject as service } from "@ember/service"; import Controller from "@ember/controller"; -import computed from "ember-addons/ember-computed-decorators"; import { dasherize } from "@ember/string"; export default Controller.extend({ router: service(), - @computed("siteSettings.enable_group_directory") + @discourseComputed("siteSettings.enable_group_directory") showGroups(enableGroupDirectory) { return !enableGroupDirectory; }, - @computed("siteSettings.enable_badges") + @discourseComputed("siteSettings.enable_badges") showBadges(enableBadges) { return this.currentUser.get("admin") && enableBadges; }, - @computed("router._router.currentPath") + @discourseComputed("router._router.currentPath") adminContentsClassName(currentPath) { let cssClasses = currentPath .split(".") 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 d53278c856..bd246d9371 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 @@ -5,9 +5,9 @@ import Controller from "@ember/controller"; import ModalFunctionality from "discourse/mixins/modal-functionality"; import { ajax } from "discourse/lib/ajax"; import { - default as computed, + default as discourseComputed, observes -} from "ember-addons/ember-computed-decorators"; +} from "discourse-common/utils/decorators"; import { popupAjaxError } from "discourse/lib/ajax-error"; const THEME_FIELD_VARIABLE_TYPE_IDS = [2, 3, 4]; @@ -69,7 +69,7 @@ export default Controller.extend(ModalFunctionality, { enabled: and("nameValid", "fileSelected"), disabled: not("enabled"), - @computed("name", "adminCustomizeThemesShow.model.theme_fields") + @discourseComputed("name", "adminCustomizeThemesShow.model.theme_fields") errorMessage(name, themeFields) { if (name) { if (!name.match(/^[a-z_][a-z0-9_-]*$/i)) { @@ -94,7 +94,7 @@ export default Controller.extend(ModalFunctionality, { return null; }, - @computed("errorMessage") + @discourseComputed("errorMessage") nameValid(errorMessage) { return null === errorMessage; }, 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 b6419c8e04..67450978d7 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 computed } from "ember-addons/ember-computed-decorators"; +import { default as discourseComputed } from "discourse-common/utils/decorators"; import { escapeExpression } from "discourse/lib/utilities"; export default Controller.extend({ @@ -8,7 +8,7 @@ export default Controller.extend({ errors: alias("model.errors"), count: alias("model.grant_count"), - @computed("count", "sample.length") + @discourseComputed("count", "sample.length") countWarning(count, sampleLength) { if (count <= 10) { return sampleLength !== count; @@ -17,12 +17,12 @@ export default Controller.extend({ } }, - @computed("model.query_plan") + @discourseComputed("model.query_plan") hasQueryPlan(queryPlan) { return !!queryPlan; }, - @computed("model.query_plan") + @discourseComputed("model.query_plan") queryPlanHtml(queryPlan) { let output = ``;
diff --git a/app/assets/javascripts/admin/controllers/modals/admin-edit-badge-groupings.js.es6 b/app/assets/javascripts/admin/controllers/modals/admin-edit-badge-groupings.js.es6
index 4d0c66143c..1629aab70b 100644
--- a/app/assets/javascripts/admin/controllers/modals/admin-edit-badge-groupings.js.es6
+++ b/app/assets/javascripts/admin/controllers/modals/admin-edit-badge-groupings.js.es6
@@ -1,7 +1,7 @@
import Controller from "@ember/controller";
import { ajax } from "discourse/lib/ajax";
import ModalFunctionality from "discourse/mixins/modal-functionality";
-import { observes } from "ember-addons/ember-computed-decorators";
+import { observes } from "discourse-common/utils/decorators";
export default Controller.extend(ModalFunctionality, {
@observes("model")
diff --git a/app/assets/javascripts/admin/controllers/modals/admin-incoming-email.js.es6 b/app/assets/javascripts/admin/controllers/modals/admin-incoming-email.js.es6
index 210d664cda..cca2cc54bf 100644
--- a/app/assets/javascripts/admin/controllers/modals/admin-incoming-email.js.es6
+++ b/app/assets/javascripts/admin/controllers/modals/admin-incoming-email.js.es6
@@ -1,12 +1,12 @@
+import discourseComputed from "discourse-common/utils/decorators";
import Controller from "@ember/controller";
import ModalFunctionality from "discourse/mixins/modal-functionality";
import IncomingEmail from "admin/models/incoming-email";
-import computed from "ember-addons/ember-computed-decorators";
import { longDate } from "discourse/lib/formatter";
import { popupAjaxError } from "discourse/lib/ajax-error";
export default Controller.extend(ModalFunctionality, {
- @computed("model.date")
+ @discourseComputed("model.date")
date(d) {
return longDate(d);
},
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 81055d733d..9aee44f72e 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
@@ -5,9 +5,9 @@ import ModalFunctionality from "discourse/mixins/modal-functionality";
import { ajax } from "discourse/lib/ajax";
import { popupAjaxError } from "discourse/lib/ajax-error";
import {
- default as computed,
+ default as discourseComputed,
observes
-} from "ember-addons/ember-computed-decorators";
+} 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";
@@ -43,7 +43,7 @@ export default Controller.extend(ModalFunctionality, {
];
},
- @computed("themesController.installedThemes")
+ @discourseComputed("themesController.installedThemes")
themes(installedThemes) {
return POPULAR_THEMES.map(t => {
if (installedThemes.includes(t.name)) {
@@ -53,7 +53,7 @@ export default Controller.extend(ModalFunctionality, {
});
},
- @computed(
+ @discourseComputed(
"loading",
"remote",
"uploadUrl",
@@ -102,12 +102,12 @@ export default Controller.extend(ModalFunctionality, {
}
},
- @computed("name")
+ @discourseComputed("name")
nameTooShort(name) {
return !name || name.length < MIN_NAME_LENGTH;
},
- @computed("component")
+ @discourseComputed("component")
placeholder(component) {
if (component) {
return I18n.t("admin.customize.theme.component_name");
@@ -116,14 +116,14 @@ export default Controller.extend(ModalFunctionality, {
}
},
- @computed("selection")
+ @discourseComputed("selection")
submitLabel(selection) {
return `admin.customize.theme.${
selection === "create" ? "create" : "install"
}`;
},
- @computed("privateChecked", "checkPrivate", "publicKey")
+ @discourseComputed("privateChecked", "checkPrivate", "publicKey")
showPublicKey(privateChecked, checkPrivate, publicKey) {
return privateChecked && checkPrivate && publicKey;
},
diff --git a/app/assets/javascripts/admin/controllers/modals/admin-silence-user.js.es6 b/app/assets/javascripts/admin/controllers/modals/admin-silence-user.js.es6
index 5c04066941..d15264f46b 100644
--- a/app/assets/javascripts/admin/controllers/modals/admin-silence-user.js.es6
+++ b/app/assets/javascripts/admin/controllers/modals/admin-silence-user.js.es6
@@ -1,6 +1,6 @@
+import discourseComputed from "discourse-common/utils/decorators";
import { isEmpty } from "@ember/utils";
import Controller from "@ember/controller";
-import computed from "ember-addons/ember-computed-decorators";
import PenaltyController from "admin/mixins/penalty-controller";
export default Controller.extend(PenaltyController, {
@@ -12,7 +12,7 @@ export default Controller.extend(PenaltyController, {
this.setProperties({ silenceUntil: null, silencing: false });
},
- @computed("silenceUntil", "reason", "silencing")
+ @discourseComputed("silenceUntil", "reason", "silencing")
submitDisabled(silenceUntil, reason, silencing) {
return silencing || isEmpty(silenceUntil) || !reason || reason.length < 1;
},
diff --git a/app/assets/javascripts/admin/controllers/modals/admin-suspend-user.js.es6 b/app/assets/javascripts/admin/controllers/modals/admin-suspend-user.js.es6
index c5afea9d88..03fa9fbc83 100644
--- a/app/assets/javascripts/admin/controllers/modals/admin-suspend-user.js.es6
+++ b/app/assets/javascripts/admin/controllers/modals/admin-suspend-user.js.es6
@@ -1,6 +1,6 @@
+import discourseComputed from "discourse-common/utils/decorators";
import { isEmpty } from "@ember/utils";
import Controller from "@ember/controller";
-import computed from "ember-addons/ember-computed-decorators";
import PenaltyController from "admin/mixins/penalty-controller";
export default Controller.extend(PenaltyController, {
@@ -12,7 +12,7 @@ export default Controller.extend(PenaltyController, {
this.setProperties({ suspendUntil: null, suspending: false });
},
- @computed("suspendUntil", "reason", "suspending")
+ @discourseComputed("suspendUntil", "reason", "suspending")
submitDisabled(suspendUntil, reason, suspending) {
return suspending || isEmpty(suspendUntil) || !reason || reason.length < 1;
},
diff --git a/app/assets/javascripts/admin/controllers/modals/admin-uploaded-image-list.js.es6 b/app/assets/javascripts/admin/controllers/modals/admin-uploaded-image-list.js.es6
index a5ac891c21..08e1e178e4 100644
--- a/app/assets/javascripts/admin/controllers/modals/admin-uploaded-image-list.js.es6
+++ b/app/assets/javascripts/admin/controllers/modals/admin-uploaded-image-list.js.es6
@@ -1,5 +1,5 @@
import Controller from "@ember/controller";
-import { on, observes } from "ember-addons/ember-computed-decorators";
+import { on, observes } from "discourse-common/utils/decorators";
import ModalFunctionality from "discourse/mixins/modal-functionality";
export default Controller.extend(ModalFunctionality, {
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 10f90ee615..9d9b73072d 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,9 +1,9 @@
import Controller from "@ember/controller";
-import { default as computed } from "ember-addons/ember-computed-decorators";
+import { default as discourseComputed } from "discourse-common/utils/decorators";
import ModalFunctionality from "discourse/mixins/modal-functionality";
export default Controller.extend(ModalFunctionality, {
- @computed("value", "model.compiledRegularExpression")
+ @discourseComputed("value", "model.compiledRegularExpression")
matches(value, regexpString) {
if (!value || !regexpString) return;
let censorRegexp = new RegExp(regexpString, "ig");
diff --git a/app/assets/javascripts/admin/mixins/period-computation.js.es6 b/app/assets/javascripts/admin/mixins/period-computation.js.es6
index 354fd0ad85..c7af0e4cb3 100644
--- a/app/assets/javascripts/admin/mixins/period-computation.js.es6
+++ b/app/assets/javascripts/admin/mixins/period-computation.js.es6
@@ -1,5 +1,5 @@
+import discourseComputed from "discourse-common/utils/decorators";
import DiscourseURL from "discourse/lib/url";
-import computed from "ember-addons/ember-computed-decorators";
import Mixin from "@ember/object/mixin";
export default Mixin.create({
@@ -12,7 +12,7 @@ export default Mixin.create({
this.availablePeriods = ["yearly", "quarterly", "monthly", "weekly"];
},
- @computed("period")
+ @discourseComputed("period")
startDate(period) {
let fullDay = moment()
.locale("en")
@@ -37,7 +37,7 @@ export default Mixin.create({
}
},
- @computed()
+ @discourseComputed()
lastWeek() {
return moment()
.locale("en")
@@ -46,7 +46,7 @@ export default Mixin.create({
.subtract(1, "week");
},
- @computed()
+ @discourseComputed()
lastMonth() {
return moment()
.locale("en")
@@ -55,7 +55,7 @@ export default Mixin.create({
.subtract(1, "month");
},
- @computed()
+ @discourseComputed()
endDate() {
return moment()
.locale("en")
@@ -64,7 +64,7 @@ export default Mixin.create({
.endOf("day");
},
- @computed()
+ @discourseComputed()
today() {
return moment()
.locale("en")
diff --git a/app/assets/javascripts/admin/mixins/setting-component.js.es6 b/app/assets/javascripts/admin/mixins/setting-component.js.es6
index 762d5cfa2b..4d7ea03c34 100644
--- a/app/assets/javascripts/admin/mixins/setting-component.js.es6
+++ b/app/assets/javascripts/admin/mixins/setting-component.js.es6
@@ -1,11 +1,11 @@
+import discourseComputed from "discourse-common/utils/decorators";
import { alias, oneWay } from "@ember/object/computed";
-import computed from "ember-addons/ember-computed-decorators";
import { categoryLinkHTML } from "discourse/helpers/category-link";
import { on } from "@ember/object/evented";
import Mixin from "@ember/object/mixin";
import showModal from "discourse/lib/show-modal";
-import AboutRoute from "discourse/routes/about";
import { Promise } from "rsvp";
+import { ajax } from "discourse/lib/ajax";
const CUSTOM_TYPES = [
"bool",
@@ -26,13 +26,21 @@ 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"),
- @computed("buffered.value", "setting.value")
+ @discourseComputed("buffered.value", "setting.value")
dirty(bufferVal, settingVal) {
if (bufferVal === null || bufferVal === undefined) bufferVal = "";
if (settingVal === null || settingVal === undefined) settingVal = "";
@@ -40,7 +48,7 @@ export default Mixin.create({
return bufferVal.toString() !== settingVal.toString();
},
- @computed("setting", "buffered.value")
+ @discourseComputed("setting", "buffered.value")
preview(setting, value) {
// A bit hacky, but allows us to use helpers
if (setting.get("setting") === "category_style") {
@@ -51,7 +59,6 @@ export default Mixin.create({
});
}
}
-
let preview = setting.get("preview");
if (preview) {
return new Handlebars.SafeString(
@@ -62,22 +69,22 @@ export default Mixin.create({
}
},
- @computed("componentType")
+ @discourseComputed("componentType")
typeClass(componentType) {
return componentType.replace(/\_/g, "-");
},
- @computed("setting.setting")
- settingName(setting) {
- return setting.replace(/\_/g, " ");
+ @discourseComputed("setting.setting", "setting.label")
+ settingName(setting, label) {
+ return label || setting.replace(/\_/g, " ");
},
- @computed("type")
+ @discourseComputed("type")
componentType(type) {
return CUSTOM_TYPES.indexOf(type) !== -1 ? type : "string";
},
- @computed("setting")
+ @discourseComputed("setting")
type(setting) {
if (setting.type === "list" && setting.list_type) {
return `${setting.list_type}_list`;
@@ -86,16 +93,36 @@ export default Mixin.create({
return setting.type;
},
- @computed("typeClass")
+ @discourseComputed("typeClass")
componentName(typeClass) {
return "site-settings/" + typeClass;
},
- @computed("setting.default", "buffered.value")
+ @discourseComputed("setting.anyValue")
+ allowAny(anyValue) {
+ return anyValue !== false;
+ },
+
+ @discourseComputed("setting.default", "buffered.value")
overridden(settingDefault, bufferedValue) {
return settingDefault !== bufferedValue;
},
+ @discourseComputed("buffered.value")
+ bufferedValues: splitPipes,
+
+ @discourseComputed("setting.defaultValues")
+ defaultValues: splitPipes,
+
+ @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.keyCode === 13) {
@@ -125,7 +152,6 @@ export default Mixin.create({
"default_email_messages_level",
"default_email_mailing_list_mode",
"default_email_mailing_list_mode_frequency",
- "disable_mailing_list_mode",
"default_email_previous_replies",
"default_email_in_reply_to",
"default_other_new_topic_duration_minutes",
@@ -151,12 +177,19 @@ export default Mixin.create({
const key = this.buffered.get("setting");
if (defaultUserPreferences.includes(key)) {
- AboutRoute.create()
- .model()
- .then(result => {
+ 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.stats.user_count,
+ count: result.user_count,
key: key.replace(/_/g, " ")
},
admin: true
@@ -166,7 +199,10 @@ export default Mixin.create({
this.updateExistingUsers = controller.updateExistingUsers;
this.send("save");
});
- });
+ } else {
+ this.send("save");
+ }
+ });
} else {
this.send("save");
}
@@ -200,6 +236,16 @@ export default Mixin.create({
toggleSecret() {
this.toggleProperty("isSecret");
+ },
+
+ setDefaultValues() {
+ this.set(
+ "buffered.value",
+ this.bufferedValues
+ .concat(this.defaultValues)
+ .uniq()
+ .join("|")
+ );
}
}
});
diff --git a/app/assets/javascripts/admin/mixins/setting-object.js.es6 b/app/assets/javascripts/admin/mixins/setting-object.js.es6
index c02004cea8..f0296e6c55 100644
--- a/app/assets/javascripts/admin/mixins/setting-object.js.es6
+++ b/app/assets/javascripts/admin/mixins/setting-object.js.es6
@@ -1,8 +1,8 @@
-import computed from "ember-addons/ember-computed-decorators";
+import discourseComputed from "discourse-common/utils/decorators";
import Mixin from "@ember/object/mixin";
export default Mixin.create({
- @computed("value", "default")
+ @discourseComputed("value", "default")
overridden(val, defaultVal) {
if (val === null) val = "";
if (defaultVal === null) defaultVal = "";
@@ -10,7 +10,7 @@ export default Mixin.create({
return val.toString() !== defaultVal.toString();
},
- @computed("valid_values")
+ @discourseComputed("valid_values")
validValues(validValues) {
const vals = [],
translateNames = this.translate_names;
@@ -25,7 +25,7 @@ export default Mixin.create({
return vals;
},
- @computed("valid_values")
+ @discourseComputed("valid_values")
allowsNone(validValues) {
if (validValues && validValues.indexOf("") >= 0) {
return "admin.settings.none";
diff --git a/app/assets/javascripts/admin/models/admin-dashboard.js.es6 b/app/assets/javascripts/admin/models/admin-dashboard.js.es6
index 20cdbe2772..756de2cc09 100644
--- a/app/assets/javascripts/admin/models/admin-dashboard.js.es6
+++ b/app/assets/javascripts/admin/models/admin-dashboard.js.es6
@@ -1,4 +1,5 @@
import { ajax } from "discourse/lib/ajax";
+import EmberObject from "@ember/object";
const GENERAL_ATTRIBUTES = [
"updated_at",
@@ -6,7 +7,7 @@ const GENERAL_ATTRIBUTES = [
"release_notes_link"
];
-const AdminDashboard = Discourse.Model.extend({});
+const AdminDashboard = EmberObject.extend({});
AdminDashboard.reopenClass({
fetch() {
diff --git a/app/assets/javascripts/admin/models/admin-user.js.es6 b/app/assets/javascripts/admin/models/admin-user.js.es6
index 880cb44c39..25a822f388 100644
--- a/app/assets/javascripts/admin/models/admin-user.js.es6
+++ b/app/assets/javascripts/admin/models/admin-user.js.es6
@@ -1,23 +1,24 @@
+import discourseComputed from "discourse-common/utils/decorators";
import { filter, or, gt, lt, not } from "@ember/object/computed";
import { iconHTML } from "discourse-common/lib/icon-library";
import { ajax } from "discourse/lib/ajax";
-import computed from "ember-addons/ember-computed-decorators";
import { propertyNotEqual } from "discourse/lib/computed";
import { popupAjaxError } from "discourse/lib/ajax-error";
import Group from "discourse/models/group";
import { userPath } from "discourse/lib/url";
import { Promise } from "rsvp";
+import User from "discourse/models/user";
const wrapAdmin = user => (user ? AdminUser.create(user) : null);
-const AdminUser = Discourse.User.extend({
+const AdminUser = User.extend({
adminUserView: true,
customGroups: filter("groups", g => !g.automatic && Group.create(g)),
automaticGroups: filter("groups", g => g.automatic && Group.create(g)),
canViewProfile: or("active", "staged"),
- @computed("bounce_score", "reset_bounce_score_after")
+ @discourseComputed("bounce_score", "reset_bounce_score_after")
bounceScore(bounce_score, reset_bounce_score_after) {
if (bounce_score > 0) {
return `${bounce_score} - ${moment(reset_bounce_score_after).format(
@@ -28,7 +29,7 @@ const AdminUser = Discourse.User.extend({
}
},
- @computed("bounce_score")
+ @discourseComputed("bounce_score")
bounceScoreExplanation(bounce_score) {
if (bounce_score === 0) {
return I18n.t("admin.user.bounce_score_explanation.none");
@@ -39,7 +40,7 @@ const AdminUser = Discourse.User.extend({
}
},
- @computed
+ @discourseComputed
bounceLink() {
return Discourse.getURL("/admin/email/bounced");
},
@@ -278,7 +279,7 @@ const AdminUser = Discourse.User.extend({
canSuspend: not("staff"),
- @computed("suspended_till", "suspended_at")
+ @discourseComputed("suspended_till", "suspended_at")
suspendDuration(suspendedTill, suspendedAt) {
suspendedAt = moment(suspendedAt);
suspendedTill = moment(suspendedTill);
@@ -513,20 +514,20 @@ const AdminUser = Discourse.User.extend({
});
},
- @computed("tl3_requirements")
+ @discourseComputed("tl3_requirements")
tl3Requirements(requirements) {
if (requirements) {
return this.store.createRecord("tl3Requirements", requirements);
}
},
- @computed("suspended_by")
+ @discourseComputed("suspended_by")
suspendedBy: wrapAdmin,
- @computed("silenced_by")
+ @discourseComputed("silenced_by")
silencedBy: wrapAdmin,
- @computed("approved_by")
+ @discourseComputed("approved_by")
approvedBy: wrapAdmin,
_formatError(event) {
diff --git a/app/assets/javascripts/admin/models/api-key.js.es6 b/app/assets/javascripts/admin/models/api-key.js.es6
index 95d8e1914c..7a20c288ae 100644
--- a/app/assets/javascripts/admin/models/api-key.js.es6
+++ b/app/assets/javascripts/admin/models/api-key.js.es6
@@ -1,10 +1,11 @@
+import discourseComputed from "discourse-common/utils/decorators";
import AdminUser from "admin/models/admin-user";
import RestModel from "discourse/models/rest";
import { ajax } from "discourse/lib/ajax";
-import computed from "ember-addons/ember-computed-decorators";
+import { computed } from "@ember/object";
const ApiKey = RestModel.extend({
- user: Ember.computed("_user", {
+ user: computed("_user", {
get() {
return this._user;
},
@@ -18,12 +19,12 @@ const ApiKey = RestModel.extend({
}
}),
- @computed("key")
+ @discourseComputed("key")
shortKey(key) {
return `${key.substring(0, 4)}...`;
},
- @computed("description")
+ @discourseComputed("description")
shortDescription(description) {
if (!description || description.length < 40) return description;
return `${description.substring(0, 40)}...`;
@@ -45,7 +46,7 @@ const ApiKey = RestModel.extend({
return this.getProperties("description", "username");
},
- @computed()
+ @discourseComputed()
basePath() {
return this.store
.adapterFor("api-key")
diff --git a/app/assets/javascripts/admin/models/backup-status.js.es6 b/app/assets/javascripts/admin/models/backup-status.js.es6
index b7deec1c10..62c360b532 100644
--- a/app/assets/javascripts/admin/models/backup-status.js.es6
+++ b/app/assets/javascripts/admin/models/backup-status.js.es6
@@ -1,10 +1,11 @@
+import discourseComputed from "discourse-common/utils/decorators";
import { not } from "@ember/object/computed";
-import computed from "ember-addons/ember-computed-decorators";
+import EmberObject from "@ember/object";
-export default Discourse.Model.extend({
+export default EmberObject.extend({
restoreDisabled: not("restoreEnabled"),
- @computed("allowRestore", "isOperationRunning")
+ @discourseComputed("allowRestore", "isOperationRunning")
restoreEnabled(allowRestore, isOperationRunning) {
return allowRestore && !isOperationRunning;
}
diff --git a/app/assets/javascripts/admin/models/backup.js.es6 b/app/assets/javascripts/admin/models/backup.js.es6
index 7cd151378a..882173300a 100644
--- a/app/assets/javascripts/admin/models/backup.js.es6
+++ b/app/assets/javascripts/admin/models/backup.js.es6
@@ -1,7 +1,8 @@
import { ajax } from "discourse/lib/ajax";
import { extractError } from "discourse/lib/ajax-error";
+import EmberObject from "@ember/object";
-const Backup = Discourse.Model.extend({
+const Backup = EmberObject.extend({
destroy() {
return ajax("/admin/backups/" + this.filename, { type: "DELETE" });
},
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 a3b22d23bc..2e4907ef9f 100644
--- a/app/assets/javascripts/admin/models/color-scheme-color.js.es6
+++ b/app/assets/javascripts/admin/models/color-scheme-color.js.es6
@@ -1,11 +1,12 @@
import {
- default as computed,
+ default as discourseComputed,
observes,
on
-} from "ember-addons/ember-computed-decorators";
+} from "discourse-common/utils/decorators";
import { propertyNotEqual } from "discourse/lib/computed";
+import EmberObject from "@ember/object";
-const ColorSchemeColor = Discourse.Model.extend({
+const ColorSchemeColor = EmberObject.extend({
@on("init")
startTrackingChanges() {
this.set("originals", { hex: this.hex || "FFFFFF" });
@@ -15,7 +16,7 @@ const ColorSchemeColor = Discourse.Model.extend({
},
// Whether value has changed since it was last saved.
- @computed("hex")
+ @discourseComputed("hex")
changed(hex) {
if (!this.originals) return false;
if (hex !== this.originals.hex) return true;
@@ -27,7 +28,7 @@ const ColorSchemeColor = Discourse.Model.extend({
overridden: propertyNotEqual("hex", "default_hex"),
// Whether the saved value is different than Discourse's default color scheme.
- @computed("default_hex", "hex")
+ @discourseComputed("default_hex", "hex")
savedIsOverriden(defaultHex) {
return this.originals.hex !== defaultHex;
},
@@ -42,7 +43,7 @@ const ColorSchemeColor = Discourse.Model.extend({
}
},
- @computed("name")
+ @discourseComputed("name")
translatedName(name) {
if (!this.is_advanced) {
return I18n.t(`admin.customize.colors.${name}.name`);
@@ -51,7 +52,7 @@ const ColorSchemeColor = Discourse.Model.extend({
}
},
- @computed("name")
+ @discourseComputed("name")
description(name) {
if (!this.is_advanced) {
return I18n.t(`admin.customize.colors.${name}.description`);
@@ -66,7 +67,7 @@ const ColorSchemeColor = Discourse.Model.extend({
@property brightness
**/
- @computed("hex")
+ @discourseComputed("hex")
brightness(hex) {
if (hex.length === 6 || hex.length === 3) {
if (hex.length === 3) {
@@ -79,9 +80,9 @@ const ColorSchemeColor = Discourse.Model.extend({
hex.substr(2, 1);
}
return Math.round(
- (parseInt("0x" + hex.substr(0, 2)) * 299 +
- parseInt("0x" + hex.substr(2, 2)) * 587 +
- parseInt("0x" + hex.substr(4, 2)) * 114) /
+ (parseInt(hex.substr(0, 2), 16) * 299 +
+ parseInt(hex.substr(2, 2), 16) * 587 +
+ parseInt(hex.substr(4, 2), 16) * 114) /
1000
);
}
@@ -94,7 +95,7 @@ const ColorSchemeColor = Discourse.Model.extend({
}
},
- @computed("hex")
+ @discourseComputed("hex")
valid(hex) {
return hex.match(/^([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/) !== null;
}
diff --git a/app/assets/javascripts/admin/models/color-scheme.js.es6 b/app/assets/javascripts/admin/models/color-scheme.js.es6
index 8875bf2e5e..8486002386 100644
--- a/app/assets/javascripts/admin/models/color-scheme.js.es6
+++ b/app/assets/javascripts/admin/models/color-scheme.js.es6
@@ -1,16 +1,17 @@
+import discourseComputed from "discourse-common/utils/decorators";
import { not } from "@ember/object/computed";
import { ajax } from "discourse/lib/ajax";
import ColorSchemeColor from "admin/models/color-scheme-color";
-import computed from "ember-addons/ember-computed-decorators";
+import EmberObject from "@ember/object";
-const ColorScheme = Discourse.Model.extend(Ember.Copyable, {
+const ColorScheme = EmberObject.extend(Ember.Copyable, {
init() {
this._super(...arguments);
this.startTrackingChanges();
},
- @computed
+ @discourseComputed
description() {
return "" + this.name;
},
@@ -42,7 +43,7 @@ const ColorScheme = Discourse.Model.extend(Ember.Copyable, {
return newScheme;
},
- @computed("name", "colors.@each.changed", "saving")
+ @discourseComputed("name", "colors.@each.changed", "saving")
changed(name) {
if (!this.originals) return false;
if (this.originals.name !== name) return true;
@@ -51,7 +52,7 @@ const ColorScheme = Discourse.Model.extend(Ember.Copyable, {
return false;
},
- @computed("changed")
+ @discourseComputed("changed")
disableSave(changed) {
if (this.theme_id) {
return false;
diff --git a/app/assets/javascripts/admin/models/email-log.js.es6 b/app/assets/javascripts/admin/models/email-log.js.es6
index f1ac52ac8d..c2eaaa26e6 100644
--- a/app/assets/javascripts/admin/models/email-log.js.es6
+++ b/app/assets/javascripts/admin/models/email-log.js.es6
@@ -1,7 +1,8 @@
import { ajax } from "discourse/lib/ajax";
import AdminUser from "admin/models/admin-user";
+import EmberObject from "@ember/object";
-const EmailLog = Discourse.Model.extend({});
+const EmailLog = EmberObject.extend({});
EmailLog.reopenClass({
create(attrs) {
diff --git a/app/assets/javascripts/admin/models/email-preview.js.es6 b/app/assets/javascripts/admin/models/email-preview.js.es6
index b8585d9080..42b7ab5878 100644
--- a/app/assets/javascripts/admin/models/email-preview.js.es6
+++ b/app/assets/javascripts/admin/models/email-preview.js.es6
@@ -1,5 +1,7 @@
import { ajax } from "discourse/lib/ajax";
-const EmailPreview = Discourse.Model.extend({});
+import EmberObject from "@ember/object";
+
+const EmailPreview = EmberObject.extend({});
export function oneWeekAgo() {
return moment()
diff --git a/app/assets/javascripts/admin/models/email-settings.js.es6 b/app/assets/javascripts/admin/models/email-settings.js.es6
index e1d838463e..1730aae7c9 100644
--- a/app/assets/javascripts/admin/models/email-settings.js.es6
+++ b/app/assets/javascripts/admin/models/email-settings.js.es6
@@ -1,5 +1,7 @@
import { ajax } from "discourse/lib/ajax";
-const EmailSettings = Discourse.Model.extend({});
+import EmberObject from "@ember/object";
+
+const EmailSettings = EmberObject.extend({});
EmailSettings.reopenClass({
find: function() {
diff --git a/app/assets/javascripts/admin/models/flag-type.js.es6 b/app/assets/javascripts/admin/models/flag-type.js.es6
index b1bf1ca828..93fb2eacc9 100644
--- a/app/assets/javascripts/admin/models/flag-type.js.es6
+++ b/app/assets/javascripts/admin/models/flag-type.js.es6
@@ -1,8 +1,8 @@
+import discourseComputed from "discourse-common/utils/decorators";
import RestModel from "discourse/models/rest";
-import computed from "ember-addons/ember-computed-decorators";
export default RestModel.extend({
- @computed("id")
+ @discourseComputed("id")
name(id) {
return I18n.t(`admin.flags.summary.action_type_${id}`, { count: 1 });
}
diff --git a/app/assets/javascripts/admin/models/incoming-email.js.es6 b/app/assets/javascripts/admin/models/incoming-email.js.es6
index fd9d68730b..8d46429a1d 100644
--- a/app/assets/javascripts/admin/models/incoming-email.js.es6
+++ b/app/assets/javascripts/admin/models/incoming-email.js.es6
@@ -1,7 +1,8 @@
import { ajax } from "discourse/lib/ajax";
import AdminUser from "admin/models/admin-user";
+import EmberObject from "@ember/object";
-const IncomingEmail = Discourse.Model.extend({});
+const IncomingEmail = EmberObject.extend({});
IncomingEmail.reopenClass({
create(attrs) {
diff --git a/app/assets/javascripts/admin/models/permalink.js.es6 b/app/assets/javascripts/admin/models/permalink.js.es6
index 9019bdbc30..b86e931692 100644
--- a/app/assets/javascripts/admin/models/permalink.js.es6
+++ b/app/assets/javascripts/admin/models/permalink.js.es6
@@ -1,5 +1,7 @@
import { ajax } from "discourse/lib/ajax";
-const Permalink = Discourse.Model.extend({
+import EmberObject from "@ember/object";
+
+const Permalink = EmberObject.extend({
save: function() {
return ajax("/admin/permalinks.json", {
type: "POST",
diff --git a/app/assets/javascripts/admin/models/report.js.es6 b/app/assets/javascripts/admin/models/report.js.es6
index 37c495e06f..91a5f54002 100644
--- a/app/assets/javascripts/admin/models/report.js.es6
+++ b/app/assets/javascripts/admin/models/report.js.es6
@@ -1,3 +1,4 @@
+import discourseComputed from "discourse-common/utils/decorators";
import { makeArray } from "discourse-common/lib/helpers";
import { isEmpty } from "@ember/utils";
import EmberObject from "@ember/object";
@@ -9,7 +10,6 @@ import {
formatUsername,
toNumber
} from "discourse/lib/utilities";
-import computed from "ember-addons/ember-computed-decorators";
import { number, durationTiny } from "discourse/lib/formatter";
import { renderAvatar } from "discourse/helpers/user-avatar";
@@ -17,17 +17,17 @@ import { renderAvatar } from "discourse/helpers/user-avatar";
// and you want to ensure cache is reset
export const SCHEMA_VERSION = 4;
-const Report = Discourse.Model.extend({
+const Report = EmberObject.extend({
average: false,
percent: false,
higher_is_better: true,
- @computed("modes")
+ @discourseComputed("modes")
isTable(modes) {
return modes.some(mode => mode === "table");
},
- @computed("type", "start_date", "end_date")
+ @discourseComputed("type", "start_date", "end_date")
reportUrl(type, start_date, end_date) {
start_date = moment
.utc(start_date)
@@ -83,32 +83,32 @@ const Report = Discourse.Model.extend({
}
},
- @computed("data", "average")
+ @discourseComputed("data", "average")
todayCount() {
return this.valueAt(0);
},
- @computed("data", "average")
+ @discourseComputed("data", "average")
yesterdayCount() {
return this.valueAt(1);
},
- @computed("data", "average")
+ @discourseComputed("data", "average")
sevenDaysAgoCount() {
return this.valueAt(7);
},
- @computed("data", "average")
+ @discourseComputed("data", "average")
thirtyDaysAgoCount() {
return this.valueAt(30);
},
- @computed("data", "average")
+ @discourseComputed("data", "average")
lastSevenDaysCount() {
return this.averageCount(7, this.valueFor(1, 7));
},
- @computed("data", "average")
+ @discourseComputed("data", "average")
lastThirtyDaysCount() {
return this.averageCount(30, this.valueFor(1, 30));
},
@@ -117,12 +117,12 @@ const Report = Discourse.Model.extend({
return this.average ? value / count : value;
},
- @computed("yesterdayCount", "higher_is_better")
+ @discourseComputed("yesterdayCount", "higher_is_better")
yesterdayTrend(yesterdayCount, higherIsBetter) {
return this._computeTrend(this.valueAt(2), yesterdayCount, higherIsBetter);
},
- @computed("lastSevenDaysCount", "higher_is_better")
+ @discourseComputed("lastSevenDaysCount", "higher_is_better")
sevenDaysTrend(lastSevenDaysCount, higherIsBetter) {
return this._computeTrend(
this.valueFor(8, 14),
@@ -131,50 +131,55 @@ const Report = Discourse.Model.extend({
);
},
- @computed("data")
+ @discourseComputed("data")
currentTotal(data) {
return data.reduce((cur, pair) => cur + pair.y, 0);
},
- @computed("data", "currentTotal")
+ @discourseComputed("data", "currentTotal")
currentAverage(data, total) {
return makeArray(data).length === 0
? 0
: parseFloat((total / parseFloat(data.length)).toFixed(1));
},
- @computed("trend", "higher_is_better")
+ @discourseComputed("trend", "higher_is_better")
trendIcon(trend, higherIsBetter) {
return this._iconForTrend(trend, higherIsBetter);
},
- @computed("sevenDaysTrend", "higher_is_better")
+ @discourseComputed("sevenDaysTrend", "higher_is_better")
sevenDaysTrendIcon(sevenDaysTrend, higherIsBetter) {
return this._iconForTrend(sevenDaysTrend, higherIsBetter);
},
- @computed("thirtyDaysTrend", "higher_is_better")
+ @discourseComputed("thirtyDaysTrend", "higher_is_better")
thirtyDaysTrendIcon(thirtyDaysTrend, higherIsBetter) {
return this._iconForTrend(thirtyDaysTrend, higherIsBetter);
},
- @computed("yesterdayTrend", "higher_is_better")
+ @discourseComputed("yesterdayTrend", "higher_is_better")
yesterdayTrendIcon(yesterdayTrend, higherIsBetter) {
return this._iconForTrend(yesterdayTrend, higherIsBetter);
},
- @computed("prev_period", "currentTotal", "currentAverage", "higher_is_better")
+ @discourseComputed(
+ "prev_period",
+ "currentTotal",
+ "currentAverage",
+ "higher_is_better"
+ )
trend(prev, currentTotal, currentAverage, higherIsBetter) {
const total = this.average ? currentAverage : currentTotal;
return this._computeTrend(prev, total, higherIsBetter);
},
- @computed("prev30Days", "lastThirtyDaysCount", "higher_is_better")
+ @discourseComputed("prev30Days", "lastThirtyDaysCount", "higher_is_better")
thirtyDaysTrend(prev30Days, lastThirtyDaysCount, higherIsBetter) {
return this._computeTrend(prev30Days, lastThirtyDaysCount, higherIsBetter);
},
- @computed("type")
+ @discourseComputed("type")
method(type) {
if (type === "time_to_first_response") {
return "average";
@@ -195,7 +200,7 @@ const Report = Discourse.Model.extend({
}
},
- @computed("prev_period", "currentTotal", "currentAverage")
+ @discourseComputed("prev_period", "currentTotal", "currentAverage")
trendTitle(prev, currentTotal, currentAverage) {
let current = this.average ? currentAverage : currentTotal;
let percent = this.percentChangeString(prev, current);
@@ -228,12 +233,12 @@ const Report = Discourse.Model.extend({
return title;
},
- @computed("yesterdayCount")
+ @discourseComputed("yesterdayCount")
yesterdayCountTitle(yesterdayCount) {
return this.changeTitle(this.valueAt(2), yesterdayCount, "two days ago");
},
- @computed("lastSevenDaysCount")
+ @discourseComputed("lastSevenDaysCount")
sevenDaysCountTitle(lastSevenDaysCount) {
return this.changeTitle(
this.valueFor(8, 14),
@@ -242,7 +247,7 @@ const Report = Discourse.Model.extend({
);
},
- @computed("prev30Days", "lastThirtyDaysCount")
+ @discourseComputed("prev30Days", "lastThirtyDaysCount")
thirtyDaysCountTitle(prev30Days, lastThirtyDaysCount) {
return this.changeTitle(
prev30Days,
@@ -251,18 +256,18 @@ const Report = Discourse.Model.extend({
);
},
- @computed("data")
+ @discourseComputed("data")
sortedData(data) {
return this.xAxisIsDate ? data.toArray().reverse() : data.toArray();
},
- @computed("data")
+ @discourseComputed("data")
xAxisIsDate() {
if (!this.data[0]) return false;
return this.data && this.data[0].x.match(/\d{4}-\d{1,2}-\d{1,2}/);
},
- @computed("labels")
+ @discourseComputed("labels")
computedLabels(labels) {
return labels.map(label => {
const type = label.type || "string";
diff --git a/app/assets/javascripts/admin/models/screened-email.js.es6 b/app/assets/javascripts/admin/models/screened-email.js.es6
index 6eb014c484..ea72510551 100644
--- a/app/assets/javascripts/admin/models/screened-email.js.es6
+++ b/app/assets/javascripts/admin/models/screened-email.js.es6
@@ -1,8 +1,9 @@
+import discourseComputed from "discourse-common/utils/decorators";
import { ajax } from "discourse/lib/ajax";
-import computed from "ember-addons/ember-computed-decorators";
+import EmberObject from "@ember/object";
-const ScreenedEmail = Discourse.Model.extend({
- @computed("action")
+const ScreenedEmail = EmberObject.extend({
+ @discourseComputed("action")
actionName(action) {
return I18n.t("admin.logs.screened_actions." + action);
},
diff --git a/app/assets/javascripts/admin/models/screened-ip-address.js.es6 b/app/assets/javascripts/admin/models/screened-ip-address.js.es6
index 0449a666f3..bfac17d86c 100644
--- a/app/assets/javascripts/admin/models/screened-ip-address.js.es6
+++ b/app/assets/javascripts/admin/models/screened-ip-address.js.es6
@@ -1,16 +1,17 @@
+import discourseComputed from "discourse-common/utils/decorators";
import { equal } from "@ember/object/computed";
import { ajax } from "discourse/lib/ajax";
-import computed from "ember-addons/ember-computed-decorators";
+import EmberObject from "@ember/object";
-const ScreenedIpAddress = Discourse.Model.extend({
- @computed("action_name")
+const ScreenedIpAddress = EmberObject.extend({
+ @discourseComputed("action_name")
actionName(actionName) {
return I18n.t(`admin.logs.screened_ips.actions.${actionName}`);
},
isBlocked: equal("action_name", "block"),
- @computed("ip_address")
+ @discourseComputed("ip_address")
isRange(ipAddress) {
return ipAddress.indexOf("/") > 0;
},
diff --git a/app/assets/javascripts/admin/models/screened-url.js.es6 b/app/assets/javascripts/admin/models/screened-url.js.es6
index b899c61962..31ea850778 100644
--- a/app/assets/javascripts/admin/models/screened-url.js.es6
+++ b/app/assets/javascripts/admin/models/screened-url.js.es6
@@ -1,8 +1,9 @@
+import discourseComputed from "discourse-common/utils/decorators";
import { ajax } from "discourse/lib/ajax";
-import computed from "ember-addons/ember-computed-decorators";
+import EmberObject from "@ember/object";
-const ScreenedUrl = Discourse.Model.extend({
- @computed("action")
+const ScreenedUrl = EmberObject.extend({
+ @discourseComputed("action")
actionName(action) {
return I18n.t("admin.logs.screened_actions." + action);
}
diff --git a/app/assets/javascripts/admin/models/site-setting.js.es6 b/app/assets/javascripts/admin/models/site-setting.js.es6
index 7760a61114..4edc89a1b9 100644
--- a/app/assets/javascripts/admin/models/site-setting.js.es6
+++ b/app/assets/javascripts/admin/models/site-setting.js.es6
@@ -1,7 +1,8 @@
import { ajax } from "discourse/lib/ajax";
import Setting from "admin/mixins/setting-object";
+import EmberObject from "@ember/object";
-const SiteSetting = Discourse.Model.extend(Setting, {});
+const SiteSetting = EmberObject.extend(Setting, {});
SiteSetting.reopenClass({
findAll() {
diff --git a/app/assets/javascripts/admin/models/staff-action-log.js.es6 b/app/assets/javascripts/admin/models/staff-action-log.js.es6
index 2d63019dda..45330b13fc 100644
--- a/app/assets/javascripts/admin/models/staff-action-log.js.es6
+++ b/app/assets/javascripts/admin/models/staff-action-log.js.es6
@@ -1,4 +1,4 @@
-import computed from "ember-addons/ember-computed-decorators";
+import discourseComputed from "discourse-common/utils/decorators";
import { ajax } from "discourse/lib/ajax";
import AdminUser from "admin/models/admin-user";
import { escapeExpression } from "discourse/lib/utilities";
@@ -13,12 +13,12 @@ function format(label, value, escape = true) {
const StaffActionLog = RestModel.extend({
showFullDetails: false,
- @computed("action_name")
+ @discourseComputed("action_name")
actionName(actionName) {
return I18n.t(`admin.logs.staff_actions.actions.${actionName}`);
},
- @computed(
+ @discourseComputed(
"email",
"ip_address",
"topic_id",
@@ -69,12 +69,12 @@ const StaffActionLog = RestModel.extend({
return formatted.length > 0 ? formatted + "
" : "";
},
- @computed("details")
+ @discourseComputed("details")
useModalForDetails(details) {
return details && details.length > 100;
},
- @computed("action_name")
+ @discourseComputed("action_name")
useCustomModalForDetails(actionName) {
return ["change_theme", "delete_theme"].includes(actionName);
}
diff --git a/app/assets/javascripts/admin/models/theme-settings.js.es6 b/app/assets/javascripts/admin/models/theme-settings.js.es6
index ab9e5bf9ce..a823592ad2 100644
--- a/app/assets/javascripts/admin/models/theme-settings.js.es6
+++ b/app/assets/javascripts/admin/models/theme-settings.js.es6
@@ -1,3 +1,4 @@
import Setting from "admin/mixins/setting-object";
+import EmberObject from "@ember/object";
-export default Discourse.Model.extend(Setting, {});
+export default EmberObject.extend(Setting, {});
diff --git a/app/assets/javascripts/admin/models/theme.js.es6 b/app/assets/javascripts/admin/models/theme.js.es6
index 4ea3d3e216..d1528bcd06 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 computed } from "ember-addons/ember-computed-decorators";
+import { default as 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";
@@ -19,8 +19,9 @@ const Theme = RestModel.extend({
isActive: or("default", "user_selectable"),
isPendingUpdates: gt("remote_theme.commits_behind", 0),
hasEditedFields: gt("editedFields.length", 0),
+ hasParents: gt("parent_themes.length", 0),
- @computed("theme_fields.[]")
+ @discourseComputed("theme_fields.[]")
targets() {
return [
{ id: 0, name: "common" },
@@ -48,7 +49,7 @@ const Theme = RestModel.extend({
});
},
- @computed("theme_fields.[]")
+ @discourseComputed("theme_fields.[]")
fieldNames() {
const common = [
"scss",
@@ -82,7 +83,11 @@ const Theme = RestModel.extend({
};
},
- @computed("fieldNames", "theme_fields.[]", "theme_fields.@each.error")
+ @discourseComputed(
+ "fieldNames",
+ "theme_fields.[]",
+ "theme_fields.@each.error"
+ )
fields(fieldNames) {
const hash = {};
Object.keys(fieldNames).forEach(target => {
@@ -112,7 +117,7 @@ const Theme = RestModel.extend({
return hash;
},
- @computed("theme_fields")
+ @discourseComputed("theme_fields")
themeFields(fields) {
if (!fields) {
this.set("theme_fields", []);
@@ -128,7 +133,7 @@ const Theme = RestModel.extend({
return hash;
},
- @computed("theme_fields", "theme_fields.[]")
+ @discourseComputed("theme_fields", "theme_fields.[]")
uploads(fields) {
if (!fields) {
return [];
@@ -138,19 +143,19 @@ const Theme = RestModel.extend({
);
},
- @computed("theme_fields", "theme_fields.@each.error")
+ @discourseComputed("theme_fields", "theme_fields.@each.error")
isBroken(fields) {
return fields && fields.any(field => field.error && field.error.length > 0);
},
- @computed("theme_fields.[]")
+ @discourseComputed("theme_fields.[]")
editedFields(fields) {
return fields.filter(
field => !Ember.isBlank(field.value) && field.type_id !== SETTINGS_TYPE_ID
);
},
- @computed("remote_theme.last_error_text")
+ @discourseComputed("remote_theme.last_error_text")
remoteError(errorText) {
if (errorText && errorText.length > 0) {
return errorText;
@@ -241,7 +246,7 @@ const Theme = RestModel.extend({
}
},
- @computed("childThemes.[]")
+ @discourseComputed("childThemes.[]")
child_theme_ids(childThemes) {
if (childThemes) {
return childThemes.map(theme => get(theme, "id"));
@@ -265,7 +270,16 @@ const Theme = RestModel.extend({
return this.saveChanges("child_theme_ids");
},
- @computed("name", "default")
+ addParentTheme(theme) {
+ let parentThemes = this.parentThemes;
+ if (!parentThemes) {
+ parentThemes = [];
+ this.set("parentThemes", parentThemes);
+ }
+ parentThemes.addObject(theme);
+ },
+
+ @discourseComputed("name", "default")
description: function(name, isDefault) {
if (isDefault) {
return I18n.t("admin.customize.theme.default_name", { name: name });
diff --git a/app/assets/javascripts/admin/models/tl3-requirements.js.es6 b/app/assets/javascripts/admin/models/tl3-requirements.js.es6
index 222c8077d7..424aea4f58 100644
--- a/app/assets/javascripts/admin/models/tl3-requirements.js.es6
+++ b/app/assets/javascripts/admin/models/tl3-requirements.js.es6
@@ -1,17 +1,18 @@
-import computed from "ember-addons/ember-computed-decorators";
+import discourseComputed from "discourse-common/utils/decorators";
+import EmberObject from "@ember/object";
-export default Discourse.Model.extend({
- @computed("days_visited", "time_period")
+export default EmberObject.extend({
+ @discourseComputed("days_visited", "time_period")
days_visited_percent(daysVisited, timePeriod) {
return Math.round((daysVisited * 100) / timePeriod);
},
- @computed("min_days_visited", "time_period")
+ @discourseComputed("min_days_visited", "time_period")
min_days_visited_percent(minDaysVisited, timePeriod) {
return Math.round((minDaysVisited * 100) / timePeriod);
},
- @computed(
+ @discourseComputed(
"days_visited",
"min_days_visited",
"num_topics_replied_to",
diff --git a/app/assets/javascripts/admin/models/version-check.js.es6 b/app/assets/javascripts/admin/models/version-check.js.es6
index 2012d0ff08..cc888b2588 100644
--- a/app/assets/javascripts/admin/models/version-check.js.es6
+++ b/app/assets/javascripts/admin/models/version-check.js.es6
@@ -1,30 +1,31 @@
+import discourseComputed from "discourse-common/utils/decorators";
import { ajax } from "discourse/lib/ajax";
-import computed from "ember-addons/ember-computed-decorators";
+import EmberObject from "@ember/object";
-const VersionCheck = Discourse.Model.extend({
- @computed("updated_at")
+const VersionCheck = EmberObject.extend({
+ @discourseComputed("updated_at")
noCheckPerformed(updatedAt) {
return updatedAt === null;
},
- @computed("missing_versions_count")
+ @discourseComputed("missing_versions_count")
upToDate(missingVersionsCount) {
return missingVersionsCount === 0 || missingVersionsCount === null;
},
- @computed("missing_versions_count")
+ @discourseComputed("missing_versions_count")
behindByOneVersion(missingVersionsCount) {
return missingVersionsCount === 1;
},
- @computed("installed_sha")
+ @discourseComputed("installed_sha")
gitLink(installedSHA) {
if (installedSHA) {
return `https://github.com/discourse/discourse/commits/${installedSHA}`;
}
},
- @computed("installed_sha")
+ @discourseComputed("installed_sha")
shortSha(installedSHA) {
if (installedSHA) {
return installedSHA.substr(0, 10);
diff --git a/app/assets/javascripts/admin/models/watched-word.js.es6 b/app/assets/javascripts/admin/models/watched-word.js.es6
index b9ef7380b6..dac78affe1 100644
--- a/app/assets/javascripts/admin/models/watched-word.js.es6
+++ b/app/assets/javascripts/admin/models/watched-word.js.es6
@@ -1,7 +1,7 @@
import { ajax } from "discourse/lib/ajax";
import EmberObject from "@ember/object";
-const WatchedWord = Discourse.Model.extend({
+const WatchedWord = EmberObject.extend({
save() {
return ajax(
"/admin/logs/watched_words" + (this.id ? "/" + this.id : "") + ".json",
diff --git a/app/assets/javascripts/admin/models/web-hook.js.es6 b/app/assets/javascripts/admin/models/web-hook.js.es6
index 84111591fa..74fd93a5f3 100644
--- a/app/assets/javascripts/admin/models/web-hook.js.es6
+++ b/app/assets/javascripts/admin/models/web-hook.js.es6
@@ -3,9 +3,10 @@ import RestModel from "discourse/models/rest";
import Category from "discourse/models/category";
import Group from "discourse/models/group";
import {
- default as computed,
+ default as discourseComputed,
observes
-} from "ember-addons/ember-computed-decorators";
+} from "discourse-common/utils/decorators";
+import Site from "discourse/models/site";
export default RestModel.extend({
content_type: 1, // json
@@ -16,7 +17,7 @@ export default RestModel.extend({
web_hook_event_types: null,
groupsFilterInName: null,
- @computed("wildcard_web_hook")
+ @discourseComputed("wildcard_web_hook")
webHookType: {
get(wildcard) {
return wildcard ? "wildcard" : "individual";
@@ -26,7 +27,7 @@ export default RestModel.extend({
}
},
- @computed("category_ids")
+ @discourseComputed("category_ids")
categories(categoryIds) {
return Category.findByIds(categoryIds);
},
@@ -36,7 +37,7 @@ export default RestModel.extend({
const groupIds = this.group_ids;
this.set(
"groupsFilterInName",
- Discourse.Site.currentProp("groups").reduce((groupNames, g) => {
+ Site.currentProp("groups").reduce((groupNames, g) => {
if (groupIds.includes(g.id)) {
groupNames.push(g.name);
}
@@ -49,7 +50,7 @@ export default RestModel.extend({
return Group.findAll({ term: term, ignore_automatic: false });
},
- @computed("wildcard_web_hook", "web_hook_event_types.[]")
+ @discourseComputed("wildcard_web_hook", "web_hook_event_types.[]")
description(isWildcardWebHook, types) {
let desc = "";
@@ -87,7 +88,7 @@ export default RestModel.extend({
group_ids:
isEmpty(groupNames) || isEmpty(groupNames[0])
? [null]
- : Discourse.Site.currentProp("groups").reduce((groupIds, g) => {
+ : Site.currentProp("groups").reduce((groupIds, g) => {
if (groupNames.includes(g.name)) {
groupIds.push(g.id);
}
diff --git a/app/assets/javascripts/admin/routes/admin-backups-index.js.es6 b/app/assets/javascripts/admin/routes/admin-backups-index.js.es6
index d20da4ac50..d463819a2a 100644
--- a/app/assets/javascripts/admin/routes/admin-backups-index.js.es6
+++ b/app/assets/javascripts/admin/routes/admin-backups-index.js.es6
@@ -4,7 +4,10 @@ import Backup from "admin/models/backup";
export default Route.extend({
activate() {
this.messageBus.subscribe("/admin/backups", backups =>
- this.controller.set("model", backups.map(backup => Backup.create(backup)))
+ this.controller.set(
+ "model",
+ backups.map(backup => Backup.create(backup))
+ )
);
},
diff --git a/app/assets/javascripts/admin/routes/admin-backups.js.es6 b/app/assets/javascripts/admin/routes/admin-backups.js.es6
index 5025f41536..6bc42d725f 100644
--- a/app/assets/javascripts/admin/routes/admin-backups.js.es6
+++ b/app/assets/javascripts/admin/routes/admin-backups.js.es6
@@ -5,6 +5,7 @@ import showModal from "discourse/lib/show-modal";
import BackupStatus from "admin/models/backup-status";
import Backup from "admin/models/backup";
import PreloadStore from "preload-store";
+import User from "discourse/models/user";
const LOG_CHANNEL = "/admin/backups/logs";
@@ -12,7 +13,7 @@ export default DiscourseRoute.extend({
activate() {
this.messageBus.subscribe(LOG_CHANNEL, log => {
if (log.message === "[STARTED]") {
- Discourse.User.currentProp("hideReadOnlyAlert", true);
+ User.currentProp("hideReadOnlyAlert", true);
this.controllerFor("adminBackups").set(
"model.isOperationRunning",
true
@@ -31,7 +32,7 @@ export default DiscourseRoute.extend({
})
);
} else if (log.message === "[SUCCESS]") {
- Discourse.User.currentProp("hideReadOnlyAlert", false);
+ User.currentProp("hideReadOnlyAlert", false);
this.controllerFor("adminBackups").set(
"model.isOperationRunning",
false
diff --git a/app/assets/javascripts/admin/routes/admin-badges-show.js.es6 b/app/assets/javascripts/admin/routes/admin-badges-show.js.es6
index 36043f6096..67acdcba58 100644
--- a/app/assets/javascripts/admin/routes/admin-badges-show.js.es6
+++ b/app/assets/javascripts/admin/routes/admin-badges-show.js.es6
@@ -15,7 +15,10 @@ export default Route.extend({
name: I18n.t("admin.badges.new_badge")
});
}
- return this.modelFor("adminBadges").findBy("id", parseInt(params.badge_id));
+ return this.modelFor("adminBadges").findBy(
+ "id",
+ parseInt(params.badge_id, 10)
+ );
},
actions: {
@@ -51,7 +54,8 @@ export default Route.extend({
})
.catch(function(error) {
badge.set("preview_loading", false);
- Ember.Logger.error(error);
+ // eslint-disable-next-line no-console
+ console.error(error);
bootbox.alert("Network error");
});
}
diff --git a/app/assets/javascripts/admin/routes/admin-customize-colors-show.js.es6 b/app/assets/javascripts/admin/routes/admin-customize-colors-show.js.es6
index 146a3a61a9..8807df2c56 100644
--- a/app/assets/javascripts/admin/routes/admin-customize-colors-show.js.es6
+++ b/app/assets/javascripts/admin/routes/admin-customize-colors-show.js.es6
@@ -2,7 +2,7 @@ import Route from "@ember/routing/route";
export default Route.extend({
model(params) {
const all = this.modelFor("adminCustomize.colors");
- const model = all.findBy("id", parseInt(params.scheme_id));
+ const model = all.findBy("id", parseInt(params.scheme_id, 10));
return model ? model : this.replaceWith("adminCustomize.colors.index");
},
diff --git a/app/assets/javascripts/admin/routes/admin-customize-themes-edit.js.es6 b/app/assets/javascripts/admin/routes/admin-customize-themes-edit.js.es6
index 335e9fd578..62a70f0d7c 100644
--- a/app/assets/javascripts/admin/routes/admin-customize-themes-edit.js.es6
+++ b/app/assets/javascripts/admin/routes/admin-customize-themes-edit.js.es6
@@ -2,7 +2,7 @@ import Route from "@ember/routing/route";
export default Route.extend({
model(params) {
const all = this.modelFor("adminCustomizeThemes");
- const model = all.findBy("id", parseInt(params.theme_id));
+ const model = all.findBy("id", parseInt(params.theme_id, 10));
return model
? {
model,
diff --git a/app/assets/javascripts/admin/routes/admin-customize-themes-show.js.es6 b/app/assets/javascripts/admin/routes/admin-customize-themes-show.js.es6
index c9573fd8ac..408917b57a 100644
--- a/app/assets/javascripts/admin/routes/admin-customize-themes-show.js.es6
+++ b/app/assets/javascripts/admin/routes/admin-customize-themes-show.js.es6
@@ -9,7 +9,7 @@ export default Route.extend({
model(params) {
const all = this.modelFor("adminCustomizeThemes");
- const model = all.findBy("id", parseInt(params.theme_id));
+ const model = all.findBy("id", parseInt(params.theme_id, 10));
return model ? model : this.replaceWith("adminCustomizeTheme.index");
},
diff --git a/app/assets/javascripts/admin/templates/api-keys-show.hbs b/app/assets/javascripts/admin/templates/api-keys-show.hbs
index a742cf994a..725c987a22 100644
--- a/app/assets/javascripts/admin/templates/api-keys-show.hbs
+++ b/app/assets/javascripts/admin/templates/api-keys-show.hbs
@@ -6,7 +6,7 @@
{{#admin-form-row label="admin.api.key"}}
{{#if model.revoked_at}}{{d-icon 'times-circle'}}{{/if}}
- {{model.key}}
+ {{model.key}}
{{/admin-form-row}}
{{#admin-form-row label="admin.api.description"}}
@@ -45,8 +45,8 @@
{{/admin-form-row}}
{{#admin-form-row label="admin.api.last_used"}}
- {{#if k.last_used_at}}
- {{format-date k.last_used_at leaveAgo="true"}}
+ {{#if model.last_used_at}}
+ {{format-date model.last_used_at leaveAgo="true"}}
{{else}}
{{i18n "admin.api.never_used"}}
{{/if}}
diff --git a/app/assets/javascripts/admin/templates/backups-index.hbs b/app/assets/javascripts/admin/templates/backups-index.hbs
index ee7abc5f3a..7f0675e5a5 100644
--- a/app/assets/javascripts/admin/templates/backups-index.hbs
+++ b/app/assets/javascripts/admin/templates/backups-index.hbs
@@ -28,6 +28,11 @@
title="admin.backups.read_only.enable.title"
label="admin.backups.read_only.enable.label"}}
{{/if}}
+
#{I18n.t("emails.secure_media_placeholder")}
") + end + end + + raw + end + def self.get_context_posts(post, topic_user, user) if (user.user_option.email_previous_replies == UserOption.previous_replies_type[:never]) || SiteSetting.private_email? diff --git a/app/models/admin_dashboard_data.rb b/app/models/admin_dashboard_data.rb index 78504482dd..889be79bd9 100644 --- a/app/models/admin_dashboard_data.rb +++ b/app/models/admin_dashboard_data.rb @@ -84,7 +84,9 @@ class AdminDashboardData @problem_messages = [ 'dashboard.bad_favicon_url', 'dashboard.poll_pop3_timeout', - 'dashboard.poll_pop3_auth_error' + 'dashboard.poll_pop3_auth_error', + 'dashboard.deprecated_api_usage', + 'dashboard.update_mail_receiver' ] add_problem_check :rails_env_check, :host_names_check, :force_https_check, diff --git a/app/models/api_key.rb b/app/models/api_key.rb index 198d746b30..91722b3603 100644 --- a/app/models/api_key.rb +++ b/app/models/api_key.rb @@ -57,9 +57,11 @@ end # allowed_ips :inet is an Array # hidden :boolean default(FALSE), not null # last_used_at :datetime +# revoked_at :datetime +# description :text # # Indexes # # index_api_keys_on_key (key) -# index_api_keys_on_user_id (user_id) UNIQUE +# index_api_keys_on_user_id (user_id) # diff --git a/app/models/badge.rb b/app/models/badge.rb index 3596190a8a..4db471bcbe 100644 --- a/app/models/badge.rb +++ b/app/models/badge.rb @@ -169,8 +169,17 @@ class Badge < ActiveRecord::Base end def self.display_name(name) - key = "badges.#{i18n_name(name)}.name" - I18n.t(key, default: name) + I18n.t(i18n_key(name), default: name) + end + + def self.i18n_key(name) + "badges.#{i18n_name(name)}.name" + end + + def self.find_system_badge_id_from_translation_key(translation_key) + return unless translation_key.starts_with?('badges.') + badge_name_klass = translation_key.split('.').second.camelize + "Badge::#{badge_name_klass}".constantize end def awarded_for_trust_level? @@ -208,6 +217,10 @@ class Badge < ActiveRecord::Base self.class.display_name(name) end + def translation_key + self.class.i18n_key(name) + end + def long_description key = "badges.#{i18n_name}.long_description" I18n.t(key, default: self[:long_description] || '', base_uri: Discourse.base_uri) diff --git a/app/models/category.rb b/app/models/category.rb index 4e2ec97a93..5af5eabac3 100644 --- a/app/models/category.rb +++ b/app/models/category.rb @@ -3,6 +3,7 @@ class Category < ActiveRecord::Base self.ignored_columns = %w{ uploaded_meta_id + suppress_from_latest } include Searchable @@ -12,7 +13,6 @@ class Category < ActiveRecord::Base include AnonCacheInvalidator include HasDestroyedWebHook - MAX_NESTING = 2 # category + subcategory REQUIRE_TOPIC_APPROVAL = 'require_topic_approval' REQUIRE_REPLY_APPROVAL = 'require_reply_approval' NUM_AUTO_BUMP_DAILY = 'num_auto_bump_daily' @@ -72,7 +72,6 @@ class Category < ActiveRecord::Base after_save :clear_url_cache after_save :index_search after_save :update_reviewables - after_save :clear_featured_cache after_destroy :reset_topic_ids_cache after_destroy :publish_category_deletion @@ -329,7 +328,7 @@ class Category < ActiveRecord::Base # This is used in a validation so has to produce accurate results before the # record has been saved - def height_of_ancestors(max_height = MAX_NESTING) + def height_of_ancestors(max_height = SiteSetting.max_category_nesting) parent_id = self.parent_category_id return max_height if parent_id == id @@ -357,7 +356,7 @@ class Category < ActiveRecord::Base # This is used in a validation so has to produce accurate results before the # record has been saved - def depth_of_descendants(max_depth = MAX_NESTING) + def depth_of_descendants(max_depth = SiteSetting.max_category_nesting) parent_id = self.parent_category_id return max_depth if parent_id == id @@ -390,7 +389,7 @@ class Category < ActiveRecord::Base errors.add(:base, I18n.t("category.errors.self_parent")) if parent_category_id == id total_depth = height_of_ancestors + 1 + depth_of_descendants - errors.add(:base, I18n.t("category.errors.depth")) if total_depth > MAX_NESTING + errors.add(:base, I18n.t("category.errors.depth")) if total_depth > SiteSetting.max_category_nesting end end @@ -532,6 +531,7 @@ class Category < ActiveRecord::Base topic = relation .visible .listable_topics + .exclude_scheduled_bump_topics .where(category_id: self.id) .where('id <> ?', self.topic_id) .where('bumped_at < ?', 1.day.ago) @@ -559,7 +559,7 @@ class Category < ActiveRecord::Base end def required_tag_group_name=(group_name) - self.required_tag_group = group_name ? TagGroup.where(name: group_name).first : nil + self.required_tag_group = group_name.blank? ? nil : TagGroup.where(name: group_name).first end def downcase_email @@ -656,10 +656,6 @@ class Category < ActiveRecord::Base @@url_cache.clear end - def clear_featured_cache - CategoryFeaturedTopic.clear_exclude_category_ids - end - def full_slug(separator = "-") start_idx = "#{Discourse.base_uri}/c/".length url[start_idx..-1].gsub("/", separator) @@ -915,12 +911,13 @@ end # subcategory_list_style :string(50) default("rows_with_featured_topics") # default_top_period :string(20) default("all") # mailinglist_mirror :boolean default(FALSE), not null -# suppress_from_latest :boolean default(FALSE) # minimum_required_tags :integer default(0), not null # navigate_to_first_post_after_read :boolean default(FALSE), not null # search_priority :integer default(0) # allow_global_tags :boolean default(FALSE), not null # reviewable_by_group_id :integer +# required_tag_group_id :integer +# min_tags_from_required_group :integer default(1), not null # # Indexes # diff --git a/app/models/category_featured_topic.rb b/app/models/category_featured_topic.rb index bc3b6ba0a2..2848383605 100644 --- a/app/models/category_featured_topic.rb +++ b/app/models/category_featured_topic.rb @@ -38,16 +38,6 @@ class CategoryFeaturedTopic < ActiveRecord::Base end end - @@exclude_category_ids = DistributedCache.new('excluded_category_ids_from_featured') - - def self.cached_exclude_category_ids - @@exclude_category_ids['ids'] ||= Category.where(suppress_from_latest: true).pluck(:id) - end - - def self.clear_exclude_category_ids - @@exclude_category_ids.clear - end - def self.clear_batch! Discourse.redis.del(NEXT_CATEGORY_ID_KEY) end @@ -59,8 +49,7 @@ class CategoryFeaturedTopic < ActiveRecord::Base per_page: c.num_featured_topics, except_topic_ids: [c.topic_id], visible: true, - no_definitions: true, - exclude_category_ids: CategoryFeaturedTopic.cached_exclude_category_ids + no_definitions: true } # It may seem a bit odd that we are running 2 queries here, when admin diff --git a/app/models/category_list.rb b/app/models/category_list.rb index f1f7b6a0d9..bc1d6a4980 100644 --- a/app/models/category_list.rb +++ b/app/models/category_list.rb @@ -92,16 +92,12 @@ class CategoryList @categories = @categories.to_a - category_user = {} - default_notification_level = nil - unless @guardian.anonymous? - category_user = Hash[*CategoryUser.where(user: @guardian.user).pluck(:category_id, :notification_level).flatten] - default_notification_level = CategoryUser.notification_levels[:regular] - end + notification_levels = CategoryUser.notification_levels_for(@guardian) + default_notification_level = CategoryUser.default_notification_level allowed_topic_create = Set.new(Category.topic_create_allowed(@guardian).pluck(:id)) @categories.each do |category| - category.notification_level = category_user[category.id] || default_notification_level + category.notification_level = notification_levels[category.id] || default_notification_level category.permission = CategoryGroup.permission_types[:full] if allowed_topic_create.include?(category.id) category.has_children = category.subcategories.present? end diff --git a/app/models/category_user.rb b/app/models/category_user.rb index e25f917d32..afcd56ce19 100644 --- a/app/models/category_user.rb +++ b/app/models/category_user.rb @@ -197,6 +197,36 @@ class CategoryUser < ActiveRecord::Base SQL end + def self.default_notification_level + SiteSetting.mute_all_categories_by_default ? notification_levels[:muted] : notification_levels[:regular] + end + + def self.notification_levels_for(guardian) + if guardian.anonymous? + notification_levels = [ + SiteSetting.default_categories_watching.split("|"), + SiteSetting.default_categories_tracking.split("|"), + SiteSetting.default_categories_watching_first_post.split("|"), + ].flatten.map { |id| [id.to_i, self.notification_levels[:regular]] } + + notification_levels += SiteSetting.default_categories_muted.split("|").map { |id| [id.to_i, self.notification_levels[:muted]] } + else + notification_levels = CategoryUser.where(user: guardian.user).pluck(:category_id, :notification_level) + end + + Hash[*notification_levels.flatten] + end + + def self.lookup_for(user, category_ids) + return {} if user.blank? || category_ids.blank? + create_lookup(CategoryUser.where(category_id: category_ids, user_id: user.id)) + end + + def self.create_lookup(category_users) + category_users.each_with_object({}) do |category_user, acc| + acc[category_user.category_id] = category_user + end + end end # == Schema Information @@ -206,10 +236,12 @@ end # id :integer not null, primary key # category_id :integer not null # user_id :integer not null -# notification_level :integer not null +# notification_level :integer +# last_seen_at :datetime # # Indexes # -# idx_category_users_category_id_user_id (category_id,user_id) UNIQUE -# idx_category_users_user_id_category_id (user_id,category_id) UNIQUE +# idx_category_users_category_id_user_id (category_id,user_id) UNIQUE +# idx_category_users_user_id_category_id (user_id,category_id) UNIQUE +# index_category_users_on_user_id_and_last_seen_at (user_id,last_seen_at) # diff --git a/app/models/developer.rb b/app/models/developer.rb index 0b0dcd9320..a789dd6dd6 100644 --- a/app/models/developer.rb +++ b/app/models/developer.rb @@ -28,3 +28,7 @@ end # id :integer not null, primary key # user_id :integer not null # +# Indexes +# +# index_developers_on_user_id (user_id) UNIQUE +# diff --git a/app/models/draft.rb b/app/models/draft.rb index 2bbc3bcaf6..4f7a03711f 100644 --- a/app/models/draft.rb +++ b/app/models/draft.rb @@ -282,5 +282,5 @@ end # # Indexes # -# index_drafts_on_user_id_and_draft_key (user_id,draft_key) +# index_drafts_on_user_id_and_draft_key (user_id,draft_key) UNIQUE # diff --git a/app/models/embedding.rb b/app/models/embedding.rb index 3b67a41858..1f2be1ee0f 100644 --- a/app/models/embedding.rb +++ b/app/models/embedding.rb @@ -12,11 +12,7 @@ class Embedding < OpenStruct embed_truncate embed_whitelist_selector embed_blacklist_selector - embed_classname_whitelist - feed_polling_enabled - feed_polling_url - feed_polling_frequency_mins - embed_username_key_from_feed) + embed_classname_whitelist) end def base_url diff --git a/app/models/instagram_user_info.rb b/app/models/instagram_user_info.rb deleted file mode 100644 index 404876aa2a..0000000000 --- a/app/models/instagram_user_info.rb +++ /dev/null @@ -1,19 +0,0 @@ -# frozen_string_literal: true - -class InstagramUserInfo < ActiveRecord::Base - - belongs_to :user - -end - -# == Schema Information -# -# Table name: instagram_user_infos -# -# id :integer not null, primary key -# user_id :integer -# screen_name :string -# instagram_user_id :integer -# created_at :datetime not null -# updated_at :datetime not null -# diff --git a/app/models/notification.rb b/app/models/notification.rb index fa6bb66490..fc0e60805b 100644 --- a/app/models/notification.rb +++ b/app/models/notification.rb @@ -4,6 +4,8 @@ class Notification < ActiveRecord::Base belongs_to :user belongs_to :topic + MEMBERSHIP_REQUEST_CONSOLIDATION_WINDOW_HOURS = 24 + validates_presence_of :data validates_presence_of :notification_type @@ -12,19 +14,34 @@ class Notification < ActiveRecord::Base scope :visible , lambda { joins('LEFT JOIN topics ON notifications.topic_id = topics.id') .where('topics.id IS NULL OR topics.deleted_at IS NULL') } - scope :filter_by_display_username_and_type, ->(username, notification_type) { - where("data::json ->> 'display_username' = ?", username) - .where(notification_type: notification_type) - .order(created_at: :desc) + scope :filter_by_consolidation_data, ->(notification_type, data) { + notifications = where(notification_type: notification_type) + + case notification_type + when types[:liked], types[:liked_consolidated] + key = "display_username" + consolidation_window = SiteSetting.likes_notification_consolidation_window_mins.minutes.ago + when types[:private_message] + key = "topic_title" + consolidation_window = MEMBERSHIP_REQUEST_CONSOLIDATION_WINDOW_HOURS.hours.ago + when types[:membership_request_consolidated] + key = "group_name" + consolidation_window = MEMBERSHIP_REQUEST_CONSOLIDATION_WINDOW_HOURS.hours.ago + end + + notifications = notifications.where("created_at > ? AND data::json ->> '#{key}' = ?", consolidation_window, data[key.to_sym]) if data[key&.to_sym].present? + notifications = notifications.where("data::json ->> 'username2' IS NULL") if notification_type == types[:liked] + + notifications } attr_accessor :skip_send_email - after_commit :send_email, on: :create after_commit :refresh_notification_count, on: [:create, :update, :destroy] after_commit(on: :create) do DiscourseEvent.trigger(:notification_created, self) + send_email unless NotificationConsolidator.new(self).consolidate! end def self.ensure_consistency! @@ -66,7 +83,8 @@ class Notification < ActiveRecord::Base liked_consolidated: 19, post_approved: 20, code_review_commit_approved: 21, - membership_request_accepted: 22 + membership_request_accepted: 22, + membership_request_consolidated: 23 ) end diff --git a/app/models/post.rb b/app/models/post.rb index 66e27fd419..73acc182d5 100644 --- a/app/models/post.rb +++ b/app/models/post.rb @@ -54,11 +54,13 @@ class Post < ActiveRecord::Base # We can pass several creating options to a post via attributes attr_accessor :image_sizes, :quoted_post_numbers, :no_bump, :invalidate_oneboxes, :cooking_options, :skip_unique_check, :skip_validation - LARGE_IMAGES ||= "large_images".freeze - BROKEN_IMAGES ||= "broken_images".freeze - DOWNLOADED_IMAGES ||= "downloaded_images".freeze - MISSING_UPLOADS ||= "missing uploads".freeze - MISSING_UPLOADS_IGNORED ||= "missing uploads ignored".freeze + LARGE_IMAGES ||= "large_images" + BROKEN_IMAGES ||= "broken_images" + DOWNLOADED_IMAGES ||= "downloaded_images" + MISSING_UPLOADS ||= "missing uploads" + MISSING_UPLOADS_IGNORED ||= "missing uploads ignored" + NOTICE_TYPE ||= "notice_type" + NOTICE_ARGS ||= "notice_args" SHORT_POST_CHARS ||= 1200 @@ -131,7 +133,8 @@ class Post < ActiveRecord::Base new_user_spam_threshold_reached: 3, flagged_by_tl3_user: 4, email_spam_header_found: 5, - flagged_by_tl4_user: 6) + flagged_by_tl4_user: 6, + email_authentication_result_header: 7) end def self.types @@ -300,6 +303,15 @@ class Post < ActiveRecord::Base options[:user_id] = post_user.id if post_user options[:omit_nofollow] = true if omit_nofollow? + if self.with_secure_media? + each_upload_url do |url| + uri = URI.parse(url) + if FileHelper.is_supported_media?(File.basename(uri.path)) + raw = raw.sub(Discourse.store.s3_upload_host, "#{Discourse.base_url}/secure-media-uploads") + end + end + end + cooked = post_analyzer.cook(raw, options) new_cooked = Plugin::Filter.apply(:after_post_cook, self, cooked) @@ -413,8 +425,8 @@ class Post < ActiveRecord::Base end def delete_post_notices - self.custom_fields.delete("notice_type") - self.custom_fields.delete("notice_args") + self.custom_fields.delete(Post::NOTICE_TYPE) + self.custom_fields.delete(Post::NOTICE_ARGS) self.save_custom_fields end @@ -492,6 +504,11 @@ class Post < ActiveRecord::Base ReviewableFlaggedPost.pending.find_by(target: self) end + def with_secure_media? + return false unless SiteSetting.secure_media? + topic&.private_message? || SiteSetting.login_required? + end + def hide!(post_action_type_id, reason = nil) return if hidden? @@ -882,6 +899,13 @@ class Post < ActiveRecord::Base end upload_ids |= Upload.where(id: downloaded_images.values).pluck(:id) + + disallowed_uploads = [] + if SiteSetting.secure_media? && !self.with_secure_media? + disallowed_uploads = Upload.where(id: upload_ids, secure: true).pluck(:original_filename) + end + return disallowed_uploads if disallowed_uploads.count > 0 + values = upload_ids.map! { |upload_id| "(#{self.id},#{upload_id})" }.join(",") PostUpload.transaction do @@ -893,6 +917,12 @@ class Post < ActiveRecord::Base end end + def update_uploads_secure_status + if Discourse.store.external? + self.uploads.each { |upload| upload.update_secure_status } + end + end + def downloaded_images JSON.parse(self.custom_fields[Post::DOWNLOADED_IMAGES].presence || "{}") rescue JSON::ParserError @@ -909,6 +939,7 @@ class Post < ActiveRecord::Base ] fragments ||= Nokogiri::HTML::fragment(self.cooked) + links = fragments.css("a/@href", "img/@src").map do |media| src = media.value next if src.blank? diff --git a/app/models/post_mover.rb b/app/models/post_mover.rb index 2cd39b80ae..facccc8ebd 100644 --- a/app/models/post_mover.rb +++ b/app/models/post_mover.rb @@ -126,6 +126,7 @@ class PostMover move_incoming_emails move_notifications update_reply_counts + update_quotes move_first_post_replies delete_post_replies copy_first_post_timings @@ -256,6 +257,18 @@ class PostMover SQL end + def update_quotes + DB.exec <<~SQL + UPDATE posts p + SET raw = REPLACE(p.raw, + ', post:' || mp.old_post_number || ', topic:' || mp.old_topic_id, + ', post:' || mp.new_post_number || ', topic:' || mp.new_topic_id), + baked_version = NULL + FROM moved_posts mp, quoted_posts qp + WHERE p.id = qp.post_id AND mp.old_post_id = qp.quoted_post_id + SQL + end + def move_first_post_replies DB.exec <<~SQL UPDATE post_replies pr diff --git a/app/models/remote_theme.rb b/app/models/remote_theme.rb index c2cd50fbcf..e278da0ddb 100644 --- a/app/models/remote_theme.rb +++ b/app/models/remote_theme.rb @@ -146,7 +146,7 @@ class RemoteTheme < ActiveRecord::Base importer.all_files.each do |filename| next unless opts = ThemeField.opts_from_file_path(filename) value = importer[filename] - updated_fields << theme.set_field(opts.merge(value: value)) + updated_fields << theme.set_field(**opts.merge(value: value)) end # Destroy fields that no longer exist in the remote theme diff --git a/app/models/reviewable.rb b/app/models/reviewable.rb index 8966621a7e..287d5a5f92 100644 --- a/app/models/reviewable.rb +++ b/app/models/reviewable.rb @@ -98,6 +98,18 @@ class Reviewable < ActiveRecord::Base %w[ReviewableFlaggedPost ReviewableQueuedPost ReviewableUser] end + def self.custom_filters + @reviewable_filters ||= [] + end + + def self.add_custom_filter(new_filter) + custom_filters << new_filter + end + + def self.clear_custom_filters! + @reviewable_filters = [] + end + def created_new! self.created_new = true self.topic = target.topic if topic.blank? && target.is_a?(Post) @@ -228,7 +240,7 @@ class Reviewable < ActiveRecord::Base priority ||= SiteSetting.reviewable_default_visibility id = Reviewable.priorities[priority.to_sym] return 0.0 if id.nil? - return PluginStore.get('reviewables', "priority_#{id}").to_f + PluginStore.get('reviewables', "priority_#{id}").to_f end def history @@ -406,7 +418,10 @@ class Reviewable < ActiveRecord::Base offset: nil, priority: nil, username: nil, - sort_order: nil + sort_order: nil, + from_date: nil, + to_date: nil, + additional_filters: {} ) min_score = Reviewable.min_score_for_priority(priority) @@ -434,6 +449,18 @@ class Reviewable < ActiveRecord::Base result = result.where(category_id: category_id) if category_id result = result.where(topic_id: topic_id) if topic_id result = result.where("score >= ?", min_score) if min_score > 0 + result = result.where("created_at >= ?", from_date) if from_date + result = result.where("created_at <= ?", to_date) if to_date + + if !custom_filters.empty? + result = custom_filters.reduce(result) do |memo, filter| + key = filter.first + filter_query = filter.last + + next(memo) unless additional_filters[key] + filter_query.call(result, additional_filters[key]) + end + end # If a reviewable doesn't have a target, allow us to filter on who created that reviewable. if user_id diff --git a/app/models/reviewable_flagged_post.rb b/app/models/reviewable_flagged_post.rb index 340d63c8b5..5f3a3a6d95 100644 --- a/app/models/reviewable_flagged_post.rb +++ b/app/models/reviewable_flagged_post.rb @@ -228,7 +228,7 @@ class ReviewableFlaggedPost < Reviewable def perform_delete_and_agree_replies(performed_by, args) result = agree(performed_by, args) - PostDestroyer.delete_with_replies(performed_by, post, self) + PostDestroyer.delete_with_replies(performed_by, post, self, defer_reply_flags: false) result end diff --git a/app/models/search_log.rb b/app/models/search_log.rb index a98f5fcaf2..003331250b 100644 --- a/app/models/search_log.rb +++ b/app/models/search_log.rb @@ -104,7 +104,7 @@ class SearchLog < ActiveRecord::Base details << { x: Date.parse(record['date'].to_s), y: record['count'] } end - return { + { type: "search_log_term", title: I18n.t("search_logs.graph_title"), start_date: start_of(period), diff --git a/app/models/site.rb b/app/models/site.rb index c888fb22c9..de8cfe0be5 100644 --- a/app/models/site.rb +++ b/app/models/site.rb @@ -55,15 +55,11 @@ class Site by_id = {} - category_user = {} - unless @guardian.anonymous? - category_user = Hash[*CategoryUser.where(user: @guardian.user).pluck(:category_id, :notification_level).flatten] - end - - regular = CategoryUser.notification_levels[:regular] + notification_levels = CategoryUser.notification_levels_for(@guardian) + default_notification_level = CategoryUser.default_notification_level categories.each do |category| - category.notification_level = category_user[category.id] || regular + category.notification_level = notification_levels[category.id] || default_notification_level category.permission = CategoryGroup.permission_types[:full] if allowed_topic_create&.include?(category.id) || @guardian.is_admin? category.has_children = with_children.include?(category.id) by_id[category.id] = category @@ -78,10 +74,6 @@ class Site Group.visible_groups(@guardian.user, "name ASC", include_everyone: true) end - def suppressed_from_latest_category_ids - categories.select { |c| c.suppress_from_latest == true }.map(&:id) - end - def archetypes Archetype.list.reject { |t| t.id == Archetype.private_message } end diff --git a/app/models/tag.rb b/app/models/tag.rb index 375832614d..30b86b38cf 100644 --- a/app/models/tag.rb +++ b/app/models/tag.rb @@ -5,15 +5,17 @@ class Tag < ActiveRecord::Base include HasDestroyedWebHook validates :name, presence: true, uniqueness: { case_sensitive: false } + validate :target_tag_validator, if: Proc.new { |t| t.new_record? || t.will_save_change_to_target_tag_id? } scope :where_name, ->(name) do name = Array(name).map(&:downcase) - where("lower(name) IN (?)", name) + where("lower(tags.name) IN (?)", name) end scope :unused, -> { where(topic_count: 0, pm_topic_count: 0) } + scope :base_tags, -> { where(target_tag_id: nil) } - has_many :tag_users # notification settings + has_many :tag_users, dependent: :destroy # notification settings has_many :topic_tags, dependent: :destroy has_many :topics, through: :topic_tags @@ -21,10 +23,14 @@ class Tag < ActiveRecord::Base has_many :category_tags, dependent: :destroy has_many :categories, through: :category_tags - has_many :tag_group_memberships + has_many :tag_group_memberships, dependent: :destroy has_many :tag_groups, through: :tag_group_memberships + belongs_to :target_tag, class_name: "Tag", optional: true + has_many :synonyms, class_name: "Tag", foreign_key: "target_tag_id", dependent: :destroy + after_save :index_search + after_save :update_synonym_associations after_commit :trigger_tag_created_event, on: :create after_commit :trigger_tag_updated_event, on: :update @@ -137,6 +143,25 @@ class Tag < ActiveRecord::Base SearchIndexer.index(self) end + def synonym? + !self.target_tag_id.nil? + end + + def target_tag_validator + if synonyms.exists? + errors.add(:target_tag_id, I18n.t("tags.synonyms_exist")) + elsif target_tag&.synonym? + errors.add(:target_tag_id, I18n.t("tags.invalid_target_tag")) + end + end + + def update_synonym_associations + if target_tag_id && saved_change_to_target_tag_id? + target_tag.tag_groups.each { |tag_group| tag_group.tags << self unless tag_group.tags.include?(self) } + target_tag.categories.each { |category| category.tags << self unless category.tags.include?(self) } + end + end + %i{ tag_created tag_updated diff --git a/app/models/tag_user.rb b/app/models/tag_user.rb index 820d750b66..b4bc5d9f17 100644 --- a/app/models/tag_user.rb +++ b/app/models/tag_user.rb @@ -21,6 +21,12 @@ class TagUser < ActiveRecord::Base tag_ids = tags.empty? ? [] : Tag.where_name(tags).pluck(:id) + Tag.where_name(tags).joins(:target_tag).each do |tag| + tag_ids[tag_ids.index(tag.id)] = tag.target_tag_id + end + + tag_ids.uniq! + remove = (old_ids - tag_ids) if remove.present? records.where('tag_id in (?)', remove).destroy_all @@ -41,7 +47,17 @@ class TagUser < ActiveRecord::Base end def self.change(user_id, tag_id, level) - tag_id = tag_id.id if tag_id.is_a?(::Tag) + if tag_id.is_a?(::Tag) + tag = tag_id + tag_id = tag.id + else + tag = Tag.find_by_id(tag_id) + end + + if tag.synonym? + tag_id = tag.target_tag_id + end + user_id = user_id.id if user_id.is_a?(::User) tag_id = tag_id.to_i diff --git a/app/models/theme.rb b/app/models/theme.rb index 0597cda86f..f0476e67a9 100644 --- a/app/models/theme.rb +++ b/app/models/theme.rb @@ -376,10 +376,15 @@ class Theme < ActiveRecord::Base fields.values end - def add_child_theme!(theme) - new_relation = child_theme_relation.new(child_theme_id: theme.id) + def add_relative_theme!(kind, theme) + new_relation = if kind == :child + child_theme_relation.new(child_theme_id: theme.id) + else + parent_theme_relation.new(parent_theme_id: theme.id) + end if new_relation.save child_themes.reload + parent_themes.reload save! Theme.clear_cache! else diff --git a/app/models/theme_field.rb b/app/models/theme_field.rb index 38c19da7bb..fe4e0bf644 100644 --- a/app/models/theme_field.rb +++ b/app/models/theme_field.rb @@ -71,6 +71,8 @@ class ThemeField < ActiveRecord::Base errors = [] javascript_cache || build_javascript_cache + errors << I18n.t("themes.errors.optimized_link") if contains_optimized_link?(html) + js_compiler = ThemeJavascriptCompiler.new(theme_id, self.theme.name) doc = Nokogiri::HTML.fragment(html) @@ -355,7 +357,11 @@ class ThemeField < ActiveRecord::Base result = ["failed"] begin result = compile_scss - self.error = nil unless error.nil? + if contains_optimized_link?(self.value) + self.error = I18n.t("themes.errors.optimized_link") + else + self.error = nil unless error.nil? + end rescue SassC::SyntaxError => e self.error = e.message unless self.destroyed? end @@ -367,6 +373,10 @@ class ThemeField < ActiveRecord::Base Theme.targets[target_id].to_s end + def contains_optimized_link?(text) + OptimizedImage::URL_REGEX.match?(text) + end + class ThemeFileMatcher OPTIONS = %i{name type target} # regex: used to match file names to fields (import). diff --git a/app/models/topic.rb b/app/models/topic.rb index 1af8598d2d..6e10a13c2f 100644 --- a/app/models/topic.rb +++ b/app/models/topic.rb @@ -135,6 +135,7 @@ class Topic < ActiveRecord::Base # When we want to temporarily attach some data to a forum topic (usually before serialization) attr_accessor :user_data + attr_accessor :category_user_data attr_accessor :posters # TODO: can replace with posters_summary once we remove old list code attr_accessor :participants @@ -157,6 +158,8 @@ class Topic < ActiveRecord::Base scope :created_since, lambda { |time_ago| where('topics.created_at > ?', time_ago) } + scope :exclude_scheduled_bump_topics, -> { where.not(id: TopicTimer.scheduled_bump_topics) } + scope :secured, lambda { |guardian = nil| ids = guardian.secure_category_ids if guardian @@ -1371,7 +1374,8 @@ class Topic < ActiveRecord::Base post_type: Post.types[:regular] ).last || first_post - update!(bumped_at: post.created_at) + self.bumped_at = post.created_at + self.save(validate: false) end def auto_close_threshold_reached? diff --git a/app/models/topic_converter.rb b/app/models/topic_converter.rb index 650333749d..120920b412 100644 --- a/app/models/topic_converter.rb +++ b/app/models/topic_converter.rb @@ -30,9 +30,9 @@ class TopicConverter ) update_user_stats + update_post_uploads_secure_status Jobs.enqueue(:topic_action_converter, topic_id: @topic.id) Jobs.enqueue(:delete_inaccessible_notifications, topic_id: @topic.id) - watch_topic(topic) end @topic @@ -49,6 +49,7 @@ class TopicConverter ) add_allowed_users + update_post_uploads_secure_status Jobs.enqueue(:topic_action_converter, topic_id: @topic.id) Jobs.enqueue(:delete_inaccessible_notifications, topic_id: @topic.id) @@ -60,31 +61,30 @@ class TopicConverter private + def posters + @posters ||= @topic.posts.distinct.pluck(:user_id).to_a + end + def update_user_stats - @topic.posts.where(deleted_at: nil).each do |p| - user = User.find(p.user_id) - # update posts count. NOTE that DirectoryItem.refresh will overwrite this by counting UserAction records. - user.user_stat.post_count += 1 - user.user_stat.save! - end + # update posts count. NOTE that DirectoryItem.refresh will overwrite this by counting UserAction records. # update topics count - @topic.user.user_stat.topic_count += 1 - @topic.user.user_stat.save! + UserStat.where(user_id: posters).update_all('post_count = post_count + 1') + UserStat.where(user_id: @topic.user_id).update_all('topic_count = topic_count + 1') end def add_allowed_users - @topic.posts.where(deleted_at: nil).each do |p| - user = User.find(p.user_id) - @topic.topic_allowed_users.build(user_id: user.id) unless @topic.topic_allowed_users.where(user_id: user.id).exists? - # update posts count. NOTE that DirectoryItem.refresh will overwrite this by counting UserAction records. - user.user_stat.post_count -= 1 - user.user_stat.save! - end - @topic.topic_allowed_users.build(user_id: @user.id) unless @topic.topic_allowed_users.where(user_id: @user.id).exists? - @topic.topic_allowed_users = @topic.topic_allowed_users.uniq(&:user_id) + # update posts count. NOTE that DirectoryItem.refresh will overwrite this by counting UserAction records. # update topics count - @topic.user.user_stat.topic_count -= 1 - @topic.user.user_stat.save! + UserStat.where(user_id: posters).update_all('post_count = post_count - 1') + UserStat.where(user_id: @topic.user_id).update_all('topic_count = topic_count - 1') + + existing_allowed_users = @topic.topic_allowed_users.pluck(:user_id) + users_to_allow = posters << @user.id + + (users_to_allow - existing_allowed_users).uniq.each do |user_id| + @topic.topic_allowed_users.build(user_id: user_id) + end + @topic.save! end @@ -97,4 +97,11 @@ class TopicConverter end end + def update_post_uploads_secure_status + @topic.posts.each do |post| + next if post.uploads.empty? + post.update_uploads_secure_status + post.rebake! + end + end end diff --git a/app/models/topic_link.rb b/app/models/topic_link.rb index 5222d438dc..ed0633ecd7 100644 --- a/app/models/topic_link.rb +++ b/app/models/topic_link.rb @@ -172,11 +172,12 @@ class TopicLink < ActiveRecord::Base internal = false topic_id = nil post_number = nil + topic = nil if upload = Upload.get_from_url(url) internal = Discourse.store.internal? # Store the same URL that will be used in the cooked version of the post - url = UrlHelper.cook_url(upload.url) + url = UrlHelper.cook_url(upload.url, secure: upload.secure?) elsif route = Discourse.route_for(parsed) internal = true @@ -185,9 +186,11 @@ class TopicLink < ActiveRecord::Base topic_id = route[:topic_id].to_i post_number = route[:post_number] || 1 + topic_slug = route[:id] # Store the canonical URL topic = Topic.find_by(id: topic_id) + topic ||= Topic.find_by(slug: topic_slug) if topic_slug topic_id = nil unless topic if topic.present? @@ -197,11 +200,11 @@ class TopicLink < ActiveRecord::Base end # Skip linking to ourselves - return nil if topic_id == post.topic_id + return nil if topic&.id == post.topic_id reflected_post = nil - if post_number && topic_id - reflected_post = Post.find_by(topic_id: topic_id, post_number: post_number.to_i) + if post_number && topic + reflected_post = Post.find_by(topic_id: topic.id, post_number: post_number.to_i) end url = url[0...TopicLink.max_url_length] @@ -216,7 +219,7 @@ class TopicLink < ActiveRecord::Base url: url, domain: parsed.host || Discourse.current_hostname, internal: internal, - link_topic_id: topic_id, + link_topic_id: topic&.id, link_post_id: reflected_post.try(:id), quote: link.is_quote, extension: file_extension) @@ -228,31 +231,27 @@ class TopicLink < ActiveRecord::Base reflected_id = nil # Create the reflection if we can - if topic_id.present? - topic = Topic.find_by(id: topic_id) + if topic && post.topic && topic.archetype != 'private_message' && post.topic.archetype != 'private_message' && post.topic.visible? + prefix = Discourse.base_url_no_prefix + reflected_url = "#{prefix}#{post.topic.relative_url(post.post_number)}" + tl = TopicLink.find_by(topic_id: topic&.id, + post_id: reflected_post&.id, + url: reflected_url) - if topic && post.topic && topic.archetype != 'private_message' && post.topic.archetype != 'private_message' && post.topic.visible? - prefix = Discourse.base_url_no_prefix - reflected_url = "#{prefix}#{post.topic.relative_url(post.post_number)}" - tl = TopicLink.find_by(topic_id: topic_id, - post_id: reflected_post.try(:id), - url: reflected_url) + unless tl + tl = TopicLink.create(user_id: post.user_id, + topic_id: topic&.id, + post_id: reflected_post&.id, + url: reflected_url, + domain: Discourse.current_hostname, + reflection: true, + internal: true, + link_topic_id: post.topic_id, + link_post_id: post.id) - unless tl - tl = TopicLink.create(user_id: post.user_id, - topic_id: topic_id, - post_id: reflected_post.try(:id), - url: reflected_url, - domain: Discourse.current_hostname, - reflection: true, - internal: true, - link_topic_id: post.topic_id, - link_post_id: post.id) - - end - - reflected_id = tl.id if tl.persisted? end + + reflected_id = tl.id if tl.persisted? end [url, reflected_id] diff --git a/app/models/topic_list.rb b/app/models/topic_list.rb index 27730561d7..52b0ee0070 100644 --- a/app/models/topic_list.rb +++ b/app/models/topic_list.rb @@ -83,6 +83,7 @@ class TopicList # Attach some data for serialization to each topic @topic_lookup = TopicUser.lookup_for(@current_user, @topics) if @current_user + @category_user_lookup = CategoryUser.lookup_for(@current_user, @topics.map(&:category_id).uniq) if @current_user post_action_type = if @current_user @@ -114,6 +115,7 @@ class TopicList @topics.each do |ft| ft.user_data = @topic_lookup[ft.id] if @topic_lookup.present? + ft.category_user_data = @category_user_lookup[ft.category_id] if @category_user_lookup.present? if ft.user_data && post_action_lookup && actions = post_action_lookup[ft.id] ft.user_data.post_action_data = { post_action_type => actions } diff --git a/app/models/topic_participants_summary.rb b/app/models/topic_participants_summary.rb index 51ecfa15cc..df3f385670 100644 --- a/app/models/topic_participants_summary.rb +++ b/app/models/topic_participants_summary.rb @@ -3,6 +3,7 @@ # This is used on a topic page class TopicParticipantsSummary attr_reader :topic, :options + PARTICIPANT_COUNT = 5 # should match maxUserCount in topic list def initialize(topic, options = {}) @topic = topic @@ -26,7 +27,7 @@ class TopicParticipantsSummary end def top_participants - user_ids.map { |id| avatar_lookup[id] }.compact.uniq.take(4) + user_ids.map { |id| avatar_lookup[id] }.compact.uniq.take(PARTICIPANT_COUNT) end def user_ids diff --git a/app/models/topic_timer.rb b/app/models/topic_timer.rb index c6213a164e..1599f77ce5 100644 --- a/app/models/topic_timer.rb +++ b/app/models/topic_timer.rb @@ -17,6 +17,8 @@ class TopicTimer < ActiveRecord::Base validate :ensure_update_will_happen + scope :scheduled_bump_topics, -> { where(status_type: TopicTimer.types[:bump], deleted_at: nil).pluck(:topic_id) } + before_save do self.created_at ||= Time.zone.now if execute_at self.public_type = self.public_type? diff --git a/app/models/topic_tracking_state.rb b/app/models/topic_tracking_state.rb index 1b99db2095..3118214f8c 100644 --- a/app/models/topic_tracking_state.rb +++ b/app/models/topic_tracking_state.rb @@ -144,6 +144,15 @@ class TopicTrackingState MessageBus.publish(self.unread_channel_key(user_id), message.as_json, user_ids: [user_id]) end + def self.publish_dismiss_new(user_id, category_id = nil) + payload = category_id ? { category_id: category_id } : {} + message = { + message_type: "dismiss_new", + payload: payload + } + MessageBus.publish(self.unread_channel_key(user_id), message.as_json, user_ids: [user_id]) + end + def self.treat_as_new_topic_clause User.where("GREATEST(CASE WHEN COALESCE(uo.new_topic_duration_minutes, :default_duration) = :always THEN u.created_at @@ -159,7 +168,6 @@ class TopicTrackingState end def self.report(user, topic_id = nil) - # Sam: this is a hairy report, in particular I need custom joins and fancy conditions # Dropping to sql_builder so I can make sense of it. # @@ -173,7 +181,8 @@ class TopicTrackingState skip_unread: true, skip_order: true, staff: user.staff?, - admin: user.admin? + admin: user.admin?, + user: user ) sql << "\nUNION ALL\n\n" @@ -184,7 +193,8 @@ class TopicTrackingState skip_order: true, staff: user.staff?, filter_old_unread: true, - admin: user.admin? + admin: user.admin?, + user: user ) DB.query( @@ -221,7 +231,8 @@ class TopicTrackingState "1=0" else TopicQuery.new_filter(Topic, "xxx").where_clause.send(:predicates).join(" AND ").gsub!("'xxx'", treat_as_new_topic_clause) + - " AND topics.created_at > :min_new_topic_date" + " AND topics.created_at > :min_new_topic_date" + + " AND (category_users.last_seen_at IS NULL OR topics.created_at > category_users.last_seen_at)" end select = (opts[:select]) || " @@ -240,7 +251,7 @@ class TopicTrackingState append = "OR u.admin" if !opts.key?(:admin) <<~SQL ( - NOT c.read_restricted #{append} OR category_id IN ( + NOT c.read_restricted #{append} OR c.id IN ( SELECT c2.id FROM categories c2 JOIN category_groups cg ON cg.category_id = c2.id JOIN group_users gu ON gu.user_id = :user_id AND cg.group_id = gu.group_id @@ -265,6 +276,7 @@ class TopicTrackingState JOIN user_options AS uo ON uo.user_id = u.id JOIN categories c ON c.id = topics.category_id LEFT JOIN topic_users tu ON tu.topic_id = topics.id AND tu.user_id = u.id + LEFT JOIN category_users ON category_users.category_id = topics.category_id AND category_users.user_id = #{opts[:user].id} WHERE u.id = :user_id AND #{filter_old_unread} topics.archetype <> 'private_message' AND @@ -272,12 +284,9 @@ class TopicTrackingState #{visibility_filter} topics.deleted_at IS NULL AND #{category_filter} - NOT EXISTS( SELECT 1 FROM category_users cu - WHERE last_read_post_number IS NULL AND - cu.user_id = :user_id AND - cu.category_id = topics.category_id AND - cu.notification_level = #{CategoryUser.notification_levels[:muted]}) - + (category_users.id IS NULL OR + last_read_post_number IS NOT NULL OR + category_users.notification_level <> #{CategoryUser.notification_levels[:muted]}) SQL if opts[:topic_id] diff --git a/app/models/trust_level3_requirements.rb b/app/models/trust_level3_requirements.rb index 6b7746b223..3092259aaf 100644 --- a/app/models/trust_level3_requirements.rb +++ b/app/models/trust_level3_requirements.rb @@ -7,9 +7,14 @@ class TrustLevel3Requirements class PenaltyCounts attr_reader :silenced, :suspended - def initialize(row) + def initialize(user, row) @silenced = row['silence_count'] || 0 @suspended = row['suspend_count'] || 0 + + # If penalty started more than 6 months ago and still continues, it will + # not be selected by the query from 'penalty_counts'. + @silenced += 1 if @silenced == 0 && user.silenced? + @suspended += 1 if @suspended == 0 && user.suspended? end def total @@ -114,7 +119,7 @@ class TrustLevel3Requirements AND uh.created_at > :since SQL - PenaltyCounts.new(DB.query_hash(sql, args).first) + PenaltyCounts.new(@user, DB.query_hash(sql, args).first) end def min_days_visited diff --git a/app/models/upload.rb b/app/models/upload.rb index 5821afea84..a6842ce9a9 100644 --- a/app/models/upload.rb +++ b/app/models/upload.rb @@ -140,11 +140,6 @@ class Upload < ActiveRecord::Base !(url =~ /^(https?:)?\/\//) end - def private? - return false if self.for_theme || self.for_site_setting - SiteSetting.prevent_anons_from_downloading_files && !FileHelper.is_supported_image?(self.original_filename) - end - def fix_dimensions! return if !FileHelper.is_supported_image?("image.#{extension}") @@ -235,6 +230,34 @@ class Upload < ActiveRecord::Base self.posts.where("cooked LIKE '%/_optimized/%'").find_each(&:rebake!) end + def update_secure_status(secure_override_value: nil) + return false if self.for_theme || self.for_site_setting + mark_secure = secure_override_value.nil? ? should_be_secure? : secure_override_value + + self.update_column("secure", mark_secure) + Discourse.store.update_upload_ACL(self) if Discourse.store.external? + end + + def should_be_secure? + mark_secure = false + if FileHelper.is_supported_media?(self.original_filename) + if SiteSetting.secure_media? + mark_secure = true if SiteSetting.login_required? + unless SiteSetting.login_required? + # first post associated with upload determines secure status + # i.e. an already public upload will stay public even if added to a new PM + first_post_with_upload = self.posts.order(sort_order: :asc).first + mark_secure = first_post_with_upload ? first_post_with_upload.with_secure_media? : false + end + else + mark_secure = false + end + else + mark_secure = SiteSetting.prevent_anons_from_downloading_files? + end + mark_secure + end + def self.migrate_to_new_scheme(limit: nil) problems = [] @@ -385,6 +408,7 @@ end # thumbnail_width :integer # thumbnail_height :integer # etag :string +# secure :boolean default(FALSE), not null # # Indexes # diff --git a/app/models/user.rb b/app/models/user.rb index 6f5ec24ff1..44dbfa7b1f 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -56,7 +56,6 @@ class User < ActiveRecord::Base has_many :user_associated_accounts, dependent: :destroy has_one :github_user_info, dependent: :destroy has_many :oauth2_user_infos, dependent: :destroy - has_one :instagram_user_info, dependent: :destroy has_many :user_second_factors, dependent: :destroy has_many :totps, -> { @@ -418,6 +417,7 @@ class User < ActiveRecord::Base def enqueue_staff_welcome_message(role) return unless staff? + return if role == :admin && User.real.where(admin: true).count == 1 Jobs.enqueue( :send_system_message, @@ -670,6 +670,15 @@ class User < ActiveRecord::Base create_visit_record!(date) unless visit_record_for(date) end + def update_timezone_if_missing(timezone) + return if timezone.blank? || !TimezoneValidator.valid?(timezone) + + # we only want to update the user's timezone if they have not set it themselves + UserOption + .where(user_id: self.id, timezone: nil) + .update_all(timezone: timezone) + end + def update_posts_read!(num_posts, opts = {}) now = opts[:at] || Time.zone.now _retry = opts[:retry] || false @@ -908,7 +917,7 @@ class User < ActiveRecord::Base def email_confirmed? email_tokens.where(email: email, confirmed: true).present? || email_tokens.empty? || - single_sign_on_record&.external_email == email + single_sign_on_record&.external_email&.downcase == email end def activate @@ -1478,8 +1487,13 @@ class User < ActiveRecord::Base def check_if_title_is_badged_granted if title_changed? && !new_record? && user_profile - badge_granted_title = title.present? && badges.where(allow_title: true, name: title).exists? - user_profile.update_column(:badge_granted_title, badge_granted_title) + badge_matching_title = title && badges.find do |badge| + badge.allow_title? && (badge.display_name == title || badge.name == title) + end + user_profile.update( + badge_granted_title: badge_matching_title.present?, + granted_title_badge_id: badge_matching_title&.id + ) end end diff --git a/app/models/user_api_key.rb b/app/models/user_api_key.rb index dbd6c52862..7d696246ca 100644 --- a/app/models/user_api_key.rb +++ b/app/models/user_api_key.rb @@ -67,9 +67,9 @@ class UserApiKey < ActiveRecord::Base end def self.invalid_auth_redirect?(auth_redirect) - return SiteSetting.allowed_user_api_auth_redirects - .split('|') - .none? { |u| WildcardUrlChecker.check_url(u, auth_redirect) } + SiteSetting.allowed_user_api_auth_redirects + .split('|') + .none? { |u| WildcardUrlChecker.check_url(u, auth_redirect) } end end diff --git a/app/models/user_auth_token.rb b/app/models/user_auth_token.rb index 30a38c68bc..48079e351d 100644 --- a/app/models/user_auth_token.rb +++ b/app/models/user_auth_token.rb @@ -8,6 +8,8 @@ class UserAuthToken < ActiveRecord::Base # used when token did not arrive at client URGENT_ROTATE_TIME = 1.minute + MAX_SESSION_COUNT = 60 + USER_ACTIONS = ['generate'] attr_accessor :unhashed_auth_token @@ -220,6 +222,14 @@ class UserAuthToken < ActiveRecord::Base end end + + def self.enforce_session_count_limit!(user_id) + tokens_to_destroy = where(user_id: user_id). + where('rotated_at > ?', SiteSetting.maximum_session_age.hours.ago). + order("rotated_at DESC").offset(MAX_SESSION_COUNT) + + tokens_to_destroy.delete_all # Returns the number of deleted rows + end end # == Schema Information diff --git a/app/models/user_history.rb b/app/models/user_history.rb index ec6fec4f11..3adbb62eb1 100644 --- a/app/models/user_history.rb +++ b/app/models/user_history.rb @@ -101,6 +101,9 @@ class UserHistory < ActiveRecord::Base api_key_create: 80, api_key_update: 81, api_key_destroy: 82, + revoke_title: 83, + change_title: 84, + override_upload_secure_status: 85 ) end @@ -175,9 +178,12 @@ class UserHistory < ActiveRecord::Base :change_theme_setting, :disable_theme_component, :enable_theme_component, + :revoke_title, + :change_title, :api_key_create, :api_key_update, :api_key_destroy, + :override_upload_secure_status ] end diff --git a/app/models/user_option.rb b/app/models/user_option.rb index 7f606671c7..10fd562c6c 100644 --- a/app/models/user_option.rb +++ b/app/models/user_option.rb @@ -42,6 +42,7 @@ class UserOption < ActiveRecord::Base validates :text_size_key, inclusion: { in: UserOption.text_sizes.values } validates :email_level, inclusion: { in: UserOption.email_level_types.values } validates :email_messages_level, inclusion: { in: UserOption.email_level_types.values } + validates :timezone, timezone: true def set_defaults self.mailing_list_mode = SiteSetting.default_email_mailing_list_mode @@ -224,6 +225,7 @@ end # email_messages_level :integer default(0), not null # title_count_mode_key :integer default(0), not null # enable_defer :boolean default(FALSE), not null +# timezone :string # # Indexes # diff --git a/app/models/user_profile.rb b/app/models/user_profile.rb index 7f16cfe90e..abd5ba06c2 100644 --- a/app/models/user_profile.rb +++ b/app/models/user_profile.rb @@ -9,6 +9,7 @@ class UserProfile < ActiveRecord::Base belongs_to :user, inverse_of: :user_profile belongs_to :card_background_upload, class_name: "Upload" belongs_to :profile_background_upload, class_name: "Upload" + belongs_to :granted_title_badge, class_name: "Badge" validates :bio_raw, length: { maximum: 3000 } validates :website, url: true, allow_blank: true, if: Proc.new { |c| c.new_record? || c.website_changed? } @@ -161,15 +162,18 @@ end # views :integer default(0), not null # profile_background_upload_id :integer # card_background_upload_id :integer +# granted_title_badge_id :bigint # # Indexes # -# index_user_profiles_on_bio_cooked_version (bio_cooked_version) -# index_user_profiles_on_card_background (card_background) -# index_user_profiles_on_profile_background (profile_background) +# index_user_profiles_on_bio_cooked_version (bio_cooked_version) +# index_user_profiles_on_card_background (card_background) +# index_user_profiles_on_granted_title_badge_id (granted_title_badge_id) +# index_user_profiles_on_profile_background (profile_background) # # Foreign Keys # # fk_rails_... (card_background_upload_id => uploads.id) +# fk_rails_... (granted_title_badge_id => badges.id) # fk_rails_... (profile_background_upload_id => uploads.id) # diff --git a/app/serializers/admin_detailed_user_serializer.rb b/app/serializers/admin_detailed_user_serializer.rb index 3555522497..4a83a594b1 100644 --- a/app/serializers/admin_detailed_user_serializer.rb +++ b/app/serializers/admin_detailed_user_serializer.rb @@ -39,7 +39,7 @@ class AdminDetailedUserSerializer < AdminUserSerializer has_many :groups, embed: :object, serializer: BasicGroupSerializer def second_factor_enabled - object.totp_enabled? + object.totp_enabled? || object.security_keys_enabled? end def can_disable_second_factor diff --git a/app/serializers/admin_user_list_serializer.rb b/app/serializers/admin_user_list_serializer.rb index e670cd767f..5e1363f108 100644 --- a/app/serializers/admin_user_list_serializer.rb +++ b/app/serializers/admin_user_list_serializer.rb @@ -23,7 +23,6 @@ class AdminUserListSerializer < BasicUserSerializer :approved, :suspended_at, :suspended_till, - :suspended, :silenced, :silenced_till, :time_read, @@ -62,10 +61,6 @@ class AdminUserListSerializer < BasicUserSerializer object.silenced_till? end - def suspended - object.suspended? - end - def include_suspended_at? object.suspended? end diff --git a/app/serializers/basic_group_serializer.rb b/app/serializers/basic_group_serializer.rb index d3c012194a..df250920d8 100644 --- a/app/serializers/basic_group_serializer.rb +++ b/app/serializers/basic_group_serializer.rb @@ -45,7 +45,7 @@ class BasicGroupSerializer < ApplicationSerializer end def bio_excerpt - PrettyText.excerpt(object.bio_cooked, 110) if object.bio_cooked.present? + PrettyText.excerpt(object.bio_cooked, 110, keep_emoji_images: true) if object.bio_cooked.present? end def include_incoming_email? diff --git a/app/serializers/basic_user_badge_serializer.rb b/app/serializers/basic_user_badge_serializer.rb index 510f23b5e6..dd3880e423 100644 --- a/app/serializers/basic_user_badge_serializer.rb +++ b/app/serializers/basic_user_badge_serializer.rb @@ -10,6 +10,6 @@ class BasicUserBadgeSerializer < ApplicationSerializer end def grouping_position - object.badge.badge_grouping.position + object.badge&.badge_grouping&.position end end diff --git a/app/serializers/category_serializer.rb b/app/serializers/category_serializer.rb index b5bae438a7..cb5b2ff056 100644 --- a/app/serializers/category_serializer.rb +++ b/app/serializers/category_serializer.rb @@ -11,7 +11,6 @@ class CategorySerializer < SiteCategorySerializer :email_in, :email_in_allow_strangers, :mailinglist_mirror, - :suppress_from_latest, :all_topics_wiki, :can_delete, :cannot_delete_reason, @@ -82,10 +81,6 @@ class CategorySerializer < SiteCategorySerializer scope && scope.can_edit?(object) end - def include_suppress_from_latest? - scope && scope.can_edit?(object) - end - def notification_level user = scope && scope.user object.notification_level || diff --git a/app/serializers/current_user_serializer.rb b/app/serializers/current_user_serializer.rb index 4ba1a982f8..a119bb324a 100644 --- a/app/serializers/current_user_serializer.rb +++ b/app/serializers/current_user_serializer.rb @@ -44,7 +44,8 @@ class CurrentUserSerializer < BasicUserSerializer :groups, :second_factor_enabled, :ignored_users, - :title_count_mode + :title_count_mode, + :timezone def groups object.visible_groups.pluck(:id, :name).map { |id, name| { id: id, name: name.downcase } } @@ -106,6 +107,10 @@ class CurrentUserSerializer < BasicUserSerializer object.user_option.redirected_to_top end + def timezone + object.user_option.timezone + end + def can_send_private_email_messages scope.can_send_private_messages_to_email? end @@ -210,6 +215,6 @@ class CurrentUserSerializer < BasicUserSerializer end def second_factor_enabled - object.totp_enabled? + object.totp_enabled? || object.security_keys_enabled? end end diff --git a/app/serializers/detailed_tag_serializer.rb b/app/serializers/detailed_tag_serializer.rb new file mode 100644 index 0000000000..25b0c555c9 --- /dev/null +++ b/app/serializers/detailed_tag_serializer.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +class DetailedTagSerializer < TagSerializer + attributes :synonyms, :tag_group_names + + has_many :categories, serializer: BasicCategorySerializer + + def synonyms + TagsController.tag_counts_json(object.synonyms) + end + + def categories + Category.secured(scope).where( + id: object.categories.pluck(:id) + + object.tag_groups.includes(:categories).map do |tg| + tg.categories.map(&:id) + end.flatten + ) + end + + def include_tag_group_names? + scope.is_admin? || SiteSetting.tags_listed_by_group == true + end + + def tag_group_names + object.tag_groups.map(&:name) + end +end diff --git a/app/serializers/group_user_serializer.rb b/app/serializers/group_user_serializer.rb index 4b79988877..aa191cf18d 100644 --- a/app/serializers/group_user_serializer.rb +++ b/app/serializers/group_user_serializer.rb @@ -7,7 +7,8 @@ class GroupUserSerializer < BasicUserSerializer :title, :last_posted_at, :last_seen_at, - :added_at + :added_at, + :timezone def include_added_at object.respond_to? :added_at diff --git a/app/serializers/invite_serializer.rb b/app/serializers/invite_serializer.rb index 26b5c1794f..befa52fc21 100644 --- a/app/serializers/invite_serializer.rb +++ b/app/serializers/invite_serializer.rb @@ -2,7 +2,7 @@ class InviteSerializer < ApplicationSerializer - attributes :email, :created_at, :redeemed_at, :expired, :user + attributes :email, :updated_at, :redeemed_at, :expired, :user def include_email? !object.redeemed? diff --git a/app/serializers/listable_topic_serializer.rb b/app/serializers/listable_topic_serializer.rb index 1336a92c14..4e5829df02 100644 --- a/app/serializers/listable_topic_serializer.rb +++ b/app/serializers/listable_topic_serializer.rb @@ -59,6 +59,7 @@ class ListableTopicSerializer < BasicTopicSerializer def seen return true if !scope || !scope.user return true if object.user_data && !object.user_data.last_read_post_number.nil? + return true if object.category_user_data&.last_seen_at && object.created_at < object.category_user_data.last_seen_at return true if object.created_at < scope.user.user_option.treat_as_new_topic_start_date false end diff --git a/app/serializers/new_post_result_serializer.rb b/app/serializers/new_post_result_serializer.rb index ca6eee7f9c..9232062711 100644 --- a/app/serializers/new_post_result_serializer.rb +++ b/app/serializers/new_post_result_serializer.rb @@ -6,7 +6,9 @@ class NewPostResultSerializer < ApplicationSerializer :errors, :success, :pending_count, - :reason + :reason, + :message, + :route_to has_one :pending_post, serializer: TopicPendingPostSerializer, root: false, embed: :objects @@ -64,4 +66,20 @@ class NewPostResultSerializer < ApplicationSerializer pending_count.present? end + def route_to + object.route_to + end + + def include_route_to? + object.route_to.present? + end + + def message + object.message + end + + def include_message? + object.message.present? + end + end diff --git a/app/serializers/post_serializer.rb b/app/serializers/post_serializer.rb index 0e2a7b3072..8651252e84 100644 --- a/app/serializers/post_serializer.rb +++ b/app/serializers/post_serializer.rb @@ -368,7 +368,7 @@ class PostSerializer < BasicPostSerializer end def notice_type - post_custom_fields["notice_type"] + post_custom_fields[Post::NOTICE_TYPE] end def include_notice_type? @@ -389,7 +389,7 @@ class PostSerializer < BasicPostSerializer end def notice_args - post_custom_fields["notice_args"] + post_custom_fields[Post::NOTICE_ARGS] end def include_notice_args? diff --git a/app/serializers/reviewable_serializer.rb b/app/serializers/reviewable_serializer.rb index b2cbdab459..af03419d2a 100644 --- a/app/serializers/reviewable_serializer.rb +++ b/app/serializers/reviewable_serializer.rb @@ -106,7 +106,7 @@ class ReviewableSerializer < ApplicationSerializer end def target_url - return object.target.url if object.target.is_a?(Post) && object.target.present? + return Discourse.base_url + object.target.url if object.target.is_a?(Post) && object.target.present? topic_url end @@ -115,7 +115,7 @@ class ReviewableSerializer < ApplicationSerializer end def topic_url - return object.topic&.url + object.topic&.url end def include_topic_url? diff --git a/app/serializers/site_serializer.rb b/app/serializers/site_serializer.rb index 3f8a653d96..d7c2ef58fe 100644 --- a/app/serializers/site_serializer.rb +++ b/app/serializers/site_serializer.rb @@ -15,7 +15,6 @@ class SiteSerializer < ApplicationSerializer :is_readonly, :disabled_plugins, :user_field_max_length, - :suppressed_from_latest_category_ids, :post_action_types, :topic_flag_types, :can_create_tag, diff --git a/app/serializers/tag_group_serializer.rb b/app/serializers/tag_group_serializer.rb index 3fa051bfa9..db61936fce 100644 --- a/app/serializers/tag_group_serializer.rb +++ b/app/serializers/tag_group_serializer.rb @@ -4,7 +4,7 @@ class TagGroupSerializer < ApplicationSerializer attributes :id, :name, :tag_names, :parent_tag_name, :one_per_topic, :permissions def tag_names - object.tags.map(&:name).sort + object.tags.base_tags.map(&:name).sort end def parent_tag_name diff --git a/app/serializers/topic_list_item_serializer.rb b/app/serializers/topic_list_item_serializer.rb index 658df93614..7962c400c0 100644 --- a/app/serializers/topic_list_item_serializer.rb +++ b/app/serializers/topic_list_item_serializer.rb @@ -14,7 +14,8 @@ class TopicListItemSerializer < ListableTopicSerializer :bookmarked_post_numbers, :liked_post_numbers, :featured_link, - :featured_link_root_domain + :featured_link_root_domain, + :allowed_user_count has_many :posters, serializer: TopicPosterSerializer, embed: :objects has_many :participants, serializer: TopicPosterSerializer, embed: :objects @@ -86,4 +87,12 @@ class TopicListItemSerializer < ListableTopicSerializer SiteSetting.topic_featured_link_enabled && object.featured_link.present? end + def allowed_user_count + object.allowed_users.count + end + + def include_allowed_user_count? + object.private_message? + end + end diff --git a/app/serializers/user_auth_token_log_serializer.rb b/app/serializers/user_auth_token_log_serializer.rb deleted file mode 100644 index 0a676f8c7c..0000000000 --- a/app/serializers/user_auth_token_log_serializer.rb +++ /dev/null @@ -1,18 +0,0 @@ -# frozen_string_literal: true - -class UserAuthTokenLogSerializer < ApplicationSerializer - include UserAuthTokensMixin - - attributes :action - - def action - case object.action - when 'generate' - I18n.t('log_in') - when 'destroy' - I18n.t('unsubscribe.log_out') - else - I18n.t('staff_action_logs.unknown') - end - end -end diff --git a/app/serializers/user_option_serializer.rb b/app/serializers/user_option_serializer.rb index 4a8a5a1d0c..4c78c7b5bd 100644 --- a/app/serializers/user_option_serializer.rb +++ b/app/serializers/user_option_serializer.rb @@ -27,7 +27,8 @@ class UserOptionSerializer < ApplicationSerializer :hide_profile_and_presence, :text_size, :text_size_seq, - :title_count_mode + :title_count_mode, + :timezone def auto_track_topics_after_msecs object.auto_track_topics_after_msecs || SiteSetting.default_other_auto_track_topics_after_msecs diff --git a/app/serializers/user_serializer.rb b/app/serializers/user_serializer.rb index 52d1214cfd..79111200ef 100644 --- a/app/serializers/user_serializer.rb +++ b/app/serializers/user_serializer.rb @@ -164,7 +164,7 @@ class UserSerializer < BasicUserSerializer end def second_factor_enabled - object.totp_enabled? + object.totp_enabled? || object.security_keys_enabled? end def include_second_factor_backup_enabled? diff --git a/app/serializers/web_hook_post_serializer.rb b/app/serializers/web_hook_post_serializer.rb index 030b55203b..2099a17c4c 100644 --- a/app/serializers/web_hook_post_serializer.rb +++ b/app/serializers/web_hook_post_serializer.rb @@ -3,6 +3,7 @@ class WebHookPostSerializer < PostSerializer attributes :topic_posts_count, + :topic_filtered_posts_count, :topic_archetype, :category_slug @@ -34,6 +35,10 @@ class WebHookPostSerializer < PostSerializer object.topic ? object.topic.posts_count : 0 end + def topic_filtered_posts_count + object.topic ? object.topic.posts.where(post_type: Post.types[:regular]).count : 0 + end + def topic_archetype object.topic ? object.topic.archetype : '' end diff --git a/app/services/badge_granter.rb b/app/services/badge_granter.rb index 737837fa67..25462cf467 100644 --- a/app/services/badge_granter.rb +++ b/app/services/badge_granter.rb @@ -72,8 +72,19 @@ class BadgeGranter StaffActionLogger.new(options[:revoked_by]).log_badge_revoke(user_badge) end - # If the user's title is the same as the badge name, remove their title. - if user_badge.user.title == user_badge.badge.name + # If the user's title is the same as the badge name OR the custom badge name, remove their title. + custom_badge_name = TranslationOverride.find_by(translation_key: user_badge.badge.translation_key)&.value + user_title_is_badge_name = user_badge.user.title == user_badge.badge.name + user_title_is_custom_badge_name = custom_badge_name.present? && user_badge.user.title == custom_badge_name + + if user_title_is_badge_name || user_title_is_custom_badge_name + if options[:revoked_by] + StaffActionLogger.new(options[:revoked_by]).log_title_revoke( + user_badge.user, + revoke_reason: 'user title was same as revoked badge name or custom badge name', + previous_value: user_badge.user.title + ) + end user_badge.user.title = nil user_badge.user.save! end diff --git a/app/services/notification_consolidator.rb b/app/services/notification_consolidator.rb new file mode 100644 index 0000000000..9ba3dc0ba4 --- /dev/null +++ b/app/services/notification_consolidator.rb @@ -0,0 +1,88 @@ +# frozen_string_literal: true + +class NotificationConsolidator + attr_reader :notification, :notification_type, :consolidation_type, :data + + def initialize(notification) + @notification = notification + @notification_type = notification.notification_type + @data = notification.data_hash + + if notification_type == Notification.types[:liked] + @consolidation_type = Notification.types[:liked_consolidated] + @data[:username] = @data[:display_username] + elsif notification_type == Notification.types[:private_message] + post_id = @data[:original_post_id] + return if post_id.blank? + + custom_field = PostCustomField.select(:value).find_by(post_id: post_id, name: "requested_group_id") + return if custom_field.blank? + + group_id = custom_field.value.to_i + group_name = Group.select(:name).find_by(id: group_id)&.name + return if group_name.blank? + + @consolidation_type = Notification.types[:membership_request_consolidated] + @data[:group_name] = group_name + end + end + + def consolidate! + return if SiteSetting.notification_consolidation_threshold.zero? || consolidation_type.blank? + + update_consolidated_notification! || create_consolidated_notification! + end + + def update_consolidated_notification! + consolidated_notification = user_notifications.filter_by_consolidation_data(consolidation_type, data).first + return if consolidated_notification.blank? + + data_hash = consolidated_notification.data_hash + data_hash["count"] += 1 + + Notification.transaction do + consolidated_notification.update!( + data: data_hash.to_json, + read: false, + updated_at: timestamp + ) + notification.destroy! + end + + consolidated_notification + end + + def create_consolidated_notification! + notifications = user_notifications.unread.filter_by_consolidation_data(notification_type, data) + return if notifications.count <= SiteSetting.notification_consolidation_threshold + + consolidated_notification = nil + + Notification.transaction do + timestamp = notifications.last.created_at + data[:count] = notifications.count + + consolidated_notification = Notification.create!( + notification_type: consolidation_type, + user_id: notification.user_id, + data: data.to_json, + updated_at: timestamp, + created_at: timestamp + ) + + notifications.destroy_all + end + + consolidated_notification + end + + private + + def user_notifications + notification.user.notifications + end + + def timestamp + @timestamp ||= Time.zone.now + end +end diff --git a/app/services/post_alerter.rb b/app/services/post_alerter.rb index d71e4b4914..f769649e53 100644 --- a/app/services/post_alerter.rb +++ b/app/services/post_alerter.rb @@ -249,7 +249,7 @@ class PostAlerter end def should_notify_edit?(notification, opts) - return notification.data_hash["display_username"] != opts[:display_username] + notification.data_hash["display_username"] != opts[:display_username] end def should_notify_like?(user, notification) @@ -331,28 +331,19 @@ class PostAlerter notification_data = {} - if is_liked - if existing_notification_of_same_type && - existing_notification_of_same_type.created_at > 1.day.ago && - ( - user.user_option.like_notification_frequency == - UserOption.like_notification_frequency_type[:always] - ) + if is_liked && + existing_notification_of_same_type && + existing_notification_of_same_type.created_at > 1.day.ago && + ( + user.user_option.like_notification_frequency == + UserOption.like_notification_frequency_type[:always] + ) - data = existing_notification_of_same_type.data_hash - notification_data["username2"] = data["display_username"] - notification_data["count"] = (data["count"] || 1).to_i + 1 - # don't use destroy so we don't trigger a notification count refresh - Notification.where(id: existing_notification_of_same_type.id).destroy_all - elsif !SiteSetting.likes_notification_consolidation_threshold.zero? - notification = consolidate_liked_notifications( - user, - post, - opts[:display_username] - ) - - return notification if notification - end + data = existing_notification_of_same_type.data_hash + notification_data["username2"] = data["display_username"] + notification_data["count"] = (data["count"] || 1).to_i + 1 + # don't use destroy so we don't trigger a notification count refresh + Notification.where(id: existing_notification_of_same_type.id).destroy_all end collapsed = false @@ -559,7 +550,7 @@ class PostAlerter end end - def notify_post_users(post, notified) + def notify_post_users(post, notified, include_category_watchers: true, include_tag_watchers: true) return unless post.topic warn_if_not_sidekiq @@ -570,8 +561,14 @@ class PostAlerter FROM topic_users WHERE notification_level = :watching AND topic_id = :topic_id + /*category*/ + /*tags*/ + ) + SQL - UNION + if include_category_watchers + condition.sub! "/*category*/", <<~SQL + UNION SELECT cu.user_id FROM category_users cu @@ -580,14 +577,12 @@ class PostAlerter WHERE cu.notification_level = :watching AND cu.category_id = :category_id AND tu.user_id IS NULL - - /*tags*/ - ) - SQL + SQL + end tag_ids = post.topic.topic_tags.pluck('topic_tags.tag_id') - if tag_ids.present? + if include_tag_watchers && tag_ids.present? condition.sub! "/*tags*/", <<~SQL UNION @@ -621,82 +616,4 @@ class PostAlerter def warn_if_not_sidekiq Rails.logger.warn("PostAlerter.#{caller_locations(1, 1)[0].label} was called outside of sidekiq") unless Sidekiq.server? end - - private - - def consolidate_liked_notifications(user, post, username) - user_notifications = user.notifications - - consolidation_window = - SiteSetting.likes_notification_consolidation_window_mins.minutes.ago - - liked_by_user_notifications = - user_notifications - .filter_by_display_username_and_type( - username, Notification.types[:liked] - ) - .where( - "created_at > ? AND data::json ->> 'username2' IS NULL", - consolidation_window - ) - - user_liked_consolidated_notification = - user_notifications - .filter_by_display_username_and_type( - username, Notification.types[:liked_consolidated] - ) - .where("created_at > ?", consolidation_window) - .first - - if user_liked_consolidated_notification - return update_consolidated_liked_notification_count!( - user_liked_consolidated_notification - ) - elsif ( - liked_by_user_notifications.count >= - SiteSetting.likes_notification_consolidation_threshold - ) - return create_consolidated_liked_notification!( - liked_by_user_notifications, - post, - username - ) - end - end - - def update_consolidated_liked_notification_count!(notification) - data = notification.data_hash - data["count"] += 1 - - notification.update!( - data: data.to_json, - read: false - ) - - notification - end - - def create_consolidated_liked_notification!(notifications, post, username) - notification = nil - - Notification.transaction do - timestamp = notifications.last.created_at - - notification = Notification.create!( - notification_type: Notification.types[:liked_consolidated], - user_id: post.user_id, - data: { - username: username, - display_username: username, - count: notifications.count + 1 - }.to_json, - updated_at: timestamp, - created_at: timestamp - ) - - notifications.each(&:destroy!) - end - - notification - end end diff --git a/app/services/search_indexer.rb b/app/services/search_indexer.rb index 8b3c84944b..17817c4c09 100644 --- a/app/services/search_indexer.rb +++ b/app/services/search_indexer.rb @@ -137,7 +137,12 @@ class SearchIndexer end category_name = topic.category&.name if topic - tag_names = topic.tags.pluck(:name).join(' ') if topic + if topic + tags = topic.tags.select(:id, :name) + unless tags.empty? + tag_names = (tags.map(&:name) + Tag.where(target_tag_id: tags.map(&:id)).pluck(:name)).join(' ') + end + end if Post === obj && obj.raw.present? && ( diff --git a/app/services/site_settings_task.rb b/app/services/site_settings_task.rb index 4cddf6d4dc..f9d185cdf2 100644 --- a/app/services/site_settings_task.rb +++ b/app/services/site_settings_task.rb @@ -36,6 +36,6 @@ class SiteSettingsTask counts[:not_found] += 1 end end - return log, counts + [log, counts] end end diff --git a/app/services/staff_action_logger.rb b/app/services/staff_action_logger.rb index f6264da3eb..9130f34692 100644 --- a/app/services/staff_action_logger.rb +++ b/app/services/staff_action_logger.rb @@ -352,6 +352,38 @@ class StaffActionLogger )) end + def log_title_revoke(user, opts = {}) + raise Discourse::InvalidParameters.new(:user) unless user + UserHistory.create!(params(opts).merge( + action: UserHistory.actions[:revoke_title], + target_user_id: user.id, + details: opts[:revoke_reason], + previous_value: opts[:previous_value] + )) + end + + def log_title_change(user, opts = {}) + raise Discourse::InvalidParameters.new(:user) unless user + UserHistory.create!(params(opts).merge( + action: UserHistory.actions[:change_title], + target_user_id: user.id, + details: opts[:details], + new_value: opts[:new_value], + previous_value: opts[:previous_value] + )) + end + + def log_change_upload_secure_status(opts = {}) + UserHistory.create!(params(opts).merge( + action: UserHistory.actions[:override_upload_secure_status], + details: [ + "upload_id: #{opts[:upload_id]}", + "reason: #{I18n.t("uploads.marked_insecure_from_theme_component_reason")}" + ].join("\n"), + new_value: opts[:new_value] + )) + end + def log_check_email(user, opts = {}) raise Discourse::InvalidParameters.new(:user) unless user UserHistory.create!(params(opts).merge( diff --git a/app/services/themes_install_task.rb b/app/services/themes_install_task.rb index 1a3ac1bbcf..8466e7b8cb 100644 --- a/app/services/themes_install_task.rb +++ b/app/services/themes_install_task.rb @@ -25,7 +25,7 @@ class ThemesInstallTask end end - return log, counts + [log, counts] end attr_reader :url, :options diff --git a/app/services/user_anonymizer.rb b/app/services/user_anonymizer.rb index 99c7dc6dd4..bb5bd3f705 100644 --- a/app/services/user_anonymizer.rb +++ b/app/services/user_anonymizer.rb @@ -64,7 +64,6 @@ class UserAnonymizer @user.single_sign_on_record.try(:destroy) @user.oauth2_user_infos.try(:destroy_all) @user.user_associated_accounts.try(:destroy_all) - @user.instagram_user_info.try(:destroy) @user.user_open_ids.find_each { |x| x.destroy } @user.api_keys.find_each { |x| x.try(:destroy) } @user.user_emails.secondary.destroy_all diff --git a/app/services/user_updater.rb b/app/services/user_updater.rb index c5d539e1db..44c262d8f2 100644 --- a/app/services/user_updater.rb +++ b/app/services/user_updater.rb @@ -40,7 +40,8 @@ class UserUpdater :homepage_id, :hide_profile_and_presence, :text_size, - :title_count_mode + :title_count_mode, + :timezone ] def initialize(actor, user) diff --git a/app/views/users_email/confirm.html.erb b/app/views/users_email/confirm.html.erb deleted file mode 100644 index 0f236a1992..0000000000 --- a/app/views/users_email/confirm.html.erb +++ /dev/null @@ -1,43 +0,0 @@ -<%= t 'change_email.authorizing_old.description' %>
- <% elsif @update_result == :complete %> -+ "><%= t('change_email.please_continue', site_name: SiteSetting.title) %> +
+ <% elsif @error %> ++ <%= t 'change_email.authorizing_new.description' %> +
++ <%= @to_email %> +
+ + <%=form_tag(u_confirm_new_email_path, method: :put) do %> + <%= hidden_field_tag 'token', @token.token %> + + <% if @show_backup_codes %> ++ <%= t 'change_email.authorizing_old.almost_done_description' %> +
+ <% elsif @error %> +
+ <%= t 'change_email.authorizing_old.description' %>
+
+
+ <%= t 'change_email.authorizing_old.old_email', email: @from_email %>
+
+ <%= t 'change_email.authorizing_old.new_email', email: @to_email %>
+