From 6cfcbc7757542c44facc3c4635d99ab604fbb9ba Mon Sep 17 00:00:00 2001 From: Yana Agun Siswanto Date: Sat, 8 Oct 2016 04:02:35 +0700 Subject: [PATCH 01/96] Update DEVELOPMENT-OSX-NATIVE.MD Changes: - Re Order the steps - Remove postgres 9.2 comment on postgres installation via homebrew (homebrew will always install the latest stable version of postgres) - Remove trailing whitespace - Update clone instruction - Adding `rake db:create` before any other database action (as it assume new development) - Remove therubyracer additional steps as it considered as obsolete --- docs/DEVELOPMENT-OSX-NATIVE.md | 53 ++++++++++++++-------------------- 1 file changed, 22 insertions(+), 31 deletions(-) diff --git a/docs/DEVELOPMENT-OSX-NATIVE.md b/docs/DEVELOPMENT-OSX-NATIVE.md index a39876babd..c3ff8b9677 100644 --- a/docs/DEVELOPMENT-OSX-NATIVE.md +++ b/docs/DEVELOPMENT-OSX-NATIVE.md @@ -14,6 +14,7 @@ If you don't already have a Ruby environment that's tuned to your liking, you ca 2. Clone the Discourse repo and cd into it. 3. Run `script/osx_dev`. 4. Review `log/osx_dev.log` to make sure everything finished successfully. +5. Jump To [Setting up your Discourse](#setting-up-your-discourse) Of course, it is good to understand what the script is doing and why. The rest of this guide goes through what's happening. @@ -132,7 +133,7 @@ If you get this error when starting `psql` from the command line: psql: could not connect to server: No such file or directory Is the server running locally and accepting connections on Unix domain socket "/tmp/.s.PGSQL.5432"? - + it is because it is still looking in the `/tmp` directory and not in `/var/pgsql_socket`. If running `psql -h /var/pgsql_socket` works then you need to configure the host in your `.bash_profile`: @@ -152,7 +153,7 @@ However, the seed data currently has some dependencies on their being a 'postgre In theory, you're not setting up with vagrant, either, and shouldn't need a vagrant user; however, again, all the seed data assumes 'vagrant'. To avoid headaches, it's probably best to go with this flow, so again, we create a 'vagrant' user. - brew install postgresql # Installs 9.2 + brew install postgresql ln -sfv /usr/local/opt/postgresql/*.plist ~/Library/LaunchAgents export PATH=/usr/local/opt/postgresql/bin:$PATH # You may want to put this in your default path! @@ -161,7 +162,7 @@ In theory, you're not setting up with vagrant, either, and shouldn't need a vagr launchctl load ~/Library/LaunchAgents/homebrew.mxcl.postgresql.plist ### Seed data relies on both 'postgres' and 'vagrant' - + createuser --createdb --superuser postgres createuser --createdb --superuser vagrant @@ -204,7 +205,7 @@ mkdir ~/.magick cd ~/.magick curl http://www.imagemagick.org/Usage/scripts/imagick_type_gen > type_gen find /System/Library/Fonts /Library/Fonts ~/Library/Fonts -name "*.[to]tf" | perl type_gen -f - > type.xml -cd /usr/local/Cellar/imagemagick//etc/ImageMagick-6 +cd /usr/local/Cellar/imagemagick//etc/ImageMagick-6 ``` Edit system config file called "type.xml" and add line near end to tell IM to @@ -230,25 +231,12 @@ outbound email and you can verify what is being sent. ## Additional Setup Tasks -You may have issues installing therubyracer when running `bundle install` -because of a dependency on libv8. This is how to fix it: - -```sh -brew tap homebrew/versions -brew uninstall v8 -brew install v8-315 -gem uninstall -a libv8 -gem uninstall -a therubyracer -gem install libv8 -v '3.16.14.13' -- --with-system-v8 -gem install therubyracer -v '0.12.2' -- --with-v8-dir=$(brew --prefix v8-315) -``` - In addition to ImageMagick we also need to install some other image related software: ```sh brew install gifsicle jpegoptim optipng -npm install -g svgo +npm install -g svgo ``` Install jhead @@ -263,28 +251,31 @@ make install ## Setting up your Discourse ### Check out the repository - - git@github.com:discourse/discourse.git ~/discourse - cd ~/discourse # Navigate into the repository, and stay there for the rest of this how-to - +```sh +git clone git@github.com:discourse/discourse.git +cd discourse # Navigate into the repository, and stay there for the rest of this how-to +``` ### What about the config files? If you've stuck to all the defaults above, the default `discourse.conf` and `redis.conf` should work out of the box. ### Install the needed gems - - bundle install # Yes, this DOES take a while. No, it's not really cloning all of rubygems :-) +```sh +bundle install +``` ### Prepare your database - - rake db:migrate - rake db:test:prepare - rake db:seed_fu +```sh +rake db:create +rake db:migrate +rake db:test:prepare +rake db:seed_fu +``` ## Now, test it out! - - bundle exec rspec - +```sh +bundle exec rspec +``` All specs should pass ### Deal with any problems which arise. From 18d032ad91b941079eb9e08a555554097adf6419 Mon Sep 17 00:00:00 2001 From: Guo Xiang Tan Date: Sat, 15 Oct 2016 01:13:58 +0800 Subject: [PATCH 02/96] PERF: Remove ordering by username. * Ordering by username results in a very expensive query for very little upside UX wise. --- app/controllers/directory_items_controller.rb | 3 +-- .../20161014171034_add_directory_items_indexes.rb | 11 +++++++++++ 2 files changed, 12 insertions(+), 2 deletions(-) create mode 100644 db/migrate/20161014171034_add_directory_items_indexes.rb diff --git a/app/controllers/directory_items_controller.rb b/app/controllers/directory_items_controller.rb index 70a729147c..6158534b40 100644 --- a/app/controllers/directory_items_controller.rb +++ b/app/controllers/directory_items_controller.rb @@ -44,8 +44,7 @@ class DirectoryItemsController < ApplicationController end end - result = result.order('users.username') - result_count = result.dup.count + result_count = result.count result = result.limit(PAGE_SIZE).offset(PAGE_SIZE * page).to_a more_params = params.slice(:period, :order, :asc) diff --git a/db/migrate/20161014171034_add_directory_items_indexes.rb b/db/migrate/20161014171034_add_directory_items_indexes.rb new file mode 100644 index 0000000000..191aed0378 --- /dev/null +++ b/db/migrate/20161014171034_add_directory_items_indexes.rb @@ -0,0 +1,11 @@ +class AddDirectoryItemsIndexes < ActiveRecord::Migration + def change + add_index :directory_items, :likes_received + add_index :directory_items, :likes_given + add_index :directory_items, :topics_entered + add_index :directory_items, :topic_count + add_index :directory_items, :post_count + add_index :directory_items, :posts_read + add_index :directory_items, :days_visited + end +end From 423bd718e8c468604496fafa68d8e41346190f45 Mon Sep 17 00:00:00 2001 From: Dax74 Date: Sun, 16 Oct 2016 18:09:40 +0200 Subject: [PATCH 03/96] Create client.it.yml Italian translation --- plugins/discourse-details/config/locales/client.it.yml | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 plugins/discourse-details/config/locales/client.it.yml diff --git a/plugins/discourse-details/config/locales/client.it.yml b/plugins/discourse-details/config/locales/client.it.yml new file mode 100644 index 0000000000..1316a2aa56 --- /dev/null +++ b/plugins/discourse-details/config/locales/client.it.yml @@ -0,0 +1,7 @@ +it: + js: + details: + title: Nascondi Dettagli + composer: + details_title: Sommario + details_text: "Questo testo verrà nascosto" From 0f448074d12c3626c8a63240d35c2ac3400f8430 Mon Sep 17 00:00:00 2001 From: Dax74 Date: Sun, 16 Oct 2016 18:11:21 +0200 Subject: [PATCH 04/96] Update client.it.yml --- plugins/discourse-details/config/locales/client.it.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/discourse-details/config/locales/client.it.yml b/plugins/discourse-details/config/locales/client.it.yml index 1316a2aa56..d5027e4686 100644 --- a/plugins/discourse-details/config/locales/client.it.yml +++ b/plugins/discourse-details/config/locales/client.it.yml @@ -3,5 +3,5 @@ it: details: title: Nascondi Dettagli composer: - details_title: Sommario + details_title: Summary details_text: "Questo testo verrà nascosto" From dffd8baa91d26c029c23ff7109f6ab90caac7a21 Mon Sep 17 00:00:00 2001 From: Ming HU Date: Tue, 18 Oct 2016 17:10:47 +0800 Subject: [PATCH 05/96] Remove user from a group by user email --- app/controllers/groups_controller.rb | 2 ++ spec/controllers/groups_controller_spec.rb | 7 +++++++ 2 files changed, 9 insertions(+) diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb index 311a11b2f9..b4024a280a 100644 --- a/app/controllers/groups_controller.rb +++ b/app/controllers/groups_controller.rb @@ -128,6 +128,8 @@ class GroupsController < ApplicationController user = User.find(params[:user_id]) elsif params[:username].present? user = User.find_by_username(params[:username]) + elsif params[:user_email].present? + user = User.find_by_email(params[:user_email]) else raise Discourse::InvalidParameters.new('user_id or username must be present') end diff --git a/spec/controllers/groups_controller_spec.rb b/spec/controllers/groups_controller_spec.rb index fded427a08..c770d8408d 100644 --- a/spec/controllers/groups_controller_spec.rb +++ b/spec/controllers/groups_controller_spec.rb @@ -224,6 +224,13 @@ describe GroupsController do user.reload expect(user.primary_group_id).to eq(nil) end + + it "removes by user_email" do + xhr :delete, :remove_member, id: group.id, user_email: user.email + expect(response).to be_success + group.reload + expect(group.users.count).to eq(0) + end end end From aee943486aa002250d1fdae3769f8fa3f87392f8 Mon Sep 17 00:00:00 2001 From: Kiril Staikov Date: Wed, 5 Oct 2016 19:28:58 -0400 Subject: [PATCH 06/96] FEATURE:'No Echo' option for mailing list mode. Mailing list mode now includes the 'no echo' option: to only receive emails of posts not created by you. If you reply to an email thread in mailing list mode, your reply will not then be echoed back to you in a duplicate email by the system. --- .../discourse/controllers/preferences.js.es6 | 3 +- .../notify_mailing_list_subscribers.rb | 7 ++++- app/jobs/regular/user_email.rb | 2 +- app/models/mailing_list_mode_site_setting.rb | 3 +- config/locales/client.en.yml | 1 + config/locales/server.en.yml | 1 + spec/jobs/enqueue_mailing_list_emails_spec.rb | 5 ++++ .../notify_mailing_list_subscribers_spec.rb | 30 ++++++++++++++++++- spec/jobs/user_email_spec.rb | 15 ++++++++++ 9 files changed, 62 insertions(+), 5 deletions(-) diff --git a/app/assets/javascripts/discourse/controllers/preferences.js.es6 b/app/assets/javascripts/discourse/controllers/preferences.js.es6 index b94d993ed1..f9077e6e43 100644 --- a/app/assets/javascripts/discourse/controllers/preferences.js.es6 +++ b/app/assets/javascripts/discourse/controllers/preferences.js.es6 @@ -79,7 +79,8 @@ export default Ember.Controller.extend(CanCheckEmails, { mailingListModeOptions() { return [ {name: I18n.t('user.mailing_list_mode.daily'), value: 0}, - {name: this.get('frequencyEstimate'), value: 1} + {name: this.get('frequencyEstimate'), value: 1}, + {name: I18n.t('user.mailing_list_mode.individual_no_echo'), value: 2} ]; }, diff --git a/app/jobs/regular/notify_mailing_list_subscribers.rb b/app/jobs/regular/notify_mailing_list_subscribers.rb index 6eed13a420..ae5e333e7e 100644 --- a/app/jobs/regular/notify_mailing_list_subscribers.rb +++ b/app/jobs/regular/notify_mailing_list_subscribers.rb @@ -16,7 +16,7 @@ module Jobs users = User.activated.not_blocked.not_suspended.real .joins(:user_option) - .where(user_options: {mailing_list_mode: true, mailing_list_mode_frequency: 1}) + .where('user_options.mailing_list_mode AND user_options.mailing_list_mode_frequency > 0') .where('NOT EXISTS( SELECT 1 FROM topic_users tu @@ -46,6 +46,11 @@ module Jobs next end + if (user.id == post.user_id) && (user.user_option.mailing_list_mode_frequency == 2) + skip(user.email, user.id, post.id, I18n.t('email_log.no_echo_mailing_list_mode')) + next + end + begin if message = UserNotifications.mailing_list_notify(user, post) EmailLog.unique_email_per_post(post, user) do diff --git a/app/jobs/regular/user_email.rb b/app/jobs/regular/user_email.rb index 50109e2a68..de1d318f4f 100644 --- a/app/jobs/regular/user_email.rb +++ b/app/jobs/regular/user_email.rb @@ -103,7 +103,7 @@ module Jobs end if user.user_option.mailing_list_mode? && - user.user_option.mailing_list_mode_frequency == 1 && # don't catch notifications for users on daily mailing list mode + user.user_option.mailing_list_mode_frequency > 0 && # don't catch notifications for users on daily mailing list mode (!post.try(:topic).try(:private_message?)) && NOTIFICATIONS_SENT_BY_MAILING_LIST.include?(email_args[:notification_type]) # no need to log a reason when the mail was already sent via the mailing list job diff --git a/app/models/mailing_list_mode_site_setting.rb b/app/models/mailing_list_mode_site_setting.rb index b2a8291d00..9a8241c1f0 100644 --- a/app/models/mailing_list_mode_site_setting.rb +++ b/app/models/mailing_list_mode_site_setting.rb @@ -9,7 +9,8 @@ class MailingListModeSiteSetting < EnumSiteSetting def self.values @values ||= [ { name: 'user.mailing_list_mode.daily', value: 0 }, - { name: 'user.mailing_list_mode.individual', value: 1 } + { name: 'user.mailing_list_mode.individual', value: 1 }, + { name: 'user.mailing_list_mode.individual_no_echo', value: 2 } ] end diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index fe210e4bb3..df618cac2b 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -535,6 +535,7 @@ en: Muted topics and categories are not included in these emails. daily: "Send daily updates" individual: "Send an email for every new post" + individual_no_echo: "Send an email for every new post except my own" many_per_day: "Send me an email for every new post (about {{dailyEmailEstimate}} per day)" few_per_day: "Send me an email for every new post (about 2 per day)" tag_settings: "Tags" diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index fd7ebcc9b6..3ae8f8fb67 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -2614,6 +2614,7 @@ en: message_to_blank: "message.to is blank" text_part_body_blank: "text_part.body is blank" body_blank: "body is blank" + no_echo_mailing_list_mode: "Mailing list notifications disabled for user's own posts" color_schemes: base_theme_name: "Base" diff --git a/spec/jobs/enqueue_mailing_list_emails_spec.rb b/spec/jobs/enqueue_mailing_list_emails_spec.rb index 820ed1e812..3ee9ebe941 100644 --- a/spec/jobs/enqueue_mailing_list_emails_spec.rb +++ b/spec/jobs/enqueue_mailing_list_emails_spec.rb @@ -82,6 +82,11 @@ describe Jobs::EnqueueMailingListEmails do expect(subject).to_not include user.id end + it "doesn't return users with mailing list mode set to 'individual_excluding_own'" do + user_option.update(mailing_list_mode_frequency: 2) + expect(subject).to_not include user.id + end + it "doesn't return a user who has received the mailing list summary earlier" do user.update(first_seen_at: 5.hours.ago) expect(subject).to_not include user.id diff --git a/spec/jobs/notify_mailing_list_subscribers_spec.rb b/spec/jobs/notify_mailing_list_subscribers_spec.rb index 899d4aedd9..92a97409b9 100644 --- a/spec/jobs/notify_mailing_list_subscribers_spec.rb +++ b/spec/jobs/notify_mailing_list_subscribers_spec.rb @@ -47,9 +47,15 @@ describe Jobs::NotifyMailingListSubscribers do end end - context "with a valid post" do + context "with a valid post authored by same user" do let!(:post) { Fabricate(:post, user: user) } + it "doesn't send the email to the user if the frequency is set to 'always with no echo'" do + user.user_option.update(mailing_list_mode: true, mailing_list_mode_frequency: 2) + UserNotifications.expects(:mailing_list_notify).never + Jobs::NotifyMailingListSubscribers.new.execute(post_id: post.id) + end + it "sends the email to the user if the frequency is set to 'always'" do user.user_option.update(mailing_list_mode: true, mailing_list_mode_frequency: 1) UserNotifications.expects(:mailing_list_notify).with(user, post).once @@ -63,6 +69,28 @@ describe Jobs::NotifyMailingListSubscribers do end end + context "with a valid post created by someone other than the user" do + let!(:post) { Fabricate(:post, user: user, user_id: 'different') } + + it "sends the email to the user if the frequency is set to 'always with no echo'" do + user.user_option.update(mailing_list_mode: true, mailing_list_mode_frequency: 2) + UserNotifications.expects(:mailing_list_notify).once + Jobs::NotifyMailingListSubscribers.new.execute(post_id: post.id) + end + + it "sends the email to the user if the frequency is set to 'always'" do + user.user_option.update(mailing_list_mode: true, mailing_list_mode_frequency: 1) + UserNotifications.expects(:mailing_list_notify).once + Jobs::NotifyMailingListSubscribers.new.execute(post_id: post.id) + end + + it "does not send the email to the user if the frequency is set to 'daily'" do + user.user_option.update(mailing_list_mode: true, mailing_list_mode_frequency: 0) + UserNotifications.expects(:mailing_list_notify).never + Jobs::NotifyMailingListSubscribers.new.execute(post_id: post.id) + end + end + context "with a deleted post" do let!(:post) { Fabricate(:post, user: user, deleted_at: Time.now) } diff --git a/spec/jobs/user_email_spec.rb b/spec/jobs/user_email_spec.rb index f99a403b4e..a1af812c12 100644 --- a/spec/jobs/user_email_spec.rb +++ b/spec/jobs/user_email_spec.rb @@ -240,6 +240,21 @@ describe Jobs::UserEmail do Jobs::UserEmail.new.execute(type: :user_mentioned, user_id: user.id, notification_type: "posted", post_id: post.id) end + it "doesn't send the mail if the user is using individual mailing list mode with no echo" do + Email::Sender.any_instance.expects(:send).never + user.user_option.update(mailing_list_mode: true, mailing_list_mode_frequency: 2) + # sometimes, we pass the notification_id + Jobs::UserEmail.new.execute(type: :user_mentioned, user_id: user.id, notification_id: notification.id, post_id: post.id) + # other times, we only pass the type of notification + Jobs::UserEmail.new.execute(type: :user_mentioned, user_id: user.id, notification_type: "posted", post_id: post.id) + # When post is nil + Jobs::UserEmail.new.execute(type: :user_mentioned, user_id: user.id, notification_type: "posted") + # When post does not have a topic + post = Fabricate(:post) + post.topic.destroy + Jobs::UserEmail.new.execute(type: :user_mentioned, user_id: user.id, notification_type: "posted", post_id: post.id) + end + it "doesn't send the email if the post has been user deleted" do Email::Sender.any_instance.expects(:send).never post.update_column(:user_deleted, true) From 35a79a70c3f1ddffacb5b120dbfd1e91b661e9b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9gis=20Hanol?= Date: Thu, 20 Oct 2016 19:53:41 +0200 Subject: [PATCH 07/96] FIX: uploading custom avatar was always hidden --- .../discourse/lib/utilities.js.es6 | 37 +++++++++---------- lib/validators/upload_validator.rb | 8 ++-- test/javascripts/lib/utilities-test.js.es6 | 31 +++++++++++++--- 3 files changed, 46 insertions(+), 30 deletions(-) diff --git a/app/assets/javascripts/discourse/lib/utilities.js.es6 b/app/assets/javascripts/discourse/lib/utilities.js.es6 index a46fdb0c8c..0385fa6555 100644 --- a/app/assets/javascripts/discourse/lib/utilities.js.es6 +++ b/app/assets/javascripts/discourse/lib/utilities.js.es6 @@ -190,10 +190,8 @@ export function validateUploadedFiles(files, bypassNewUserRestriction) { export function validateUploadedFile(file, type, bypassNewUserRestriction) { // check that the uploaded file is authorized - if (!authorizesAllExtensions() && - !isAuthorizedUpload(file)) { - var extensions = authorizedExtensions(); - bootbox.alert(I18n.t('post.errors.upload_not_authorized', { authorized_extensions: extensions })); + if (!authorizesAllExtensions() && !isAuthorizedUpload(file)) { + bootbox.alert(I18n.t('post.errors.upload_not_authorized', { authorized_extensions: authorizedExtensions() })); return false; } @@ -217,23 +215,24 @@ export function authorizesAllExtensions() { return Discourse.SiteSettings.authorized_extensions.indexOf("*") >= 0; } +function extensions() { + return Discourse.SiteSettings.authorized_extensions + .toLowerCase() + .replace(/[\s\.]+/g, "") + .split("|") + .filter(ext => ext.indexOf("*") === -1); +} + +function extensionsRegex() { + return new RegExp("\\.(" + extensions().join("|") + ")$", "i"); +} + export function isAuthorizedUpload(file) { - if (file && file.name) { - var extensions = _.chain(Discourse.SiteSettings.authorized_extensions.split("|")) - .reject(function(extension) { return extension.indexOf("*") >= 0; }) - .map(function(extension) { return (extension.indexOf(".") === 0 ? extension.substring(1) : extension).replace(".", "\\."); }) - .value(); - return new RegExp("\\.(" + extensions.join("|") + ")$", "i").test(file.name); - } - return false; + return file && file.name && extensionsRegex().test(file.name); } export function authorizedExtensions() { - return _.chain(Discourse.SiteSettings.authorized_extensions.split("|")) - .reject(function(extension) { return extension.indexOf("*") >= 0; }) - .map(function(extension) { return extension.toLowerCase(); }) - .value() - .join(", "); + return extensions().join(", "); } export function uploadLocation(url) { @@ -267,12 +266,12 @@ export function isAnImage(path) { export function allowsImages() { return authorizesAllExtensions() || - (/\.(png|jpe?g|gif|bmp|tiff?|svg|webp|ico)/i).test(authorizedExtensions()); + (/(png|jpe?g|gif|bmp|tiff?|svg|webp|ico)/i).test(authorizedExtensions()); } export function allowsAttachments() { return authorizesAllExtensions() || - !/^(\.(png|jpe?g|gif|bmp|tiff?|svg|webp|ico)(,\s)?)+$/i.test(authorizedExtensions()); + !/^((png|jpe?g|gif|bmp|tiff?|svg|webp|ico)(,\s)?)+$/i.test(authorizedExtensions()); } export function displayErrorForUpload(data) { diff --git a/lib/validators/upload_validator.rb b/lib/validators/upload_validator.rb index 9d871ba520..5612662cb1 100644 --- a/lib/validators/upload_validator.rb +++ b/lib/validators/upload_validator.rb @@ -49,12 +49,10 @@ class Validators::UploadValidator < ActiveModel::Validator authorized_uploads = Set.new SiteSetting.authorized_extensions - .tr(" ", "") + .gsub(/[\s\.]+/, "") + .downcase .split("|") - .each do |extension| - next if extension.include?("*") - authorized_uploads << (extension.start_with?(".") ? extension[1..-1] : extension).downcase - end + .each { |extension| authorized_uploads << extension unless extension.include?("*") } authorized_uploads end diff --git a/test/javascripts/lib/utilities-test.js.es6 b/test/javascripts/lib/utilities-test.js.es6 index fd3583de8c..403cb2c971 100644 --- a/test/javascripts/lib/utilities-test.js.es6 +++ b/test/javascripts/lib/utilities-test.js.es6 @@ -5,6 +5,8 @@ import { extractDomainFromUrl, isAnImage, avatarUrl, + authorizedExtensions, + allowsImages, allowsAttachments, getRawSize, avatarImg, @@ -63,12 +65,11 @@ test("new user cannot upload attachments", function() { }); test("ensures an authorized upload", function() { - var html = { name: "unauthorized.html" }; - var extensions = Discourse.SiteSettings.authorized_extensions.replace(/\|/g, ", "); + const html = { name: "unauthorized.html" }; sandbox.stub(bootbox, "alert"); not(validUpload([html])); - ok(bootbox.alert.calledWith(I18n.t('post.errors.upload_not_authorized', { authorized_extensions: extensions }))); + ok(bootbox.alert.calledWith(I18n.t('post.errors.upload_not_authorized', { authorized_extensions: authorizedExtensions() }))); }); var imageSize = 10 * 1024; @@ -163,15 +164,33 @@ test("avatarImg", function() { setDevicePixelRatio(oldRatio); }); +test("allowsImages", function() { + Discourse.SiteSettings.authorized_extensions = "jpg|jpeg|gif"; + ok(allowsImages(), "works"); + + Discourse.SiteSettings.authorized_extensions = ".jpg|.jpeg|.gif"; + ok(allowsImages(), "works with old extensions syntax"); + + Discourse.SiteSettings.authorized_extensions = "txt|pdf|*"; + ok(allowsImages(), "images are allowed when all extensions are allowed"); + + Discourse.SiteSettings.authorized_extensions = "json|jpg|pdf|txt"; + ok(allowsImages(), "images are allowed when at least one extension is an image extension"); +}); + + test("allowsAttachments", function() { - Discourse.SiteSettings.authorized_extensions = ".jpg, .jpeg, .gif"; + Discourse.SiteSettings.authorized_extensions = "jpg|jpeg|gif"; not(allowsAttachments(), "no attachments allowed by default"); - Discourse.SiteSettings.authorized_extensions = ".jpg, .jpeg, .gif, *"; + Discourse.SiteSettings.authorized_extensions = "jpg|jpeg|gif|*"; ok(allowsAttachments(), "attachments are allowed when all extensions are allowed"); - Discourse.SiteSettings.authorized_extensions = ".jpg, .jpeg, .gif, .pdf"; + Discourse.SiteSettings.authorized_extensions = "jpg|jpeg|gif|pdf"; ok(allowsAttachments(), "attachments are allowed when at least one extension is not an image extension"); + + Discourse.SiteSettings.authorized_extensions = ".jpg|.jpeg|.gif|.pdf"; + ok(allowsAttachments(), "works with old extensions syntax"); }); test("defaultHomepage", function() { From febbd27ba605fd85fd85e8852b7fb47791368991 Mon Sep 17 00:00:00 2001 From: Jeff Atwood Date: Thu, 20 Oct 2016 14:49:06 -0700 Subject: [PATCH 08/96] remove gmail/live SMTP warning --- app/models/admin_dashboard_data.rb | 6 +----- config/locales/server.en.yml | 3 +-- spec/models/admin_dashboard_data_spec.rb | 23 ----------------------- 3 files changed, 2 insertions(+), 30 deletions(-) diff --git a/app/models/admin_dashboard_data.rb b/app/models/admin_dashboard_data.rb index bfcaff65fa..5ac4bd55c3 100644 --- a/app/models/admin_dashboard_data.rb +++ b/app/models/admin_dashboard_data.rb @@ -94,7 +94,7 @@ class AdminDashboardData :ram_check, :google_oauth2_config_check, :facebook_config_check, :twitter_config_check, :github_config_check, :s3_config_check, :image_magick_check, - :failing_emails_check, :send_consumer_email_check, + :failing_emails_check, :subfolder_ends_in_slash_check, :pop3_polling_configuration, :email_polling_errored_recently @@ -214,10 +214,6 @@ class AdminDashboardData I18n.t('dashboard.failing_emails_warning', num_failed_jobs: num_failed_jobs) if num_failed_jobs > 0 end - def send_consumer_email_check - I18n.t('dashboard.consumer_email_warning') if Rails.env.production? and ActionMailer::Base.smtp_settings[:address] =~ /gmail\.com|live\.com|yahoo\.com/ - end - def subfolder_ends_in_slash_check I18n.t('dashboard.subfolder_ends_in_slash') if Discourse.base_uri =~ /\/$/ end diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index fd7ebcc9b6..01ba7f9cc8 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -816,8 +816,7 @@ en: s3_config_warning: 'The server is configured to upload files to s3, but at least one the following setting is not set: s3_access_key_id, s3_secret_access_key or s3_upload_bucket. Go to the Site Settings and update the settings. See "How to set up image uploads to S3?" to learn more.' s3_backup_config_warning: 'The server is configured to upload backups to s3, but at least one the following setting is not set: s3_access_key_id, s3_secret_access_key or s3_backup_bucket. Go to the Site Settings and update the settings. See "How to set up image uploads to S3?" to learn more.' image_magick_warning: 'The server is configured to create thumbnails of large images, but ImageMagick is not installed. Install ImageMagick using your favorite package manager or download the latest release.' - failing_emails_warning: 'There are %{num_failed_jobs} email jobs that failed. Check your app.yml and ensure that the mail server settings are correct. See the failed jobs in Sidekiq.' - consumer_email_warning: "Your site is configured to use Gmail (or another consumer email service) to send email. Gmail limits how many emails you can send. Consider using an email service provider like mandrill.com to ensure email deliverability." + failing_emails_warning: 'There are %{num_failed_jobs} email jobs that failed. Check your app.yml and ensure that the mail server settings are correct. See the failed jobs in Sidekiq.' subfolder_ends_in_slash: "Your subfolder setup is incorrect; the DISCOURSE_RELATIVE_URL_ROOT ends in a slash." email_polling_errored_recently: one: "Email polling has generated an error in the past 24 hours. Look at the logs for more details." diff --git a/spec/models/admin_dashboard_data_spec.rb b/spec/models/admin_dashboard_data_spec.rb index aa9f7080b3..6a0cd5a3ca 100644 --- a/spec/models/admin_dashboard_data_spec.rb +++ b/spec/models/admin_dashboard_data_spec.rb @@ -123,29 +123,6 @@ describe AdminDashboardData do end end - describe 'send_consumer_email_check' do - subject { described_class.new.send_consumer_email_check } - - it 'returns nil if gmail.com is not in the smtp_settings address' do - ActionMailer::Base.stubs(:smtp_settings).returns({address: 'mandrillapp.com'}) - expect(subject).to be_nil - end - - context 'gmail.com is in the smtp_settings address' do - before { ActionMailer::Base.stubs(:smtp_settings).returns({address: 'smtp.gmail.com'}) } - - it 'returns nil in development env' do - Rails.stubs(env: ActiveSupport::StringInquirer.new('development')) - expect(subject).to be_nil - end - - it 'returns a string when in production env' do - Rails.stubs(env: ActiveSupport::StringInquirer.new('production')) - expect(subject).not_to be_nil - end - end - end - describe 'auth_config_checks' do shared_examples 'problem detection for login providers' do From 35d248ab0d3d5338ae4ddd98960e94b0f5b7df65 Mon Sep 17 00:00:00 2001 From: Sam Date: Fri, 21 Oct 2016 10:53:58 +1100 Subject: [PATCH 09/96] FIX: if badge has an image do not override icon --- app/models/badge.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/app/models/badge.rb b/app/models/badge.rb index 9c743f14dd..6851b210dc 100644 --- a/app/models/badge.rb +++ b/app/models/badge.rb @@ -129,8 +129,10 @@ class Badge < ActiveRecord::Base end def default_icon=(val) - self.icon ||= val - self.icon = val if self.icon = "fa-certificate" + unless self.image + self.icon ||= val + self.icon = val if self.icon = "fa-certificate" + end end def default_name=(val) From 2a61cc8c88a361394edc080361faa9b607db4bd5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9gis=20Hanol?= Date: Fri, 21 Oct 2016 12:37:03 +0200 Subject: [PATCH 10/96] FIX: email styling with blacklisted iframes --- lib/email/styles.rb | 9 +++++++-- spec/components/email/styles_spec.rb | 6 ++++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/lib/email/styles.rb b/lib/email/styles.rb index 9186bb3f13..cb20f3dca1 100644 --- a/lib/email/styles.rb +++ b/lib/email/styles.rb @@ -129,13 +129,18 @@ module Email # iframes can't go in emails, so replace them with clickable links @fragment.css('iframe').each do |i| begin - src_uri = URI(i['src']) + # sometimes, iframes are blacklisted... + if i["src"].blank? + i.remove + next + end + src_uri = URI(i['src']) # If an iframe is protocol relative, use SSL when displaying it display_src = "#{src_uri.scheme || 'https'}://#{src_uri.host}#{src_uri.path}#{src_uri.query.nil? ? '' : '?' + src_uri.query}#{src_uri.fragment.nil? ? '' : '#' + src_uri.fragment}" i.replace "

#{CGI.escapeHTML(display_src)}

" rescue URI::InvalidURIError - # If the URL is weird, remove it + # If the URL is weird, remove the iframe i.remove end end diff --git a/spec/components/email/styles_spec.rb b/spec/components/email/styles_spec.rb index d9824f4cf7..97e09c726c 100644 --- a/spec/components/email/styles_spec.rb +++ b/spec/components/email/styles_spec.rb @@ -95,6 +95,12 @@ describe Email::Styles do expect(frag.at('iframe')).to be_blank expect(frag.at('a')).to be_blank end + + it "won't allow empty iframe src, strips them with no link" do + frag = html_fragment("") + expect(frag.at('iframe')).to be_blank + expect(frag.at('a')).to be_blank + end end context "rewriting protocol relative URLs to the forum" do From bf915322603c0b7ec8157e99e00f4c3c7b280289 Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Thu, 20 Oct 2016 13:26:41 -0400 Subject: [PATCH 11/96] Fixes some Ember Deprecations for 1.13: MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove ArrayController - Remove {{view}} from templates - Replace many cases of needs: [‘controller’] with inject - Enable Ember Legacy Views --- .../admin-backups-logs.js.es6 | 19 +++-- .../admin/controllers/admin-api-keys.js.es6 | 54 ++---------- .../controllers/admin-backups-index.js.es6 | 13 +-- .../controllers/admin-backups-logs.js.es6 | 7 +- .../controllers/admin-badges-show.js.es6 | 82 +++++++++---------- .../admin/controllers/admin-badges.js.es6 | 2 +- .../controllers/admin-customize-colors.js.es6 | 19 ++--- .../admin/controllers/admin-emojis.js.es6 | 14 ++-- .../admin/controllers/admin-flags-list.js.es6 | 14 ++-- .../admin/controllers/admin-group.js.es6 | 8 +- .../controllers/admin-groups-type.js.es6 | 20 ++--- .../admin-logs-screened-emails.js.es6 | 13 ++- .../admin-logs-screened-ip-addresses.js.es6 | 7 +- .../admin-logs-screened-urls.js.es6 | 11 ++- .../admin-logs-staff-action-logs.js.es6 | 2 +- .../admin/controllers/admin-permalinks.js.es6 | 17 ++-- .../admin/controllers/admin-plugins.js.es6 | 11 ++- .../admin-site-settings-category.js.es6 | 4 +- .../controllers/admin-site-settings.js.es6 | 2 +- .../controllers/admin-user-badges.js.es6 | 43 ++++------ .../controllers/admin-users-list-show.js.es6 | 2 +- .../modals/admin-agree-flag.js.es6 | 13 ++- .../modals/admin-delete-flag.js.es6 | 31 +++---- .../javascripts/admin/models/backup.js.es6 | 3 - .../admin/routes/admin-backups-logs.js.es6 | 12 +-- .../admin/routes/admin-backups.js.es6 | 4 +- .../admin/routes/admin-badges.js.es6 | 21 +++-- .../javascripts/admin/templates/api-keys.hbs | 3 +- .../admin/templates/backups-logs.hbs | 1 + .../admin/templates/backups_index.hbs | 10 +-- .../javascripts/admin/templates/emojis.hbs | 4 +- .../admin/templates/groups_type.hbs | 2 +- .../bulk-notification-level.js.es6 | 4 +- .../discourse/controllers/group-posts.js.es6 | 7 +- .../preferences/badge-title.js.es6 | 15 ++-- .../controllers/preferences/card-badge.js.es6 | 15 ++-- .../controllers/tag-groups-show.js.es6 | 13 ++- .../discourse/controllers/tag-groups.js.es6 | 8 +- .../controllers/topic-bulk-actions.js.es6 | 2 +- .../discourse/controllers/topic.js.es6 | 1 + .../discourse/controllers/user-badges.js.es6 | 24 ++---- .../controllers/user-notifications.js.es6 | 10 +-- .../templates/discovery/categories.hbs | 4 +- .../templates/modal/topic-bulk-actions.hbs | 2 +- .../javascripts/discourse/templates/topic.hbs | 2 +- .../discourse/templates/user/badges.hbs | 4 +- .../templates/user/notifications.hbs | 2 +- .../javascripts/discourse/views/topic.js.es6 | 5 +- app/assets/javascripts/env.js | 3 +- .../controllers/admin-user-badges-test.js.es6 | 2 +- 50 files changed, 255 insertions(+), 336 deletions(-) rename app/assets/javascripts/admin/{views => components}/admin-backups-logs.js.es6 (82%) create mode 100644 app/assets/javascripts/admin/templates/backups-logs.hbs diff --git a/app/assets/javascripts/admin/views/admin-backups-logs.js.es6 b/app/assets/javascripts/admin/components/admin-backups-logs.js.es6 similarity index 82% rename from app/assets/javascripts/admin/views/admin-backups-logs.js.es6 rename to app/assets/javascripts/admin/components/admin-backups-logs.js.es6 index 1929abf5a2..cb9e39eb60 100644 --- a/app/assets/javascripts/admin/views/admin-backups-logs.js.es6 +++ b/app/assets/javascripts/admin/components/admin-backups-logs.js.es6 @@ -2,17 +2,20 @@ import debounce from 'discourse/lib/debounce'; import { renderSpinner } from 'discourse/helpers/loading-spinner'; import { escapeExpression } from 'discourse/lib/utilities'; -export default Ember.View.extend({ +export default Ember.Component.extend({ classNames: ["admin-backups-logs"], - _initialize: function() { this._reset(); }.on("init"), + init() { + this._super(); + this._reset(); + }, _reset() { this.setProperties({ formattedLogs: "", index: 0 }); }, _updateFormattedLogs: debounce(function() { - const logs = this.get("controller.model"); + const logs = this.get("logs"); if (logs.length === 0) { this._reset(); // reset the cached logs whenever the model is reset } else { @@ -28,7 +31,7 @@ export default Ember.View.extend({ // force rerender this.rerender(); } - }, 150).observes("controller.model.[]"), + }, 150).observes("logs.[]").on('init'), render(buffer) { const formattedLogs = this.get("formattedLogs"); @@ -40,14 +43,14 @@ export default Ember.View.extend({ buffer.push("

" + I18n.t("admin.backups.logs.none") + "

"); } // add a loading indicator - if (this.get("controller.status.model.isOperationRunning")) { + if (this.get("status.isOperationRunning")) { buffer.push(renderSpinner('small')); } }, - _forceScrollToBottom: function() { + didInsertElement() { + this._super(); const $div = this.$()[0]; $div.scrollTop = $div.scrollHeight; - }.on("didInsertElement") - + }, }); diff --git a/app/assets/javascripts/admin/controllers/admin-api-keys.js.es6 b/app/assets/javascripts/admin/controllers/admin-api-keys.js.es6 index 82366d3bd7..529538263c 100644 --- a/app/assets/javascripts/admin/controllers/admin-api-keys.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-api-keys.js.es6 @@ -1,66 +1,30 @@ import ApiKey from 'admin/models/api-key'; -/** - This controller supports the interface for dealing with API keys - - @class AdminApiController - @extends Ember.ArrayController - @namespace Discourse - @module Discourse -**/ -export default Ember.ArrayController.extend({ +export default Ember.Controller.extend({ actions: { - /** - Generates a master api key - - @method generateMasterKey - **/ - generateMasterKey: function() { - var self = this; - ApiKey.generateMasterKey().then(function (key) { - self.get('model').pushObject(key); - }); + generateMasterKey() { + ApiKey.generateMasterKey().then(key => this.get('model').pushObject(key)); }, - /** - Creates an API key instance with internal user object - - @method regenerateKey - @param {ApiKey} key the key to regenerate - **/ - regenerateKey: function(key) { - bootbox.confirm(I18n.t("admin.api.confirm_regen"), I18n.t("no_value"), I18n.t("yes_value"), function(result) { + regenerateKey(key) { + bootbox.confirm(I18n.t("admin.api.confirm_regen"), I18n.t("no_value"), I18n.t("yes_value"), result => { if (result) { key.regenerate(); } }); }, - /** - Revokes an API key - - @method revokeKey - @param {ApiKey} key the key to revoke - **/ - revokeKey: function(key) { - var self = this; - bootbox.confirm(I18n.t("admin.api.confirm_revoke"), I18n.t("no_value"), I18n.t("yes_value"), function(result) { + revokeKey(key) { + bootbox.confirm(I18n.t("admin.api.confirm_revoke"), I18n.t("no_value"), I18n.t("yes_value"), result => { if (result) { - key.revoke().then(function() { - self.get('model').removeObject(key); - }); + key.revoke().then(() => this.get('model').removeObject(key)); } }); } }, - /** - Has a master key already been generated? - - @property hasMasterKey - @type {Boolean} - **/ + // Has a master key already been generated? hasMasterKey: function() { return !!this.get('model').findBy('user', null); }.property('model.[]') 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 98f76405a2..605ebe83af 100644 --- a/app/assets/javascripts/admin/controllers/admin-backups-index.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-backups-index.js.es6 @@ -1,19 +1,20 @@ import { ajax } from 'discourse/lib/ajax'; -export default Ember.ArrayController.extend({ - needs: ["adminBackups"], - status: Ember.computed.alias("controllers.adminBackups"), + +export default Ember.Controller.extend({ + adminBackups: Ember.inject.controller(), + status: Ember.computed.alias('adminBackups.model'), uploadLabel: function() { return I18n.t("admin.backups.upload.label"); }.property(), restoreTitle: function() { - if (!this.get('status.model.allowRestore')) { + if (!this.get('status.allowRestore')) { return "admin.backups.operations.restore.is_disabled"; - } else if (this.get("status.model.isOperationRunning")) { + } else if (this.get("status.isOperationRunning")) { return "admin.backups.operations.is_running"; } else { return "admin.backups.operations.restore.title"; } - }.property("status.model.{allowRestore,isOperationRunning}"), + }.property("status.{allowRestore,isOperationRunning}"), actions: { diff --git a/app/assets/javascripts/admin/controllers/admin-backups-logs.js.es6 b/app/assets/javascripts/admin/controllers/admin-backups-logs.js.es6 index 1b3d90346a..38bdd6f571 100644 --- a/app/assets/javascripts/admin/controllers/admin-backups-logs.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-backups-logs.js.es6 @@ -1,4 +1,5 @@ -export default Ember.ArrayController.extend({ - needs: ["adminBackups"], - status: Em.computed.alias("controllers.adminBackups") +export default Ember.Controller.extend({ + logs: [], + adminBackups: Ember.inject.controller(), + status: Em.computed.alias("adminBackups.model") }); 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 5ec2cb4541..122a63c42d 100644 --- a/app/assets/javascripts/admin/controllers/admin-badges-show.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-badges-show.js.es6 @@ -3,14 +3,14 @@ import BufferedContent from 'discourse/mixins/buffered-content'; import { propertyNotEqual } from 'discourse/lib/computed'; export default Ember.Controller.extend(BufferedContent, { - needs: ['admin-badges'], + adminBadges: Ember.inject.controller(), saving: false, savingStatus: '', - badgeTypes: Em.computed.alias('controllers.admin-badges.badgeTypes'), - badgeGroupings: Em.computed.alias('controllers.admin-badges.badgeGroupings'), - badgeTriggers: Em.computed.alias('controllers.admin-badges.badgeTriggers'), - protectedSystemFields: Em.computed.alias('controllers.admin-badges.protectedSystemFields'), + badgeTypes: Em.computed.alias('adminBadges.badgeTypes'), + badgeGroupings: Em.computed.alias('adminBadges.badgeGroupings'), + badgeTriggers: Em.computed.alias('adminBadges.badgeTriggers'), + protectedSystemFields: Em.computed.alias('adminBadges.protectedSystemFields'), readOnly: Ember.computed.alias('buffered.system'), showDisplayName: propertyNotEqual('name', 'displayName'), @@ -30,16 +30,15 @@ export default Ember.Controller.extend(BufferedContent, { }.observes('model.id'), actions: { - save: function() { + save() { if (!this.get('saving')) { - var fields = ['allow_title', 'multiple_grant', - 'listable', 'auto_revoke', - 'enabled', 'show_posts', - 'target_posts', 'name', 'description', - 'long_description', - 'icon', 'image', 'query', 'badge_grouping_id', - 'trigger', 'badge_type_id'], - self = this; + let fields = ['allow_title', 'multiple_grant', + 'listable', 'auto_revoke', + 'enabled', 'show_posts', + 'target_posts', 'name', 'description', + 'long_description', + 'icon', 'image', 'query', 'badge_grouping_id', + 'trigger', 'badge_type_id']; if (this.get('buffered.system')){ var protectedFields = this.get('protectedSystemFields'); @@ -51,54 +50,55 @@ export default Ember.Controller.extend(BufferedContent, { this.set('saving', true); this.set('savingStatus', I18n.t('saving')); - var boolFields = ['allow_title', 'multiple_grant', - 'listable', 'auto_revoke', - 'enabled', 'show_posts', - 'target_posts' ]; + const boolFields = ['allow_title', 'multiple_grant', + 'listable', 'auto_revoke', + 'enabled', 'show_posts', + 'target_posts' ]; - var data = {}, - buffered = this.get('buffered'); + const data = {}; + const buffered = this.get('buffered'); fields.forEach(function(field){ var d = buffered.get(field); if (_.include(boolFields, field)) { d = !!d; } data[field] = d; }); - var newBadge = !this.get('id'), - model = this.get('model'); - this.get('model').save(data).then(function() { + const newBadge = !this.get('id'); + const model = this.get('model'); + this.get('model').save(data).then(() => { if (newBadge) { - var adminBadgesController = self.get('controllers.admin-badges'); - if (!adminBadgesController.contains(model)) adminBadgesController.pushObject(model); - self.transitionToRoute('adminBadges.show', model.get('id')); + const adminBadges = this.get('adminBadges.model'); + if (!adminBadges.contains(model)) { + adminBadges.pushObject(model); + } + this.transitionToRoute('adminBadges.show', model.get('id')); } else { - self.commitBuffer(); - self.set('savingStatus', I18n.t('saved')); + this.commitBuffer(); + this.set('savingStatus', I18n.t('saved')); } - }).catch(popupAjaxError).finally(function() { - self.set('saving', false); - self.set('savingStatus', ''); + }).catch(popupAjaxError).finally(() => { + this.set('saving', false); + this.set('savingStatus', ''); }); } }, - destroy: function() { - var self = this, - adminBadgesController = this.get('controllers.admin-badges'), - model = this.get('model'); + destroy() { + const adminBadges = this.get('adminBadges.model'); + const model = this.get('model'); if (!model.get('id')) { - self.transitionToRoute('adminBadges.index'); + this.transitionToRoute('adminBadges.index'); return; } - return bootbox.confirm(I18n.t("admin.badges.delete_confirm"), I18n.t("no_value"), I18n.t("yes_value"), function(result) { + return bootbox.confirm(I18n.t("admin.badges.delete_confirm"), I18n.t("no_value"), I18n.t("yes_value"), result => { if (result) { - model.destroy().then(function() { - adminBadgesController.removeObject(model); - self.transitionToRoute('adminBadges.index'); - }).catch(function() { + model.destroy().then(() => { + adminBadges.removeObject(model); + this.transitionToRoute('adminBadges.index'); + }).catch(() => { bootbox.alert(I18n.t('generic_error')); }); } diff --git a/app/assets/javascripts/admin/controllers/admin-badges.js.es6 b/app/assets/javascripts/admin/controllers/admin-badges.js.es6 index 24c4c05139..77c79b724a 100644 --- a/app/assets/javascripts/admin/controllers/admin-badges.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-badges.js.es6 @@ -1 +1 @@ -export default Ember.ArrayController.extend(); +export default Ember.Controller.extend(); 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 4a6a58021b..ae253aec84 100644 --- a/app/assets/javascripts/admin/controllers/admin-customize-colors.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-customize-colors.js.es6 @@ -1,4 +1,4 @@ -export default Ember.ArrayController.extend({ +export default Ember.Controller.extend({ onlyOverridden: false, baseColorScheme: function() { @@ -13,8 +13,8 @@ export default Ember.ArrayController.extend({ return baseColorsHash; }.property('baseColorScheme'), - removeSelected: function() { - this.removeObject(this.get('selectedItem')); + removeSelected() { + this.get('model').removeObject(this.get('selectedItem')); this.set('selectedItem', null); }, @@ -26,8 +26,7 @@ export default Ember.ArrayController.extend({ return; } - var matches = Em.A(); - + const matches = []; _.each(this.get('selectedItem.colors'), function(color){ if (color.get('overridden')) matches.pushObject(color); }); @@ -58,10 +57,10 @@ export default Ember.ArrayController.extend({ this.filterContent(); }, - newColorScheme: function() { - var newColorScheme = Em.copy(this.get('baseColorScheme'), true); + newColorScheme() { + const newColorScheme = Em.copy(this.get('baseColorScheme'), true); newColorScheme.set('name', I18n.t('admin.customize.colors.new_name')); - this.pushObject(newColorScheme); + this.get('model').pushObject(newColorScheme); this.send('selectColorScheme', newColorScheme); this.set('onlyOverridden', false); }, @@ -86,10 +85,10 @@ export default Ember.ArrayController.extend({ this.updateEnabled(); }, - copy: function(colorScheme) { + copy(colorScheme) { var newColorScheme = Em.copy(colorScheme, true); newColorScheme.set('name', I18n.t('admin.customize.colors.copy_name_prefix') + ' ' + colorScheme.get('name')); - this.pushObject(newColorScheme); + this.get('model').pushObject(newColorScheme); this.send('selectColorScheme', newColorScheme); }, diff --git a/app/assets/javascripts/admin/controllers/admin-emojis.js.es6 b/app/assets/javascripts/admin/controllers/admin-emojis.js.es6 index b111a5952b..29aecc4773 100644 --- a/app/assets/javascripts/admin/controllers/admin-emojis.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-emojis.js.es6 @@ -1,23 +1,23 @@ import { ajax } from 'discourse/lib/ajax'; -export default Ember.ArrayController.extend({ - sortProperties: ["name"], +export default Ember.Controller.extend({ + sortedEmojis: Ember.computed.sort('model', 'emojiSorting'), + emojiSorting: ['name'], actions: { emojiUploaded(emoji) { emoji.url += "?t=" + new Date().getTime(); - this.pushObject(Ember.Object.create(emoji)); + this.get('model').pushObject(Ember.Object.create(emoji)); }, destroy(emoji) { - const self = this; return bootbox.confirm( I18n.t("admin.emoji.delete_confirm", { name: emoji.get("name") }), I18n.t("no_value"), I18n.t("yes_value"), - function(destroy) { + destroy => { if (destroy) { - return ajax("/admin/customize/emojis/" + emoji.get("name"), { type: "DELETE" }).then(function() { - self.removeObject(emoji); + return ajax("/admin/customize/emojis/" + emoji.get("name"), { type: "DELETE" }).then(() => { + this.get('model').removeObject(emoji); }); } } diff --git a/app/assets/javascripts/admin/controllers/admin-flags-list.js.es6 b/app/assets/javascripts/admin/controllers/admin-flags-list.js.es6 index 0c85749775..e3101e2fa3 100644 --- a/app/assets/javascripts/admin/controllers/admin-flags-list.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-flags-list.js.es6 @@ -1,6 +1,6 @@ import FlaggedPost from 'admin/models/flagged-post'; -export default Ember.ArrayController.extend({ +export default Ember.Controller.extend({ query: null, adminOldFlagsView: Em.computed.equal("query", "old"), @@ -8,18 +8,16 @@ export default Ember.ArrayController.extend({ actions: { disagreeFlags(flaggedPost) { - var self = this; - flaggedPost.disagreeFlags().then(function () { - self.removeObject(flaggedPost); + flaggedPost.disagreeFlags().then(() => { + this.get('model').removeObject(flaggedPost); }, function () { bootbox.alert(I18n.t("admin.flags.error")); }); }, deferFlags(flaggedPost) { - var self = this; - flaggedPost.deferFlags().then(function () { - self.removeObject(flaggedPost); + flaggedPost.deferFlags().then(() => { + this.get('model').removeObject(flaggedPost); }, function () { bootbox.alert(I18n.t("admin.flags.error")); }); @@ -29,7 +27,7 @@ export default Ember.ArrayController.extend({ this.send("disagreeFlags", item); }, - loadMore(){ + loadMore() { const flags = this.get('model'); return FlaggedPost.findAll(this.get('query'), flags.length+1).then(data => { if (data.length===0) { diff --git a/app/assets/javascripts/admin/controllers/admin-group.js.es6 b/app/assets/javascripts/admin/controllers/admin-group.js.es6 index bf19293117..e7d53800e7 100644 --- a/app/assets/javascripts/admin/controllers/admin-group.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-group.js.es6 @@ -4,7 +4,7 @@ import { escapeExpression } from 'discourse/lib/utilities'; import computed from 'ember-addons/ember-computed-decorators'; export default Ember.Controller.extend({ - needs: ['adminGroupsType'], + adminGroupsType: Ember.inject.controller(), disableSave: false, savingStatus: '', @@ -131,13 +131,13 @@ export default Ember.Controller.extend({ save() { const group = this.get('model'), - groupsController = this.get("controllers.adminGroupsType"), + groupsController = this.get("adminGroupsType"), groupType = groupsController.get("type"); this.set('disableSave', true); this.set('savingStatus', I18n.t('saving')); - let promise = group.get("id") ? group.save() : group.create().then(() => groupsController.addObject(group)); + let promise = group.get("id") ? group.save() : group.create().then(() => groupsController.get('model').addObject(group)); promise.then(() => { this.transitionToRoute("adminGroup", groupType, group.get('name')); @@ -148,7 +148,7 @@ export default Ember.Controller.extend({ destroy() { const group = this.get('model'), - groupsController = this.get('controllers.adminGroupsType'), + groupsController = this.get('adminGroupsType'), self = this; if (!group.get('id')) { diff --git a/app/assets/javascripts/admin/controllers/admin-groups-type.js.es6 b/app/assets/javascripts/admin/controllers/admin-groups-type.js.es6 index 9a5962cccf..10d7ad01cf 100644 --- a/app/assets/javascripts/admin/controllers/admin-groups-type.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-groups-type.js.es6 @@ -1,18 +1,18 @@ import { ajax } from 'discourse/lib/ajax'; -export default Ember.ArrayController.extend({ - sortProperties: ['name'], +export default Ember.Controller.extend({ + sortedGroups: Ember.computed.sort('model', 'groupSorting'), + groupSorting: ['name'], + refreshingAutoGroups: false, - isAuto: function(){ - return this.get('type') === 'automatic'; - }.property('type'), + + isAuto: Ember.computed.equal('type', 'automatic'), actions: { - refreshAutoGroups: function(){ - var self = this; + refreshAutoGroups() { this.set('refreshingAutoGroups', true); - ajax('/admin/groups/refresh_automatic_groups', {type: 'POST'}).then(function() { - self.transitionToRoute("adminGroupsType", "automatic").then(function() { - self.set('refreshingAutoGroups', false); + ajax('/admin/groups/refresh_automatic_groups', {type: 'POST'}).then(() => { + this.transitionToRoute("adminGroupsType", "automatic").then(() => { + this.set('refreshingAutoGroups', false); }); }); } diff --git a/app/assets/javascripts/admin/controllers/admin-logs-screened-emails.js.es6 b/app/assets/javascripts/admin/controllers/admin-logs-screened-emails.js.es6 index fe158a33ab..13685f16b9 100644 --- a/app/assets/javascripts/admin/controllers/admin-logs-screened-emails.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-logs-screened-emails.js.es6 @@ -2,12 +2,12 @@ import { exportEntity } from 'discourse/lib/export-csv'; import { outputExportResult } from 'discourse/lib/export-result'; import ScreenedEmail from 'admin/models/screened-email'; -export default Ember.ArrayController.extend({ +export default Ember.Controller.extend({ loading: false, actions: { clearBlock(row){ - row.clearBlock().then(function(){ + row.clearBlock().then(function() { // feeling lazy window.location.reload(); }); @@ -19,11 +19,10 @@ export default Ember.ArrayController.extend({ }, show() { - var self = this; - self.set('loading', true); - ScreenedEmail.findAll().then(function(result) { - self.set('model', result); - self.set('loading', false); + this.set('loading', true); + ScreenedEmail.findAll().then(result => { + this.set('model', result); + this.set('loading', false); }); } }); 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 88f46d6ccb..b654e6d65f 100644 --- a/app/assets/javascripts/admin/controllers/admin-logs-screened-ip-addresses.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-logs-screened-ip-addresses.js.es6 @@ -3,7 +3,7 @@ import { outputExportResult } from 'discourse/lib/export-result'; import { exportEntity } from 'discourse/lib/export-csv'; import ScreenedIpAddress from 'admin/models/screened-ip-address'; -export default Ember.ArrayController.extend({ +export default Ember.Controller.extend({ loading: false, filter: null, savedIpAddress: null, @@ -63,16 +63,15 @@ export default Ember.ArrayController.extend({ }, destroy(record) { - const self = this; return bootbox.confirm( I18n.t("admin.logs.screened_ips.delete_confirm", { ip_address: record.get('ip_address') }), I18n.t("no_value"), I18n.t("yes_value"), - function (result) { + result => { if (result) { record.destroy().then(deleted => { if (deleted) { - self.get("content").removeObject(record); + this.get("model").removeObject(record); } else { bootbox.alert(I18n.t("generic_error")); } diff --git a/app/assets/javascripts/admin/controllers/admin-logs-screened-urls.js.es6 b/app/assets/javascripts/admin/controllers/admin-logs-screened-urls.js.es6 index 5f5e333960..d57a151d21 100644 --- a/app/assets/javascripts/admin/controllers/admin-logs-screened-urls.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-logs-screened-urls.js.es6 @@ -2,15 +2,14 @@ import { exportEntity } from 'discourse/lib/export-csv'; import { outputExportResult } from 'discourse/lib/export-result'; import ScreenedUrl from 'admin/models/screened-url'; -export default Ember.ArrayController.extend({ +export default Ember.Controller.extend({ loading: false, show() { - const self = this; - self.set('loading', true); - ScreenedUrl.findAll().then(function(result) { - self.set('model', result); - self.set('loading', false); + this.set('loading', true); + ScreenedUrl.findAll().then(result => { + this.set('model', result); + this.set('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 956a737e57..98f135dc57 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 @@ -2,7 +2,7 @@ import { exportEntity } from 'discourse/lib/export-csv'; import { outputExportResult } from 'discourse/lib/export-result'; import StaffActionLog from 'admin/models/staff-action-log'; -export default Ember.ArrayController.extend({ +export default Ember.Controller.extend({ loading: false, filters: null, diff --git a/app/assets/javascripts/admin/controllers/admin-permalinks.js.es6 b/app/assets/javascripts/admin/controllers/admin-permalinks.js.es6 index 9c5ab4bb3e..6bfcc66922 100644 --- a/app/assets/javascripts/admin/controllers/admin-permalinks.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-permalinks.js.es6 @@ -1,16 +1,14 @@ import debounce from 'discourse/lib/debounce'; import Permalink from 'admin/models/permalink'; -export default Ember.ArrayController.extend({ +export default Ember.Controller.extend({ loading: false, filter: null, show: debounce(function() { - var self = this; - self.set('loading', true); - Permalink.findAll(self.get("filter")).then(function(result) { - self.set('model', result); - self.set('loading', false); + Permalink.findAll(this.get("filter")).then(result => { + this.set('model', result); + this.set('loading', false); }); }, 250).observes("filter"), @@ -20,12 +18,11 @@ export default Ember.ArrayController.extend({ }, destroy: function(record) { - const self = this; - return bootbox.confirm(I18n.t("admin.permalink.delete_confirm"), I18n.t("no_value"), I18n.t("yes_value"), function(result) { + return bootbox.confirm(I18n.t("admin.permalink.delete_confirm"), I18n.t("no_value"), I18n.t("yes_value"), result => { if (result) { - record.destroy().then(function(deleted) { + record.destroy().then(deleted => { if (deleted) { - self.removeObject(record); + this.get('model').removeObject(record); } else { bootbox.alert(I18n.t("generic_error")); } diff --git a/app/assets/javascripts/admin/controllers/admin-plugins.js.es6 b/app/assets/javascripts/admin/controllers/admin-plugins.js.es6 index b2eb1335ed..5393a27c8f 100644 --- a/app/assets/javascripts/admin/controllers/admin-plugins.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-plugins.js.es6 @@ -1,10 +1,9 @@ -export default Ember.ArrayController.extend({ - +export default Ember.Controller.extend({ adminRoutes: function() { - return this.get('model').map(function(p) { - if (p.get('enabled')) { - return p.admin_route; - } + return this.get('model').map(p => { + if (p.get('enabled')) { + return p.admin_route; + } }).compact(); }.property() }); 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 b16db34e3e..3feb90f40e 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,11 +1,11 @@ export default Ember.Controller.extend({ categoryNameKey: null, - needs: ['adminSiteSettings'], + adminSiteSettings: Ember.inject.controller(), filteredContent: function() { if (!this.get('categoryNameKey')) { return []; } - const category = this.get('controllers.adminSiteSettings.content').findProperty('nameKey', this.get('categoryNameKey')); + const category = this.get('adminSiteSettings.allSiteSettings').findProperty('nameKey', this.get('categoryNameKey')); if (category) { return category.siteSettings; } else { 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 b7c4302e87..02a907d7af 100644 --- a/app/assets/javascripts/admin/controllers/admin-site-settings.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-site-settings.js.es6 @@ -1,6 +1,6 @@ import debounce from 'discourse/lib/debounce'; -export default Ember.ArrayController.extend({ +export default Ember.Controller.extend({ filter: null, onlyOverridden: false, filtered: Ember.computed.notEmpty('filter'), 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 c8c0ba6219..a2b7c94f17 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,11 @@ import UserBadge from 'discourse/models/user-badge'; -export default Ember.ArrayController.extend({ - needs: ["adminUser"], - user: Em.computed.alias('controllers.adminUser.model'), - sortProperties: ['granted_at'], - sortAscending: false, +export default Ember.Controller.extend({ + adminUser: Ember.inject.controller(), + user: Ember.computed.alias('adminUser.model'), + + sortedBadges: Ember.computed.sort('model', 'badgeSortOrder'), + badgeSortOrder: ['granted_at:desc'], groupedBadges: function(){ const allBadges = this.get('model'); @@ -38,8 +39,6 @@ export default Ember.ArrayController.extend({ }); return _(expanded).sortBy(group => group.granted_at).reverse().value(); - - }.property('model', 'model.[]', 'model.expandedBadges.[]'), /** @@ -80,22 +79,15 @@ export default Ember.ArrayController.extend({ model.get('expandedBadges').pushObject(userBadge.badge.id); }, - /** - Grant the selected badge to the user. - - @method grantBadge - @param {Integer} badgeId id of the badge we want to grant. - **/ - grantBadge: function(badgeId) { - var self = this; - UserBadge.grant(badgeId, this.get('user.username'), this.get('badgeReason')).then(function(userBadge) { - self.set('badgeReason', ''); - self.pushObject(userBadge); - Ember.run.next(function() { + grantBadge(badgeId) { + UserBadge.grant(badgeId, this.get('user.username'), this.get('badgeReason')).then(userBadge => { + this.set('badgeReason', ''); + this.get('model').pushObject(userBadge); + Ember.run.next(() => { // Update the selected badge ID after the combobox has re-rendered. - var newSelectedBadge = self.get('grantableBadges')[0]; + const newSelectedBadge = this.get('grantableBadges')[0]; if (newSelectedBadge) { - self.set('selectedBadgeId', newSelectedBadge.get('id')); + this.set('selectedBadgeId', newSelectedBadge.get('id')); } }); }, function() { @@ -104,12 +96,11 @@ export default Ember.ArrayController.extend({ }); }, - revokeBadge: function(userBadge) { - var self = this; - return bootbox.confirm(I18n.t("admin.badges.revoke_confirm"), I18n.t("no_value"), I18n.t("yes_value"), function(result) { + revokeBadge(userBadge) { + return bootbox.confirm(I18n.t("admin.badges.revoke_confirm"), I18n.t("no_value"), I18n.t("yes_value"), result => { if (result) { - userBadge.revoke().then(function() { - self.get('model').removeObject(userBadge); + userBadge.revoke().then(() => { + this.get('model').removeObject(userBadge); }); } }); 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 af4a0297ef..d3ea899d81 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 @@ -2,7 +2,7 @@ import debounce from 'discourse/lib/debounce'; import { i18n } from 'discourse/lib/computed'; import AdminUser from 'admin/models/admin-user'; -export default Ember.ArrayController.extend({ +export default Ember.Controller.extend({ query: null, showEmails: false, refreshing: false, diff --git a/app/assets/javascripts/admin/controllers/modals/admin-agree-flag.js.es6 b/app/assets/javascripts/admin/controllers/modals/admin-agree-flag.js.es6 index 2924eb6699..7f1f4f2e49 100644 --- a/app/assets/javascripts/admin/controllers/modals/admin-agree-flag.js.es6 +++ b/app/assets/javascripts/admin/controllers/modals/admin-agree-flag.js.es6 @@ -1,16 +1,15 @@ import ModalFunctionality from 'discourse/mixins/modal-functionality'; export default Ember.Controller.extend(ModalFunctionality, { - needs: ["admin-flags-list"], + adminFlagsList: Ember.inject.controller(), _agreeFlag: function (actionOnPost) { - var adminFlagController = this.get("controllers.admin-flags-list"); - var post = this.get("content"); - var self = this; + const adminFlagController = this.get("adminFlagsList"); + const post = this.get("content"); - return post.agreeFlags(actionOnPost).then(function () { - adminFlagController.removeObject(post); - self.send("closeModal"); + return post.agreeFlags(actionOnPost).then(() => { + adminFlagController.get('model').removeObject(post); + this.send("closeModal"); }, function () { bootbox.alert(I18n.t("admin.flags.error")); }); diff --git a/app/assets/javascripts/admin/controllers/modals/admin-delete-flag.js.es6 b/app/assets/javascripts/admin/controllers/modals/admin-delete-flag.js.es6 index 284b8bfd09..fc2e062794 100644 --- a/app/assets/javascripts/admin/controllers/modals/admin-delete-flag.js.es6 +++ b/app/assets/javascripts/admin/controllers/modals/admin-delete-flag.js.es6 @@ -1,36 +1,31 @@ import ModalFunctionality from 'discourse/mixins/modal-functionality'; export default Ember.Controller.extend(ModalFunctionality, { - needs: ["admin-flags-list"], + adminFlagsList: Ember.inject.controller(), actions: { + deletePostDeferFlag() { + const adminFlagController = this.get("adminFlagsList"); + const post = this.get("content"); - deletePostDeferFlag: function () { - var adminFlagController = this.get("controllers.admin-flags-list"); - var post = this.get("content"); - var self = this; - - return post.deferFlags(true).then(function () { - adminFlagController.removeObject(post); - self.send("closeModal"); + return post.deferFlags(true).then(() => { + adminFlagController.get('model').removeObject(post); + this.send("closeModal"); }, function () { bootbox.alert(I18n.t("admin.flags.error")); }); }, - deletePostAgreeFlag: function () { - var adminFlagController = this.get("controllers.admin-flags-list"); - var post = this.get("content"); - var self = this; + deletePostAgreeFlag() { + const adminFlagController = this.get("adminFlagsList"); + const post = this.get("content"); - return post.agreeFlags("delete").then(function () { - adminFlagController.removeObject(post); - self.send("closeModal"); + return post.agreeFlags("delete").then(() => { + adminFlagController.get('model').removeObject(post); + this.send("closeModal"); }, function () { bootbox.alert(I18n.t("admin.flags.error")); }); } - } - }); diff --git a/app/assets/javascripts/admin/models/backup.js.es6 b/app/assets/javascripts/admin/models/backup.js.es6 index d7baceebac..7fe81441c3 100644 --- a/app/assets/javascripts/admin/models/backup.js.es6 +++ b/app/assets/javascripts/admin/models/backup.js.es6 @@ -2,7 +2,6 @@ import { ajax } from 'discourse/lib/ajax'; import PreloadStore from 'preload-store'; const Backup = Discourse.Model.extend({ - destroy() { return ajax("/admin/backups/" + this.get("filename"), { type: "DELETE" }); }, @@ -13,11 +12,9 @@ const Backup = Discourse.Model.extend({ data: { client_id: window.MessageBus.clientId } }); } - }); Backup.reopenClass({ - find() { return PreloadStore.getAndRemove("backups", () => ajax("/admin/backups.json")) .then(backups => backups.map(backup => Backup.create(backup))); diff --git a/app/assets/javascripts/admin/routes/admin-backups-logs.js.es6 b/app/assets/javascripts/admin/routes/admin-backups-logs.js.es6 index cd01295f5d..c036a10742 100644 --- a/app/assets/javascripts/admin/routes/admin-backups-logs.js.es6 +++ b/app/assets/javascripts/admin/routes/admin-backups-logs.js.es6 @@ -5,17 +5,17 @@ export default Ember.Route.extend({ // since the logs are pushed via the message bus // we only want to preload them (hence the beforeModel hook) beforeModel() { - const logsController = this.controllerFor("adminBackupsLogs"); + const logs = this.controllerFor("adminBackupsLogs").get('logs'); // preload the logs if any PreloadStore.getAndRemove("logs").then(function (preloadedLogs) { if (preloadedLogs && preloadedLogs.length) { // we need to filter out message like: "[SUCCESS]" // and convert POJOs to Ember Objects - const logs = _.chain(preloadedLogs) - .reject(function (log) { return log.message.length === 0 || log.message[0] === "["; }) - .map(function (log) { return Em.Object.create(log); }) - .value(); - logsController.pushObjects(logs); + const newLogs = _.chain(preloadedLogs) + .reject(function (log) { return log.message.length === 0 || log.message[0] === "["; }) + .map(function (log) { return Em.Object.create(log); }) + .value(); + logs.pushObjects(newLogs); } }); }, diff --git a/app/assets/javascripts/admin/routes/admin-backups.js.es6 b/app/assets/javascripts/admin/routes/admin-backups.js.es6 index d590019431..ad6f79a8cb 100644 --- a/app/assets/javascripts/admin/routes/admin-backups.js.es6 +++ b/app/assets/javascripts/admin/routes/admin-backups.js.es6 @@ -15,7 +15,7 @@ export default Discourse.Route.extend({ _processLogMessage(log) { if (log.message === "[STARTED]") { this.controllerFor("adminBackups").set("model.isOperationRunning", true); - this.controllerFor("adminBackupsLogs").clear(); + this.controllerFor("adminBackupsLogs").get('logs').clear(); } else if (log.message === "[FAILED]") { this.controllerFor("adminBackups").set("model.isOperationRunning", false); bootbox.alert(I18n.t("admin.backups.operations.failed", { operation: log.operation })); @@ -27,7 +27,7 @@ export default Discourse.Route.extend({ window.location.pathname = Discourse.getURL("/"); } } else { - this.controllerFor("adminBackupsLogs").pushObject(Em.Object.create(log)); + this.controllerFor("adminBackupsLogs").get('logs').pushObject(Em.Object.create(log)); } }, diff --git a/app/assets/javascripts/admin/routes/admin-badges.js.es6 b/app/assets/javascripts/admin/routes/admin-badges.js.es6 index 68da5edb6b..96ecddab23 100644 --- a/app/assets/javascripts/admin/routes/admin-badges.js.es6 +++ b/app/assets/javascripts/admin/routes/admin-badges.js.es6 @@ -5,21 +5,20 @@ import BadgeGrouping from 'discourse/models/badge-grouping'; export default Discourse.Route.extend({ _json: null, - model: function() { - var self = this; - return ajax('/admin/badges.json').then(function(json) { - self._json = json; + model() { + return ajax('/admin/badges.json').then(json => { + this._json = json; return Badge.createFromJson(json); }); }, - setupController: function(controller, model) { - var json = this._json, - triggers = [], - badgeGroupings = []; + setupController(controller, model) { + const json = this._json; + const badgeTriggers = []; + const badgeGroupings = []; _.each(json.admin_badges.triggers,function(v,k){ - triggers.push({id: v, name: I18n.t('admin.badges.trigger_type.'+k)}); + badgeTriggers.push({id: v, name: I18n.t('admin.badges.trigger_type.'+k)}); }); json.badge_groupings.forEach(function(badgeGroupingJson) { @@ -30,8 +29,8 @@ export default Discourse.Route.extend({ badgeGroupings: badgeGroupings, badgeTypes: json.badge_types, protectedSystemFields: json.admin_badges.protected_system_fields, - badgeTriggers: triggers, - model: model + badgeTriggers, + model }); } }); diff --git a/app/assets/javascripts/admin/templates/api-keys.hbs b/app/assets/javascripts/admin/templates/api-keys.hbs index 4123e57638..60449052bb 100644 --- a/app/assets/javascripts/admin/templates/api-keys.hbs +++ b/app/assets/javascripts/admin/templates/api-keys.hbs @@ -30,5 +30,4 @@ {{#unless hasMasterKey}} -{{/unless }} - +{{/unless}} diff --git a/app/assets/javascripts/admin/templates/backups-logs.hbs b/app/assets/javascripts/admin/templates/backups-logs.hbs new file mode 100644 index 0000000000..34ec15f84a --- /dev/null +++ b/app/assets/javascripts/admin/templates/backups-logs.hbs @@ -0,0 +1 @@ +{{admin-backups-logs logs=logs status=status}} diff --git a/app/assets/javascripts/admin/templates/backups_index.hbs b/app/assets/javascripts/admin/templates/backups_index.hbs index c7323ebea0..308e73f0fd 100644 --- a/app/assets/javascripts/admin/templates/backups_index.hbs +++ b/app/assets/javascripts/admin/templates/backups_index.hbs @@ -6,9 +6,9 @@
{{resumable-upload target="/admin/backups/upload" success="uploadSuccess" error="uploadError" uploadText=uploadLabel title="admin.backups.upload.title"}} {{#if site.isReadOnly}} - {{d-button icon="eye" action="toggleReadOnlyMode" disabled=status.model.isOperationRunning title="admin.backups.read_only.disable.title" label="admin.backups.read_only.disable.label"}} + {{d-button icon="eye" action="toggleReadOnlyMode" disabled=status.isOperationRunning title="admin.backups.read_only.disable.title" label="admin.backups.read_only.disable.label"}} {{else}} - {{d-button icon="eye" action="toggleReadOnlyMode" disabled=status.model.isOperationRunning title="admin.backups.read_only.enable.title" label="admin.backups.read_only.enable.label"}} + {{d-button icon="eye" action="toggleReadOnlyMode" disabled=status.isOperationRunning title="admin.backups.read_only.enable.title" label="admin.backups.read_only.enable.label"}} {{/if}}
@@ -20,12 +20,12 @@
{{fa-icon "download"}}{{i18n 'admin.backups.operations.download.label'}} - {{#if status.model.isOperationRunning}} + {{#if status.isOperationRunning}} {{d-button icon="trash-o" action="destroyBackup" actionParam=backup class="btn-danger" disabled="true" title="admin.backups.operations.is_running"}} - {{d-button icon="play" action="startRestore" actionParam=backup disabled=status.model.restoreDisabled title=restoreTitle label="admin.backups.operations.restore.label"}} + {{d-button icon="play" action="startRestore" actionParam=backup disabled=status.restoreDisabled title=restoreTitle label="admin.backups.operations.restore.label"}} {{else}} {{d-button icon="trash-o" action="destroyBackup" actionParam=backup class="btn-danger" title="admin.backups.operations.destroy.title"}} - {{d-button icon="play" action="startRestore" actionParam=backup disabled=status.model.restoreDisabled title=restoreTitle label="admin.backups.operations.restore.label"}} + {{d-button icon="play" action="startRestore" actionParam=backup disabled=status.restoreDisabled title=restoreTitle label="admin.backups.operations.restore.label"}} {{/if}}
diff --git a/app/assets/javascripts/admin/templates/emojis.hbs b/app/assets/javascripts/admin/templates/emojis.hbs index ca6fd742d4..8972e2ab27 100644 --- a/app/assets/javascripts/admin/templates/emojis.hbs +++ b/app/assets/javascripts/admin/templates/emojis.hbs @@ -5,7 +5,7 @@

{{emoji-uploader done="emojiUploaded"}}

- {{#if model}} + {{#if sortedEmojis}}
@@ -16,7 +16,7 @@ - {{#each model as |e|}} + {{#each sortedEmojis as |e|}} diff --git a/app/assets/javascripts/admin/templates/groups_type.hbs b/app/assets/javascripts/admin/templates/groups_type.hbs index 45bb054ad7..d1d2223c2b 100644 --- a/app/assets/javascripts/admin/templates/groups_type.hbs +++ b/app/assets/javascripts/admin/templates/groups_type.hbs @@ -2,7 +2,7 @@

{{i18n 'admin.groups.edit'}}

    - {{#each model as |group|}} + {{#each sortedGroups as |group|}}
  • {{#link-to "adminGroup" group.type group.name}}{{group.name}} {{#if group.userCountDisplay}} diff --git a/app/assets/javascripts/discourse/controllers/bulk-notification-level.js.es6 b/app/assets/javascripts/discourse/controllers/bulk-notification-level.js.es6 index d4a3ebbd2a..cd0cf9c11b 100644 --- a/app/assets/javascripts/discourse/controllers/bulk-notification-level.js.es6 +++ b/app/assets/javascripts/discourse/controllers/bulk-notification-level.js.es6 @@ -3,7 +3,7 @@ import { topicLevels } from 'discourse/lib/notification-levels'; // Support for changing the notification level of various topics export default Ember.Controller.extend({ - needs: ['topic-bulk-actions'], + topicBulkActions: Ember.inject.controller(), notificationLevelId: null, @computed @@ -21,7 +21,7 @@ export default Ember.Controller.extend({ actions: { changeNotificationLevel() { - this.get('controllers.topic-bulk-actions').performAndRefresh({ + this.get('topicBulkActions').performAndRefresh({ type: 'change_notification_level', notification_level_id: this.get('notificationLevelId') }); diff --git a/app/assets/javascripts/discourse/controllers/group-posts.js.es6 b/app/assets/javascripts/discourse/controllers/group-posts.js.es6 index c9e791c1f1..c2434c83c6 100644 --- a/app/assets/javascripts/discourse/controllers/group-posts.js.es6 +++ b/app/assets/javascripts/discourse/controllers/group-posts.js.es6 @@ -1,19 +1,18 @@ import { fmt } from 'discourse/lib/computed'; -export default Ember.ArrayController.extend({ - needs: ['group'], +export default Ember.Controller.extend({ + group: Ember.inject.controller(), loading: false, emptyText: fmt('type', 'groups.empty.%@'), actions: { loadMore() { - if (this.get('loading')) { return; } this.set('loading', true); const posts = this.get('model'); if (posts && posts.length) { const beforePostId = posts[posts.length-1].get('id'); - const group = this.get('controllers.group.model'); + const group = this.get('group.model'); const opts = { beforePostId, type: this.get('type') }; group.findPosts(opts).then(newPosts => { diff --git a/app/assets/javascripts/discourse/controllers/preferences/badge-title.js.es6 b/app/assets/javascripts/discourse/controllers/preferences/badge-title.js.es6 index b71660d17f..f2c543e6be 100644 --- a/app/assets/javascripts/discourse/controllers/preferences/badge-title.js.es6 +++ b/app/assets/javascripts/discourse/controllers/preferences/badge-title.js.es6 @@ -1,27 +1,26 @@ import { ajax } from 'discourse/lib/ajax'; import BadgeSelectController from "discourse/mixins/badge-select-controller"; -export default Ember.ArrayController.extend(BadgeSelectController, { +export default Ember.Controller.extend(BadgeSelectController, { filteredList: function() { return this.get('model').filterBy('badge.allow_title', true); }.property('model'), actions: { - save: function() { + save() { this.setProperties({ saved: false, saving: true }); - var self = this; ajax(this.get('user.path') + "/preferences/badge_title", { type: "PUT", - data: { user_badge_id: self.get('selectedUserBadgeId') } - }).then(function() { - self.setProperties({ + data: { user_badge_id: this.get('selectedUserBadgeId') } + }).then(() => { + this.setProperties({ saved: true, saving: false, - "user.title": self.get('selectedUserBadge.badge.name') + "user.title": this.get('selectedUserBadge.badge.name') }); - }, function() { + }, () => { bootbox.alert(I18n.t('generic_error')); }); } diff --git a/app/assets/javascripts/discourse/controllers/preferences/card-badge.js.es6 b/app/assets/javascripts/discourse/controllers/preferences/card-badge.js.es6 index d0c42fb558..9f820f9647 100644 --- a/app/assets/javascripts/discourse/controllers/preferences/card-badge.js.es6 +++ b/app/assets/javascripts/discourse/controllers/preferences/card-badge.js.es6 @@ -1,7 +1,7 @@ import { ajax } from 'discourse/lib/ajax'; import BadgeSelectController from "discourse/mixins/badge-select-controller"; -export default Ember.ArrayController.extend(BadgeSelectController, { +export default Ember.Controller.extend(BadgeSelectController, { filteredList: function() { return this.get('model').filter(function(b) { return !Ember.isEmpty(b.get('badge.image')); @@ -12,18 +12,17 @@ export default Ember.ArrayController.extend(BadgeSelectController, { save: function() { this.setProperties({ saved: false, saving: true }); - var self = this; ajax(this.get('user.path') + "/preferences/card-badge", { type: "PUT", - data: { user_badge_id: self.get('selectedUserBadgeId') } - }).then(function() { - self.setProperties({ + data: { user_badge_id: this.get('selectedUserBadgeId') } + }).then(() => { + this.setProperties({ saved: true, saving: false, - "user.card_image_badge": self.get('selectedUserBadge.badge.image') + "user.card_image_badge": this.get('selectedUserBadge.badge.image') }); - }).catch(function() { - self.set('saving', false); + }).catch(() => { + this.set('saving', false); bootbox.alert(I18n.t('generic_error')); }); } diff --git a/app/assets/javascripts/discourse/controllers/tag-groups-show.js.es6 b/app/assets/javascripts/discourse/controllers/tag-groups-show.js.es6 index 1eca5728ab..d575844cad 100644 --- a/app/assets/javascripts/discourse/controllers/tag-groups-show.js.es6 +++ b/app/assets/javascripts/discourse/controllers/tag-groups-show.js.es6 @@ -1,5 +1,5 @@ export default Ember.Controller.extend({ - needs: ['tagGroups'], + tagGroups: Ember.inject.controller(), actions: { save() { @@ -7,17 +7,16 @@ export default Ember.Controller.extend({ }, destroy() { - const self = this; return bootbox.confirm( I18n.t("tagging.groups.confirm_delete"), I18n.t("no_value"), I18n.t("yes_value"), - function(destroy) { + destroy => { if (destroy) { - const c = self.controllerFor('tagGroups'); - return self.get('model').destroy().then(function() { - c.removeObject(self.get('model')); - self.transitionToRoute('tagGroups'); + const c = this.get('tagGroups.model'); + return this.get('model').destroy().then(() => { + c.removeObject(this.get('model')); + this.transitionToRoute('tagGroups'); }); } } diff --git a/app/assets/javascripts/discourse/controllers/tag-groups.js.es6 b/app/assets/javascripts/discourse/controllers/tag-groups.js.es6 index 4e52a8ce47..d4785f36aa 100644 --- a/app/assets/javascripts/discourse/controllers/tag-groups.js.es6 +++ b/app/assets/javascripts/discourse/controllers/tag-groups.js.es6 @@ -1,6 +1,6 @@ -export default Ember.ArrayController.extend({ +export default Ember.Controller.extend({ actions: { - selectTagGroup: function(tagGroup) { + selectTagGroup(tagGroup) { if (this.get('selectedItem')) { this.get('selectedItem').set('selected', false); } this.set('selectedItem', tagGroup); tagGroup.set('selected', true); @@ -8,10 +8,10 @@ export default Ember.ArrayController.extend({ this.transitionToRoute('tagGroups.show', tagGroup); }, - newTagGroup: function() { + newTagGroup() { const newTagGroup = this.store.createRecord('tag-group'); newTagGroup.set('name', I18n.t('tagging.groups.new_name')); - this.pushObject(newTagGroup); + this.get('model').pushObject(newTagGroup); this.send('selectTagGroup', newTagGroup); } } diff --git a/app/assets/javascripts/discourse/controllers/topic-bulk-actions.js.es6 b/app/assets/javascripts/discourse/controllers/topic-bulk-actions.js.es6 index e1c710115c..49c5e1d7fb 100644 --- a/app/assets/javascripts/discourse/controllers/topic-bulk-actions.js.es6 +++ b/app/assets/javascripts/discourse/controllers/topic-bulk-actions.js.es6 @@ -17,7 +17,7 @@ addBulkButton('unlistTopics', 'unlist_topics'); addBulkButton('showTagTopics', 'change_tags'); // Modal for performing bulk actions on topics -export default Ember.ArrayController.extend(ModalFunctionality, { +export default Ember.Controller.extend(ModalFunctionality, { tags: null, buttonRows: null, diff --git a/app/assets/javascripts/discourse/controllers/topic.js.es6 b/app/assets/javascripts/discourse/controllers/topic.js.es6 index cf634b9832..c6af25b50b 100644 --- a/app/assets/javascripts/discourse/controllers/topic.js.es6 +++ b/app/assets/javascripts/discourse/controllers/topic.js.es6 @@ -24,6 +24,7 @@ export default Ember.Controller.extend(SelectedPostsCount, BufferedContent, { retrying: false, userTriggeredProgress: null, _progressIndex: null, + hasScrolled: null, topicDelegated: [ 'toggleMultiSelect', diff --git a/app/assets/javascripts/discourse/controllers/user-badges.js.es6 b/app/assets/javascripts/discourse/controllers/user-badges.js.es6 index 76b2a05cd9..3089a07377 100644 --- a/app/assets/javascripts/discourse/controllers/user-badges.js.es6 +++ b/app/assets/javascripts/discourse/controllers/user-badges.js.es6 @@ -1,20 +1,6 @@ -export default Ember.ArrayController.extend({ - needs: ["user"], - user: Em.computed.alias("controllers.user.model"), - sortProperties: ['badge.badge_type.sort_order', 'badge.name'], - orderBy: function(ub1, ub2){ - var sr1 = ub1.get('badge.badge_type.sort_order'); - var sr2 = ub2.get('badge.badge_type.sort_order'); - - - if(sr1 > sr2) { - return -1; - } - - if(sr2 > sr1) { - return 1; - } - - return ub1.get('badge.name') < ub2.get('badge.name') ? -1 : 1; - } +export default Ember.Controller.extend({ + user: Ember.inject.controller(), + username: Ember.computed.alias('user.model.username_lower'), + sortedBadges: Ember.computed.sort('model', 'badgeSortOrder'), + badgeSortOrder: ['badge.badge_type.sort_order', 'badge.name'], }); diff --git a/app/assets/javascripts/discourse/controllers/user-notifications.js.es6 b/app/assets/javascripts/discourse/controllers/user-notifications.js.es6 index c185d2c29a..44b25f1043 100644 --- a/app/assets/javascripts/discourse/controllers/user-notifications.js.es6 +++ b/app/assets/javascripts/discourse/controllers/user-notifications.js.es6 @@ -1,12 +1,12 @@ import { ajax } from 'discourse/lib/ajax'; import { default as computed, observes } from 'ember-addons/ember-computed-decorators'; -export default Ember.ArrayController.extend({ - needs: ['application'], +export default Ember.Controller.extend({ + application: Ember.inject.controller(), @observes('model.canLoadMore') _showFooter() { - this.set("controllers.application.showFooter", !this.get("model.canLoadMore")); + this.set("application.showFooter", !this.get("model.canLoadMore")); }, @computed('model.content.length') @@ -16,11 +16,9 @@ export default Ember.ArrayController.extend({ @computed('model.content.@each.read') allNotificationsRead() { - return !this.get('model.content').some((notification) => !notification.get('read')); + return !this.get('model.content').some(notification => !notification.get('read')); }, - currentPath: Em.computed.alias('controllers.application.currentPath'), - actions: { resetNew() { ajax('/notifications/mark-read', { method: 'PUT' }).then(() => { diff --git a/app/assets/javascripts/discourse/templates/discovery/categories.hbs b/app/assets/javascripts/discourse/templates/discovery/categories.hbs index 70f1e8e387..ae53e4cc78 100644 --- a/app/assets/javascripts/discourse/templates/discovery/categories.hbs +++ b/app/assets/javascripts/discourse/templates/discovery/categories.hbs @@ -1,6 +1,6 @@ {{#discovery-categories refresh="refresh"}} - {{component controller.categoryPageStyle + {{component categoryPageStyle categories=model.categories - latestTopicOnly=controller.latestTopicOnly + latestTopicOnly=latestTopicOnly topics=model.topics}} {{/discovery-categories}} diff --git a/app/assets/javascripts/discourse/templates/modal/topic-bulk-actions.hbs b/app/assets/javascripts/discourse/templates/modal/topic-bulk-actions.hbs index 330de1ad81..3e4734e9da 100644 --- a/app/assets/javascripts/discourse/templates/modal/topic-bulk-actions.hbs +++ b/app/assets/javascripts/discourse/templates/modal/topic-bulk-actions.hbs @@ -1,6 +1,6 @@ - <%= submit_tag(t('first_installation.register.button'), class: 'wizard-btn primary') %> + <%= submit_tag(t('finish_installation.register.button'), class: 'wizard-btn primary') %> <%- end %> <%- else -%> -

    <%= raw(t 'first_installation.register.no_emails') %>

    +

    <%= raw(t 'finish_installation.register.no_emails') %>

    <%- end %>
:{{e.name}}: