Version bump

This commit is contained in:
Neil Lalonde 2020-01-21 17:08:31 -05:00
commit 686c3f6a2f
657 changed files with 16204 additions and 5689 deletions

View File

@ -1,5 +1,5 @@
name: CI
on:
push:
branches:
@ -7,7 +7,7 @@ on:
pull_request:
branches-ignore:
- 'tests-passed'
jobs:
build:
name: "${{ matrix.target }}-${{ matrix.build_types }}"
@ -31,14 +31,14 @@ jobs:
build_types: [ 'BACKEND', 'FRONTEND', 'LINT' ]
target: [ 'PLUGINS', 'CORE' ]
os: [ ubuntu-latest ]
ruby: [ '2.6.3' ]
ruby: [ '2.6' ]
postgres: [ '10' ]
redis: [ '4.x' ]
services:
postgres:
image: postgres:${{ matrix.postgres }}
ports:
ports:
- 5432:5432
env:
POSTGRES_USER: discourse
@ -88,14 +88,14 @@ jobs:
key: ${{ runner.os }}-gem-${{ hashFiles('**/Gemfile.lock') }}
restore-keys: |
${{ runner.os }}-gem-
- name: Setup gems
run: bundle install --without development --deployment --jobs 4 --retry 3
- name: Get yarn cache directory
id: yarn-cache-dir
run: echo "::set-output name=dir::$(yarn cache dir)"
- name: Yarn cache
uses: actions/cache@v1
id: yarn-cache
@ -113,7 +113,7 @@ jobs:
run: bin/rake plugin:install_all_official
- name: Create database
if: env.BUILD_TYPE != 'LINT'
if: env.BUILD_TYPE != 'LINT'
run: bin/rake db:create && bin/rake db:migrate
- name: Create parallel databases
@ -123,7 +123,7 @@ jobs:
- name: Rubocop
if: env.BUILD_TYPE == 'LINT'
run: bundle exec rubocop .
- name: ESLint
if: env.BUILD_TYPE == 'LINT'
run: yarn eslint app/assets/javascripts test/javascripts && yarn eslint --ext .es6 app/assets/javascripts test/javascripts plugins
@ -133,7 +133,7 @@ jobs:
run: |
yarn prettier -v
yarn prettier --list-different "app/assets/stylesheets/**/*.scss" "app/assets/javascripts/**/*.es6" "test/javascripts/**/*.es6" "plugins/**/*.scss" "plugins/**/*.es6"
- name: Core RSpec
if: env.BUILD_TYPE == 'BACKEND' && env.TARGET == 'CORE'
run: bin/turbo_rspec && bin/rake plugin:spec
@ -146,14 +146,13 @@ jobs:
if: env.BUILD_TYPE == 'FRONTEND' && env.TARGET == 'CORE'
run: bundle exec rake qunit:test['1200000']
timeout-minutes: 30
- name: Wizard QUnit
if: env.BUILD_TYPE == 'FRONTEND' && env.TARGET == 'CORE'
run: bundle exec rake qunit:test['1200000','/wizard/qunit']
timeout-minutes: 30
- name: Plugin QUnit # Tests core plugins in TARGET=CORE, and all plugins in TARGET=PLUGINS
if: env.BUILD_TYPE == 'FRONTEND'
run: bundle exec rake plugin:qunit
timeout-minutes: 30

1
.gitignore vendored
View File

@ -32,6 +32,7 @@ config/discourse.conf
# Ignore the default SQLite database and db dumps
*.sql
*.sql.gz
!/spec/fixtures/**/*.sql
/db/*.sqlite3
/db/structure.sql
/db/schema.rb

View File

@ -1 +1 @@
2.6.1
2.6.5

View File

@ -131,6 +131,12 @@ gem 'mini_racer'
# TODO: determine why highline is being held back and upgrade to latest
gem 'highline', '~> 1.7.0', require: false
# TODO: Upgrading breaks Sidekiq Web
# This is a bit of a hornets nest cause in an ideal world we much prefer
# if Sidekiq reused session and CSRF mitigation with Discourse on the
# _forum_session cookie instead of a rack.session cookie
gem 'rack', '2.0.8'
gem 'rack-protection' # security
gem 'cbor', require: false
gem 'cose', require: false

View File

@ -45,16 +45,16 @@ GEM
rake (>= 10.4, < 14.0)
ast (2.4.0)
aws-eventstream (1.0.3)
aws-partitions (1.256.0)
aws-sdk-core (3.86.0)
aws-partitions (1.266.0)
aws-sdk-core (3.89.1)
aws-eventstream (~> 1.0, >= 1.0.2)
aws-partitions (~> 1, >= 1.239.0)
aws-sigv4 (~> 1.1)
jmespath (~> 1.0)
aws-sdk-kms (1.27.0)
aws-sdk-kms (1.28.0)
aws-sdk-core (~> 3, >= 3.71.0)
aws-sigv4 (~> 1.1)
aws-sdk-s3 (1.59.1)
aws-sdk-s3 (1.60.1)
aws-sdk-core (~> 3, >= 3.83.0)
aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.1)
@ -75,10 +75,10 @@ GEM
bootsnap (1.4.5)
msgpack (~> 1.0)
builder (3.2.4)
bullet (6.0.2)
bullet (6.1.0)
activesupport (>= 3.0.0)
uniform_notifier (~> 1.11)
byebug (11.0.1)
byebug (11.1.0)
cbor (0.5.9.6)
certified (1.0.0)
chunky_png (1.3.11)
@ -86,12 +86,12 @@ GEM
colored2 (3.1.2)
concurrent-ruby (1.1.5)
connection_pool (2.2.2)
cose (0.9.0)
cose (0.10.0)
cbor (~> 0.5.9)
cppjieba_rb (0.3.3)
crack (0.4.3)
safe_yaml (~> 1.0.0)
crass (1.0.5)
crass (1.0.6)
css_parser (1.7.1)
addressable
debug_inspector (0.0.3)
@ -120,7 +120,7 @@ GEM
railties (>= 3.1)
ember-source (2.18.2)
erubi (1.9.0)
excon (0.71.0)
excon (0.71.1)
execjs (2.7.0)
exifr (1.3.6)
fabrication (2.21.0)
@ -133,7 +133,7 @@ GEM
rake-compiler
fast_xs (0.8.0)
fastimage (2.1.7)
ffi (1.11.3)
ffi (1.12.1)
flamegraph (0.9.5)
fspath (3.1.2)
gc_tracer (1.5.1)
@ -146,10 +146,10 @@ GEM
hkdf (0.3.0)
htmlentities (4.3.4)
http_accept_language (2.1.1)
i18n (1.7.0)
i18n (1.8.2)
concurrent-ruby (~> 1.0)
image_size (1.5.0)
in_threads (1.5.3)
in_threads (1.5.4)
jaro_winkler (1.5.4)
jmespath (1.4.0)
jquery-rails (4.3.5)
@ -187,14 +187,14 @@ GEM
method_source (0.9.2)
mini_mime (1.0.2)
mini_portile2 (2.4.0)
mini_racer (0.2.8)
mini_racer (0.2.9)
libv8 (>= 6.9.411)
mini_scheduler (0.12.2)
sidekiq
mini_sql (0.2.2)
mini_sql (0.2.4)
mini_suffix (0.3.0)
ffi (~> 1.9)
minitest (5.13.0)
minitest (5.14.0)
mocha (1.8.0)
metaclass (~> 0.0.1)
mock_redis (0.22.0)
@ -215,7 +215,7 @@ GEM
multi_json (~> 1.3)
multi_xml (~> 0.5)
rack (>= 1.2, < 3)
oj (3.10.0)
oj (3.10.1)
omniauth (1.9.0)
hashie (>= 3.4.6, < 3.7.0)
rack (>= 1.6.2, < 3)
@ -243,7 +243,8 @@ GEM
omniauth-twitter (1.4.0)
omniauth-oauth (~> 1.1)
rack
onebox (1.9.24)
onebox (1.9.25)
addressable (~> 2.7.0)
htmlentities (~> 4.3)
multi_json (~> 1.11)
mustache
@ -256,9 +257,9 @@ GEM
parallel (1.19.1)
parallel_tests (2.30.0)
parallel
parser (2.6.5.0)
parser (2.7.0.2)
ast (~> 2.4.0)
pg (1.1.4)
pg (1.2.2)
progress (3.5.2)
pry (0.12.2)
coderay (~> 1.1.0)
@ -267,7 +268,7 @@ GEM
pry (>= 0.9.10, < 0.13.0)
pry-rails (0.3.9)
pry (>= 0.10.4)
public_suffix (4.0.1)
public_suffix (4.0.3)
puma (4.3.1)
nio4r (~> 2.0)
r2 (0.2.7)
@ -277,7 +278,7 @@ GEM
rack-openid (1.3.1)
rack (>= 1.1.0)
ruby-openid (>= 2.1.8)
rack-protection (2.0.7)
rack-protection (2.0.8.1)
rack
rack-test (1.1.0)
rack (>= 1.0, < 3)
@ -298,10 +299,10 @@ GEM
rainbow (3.0.0)
raindrops (0.19.0)
rake (13.0.1)
rake-compiler (1.0.8)
rake-compiler (1.1.0)
rake
rb-fsevent (0.10.3)
rb-inotify (0.10.0)
rb-inotify (0.10.1)
ffi (~> 1.0)
rbtrace (0.4.11)
ffi (>= 1.0.6)
@ -311,7 +312,7 @@ GEM
redis (4.1.3)
redis-namespace (1.7.0)
redis (>= 3.0.4)
request_store (1.4.1)
request_store (1.5.0)
rack (>= 1.4)
rinku (2.0.6)
rotp (5.1.0)
@ -324,15 +325,15 @@ GEM
rspec-core (~> 3.9.0)
rspec-expectations (~> 3.9.0)
rspec-mocks (~> 3.9.0)
rspec-core (3.9.0)
rspec-support (~> 3.9.0)
rspec-core (3.9.1)
rspec-support (~> 3.9.1)
rspec-expectations (3.9.0)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.9.0)
rspec-html-matchers (0.9.2)
nokogiri (~> 1)
rspec (>= 3.0.0.a, < 4)
rspec-mocks (3.9.0)
rspec-mocks (3.9.1)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.9.0)
rspec-rails (4.0.0.beta2)
@ -343,12 +344,12 @@ GEM
rspec-expectations (~> 3.8)
rspec-mocks (~> 3.8)
rspec-support (~> 3.8)
rspec-support (3.9.0)
rspec-support (3.9.2)
rtlit (0.0.5)
rubocop (0.77.0)
rubocop (0.79.0)
jaro_winkler (~> 1.5.1)
parallel (~> 1.10)
parser (>= 2.6)
parser (>= 2.7.0.1)
rainbow (>= 2.2.2, < 4.0)
ruby-progressbar (~> 1.7)
unicode-display_width (>= 1.4.0, < 1.7)
@ -378,9 +379,9 @@ GEM
seed-fu (2.3.9)
activerecord (>= 3.1)
activesupport (>= 3.1)
shoulda-matchers (4.1.2)
shoulda-matchers (4.2.0)
activesupport (>= 4.2.0)
sidekiq (6.0.3)
sidekiq (6.0.4)
connection_pool (>= 2.2.2)
rack (>= 2.0.0)
rack-protection (>= 2.0.0)
@ -398,20 +399,20 @@ GEM
activesupport (>= 4.0)
sprockets (>= 3.0.0)
sshkey (2.0.0)
stackprof (0.2.14)
test-prof (0.10.1)
stackprof (0.2.15)
test-prof (0.10.2)
thor (1.0.1)
thread_safe (0.3.6)
tilt (2.0.10)
tzinfo (1.2.5)
tzinfo (1.2.6)
thread_safe (~> 0.1)
uglifier (4.2.0)
execjs (>= 0.3.0, < 3)
unf (0.1.4)
unf_ext
unf_ext (0.0.7.6)
unicode-display_width (1.6.0)
unicorn (5.5.1)
unicode-display_width (1.6.1)
unicorn (5.5.2)
kgio (~> 2.6)
raindrops (~> 0.7)
uniform_notifier (1.13.0)
@ -511,6 +512,7 @@ DEPENDENCIES
pry-rails
puma
r2
rack (= 2.0.8)
rack-mini-profiler
rack-protection
rails_multisite

View File

@ -44,14 +44,16 @@ If you're looking for business class hosting, see [discourse.org/buy](https://ww
## Requirements
Discourse is built for the *next* 10 years of the Internet, so our requirements are high:
Discourse is built for the *next* 10 years of the Internet, so our requirements are high.
Discourse supports the **latest, stable releases** of all major browsers and platforms:
| Browsers | Tablets | Phones |
| --------------------- | ------------ | ------------ |
| Safari 10+ | iPad 4+ | iOS 10+ |
| Google Chrome 57+ | Android 4.4+ | Android 4.4+ |
| Internet Explorer 11+ | | |
| Firefox 52+ | | |
| Apple Safari | iPadOS | iOS |
| Google Chrome | Android | Android |
| Microsoft Edge | | |
| Mozilla Firefox | | |
## Built With

View File

@ -1,46 +0,0 @@
import { getWebauthnCredential } from "discourse/lib/webauthn";
export default function() {
document.getElementById(
"activate-security-key-alternative"
).onclick = function() {
document.getElementById("second-factor-forms").style.display = "block";
document.getElementById("primary-security-key-form").style.display = "none";
};
document.getElementById("submit-security-key").onclick = function(e) {
e.preventDefault();
getWebauthnCredential(
document.getElementById("security-key-challenge").value,
document
.getElementById("security-key-allowed-credential-ids")
.value.split(","),
credentialData => {
document.getElementById(
"security-key-credential"
).value = JSON.stringify(credentialData);
e.target.parentElement.submit();
},
errorMessage => {
document.getElementById("security-key-error").innerText = errorMessage;
}
);
};
const useTotp = I18n.t("login.second_factor_toggle.totp");
const useBackup = I18n.t("login.second_factor_toggle.backup_code");
const backupForm = document.getElementById("backup-second-factor-form");
const primaryForm = document.getElementById("primary-second-factor-form");
document.getElementById("toggle-form").onclick = function(event) {
event.preventDefault();
if (backupForm.style.display === "none") {
backupForm.style.display = "block";
primaryForm.style.display = "none";
document.getElementById("toggle-form").innerHTML = useTotp;
} else {
backupForm.style.display = "none";
primaryForm.style.display = "block";
document.getElementById("toggle-form").innerHTML = useBackup;
}
};
}

View File

@ -1 +0,0 @@
require("admin-login/admin-login").default();

View File

@ -8,7 +8,7 @@ import ReportLoader from "discourse/lib/reports-loader";
import { exportEntity } from "discourse/lib/export-csv";
import { outputExportResult } from "discourse/lib/export-result";
import { isNumeric } from "discourse/lib/utilities";
import { SCHEMA_VERSION, default as Report } from "admin/models/report";
import Report, { SCHEMA_VERSION } from "admin/models/report";
import ENV from "discourse-common/config/environment";
const TABLE_OPTIONS = {

View File

@ -1,6 +1,6 @@
import { next } from "@ember/runloop";
import Component from "@ember/component";
import { default as discourseComputed } from "discourse-common/utils/decorators";
import discourseComputed from "discourse-common/utils/decorators";
import { fmt } from "discourse/lib/computed";
export default Component.extend({

View File

@ -7,8 +7,7 @@ import { bufferedProperty } from "discourse/mixins/buffered-content";
import { popupAjaxError } from "discourse/lib/ajax-error";
import { propertyEqual } from "discourse/lib/computed";
import { i18n } from "discourse/lib/computed";
import {
default as discourseComputed,
import discourseComputed, {
observes,
on
} from "discourse-common/utils/decorators";

View File

@ -1,6 +1,7 @@
import { schedule } from "@ember/runloop";
import Component from "@ember/component";
import { default as loadScript, loadCSS } from "discourse/lib/load-script";
import loadScript, { loadCSS } from "discourse/lib/load-script";
import { observes } from "discourse-common/utils/decorators";
/**
An input field for a color.
@ -11,6 +12,8 @@ import { default as loadScript, loadCSS } from "discourse/lib/load-script";
**/
export default Component.extend({
classNames: ["color-picker"],
@observes("hexValue", "brightnessValue", "valid")
hexValueChanged: function() {
var hex = this.hexValue;
let text = this.element.querySelector("input.hex-input");
@ -33,7 +36,7 @@ export default Component.extend({
} else {
text.setAttribute("style", "");
}
}.observes("hexValue", "brightnessValue", "valid"),
},
didInsertElement() {
loadScript("/javascripts/spectrum.js").then(() => {

View File

@ -1,8 +1,5 @@
import Component from "@ember/component";
import {
default as discourseComputed,
observes
} from "discourse-common/utils/decorators";
import discourseComputed, { observes } from "discourse-common/utils/decorators";
export default Component.extend({
classNames: ["inline-edit"],

View File

@ -1,7 +1,7 @@
import EmberObject from "@ember/object";
import { later } from "@ember/runloop";
import Component from "@ember/component";
import { default as discourseComputed } from "discourse-common/utils/decorators";
import discourseComputed from "discourse-common/utils/decorators";
import { ajax } from "discourse/lib/ajax";
import AdminUser from "admin/models/admin-user";
import copyText from "discourse/lib/copy-text";

View File

@ -1,6 +1,6 @@
import { schedule } from "@ember/runloop";
import Component from "@ember/component";
import { default as discourseComputed } from "discourse-common/utils/decorators";
import discourseComputed from "discourse-common/utils/decorators";
import { fmt } from "discourse/lib/computed";
import Permalink from "admin/models/permalink";

View File

@ -1,5 +1,5 @@
import Category from "discourse/models/category";
import { default as discourseComputed } from "discourse-common/utils/decorators";
import discourseComputed from "discourse-common/utils/decorators";
import FilterComponent from "admin/components/report-filters/filter";
export default FilterComponent.extend({

View File

@ -1,5 +1,5 @@
import FilterComponent from "admin/components/report-filters/filter";
import { default as discourseComputed } from "discourse-common/utils/decorators";
import discourseComputed from "discourse-common/utils/decorators";
export default FilterComponent.extend({
classNames: ["group-filter"],

View File

@ -2,10 +2,7 @@ import { schedule } from "@ember/runloop";
import { later } from "@ember/runloop";
import Component from "@ember/component";
import { iconHTML } from "discourse-common/lib/icon-library";
import {
default as discourseComputed,
on
} from "discourse-common/utils/decorators";
import discourseComputed, { on } from "discourse-common/utils/decorators";
/*global Resumable:true */

View File

@ -1,10 +1,7 @@
import { gt, and } from "@ember/object/computed";
import { schedule } from "@ember/runloop";
import Component from "@ember/component";
import {
default as discourseComputed,
observes
} from "discourse-common/utils/decorators";
import discourseComputed, { observes } from "discourse-common/utils/decorators";
import { iconHTML } from "discourse-common/lib/icon-library";
import { escape } from "pretty-text/sanitizer";
import ENV from "discourse-common/config/environment";

View File

@ -2,8 +2,7 @@ import { isEmpty } from "@ember/utils";
import { schedule } from "@ember/runloop";
import Component from "@ember/component";
import WatchedWord from "admin/models/watched-word";
import {
default as discourseComputed,
import discourseComputed, {
on,
observes
} from "discourse-common/utils/decorators";

View File

@ -1,4 +1,4 @@
import { default as discourseComputed } from "discourse-common/utils/decorators";
import discourseComputed from "discourse-common/utils/decorators";
import { popupAjaxError } from "discourse/lib/ajax-error";
export default Ember.Controller.extend({

View File

@ -2,7 +2,7 @@ import { alias, equal } from "@ember/object/computed";
import { inject } from "@ember/controller";
import Controller from "@ember/controller";
import { ajax } from "discourse/lib/ajax";
import { default as discourseComputed } from "discourse-common/utils/decorators";
import discourseComputed from "discourse-common/utils/decorators";
import { setting, i18n } from "discourse/lib/computed";
export default Controller.extend({

View File

@ -0,0 +1,35 @@
import Controller from "@ember/controller";
import { ajax } from "discourse/lib/ajax";
import { popupAjaxError } from "discourse/lib/ajax-error";
export default Controller.extend({
saving: false,
actions: {
massAward() {
const file = document.querySelector("#massAwardCSVUpload").files[0];
if (this.model && file) {
const options = {
type: "POST",
processData: false,
contentType: false,
data: new FormData()
};
options.data.append("file", file);
this.set("saving", true);
ajax(`/admin/badges/award/${this.model.id}`, options)
.then(() => {
bootbox.alert(I18n.t("admin.badges.mass_award.success"));
})
.catch(popupAjaxError)
.finally(() => this.set("saving", false));
} else {
bootbox.alert(I18n.t("admin.badges.mass_award.aborted"));
}
}
}
});

View File

@ -1,4 +1,4 @@
import discourseComputed from "discourse-common/utils/decorators";
import discourseComputed, { observes } from "discourse-common/utils/decorators";
import { alias } from "@ember/object/computed";
import { inject } from "@ember/controller";
import Controller from "@ember/controller";
@ -27,10 +27,11 @@ export default Controller.extend(bufferedProperty("model"), {
return modelQuery && modelQuery.trim().length > 0;
},
@observes("model.id")
_resetSaving: function() {
this.set("saving", false);
this.set("savingStatus", "");
}.observes("model.id"),
},
actions: {
save() {

View File

@ -1,2 +1,18 @@
import Controller from "@ember/controller";
export default Controller.extend();
import { inject as service } from "@ember/service";
import discourseComputed from "discourse-common/utils/decorators";
export default Controller.extend({
routing: service("-routing"),
@discourseComputed("routing.currentRouteName")
selectedRoute() {
const currentRoute = this.routing.currentRouteName;
const indexRoute = "adminBadges.index";
if (currentRoute === indexRoute) {
return "adminBadges.show";
} else {
return this.routing.currentRouteName;
}
}
});

View File

@ -1,7 +1,7 @@
import EmberObject from "@ember/object";
import Controller from "@ember/controller";
import showModal from "discourse/lib/show-modal";
import { default as discourseComputed } from "discourse-common/utils/decorators";
import discourseComputed from "discourse-common/utils/decorators";
export default Controller.extend({
@discourseComputed("model.@each.id")

View File

@ -1,6 +1,6 @@
import Controller from "@ember/controller";
import { url } from "discourse/lib/computed";
import { default as discourseComputed } from "discourse-common/utils/decorators";
import discourseComputed from "discourse-common/utils/decorators";
export default Controller.extend({
section: null,

View File

@ -7,7 +7,7 @@ import {
notEmpty
} from "@ember/object/computed";
import Controller from "@ember/controller";
import { default as discourseComputed } from "discourse-common/utils/decorators";
import discourseComputed from "discourse-common/utils/decorators";
import { url } from "discourse/lib/computed";
import { popupAjaxError } from "discourse/lib/ajax-error";
import showModal from "discourse/lib/show-modal";

View File

@ -1,5 +1,5 @@
import Controller from "@ember/controller";
import { default as discourseComputed } from "discourse-common/utils/decorators";
import discourseComputed from "discourse-common/utils/decorators";
import { THEMES } from "admin/models/theme";
export default Controller.extend({

View File

@ -1,8 +1,10 @@
import AdminEmailLogsController from "admin/controllers/admin-email-logs";
import discourseDebounce from "discourse/lib/debounce";
import { observes } from "discourse-common/utils/decorators";
export default AdminEmailLogsController.extend({
@observes("filter.{status,user,address,type}")
filterEmailLogs: discourseDebounce(function() {
this.loadLogs();
}, 250).observes("filter.{status,user,address,type}")
}, 250)
});

View File

@ -1,6 +1,8 @@
import { empty } from "@ember/object/computed";
import Controller from "@ember/controller";
import { ajax } from "discourse/lib/ajax";
import { observes } from "discourse-common/utils/decorators";
export default Controller.extend({
/**
Is the "send test email" button disabled?
@ -14,9 +16,10 @@ export default Controller.extend({
@method testEmailAddressChanged
**/
@observes("testEmailAddress")
testEmailAddressChanged: function() {
this.set("sentTestEmail", false);
}.observes("testEmailAddress"),
},
actions: {
/**

View File

@ -1,11 +1,13 @@
import AdminEmailLogsController from "admin/controllers/admin-email-logs";
import discourseDebounce from "discourse/lib/debounce";
import IncomingEmail from "admin/models/incoming-email";
import { observes } from "discourse-common/utils/decorators";
export default AdminEmailLogsController.extend({
@observes("filter.{status,from,to,subject}")
filterIncomingEmails: discourseDebounce(function() {
this.loadLogs(IncomingEmail);
}, 250).observes("filter.{status,from,to,subject}"),
}, 250),
actions: {
loadMore() {

View File

@ -1,11 +1,13 @@
import AdminEmailLogsController from "admin/controllers/admin-email-logs";
import discourseDebounce from "discourse/lib/debounce";
import IncomingEmail from "admin/models/incoming-email";
import { observes } from "discourse-common/utils/decorators";
export default AdminEmailLogsController.extend({
@observes("filter.{status,from,to,subject,error}")
filterIncomingEmails: discourseDebounce(function() {
this.loadLogs(IncomingEmail);
}, 250).observes("filter.{status,from,to,subject,error}"),
}, 250),
actions: {
loadMore() {

View File

@ -1,8 +1,10 @@
import AdminEmailLogsController from "admin/controllers/admin-email-logs";
import discourseDebounce from "discourse/lib/debounce";
import { observes } from "discourse-common/utils/decorators";
export default AdminEmailLogsController.extend({
@observes("filter.{status,user,address,type,reply_key}")
filterEmailLogs: discourseDebounce(function() {
this.loadLogs();
}, 250).observes("filter.{status,user,address,type,reply_key}")
}, 250)
});

View File

@ -1,8 +1,10 @@
import AdminEmailLogsController from "admin/controllers/admin-email-logs";
import discourseDebounce from "discourse/lib/debounce";
import { observes } from "discourse-common/utils/decorators";
export default AdminEmailLogsController.extend({
@observes("filter.{status,user,address,type}")
filterEmailLogs: discourseDebounce(function() {
this.loadLogs();
}, 250).observes("filter.{status,user,address,type}")
}, 250)
});

View File

@ -3,18 +3,20 @@ import discourseDebounce from "discourse/lib/debounce";
import { outputExportResult } from "discourse/lib/export-result";
import { exportEntity } from "discourse/lib/export-csv";
import ScreenedIpAddress from "admin/models/screened-ip-address";
import { observes } from "discourse-common/utils/decorators";
export default Controller.extend({
loading: false,
filter: null,
savedIpAddress: null,
@observes("filter")
show: discourseDebounce(function() {
this.set("loading", true);
ScreenedIpAddress.findAll(this.filter).then(result => {
this.setProperties({ model: result, loading: false });
});
}, 250).observes("filter"),
}, 250),
actions: {
allow(record) {

View File

@ -4,10 +4,7 @@ import { scheduleOnce } from "@ember/runloop";
import Controller from "@ember/controller";
import { exportEntity } from "discourse/lib/export-csv";
import { outputExportResult } from "discourse/lib/export-result";
import {
default as discourseComputed,
on
} from "discourse-common/utils/decorators";
import discourseComputed, { on } from "discourse-common/utils/decorators";
export default Controller.extend({
model: null,

View File

@ -1,17 +1,19 @@
import Controller from "@ember/controller";
import discourseDebounce from "discourse/lib/debounce";
import Permalink from "admin/models/permalink";
import { observes } from "discourse-common/utils/decorators";
export default Controller.extend({
loading: false,
filter: null,
@observes("filter")
show: discourseDebounce(function() {
Permalink.findAll(this.filter).then(result => {
this.set("model", result);
this.set("loading", false);
});
}, 250).observes("filter"),
}, 250),
actions: {
recordAdded(arg) {

View File

@ -2,6 +2,7 @@ import { isEmpty } from "@ember/utils";
import { alias } from "@ember/object/computed";
import Controller from "@ember/controller";
import discourseDebounce from "discourse/lib/debounce";
import { observes } from "discourse-common/utils/decorators";
export default Controller.extend({
filter: null,
@ -76,13 +77,14 @@ export default Controller.extend({
);
},
@observes("filter", "onlyOverridden", "model")
filterContent: discourseDebounce(function() {
if (this._skipBounce) {
this.set("_skipBounce", false);
} else {
this.filterContentNow();
}
}, 250).observes("filter", "onlyOverridden", "model"),
}, 250),
actions: {
clearFilter() {

View File

@ -6,7 +6,7 @@ import CanCheckEmails from "discourse/mixins/can-check-emails";
import { propertyNotEqual, setting } from "discourse/lib/computed";
import { userPath } from "discourse/lib/url";
import { popupAjaxError } from "discourse/lib/ajax-error";
import { default as discourseComputed } from "discourse-common/utils/decorators";
import discourseComputed from "discourse-common/utils/decorators";
import { fmt } from "discourse/lib/computed";
import { htmlSafe } from "@ember/template";

View File

@ -1,4 +1,4 @@
import discourseComputed from "discourse-common/utils/decorators";
import discourseComputed, { observes } from "discourse-common/utils/decorators";
import Controller from "@ember/controller";
import discourseDebounce from "discourse/lib/debounce";
import { i18n } from "discourse/lib/computed";
@ -29,9 +29,10 @@ export default Controller.extend(CanCheckEmails, {
return I18n.t("admin.users.titles." + query);
},
@observes("listFilter")
_filterUsers: discourseDebounce(function() {
this.resetFilters();
}, 250).observes("listFilter"),
}, 250),
resetFilters() {
this._page = 1;

View File

@ -3,6 +3,7 @@ import { alias } from "@ember/object/computed";
import EmberObject from "@ember/object";
import Controller from "@ember/controller";
import discourseDebounce from "discourse/lib/debounce";
import { observes } from "discourse-common/utils/decorators";
export default Controller.extend({
filter: null,
@ -43,10 +44,11 @@ export default Controller.extend({
this.set("model", matchesByAction);
},
@observes("filter")
filterContent: discourseDebounce(function() {
this.filterContentNow();
this.set("filtered", !isEmpty(this.filter));
}, 250).observes("filter"),
}, 250),
actions: {
clearFilter() {

View File

@ -4,10 +4,7 @@ import { inject } from "@ember/controller";
import Controller from "@ember/controller";
import ModalFunctionality from "discourse/mixins/modal-functionality";
import { ajax } from "discourse/lib/ajax";
import {
default as discourseComputed,
observes
} from "discourse-common/utils/decorators";
import discourseComputed, { observes } from "discourse-common/utils/decorators";
import { popupAjaxError } from "discourse/lib/ajax-error";
const THEME_FIELD_VARIABLE_TYPE_IDS = [2, 3, 4];

View File

@ -1,6 +1,6 @@
import { alias, map } from "@ember/object/computed";
import Controller from "@ember/controller";
import { default as discourseComputed } from "discourse-common/utils/decorators";
import discourseComputed from "discourse-common/utils/decorators";
import { escapeExpression } from "discourse/lib/utilities";
export default Controller.extend({

View File

@ -4,10 +4,7 @@ import Controller from "@ember/controller";
import ModalFunctionality from "discourse/mixins/modal-functionality";
import { ajax } from "discourse/lib/ajax";
import { popupAjaxError } from "discourse/lib/ajax-error";
import {
default as discourseComputed,
observes
} from "discourse-common/utils/decorators";
import discourseComputed, { observes } from "discourse-common/utils/decorators";
import { THEMES, COMPONENTS } from "admin/models/theme";
import { POPULAR_THEMES } from "discourse-common/helpers/popular-themes";
import { set } from "@ember/object";

View File

@ -1,5 +1,5 @@
import Controller from "@ember/controller";
import { default as discourseComputed } from "discourse-common/utils/decorators";
import discourseComputed from "discourse-common/utils/decorators";
import ModalFunctionality from "discourse/mixins/modal-functionality";
export default Controller.extend(ModalFunctionality, {

View File

@ -1,5 +1,4 @@
import {
default as discourseComputed,
import discourseComputed, {
observes,
on
} from "discourse-common/utils/decorators";

View File

@ -22,11 +22,6 @@ const Report = EmberObject.extend({
percent: false,
higher_is_better: true,
@discourseComputed("modes")
isTable(modes) {
return modes.some(mode => mode === "table");
},
@discourseComputed("type", "start_date", "end_date")
reportUrl(type, start_date, end_date) {
start_date = moment

View File

@ -2,7 +2,7 @@ import { get } from "@ember/object";
import { isEmpty } from "@ember/utils";
import { or, gt } from "@ember/object/computed";
import RestModel from "discourse/models/rest";
import { default as discourseComputed } from "discourse-common/utils/decorators";
import discourseComputed from "discourse-common/utils/decorators";
import { popupAjaxError } from "discourse/lib/ajax-error";
import { ajax } from "discourse/lib/ajax";
import { escapeExpression } from "discourse/lib/utilities";

View File

@ -2,10 +2,7 @@ import { isEmpty } from "@ember/utils";
import RestModel from "discourse/models/rest";
import Category from "discourse/models/category";
import Group from "discourse/models/group";
import {
default as discourseComputed,
observes
} from "discourse-common/utils/decorators";
import discourseComputed, { observes } from "discourse-common/utils/decorators";
import Site from "discourse/models/site";
export default RestModel.extend({

View File

@ -0,0 +1,12 @@
import Route from "discourse/routes/discourse";
export default Route.extend({
model(params) {
if (params.badge_id !== "new") {
return this.modelFor("adminBadges").findBy(
"id",
parseInt(params.badge_id, 10)
);
}
}
});

View File

@ -1,8 +1,5 @@
import DiscourseRoute from "discourse/routes/discourse";
import {
default as EmailPreview,
oneWeekAgo
} from "admin/models/email-preview";
import EmailPreview, { oneWeekAgo } from "admin/models/email-preview";
export default DiscourseRoute.extend({
model() {

View File

@ -190,6 +190,7 @@ export default function() {
"adminBadges",
{ path: "/badges", resetNamespace: true },
function() {
this.route("award", { path: "/award/:badge_id" });
this.route("show", { path: "/:badge_id" });
}
);

View File

@ -64,7 +64,7 @@
label="admin.api.undo_revoke"}}
{{d-button
action=(action "deleteKey")
actionParam=model icon="trash"
actionParam=model icon="trash-alt"
label="admin.api.delete"
class="btn-danger"}}
{{else}}
@ -77,4 +77,4 @@
{{/if}}
</div>
{{/admin-form-row}}
</div>
</div>

View File

@ -0,0 +1,32 @@
{{#d-section class="award-badge"}}
<h1>{{i18n 'admin.badges.mass_award.title'}}</h1>
<p>{{i18n 'admin.badges.mass_award.description'}}</p>
{{#if model}}
<form class="form-horizontal">
<div class='badge-preview'>
{{#if model}}
{{icon-or-image model}}
<span class="badge-display-name">{{model.name}}</span>
{{else}}
<span class='badge-placeholder'>{{I18n 'admin.badges.mass_award.no_badge_selected'}}</span>
{{/if}}
</div>
<div>
<h4>{{I18n 'admin.badges.mass_award.upload_csv'}}</h4>
<input type='file' id='massAwardCSVUpload' accept='.csv' />
</div>
{{d-button
class="btn-primary"
action=(action 'massAward')
disabled=saving
label="admin.badges.mass_award.perform"}}
{{#link-to 'adminBadges.index' class="btn btn-danger"}}
{{d-icon "times"}}
<span>{{i18n 'cancel'}}</span>
{{/link-to}}
</form>
{{else}}
<span class='badge-required'>{{I18n 'admin.badges.mass_award.no_badge_selected'}}</span>
{{/if}}
{{/d-section}}

View File

@ -6,13 +6,18 @@
{{d-icon "plus"}}
<span>{{i18n 'admin.badges.new'}}</span>
{{/link-to}}
{{#link-to 'adminBadges.award' 'new' class="btn"}}
{{d-icon "upload"}}
<span>{{i18n 'admin.badges.mass_award.title'}}</span>
{{/link-to}}
</div>
</div>
<div class='content-list'>
<ul class="admin-badge-list">
{{#each model as |badge|}}
<li class="admin-badge-list-item">
{{#link-to 'adminBadges.show' badge.id}}
{{#link-to selectedRoute badge.id}}
{{badge-button badge=badge}}
{{#if badge.newBadge}}
<span class="list-badge">{{i18n 'filters.new.lower_title'}}</span>
@ -23,4 +28,4 @@
</ul>
</div>
{{outlet}}
</div>
</div>

View File

@ -160,17 +160,15 @@
</div>
{{/each}}
{{#if model.isTable}}
<div class="control">
<div class="input">
{{d-button
class="btn-default export-csv-btn"
action=(action "exportCsv")
label="admin.export_csv.button_text"
icon="download"}}
</div>
<div class="control">
<div class="input">
{{d-button
class="btn-default export-csv-btn"
action=(action "exportCsv")
label="admin.export_csv.button_text"
icon="download"}}
</div>
{{/if}}
</div>
{{#if showRefresh}}
<div class="control">

View File

@ -26,7 +26,7 @@
{{#if countWarning}}
<div class="count-warning">
<p class="heading">
{{d-icon "warning"}}
{{d-icon "exclamation-triangle"}}
{{i18n "admin.badges.preview.bad_count_warning.header"}}
</p>
<p class="body">

View File

@ -0,0 +1,23 @@
import { getWebauthnCredential } from "discourse/lib/webauthn";
document.getElementById("submit-security-key").onclick = function(e) {
e.preventDefault();
getWebauthnCredential(
document.getElementById("security-key-challenge").value,
document
.getElementById("security-key-allowed-credential-ids")
.value.split(","),
credentialData => {
document.getElementById("security-key-credential").value = JSON.stringify(
credentialData
);
$(e.target)
.parents("form")
.submit();
},
errorMessage => {
document.getElementById("security-key-error").innerText = errorMessage;
}
);
};

View File

@ -0,0 +1 @@
require("confirm-new-email/confirm-new-email").default();

View File

@ -1,46 +0,0 @@
import { scheduleOnce } from "@ember/runloop";
// Ember 2.0 removes buffered rendering, but we can still implement it ourselves.
// In the long term we'll want to remove this.
const Mixin = {
_customRender() {
if (!this.element || this.isDestroying || this.isDestroyed) {
return;
}
const buffer = [];
this.buildBuffer(buffer);
this.element.innerHTML = buffer.join("");
},
rerenderBuffer() {
scheduleOnce("render", this, this._customRender);
}
};
export function bufferedRender(obj) {
if (!obj.buildBuffer) {
Ember.warn("Missing `buildBuffer` method", {
id: "discourse.buffered-render.missing-build-buffer"
});
return obj;
}
const caller = {};
caller.didRender = function() {
this._super(...arguments);
this._customRender();
};
const triggers = obj.rerenderTriggers;
if (triggers) {
caller.init = function() {
this._super(...arguments);
triggers.forEach(k => this.addObserver(k, this.rerenderBuffer));
};
}
delete obj.rerenderTriggers;
return Ember.Mixin.create(Mixin, caller, obj);
}

View File

@ -1,5 +1,6 @@
import { get } from "@ember/object";
import Helper from "@ember/component/helper";
import RawHandlebars from "discourse-common/lib/raw-handlebars";
export function makeArray(obj) {
if (obj === null || obj === undefined) {
@ -88,5 +89,5 @@ export function registerUnbound(name, fn) {
_helpers[name] = Helper.extend({
compute: (params, args) => fn(...params, args)
});
Handlebars.registerHelper(name, func);
RawHandlebars.registerHelper(name, func);
}

View File

@ -6,11 +6,11 @@ const SVG_NAMESPACE = "http://www.w3.org/2000/svg";
let _renderers = [];
const REPLACEMENTS = {
"d-tracking": "circle",
"d-muted": "times-circle",
"d-regular": "far-circle",
"d-watching": "exclamation-circle",
"d-watching-first": "far-dot-circle",
"d-tracking": "bell",
"d-muted": "discourse-bell-slash",
"d-regular": "far-bell",
"d-watching": "discourse-bell-exclamation",
"d-watching-first": "discourse-bell-one",
"d-drop-expanded": "caret-down",
"d-drop-collapsed": "caret-right",
"d-unliked": "far-heart",
@ -29,11 +29,11 @@ const REPLACEMENTS = {
"notification.invited_to_private_message": "far-envelope",
"notification.invited_to_topic": "hand-point-right",
"notification.invitee_accepted": "user",
"notification.moved_post": "sign-out",
"notification.moved_post": "sign-out-alt",
"notification.linked": "link",
"notification.granted_badge": "certificate",
"notification.topic_reminder": "far-clock",
"notification.watching_first_post": "far-dot-circle",
"notification.watching_first_post": "discourse-bell-one",
"notification.group_message_summary": "users",
"notification.post_approved": "check",
"notification.membership_request_accepted": "user-plus",
@ -580,21 +580,25 @@ function warnIfMissing(id) {
}
}
const reportedIcons = [];
function warnIfDeprecated(oldId, newId) {
deprecated(
`Please replace all occurrences of "${oldId}"" with "${newId}". FontAwesome 4.7 icon names are now deprecated and will be removed in the next release.`
);
if (!Discourse.testing) {
if (!Discourse.testing && !reportedIcons.includes(oldId)) {
const errorData = {
message: `FA icon deprecation: replace "${oldId}"" with "${newId}".`,
stacktrace: Error().stack
};
Ember.$.ajax(`${Discourse.BaseUri}/logs/report_js_error`, {
errorData,
data: errorData,
type: "POST",
cache: false
});
reportedIcons.push(oldId);
}
}

View File

@ -1,14 +1,23 @@
import { get } from "@ember/object";
export function registerRawHelpers(hbs, handlebarsClass) {
hbs.helper = function() {};
hbs.helpers = Object.create(handlebarsClass.helpers);
if (!hbs.helpers) {
hbs.helpers = Object.create(handlebarsClass.helpers);
}
hbs.helpers["get"] = function(context, options) {
var firstContext = options.contexts[0];
var val = firstContext[context];
if (!context || !options.contexts) {
return;
}
if (context.indexOf("controller.") === 0) {
if (typeof context !== "string") {
return context;
}
let firstContext = options.contexts[0];
let val = firstContext[context];
if (context.toString().indexOf("controller.") === 0) {
context = context.slice(context.indexOf(".") + 1);
}

View File

@ -1,9 +1,6 @@
/*global Mousetrap:true*/
import { buildResolver } from "discourse-common/resolver";
import {
default as discourseComputed,
observes
} from "discourse-common/utils/decorators";
import discourseComputed, { observes } from "discourse-common/utils/decorators";
import { computed } from "@ember/object";
import FocusEvent from "discourse-common/mixins/focus-event";
import EmberObject from "@ember/object";

View File

@ -2,6 +2,6 @@ import RESTAdapter from "discourse/adapters/rest";
export default RESTAdapter.extend({
pathFor(store, type, id) {
return "/tags/" + id + "/info";
return "/tag/" + id + "/info";
}
});

View File

@ -2,6 +2,6 @@ import RESTAdapter from "discourse/adapters/rest";
export default RESTAdapter.extend({
pathFor(store, type, id) {
return "/tags/" + id + "/notifications";
return "/tag/" + id + "/notifications";
}
});

View File

@ -1,8 +1,7 @@
import Component from "@ember/component";
import {
import discourseComputed, {
on,
observes,
default as discourseComputed
observes
} from "discourse-common/utils/decorators";
import { findRawTemplate } from "discourse/lib/raw-templates";
const { makeArray } = Ember;

View File

@ -1,4 +1,4 @@
import discourseComputed from "discourse-common/utils/decorators";
import discourseComputed, { observes } from "discourse-common/utils/decorators";
import { alias, not } from "@ember/object/computed";
import Component from "@ember/component";
@ -16,9 +16,10 @@ export default Component.extend({
}
},
@observes("topicList.[]")
_topicListChanged: function() {
this._initFromTopicList(this.topicList);
}.observes("topicList.[]"),
},
_initFromTopicList(topicList) {
if (topicList !== null) {

View File

@ -1,6 +1,6 @@
import { filter } from "@ember/object/computed";
import Component from "@ember/component";
import { default as discourseComputed } from "discourse-common/utils/decorators";
import discourseComputed from "discourse-common/utils/decorators";
import deprecated from "discourse-common/lib/deprecated";
// A breadcrumb including category drop downs

View File

@ -1,7 +1,6 @@
import discourseComputed from "discourse-common/utils/decorators";
import { isEmpty } from "@ember/utils";
import Component from "@ember/component";
import DiscourseURL from "discourse/lib/url";
export default Component.extend({
tagName: "section",
@ -19,16 +18,5 @@ export default Component.extend({
@discourseComputed("categories.[].subcategories")
hasSubcategories() {
return this.categories.any(c => !isEmpty(c.get("subcategories")));
},
click(e) {
if (!$(e.target).is("a")) {
const url = $(e.target)
.closest(".category-box")
.data("url");
if (url) {
DiscourseURL.routeTo(url);
}
}
}
});

View File

@ -1,6 +1,6 @@
import { alias, equal } from "@ember/object/computed";
import Component from "@ember/component";
import { default as discourseComputed } from "discourse-common/utils/decorators";
import discourseComputed from "discourse-common/utils/decorators";
import {
PRIVATE_MESSAGE,
CREATE_TOPIC,

View File

@ -4,10 +4,7 @@ import { cancel } from "@ember/runloop";
import { scheduleOnce } from "@ember/runloop";
import { later } from "@ember/runloop";
import Component from "@ember/component";
import {
default as discourseComputed,
observes
} from "discourse-common/utils/decorators";
import discourseComputed, { observes } from "discourse-common/utils/decorators";
import Composer from "discourse/models/composer";
import afterTransition from "discourse/lib/after-transition";
import positioningWorkaround from "discourse/lib/safari-hacks";
@ -70,7 +67,7 @@ export default Component.extend(KeyEnterEscape, {
return;
}
const h = $("#reply-control").height() || 0;
const h = $("#reply-control:not(.saving)").height() || 0;
this.movePanels(h);
});
},

View File

@ -5,8 +5,7 @@ import { scheduleOnce } from "@ember/runloop";
import { later } from "@ember/runloop";
import Component from "@ember/component";
import userSearch from "discourse/lib/user-search";
import {
default as discourseComputed,
import discourseComputed, {
observes,
on
} from "discourse-common/utils/decorators";

View File

@ -97,16 +97,11 @@ export default Component.extend({
const composer = this.composer;
if (composer.get("privateMessage")) {
let usernames = composer.get("targetUsernames");
if (usernames) {
usernames = usernames.split(",");
}
const recipients = composer.targetRecipientsArray;
if (
usernames &&
usernames.length === 1 &&
usernames[0] === this.currentUser.get("username")
recipients.length > 0 &&
recipients.every(r => r.name === this.currentUser.get("username"))
) {
const message =
this._yourselfConfirm ||

View File

@ -3,10 +3,7 @@ import { next } from "@ember/runloop";
import { debounce } from "@ember/runloop";
import { schedule } from "@ember/runloop";
import Component from "@ember/component";
import {
default as discourseComputed,
observes
} from "discourse-common/utils/decorators";
import discourseComputed, { observes } from "discourse-common/utils/decorators";
import { load } from "pretty-text/oneboxer";
import { lookupCache } from "pretty-text/oneboxer-cache";
import { ajax } from "discourse/lib/ajax";

View File

@ -1,9 +1,6 @@
import { schedule } from "@ember/runloop";
import Component from "@ember/component";
import {
default as discourseComputed,
observes
} from "discourse-common/utils/decorators";
import discourseComputed, { observes } from "discourse-common/utils/decorators";
export default Component.extend({
showSelector: true,

View File

@ -1,6 +1,6 @@
import { notEmpty, empty, equal } from "@ember/object/computed";
import Component from "@ember/component";
import { default as discourseComputed } from "discourse-common/utils/decorators";
import discourseComputed from "discourse-common/utils/decorators";
import DiscourseURL from "discourse/lib/url";
export default Component.extend({

View File

@ -6,8 +6,7 @@ import { later } from "@ember/runloop";
import { inject as service } from "@ember/service";
import Component from "@ember/component";
/*global Mousetrap:true */
import {
default as discourseComputed,
import discourseComputed, {
on,
observes
} from "discourse-common/utils/decorators";
@ -842,7 +841,7 @@ export default Component.extend({
}
const isComposer = $("#reply-control .d-editor-input").is(":focus");
let { clipboard, canPasteHtml } = clipboardData(e, isComposer);
let { clipboard, canPasteHtml, canUpload } = clipboardData(e, isComposer);
let plainText = clipboard.getData("text/plain");
let html = clipboard.getData("text/html");
@ -892,7 +891,7 @@ export default Component.extend({
}
}
if (handled) {
if (handled || canUpload) {
e.preventDefault();
}
},

View File

@ -2,10 +2,7 @@ import { next } from "@ember/runloop";
import Component from "@ember/component";
/* global Pikaday:true */
import loadScript from "discourse/lib/load-script";
import {
default as discourseComputed,
on
} from "discourse-common/utils/decorators";
import discourseComputed, { on } from "discourse-common/utils/decorators";
export default Component.extend({
classNames: ["d-date-input"],

View File

@ -2,10 +2,7 @@ import { next } from "@ember/runloop";
import Component from "@ember/component";
/* global Pikaday:true */
import loadScript from "discourse/lib/load-script";
import {
default as discourseComputed,
on
} from "discourse-common/utils/decorators";
import discourseComputed, { on } from "discourse-common/utils/decorators";
const DATE_FORMAT = "YYYY-MM-DD";

View File

@ -1,5 +1,5 @@
import Component from "@ember/component";
import { default as discourseComputed } from "discourse-common/utils/decorators";
import discourseComputed from "discourse-common/utils/decorators";
export default Component.extend({
tagName: "span",

View File

@ -13,6 +13,6 @@ export default Component.extend({
@discourseComputed("tagRecord.id")
href(tagRecordId) {
return Discourse.getURL("/tags/" + tagRecordId);
return Discourse.getURL("/tag/" + tagRecordId);
}
});

View File

@ -1,6 +1,6 @@
import EmberObject from "@ember/object";
import { buildCategoryPanel } from "discourse/components/edit-category-panel";
import { default as discourseComputed } from "discourse-common/utils/decorators";
import discourseComputed from "discourse-common/utils/decorators";
export default buildCategoryPanel("images").extend({
@discourseComputed("category.uploaded_background.url")

View File

@ -1,12 +1,14 @@
import { scheduleOnce } from "@ember/runloop";
import { buildCategoryPanel } from "discourse/components/edit-category-panel";
import { observes } from "discourse-common/utils/decorators";
export default buildCategoryPanel("topic-template", {
@observes("activeTab")
_activeTabChanged: function() {
if (this.activeTab) {
scheduleOnce("afterRender", () =>
this.element.querySelector(".d-editor-input").focus()
);
}
}.observes("activeTab")
}
});

View File

@ -2,8 +2,7 @@ import { isEmpty } from "@ember/utils";
import { alias, equal, or } from "@ember/object/computed";
import { schedule } from "@ember/runloop";
import Component from "@ember/component";
import {
default as discourseComputed,
import discourseComputed, {
observes,
on
} from "discourse-common/utils/decorators";

View File

@ -1,6 +1,6 @@
import { notEmpty, not } from "@ember/object/computed";
import Component from "@ember/component";
import { default as discourseComputed } from "discourse-common/utils/decorators";
import discourseComputed from "discourse-common/utils/decorators";
import UploadMixin from "discourse/mixins/upload";
export default Component.extend(UploadMixin, {

View File

@ -1,5 +1,5 @@
import Component from "@ember/component";
import { default as discourseComputed } from "discourse-common/utils/decorators";
import discourseComputed from "discourse-common/utils/decorators";
export default Component.extend({
tagName: "button",

View File

@ -1,10 +1,7 @@
import { isEmpty } from "@ember/utils";
import { equal, and, empty } from "@ember/object/computed";
import Component from "@ember/component";
import {
default as discourseComputed,
observes
} from "discourse-common/utils/decorators";
import discourseComputed, { observes } from "discourse-common/utils/decorators";
import { FORMAT } from "select-kit/components/future-date-input-selector";
import { PUBLISH_TO_CATEGORY_STATUS_TYPE } from "discourse/controllers/edit-topic-timer";

View File

@ -1,119 +1,233 @@
import { bind } from "@ember/runloop";
import { bind, cancel } from "@ember/runloop";
import Component from "@ember/component";
import { on } from "discourse-common/utils/decorators";
import { iconHTML } from "discourse-common/lib/icon-library";
import LogsNotice from "discourse/services/logs-notice";
import { bufferedRender } from "discourse-common/lib/buffered-render";
import EmberObject from "@ember/object";
export default Component.extend(
bufferedRender({
rerenderTriggers: ["site.isReadOnly", "siteSettings.disable_emails"],
const _pluginNotices = [];
buildBuffer(buffer) {
export function addGlobalNotice(text, id, options = {}) {
_pluginNotices.push(Notice.create({ text, id, options }));
}
const GLOBAL_NOTICE_DISMISSED_PROMPT_KEY = "dismissed-global-notice-v2";
const Notice = EmberObject.extend({
text: null,
id: null,
options: null,
init() {
this._super(...arguments);
const defaults = {
// can this banner be hidden
dismissable: false,
// prepend html content
html: null,
// will define the style of the banner, follows alerts styling
level: "info",
// should the banner be permanently hidden?
persistentDismiss: true,
// callback function when dismissing a banner
onDismiss: null,
// show/hide banner function, will take precedence over everything
visibility: null,
// how long before banner should show again, eg: moment.duration(1, "week")
dismissDuration: null
};
this.options = this.set(
"options",
Object.assign(defaults, this.options || {})
);
}
});
export default Component.extend({
logNotice: null,
init() {
this._super(...arguments);
this._setupObservers();
},
willDestroyElement() {
this._super(...arguments);
this._tearDownObservers();
},
notices: Ember.computed(
"site.isReadOnly",
"siteSettings.disable_emails",
"logNotice.{id,text,hidden}",
function() {
let notices = [];
if ($.cookie("dosp") === "1") {
$.removeCookie("dosp", { path: "/" });
notices.push([I18n.t("forced_anonymous"), "forced-anonymous"]);
notices.push(
Notice.create({
text: I18n.t("forced_anonymous"),
id: "forced-anonymous"
})
);
}
if (this.session.get("safe_mode")) {
notices.push([I18n.t("safe_mode.enabled"), "safe-mode"]);
if (this.session && this.session.safe_mode) {
notices.push(
Notice.create({ text: I18n.t("safe_mode.enabled"), id: "safe-mode" })
);
}
if (this.site.get("isReadOnly")) {
notices.push([I18n.t("read_only_mode.enabled"), "alert-read-only"]);
if (this.site.isReadOnly) {
notices.push(
Notice.create({
text: I18n.t("read_only_mode.enabled"),
id: "alert-read-only"
})
);
}
if (
this.siteSettings.disable_emails === "yes" ||
this.siteSettings.disable_emails === "non-staff"
) {
notices.push([I18n.t("emails_are_disabled"), "alert-emails-disabled"]);
notices.push(
Notice.create({
text: I18n.t("emails_are_disabled"),
id: "alert-emails-disabled"
})
);
}
if (this.site.get("wizard_required")) {
if (this.site.wizard_required) {
const requiredText = I18n.t("wizard_required", {
url: Discourse.getURL("/wizard")
});
notices.push([requiredText, "alert-wizard"]);
notices.push(Notice.create({ text: requiredText, id: "alert-wizard" }));
}
if (
this.currentUser &&
this.currentUser.get("staff") &&
this.get("currentUser.staff") &&
this.siteSettings.bootstrap_mode_enabled
) {
if (this.siteSettings.bootstrap_mode_min_users > 0) {
notices.push([
I18n.t("bootstrap_mode_enabled", {
min_users: this.siteSettings.bootstrap_mode_min_users
}),
"alert-bootstrap-mode"
]);
notices.push(
Notice.create({
text: I18n.t("bootstrap_mode_enabled", {
min_users: this.siteSettings.bootstrap_mode_min_users
}),
id: "alert-bootstrap-mode"
})
);
} else {
notices.push([
I18n.t("bootstrap_mode_disabled"),
"alert-bootstrap-mode"
]);
notices.push(
Notice.create({
text: I18n.t("bootstrap_mode_disabled"),
id: "alert-bootstrap-mode"
})
);
}
}
if (!_.isEmpty(this.siteSettings.global_notice)) {
notices.push([this.siteSettings.global_notice, "alert-global-notice"]);
}
if (!LogsNotice.currentProp("hidden")) {
notices.push([
LogsNotice.currentProp("message"),
"alert-logs-notice",
`<button class='btn btn-flat close'>${iconHTML("times")}</button>`
]);
}
if (notices.length > 0) {
buffer.push(
notices
.map(n => {
var html = `<div class='row'><div class='alert alert-info ${n[1]}'>`;
if (n[2]) html += n[2];
html += `${n[0]}</div></div>`;
return html;
})
.join("")
if (
this.siteSettings.global_notice &&
this.siteSettings.global_notice.length
) {
notices.push(
Notice.create({
text: this.siteSettings.global_notice,
id: "alert-global-notice"
})
);
}
},
@on("didInsertElement")
_setupLogsNotice() {
this._boundRerenderBuffer = bind(this, this.rerenderBuffer);
LogsNotice.current().addObserver("hidden", this._boundRerenderBuffer);
this._boundResetCurrentProp = bind(this, this._resetCurrentProp);
$(this.element).on(
"click.global-notice",
".alert-logs-notice .close",
this._boundResetCurrentProp
);
},
@on("willDestroyElement")
_teardownLogsNotice() {
if (this._boundResetCurrentProp) {
$(this.element).off("click.global-notice", this._boundResetCurrentProp);
if (this.logNotice) {
notices.push(this.logNotice);
}
if (this._boundRerenderBuffer) {
LogsNotice.current().removeObserver(
"hidden",
this._boundRerenderBuffer
);
}
},
return notices.concat(_pluginNotices).filter(notice => {
if (notice.options.visibility) {
return notice.options.visibility(notice);
} else {
const key = `${GLOBAL_NOTICE_DISMISSED_PROMPT_KEY}-${notice.id}`;
const value = this.keyValueStore.get(key);
_resetCurrentProp() {
LogsNotice.currentProp("text", "");
// banner has never been dismissed
if (!value) {
return true;
}
// banner has no persistent dismiss and should always show on load
if (!notice.options.persistentDismiss) {
return true;
}
if (notice.options.dismissDuration) {
const resetAt = moment(value).add(notice.options.dismissDuration);
return moment().isAfter(resetAt);
} else {
return false;
}
}
});
}
})
);
),
actions: {
dismissNotice(notice) {
if (notice.options.onDismiss) {
notice.options.onDismiss(notice);
}
if (notice.options.persistentDismiss) {
this.keyValueStore.set({
key: `${GLOBAL_NOTICE_DISMISSED_PROMPT_KEY}-${notice.id}`,
value: moment().toISOString(true)
});
}
const alert = document.getElementById(`global-notice-${notice.id}`);
if (alert) alert.style.display = "none";
}
},
_setupObservers() {
this._boundLogsNoticeHandler = bind(this, this._handleLogsNoticeUpdate);
LogsNotice.current().addObserver("hidden", this._boundLogsNoticeHandler);
LogsNotice.current().addObserver("text", this._boundLogsNoticeHandler);
},
_tearDownObservers() {
if (this._boundLogsNoticeHandler) {
LogsNotice.current().removeObserver("text", this._boundLogsNoticeHandler);
LogsNotice.current().removeObserver(
"hidden",
this._boundLogsNoticeHandler
);
cancel(this._boundLogsNoticeHandler);
}
},
_handleLogsNoticeUpdate() {
const logNotice = Notice.create({
text: LogsNotice.currentProp("message"),
id: "alert-logs-notice",
options: {
dismissable: true,
persistentDismiss: false,
visibility() {
return !LogsNotice.currentProp("hidden");
},
onDismiss() {
LogsNotice.currentProp("hidden", true);
LogsNotice.currentProp("text", "");
}
}
});
this.set("logNotice", logNotice);
}
});

View File

@ -1,7 +1,7 @@
import { alias, match, gt, or } from "@ember/object/computed";
import Component from "@ember/component";
import { setting } from "discourse/lib/computed";
import { default as discourseComputed } from "discourse-common/utils/decorators";
import discourseComputed from "discourse-common/utils/decorators";
import CardContentsBase from "discourse/mixins/card-contents-base";
import CleansUp from "discourse/mixins/cleans-up";
import { groupPath } from "discourse/lib/url";

View File

@ -1,5 +1,5 @@
import Component from "@ember/component";
import { default as discourseComputed } from "discourse-common/utils/decorators";
import discourseComputed from "discourse-common/utils/decorators";
import { popupAjaxError } from "discourse/lib/ajax-error";
import showModal from "discourse/lib/show-modal";
@ -31,6 +31,14 @@ export default Component.extend({
$.cookie("destination_url", window.location.href);
},
removeFromGroup() {
this.model
.removeMember(this.currentUser)
.then(() => this.model.set("is_group_user", false))
.catch(popupAjaxError)
.finally(() => this.set("updatingMembership", false));
},
actions: {
joinGroup() {
if (this.currentUser) {
@ -53,17 +61,21 @@ export default Component.extend({
leaveGroup() {
this.set("updatingMembership", true);
const model = this.model;
model
.removeMember(this.currentUser)
.then(() => {
model.set("is_group_user", false);
})
.catch(popupAjaxError)
.finally(() => {
this.set("updatingMembership", false);
});
if (this.model.public_admission) {
this.removeFromGroup();
} else {
return bootbox.confirm(
I18n.t("groups.confirm_leave"),
I18n.t("no_value"),
I18n.t("yes_value"),
result => {
result
? this.removeFromGroup()
: this.set("updatingMembership", false);
}
);
}
},
showRequestMembershipForm() {

View File

@ -1,9 +1,8 @@
import { isEmpty } from "@ember/utils";
import Component from "@ember/component";
import {
import discourseComputed, {
on,
observes,
default as discourseComputed
observes
} from "discourse-common/utils/decorators";
import { findRawTemplate } from "discourse/lib/raw-templates";

View File

@ -1,5 +1,5 @@
import Component from "@ember/component";
import { default as discourseComputed } from "discourse-common/utils/decorators";
import discourseComputed from "discourse-common/utils/decorators";
export default Component.extend({
init() {

View File

@ -1,10 +1,7 @@
import { isEmpty } from "@ember/utils";
import { not } from "@ember/object/computed";
import Component from "@ember/component";
import {
default as discourseComputed,
observes
} from "discourse-common/utils/decorators";
import discourseComputed, { observes } from "discourse-common/utils/decorators";
import Group from "discourse/models/group";
import { popupAjaxError } from "discourse/lib/ajax-error";
import discourseDebounce from "discourse/lib/debounce";

View File

@ -4,7 +4,6 @@ import { iconHTML } from "discourse-common/lib/icon-library";
export default Component.extend({
classNameBindings: [":tip", "good", "bad"],
rerenderTriggers: ["validation"],
tipIcon: null,
tipReason: null,

View File

@ -1,9 +1,6 @@
import { next } from "@ember/runloop";
import Component from "@ember/component";
import {
default as discourseComputed,
observes
} from "discourse-common/utils/decorators";
import discourseComputed, { observes } from "discourse-common/utils/decorators";
import DiscourseURL from "discourse/lib/url";
import { renderedConnectorsFor } from "discourse/lib/plugin-connectors";
import FilterModeMixin from "discourse/mixins/filter-mode";

View File

@ -1,6 +1,6 @@
import { default as discourseComputed } from "discourse-common/utils/decorators";
import discourseComputed from "discourse-common/utils/decorators";
import { keyValueStore as pushNotificationKeyValueStore } from "discourse/lib/push-notifications";
import { default as DesktopNotificationConfig } from "discourse/components/desktop-notification-config";
import DesktopNotificationConfig from "discourse/components/desktop-notification-config";
const userDismissedPromptKey = "dismissed-prompt";

View File

@ -1,10 +1,7 @@
import { alias, not } from "@ember/object/computed";
import Component from "@ember/component";
import { iconHTML } from "discourse-common/lib/icon-library";
import {
default as discourseComputed,
observes
} from "discourse-common/utils/decorators";
import discourseComputed, { observes } from "discourse-common/utils/decorators";
export default Component.extend({
classNameBindings: [":popup-tip", "good", "bad", "lastShownAt::hide"],

View File

@ -0,0 +1,8 @@
import Component from "@ember/component";
export default Component.extend({
tagName: null,
href: null,
title: null,
ariaLabel: null
});

Some files were not shown because too many files have changed in this diff Show More