From 0c11caf796e65a2edf94f1729302c0d547616eef Mon Sep 17 00:00:00 2001 From: Sam Date: Thu, 3 Jan 2019 08:16:33 +1100 Subject: [PATCH 001/157] UX: remove gray background from lighbox This avoids a 3 way transition when loading images - grey - ultra low res - full image Instead we go - ultra low res - full image --- app/assets/stylesheets/common/base/lightbox.scss | 1 - 1 file changed, 1 deletion(-) diff --git a/app/assets/stylesheets/common/base/lightbox.scss b/app/assets/stylesheets/common/base/lightbox.scss index 1c1e46ade0..a83bab747c 100644 --- a/app/assets/stylesheets/common/base/lightbox.scss +++ b/app/assets/stylesheets/common/base/lightbox.scss @@ -2,7 +2,6 @@ position: relative; display: inline-block; overflow: hidden; - background: $primary-low; &:hover .meta { opacity: 0.9; transition: opacity 0.5s; From 49fbedc445da74fae26b93ed97d1b45adfcb9f0e Mon Sep 17 00:00:00 2001 From: Jeff Wong Date: Wed, 2 Jan 2019 14:18:14 -0800 Subject: [PATCH 002/157] FIX: make full height menu and cloak respect custom headers (#6845) --- .../discourse/components/site-header.js.es6 | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/discourse/components/site-header.js.es6 b/app/assets/javascripts/discourse/components/site-header.js.es6 index ab765a065c..a0dd8a2f9a 100644 --- a/app/assets/javascripts/discourse/components/site-header.js.es6 +++ b/app/assets/javascripts/discourse/components/site-header.js.es6 @@ -367,7 +367,7 @@ const SiteHeaderComponent = MountWidget.extend(Docking, PanEvents, { $headerCloak.show(); } - const menuTop = this.site.mobileView ? 0 : headerHeight(); + const menuTop = this.site.mobileView ? headerTop() : headerHeight(); let height; const winHeightOffset = 16; @@ -386,6 +386,7 @@ const SiteHeaderComponent = MountWidget.extend(Docking, PanEvents, { } if (style.top !== menuTop + "px" || style.height !== height) { $panel.css({ top: menuTop + "px", height }); + $(".header-cloak").css({ top: menuTop + "px" }); } $("body").removeClass("drop-down-mode"); } @@ -434,3 +435,10 @@ export function headerHeight() { $header.outerHeight() + headerOffsetTop - $(window).scrollTop() ); } + +export function headerTop() { + const $header = $("header.d-header"); + const headerOffset = $header.offset(); + const headerOffsetTop = headerOffset ? headerOffset.top : 0; + return parseInt(headerOffsetTop - $(window).scrollTop()); +} From 4af7471ead2375e4346fc9509dfd2505845e1f52 Mon Sep 17 00:00:00 2001 From: Kris Date: Wed, 2 Jan 2019 17:54:22 -0500 Subject: [PATCH 003/157] Better contrast ratio match between dark and light themes --- app/assets/stylesheets/common/base/emoji.scss | 8 ++++---- app/assets/stylesheets/common/foundation/variables.scss | 6 +++--- app/assets/stylesheets/common/select-kit/combo-box.scss | 4 ++-- .../common/select-kit/dropdown-select-box.scss | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/app/assets/stylesheets/common/base/emoji.scss b/app/assets/stylesheets/common/base/emoji.scss index df07721f59..d931856bfe 100644 --- a/app/assets/stylesheets/common/base/emoji.scss +++ b/app/assets/stylesheets/common/base/emoji.scss @@ -20,7 +20,7 @@ sup img.emoji { height: 300px; color: dark-light-choose(darken($primary, 40%), blend-primary-secondary(90%)); background-color: $secondary; - border: 1px solid dark-light-diff($primary, $secondary, 90%, -60%); + border: 1px solid $primary-low; } .emoji-picker .categories-column { @@ -29,7 +29,7 @@ sup img.emoji { flex: 1 0 0; align-items: center; justify-content: space-between; - border-right: 1px solid dark-light-diff($primary, $secondary, 90%, -60%); + border-right: 1px solid $primary-low; min-width: 36px; } @@ -102,7 +102,7 @@ sup img.emoji { align-items: center; display: flex; justify-content: space-between; - border-top: 1px solid dark-light-diff($primary, $secondary, 90%, -60%); + border-top: 1px solid $primary-low; } .emoji-picker .info { @@ -202,7 +202,7 @@ sup img.emoji { .emoji-picker .filter { background-color: none; - border-bottom: 1px solid dark-light-diff($primary, $secondary, 90%, -60%); + border-bottom: 1px solid $primary-low; padding: 5px; display: flex; position: relative; diff --git a/app/assets/stylesheets/common/foundation/variables.scss b/app/assets/stylesheets/common/foundation/variables.scss index 114811ef58..e0da4fcdaf 100644 --- a/app/assets/stylesheets/common/foundation/variables.scss +++ b/app/assets/stylesheets/common/foundation/variables.scss @@ -195,14 +195,14 @@ $box-shadow: ( // standard color transformations, use these if possible, and add any new dark-light-diffs here //primary -$primary-very-low: dark-light-diff($primary, $secondary, 97%, -80%); -$primary-low: dark-light-diff($primary, $secondary, 90%, -65%); +$primary-very-low: dark-light-diff($primary, $secondary, 97%, -82%); +$primary-low: dark-light-diff($primary, $secondary, 90%, -78%); $primary-low-mid: dark-light-diff($primary, $secondary, 70%, -45%); $primary-medium: dark-light-diff($primary, $secondary, 50%, -35%); $primary-high: dark-light-diff($primary, $secondary, 30%, -25%); //header_primary -$header_primary-low: dark-light-diff($header_primary, $secondary, 90%, -65%); +$header_primary-low: dark-light-diff($header_primary, $secondary, 90%, -78%); $header_primary-medium: dark-light-diff($header_primary, $secondary, 50%, -35%); $header_primary-high: dark-light-diff( $header_primary, diff --git a/app/assets/stylesheets/common/select-kit/combo-box.scss b/app/assets/stylesheets/common/select-kit/combo-box.scss index 102a6da1db..8b6b4d731e 100644 --- a/app/assets/stylesheets/common/select-kit/combo-box.scss +++ b/app/assets/stylesheets/common/select-kit/combo-box.scss @@ -19,8 +19,8 @@ .select-kit-filter { padding: $input-padding; - border-top: 1px solid dark-light-diff($primary, $secondary, 90%, -60%); - border-bottom: 1px solid dark-light-diff($primary, $secondary, 90%, -60%); + border-top: 1px solid $primary-low; + border-bottom: 1px solid $primary-low; .spinner { flex: 0 0 auto; diff --git a/app/assets/stylesheets/common/select-kit/dropdown-select-box.scss b/app/assets/stylesheets/common/select-kit/dropdown-select-box.scss index 6c148f67fb..53c37d640c 100644 --- a/app/assets/stylesheets/common/select-kit/dropdown-select-box.scss +++ b/app/assets/stylesheets/common/select-kit/dropdown-select-box.scss @@ -14,7 +14,7 @@ } .select-kit-body { - border: 1px solid dark-light-diff($primary, $secondary, 90%, -60%); + border: 1px solid $primary-low; background-clip: padding-box; box-shadow: shadow("dropdown"); max-width: 300px; From a953b71797f9caf36f86ad361dcf8dff0f1845ec Mon Sep 17 00:00:00 2001 From: Penar Musaraj Date: Wed, 2 Jan 2019 19:07:36 -0500 Subject: [PATCH 004/157] FEATURE: allow custom HighlightJS languages Adds pluginApi function that allows themes and plugins to register languages for HighlightJS. --- .../discourse/lib/highlight-syntax.js.es6 | 18 ++++++++++++++++- .../discourse/lib/plugin-api.js.es6 | 20 ++++++++++++++++++- 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/discourse/lib/highlight-syntax.js.es6 b/app/assets/javascripts/discourse/lib/highlight-syntax.js.es6 index bfc24afb4d..48543dcd1d 100644 --- a/app/assets/javascripts/discourse/lib/highlight-syntax.js.es6 +++ b/app/assets/javascripts/discourse/lib/highlight-syntax.js.es6 @@ -1,4 +1,5 @@ /*global hljs:true */ +let _moreLanguages = []; import loadScript from "discourse/lib/load-script"; @@ -14,6 +15,21 @@ export default function highlightSyntax($elem) { $(selector, $elem).each(function(i, e) { $(e).removeClass("lang-auto"); - loadScript(path).then(() => hljs.highlightBlock(e)); + loadScript(path).then(() => { + customHighlightJSLanguages(); + hljs.highlightBlock(e); + }); + }); +} + +export function registerHighlightJSLanguage(name, fn) { + _moreLanguages.push({ name: name, fn: fn }); +} + +function customHighlightJSLanguages() { + _moreLanguages.forEach(l => { + if (hljs.getLanguage(l.name) === undefined) { + hljs.registerLanguage(l.name, l.fn); + } }); } diff --git a/app/assets/javascripts/discourse/lib/plugin-api.js.es6 b/app/assets/javascripts/discourse/lib/plugin-api.js.es6 index ba10a5c2d9..ecaff63a63 100644 --- a/app/assets/javascripts/discourse/lib/plugin-api.js.es6 +++ b/app/assets/javascripts/discourse/lib/plugin-api.js.es6 @@ -4,6 +4,7 @@ import ComposerEditor from "discourse/components/composer-editor"; import DiscourseBanner from "discourse/components/discourse-banner"; import { addButton } from "discourse/widgets/post-menu"; import { includeAttributes } from "discourse/lib/transform-post"; +import { registerHighlightJSLanguage } from "discourse/lib/highlight-syntax"; import { addToolbarCallback } from "discourse/components/d-editor"; import { addWidgetCleanCallback } from "discourse/components/mount-widget"; import { @@ -40,7 +41,7 @@ import Sharing from "discourse/lib/sharing"; import { addComposerUploadHandler } from "discourse/components/composer-editor"; // If you add any methods to the API ensure you bump up this number -const PLUGIN_API_VERSION = "0.8.26"; +const PLUGIN_API_VERSION = "0.8.27"; class PluginApi { constructor(version, container) { @@ -790,6 +791,23 @@ class PluginApi { replaceCategoryLinkRenderer(fn) { replaceCategoryLinkRenderer(fn); } + + /** + * Registers custom languages for use with HighlightJS. + * + * See https://highlightjs.readthedocs.io/en/latest/language-guide.html + * for instructions on how to define a new language for HighlightJS. + * Build minified language file by running "node tools/build.js -t cdn" in the HighlightJS repo + * and use the minified output as the registering function. + * + * Example: + * + * let aLang = function(e){return{cI:!1,c:[{bK:"GET HEAD PUT POST DELETE PATCH",e:"$",c:[{cN:"title",b:"/?.+"}]},{b:"^{$",e:"^}$",sL:"json"}]}} + * api.registerHighlightJSLanguage("kibana", aLang); + **/ + registerHighlightJSLanguage(name, fn) { + registerHighlightJSLanguage(name, fn); + } } let _pluginv01; From bea7a8a4d1ab28a52540551f8c7fa909be7bc8a0 Mon Sep 17 00:00:00 2001 From: Arpit Jalan Date: Thu, 3 Jan 2019 07:46:05 +0530 Subject: [PATCH 005/157] FIX: show accurate error message based on invite token validity --- app/controllers/invites_controller.rb | 21 +++++++++++++-------- spec/requests/invites_controller_spec.rb | 2 +- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/app/controllers/invites_controller.rb b/app/controllers/invites_controller.rb index 77dba98e5d..c888cffab4 100644 --- a/app/controllers/invites_controller.rb +++ b/app/controllers/invites_controller.rb @@ -19,16 +19,21 @@ class InvitesController < ApplicationController invite = Invite.find_by(invite_key: params[:id]) - if invite.present? && !invite.redeemed? - store_preloaded("invite_info", MultiJson.dump( - invited_by: UserNameSerializer.new(invite.invited_by, scope: guardian, root: false), - email: invite.email, - username: UserNameSuggester.suggest(invite.email)) - ) + if invite.present? + if !invite.redeemed? + store_preloaded("invite_info", MultiJson.dump( + invited_by: UserNameSerializer.new(invite.invited_by, scope: guardian, root: false), + email: invite.email, + username: UserNameSuggester.suggest(invite.email)) + ) - render layout: 'application' + render layout: 'application' + else + flash.now[:error] = I18n.t('invite.not_found_template', site_name: SiteSetting.title, base_url: Discourse.base_url) + render layout: 'no_ember' + end else - flash.now[:error] = I18n.t('invite.not_found_template', site_name: SiteSetting.title, base_url: Discourse.base_url) + flash.now[:error] = I18n.t('invite.not_found') render layout: 'no_ember' end end diff --git a/spec/requests/invites_controller_spec.rb b/spec/requests/invites_controller_spec.rb index d51a3013ed..c2557e5c7c 100644 --- a/spec/requests/invites_controller_spec.rb +++ b/spec/requests/invites_controller_spec.rb @@ -12,7 +12,7 @@ describe InvitesController do body = response.body expect(body).to_not have_tag(:script, with: { src: '/assets/application.js' }) - expect(CGI.unescapeHTML(body)).to include(I18n.t('invite.not_found_template', site_name: SiteSetting.title, base_url: Discourse.base_url)) + expect(CGI.unescapeHTML(body)).to include(I18n.t('invite.not_found', site_name: SiteSetting.title, base_url: Discourse.base_url)) end it "renders the accept invite page if invite exists" do From 570877da3c39707c2101c3510fab1509fb8ba3e2 Mon Sep 17 00:00:00 2001 From: Sam Date: Thu, 3 Jan 2019 17:07:30 +1100 Subject: [PATCH 006/157] FEATURE: store thumbnail algorithm version in optimized image table Previously we had no idea what algorithm generated thumbnails, this starts tracking the version. We also bumped up the version to force all optimized images to be generated. This is important cause we recently introduced pngquant which results in much smaller images. --- app/models/optimized_image.rb | 7 ++--- ...3051737_add_version_to_optimized_images.rb | 5 ++++ spec/models/optimized_image_spec.rb | 26 +++++++++++++++++++ 3 files changed, 35 insertions(+), 3 deletions(-) create mode 100644 db/migrate/20190103051737_add_version_to_optimized_images.rb diff --git a/app/models/optimized_image.rb b/app/models/optimized_image.rb index 5e5e8e4191..5cc2484654 100644 --- a/app/models/optimized_image.rb +++ b/app/models/optimized_image.rb @@ -7,7 +7,7 @@ class OptimizedImage < ActiveRecord::Base belongs_to :upload # BUMP UP if optimized image algorithm changes - VERSION = 1 + VERSION = 2 def self.lock(upload_id, width, height) @hostname ||= `hostname`.strip rescue "unknown" @@ -43,7 +43,7 @@ class OptimizedImage < ActiveRecord::Base thumbnail = find_by(upload_id: upload.id, width: width, height: height) # correct bad thumbnail if needed - if thumbnail && thumbnail.url.blank? + if thumbnail && (thumbnail.url.blank? || thumbnail.version != VERSION) thumbnail.destroy! thumbnail = nil end @@ -94,7 +94,8 @@ class OptimizedImage < ActiveRecord::Base width: width, height: height, url: "", - filesize: File.size(temp_path) + filesize: File.size(temp_path), + version: VERSION ) # store the optimized image and update its url diff --git a/db/migrate/20190103051737_add_version_to_optimized_images.rb b/db/migrate/20190103051737_add_version_to_optimized_images.rb new file mode 100644 index 0000000000..88d2fb9958 --- /dev/null +++ b/db/migrate/20190103051737_add_version_to_optimized_images.rb @@ -0,0 +1,5 @@ +class AddVersionToOptimizedImages < ActiveRecord::Migration[5.2] + def change + add_column :optimized_images, :version, :integer + end +end diff --git a/spec/models/optimized_image_spec.rb b/spec/models/optimized_image_spec.rb index 35e95cd9be..4c832a8f4b 100644 --- a/spec/models/optimized_image_spec.rb +++ b/spec/models/optimized_image_spec.rb @@ -201,6 +201,32 @@ describe OptimizedImage do describe ".create_for" do + context "versioning" do + let(:filename) { 'logo.png' } + let(:file) { file_from_fixtures(filename) } + + it "is able to update optimized images on version change" do + upload = UploadCreator.new(file, filename).create_for(Discourse.system_user.id) + optimized = OptimizedImage.create_for(upload, 10, 10) + + expect(optimized.version).to eq(OptimizedImage::VERSION) + + optimized_again = OptimizedImage.create_for(upload, 10, 10) + expect(optimized_again.id).to eq(optimized.id) + + optimized.update_columns(version: nil) + old_id = optimized.id + + optimized_new = OptimizedImage.create_for(upload, 10, 10) + + expect(optimized_new.id).not_to eq(old_id) + + # cleanup (which transaction rollback may miss) + optimized_new.destroy + upload.destroy + end + end + it "is able to 'optimize' an svg" do # we don't really optimize anything, we simply copy # but at least this confirms this actually works From 30a1d29a7eea11f949911bef5768618b89ac1753 Mon Sep 17 00:00:00 2001 From: Sam Date: Thu, 3 Jan 2019 17:19:38 +1100 Subject: [PATCH 007/157] FEATURE: force rebake of all posts with images This was done to pick up 3 changes 1. New pngquant which will result in much smaller images 2. Placeholder images which are missing from old posts 3. Retina images missing from old posts Also picks up on Image Magick upgrade which slightly alters resize algorithm. Rebake trickles per: `rebake_old_posts_count` site setting. (100 per 15 minutes) --- ...03060819_force_rebake_on_posts_with_images.rb | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 db/migrate/20190103060819_force_rebake_on_posts_with_images.rb diff --git a/db/migrate/20190103060819_force_rebake_on_posts_with_images.rb b/db/migrate/20190103060819_force_rebake_on_posts_with_images.rb new file mode 100644 index 0000000000..3837aae59e --- /dev/null +++ b/db/migrate/20190103060819_force_rebake_on_posts_with_images.rb @@ -0,0 +1,16 @@ +class ForceRebakeOnPostsWithImages < ActiveRecord::Migration[5.2] + def up + + # commit message has more info: + # Picking up changes with pngquant, placeholder image, new image magick, retina images + + execute <<~SQL + UPDATE posts SET baked_version = 0 + WHERE id IN (SELECT post_id FROM post_uploads) + SQL + end + + def down + # no op, does not really matter + end +end From 993f847a2c6f40fbbb9c2bd2ca4d483dbb46b5bf Mon Sep 17 00:00:00 2001 From: Arpit Jalan Date: Wed, 2 Jan 2019 18:01:50 +0530 Subject: [PATCH 008/157] FIX: trim trailing slash from topic links --- app/models/topic_link.rb | 2 +- spec/models/topic_link_spec.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/models/topic_link.rb b/app/models/topic_link.rb index a7be7fa00c..b8b72b7109 100644 --- a/app/models/topic_link.rb +++ b/app/models/topic_link.rb @@ -205,7 +205,7 @@ SQL reflected_post = Post.find_by(topic_id: topic_id, post_number: post_number.to_i) end - url = url[0...TopicLink.max_url_length] + url = url[0...TopicLink.max_url_length]&.chomp("/") return nil if parsed && parsed.host && parsed.host.length > TopicLink.max_domain_length unless TopicLink.exists?(topic_id: post.topic_id, post_id: post.id, url: url) diff --git a/spec/models/topic_link_spec.rb b/spec/models/topic_link_spec.rb index 8c7cea0a4a..b45861ceb1 100644 --- a/spec/models/topic_link_spec.rb +++ b/spec/models/topic_link_spec.rb @@ -41,7 +41,7 @@ describe TopicLink do it 'works' do expect(topic.topic_links.pluck(:url)).to contain_exactly( - "http://a.com/", + "http://a.com", "https://b.com/b", "//b.com/#{'a' * 500}"[0...TopicLink.max_url_length] ) From c666ef556d1a7d2f154794bf77e5f42f2475910a Mon Sep 17 00:00:00 2001 From: Guo Xiang Tan Date: Thu, 3 Jan 2019 15:34:10 +0800 Subject: [PATCH 009/157] Fix the build. Ref 570877da3c39707c2101c3510fab1509fb8ba3e2 --- spec/components/cooked_post_processor_spec.rb | 13 +++---- spec/components/file_store/s3_store_spec.rb | 36 ++++++++++++------- spec/requests/user_avatars_controller_spec.rb | 3 +- 3 files changed, 33 insertions(+), 19 deletions(-) diff --git a/spec/components/cooked_post_processor_spec.rb b/spec/components/cooked_post_processor_spec.rb index de1b4257b8..a8bedc1a73 100644 --- a/spec/components/cooked_post_processor_spec.rb +++ b/spec/components/cooked_post_processor_spec.rb @@ -273,7 +273,8 @@ describe CookedPostProcessor do upload_id: upload.id, sha1: SecureRandom.hex, extension: '.jpg', - filesize: 500 + filesize: 500, + version: OptimizedImage::VERSION ) # fake 3x optimized image, we lose 2 pixels here over original due to rounding on downsize @@ -407,7 +408,7 @@ describe CookedPostProcessor do FileStore::BaseStore.any_instance.expects(:get_depth_for).returns(0) cpp.post_process_images - expect(cpp.html).to match_html "

+ expect(cpp.html).to match_html "

" expect(cpp).to be_dirty @@ -500,7 +501,7 @@ describe CookedPostProcessor do it "crops the image" do cpp.post_process_images - expect(cpp.html).to match_html "

+ expect(cpp.html).to match_html "

" expect(cpp).to be_dirty @@ -531,7 +532,7 @@ describe CookedPostProcessor do it "generates overlay information" do cpp.post_process_images - expect(cpp.html).to match_html "

+ expect(cpp.html).to match_html "

" expect(cpp).to be_dirty @@ -540,7 +541,7 @@ describe CookedPostProcessor do it "should escape the filename" do upload.update_attributes!(original_filename: ">.png") cpp.post_process_images - expect(cpp.html).to match_html "

+ expect(cpp.html).to match_html "

" end @@ -566,7 +567,7 @@ describe CookedPostProcessor do it "generates overlay information" do cpp.post_process_images - expect(cpp.html).to match_html "

+ expect(cpp.html).to match_html "

" expect(cpp).to be_dirty diff --git a/spec/components/file_store/s3_store_spec.rb b/spec/components/file_store/s3_store_spec.rb index 53db46819e..993e3d315c 100644 --- a/spec/components/file_store/s3_store_spec.rb +++ b/spec/components/file_store/s3_store_spec.rb @@ -176,21 +176,25 @@ describe FileStore::S3Store do end describe "#remove_optimized_image" do - let(:optimized_image) do - Fabricate(:optimized_image, - url: "//s3-upload-bucket.s3.dualstack.us-west-1.amazonaws.com/optimized/1X/#{upload.sha1}_1_100x200.png", - upload: upload + let(:optimized_image) { Fabricate(:optimized_image, upload: upload) } + + let(:image_path) do + FileStore::BaseStore.new.get_path_for_optimized_image(optimized_image) + end + + before do + optimized_image.update!( + url: "//s3-upload-bucket.s3.dualstack.us-west-1.amazonaws.com#{image_path}" ) end it "removes the file from s3 with the right paths" do - store.expects(:get_depth_for).with(optimized_image.upload.id).returns(0) s3_helper.expects(:s3_bucket).returns(s3_bucket).at_least_once s3_object = stub - s3_bucket.expects(:object).with("tombstone/optimized/1X/#{upload.sha1}_1_100x200.png").returns(s3_object) - s3_object.expects(:copy_from).with(copy_source: "s3-upload-bucket/optimized/1X/#{upload.sha1}_1_100x200.png") - s3_bucket.expects(:object).with("optimized/1X/#{upload.sha1}_1_100x200.png").returns(s3_object) + s3_bucket.expects(:object).with("tombstone/#{image_path}").returns(s3_object) + s3_object.expects(:copy_from).with(copy_source: "s3-upload-bucket/#{image_path}") + s3_bucket.expects(:object).with("#{image_path}").returns(s3_object) s3_object.expects(:delete) store.remove_optimized_image(optimized_image) @@ -202,13 +206,21 @@ describe FileStore::S3Store do end it "removes the file from s3 with the right paths" do - store.expects(:get_depth_for).with(optimized_image.upload.id).returns(0) s3_helper.expects(:s3_bucket).returns(s3_bucket).at_least_once s3_object = stub - s3_bucket.expects(:object).with("discourse-uploads/tombstone/optimized/1X/#{upload.sha1}_1_100x200.png").returns(s3_object) - s3_object.expects(:copy_from).with(copy_source: "s3-upload-bucket/discourse-uploads/optimized/1X/#{upload.sha1}_1_100x200.png") - s3_bucket.expects(:object).with("discourse-uploads/optimized/1X/#{upload.sha1}_1_100x200.png").returns(s3_object) + s3_bucket.expects(:object) + .with("discourse-uploads/tombstone/#{image_path}") + .returns(s3_object) + + s3_object.expects(:copy_from).with( + copy_source: "s3-upload-bucket/discourse-uploads/#{image_path}" + ) + + s3_bucket.expects(:object).with( + "discourse-uploads/#{image_path}" + ).returns(s3_object) + s3_object.expects(:delete) store.remove_optimized_image(optimized_image) diff --git a/spec/requests/user_avatars_controller_spec.rb b/spec/requests/user_avatars_controller_spec.rb index 33a63799d7..7341cd6b1a 100644 --- a/spec/requests/user_avatars_controller_spec.rb +++ b/spec/requests/user_avatars_controller_spec.rb @@ -84,7 +84,8 @@ describe UserAvatarsController do upload: upload, width: 98, height: 98, - url: "//test.s3.dualstack.us-east-1.amazonaws.com/something/else" + url: "//test.s3.dualstack.us-east-1.amazonaws.com/something/else", + version: OptimizedImage::VERSION ) user = Fabricate(:user, uploaded_avatar_id: upload.id) From d1597683f378fd151483d894b1e06cf5be9d6eb7 Mon Sep 17 00:00:00 2001 From: Arpit Jalan Date: Thu, 3 Jan 2019 17:29:22 +0530 Subject: [PATCH 010/157] Revert "FIX: trim trailing slash from topic links" This reverts commit 993f847a2c6f40fbbb9c2bd2ca4d483dbb46b5bf. There is an edge case where the link click redirect fails when the URL has trailing slash. Need to figure out a better fix for this. --- app/models/topic_link.rb | 2 +- spec/models/topic_link_spec.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/models/topic_link.rb b/app/models/topic_link.rb index b8b72b7109..a7be7fa00c 100644 --- a/app/models/topic_link.rb +++ b/app/models/topic_link.rb @@ -205,7 +205,7 @@ SQL reflected_post = Post.find_by(topic_id: topic_id, post_number: post_number.to_i) end - url = url[0...TopicLink.max_url_length]&.chomp("/") + url = url[0...TopicLink.max_url_length] return nil if parsed && parsed.host && parsed.host.length > TopicLink.max_domain_length unless TopicLink.exists?(topic_id: post.topic_id, post_id: post.id, url: url) diff --git a/spec/models/topic_link_spec.rb b/spec/models/topic_link_spec.rb index b45861ceb1..8c7cea0a4a 100644 --- a/spec/models/topic_link_spec.rb +++ b/spec/models/topic_link_spec.rb @@ -41,7 +41,7 @@ describe TopicLink do it 'works' do expect(topic.topic_links.pluck(:url)).to contain_exactly( - "http://a.com", + "http://a.com/", "https://b.com/b", "//b.com/#{'a' * 500}"[0...TopicLink.max_url_length] ) From 4aa399275775758d8afcc3e9ebfd099a6debad1d Mon Sep 17 00:00:00 2001 From: Gerhard Schlager Date: Thu, 3 Jan 2019 13:46:21 +0100 Subject: [PATCH 011/157] Fix Travis build Travis doesn't provide the latest Rubygems yet and it doesn't make sense to use Bundler 2 until we've upgraded anyway. --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 08be3437dc..6e2677a58a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -49,7 +49,7 @@ before_install: - wget -qO- https://raw.githubusercontent.com/discourse/discourse_docker/master/image/base/install-pngquant | sudo sh - nvm install node - node --version - - gem install bundler + - gem install bundler -v 1.17.3 - git clone --depth=1 https://github.com/discourse/discourse-backup-uploads-to-s3.git plugins/discourse-backup-uploads-to-s3 - git clone --depth=1 https://github.com/discourse/discourse-spoiler-alert.git plugins/discourse-spoiler-alert - git clone --depth=1 https://github.com/discourse/discourse-cakeday.git plugins/discourse-cakeday From b089ac1537d47624fe690eacc14106c16138a3ae Mon Sep 17 00:00:00 2001 From: Gerhard Schlager Date: Thu, 3 Jan 2019 14:13:36 +0100 Subject: [PATCH 012/157] FIX: Posting without bump raised an error for TL4 --- lib/guardian/post_guardian.rb | 2 +- spec/requests/posts_controller_spec.rb | 10 +++++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/lib/guardian/post_guardian.rb b/lib/guardian/post_guardian.rb index d30db1972d..0163496b82 100644 --- a/lib/guardian/post_guardian.rb +++ b/lib/guardian/post_guardian.rb @@ -267,6 +267,6 @@ module PostGuardian end def can_skip_bump? - is_staff? + is_staff? || @user.has_trust_level?(TrustLevel[4]) end end diff --git a/spec/requests/posts_controller_spec.rb b/spec/requests/posts_controller_spec.rb index 8290e1b445..63f9ba875f 100644 --- a/spec/requests/posts_controller_spec.rb +++ b/spec/requests/posts_controller_spec.rb @@ -1160,10 +1160,18 @@ describe PostsController do include_examples "it works" end + context "TL4 users" do + before do + sign_in(Fabricate(:trust_level_4)) + end + + include_examples "it works" + end + context "users" do let(:topic) { Fabricate(:topic) } - [:user, :trust_level_4].each do |user| + [:user].each do |user| it "will raise an error for #{user}" do sign_in(Fabricate(user)) post "/posts.json", params: { From cb317430a1c27f4d45f3a53efbcd29c77ea8f9e0 Mon Sep 17 00:00:00 2001 From: David Taylor Date: Thu, 3 Jan 2019 17:52:31 +0000 Subject: [PATCH 013/157] Revert "FEATURE: force rebake of all posts with images" This reverts commit 30a1d29a7eea11f949911bef5768618b89ac1753 due to performance issues on large clusters --- ...03060819_force_rebake_on_posts_with_images.rb | 16 ---------------- 1 file changed, 16 deletions(-) delete mode 100644 db/migrate/20190103060819_force_rebake_on_posts_with_images.rb diff --git a/db/migrate/20190103060819_force_rebake_on_posts_with_images.rb b/db/migrate/20190103060819_force_rebake_on_posts_with_images.rb deleted file mode 100644 index 3837aae59e..0000000000 --- a/db/migrate/20190103060819_force_rebake_on_posts_with_images.rb +++ /dev/null @@ -1,16 +0,0 @@ -class ForceRebakeOnPostsWithImages < ActiveRecord::Migration[5.2] - def up - - # commit message has more info: - # Picking up changes with pngquant, placeholder image, new image magick, retina images - - execute <<~SQL - UPDATE posts SET baked_version = 0 - WHERE id IN (SELECT post_id FROM post_uploads) - SQL - end - - def down - # no op, does not really matter - end -end From 385829d7be7cc85b2eb561b7a2640768bc7710de Mon Sep 17 00:00:00 2001 From: Vinoth Kannan Date: Fri, 4 Jan 2019 00:29:13 +0530 Subject: [PATCH 014/157] FEATURE: Display error message when category restriction is applied for tags --- .../components/mini-tag-chooser.js.es6 | 1 + .../select-kit/components/select-kit.js.es6 | 4 ++- .../select-kit/components/tag-chooser.js.es6 | 2 ++ .../common/select-kit/select-kit.scss | 4 --- app/controllers/tags_controller.rb | 17 ++++++++- config/locales/server.en.yml | 5 +++ spec/requests/tags_controller_spec.rb | 35 ++++++++++++++----- 7 files changed, 54 insertions(+), 14 deletions(-) diff --git a/app/assets/javascripts/select-kit/components/mini-tag-chooser.js.es6 b/app/assets/javascripts/select-kit/components/mini-tag-chooser.js.es6 index 0b2f9a7e95..f263fc1789 100644 --- a/app/assets/javascripts/select-kit/components/mini-tag-chooser.js.es6 +++ b/app/assets/javascripts/select-kit/components/mini-tag-chooser.js.es6 @@ -182,6 +182,7 @@ export default ComboBox.extend(TagsMixin, { let results = json.results; context.set("termMatchesForbidden", json.forbidden ? true : false); + context.set("termMatchErrorMessage", json.forbidden_message); if (context.get("siteSettings.tags_sort_alphabetically")) { results = results.sort((a, b) => a.id > b.id); diff --git a/app/assets/javascripts/select-kit/components/select-kit.js.es6 b/app/assets/javascripts/select-kit/components/select-kit.js.es6 index b907e875b7..af33293301 100644 --- a/app/assets/javascripts/select-kit/components/select-kit.js.es6 +++ b/app/assets/javascripts/select-kit/components/select-kit.js.es6 @@ -277,7 +277,9 @@ export default Ember.Component.extend( collectionComputedContent.length === 0 && !isLoading ) { - return I18n.t("select_kit.no_content"); + return ( + this.get("termMatchErrorMessage") || I18n.t("select_kit.no_content") + ); } }, diff --git a/app/assets/javascripts/select-kit/components/tag-chooser.js.es6 b/app/assets/javascripts/select-kit/components/tag-chooser.js.es6 index 90c291b4de..807b86baf6 100644 --- a/app/assets/javascripts/select-kit/components/tag-chooser.js.es6 +++ b/app/assets/javascripts/select-kit/components/tag-chooser.js.es6 @@ -27,6 +27,7 @@ export default MultiSelectComponent.extend(TagsMixin, { } this.set("termMatchesForbidden", false); + this.set("termMatchErrorMessage", null); this.set("templateForRow", rowComponent => { const tag = rowComponent.get("computedContent"); @@ -117,6 +118,7 @@ export default MultiSelectComponent.extend(TagsMixin, { let results = json.results; context.set("termMatchesForbidden", json.forbidden ? true : false); + context.set("termMatchErrorMessage", json.forbidden_message); if (context.get("blacklist")) { results = results.filter(result => { diff --git a/app/assets/stylesheets/common/select-kit/select-kit.scss b/app/assets/stylesheets/common/select-kit/select-kit.scss index cd5c7576d2..a96520b2ed 100644 --- a/app/assets/stylesheets/common/select-kit/select-kit.scss +++ b/app/assets/stylesheets/common/select-kit/select-kit.scss @@ -125,10 +125,6 @@ align-items: center; justify-content: flex-start; - &.no-content { - white-space: nowrap; - } - .name { margin: 0; overflow: hidden; diff --git a/app/controllers/tags_controller.rb b/app/controllers/tags_controller.rb index 2c02fc71e3..f1e49da290 100644 --- a/app/controllers/tags_controller.rb +++ b/app/controllers/tags_controller.rb @@ -210,9 +210,24 @@ class TagsController < ::ApplicationController json_response = { results: tags } - if Tag.where_name(clean_name).exists? && !tags.find { |h| h[:id].downcase == clean_name.downcase } + if !tags.find { |h| h[:id].downcase == clean_name.downcase } && tag = Tag.where_name(clean_name).first # filter_allowed_tags determined that the tag entered is not allowed json_response[:forbidden] = params[:q] + + category_names = tag.categories.where(id: guardian.allowed_category_ids).pluck(:name) + category_names += Category.joins(tag_groups: :tags).where(id: guardian.allowed_category_ids, "tags.id": tag.id).pluck(:name) + + if category_names.present? + category_names.uniq! + json_response[:forbidden_message] = I18n.t( + "tags.forbidden.restricted_to", + count: category_names.count, + tag_name: tag.name, + category_names: category_names.join(", ") + ) + else + json_response[:forbidden_message] = I18n.t("tags.forbidden.in_this_category", tag_name: tag.name) + end end render json: json_response diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index c8e16216a4..f585ac9ea3 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -4052,6 +4052,11 @@ en: staff_tag_remove_disallowed: "The tag \"%{tag}\" may only be removed by staff." minimum_required_tags: "You must select at least %{count} tags." upload_row_too_long: "The CSV file should have one tag per line. Optionally the tag can be followed by a comma, then the tag group name." + forbidden: + in_this_category: "\"%{tag_name}\" cannot be used in this category" + restricted_to: + one: "\"%{tag_name}\" is restricted to the \"%{category_names}\" category" + other: "\"%{tag_name}\" is restricted to the following categories: %{category_names}" rss_by_tag: "Topics tagged %{tag}" finish_installation: diff --git a/spec/requests/tags_controller_spec.rb b/spec/requests/tags_controller_spec.rb index 740e628dcf..ade557a66e 100644 --- a/spec/requests/tags_controller_spec.rb +++ b/spec/requests/tags_controller_spec.rb @@ -319,14 +319,33 @@ describe TagsController do expect(json['results'].map { |j| j['id'] }).to eq(['tag', 'tag2']) end - it "can say if given tag is not allowed" do - yup, nope = Fabricate(:tag, name: 'yup'), Fabricate(:tag, name: 'nope') - category = Fabricate(:category, tags: [yup]) - get "/tags/filter/search.json", params: { q: 'nope', categoryId: category.id } - expect(response.status).to eq(200) - json = ::JSON.parse(response.body) - expect(json["results"].map { |j| j["id"] }.sort).to eq([]) - expect(json["forbidden"]).to be_present + context 'with category restriction' do + let(:yup) { Fabricate(:tag, name: 'yup') } + let(:category) { Fabricate(:category, tags: [yup]) } + + it "can say if given tag is not allowed" do + nope = Fabricate(:tag, name: 'nope') + get "/tags/filter/search.json", params: { q: nope.name, categoryId: category.id } + expect(response.status).to eq(200) + json = ::JSON.parse(response.body) + expect(json["results"].map { |j| j["id"] }.sort).to eq([]) + expect(json["forbidden"]).to be_present + expect(json["forbidden_message"]).to eq(I18n.t("tags.forbidden.in_this_category", tag_name: nope.name)) + end + + it "can say if given tag is restricted to different category" do + category + get "/tags/filter/search.json", params: { q: yup.name, categoryId: Fabricate(:category).id } + json = ::JSON.parse(response.body) + expect(json["results"].map { |j| j["id"] }.sort).to eq([]) + expect(json["forbidden"]).to be_present + expect(json["forbidden_message"]).to eq(I18n.t( + "tags.forbidden.restricted_to", + count: 1, + tag_name: yup.name, + category_names: category.name + )) + end end it "matches tags after sanitizing input" do From 05a3e3670fd6da3f0390a106605bf71ddf2f3667 Mon Sep 17 00:00:00 2001 From: Sam Date: Fri, 4 Jan 2019 08:13:06 +1100 Subject: [PATCH 015/157] FEATURE: add rake task that resets ACL on every object in S3 Some previous migrations to S3 may have bad ACLs set on objects. This introduces a new rake task (`rake s3:correct_acl`) that will reset ACL on every S3 object. Vast majority of users will never have to run it, but if you have ACL issues this is the atomic solution. --- lib/tasks/s3.rake | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/lib/tasks/s3.rake b/lib/tasks/s3.rake index 356ca55cc2..58d12e1000 100644 --- a/lib/tasks/s3.rake +++ b/lib/tasks/s3.rake @@ -101,6 +101,36 @@ def ensure_s3_configured! end end +task 's3:correct_acl' => :environment do + ensure_s3_configured! + + puts "ensuring public-read is set on every upload and optimized image" + + i = 0 + + base_url = Discourse.store.absolute_base_url + + objects = Upload.pluck(:id, :url).map { |array| array << :upload } + objects.concat(OptimizedImage.pluck(:id, :url).map { |array| array << :optimized_image }) + + puts "#{objects.length} objects found" + + objects.each do |id, url, type| + i += 1 + if !url.start_with?(base_url) + puts "Skipping #{type} #{id} since it is not stored on s3, url is #{url}" + else + key = url[(base_url.length + 1)..-1] + object = Discourse.store.s3_helper.object(key) + object.acl.put(acl: "public-read") + end + if i % 100 == 0 + puts "#{i} done" + end + end + +end + task 's3:upload_assets' => :environment do ensure_s3_configured! From e2dca641c698e3c85a07a9cf38f327a46f0f7140 Mon Sep 17 00:00:00 2001 From: Sam Date: Fri, 4 Jan 2019 08:32:09 +1100 Subject: [PATCH 016/157] handle exceptions in s3:correct_acl task We need to handle arbitrary exceptions in this task, especially since the task is not easily resumable. Simply output problem uploads as you hit them for now. --- lib/tasks/s3.rake | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/lib/tasks/s3.rake b/lib/tasks/s3.rake index 58d12e1000..5ad2de0174 100644 --- a/lib/tasks/s3.rake +++ b/lib/tasks/s3.rake @@ -120,9 +120,13 @@ task 's3:correct_acl' => :environment do if !url.start_with?(base_url) puts "Skipping #{type} #{id} since it is not stored on s3, url is #{url}" else - key = url[(base_url.length + 1)..-1] - object = Discourse.store.s3_helper.object(key) - object.acl.put(acl: "public-read") + begin + key = url[(base_url.length + 1)..-1] + object = Discourse.store.s3_helper.object(key) + object.acl.put(acl: "public-read") + rescue => e + puts "Skipping #{type} #{id} url is #{url} #{e}" + end end if i % 100 == 0 puts "#{i} done" From 70269c7c9727cf9c4d2f7d7fd4ce607257f37196 Mon Sep 17 00:00:00 2001 From: Sam Date: Fri, 4 Jan 2019 09:24:46 +1100 Subject: [PATCH 017/157] FEATURE: tighter limits on per cluster post rebakes We have the periodical job that regularly will rebake old posts. This is used to trickle in update to cooked markdown. The problem is that each rebake can issue multiple background jobs (post process and pull hotlinked images) Previously we had no per-cluster limit so cluster running 100s of sites could flood the sidekiq queue with rebake related jobs. New system introduces a hard limit of 300 rebakes per 15 minutes across a cluster to ensure the sidekiq job is not dominated by this. We also reduced `rebake_old_posts_count` to 80, which is a safer default. --- app/models/post.rb | 19 +++++++++++++++++++ config/discourse_defaults.conf | 5 +++++ config/site_settings.yml | 2 +- spec/models/post_spec.rb | 21 +++++++++++++++++++++ 4 files changed, 46 insertions(+), 1 deletion(-) diff --git a/app/models/post.rb b/app/models/post.rb index 7279a0ff27..ff6c32890b 100644 --- a/app/models/post.rb +++ b/app/models/post.rb @@ -516,13 +516,32 @@ class Post < ActiveRecord::Base end def self.rebake_old(limit) + + limiter = RateLimiter.new( + nil, + "global_periodical_rebake_limit", + GlobalSetting.max_old_rebakes_per_15_minutes, + 900, + global: true + ) + problems = [] Post.where('baked_version IS NULL OR baked_version < ?', BAKED_VERSION) .order('id desc') .limit(limit).pluck(:id).each do |id| begin + + break if !limiter.can_perform? + post = Post.find(id) post.rebake! + + begin + limiter.performed! + rescue RateLimiter::LimitExceeded + break + end + rescue => e problems << { post: post, ex: e } diff --git a/config/discourse_defaults.conf b/config/discourse_defaults.conf index 43ce40ba2d..cde2c3666d 100644 --- a/config/discourse_defaults.conf +++ b/config/discourse_defaults.conf @@ -202,3 +202,8 @@ force_anonymous_min_queue_seconds = 1 # only trigger anon if we see more than N requests for this path in last 10 seconds force_anonymous_min_per_10_seconds = 3 +# maximum number of posts rebaked across the cluster in the periodical job +# rebake process is very expensive, on multisite we have to make sure we never +# flood the queue +max_old_rebakes_per_15_minutes = 300 + diff --git a/config/site_settings.yml b/config/site_settings.yml index ebfd493e87..afaa044a15 100644 --- a/config/site_settings.yml +++ b/config/site_settings.yml @@ -1411,7 +1411,7 @@ developer: top_topics_formula_least_likes_per_post_multiplier: default: 3 rebake_old_posts_count: - default: 100 + default: 80 min: 1 migrate_to_new_scheme: hidden: true diff --git a/spec/models/post_spec.rb b/spec/models/post_spec.rb index dce839102d..9ddfccfb3f 100644 --- a/spec/models/post_spec.rb +++ b/spec/models/post_spec.rb @@ -1153,6 +1153,27 @@ describe Post do post.reload expect(post.baked_at).to eq(baked) end + + it "will rate limit globally" do + + post1 = create_post + post2 = create_post + post3 = create_post + + Post.where(id: [post1.id, post2.id, post3.id]).update_all(baked_version: -1) + + global_setting :max_old_rebakes_per_15_minutes, 2 + + RateLimiter.clear_all_global! + RateLimiter.enable + + Post.rebake_old(100) + + expect(post3.reload.baked_version).not_to eq(-1) + expect(post2.reload.baked_version).not_to eq(-1) + expect(post1.reload.baked_version).to eq(-1) + + end end describe ".unhide!" do From 6961a4f43ebfde37f4ecab3d2d72a6fa642a27fb Mon Sep 17 00:00:00 2001 From: Sam Date: Fri, 4 Jan 2019 09:25:04 +1100 Subject: [PATCH 018/157] Revert "Revert "FEATURE: force rebake of all posts with images"" This reverts commit cb317430a1c27f4d45f3a53efbcd29c77ea8f9e0. No longer needed per: 70269c7c9727cf9c4d2 --- ...03060819_force_rebake_on_posts_with_images.rb | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 db/migrate/20190103060819_force_rebake_on_posts_with_images.rb diff --git a/db/migrate/20190103060819_force_rebake_on_posts_with_images.rb b/db/migrate/20190103060819_force_rebake_on_posts_with_images.rb new file mode 100644 index 0000000000..3837aae59e --- /dev/null +++ b/db/migrate/20190103060819_force_rebake_on_posts_with_images.rb @@ -0,0 +1,16 @@ +class ForceRebakeOnPostsWithImages < ActiveRecord::Migration[5.2] + def up + + # commit message has more info: + # Picking up changes with pngquant, placeholder image, new image magick, retina images + + execute <<~SQL + UPDATE posts SET baked_version = 0 + WHERE id IN (SELECT post_id FROM post_uploads) + SQL + end + + def down + # no op, does not really matter + end +end From 5f0f7f909d3caaf737cd5b69203ccd1b60ad1795 Mon Sep 17 00:00:00 2001 From: Guo Xiang Tan Date: Fri, 4 Jan 2019 07:50:35 +0800 Subject: [PATCH 019/157] FIX: Incorrect CDN URL for site setting uploads when s3 is enabled. --- lib/global_path.rb | 3 ++- spec/helpers/user_notifications_helper_spec.rb | 10 ++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/lib/global_path.rb b/lib/global_path.rb index 10d1fbdda5..17479b60eb 100644 --- a/lib/global_path.rb +++ b/lib/global_path.rb @@ -11,7 +11,8 @@ module GlobalPath if SiteSetting.Upload.s3_cdn_url.present? p = Discourse.store.cdn_url(p) end - p =~ /^http/ ? p : cdn_path(p) + + (p =~ /^http/ || p =~ /^\/\//) ? p : cdn_path(p) end def cdn_relative_path(path) diff --git a/spec/helpers/user_notifications_helper_spec.rb b/spec/helpers/user_notifications_helper_spec.rb index 735a69e19b..f1aeb8a4c9 100644 --- a/spec/helpers/user_notifications_helper_spec.rb +++ b/spec/helpers/user_notifications_helper_spec.rb @@ -108,6 +108,16 @@ describe UserNotificationsHelper do ) end + describe 'when global cdn path is configured' do + it 'should return the right url' do + GlobalSetting.stubs(:cdn_url).returns('https://some.cdn.com/cluster') + + expect(helper.logo_url).to eq( + "http://s3-upload-bucket.s3.dualstack.us-east-1.amazonaws.com/original/1X/somesha1.png" + ) + end + end + describe 'when cdn path is configured' do before do SiteSetting.s3_cdn_url = 'https://some.cdn.com' From e74dd273b9a06dc2a6c1746a97a46d764b22577c Mon Sep 17 00:00:00 2001 From: Joshua Rosenfeld Date: Thu, 3 Jan 2019 19:08:25 -0500 Subject: [PATCH 020/157] UX: Update site setting description to match current function --- config/locales/server.en.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index f585ac9ea3..848fdc4a3f 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -1361,7 +1361,7 @@ en: topics_per_period_in_top_page: "Number of top topics shown on the expanded 'Show More' top topics." redirect_users_to_top_page: "Automatically redirect new and long absent users to the top page." top_page_default_timeframe: "Default timeframe for the top view page." - show_email_on_profile: "Show a user's email on their profile (only visible to themselves and staff)" + show_email_on_profile: "Allow moderators to view user emails" prioritize_username_in_ux: "Show username first on user page, user card and posts (when disabled name is shown first)" enable_rich_text_paste: "Enable automatic HTML to Markdown conversion when pasting text into the composer. (Experimental)" From d7a82f146afc5fc0eb04522ad8d68ebc880cd806 Mon Sep 17 00:00:00 2001 From: Jeff Atwood Date: Thu, 3 Jan 2019 16:47:39 -0800 Subject: [PATCH 021/157] minor copyedit --- config/locales/client.en.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index bb24a2ff5a..15b5420da1 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -3716,7 +3716,7 @@ en: last_emailed: "Last Emailed" not_found: "Sorry, that username doesn't exist in our system." id_not_found: "Sorry, that user id doesn't exist in our system." - active: "Active" + active: "Activated" show_emails: "Show Emails" nav: new: "New" From d0f38dbb071bac90a19882a9c07a081d1135a17d Mon Sep 17 00:00:00 2001 From: Sam Date: Fri, 4 Jan 2019 11:49:37 +1100 Subject: [PATCH 022/157] FIX: image rendered temporarily in wrong position while loading Previously non lightboxed images would render in the wrong spot while loading. We assumed the image we were rendering while loading was at 0,0 position. This is not the case on non-lightboxed images cause they have no surrounding DIV. --- app/assets/javascripts/discourse/lib/lazy-load-images.js.es6 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/discourse/lib/lazy-load-images.js.es6 b/app/assets/javascripts/discourse/lib/lazy-load-images.js.es6 index c159fe620a..b291fcdc94 100644 --- a/app/assets/javascripts/discourse/lib/lazy-load-images.js.es6 +++ b/app/assets/javascripts/discourse/lib/lazy-load-images.js.es6 @@ -47,8 +47,8 @@ function show(image) { copyImg.srcset = imageData.srcset || copyImg.srcset; copyImg.style.position = "absolute"; - copyImg.style.top = 0; - copyImg.style.left = 0; + copyImg.style.top = `${image.offsetTop}px`; + copyImg.style.left = `${image.offsetLeft}px`; copyImg.style.width = imageData.width; copyImg.style.height = imageData.height; From 19d7545318f8a42168261d5c09b8978d2ae63b90 Mon Sep 17 00:00:00 2001 From: cfitz Date: Fri, 4 Jan 2019 04:46:18 +0100 Subject: [PATCH 023/157] FEATURE: Make auth_redirect param options on user_api_keys This is a possible solution for https://meta.discourse.org/t/user-api-keys-specification/48536/19 This allows for user-api-key requests to not require a redirect url. Instead, the encypted payload will just be displayed after creation ( which can be copied pasted into an env for a CLI, for example ) Also: Show instructions when creating user-api-key w/out redirect This adds a view to show instructions when requesting a user-api-key without a redirect. It adds a erb template and json format. Also adds a i18n user_api_key.instructions for server.en.yml --- app/controllers/user_api_keys_controller.rb | 24 +++++++++---- app/views/user_api_keys/new.html.erb | 2 +- app/views/user_api_keys/show.html.erb | 2 ++ config/locales/server.en.yml | 1 + .../requests/user_api_keys_controller_spec.rb | 35 +++++++++++++++++++ 5 files changed, 56 insertions(+), 8 deletions(-) create mode 100644 app/views/user_api_keys/show.html.erb diff --git a/app/controllers/user_api_keys_controller.rb b/app/controllers/user_api_keys_controller.rb index 44c3efe132..a9db864e90 100644 --- a/app/controllers/user_api_keys_controller.rb +++ b/app/controllers/user_api_keys_controller.rb @@ -51,9 +51,9 @@ class UserApiKeysController < ApplicationController require_params - unless SiteSetting.allowed_user_api_auth_redirects + if params.key?(:auth_redirect) && SiteSetting.allowed_user_api_auth_redirects .split('|') - .any? { |u| params[:auth_redirect] == u } + .none? { |u| params[:auth_redirect] == u } raise Discourse::InvalidAccess end @@ -61,12 +61,13 @@ class UserApiKeysController < ApplicationController raise Discourse::InvalidAccess unless meets_tl? validate_params + @application_name = params[:application_name] # destroy any old keys we had UserApiKey.where(user_id: current_user.id, client_id: params[:client_id]).destroy_all key = UserApiKey.create!( - application_name: params[:application_name], + application_name: @application_name, client_id: params[:client_id], user_id: current_user.id, push_url: params[:push_url], @@ -76,7 +77,7 @@ class UserApiKeysController < ApplicationController # we keep the payload short so it encrypts easily with public key # it is often restricted to 128 chars - payload = { + @payload = { key: key.key, nonce: params[:nonce], push: key.has_push?, @@ -84,9 +85,19 @@ class UserApiKeysController < ApplicationController }.to_json public_key = OpenSSL::PKey::RSA.new(params[:public_key]) - payload = Base64.encode64(public_key.public_encrypt(payload)) + @payload = Base64.encode64(public_key.public_encrypt(@payload)) - redirect_to "#{params[:auth_redirect]}?payload=#{CGI.escape(payload)}" + if params[:auth_redirect] + redirect_to("#{params[:auth_redirect]}?payload=#{CGI.escape(@payload)}") + else + respond_to do |format| + format.html { render :show } + format.json do + instructions = I18n.t("user_api_key.instructions", application_name: @application_name) + render json: { payload: @payload, instructions: instructions } + end + end + end end def revoke @@ -124,7 +135,6 @@ class UserApiKeysController < ApplicationController :nonce, :scopes, :client_id, - :auth_redirect, :application_name ].each { |p| params.require(p) } end diff --git a/app/views/user_api_keys/new.html.erb b/app/views/user_api_keys/new.html.erb index 28531e07db..7657919d62 100644 --- a/app/views/user_api_keys/new.html.erb +++ b/app/views/user_api_keys/new.html.erb @@ -24,7 +24,7 @@ <%= hidden_field_tag 'access', @access %> <%= hidden_field_tag 'nonce', @nonce %> <%= hidden_field_tag 'client_id', @client_id %> - <%= hidden_field_tag 'auth_redirect', @auth_redirect %> + <%= hidden_field_tag('auth_redirect', @auth_redirect) if @auth_redirect %> <%= hidden_field_tag 'push_url', @push_url %> <%= hidden_field_tag 'public_key', @public_key%> <%= hidden_field_tag 'scopes', @scopes%> diff --git a/app/views/user_api_keys/show.html.erb b/app/views/user_api_keys/show.html.erb new file mode 100644 index 0000000000..7d657efe09 --- /dev/null +++ b/app/views/user_api_keys/show.html.erb @@ -0,0 +1,2 @@ +

<%= t("user_api_key.instructions", application_name: @application_name) %>

+<%= @payload %> diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index 848fdc4a3f..0cfb9cdd12 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -874,6 +874,7 @@ en: read: "read" read_write: "read/write" description: "\"%{application_name}\" is requesting the following access to your account:" + instructions: "We just generated a new user API key for you to use with \"%{application_name}\", please paste the following key into your application:" no_trust_level: "Sorry, you do not have the required trust level to access the user API" generic_error: "Sorry, we are unable to issue user API keys, this feature may be disabled by the site admin" scopes: diff --git a/spec/requests/user_api_keys_controller_spec.rb b/spec/requests/user_api_keys_controller_spec.rb index ddaedd2589..ee5ffb4d7f 100644 --- a/spec/requests/user_api_keys_controller_spec.rb +++ b/spec/requests/user_api_keys_controller_spec.rb @@ -205,5 +205,40 @@ describe UserApiKeysController do expect(response.status).to eq(302) end + + it "will just show the payload if no redirect" do + user = Fabricate(:user, trust_level: 0) + sign_in(user) + + args.delete(:auth_redirect) + + SiteSetting.min_trust_level_for_user_api_key = 0 + post "/user-api-key", params: args + expect(response.status).not_to eq(302) + payload = Nokogiri::HTML(response.body).at('code').content + encrypted = Base64.decode64(payload) + key = OpenSSL::PKey::RSA.new(private_key) + parsed = JSON.parse(key.private_decrypt(encrypted)) + api_key = UserApiKey.find_by(key: parsed["key"]) + expect(api_key.user_id).to eq(user.id) + end + + it "will just show the JSON payload if no redirect" do + user = Fabricate(:user, trust_level: 0) + sign_in(user) + + args.delete(:auth_redirect) + + SiteSetting.min_trust_level_for_user_api_key = 0 + post "/user-api-key.json", params: args + expect(response.status).not_to eq(302) + payload = JSON.parse(response.body)["payload"] + encrypted = Base64.decode64(payload) + key = OpenSSL::PKey::RSA.new(private_key) + parsed = JSON.parse(key.private_decrypt(encrypted)) + api_key = UserApiKey.find_by(key: parsed["key"]) + expect(api_key.user_id).to eq(user.id) + + end end end From a2f1d47506b9e3d93e558842b5b34234d215eaa8 Mon Sep 17 00:00:00 2001 From: Kris Date: Thu, 3 Jan 2019 22:47:08 -0500 Subject: [PATCH 024/157] Slightly better alignment of PM participants in header --- app/assets/stylesheets/common/base/header.scss | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/assets/stylesheets/common/base/header.scss b/app/assets/stylesheets/common/base/header.scss index 081c35c25c..8b98edb954 100644 --- a/app/assets/stylesheets/common/base/header.scss +++ b/app/assets/stylesheets/common/base/header.scss @@ -310,9 +310,8 @@ } .trigger-group-card { - height: 16px; margin: 0 4px; - padding: 1px 4px; + padding: 1px 4px 0; border: 1px solid $primary-low; border-radius: 0.25em; align-items: center; From 8f35fd4595cf33ceb823b8ce09f5ee291a020226 Mon Sep 17 00:00:00 2001 From: Sam Date: Fri, 4 Jan 2019 15:05:52 +1100 Subject: [PATCH 025/157] FEATURE: remove global settings for redis sentinels This global setting is never used, configuring Discourse with sentinel is unsupported. --- config/discourse_defaults.conf | 3 --- 1 file changed, 3 deletions(-) diff --git a/config/discourse_defaults.conf b/config/discourse_defaults.conf index cde2c3666d..2957af97e6 100644 --- a/config/discourse_defaults.conf +++ b/config/discourse_defaults.conf @@ -117,9 +117,6 @@ redis_db = 0 # redis password redis_password = -# redis sentinels eg -# redis_sentinels = 10.0.0.1:26381,10.0.0.2:26381 -redis_sentinels = # enable Cross-origin Resource Sharing (CORS) directly at the application level enable_cors = false From 8b7a2d1cb725df422a34ea4a1082a6edd756abae Mon Sep 17 00:00:00 2001 From: Sam Date: Fri, 4 Jan 2019 15:08:22 +1100 Subject: [PATCH 026/157] FEATURE: add setting to bypass sending redis CLIENT commands Some cloud providers (Google Memorystore) do not support any CLIENT commands By setting :id to nil in the redis config hash we can avoid these commands. This adds a special global setting GCE users can enable: `DISCOURSE_REDIS_SKIP_CLIENT_COMMANDS = true` --- app/models/global_setting.rb | 1 + config/discourse_defaults.conf | 2 ++ 2 files changed, 3 insertions(+) diff --git a/app/models/global_setting.rb b/app/models/global_setting.rb index c81430d2cd..67320ee5e8 100644 --- a/app/models/global_setting.rb +++ b/app/models/global_setting.rb @@ -152,6 +152,7 @@ class GlobalSetting c[:password] = redis_password if redis_password.present? c[:db] = redis_db if redis_db != 0 c[:db] = 1 if Rails.env == "test" + c[:id] = nil if redis_skip_client_commands c.freeze end diff --git a/config/discourse_defaults.conf b/config/discourse_defaults.conf index 2957af97e6..1b5fba6073 100644 --- a/config/discourse_defaults.conf +++ b/config/discourse_defaults.conf @@ -117,6 +117,8 @@ redis_db = 0 # redis password redis_password = +# skip configuring client id for cloud providers who support no client commands +redis_skip_client_commands = false # enable Cross-origin Resource Sharing (CORS) directly at the application level enable_cors = false From 3a04e04ccb56fe6d29cd65463a2ab9c1e3241e4c Mon Sep 17 00:00:00 2001 From: Joe <33972521+hnb-ku@users.noreply.github.com> Date: Fri, 4 Jan 2019 12:11:42 +0800 Subject: [PATCH 027/157] UX: excerpts don't wrap on IE11 (#6847) --- app/assets/stylesheets/common/base/_topic-list.scss | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/assets/stylesheets/common/base/_topic-list.scss b/app/assets/stylesheets/common/base/_topic-list.scss index d276ea033c..63842ebcb6 100644 --- a/app/assets/stylesheets/common/base/_topic-list.scss +++ b/app/assets/stylesheets/common/base/_topic-list.scss @@ -144,6 +144,9 @@ .discourse-tag.box { margin-right: 0.25em; } + .topic-excerpt { + flex: 1 1 0%; // IE11 fix - unit on flexbasis is required + } } .topic-featured-link { From 75dbb98cca14e3cfcf54035d64ebd603ff64475b Mon Sep 17 00:00:00 2001 From: Vinoth Kannan Date: Fri, 4 Jan 2019 11:46:22 +0530 Subject: [PATCH 028/157] FEATURE: Add S3 etag value to uploads table (#6795) --- app/models/upload.rb | 2 ++ .../20181218071253_add_etag_to_uploads.rb | 9 ++++++ lib/file_store/s3_store.rb | 15 ++++++---- lib/s3_helper.rb | 23 ++++++++++++-- lib/upload_creator.rb | 3 +- spec/components/file_store/s3_store_spec.rb | 18 ++++++++--- spec/lib/upload_creator_spec.rb | 30 +++++++++++++++++++ spec/multisite/s3_store_spec.rb | 2 ++ 8 files changed, 88 insertions(+), 14 deletions(-) create mode 100644 db/migrate/20181218071253_add_etag_to_uploads.rb diff --git a/app/models/upload.rb b/app/models/upload.rb index a90eb4c14e..c3fd30203d 100644 --- a/app/models/upload.rb +++ b/app/models/upload.rb @@ -289,6 +289,7 @@ end # extension :string(10) # thumbnail_width :integer # thumbnail_height :integer +# etag :string # # Indexes # @@ -297,4 +298,5 @@ end # index_uploads_on_sha1 (sha1) UNIQUE # index_uploads_on_url (url) # index_uploads_on_user_id (user_id) +# index_uploads_on_etag (etag) # diff --git a/db/migrate/20181218071253_add_etag_to_uploads.rb b/db/migrate/20181218071253_add_etag_to_uploads.rb new file mode 100644 index 0000000000..dacc0957c1 --- /dev/null +++ b/db/migrate/20181218071253_add_etag_to_uploads.rb @@ -0,0 +1,9 @@ +class AddEtagToUploads < ActiveRecord::Migration[5.2] + def change + add_column :uploads, :etag, :string + add_index :uploads, [:etag] + + add_column :optimized_images, :etag, :string + add_index :optimized_images, [:etag] + end +end diff --git a/lib/file_store/s3_store.rb b/lib/file_store/s3_store.rb index da309f3400..119dd9c0a1 100644 --- a/lib/file_store/s3_store.rb +++ b/lib/file_store/s3_store.rb @@ -19,12 +19,14 @@ module FileStore def store_upload(file, upload, content_type = nil) path = get_path_for_upload(upload) - store_file(file, path, filename: upload.original_filename, content_type: content_type, cache_locally: true) + url, upload.etag = store_file(file, path, filename: upload.original_filename, content_type: content_type, cache_locally: true) + url end def store_optimized_image(file, optimized_image, content_type = nil) path = get_path_for_optimized_image(optimized_image) - store_file(file, path, content_type: content_type) + url, optimized_image.etag = store_file(file, path, content_type: content_type) + url end # options @@ -42,13 +44,14 @@ module FileStore } # add a "content disposition" header for "attachments" options[:content_disposition] = "attachment; filename=\"#{filename}\"" unless FileHelper.is_supported_image?(filename) - # if this fails, it will throw an exception path.prepend(File.join(upload_path, "/")) if Rails.configuration.multisite - path = @s3_helper.upload(file, path, options) - # return the upload url - File.join(absolute_base_url, path) + # if this fails, it will throw an exception + path, etag = @s3_helper.upload(file, path, options) + + # return the upload url and etag + return File.join(absolute_base_url, path), etag end def remove_file(url, path) diff --git a/lib/s3_helper.rb b/lib/s3_helper.rb index 708c232133..6741debf6a 100644 --- a/lib/s3_helper.rb +++ b/lib/s3_helper.rb @@ -24,8 +24,21 @@ class S3Helper def upload(file, path, options = {}) path = get_path_for_s3_upload(path) - s3_bucket.object(path).upload_file(file, options) - path + obj = s3_bucket.object(path) + + etag = begin + if File.size(file) >= Aws::S3::FileUploader::FIFTEEN_MEGABYTES + options[:multipart_threshold] = Aws::S3::FileUploader::FIFTEEN_MEGABYTES + obj.upload_file(file, options) + obj.load + obj.etag + else + options[:body] = file + obj.put(options).etag + end + end + + return path, etag end def remove(s3_filename, copy_to_tombstone = false) @@ -210,8 +223,12 @@ class S3Helper File.join("uploads", RailsMultisite::ConnectionManagement.current_db, "/") end + def s3_client + Aws::S3::Client.new(@s3_options) + end + def s3_resource - Aws::S3::Resource.new(@s3_options) + Aws::S3::Resource.new(client: s3_client) end def s3_bucket diff --git a/lib/upload_creator.rb b/lib/upload_creator.rb index 35fa225e23..2d7cc9166c 100644 --- a/lib/upload_creator.rb +++ b/lib/upload_creator.rb @@ -126,7 +126,8 @@ class UploadCreator url = Discourse.store.store_upload(f, @upload) if url.present? - @upload.update!(url: url) + @upload.url = url + @upload.save! else @upload.errors.add(:url, I18n.t("upload.store_failure", upload_id: @upload.id, user_id: user_id)) end diff --git a/spec/components/file_store/s3_store_spec.rb b/spec/components/file_store/s3_store_spec.rb index 993e3d315c..21f77761c7 100644 --- a/spec/components/file_store/s3_store_spec.rb +++ b/spec/components/file_store/s3_store_spec.rb @@ -38,6 +38,8 @@ describe FileStore::S3Store do context 'uploading to s3' do include_context "s3 helpers" + let(:etag) { "etag" } + describe "#store_upload" do it "returns an absolute schemaless url" do store.expects(:get_depth_for).with(upload.id).returns(0) @@ -45,11 +47,13 @@ describe FileStore::S3Store do s3_object = stub s3_bucket.expects(:object).with("original/1X/#{upload.sha1}.png").returns(s3_object) - s3_object.expects(:upload_file) + + s3_object.stubs(:put).returns(Aws::S3::Types::PutObjectOutput.new(etag: etag)) expect(store.store_upload(uploaded_file, upload)).to eq( "//s3-upload-bucket.s3.dualstack.us-west-1.amazonaws.com/original/1X/#{upload.sha1}.png" ) + expect(upload.etag).to eq(etag) end describe "when s3_upload_bucket includes folders path" do @@ -63,11 +67,13 @@ describe FileStore::S3Store do s3_object = stub s3_bucket.expects(:object).with("discourse-uploads/original/1X/#{upload.sha1}.png").returns(s3_object) - s3_object.expects(:upload_file) + + s3_object.stubs(:put).returns(Aws::S3::Types::PutObjectOutput.new(etag: etag)) expect(store.store_upload(uploaded_file, upload)).to eq( "//s3-upload-bucket.s3.dualstack.us-west-1.amazonaws.com/discourse-uploads/original/1X/#{upload.sha1}.png" ) + expect(upload.etag).to eq(etag) end end end @@ -80,11 +86,13 @@ describe FileStore::S3Store do path = "optimized/1X/#{optimized_image.upload.sha1}_#{OptimizedImage::VERSION}_100x200.png" s3_bucket.expects(:object).with(path).returns(s3_object) - s3_object.expects(:upload_file) + + s3_object.stubs(:put).returns(Aws::S3::Types::PutObjectOutput.new(etag: etag)) expect(store.store_optimized_image(optimized_image_file, optimized_image)).to eq( "//s3-upload-bucket.s3.dualstack.us-west-1.amazonaws.com/#{path}" ) + expect(optimized_image.etag).to eq(etag) end describe "when s3_upload_bucket includes folders path" do @@ -99,11 +107,13 @@ describe FileStore::S3Store do path = "discourse-uploads/optimized/1X/#{optimized_image.upload.sha1}_#{OptimizedImage::VERSION}_100x200.png" s3_bucket.expects(:object).with(path).returns(s3_object) - s3_object.expects(:upload_file) + + s3_object.stubs(:put).returns(Aws::S3::Types::PutObjectOutput.new(etag: etag)) expect(store.store_optimized_image(optimized_image_file, optimized_image)).to eq( "//s3-upload-bucket.s3.dualstack.us-west-1.amazonaws.com/#{path}" ) + expect(optimized_image.etag).to eq(etag) end end end diff --git a/spec/lib/upload_creator_spec.rb b/spec/lib/upload_creator_spec.rb index 897c946aee..0c2120deeb 100644 --- a/spec/lib/upload_creator_spec.rb +++ b/spec/lib/upload_creator_spec.rb @@ -1,4 +1,5 @@ require 'rails_helper' +require 'file_store/s3_store' RSpec.describe UploadCreator do let(:user) { Fabricate(:user) } @@ -166,5 +167,34 @@ RSpec.describe UploadCreator do expect(upload.original_filename).to eq('should_be_jpeg.jpg') end end + + describe 'uploading to s3' do + let(:filename) { "should_be_jpeg.png" } + let(:file) { file_from_fixtures(filename) } + + before do + SiteSetting.s3_upload_bucket = "s3-upload-bucket" + SiteSetting.s3_access_key_id = "s3-access-key-id" + SiteSetting.s3_secret_access_key = "s3-secret-access-key" + SiteSetting.s3_region = 'us-west-1' + SiteSetting.enable_s3_uploads = true + + store = FileStore::S3Store.new + s3_helper = store.instance_variable_get(:@s3_helper) + client = Aws::S3::Client.new(stub_responses: true) + s3_helper.stubs(:s3_client).returns(client) + Discourse.stubs(:store).returns(store) + end + + it 'should store the file and return etag' do + expect { + UploadCreator.new(file, filename).create_for(user.id) + }.to change { Upload.count }.by(1) + + upload = Upload.last + + expect(upload.etag).to eq('ETag') + end + end end end diff --git a/spec/multisite/s3_store_spec.rb b/spec/multisite/s3_store_spec.rb index 6c023d15b6..4ac8527eb6 100644 --- a/spec/multisite/s3_store_spec.rb +++ b/spec/multisite/s3_store_spec.rb @@ -24,12 +24,14 @@ RSpec.describe 'Multisite s3 uploads', type: :multisite do expect(store.store_upload(uploaded_file, upload)).to eq( "//#{SiteSetting.s3_upload_bucket}.s3.dualstack.us-east-1.amazonaws.com/uploads/default/original/1X/c530c06cf89c410c0355d7852644a73fc3ec8c04.png" ) + expect(upload.etag).to eq("ETag") end conn.with_connection('second') do expect(store.store_upload(uploaded_file, upload)).to eq( "//#{SiteSetting.s3_upload_bucket}.s3.dualstack.us-east-1.amazonaws.com/uploads/second/original/1X/c530c06cf89c410c0355d7852644a73fc3ec8c04.png" ) + expect(upload.etag).to eq("ETag") end end end From 940a61037c02d17228ee06f9d05170f99dd6504a Mon Sep 17 00:00:00 2001 From: Vinoth Kannan Date: Fri, 4 Jan 2019 12:16:09 +0530 Subject: [PATCH 029/157] DEV: Add option to pass s3 client in param --- lib/s3_helper.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/s3_helper.rb b/lib/s3_helper.rb index 6741debf6a..427b6ceaa6 100644 --- a/lib/s3_helper.rb +++ b/lib/s3_helper.rb @@ -7,6 +7,7 @@ class S3Helper attr_reader :s3_bucket_name, :s3_bucket_folder_path def initialize(s3_bucket_name, tombstone_prefix = '', options = {}) + @s3_client = options.delete(:client) @s3_options = default_s3_options.merge(options) @s3_bucket_name, @s3_bucket_folder_path = begin @@ -224,7 +225,7 @@ class S3Helper end def s3_client - Aws::S3::Client.new(@s3_options) + @s3_client ||= Aws::S3::Client.new(@s3_options) end def s3_resource From 9c91e683516408cc2e6c0a9667f524ee61d048ed Mon Sep 17 00:00:00 2001 From: Sam Date: Fri, 4 Jan 2019 18:43:53 +1100 Subject: [PATCH 030/157] PERF: remove image optimization throttling from Sidekiq Previously we only allowed one image optimization per machine, this meant there was cross talk between avatar resizing and Sidekiq. This could lead to large amounts of starvation when optimized image version changed which in turn could block the Sidekiq queue. This increases amount of allowed load on machines but this is preferable to having crosstalk between avatar resizing and Sidekiq. --- app/models/optimized_image.rb | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/app/models/optimized_image.rb b/app/models/optimized_image.rb index 5cc2484654..2b5f9864e8 100644 --- a/app/models/optimized_image.rb +++ b/app/models/optimized_image.rb @@ -11,12 +11,20 @@ class OptimizedImage < ActiveRecord::Base def self.lock(upload_id, width, height) @hostname ||= `hostname`.strip rescue "unknown" - # note, the extra lock here ensures we only optimize one image per machine - # this can very easily lead to runaway CPU so slowing it down is beneficial - DistributedMutex.synchronize("optimized_image_host_#{@hostname}") do + # note, the extra lock here ensures we only optimize one image per machine on webs + # this can very easily lead to runaway CPU so slowing it down is beneficial and it is hijacked + # + # we can not afford this blocking in Sidekiq cause it can lead to starvation + if Sidekiq.server? DistributedMutex.synchronize("optimized_image_#{upload_id}_#{width}_#{height}") do yield end + else + DistributedMutex.synchronize("optimized_image_host_#{@hostname}") do + DistributedMutex.synchronize("optimized_image_#{upload_id}_#{width}_#{height}") do + yield + end + end end end From 82d7f9ce5e9dab7f51a64c09aec75c37987935c3 Mon Sep 17 00:00:00 2001 From: Vinoth Kannan Date: Fri, 4 Jan 2019 13:25:11 +0530 Subject: [PATCH 031/157] fix the build Checking size for a file object directly will cause issue if it is a closed stream --- lib/s3_helper.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/s3_helper.rb b/lib/s3_helper.rb index 427b6ceaa6..1ecd8223d6 100644 --- a/lib/s3_helper.rb +++ b/lib/s3_helper.rb @@ -28,7 +28,7 @@ class S3Helper obj = s3_bucket.object(path) etag = begin - if File.size(file) >= Aws::S3::FileUploader::FIFTEEN_MEGABYTES + if File.size(file.path) >= Aws::S3::FileUploader::FIFTEEN_MEGABYTES options[:multipart_threshold] = Aws::S3::FileUploader::FIFTEEN_MEGABYTES obj.upload_file(file, options) obj.load From 902f53511102911855b65ab681b766cf83534bc5 Mon Sep 17 00:00:00 2001 From: Vinoth Kannan Date: Fri, 4 Jan 2019 15:30:45 +0530 Subject: [PATCH 032/157] FIX: upload method in S3Helper will expect a file object param --- lib/tasks/s3.rake | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/tasks/s3.rake b/lib/tasks/s3.rake index 5ad2de0174..69c854827a 100644 --- a/lib/tasks/s3.rake +++ b/lib/tasks/s3.rake @@ -33,7 +33,10 @@ def upload(path, remote_path, content_type, content_encoding = nil) puts "Skipping: #{remote_path}" else puts "Uploading: #{remote_path}" - helper.upload(path, remote_path, options) + + File.open(path) do |file| + helper.upload(file, remote_path, options) + end end end From c5b7bda198e04d93213f563b2a87b959d488ea6c Mon Sep 17 00:00:00 2001 From: Rishabh Date: Fri, 4 Jan 2019 18:09:54 +0530 Subject: [PATCH 033/157] DEV: Show migrate_to_s3 output on a new line --- lib/tasks/uploads.rake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/tasks/uploads.rake b/lib/tasks/uploads.rake index 73f0b3372d..d481dcb08f 100644 --- a/lib/tasks/uploads.rake +++ b/lib/tasks/uploads.rake @@ -278,7 +278,7 @@ def migrate_to_s3 end puts " => #{s3_objects.size} files" - print " - Syncing files to S3" + print " - Syncing files to S3\n" synced = 0 failed = [] From 5bf16d7d107fa8c5654f56e83a3b72e6005bf1c6 Mon Sep 17 00:00:00 2001 From: David Taylor Date: Fri, 4 Jan 2019 13:08:04 +0000 Subject: [PATCH 034/157] FEATURE: Topic timer for bumping a topic in the future --- .../components/edit-topic-timer-form.js.es6 | 11 ++++-- .../controllers/edit-topic-timer.js.es6 | 5 +++ app/jobs/regular/bump_topic.rb | 20 +++++++++++ app/models/topic_timer.rb | 11 +++++- config/locales/client.en.yml | 3 ++ spec/jobs/bump_topic_spec.rb | 35 +++++++++++++++++++ 6 files changed, 82 insertions(+), 3 deletions(-) create mode 100644 app/jobs/regular/bump_topic.rb create mode 100644 spec/jobs/bump_topic_spec.rb diff --git a/app/assets/javascripts/discourse/components/edit-topic-timer-form.js.es6 b/app/assets/javascripts/discourse/components/edit-topic-timer-form.js.es6 index d231539ccf..e343187acb 100644 --- a/app/assets/javascripts/discourse/components/edit-topic-timer-form.js.es6 +++ b/app/assets/javascripts/discourse/components/edit-topic-timer-form.js.es6 @@ -9,7 +9,8 @@ import { OPEN_STATUS_TYPE, DELETE_STATUS_TYPE, REMINDER_TYPE, - CLOSE_STATUS_TYPE + CLOSE_STATUS_TYPE, + BUMP_TYPE } from "discourse/controllers/edit-topic-timer"; export default Ember.Component.extend({ @@ -17,12 +18,18 @@ export default Ember.Component.extend({ autoOpen: Ember.computed.equal("selection", OPEN_STATUS_TYPE), autoClose: Ember.computed.equal("selection", CLOSE_STATUS_TYPE), autoDelete: Ember.computed.equal("selection", DELETE_STATUS_TYPE), + autoBump: Ember.computed.equal("selection", BUMP_TYPE), publishToCategory: Ember.computed.equal( "selection", PUBLISH_TO_CATEGORY_STATUS_TYPE ), reminder: Ember.computed.equal("selection", REMINDER_TYPE), - showTimeOnly: Ember.computed.or("autoOpen", "autoDelete", "reminder"), + showTimeOnly: Ember.computed.or( + "autoOpen", + "autoDelete", + "reminder", + "autoBump" + ), @computed( "topicTimer.updateTime", diff --git a/app/assets/javascripts/discourse/controllers/edit-topic-timer.js.es6 b/app/assets/javascripts/discourse/controllers/edit-topic-timer.js.es6 index 205bc1e6ef..5b7dec66a1 100644 --- a/app/assets/javascripts/discourse/controllers/edit-topic-timer.js.es6 +++ b/app/assets/javascripts/discourse/controllers/edit-topic-timer.js.es6 @@ -8,6 +8,7 @@ export const OPEN_STATUS_TYPE = "open"; export const PUBLISH_TO_CATEGORY_STATUS_TYPE = "publish_to_category"; export const DELETE_STATUS_TYPE = "delete"; export const REMINDER_TYPE = "reminder"; +export const BUMP_TYPE = "bump"; export default Ember.Controller.extend(ModalFunctionality, { loading: false, @@ -31,6 +32,10 @@ export default Ember.Controller.extend(ModalFunctionality, { { id: PUBLISH_TO_CATEGORY_STATUS_TYPE, name: I18n.t("topic.publish_to_category.title") + }, + { + id: BUMP_TYPE, + name: I18n.t("topic.auto_bump.title") } ]; if (this.currentUser.get("staff")) { diff --git a/app/jobs/regular/bump_topic.rb b/app/jobs/regular/bump_topic.rb new file mode 100644 index 0000000000..f4e2193754 --- /dev/null +++ b/app/jobs/regular/bump_topic.rb @@ -0,0 +1,20 @@ +module Jobs + class BumpTopic < Jobs::Base + + def execute(args) + topic_timer = TopicTimer.find_by(id: args[:topic_timer_id] || args[:topic_status_update_id]) + + topic = topic_timer&.topic + + if topic_timer.blank? || topic.blank? || topic_timer.execute_at > Time.zone.now + return + end + + if Guardian.new(topic_timer.user).can_create_post_on_topic?(topic) + topic.add_small_action(Discourse.system_user, "autobumped", nil, bump: true) + end + topic_timer.trash!(Discourse.system_user) + end + + end +end diff --git a/app/models/topic_timer.rb b/app/models/topic_timer.rb index e884f33ade..c8229bcca4 100644 --- a/app/models/topic_timer.rb +++ b/app/models/topic_timer.rb @@ -42,7 +42,8 @@ class TopicTimer < ActiveRecord::Base open: 2, publish_to_category: 3, delete: 4, - reminder: 5 + reminder: 5, + bump: 6 ) end @@ -108,6 +109,14 @@ class TopicTimer < ActiveRecord::Base Jobs.cancel_scheduled_job(:topic_reminder, topic_timer_id: id) end + def cancel_auto_bump_job + Jobs.cancel_scheduled_job(:bump_topic, topic_timer_id: id) + end + + def schedule_auto_bump_job(time) + Jobs.enqueue_at(time, :bump_topic, topic_timer_id: id) + end + def schedule_auto_open_job(time) return unless topic topic.update_status('closed', true, user) if !topic.closed diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 15b5420da1..979520d084 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -1817,6 +1817,8 @@ en: based_on_last_post: "Don't close until the last post in the topic is at least this old." auto_delete: title: "Auto-Delete Topic" + auto_bump: + title: "Auto-Bump Topic" reminder: title: "Remind Me" @@ -1826,6 +1828,7 @@ en: auto_publish_to_category: "This topic will be published to #%{categoryName} %{timeLeft}." auto_close_based_on_last_post: "This topic will close %{duration} after the last reply." auto_delete: "This topic will be automatically deleted %{timeLeft}." + auto_bump: "This topic will be automatically bumped %{timeLeft}." auto_reminder: "You will be reminded about this topic %{timeLeft}." auto_close_title: 'Auto-Close Settings' auto_close_immediate: diff --git a/spec/jobs/bump_topic_spec.rb b/spec/jobs/bump_topic_spec.rb new file mode 100644 index 0000000000..1f90d54db2 --- /dev/null +++ b/spec/jobs/bump_topic_spec.rb @@ -0,0 +1,35 @@ +require 'rails_helper' + +describe Jobs::BumpTopic do + let(:admin) { Fabricate(:admin) } + let(:user) { Fabricate(:user) } + + it "can bump a topic" do + topic = Fabricate(:topic_timer, user: admin).topic + create_post(topic: topic) + + freeze_time (2.hours.from_now) + + expect do + described_class.new.execute(topic_timer_id: topic.public_topic_timer.id) + end.to change { topic.posts.count }.by (1) + + expect(topic.reload.public_topic_timer).to eq(nil) + end + + it "respects the guardian" do + topic = Fabricate(:topic_timer, user: user).topic + create_post(topic: topic) + topic.category = Fabricate(:private_category, group: Fabricate(:group)) + topic.save! + + freeze_time (2.hours.from_now) + + expect do + described_class.new.execute(topic_timer_id: topic.public_topic_timer.id) + end.to change { topic.posts.count }.by (0) + + expect(topic.reload.public_topic_timer).to eq(nil) + end + +end From 88e861e8951846ea41811315989a17c0114baae5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9gis=20Hanol?= Date: Fri, 4 Jan 2019 15:17:54 +0100 Subject: [PATCH 035/157] FIX: prevent error when badge has already been awarded --- app/services/badge_granter.rb | 137 ++++++++++++++-------------- spec/services/badge_granter_spec.rb | 15 ++- 2 files changed, 78 insertions(+), 74 deletions(-) diff --git a/app/services/badge_granter.rb b/app/services/badge_granter.rb index 6999be26e1..675a58e832 100644 --- a/app/services/badge_granter.rb +++ b/app/services/badge_granter.rb @@ -139,8 +139,7 @@ class BadgeGranter end def self.find_by_type(type) - id = "Badge::Trigger::#{type}".constantize - Badge.where(trigger: id) + Badge.where(trigger: "Badge::Trigger::#{type}".constantize) end def self.queue_key @@ -151,15 +150,18 @@ class BadgeGranter # :target_posts - whether the badge targets posts # :trigger - the Badge::Trigger id def self.contract_checks!(sql, opts = {}) - return unless sql.present? + return if sql.blank? + if Badge::Trigger.uses_post_ids?(opts[:trigger]) raise("Contract violation:\nQuery triggers on posts, but does not reference the ':post_ids' array") unless sql.match(/:post_ids/) raise "Contract violation:\nQuery triggers on posts, but references the ':user_ids' array" if sql.match(/:user_ids/) end + if Badge::Trigger.uses_user_ids?(opts[:trigger]) raise "Contract violation:\nQuery triggers on users, but does not reference the ':user_ids' array" unless sql.match(/:user_ids/) raise "Contract violation:\nQuery triggers on users, but references the ':post_ids' array" if sql.match(/:post_ids/) end + if opts[:trigger] && !Badge::Trigger.is_none?(opts[:trigger]) raise "Contract violation:\nQuery is triggered, but does not reference the ':backfill' parameter.\n(Hint: if :backfill is TRUE, you should ignore the :post_ids/:user_ids)" unless sql.match(/:backfill/) end @@ -168,6 +170,7 @@ class BadgeGranter if opts[:target_posts] raise "Contract violation:\nQuery targets posts, but does not return a 'post_id' column" unless sql.match(/post_id/) end + raise "Contract violation:\nQuery does not return a 'user_id' column" unless sql.match(/user_id/) raise "Contract violation:\nQuery does not return a 'granted_at' column" unless sql.match(/granted_at/) raise "Contract violation:\nQuery ends with a semicolon. Remove the semicolon; your sql will be used in a subquery." if sql.match(/;\s*\z/) @@ -186,22 +189,25 @@ class BadgeGranter count_sql = "SELECT COUNT(*) count FROM (#{sql}) q WHERE :backfill = :backfill" grant_count = DB.query_single(count_sql, params).first.to_i - grants_sql = - if opts[:target_posts] - "SELECT u.id, u.username, q.post_id, t.title, q.granted_at - FROM(#{sql}) q - JOIN users u on u.id = q.user_id + grants_sql = if opts[:target_posts] + <<~SQL + SELECT u.id, u.username, q.post_id, t.title, q.granted_at + FROM (#{sql}) q + JOIN users u on u.id = q.user_id LEFT JOIN badge_posts p on p.id = q.post_id LEFT JOIN topics t on t.id = p.topic_id - WHERE :backfill = :backfill - LIMIT 10" - else - "SELECT u.id, u.username, q.granted_at - FROM(#{sql}) q - JOIN users u on u.id = q.user_id - WHERE :backfill = :backfill - LIMIT 10" - end + WHERE :backfill = :backfill + LIMIT 10 + SQL + else + <<~SQL + SELECT u.id, u.username, q.granted_at + FROM (#{sql}) q + JOIN users u on u.id = q.user_id + WHERE :backfill = :backfill + LIMIT 10 + SQL + end query_plan = nil # HACK: active record sanitization too flexible, force it to go down the sanitization path that cares not for % stuff @@ -235,29 +241,30 @@ class BadgeGranter user_ids = opts[:user_ids] if opts # safeguard fall back to full backfill if more than 200 - if (post_ids && post_ids.length > MAX_ITEMS_FOR_DELTA) || - (user_ids && user_ids.length > MAX_ITEMS_FOR_DELTA) + if (post_ids && post_ids.size > MAX_ITEMS_FOR_DELTA) || + (user_ids && user_ids.size > MAX_ITEMS_FOR_DELTA) post_ids = nil user_ids = nil end - post_ids = nil unless post_ids.present? - user_ids = nil unless user_ids.present? + post_ids = nil if post_ids.blank? + user_ids = nil if user_ids.blank? full_backfill = !user_ids && !post_ids post_clause = badge.target_posts ? "AND (q.post_id = ub.post_id OR NOT :multiple_grant)" : "" post_id_field = badge.target_posts ? "q.post_id" : "NULL" - sql = "DELETE FROM user_badges - WHERE id in ( - SELECT ub.id - FROM user_badges ub - LEFT JOIN ( #{badge.query} ) q - ON q.user_id = ub.user_id - #{post_clause} - WHERE ub.badge_id = :id AND q.user_id IS NULL - )" + sql = <<~SQL + DELETE FROM user_badges + WHERE id IN ( + SELECT ub.id + FROM user_badges ub + LEFT JOIN (#{badge.query}) q ON q.user_id = ub.user_id + #{post_clause} + WHERE ub.badge_id = :id AND q.user_id IS NULL + ) + SQL DB.exec( sql, @@ -270,33 +277,34 @@ class BadgeGranter sql = <<~SQL WITH w as ( - INSERT INTO user_badges(badge_id, user_id, granted_at, granted_by_id, post_id) - SELECT :id, q.user_id, q.granted_at, -1, #{post_id_field} - FROM ( #{badge.query} ) q - LEFT JOIN user_badges ub ON - ub.badge_id = :id AND ub.user_id = q.user_id + INSERT INTO user_badges(badge_id, user_id, granted_at, granted_by_id, post_id) + SELECT :id, q.user_id, q.granted_at, -1, #{post_id_field} + FROM (#{badge.query}) q + LEFT JOIN user_badges ub ON ub.badge_id = :id AND ub.user_id = q.user_id #{post_clause} - /*where*/ - RETURNING id, user_id, granted_at + /*where*/ + ON CONFLICT DO NOTHING + RETURNING id, user_id, granted_at ) - select w.*, username, locale, (u.admin OR u.moderator) AS staff FROM w - JOIN users u on u.id = w.user_id + SELECT w.*, username, locale, (u.admin OR u.moderator) AS staff + FROM w + JOIN users u on u.id = w.user_id SQL builder = DB.build(sql) - builder.where("ub.badge_id IS NULL AND q.user_id <> -1") + builder.where("ub.badge_id IS NULL AND q.user_id > 0") if (post_ids || user_ids) && !badge.query.include?(":backfill") Rails.logger.warn "Your triggered badge query for #{badge.name} does not include the :backfill param, skipping!" return end - if (post_ids && !badge.query.include?(":post_ids")) + if post_ids && !badge.query.include?(":post_ids") Rails.logger.warn "Your triggered badge query for #{badge.name} does not include the :post_ids param, skipping!" return end - if (user_ids && !badge.query.include?(":user_ids")) + if user_ids && !badge.query.include?(":user_ids") Rails.logger.warn "Your triggered badge query for #{badge.name} does not include the :user_ids param, skipping!" return end @@ -309,32 +317,29 @@ class BadgeGranter user_ids: user_ids || [-2]).each do |row| # old bronze badges do not matter - next if badge.badge_type_id == (BadgeType::Bronze) && row.granted_at < (2.days.ago) + next if badge.badge_type_id == BadgeType::Bronze && row.granted_at < 2.days.ago # Try to use user locale in the badge notification if possible without too much resources - notification_locale = - if SiteSetting.allow_user_locale && row.locale.present? - row.locale - else - SiteSetting.default_locale - end + notification_locale = if SiteSetting.allow_user_locale && row.locale.present? + row.locale + else + SiteSetting.default_locale + end - # Make this variable in this scope - notification = nil + next if row.staff && badge.awarded_for_trust_level? - next if (row.staff && badge.awarded_for_trust_level?) - - I18n.with_locale(notification_locale) do - notification = Notification.create!( - user_id: row.user_id, - notification_type: Notification.types[:granted_badge], - data: { - badge_id: badge.id, - badge_name: badge.display_name, - badge_slug: badge.slug, - badge_title: badge.allow_title, - username: row.username - }.to_json) + notification = I18n.with_locale(notification_locale) do + Notification.create!( + user_id: row.user_id, + notification_type: Notification.types[:granted_badge], + data: { + badge_id: badge.id, + badge_name: badge.display_name, + badge_slug: badge.slug, + badge_title: badge.allow_title, + username: row.username + }.to_json + ) end DB.exec( @@ -345,9 +350,9 @@ class BadgeGranter end badge.reset_grant_count! - rescue => ex + rescue => e Rails.logger.error("Failed to backfill '#{badge.name}' badge: #{opts}") - raise ex + raise e end def self.revoke_ungranted_titles! diff --git a/spec/services/badge_granter_spec.rb b/spec/services/badge_granter_spec.rb index 451cb158a1..e79288ce7e 100644 --- a/spec/services/badge_granter_spec.rb +++ b/spec/services/badge_granter_spec.rb @@ -74,31 +74,30 @@ describe BadgeGranter do end it 'should grant missing badges' do + nice_topic = Badge.find(Badge::NiceTopic) good_topic = Badge.find(Badge::GoodTopic) post = Fabricate(:post, like_count: 30) + 2.times { - BadgeGranter.backfill(Badge.find(Badge::NiceTopic), post_ids: [post.id]) + BadgeGranter.backfill(nice_topic, post_ids: [post.id]) BadgeGranter.backfill(good_topic) } # TODO add welcome - expect(post.user.user_badges.pluck(:badge_id).sort).to eq([Badge::NiceTopic, Badge::GoodTopic]) - + expect(post.user.user_badges.pluck(:badge_id)).to contain_exactly(nice_topic.id, good_topic.id) expect(post.user.notifications.count).to eq(2) - notification = post.user.notifications.last - data = notification.data_hash + data = post.user.notifications.last.data_hash expect(data["badge_id"]).to eq(good_topic.id) expect(data["badge_slug"]).to eq(good_topic.slug) expect(data["username"]).to eq(post.user.username) - expect(Badge.find(Badge::NiceTopic).grant_count).to eq(1) - expect(Badge.find(Badge::GoodTopic).grant_count).to eq(1) + expect(nice_topic.grant_count).to eq(1) + expect(good_topic.grant_count).to eq(1) end it 'should grant badges in the user locale' do - SiteSetting.allow_user_locale = true nice_topic = Badge.find(Badge::NiceTopic) From 95e5f8380d09542a57a99de8ca94596561b1b9d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9gis=20Hanol?= Date: Fri, 4 Jan 2019 15:14:16 +0100 Subject: [PATCH 036/157] FEATURE: Allow plugins to add custom emoji translations FIX: buildTranslationTree was erroring when translations overlapped (ie. ":-)" and ":-))") FIX: emoji translations wasn't working properly when translations overlapped --- .../pretty-text/emoji/data.js.es6.erb | 31 +--------- .../engines/discourse-markdown/emoji.js.es6 | 58 +++++++----------- app/models/emoji.rb | 60 +++++++++++-------- lib/emoji/db.json | 30 +++++++++- lib/plugin/instance.rb | 13 ++++ 5 files changed, 98 insertions(+), 94 deletions(-) diff --git a/app/assets/javascripts/pretty-text/emoji/data.js.es6.erb b/app/assets/javascripts/pretty-text/emoji/data.js.es6.erb index 765ecc3303..32a9b986bd 100644 --- a/app/assets/javascripts/pretty-text/emoji/data.js.es6.erb +++ b/app/assets/javascripts/pretty-text/emoji/data.js.es6.erb @@ -1,32 +1,5 @@ export const emojis = <%= Emoji.standard.map(&:name).flatten.inspect %>; export const tonableEmojis = <%= Emoji.tonable_emojis.flatten.inspect %>; export const aliases = <%= Emoji.aliases.inspect.gsub("=>", ":") %>; -export const searchAliases = <%= Emoji.searchAliases.inspect.gsub("=>", ":") %>; -export const translations = { - ':)' : 'slight_smile', - ':-)' : 'slight_smile', - '^_^' : 'slight_smile', - '^__^' : 'slight_smile', - ':(' : 'frowning', - ':-(' : 'frowning', - ';)' : 'wink', - ';-)' : 'wink', - ':\'(' : 'cry', - ':\'-(': 'cry', - ':-\'(': 'cry', - ':p' : 'stuck_out_tongue', - ':P' : 'stuck_out_tongue', - ':-P' : 'stuck_out_tongue', - ':O' : 'open_mouth', - ':-O' : 'open_mouth', - ':D' : 'smiley', - ':-D' : 'smiley', - ':|' : 'expressionless', - ':-|' : 'expressionless', - ':/' : 'confused', - '8-)' : 'sunglasses', - ";P" : 'stuck_out_tongue_winking_eye', - ";-P" : 'stuck_out_tongue_winking_eye', - ":$" : 'blush', - ":-$" : 'blush' -}; +export const searchAliases = <%= Emoji.search_aliases.inspect.gsub("=>", ":") %>; +export const translations = <%= Emoji.translations.inspect.gsub("=>", ":") %>; diff --git a/app/assets/javascripts/pretty-text/engines/discourse-markdown/emoji.js.es6 b/app/assets/javascripts/pretty-text/engines/discourse-markdown/emoji.js.es6 index 548cf74638..ad9e83414f 100644 --- a/app/assets/javascripts/pretty-text/engines/discourse-markdown/emoji.js.es6 +++ b/app/assets/javascripts/pretty-text/engines/discourse-markdown/emoji.js.es6 @@ -9,22 +9,18 @@ let translationTree = null; // We build a data structure that allows us to quickly // search through our N next chars to see if any match // one of our alias emojis. -// function buildTranslationTree() { let tree = []; let lastNode; - Object.keys(translations).forEach(function(key) { - let i; + Object.keys(translations).forEach(key => { let node = tree; - for (i = 0; i < key.length; i++) { + for (let i = 0; i < key.length; i++) { let code = key.charCodeAt(i); - let j; - let found = false; - for (j = 0; j < node.length; j++) { + for (let j = 0; j < node.length; j++) { if (node[j][0] === code) { node = node[j][1]; found = true; @@ -33,7 +29,7 @@ function buildTranslationTree() { } if (!found) { - // token, children, value + // code, children, value let tmp = [code, []]; node.push(tmp); lastNode = tmp; @@ -41,7 +37,7 @@ function buildTranslationTree() { } } - lastNode[1] = translations[key]; + lastNode[2] = translations[key]; }); return tree; @@ -121,28 +117,22 @@ function getEmojiTokenByName(name, state) { function getEmojiTokenByTranslation(content, pos, state) { translationTree = translationTree || buildTranslationTree(); - let currentTree = translationTree; - - let i; - let search = true; - let found = false; + let t = translationTree; let start = pos; + let found = null; - while (search) { - search = false; + while (t.length > 0 && pos < content.length) { let code = content.charCodeAt(pos); - for (i = 0; i < currentTree.length; i++) { - if (currentTree[i][0] === code) { - currentTree = currentTree[i][1]; - pos++; - search = true; - if (typeof currentTree === "string") { - found = currentTree; - } + for (let i = 0; i < t.length; i++) { + if (t[i][0] === code) { + found = t[i][2]; + t = t[i][1]; break; } } + + pos++; } if (!found) { @@ -174,17 +164,9 @@ function getEmojiTokenByTranslation(content, pos, state) { } } -function applyEmoji( - content, - state, - emojiUnicodeReplacer, - enableShortcuts, - inlineEmoji -) { - let i; +function applyEmoji(content, state, emojiUnicodeReplacer, enableShortcuts, inlineEmoji) { let result = null; let contentToken = null; - let start = 0; if (emojiUnicodeReplacer) { @@ -193,10 +175,10 @@ function applyEmoji( let endToken = content.length; - for (i = 0; i < content.length - 1; i++) { + for (let i = 0; i < content.length - 1; i++) { let offset = 0; - const emojiName = getEmojiName(content, i, state, inlineEmoji); let token = null; + const emojiName = getEmojiName(content, i, state, inlineEmoji); if (emojiName) { token = getEmojiTokenByName(emojiName, state); @@ -207,8 +189,7 @@ function applyEmoji( if (enableShortcuts && !token) { // handle aliases (note: we can't do this in inline cause ; is not a split point) - // - let info = getEmojiTokenByTranslation(content, i, state); + const info = getEmojiTokenByTranslation(content, i, state); if (info) { offset = info.pos - i; @@ -225,7 +206,8 @@ function applyEmoji( } result.push(token); - endToken = start = i + offset; + i += offset; + endToken = start = i; } } diff --git a/app/models/emoji.rb b/app/models/emoji.rb index e7889896ef..c20d26ef06 100644 --- a/app/models/emoji.rb +++ b/app/models/emoji.rb @@ -25,10 +25,14 @@ class Emoji Discourse.cache.fetch(cache_key("aliases_emojis")) { db['aliases'] } end - def self.searchAliases + def self.search_aliases Discourse.cache.fetch(cache_key("search_aliases_emojis")) { db['searchAliases'] } end + def self.translations + Discourse.cache.fetch(cache_key("translations_emojis")) { load_translations } + end + def self.custom Discourse.cache.fetch(cache_key("custom_emojis")) { load_custom } end @@ -63,13 +67,13 @@ class Emoji end def self.clear_cache - %w{custom standard aliases search_aliases all tonable}.each do |key| + %w{custom standard aliases search_aliases translations all tonable}.each do |key| Discourse.cache.delete(cache_key("#{key}_emojis")) end end def self.db_file - "#{Rails.root}/lib/emoji/db.json" + @db_file ||= "#{Rails.root}/lib/emoji/db.json" end def self.db @@ -101,6 +105,10 @@ class Emoji result end + def self.load_translations + db["translations"].merge(Plugin::CustomEmoji.translations) + end + def self.base_directory "public#{base_url}" end @@ -117,35 +125,35 @@ class Emoji end def self.unicode_replacements - return @unicode_replacements if @unicode_replacements + @unicode_replacements ||= begin + replacements = {} + is_tonable_emojis = Emoji.tonable_emojis + fitzpatrick_scales = FITZPATRICK_SCALE.map { |scale| scale.to_i(16) } - @unicode_replacements = {} - is_tonable_emojis = Emoji.tonable_emojis - fitzpatrick_scales = FITZPATRICK_SCALE.map { |scale| scale.to_i(16) } + db['emojis'].each do |e| + name = e['name'] + next if name == 'tm'.freeze - db['emojis'].each do |e| - name = e['name'] - next if name == 'tm'.freeze + code = replacement_code(e['code']) + next unless code - code = replacement_code(e['code']) - next unless code - - @unicode_replacements[code] = name - if is_tonable_emojis.include?(name) - fitzpatrick_scales.each_with_index do |scale, index| - toned_code = code.codepoints.insert(1, scale).pack("U*".freeze) - @unicode_replacements[toned_code] = "#{name}:t#{index + 2}" + replacements[code] = name + if is_tonable_emojis.include?(name) + fitzpatrick_scales.each_with_index do |scale, index| + toned_code = code.codepoints.insert(1, scale).pack("U*".freeze) + replacements[toned_code] = "#{name}:t#{index + 2}" + end end end + + replacements["\u{2639}"] = 'frowning' + replacements["\u{263A}"] = 'slight_smile' + replacements["\u{263B}"] = 'slight_smile' + replacements["\u{2661}"] = 'heart' + replacements["\u{2665}"] = 'heart' + + replacements end - - @unicode_replacements["\u{2639}"] = 'frowning' - @unicode_replacements["\u{263A}"] = 'slight_smile' - @unicode_replacements["\u{263B}"] = 'slight_smile' - @unicode_replacements["\u{2661}"] = 'heart' - @unicode_replacements["\u{2665}"] = 'heart' - - @unicode_replacements end def self.unicode_unescape(string) diff --git a/lib/emoji/db.json b/lib/emoji/db.json index 31bf152c41..90c09f070d 100644 --- a/lib/emoji/db.json +++ b/lib/emoji/db.json @@ -7115,5 +7115,33 @@ "cry": [ "sob" ] + }, + "translations": { + ":)" : "slight_smile", + ":-)" : "slight_smile", + "^_^" : "slight_smile", + "^__^": "slight_smile", + ":(" : "frowning", + ":-(" : "frowning", + ";)" : "wink", + ";-)" : "wink", + ":'(" : "cry", + ":'-(": "cry", + ":-'(": "cry", + ":p" : "stuck_out_tongue", + ":P" : "stuck_out_tongue", + ":-P" : "stuck_out_tongue", + ":O" : "open_mouth", + ":-O" : "open_mouth", + ":D" : "smiley", + ":-D" : "smiley", + ":|" : "expressionless", + ":-|" : "expressionless", + ":/" : "confused", + "8-)" : "sunglasses", + ";P" : "stuck_out_tongue_winking_eye", + ";-P" : "stuck_out_tongue_winking_eye", + ":$" : "blush", + ":-$" : "blush" } -} \ No newline at end of file +} diff --git a/lib/plugin/instance.rb b/lib/plugin/instance.rb index b19d6cedb9..53552d0b47 100644 --- a/lib/plugin/instance.rb +++ b/lib/plugin/instance.rb @@ -16,6 +16,15 @@ class Plugin::CustomEmoji @@cache_key = Digest::SHA1.hexdigest(cache_key + name)[0..10] emojis[name] = url end + + def self.translations + @@translations ||= {} + end + + def self.translate(from, to) + @@cache_key = Digest::SHA1.hexdigest(cache_key + from)[0..10] + translations[from] = to + end end class Plugin::Instance @@ -429,6 +438,10 @@ class Plugin::Instance Plugin::CustomEmoji.register(name, url) end + def translate_emoji(from, to) + Plugin::CustomEmoji.translate(from, to) + end + def automatic_assets css = styles.join("\n") js = javascripts.join("\n") From 788719d27143ec2a26feba1a84c413d958df13ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9gis=20Hanol?= Date: Fri, 4 Jan 2019 15:30:17 +0100 Subject: [PATCH 037/157] DEV: speed up posts base imports --- lib/post_creator.rb | 22 +++++------- lib/upload_creator.rb | 14 ++------ script/import_scripts/base.rb | 64 +++++++++++++++++++++++------------ 3 files changed, 54 insertions(+), 46 deletions(-) diff --git a/lib/post_creator.rb b/lib/post_creator.rb index c5c09d04d1..173a30cf20 100644 --- a/lib/post_creator.rb +++ b/lib/post_creator.rb @@ -174,15 +174,14 @@ class PostCreator update_user_counts create_embedded_topic link_post_uploads - ensure_in_allowed_users if guardian.is_staff? unarchive_message - @post.advance_draft_sequence + @post.advance_draft_sequence unless @opts[:import_mode] @post.save_reply_relationships end end - if @post && errors.blank? + if @post && errors.blank? && !@opts[:import_mode] # update counters etc. @post.topic.reload @@ -194,12 +193,10 @@ class PostCreator trigger_after_events unless opts[:skip_events] - auto_close unless @opts[:import_mode] + auto_close end - if @post || @spam - handle_spam unless @opts[:import_mode] - end + handle_spam if !opts[:import_mode] && (@post || @spam) @post end @@ -428,6 +425,8 @@ class PostCreator end def update_topic_auto_close + return if @opts[:import_mode] + if @topic.closed? @topic.delete_topic_timer(TopicTimer.types[:close]) else @@ -510,9 +509,7 @@ class PostCreator end def publish - return if @opts[:import_mode] - return unless @post.post_number > 1 - + return if @opts[:import_mode] || @post.post_number == 1 @post.publish_change_to_clients! :created end @@ -522,7 +519,7 @@ class PostCreator end def track_topic - return if @opts[:auto_track] == false + return if @opts[:import_mode] || @opts[:auto_track] == false unless @user.user_option.disable_jump_reply? TopicUser.change(@post.user_id, @@ -540,8 +537,7 @@ class PostCreator if @user.staged TopicUser.auto_notification_for_staging(@user.id, @topic.id, TopicUser.notification_reasons[:auto_watch]) - else - return if @topic.private_message? + elsif !@topic.private_message? notification_level = @user.user_option.notification_level_when_replying || NotificationLevels.topic_levels[:tracking] TopicUser.auto_notification(@user.id, @topic.id, TopicUser.notification_reasons[:created_post], notification_level) end diff --git a/lib/upload_creator.rb b/lib/upload_creator.rb index 2d7cc9166c..a1386feb2e 100644 --- a/lib/upload_creator.rb +++ b/lib/upload_creator.rb @@ -80,8 +80,8 @@ class UploadCreator end fixed_original_filename = nil - if is_image + if is_image current_extension = File.extname(@filename).downcase.sub("jpeg", "jpg") expected_extension = ".#{image_type}".downcase.sub("jpeg", "jpg") @@ -89,11 +89,7 @@ class UploadCreator # otherwise validation will fail and we can not save # TODO decide if we only run the validation on the extension if current_extension != expected_extension - basename = File.basename(@filename, current_extension) - - if basename.length == 0 - basename = "image" - end + basename = File.basename(@filename, current_extension).presence || "image" fixed_original_filename = "#{basename}#{expected_extension}" end end @@ -173,10 +169,7 @@ class UploadCreator MIN_CONVERT_TO_JPEG_SAVING_RATIO = 0.70 def convert_to_jpeg! - - if filesize < MIN_CONVERT_TO_JPEG_BYTES_SAVED - return - end + return if filesize < MIN_CONVERT_TO_JPEG_BYTES_SAVED jpeg_tempfile = Tempfile.new(["image", ".jpg"]) @@ -290,7 +283,6 @@ class UploadCreator def crop! max_pixel_ratio = Discourse::PIXEL_RATIOS.max - filename_with_correct_ext = "image.#{@image_info.type}" case @opts[:type] diff --git a/script/import_scripts/base.rb b/script/import_scripts/base.rb index 3ce3ef0452..30dfb7cb08 100644 --- a/script/import_scripts/base.rb +++ b/script/import_scripts/base.rb @@ -54,6 +54,8 @@ class ImportScripts::Base update_last_posted_at update_last_seen_at update_user_stats + update_topic_users + update_post_timings update_feature_topic_users update_category_featured_topics update_topic_count_replies @@ -313,7 +315,7 @@ class ImportScripts::Base unless opts[:email][EmailValidator.email_regex] opts[:email] = fake_email - puts "Invalid email #{original_email} for #{opts[:username]}. Using: #{opts[:email]}" + puts "Invalid email '#{original_email}' for '#{opts[:username]}'. Using '#{opts[:email]}'" end opts[:name] = original_username if original_name.blank? && opts[:username] != original_username @@ -326,7 +328,7 @@ class ImportScripts::Base u = User.new(opts) (opts[:custom_fields] || {}).each { |k, v| u.custom_fields[k] = v } u.custom_fields["import_id"] = import_id - u.custom_fields["import_username"] = opts[:username] if original_username.present? + u.custom_fields["import_username"] = original_username if original_username.present? && original_username != opts[:username] u.custom_fields["import_avatar_url"] = avatar_url if avatar_url.present? u.custom_fields["import_pass"] = opts[:password] if opts[:password].present? u.custom_fields["import_email"] = original_email if original_email != opts[:email] @@ -506,21 +508,19 @@ class ImportScripts::Base import_id = params.delete(:id).to_s if post_id_from_imported_post_id(import_id) - skipped += 1 # already imported this post + skipped += 1 else begin new_post = create_post(params, import_id) if new_post.is_a?(Post) add_post(import_id, new_post) add_topic(new_post) - created_post(new_post) - created += 1 else skipped += 1 puts "Error creating post #{import_id}. Skipping." - puts new_post.inspect + p new_post end rescue Discourse::InvalidAccess => e skipped += 1 @@ -632,7 +632,7 @@ class ImportScripts::Base def update_topic_status puts "", "Updating topic status" - DB.exec(<<~SQL) + DB.exec <<~SQL UPDATE topics AS t SET closed = TRUE WHERE EXISTS( @@ -642,7 +642,7 @@ class ImportScripts::Base ) SQL - DB.exec(<<~SQL) + DB.exec <<~SQL UPDATE topics AS t SET archived = TRUE WHERE EXISTS( @@ -652,7 +652,7 @@ class ImportScripts::Base ) SQL - DB.exec(<<~SQL) + DB.exec <<~SQL DELETE FROM topic_custom_fields WHERE name IN ('import_closed', 'import_archived') SQL @@ -660,13 +660,16 @@ class ImportScripts::Base def update_bumped_at puts "", "Updating bumped_at on topics" - DB.exec("update topics t set bumped_at = COALESCE((select max(created_at) from posts where topic_id = t.id and post_type = #{Post.types[:regular]}), bumped_at)") + DB.exec <<~SQL + UPDATE topics t + SET bumped_at = COALESCE((SELECT MAX(created_at) FROM posts WHERE topic_id = t.id AND post_type = #{Post.types[:regular]}), bumped_at) + SQL end def update_last_posted_at puts "", "Updating last posted at on users" - sql = <<-SQL + DB.exec <<~SQL WITH lpa AS ( SELECT user_id, MAX(posts.created_at) AS last_posted_at FROM posts @@ -679,8 +682,6 @@ class ImportScripts::Base WHERE u1.id = users.id AND users.last_posted_at <> lpa.last_posted_at SQL - - DB.exec(sql) end def update_user_stats @@ -699,7 +700,7 @@ class ImportScripts::Base puts "", "Updating first_post_created_at..." - sql = <<-SQL + DB.exec <<~SQL WITH sub AS ( SELECT user_id, MIN(posts.created_at) AS first_post_created_at FROM posts @@ -713,11 +714,9 @@ class ImportScripts::Base AND user_stats.first_post_created_at <> sub.first_post_created_at SQL - DB.exec(sql) - puts "", "Updating user post_count..." - sql = <<-SQL + DB.exec <<~SQL WITH sub AS ( SELECT user_id, COUNT(*) AS post_count FROM posts @@ -731,11 +730,9 @@ class ImportScripts::Base AND user_stats.post_count <> sub.post_count SQL - DB.exec(sql) - puts "", "Updating user topic_count..." - sql = <<-SQL + DB.exec <<~SQL WITH sub AS ( SELECT user_id, COUNT(*) AS topic_count FROM topics @@ -748,8 +745,6 @@ class ImportScripts::Base WHERE u1.user_id = user_stats.user_id AND user_stats.topic_count <> sub.topic_count SQL - - DB.exec(sql) end # scripts that are able to import last_seen_at from the source data should override this method @@ -760,6 +755,31 @@ class ImportScripts::Base DB.exec("UPDATE users SET last_seen_at = last_posted_at WHERE last_posted_at IS NOT NULL") end + def update_topic_users + puts "", "Updating topic users" + + DB.exec <<~SQL + INSERT INTO topic_users (user_id, topic_id, posted, last_read_post_number, highest_seen_post_number, first_visited_at, last_visited_at, total_msecs_viewed) + SELECT user_id, topic_id, 't' , MAX(post_number), MAX(post_number), MIN(created_at), MAX(created_at), COUNT(id) * 5000 + FROM posts + WHERE user_id > 0 + GROUP BY user_id, topic_id + ON CONFLICT DO NOTHING + SQL + end + + def update_post_timings + puts "", "Updating post timings" + + DB.exec <<~SQL + INSERT INTO post_timings (topic_id, post_number, user_id, msecs) + SELECT topic_id, post_number, user_id, 5000 + FROM posts + WHERE user_id > 0 + ON CONFLICT DO NOTHING + SQL + end + def update_feature_topic_users puts "", "Updating featured topic users" From c0a8bb9a919ff5a11ed453007723ba671b12977d Mon Sep 17 00:00:00 2001 From: Gerhard Schlager Date: Fri, 4 Jan 2019 16:06:21 +0100 Subject: [PATCH 038/157] FEATURE: Include "via " in email From header --- app/mailers/user_notifications.rb | 2 +- config/locales/server.en.yml | 1 + lib/email.rb | 4 ++++ lib/email/message_builder.rb | 8 ++++---- spec/mailers/user_notifications_spec.rb | 10 +++++----- 5 files changed, 15 insertions(+), 10 deletions(-) diff --git a/app/mailers/user_notifications.rb b/app/mailers/user_notifications.rb index af664694dc..429e78b17a 100644 --- a/app/mailers/user_notifications.rb +++ b/app/mailers/user_notifications.rb @@ -412,7 +412,7 @@ class UserNotifications < ActionMailer::Base title: notification_data[:topic_title], post: post, username: original_username, - from_alias: user_name, + from_alias: I18n.t('email_from', user_name: user_name, site_name: Email.site_title), allow_reply_by_email: allow_reply_by_email, use_site_subject: opts[:use_site_subject], add_re_to_subject: opts[:add_re_to_subject], diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index 0cfb9cdd12..906e5eea50 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -2884,6 +2884,7 @@ en: subject_re: "Re: " subject_pm: "[PM] " + email_from: "%{user_name} via %{site_name}" user_notifications: previous_discussion: "Previous Replies" diff --git a/lib/email.rb b/lib/email.rb index ed9182980d..9b7e13e81d 100644 --- a/lib/email.rb +++ b/lib/email.rb @@ -37,4 +37,8 @@ module Email [text&.decoded, html&.decoded] end + def self.site_title + SiteSetting.email_site_title.presence || SiteSetting.title + end + end diff --git a/lib/email/message_builder.rb b/lib/email/message_builder.rb index 0d39cd5eff..146f49774b 100644 --- a/lib/email/message_builder.rb +++ b/lib/email/message_builder.rb @@ -208,8 +208,8 @@ module Email SiteSetting.email_site_title.blank? && SiteSetting.title.blank? - if !@opts[:from_alias].blank? - "\"#{Email.cleanup_alias(@opts[:from_alias])}\" <#{source}>" + if @opts[:from_alias].present? + %Q|"#{Email.cleanup_alias(@opts[:from_alias])}" <#{source}>| elsif source == SiteSetting.notification_email || source == SiteSetting.reply_by_email_address site_alias_email(source) else @@ -218,8 +218,8 @@ module Email end def site_alias_email(source) - from_alias = SiteSetting.email_site_title.presence || SiteSetting.title - "\"#{Email.cleanup_alias(from_alias)}\" <#{source}>" + from_alias = Email.site_title + %Q|"#{Email.cleanup_alias(from_alias)}" <#{source}>| end end diff --git a/spec/mailers/user_notifications_spec.rb b/spec/mailers/user_notifications_spec.rb index bd56dbd542..4c350124ee 100644 --- a/spec/mailers/user_notifications_spec.rb +++ b/spec/mailers/user_notifications_spec.rb @@ -247,7 +247,7 @@ describe UserNotifications do notification_data_hash: notification.data_hash ) # from should include full user name - expect(mail[:from].display_names).to eql(['John Doe']) + expect(mail[:from].display_names).to eql(['John Doe via Discourse']) # subject should include category name expect(mail.subject).to match(/India/) @@ -350,7 +350,7 @@ describe UserNotifications do expect(mail[:from].display_names).to_not eql(['John Doe']) # from should include username if "show user full names" is disabled - expect(mail[:from].display_names).to eql(['john']) + expect(mail[:from].display_names).to eql(['john via Discourse']) # subject should not include category name expect(mail.subject).not_to match(/Uncategorized/) @@ -397,7 +397,7 @@ describe UserNotifications do ) # from should include username if full user name is not provided - expect(mail[:from].display_names).to eql(['john']) + expect(mail[:from].display_names).to eql(['john via Discourse']) # subject should include "[PM]" expect(mail.subject).to include("[PM] ") @@ -656,13 +656,13 @@ describe UserNotifications do it "should have user name as from_alias" do SiteSetting.enable_names = true SiteSetting.display_name_on_posts = true - expects_build_with(has_entry(:from_alias, "#{user.name}")) + expects_build_with(has_entry(:from_alias, "#{user.name} via Discourse")) end it "should not have user name as from_alias if display_name_on_posts is disabled" do SiteSetting.enable_names = false SiteSetting.display_name_on_posts = false - expects_build_with(has_entry(:from_alias, "walterwhite")) + expects_build_with(has_entry(:from_alias, "walterwhite via Discourse")) end it "should explain how to respond" do From 858a456aaf3faaaa4cffd9a45e26c5da70f9ef09 Mon Sep 17 00:00:00 2001 From: Gerhard Schlager Date: Fri, 4 Jan 2019 16:08:06 +0100 Subject: [PATCH 039/157] FEATURE: Use email_site_title in From of digest emails --- app/mailers/user_notifications.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/mailers/user_notifications.rb b/app/mailers/user_notifications.rb index 429e78b17a..9d442daa8d 100644 --- a/app/mailers/user_notifications.rb +++ b/app/mailers/user_notifications.rb @@ -235,7 +235,7 @@ class UserNotifications < ActionMailer::Base @preheader_text = I18n.t('user_notifications.digest.preheader', last_seen_at: @last_seen_at) opts = { - from_alias: I18n.t('user_notifications.digest.from', site_name: SiteSetting.title), + from_alias: I18n.t('user_notifications.digest.from', site_name: Email.site_title), subject: I18n.t('user_notifications.digest.subject_template', email_prefix: @email_prefix, date: short_date(Time.now)), add_unsubscribe_link: true, unsubscribe_url: "#{Discourse.base_url}/email/unsubscribe/#{@unsubscribe_key}", From f400830575d800e7fb2fc206f0967773af120fa4 Mon Sep 17 00:00:00 2001 From: Penar Musaraj Date: Fri, 4 Jan 2019 11:14:35 -0500 Subject: [PATCH 040/157] DEV: Remove old _firefoxPastingHack --- .../components/composer-editor.js.es6 | 100 ------------------ 1 file changed, 100 deletions(-) diff --git a/app/assets/javascripts/discourse/components/composer-editor.js.es6 b/app/assets/javascripts/discourse/components/composer-editor.js.es6 index 32058c6bc4..1f08e214cd 100644 --- a/app/assets/javascripts/discourse/components/composer-editor.js.es6 +++ b/app/assets/javascripts/discourse/components/composer-editor.js.es6 @@ -760,106 +760,6 @@ export default Ember.Component.extend({ $("#mobile-uploader").click(); }); } - - this._firefoxPastingHack(); - }, - - // Believe it or not pasting an image in Firefox doesn't work without this code - _firefoxPastingHack() { - const uaMatch = navigator.userAgent.match(/Firefox\/(\d+)\.\d/); - if (uaMatch) { - let uaVersion = parseInt(uaMatch[1]); - if (uaVersion < 24 || 50 <= uaVersion) { - // The hack is no longer required in FF 50 and later. - // See: https://bugzilla.mozilla.org/show_bug.cgi?id=906420 - return; - } - this.$().append( - Ember.$( - "
" - ) - ); - this.$("textarea").off("keydown.contenteditable"); - this.$("textarea").on("keydown.contenteditable", event => { - // Catch Ctrl+v / Cmd+v and hijack focus to a contenteditable div. We can't - // use the onpaste event because for some reason the paste isn't resumed - // after we switch focus, probably because it is being executed too late. - if ((event.ctrlKey || event.metaKey) && event.keyCode === 86) { - // Save the current textarea selection. - const textarea = this.$("textarea")[0]; - const selectionStart = textarea.selectionStart; - const selectionEnd = textarea.selectionEnd; - - // Focus the contenteditable div. - const contentEditableDiv = this.$("#contenteditable"); - contentEditableDiv.focus(); - - // The paste doesn't finish immediately and we don't have any onpaste - // event, so wait for 100ms which _should_ be enough time. - Ember.run.later(() => { - const pastedImg = contentEditableDiv.find("img"); - - if (pastedImg.length === 1) { - pastedImg.remove(); - } - - // For restoring the selection. - textarea.focus(); - const textareaContent = $(textarea).val(), - startContent = textareaContent.substring(0, selectionStart), - endContent = textareaContent.substring(selectionEnd); - - const restoreSelection = function(pastedText) { - $(textarea).val(startContent + pastedText + endContent); - textarea.selectionStart = selectionStart + pastedText.length; - textarea.selectionEnd = textarea.selectionStart; - }; - - if (contentEditableDiv.html().length > 0) { - // If the image wasn't the only pasted content we just give up and - // fall back to the original pasted text. - contentEditableDiv.find("br").replaceWith("\n"); - restoreSelection(contentEditableDiv.text()); - } else { - // Depending on how the image is pasted in, we may get either a - // normal URL or a data URI. If we get a data URI we can convert it - // to a Blob and upload that, but if it is a regular URL that - // operation is prevented for security purposes. When we get a regular - // URL let's just create an tag for the image. - const imageSrc = pastedImg.attr("src"); - - if (imageSrc.match(/^data:image/)) { - // Restore the cursor position, and remove any selected text. - restoreSelection(""); - - // Create a Blob to upload. - const image = new Image(); - image.onload = () => { - // Create a new canvas. - const canvas = document.createElementNS( - "http://www.w3.org/1999/xhtml", - "canvas" - ); - canvas.height = image.height; - canvas.width = image.width; - const ctx = canvas.getContext("2d"); - ctx.drawImage(image, 0, 0); - - canvas.toBlob(blob => - this.$().fileupload("add", { files: blob }) - ); - }; - image.src = imageSrc; - } else { - restoreSelection(""); - } - } - - contentEditableDiv.html(""); - }, 100); - } - }); - } }, @on("willDestroyElement") From 5ac1e3d4cd345d75fc02eed3d1c24a23cd915ffa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9gis=20Hanol?= Date: Fri, 4 Jan 2019 17:19:44 +0100 Subject: [PATCH 041/157] FIX: emojis are hard :shrug: --- .../engines/discourse-markdown/emoji.js.es6 | 46 ++++++++++++------- 1 file changed, 30 insertions(+), 16 deletions(-) diff --git a/app/assets/javascripts/pretty-text/engines/discourse-markdown/emoji.js.es6 b/app/assets/javascripts/pretty-text/engines/discourse-markdown/emoji.js.es6 index ad9e83414f..f1fc15e7aa 100644 --- a/app/assets/javascripts/pretty-text/engines/discourse-markdown/emoji.js.es6 +++ b/app/assets/javascripts/pretty-text/engines/discourse-markdown/emoji.js.es6 @@ -122,16 +122,22 @@ function getEmojiTokenByTranslation(content, pos, state) { let found = null; while (t.length > 0 && pos < content.length) { + let matched = false; let code = content.charCodeAt(pos); for (let i = 0; i < t.length; i++) { if (t[i][0] === code) { + matched = true; found = t[i][2]; t = t[i][1]; break; } } + if (!matched) { + return; + } + pos++; } @@ -164,26 +170,32 @@ function getEmojiTokenByTranslation(content, pos, state) { } } -function applyEmoji(content, state, emojiUnicodeReplacer, enableShortcuts, inlineEmoji) { +function applyEmoji( + content, + state, + emojiUnicodeReplacer, + enableShortcuts, + inlineEmoji +) { let result = null; - let contentToken = null; let start = 0; if (emojiUnicodeReplacer) { content = emojiUnicodeReplacer(content); } - let endToken = content.length; + let end = content.length; for (let i = 0; i < content.length - 1; i++) { let offset = 0; let token = null; - const emojiName = getEmojiName(content, i, state, inlineEmoji); - if (emojiName) { - token = getEmojiTokenByName(emojiName, state); + const name = getEmojiName(content, i, state, inlineEmoji); + + if (name) { + token = getEmojiTokenByName(name, state); if (token) { - offset = emojiName.length + 2; + offset = name.length + 2; } } @@ -199,22 +211,24 @@ function applyEmoji(content, state, emojiUnicodeReplacer, enableShortcuts, inlin if (token) { result = result || []; + if (i - start > 0) { - contentToken = new state.Token("text", "", 0); - contentToken.content = content.slice(start, i); - result.push(contentToken); + let text = new state.Token("text", "", 0); + text.content = content.slice(start, i); + result.push(text); } result.push(token); - i += offset; - endToken = start = i; + + end = start = i + offset; + i += offset - 1; } } - if (endToken < content.length) { - contentToken = new state.Token("text", "", 0); - contentToken.content = content.slice(endToken); - result.push(contentToken); + if (end < content.length) { + let text = new state.Token("text", "", 0); + text.content = content.slice(end); + result.push(text); } return result; From 5eaf3cb1047412cb4f295e75dea4fb6eae580596 Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Fri, 4 Jan 2019 13:14:50 -0500 Subject: [PATCH 042/157] Adjusts the `minimum_flag_threshold` for TL3/TL4 actions Before this patch, a high trust level user could flag something and have an action be taken, as well as skipping the flag queue. Now, if a TL3/TL4 cause an action, the flag will skip the minimum visibility check and allow staff to review it. --- app/models/post_action.rb | 26 ++++++++++++++++++++---- lib/flag_query.rb | 4 ++-- spec/components/flag_query_spec.rb | 32 ++++++++++++++++++++++++++++-- spec/models/post_action_spec.rb | 20 +++++++++++++++++++ 4 files changed, 74 insertions(+), 8 deletions(-) diff --git a/app/models/post_action.rb b/app/models/post_action.rb index 8efa750b53..1e533b0a9b 100644 --- a/app/models/post_action.rb +++ b/app/models/post_action.rb @@ -52,6 +52,26 @@ class PostAction < ActiveRecord::Base .count end + # Forums can choose to apply a minimum number of flags required before it shows up in + # the admin interface. One exception is posts hidden by tl3/tl4 - we want those to + # show up even if the minimum visibility is not met. + def self.apply_minimum_visibility(relation) + return relation unless SiteSetting.min_flags_staff_visibility > 1 + + params = { + min_flags: SiteSetting.min_flags_staff_visibility, + hidden_reasons: Post.hidden_reasons.only(:flagged_by_tl3_user, :flagged_by_tl4_user).values + } + + relation.having(<<~SQL, params) + (COUNT(*) >= :min_flags) OR + (SUM(CASE + WHEN posts.hidden_reason_id IN (:hidden_reasons) THEN 1 + ELSE 0 + END) > 0) + SQL + end + def self.update_flagged_posts_count flagged_relation = PostAction.active .flags @@ -61,10 +81,7 @@ class PostAction < ActiveRecord::Base .where('posts.user_id > 0') .group("posts.id") - if SiteSetting.min_flags_staff_visibility > 1 - flagged_relation = flagged_relation - .having("count(*) >= ?", SiteSetting.min_flags_staff_visibility) - end + flagged_relation = apply_minimum_visibility(flagged_relation) posts_flagged_count = flagged_relation .pluck("posts.id") @@ -638,6 +655,7 @@ class PostAction < ActiveRecord::Base message_type: hiding_again ? :post_hidden_again : :post_hidden, message_options: options) end + update_flagged_posts_count end def self.guess_hide_reason(post) diff --git a/lib/flag_query.rb b/lib/flag_query.rb index f3ef77ec0f..cd0ed15089 100644 --- a/lib/flag_query.rb +++ b/lib/flag_query.rb @@ -35,8 +35,8 @@ module FlagQuery .group(:post_id) .order('MIN(post_actions.created_at) DESC') - if opts[:filter] != "old" && SiteSetting.min_flags_staff_visibility > 1 - post_ids_relation = post_ids_relation.having("count(*) >= ?", SiteSetting.min_flags_staff_visibility) + if opts[:filter] != "old" + post_ids_relation = PostAction.apply_minimum_visibility(post_ids_relation) end post_ids = post_ids_relation.pluck(:post_id).uniq diff --git a/spec/components/flag_query_spec.rb b/spec/components/flag_query_spec.rb index 428a5aa319..aee160780e 100644 --- a/spec/components/flag_query_spec.rb +++ b/spec/components/flag_query_spec.rb @@ -114,11 +114,11 @@ describe FlagQuery do it "respects `min_flags_staff_visibility`" do admin = Fabricate(:admin) - moderator = Fabricate(:moderator) + flagger = Fabricate(:user) post = create_post - PostAction.act(moderator, post, PostActionType.types[:spam]) + PostAction.act(flagger, post, PostActionType.types[:spam]) SiteSetting.min_flags_staff_visibility = 2 posts, topics, users = FlagQuery.flagged_posts_report(admin) @@ -133,5 +133,33 @@ describe FlagQuery do expect(users).to be_present end + it "respects `min_flags_staff_visibility` for tl3 hidden spam" do + admin = Fabricate(:admin) + tl3 = Fabricate(:user, trust_level: 3) + post = create_post + + post.user.update_column(:trust_level, 0) + PostAction.act(tl3, post, PostActionType.types[:spam]) + + SiteSetting.min_flags_staff_visibility = 2 + posts, topics, users = FlagQuery.flagged_posts_report(admin) + expect(posts).to be_present + expect(topics).to be_present + expect(users).to be_present + end + + it "respects `min_flags_staff_visibility` for tl4 hidden posts" do + admin = Fabricate(:admin) + tl4 = Fabricate(:user, trust_level: 4) + post = create_post + PostAction.act(tl4, post, PostActionType.types[:spam]) + + SiteSetting.min_flags_staff_visibility = 2 + posts, topics, users = FlagQuery.flagged_posts_report(admin) + expect(posts).to be_present + expect(topics).to be_present + expect(users).to be_present + end + end end diff --git a/spec/models/post_action_spec.rb b/spec/models/post_action_spec.rb index a99ecc795e..823128d1a8 100644 --- a/spec/models/post_action_spec.rb +++ b/spec/models/post_action_spec.rb @@ -160,6 +160,26 @@ describe PostAction do expect(PostAction.flagged_posts_count).to eq(1) end + it "tl3 hidden posts will supersede min_flags_staff_visibility" do + SiteSetting.min_flags_staff_visibility = 2 + expect(PostAction.flagged_posts_count).to eq(0) + + codinghorror.update_column(:trust_level, 3) + post.user.update_column(:trust_level, 0) + PostAction.act(codinghorror, post, PostActionType.types[:spam]) + expect(PostAction.flagged_posts_count).to eq(1) + end + + it "tl4 hidden posts will supersede min_flags_staff_visibility" do + SiteSetting.min_flags_staff_visibility = 2 + expect(PostAction.flagged_posts_count).to eq(0) + + codinghorror.update_column(:trust_level, 4) + PostAction.act(codinghorror, post, PostActionType.types[:off_topic]) + + expect(PostAction.flagged_posts_count).to eq(1) + end + it "should reset counts when a topic is deleted" do PostAction.act(codinghorror, post, PostActionType.types[:off_topic]) post.topic.trash! From efc481d9c09f2ace79a26d064ec9b3473f25cd8f Mon Sep 17 00:00:00 2001 From: Rishabh Date: Sat, 5 Jan 2019 01:20:00 +0530 Subject: [PATCH 043/157] DEV: Use puts instead of printing newline (follow up on c5b7bda1) --- lib/tasks/uploads.rake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/tasks/uploads.rake b/lib/tasks/uploads.rake index d481dcb08f..a86d6b922b 100644 --- a/lib/tasks/uploads.rake +++ b/lib/tasks/uploads.rake @@ -278,7 +278,7 @@ def migrate_to_s3 end puts " => #{s3_objects.size} files" - print " - Syncing files to S3\n" + puts " - Syncing files to S3" synced = 0 failed = [] From aba18a42a7a115b7cb19f11f78a1fb83632ac829 Mon Sep 17 00:00:00 2001 From: Vinoth Kannan Date: Sat, 5 Jan 2019 03:51:15 +0530 Subject: [PATCH 044/157] UX: Improve PM small header alignment with recipient avatars --- .../stylesheets/common/base/header.scss | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/app/assets/stylesheets/common/base/header.scss b/app/assets/stylesheets/common/base/header.scss index 8b98edb954..36fd35aab9 100644 --- a/app/assets/stylesheets/common/base/header.scss +++ b/app/assets/stylesheets/common/base/header.scss @@ -258,7 +258,8 @@ .badge-wrapper { margin-right: 8px; &.bullet { - padding-top: 1px; // alignment hack + padding-top: 2px; // alignment hack + line-height: 18px; } } .badge-wrapper.bullet { @@ -287,7 +288,10 @@ white-space: nowrap; text-overflow: ellipsis; .discourse-tag { - display: inline; + display: block; + float: left; + line-height: 18px; + margin: 2px 4px 0 0; } } } @@ -299,28 +303,28 @@ } .topic-header-participants { - &:not(:first-child) { - margin-left: 4px; - } - > span { margin: 0 2px; - display: inline-block; + display: block; + float: left; height: 20px; } .trigger-group-card { + display: block; + float: left; margin: 0 4px; - padding: 1px 4px 0; + padding: 1px 4px; border: 1px solid $primary-low; border-radius: 0.25em; align-items: center; + height: 16px; a { color: $primary-high; .d-icon { - margin-right: 4px; + margin: 1px 4px 0 0; } } } From 3589f3e0237ca0da8eec108db794120c6edba370 Mon Sep 17 00:00:00 2001 From: Vinoth Kannan Date: Sat, 5 Jan 2019 04:58:56 +0530 Subject: [PATCH 045/157] UX: Display user and group cards over the small PM header --- .../discourse/mixins/card-contents-base.js.es6 | 14 +++++++++++++- app/assets/stylesheets/desktop/user-card.scss | 4 ++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/discourse/mixins/card-contents-base.js.es6 b/app/assets/javascripts/discourse/mixins/card-contents-base.js.es6 index 7c00979de4..1e9160d3f7 100644 --- a/app/assets/javascripts/discourse/mixins/card-contents-base.js.es6 +++ b/app/assets/javascripts/discourse/mixins/card-contents-base.js.es6 @@ -16,6 +16,7 @@ export default Ember.Mixin.create({ cardTarget: null, post: null, isFixed: false, + isDocked: false, _show(username, $target) { // No user card for anon @@ -123,6 +124,8 @@ export default Ember.Mixin.create({ }); this.appEvents.on(`topic-header:trigger-${id}`, (username, $target) => { + this.set("isFixed", true); + this.set("isDocked", true); return this._show(username, $target); }); }, @@ -135,6 +138,7 @@ export default Ember.Mixin.create({ const width = this.$().width(); const height = 175; const isFixed = this.get("isFixed"); + const isDocked = this.get("isDocked"); let verticalAdjustments = 0; @@ -183,9 +187,16 @@ export default Ember.Mixin.create({ position.top = "unset"; } } + + if (isDocked && position.top < 44) { + position.top = 44; + } + this.$().css(position); } + this.$().toggleClass("docked-card", isDocked); + // After the card is shown, focus on the first link // // note: we DO NOT use afterRender here cause _positionCard may @@ -209,7 +220,8 @@ export default Ember.Mixin.create({ loading: null, cardTarget: null, post: null, - isFixed: false + isFixed: false, + isDocked: false }); }, diff --git a/app/assets/stylesheets/desktop/user-card.scss b/app/assets/stylesheets/desktop/user-card.scss index f94ae71a90..f787290005 100644 --- a/app/assets/stylesheets/desktop/user-card.scss +++ b/app/assets/stylesheets/desktop/user-card.scss @@ -33,6 +33,10 @@ $user_card_background: $secondary; z-index: z("composer", "content") + 1; } + &.docked-card { + z-index: z("header") + 1; + } + .card-content { padding: 12px 12px 0 12px; background: rgba($user_card_background, 0.85); From 046e4ab413b219e70a89a49630560232ba74e038 Mon Sep 17 00:00:00 2001 From: Vinoth Kannan Date: Sat, 5 Jan 2019 14:06:06 +0530 Subject: [PATCH 046/157] FIX: Improve topic small header alignment in mobile Force topic-header-extra height to 20px --- app/assets/stylesheets/common/base/header.scss | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/assets/stylesheets/common/base/header.scss b/app/assets/stylesheets/common/base/header.scss index 36fd35aab9..3fd1d6fc22 100644 --- a/app/assets/stylesheets/common/base/header.scss +++ b/app/assets/stylesheets/common/base/header.scss @@ -287,6 +287,7 @@ overflow: hidden; white-space: nowrap; text-overflow: ellipsis; + height: 20px; .discourse-tag { display: block; float: left; @@ -303,6 +304,8 @@ } .topic-header-participants { + height: 20px; + > span { margin: 0 2px; display: block; From 3a9fea4409099019daf0cdc86f1d3b226a204beb Mon Sep 17 00:00:00 2001 From: Joe <33972521+hnb-ku@users.noreply.github.com> Date: Sat, 5 Jan 2019 23:09:44 +0800 Subject: [PATCH 047/157] align and truncate header tags and participants --- .../stylesheets/common/base/header.scss | 81 ++++++++++++------- 1 file changed, 53 insertions(+), 28 deletions(-) diff --git a/app/assets/stylesheets/common/base/header.scss b/app/assets/stylesheets/common/base/header.scss index 3fd1d6fc22..598b319b4c 100644 --- a/app/assets/stylesheets/common/base/header.scss +++ b/app/assets/stylesheets/common/base/header.scss @@ -259,7 +259,6 @@ margin-right: 8px; &.bullet { padding-top: 2px; // alignment hack - line-height: 18px; } } .badge-wrapper.bullet { @@ -287,55 +286,81 @@ overflow: hidden; white-space: nowrap; text-overflow: ellipsis; - height: 20px; .discourse-tag { - display: block; - float: left; - line-height: 18px; - margin: 2px 4px 0 0; + display: inline; // tags need to stay inline in order for them to truncate } } } - // if a topic has both categories and tags, the tag container should shrink - // instead of wrapping to the next line. - .categories-wrapper + .topic-header-extra { - min-width: 0; + // the tag container should shrink + .topic-header-extra { + min-width: 1px; // 1px value is needed to avoid IE11 flexbox bug } } -.topic-header-participants { - height: 20px; +// PM header participants - > span { - margin: 0 2px; - display: block; - float: left; - height: 20px; +$avatar-height: 1.641em; +$mobile-avatar-height: 1.532em; + +.topic-header-participants { + display: flex; + align-items: center; + overflow: hidden; + font-size: $font-down-1; + margin-left: 5px; + + .trigger-user-card, + .trigger-group-card { + &:not(:last-of-type) { + margin-right: 5px; + } + } + + .trigger-user-card { + .icon { + height: $avatar-height; + .mobile-view & { + height: $mobile-avatar-height; + } + display: inline-block; + img { + height: 100%; + width: auto; + } + } } .trigger-group-card { - display: block; - float: left; - margin: 0 4px; - padding: 1px 4px; + padding: 0 5px; border: 1px solid $primary-low; border-radius: 0.25em; - align-items: center; - height: 16px; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; - a { + .icon { + display: flex; + align-items: center; + height: $avatar-height; + .mobile-view & { + height: $mobile-avatar-height; + } color: $primary-high; .d-icon { - margin: 1px 4px 0 0; + margin-right: 5px; } } + + span { + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + } } .more-participants { - display: inline-block; color: $header_primary-high; - line-height: 20px; - padding: 0 4px; + margin-left: 5px; } } From effb3262a1787b94bbc9875e82343c331476d5c2 Mon Sep 17 00:00:00 2001 From: Joe <33972521+hnb-ku@users.noreply.github.com> Date: Sun, 6 Jan 2019 00:11:53 +0800 Subject: [PATCH 048/157] UX: no need for margin if participants is first-child --- app/assets/stylesheets/common/base/header.scss | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/assets/stylesheets/common/base/header.scss b/app/assets/stylesheets/common/base/header.scss index 598b319b4c..47acb5a2c3 100644 --- a/app/assets/stylesheets/common/base/header.scss +++ b/app/assets/stylesheets/common/base/header.scss @@ -307,8 +307,9 @@ $mobile-avatar-height: 1.532em; align-items: center; overflow: hidden; font-size: $font-down-1; - margin-left: 5px; - + &:not(:first-child) { + margin-left: 5px; + } .trigger-user-card, .trigger-group-card { &:not(:last-of-type) { From 37088c4221bbaa8de1d24309376da1740ce455a2 Mon Sep 17 00:00:00 2001 From: Sam Date: Sun, 6 Jan 2019 15:14:41 +1100 Subject: [PATCH 049/157] PERF: index on topic_id for user_histories table We query this table when getting composer messages with the queries such as: ``` SELECT 1 AS one FROM "user_histories" WHERE "user_histories"."target_user_id" = 1 AND "user_histories"."action" = 9 AND "user_histories"."topic_id" = 105794 LIMIT 1 ``` This index ensures this query remains very quick, regardless of user history size. --- .../20190106041015_add_topic_id_index_to_user_histories.rb | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 db/migrate/20190106041015_add_topic_id_index_to_user_histories.rb diff --git a/db/migrate/20190106041015_add_topic_id_index_to_user_histories.rb b/db/migrate/20190106041015_add_topic_id_index_to_user_histories.rb new file mode 100644 index 0000000000..8172657fb1 --- /dev/null +++ b/db/migrate/20190106041015_add_topic_id_index_to_user_histories.rb @@ -0,0 +1,5 @@ +class AddTopicIdIndexToUserHistories < ActiveRecord::Migration[5.2] + def change + add_index :user_histories, [:topic_id, :target_user_id, :action] + end +end From 77d947701c870cb4bd4ec7cb8e9786962c5bf58e Mon Sep 17 00:00:00 2001 From: Sam Date: Mon, 7 Jan 2019 13:10:37 +1100 Subject: [PATCH 050/157] DEV: Add missing discourse script to docker dev --- bin/docker/discourse | 5 +++++ 1 file changed, 5 insertions(+) create mode 100755 bin/docker/discourse diff --git a/bin/docker/discourse b/bin/docker/discourse new file mode 100755 index 0000000000..3ae9852f6d --- /dev/null +++ b/bin/docker/discourse @@ -0,0 +1,5 @@ +#!/bin/bash + +PARAMS="$@" +CMD="cd /src && USER=discourse RUBY_GLOBAL_METHOD_CACHE_SIZE=131072 LD_PRELOAD=/usr/lib/libjemalloc.so RAILS_ENV=${RAILS_ENV:=development} script/discourse $PARAMS" +docker exec -it -u discourse:discourse discourse_dev /bin/bash -c "$CMD" From e0bc82657b36776cf755fda62d1bbcbed19df1e9 Mon Sep 17 00:00:00 2001 From: Arpit Jalan Date: Mon, 7 Jan 2019 14:22:08 +0530 Subject: [PATCH 051/157] FIX: better accept invite flow when user is invited via a link --- .../discourse/controllers/invites-show.js.es6 | 5 +++-- .../discourse/templates/invites/show.hbs | 10 ++++------ app/controllers/invites_controller.rb | 15 +++++++++------ config/locales/server.en.yml | 1 + spec/requests/invites_controller_spec.rb | 1 + 5 files changed, 18 insertions(+), 14 deletions(-) diff --git a/app/assets/javascripts/discourse/controllers/invites-show.js.es6 b/app/assets/javascripts/discourse/controllers/invites-show.js.es6 index 78c6bbcf0c..128269b8e6 100644 --- a/app/assets/javascripts/discourse/controllers/invites-show.js.es6 +++ b/app/assets/javascripts/discourse/controllers/invites-show.js.es6 @@ -94,8 +94,9 @@ export default Ember.Controller.extend( "successMessage", result.message || I18n.t("invites.success") ); - this.set("redirectTo", result.redirect_to); - DiscourseURL.redirectTo(result.redirect_to || "/"); + if (result.redirect_to) { + DiscourseURL.redirectTo(result.redirect_to || "/"); + } } else { if ( result.errors && diff --git a/app/assets/javascripts/discourse/templates/invites/show.hbs b/app/assets/javascripts/discourse/templates/invites/show.hbs index 65c775087b..e8877238d3 100644 --- a/app/assets/javascripts/discourse/templates/invites/show.hbs +++ b/app/assets/javascripts/discourse/templates/invites/show.hbs @@ -8,13 +8,12 @@
-

{{i18n 'invites.invited_by'}}

- -

{{user-info user=invitedBy}}

- {{#if successMessage}} -

{{successMessage}}

+

+

{{{successMessage}}}

{{else}} +

{{i18n 'invites.invited_by'}}

+

{{user-info user=invitedBy}}

{{{yourEmailMessage}}} {{#if externalAuthsEnabled}} @@ -63,7 +62,6 @@

{{errorMessage}}
{{/if}} - {{/if}}
diff --git a/app/controllers/invites_controller.rb b/app/controllers/invites_controller.rb index c888cffab4..a178e401da 100644 --- a/app/controllers/invites_controller.rb +++ b/app/controllers/invites_controller.rb @@ -47,16 +47,19 @@ class InvitesController < ApplicationController begin user = invite.redeem(username: params[:username], name: params[:name], password: params[:password], user_custom_fields: params[:user_custom_fields]) if user.present? - log_on_user(user) + log_on_user(user) if user.active? post_process_invite(user) end - topic = user.present? ? invite.topics.first : nil + response = { success: true } + if user.present? && user.active? + topic = invite.topics.first + response[:redirect_to] = topic.present? ? path("#{topic.relative_url}") : path("/") + else + response[:message] = I18n.t('invite.confirm_email') + end - render json: { - success: true, - redirect_to: topic.present? ? path("#{topic.relative_url}") : path("/") - } + render json: response rescue ActiveRecord::RecordInvalid, ActiveRecord::RecordNotSaved => e render json: { success: false, diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index 906e5eea50..743a573e48 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -185,6 +185,7 @@ en:

Otherwise please Reset Password.

user_exists: "There's no need to invite %{email}, they already have an account!" + confirm_email: "

You’re almost done! We sent an activation mail to your email address. Please follow the instructions in the mail to activate your account.

If it doesn’t arrive, check your spam folder.

" bulk_invite: file_should_be_csv: "The uploaded file should be of csv format." diff --git a/spec/requests/invites_controller_spec.rb b/spec/requests/invites_controller_spec.rb index c2557e5c7c..b814df7234 100644 --- a/spec/requests/invites_controller_spec.rb +++ b/spec/requests/invites_controller_spec.rb @@ -355,6 +355,7 @@ describe InvitesController do put "/invites/show/#{invite.invite_key}.json", params: { password: "verystrongpassword" } expect(response.status).to eq(200) expect(JSON.parse(response.body)["success"]).to eq(true) + expect(JSON.parse(response.body)["message"]).to eq(I18n.t("invite.confirm_email")) invited_user = User.find_by_email(invite.email) expect(invited_user.active).to eq(false) From 6c8069c65ada8f5ee2d7c3f925136156152e6f65 Mon Sep 17 00:00:00 2001 From: Maja Komel Date: Sun, 6 Jan 2019 23:12:02 +0100 Subject: [PATCH 052/157] FIX: properly escape embed url --- app/models/topic_embed.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/topic_embed.rb b/app/models/topic_embed.rb index 9284c41a76..5258eac263 100644 --- a/app/models/topic_embed.rb +++ b/app/models/topic_embed.rb @@ -209,7 +209,7 @@ class TopicEmbed < ActiveRecord::Base def self.topic_id_for_embed(embed_url) embed_url = normalize_url(embed_url).sub(/^https?\:\/\//, '') - TopicEmbed.where("embed_url ~* '^https?://#{Regexp.escape(embed_url)}$'").pluck(:topic_id).first + TopicEmbed.where("embed_url ~* ?", "^https?://#{Regexp.escape(embed_url)}$").pluck(:topic_id).first end def self.first_paragraph_from(html) From c76c44bc660fb87ed35273999af674662c055b08 Mon Sep 17 00:00:00 2001 From: Arpit Jalan Date: Tue, 8 Jan 2019 09:20:08 +0530 Subject: [PATCH 053/157] bump onebox version - FEATURE: Add support for Twitter cards. - FIX: add more https hosts --- Gemfile | 2 +- Gemfile.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Gemfile b/Gemfile index f8435e308b..88d2c4f81a 100644 --- a/Gemfile +++ b/Gemfile @@ -36,7 +36,7 @@ gem 'redis-namespace' gem 'active_model_serializers', '~> 0.8.3' -gem 'onebox', '1.8.71' +gem 'onebox', '1.8.73' gem 'http_accept_language', '~>2.0.5', require: false diff --git a/Gemfile.lock b/Gemfile.lock index 7377a8727c..88e2d49cea 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -258,7 +258,7 @@ GEM omniauth-twitter (1.4.0) omniauth-oauth (~> 1.1) rack - onebox (1.8.71) + onebox (1.8.73) htmlentities (~> 4.3) moneta (~> 1.0) multi_json (~> 1.11) @@ -512,7 +512,7 @@ DEPENDENCIES omniauth-oauth2 omniauth-openid omniauth-twitter - onebox (= 1.8.71) + onebox (= 1.8.73) openid-redis-store pg pry-nav From ef72a9a1fef0e6dfaed2ebdbb8b4770e851794ff Mon Sep 17 00:00:00 2001 From: Guo Xiang Tan Date: Tue, 8 Jan 2019 13:40:38 +0800 Subject: [PATCH 054/157] UX: Default search log index to yearly. --- .../admin/controllers/admin-search-logs-index.js.es6 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/admin/controllers/admin-search-logs-index.js.es6 b/app/assets/javascripts/admin/controllers/admin-search-logs-index.js.es6 index c46b7d1cd1..50f3d7c5ef 100644 --- a/app/assets/javascripts/admin/controllers/admin-search-logs-index.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-search-logs-index.js.es6 @@ -1,6 +1,6 @@ export default Ember.Controller.extend({ loading: false, - period: "all", + period: "yearly", searchType: "all", searchTypeOptions: [ From 9919f16041da1d389ab7efbfcf571d920666603f Mon Sep 17 00:00:00 2001 From: Arpit Jalan Date: Tue, 8 Jan 2019 11:17:05 +0530 Subject: [PATCH 055/157] FIX: use absolute URL for twitter:image tag --- app/helpers/application_helper.rb | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index cd3ed04453..82a5d5fbae 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -210,17 +210,10 @@ module ApplicationHelper opts[:image] = SiteSetting.site_apple_touch_icon_url end - # Use the correct scheme for open graph image - if opts[:image].present? - if opts[:image].start_with?("//") - uri = URI(Discourse.base_url) - opts[:image] = "#{uri.scheme}:#{opts[:image]}" - elsif opts[:image].start_with?("/uploads/") - opts[:image] = "#{Discourse.base_url}#{opts[:image]}" - elsif GlobalSetting.relative_url_root && opts[:image].start_with?(GlobalSetting.relative_url_root) - opts[:image] = "#{Discourse.base_url_no_prefix}#{opts[:image]}" - end - end + # Use the correct scheme for opengraph/twitter image + opts[:image] = get_absolute_image_url(opts[:image]) if opts[:image].present? + opts[:twitter_summary_large_image] = + get_absolute_image_url(opts[:twitter_summary_large_image]) if opts[:twitter_summary_large_image].present? # Add opengraph & twitter tags result = [] @@ -452,4 +445,17 @@ module ApplicationHelper setup_data end + + def get_absolute_image_url(link) + absolute_url = link + if link.start_with?("//") + uri = URI(Discourse.base_url) + absolute_url = "#{uri.scheme}:#{link}" + elsif link.start_with?("/uploads/") + absolute_url = "#{Discourse.base_url}#{link}" + elsif GlobalSetting.relative_url_root && link.start_with?(GlobalSetting.relative_url_root) + absolute_url = "#{Discourse.base_url_no_prefix}#{link}" + end + absolute_url + end end From 4ebf170fe43329cdd49a9925c7e62c001132e516 Mon Sep 17 00:00:00 2001 From: Arpit Jalan Date: Tue, 8 Jan 2019 11:34:26 +0530 Subject: [PATCH 056/157] DEV: no need for conditional redirect in invites --- .../javascripts/discourse/controllers/invites-show.js.es6 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/discourse/controllers/invites-show.js.es6 b/app/assets/javascripts/discourse/controllers/invites-show.js.es6 index 128269b8e6..b710b3018f 100644 --- a/app/assets/javascripts/discourse/controllers/invites-show.js.es6 +++ b/app/assets/javascripts/discourse/controllers/invites-show.js.es6 @@ -95,7 +95,7 @@ export default Ember.Controller.extend( result.message || I18n.t("invites.success") ); if (result.redirect_to) { - DiscourseURL.redirectTo(result.redirect_to || "/"); + DiscourseURL.redirectTo(result.redirect_to); } } else { if ( From 3457395f75df04e473f5ba46b3b842c9bb21f2e2 Mon Sep 17 00:00:00 2001 From: Joe <33972521+hnb-ku@users.noreply.github.com> Date: Tue, 8 Jan 2019 14:55:15 +0800 Subject: [PATCH 057/157] UX: moves topic-list excerpts out of category / tag div --- .../templates/list/topic-list-item.raw.hbs | 44 +++++++++---------- .../stylesheets/common/base/_topic-list.scss | 3 -- 2 files changed, 22 insertions(+), 25 deletions(-) diff --git a/app/assets/javascripts/discourse/templates/list/topic-list-item.raw.hbs b/app/assets/javascripts/discourse/templates/list/topic-list-item.raw.hbs index d0b1077dd4..095518f07a 100644 --- a/app/assets/javascripts/discourse/templates/list/topic-list-item.raw.hbs +++ b/app/assets/javascripts/discourse/templates/list/topic-list-item.raw.hbs @@ -17,25 +17,25 @@ {{~raw "topic-status" topic=topic}} {{~topic-link topic class="raw-link raw-topic-link"}} {{~#if topic.featured_link}} - {{~topic-featured-link topic}} + {{~topic-featured-link topic}} {{~/if}} {{~raw-plugin-outlet name="topic-list-after-title"}} {{~#if showTopicPostBadges}} - {{~raw "topic-post-badges" unread=topic.unread newPosts=topic.displayNewPosts unseen=topic.unseen url=topic.lastUnreadUrl newDotText=newDotText}} + {{~raw "topic-post-badges" unread=topic.unread newPosts=topic.displayNewPosts unseen=topic.unseen url=topic.lastUnreadUrl newDotText=newDotText}} {{~/if}} {{#if expandPinned}} {{raw "list/topic-excerpt" topic=topic}} {{/if}} - {{raw "list/action-list" topic=topic postNumbers=topic.liked_post_numbers className="likes" icon="heart"}} -
{{#if showPosters}} @@ -49,23 +49,23 @@ {{/if}} {{#if showLikes}} - - {{#if hasLikes}} - - {{number topic.like_count}} {{d-icon "heart"}} - - {{/if}} + + {{#if hasLikes}} + + {{number topic.like_count}} {{d-icon "heart"}} + +{{/if}} {{/if}} {{#if showOpLikes}} - - {{#if hasOpLikes}} - - {{number topic.op_like_count}} {{d-icon "heart"}} - - {{/if}} + + {{#if hasOpLikes}} + + {{number topic.op_like_count}} {{d-icon "heart"}} + +{{/if}} {{/if}} {{number topic.views numberKey="views_long"}} -{{raw "list/activity-column" topic=topic class="num" tagName="td"}} +{{raw "list/activity-column" topic=topic class="num" tagName="td"}} \ No newline at end of file diff --git a/app/assets/stylesheets/common/base/_topic-list.scss b/app/assets/stylesheets/common/base/_topic-list.scss index 63842ebcb6..d276ea033c 100644 --- a/app/assets/stylesheets/common/base/_topic-list.scss +++ b/app/assets/stylesheets/common/base/_topic-list.scss @@ -144,9 +144,6 @@ .discourse-tag.box { margin-right: 0.25em; } - .topic-excerpt { - flex: 1 1 0%; // IE11 fix - unit on flexbasis is required - } } .topic-featured-link { From 05c015d2527722bbdb1ae27cf8f30399068f4a7a Mon Sep 17 00:00:00 2001 From: Arpit Jalan Date: Tue, 8 Jan 2019 12:40:20 +0530 Subject: [PATCH 058/157] DEV: add a spec for "accept invite" log_on_user behaviour --- spec/requests/invites_controller_spec.rb | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/spec/requests/invites_controller_spec.rb b/spec/requests/invites_controller_spec.rb index b814df7234..037d2aa047 100644 --- a/spec/requests/invites_controller_spec.rb +++ b/spec/requests/invites_controller_spec.rb @@ -335,7 +335,10 @@ describe InvitesController do before { invite.update_column(:via_email, true) } it "doesn't send an activation email and activates the user" do - put "/invites/show/#{invite.invite_key}.json", params: { password: "verystrongpassword" } + expect do + put "/invites/show/#{invite.invite_key}.json", params: { password: "verystrongpassword" } + end.to change { UserAuthToken.count }.by(1) + expect(response.status).to eq(200) expect(JSON.parse(response.body)["success"]).to eq(true) @@ -352,7 +355,10 @@ describe InvitesController do before { invite.update_column(:via_email, false) } it "sends an activation email and doesn't activate the user" do - put "/invites/show/#{invite.invite_key}.json", params: { password: "verystrongpassword" } + expect do + put "/invites/show/#{invite.invite_key}.json", params: { password: "verystrongpassword" } + end.not_to change { UserAuthToken.count } + expect(response.status).to eq(200) expect(JSON.parse(response.body)["success"]).to eq(true) expect(JSON.parse(response.body)["message"]).to eq(I18n.t("invite.confirm_email")) From ec27db78bec4b50a475a5dd830bc0d695bfe66cd Mon Sep 17 00:00:00 2001 From: Guo Xiang Tan Date: Tue, 8 Jan 2019 15:02:51 +0800 Subject: [PATCH 059/157] FIX: Set unique post key for a user outside of transaction. Previously, the Redis key was set within the transaction and the key isn't deleted if the transaction is not successful. Note that this isn't tested because we don't have a repro of what can raise an error within the transaction. https://meta.discourse.org/t/body-is-too-similar-to-what-you-previously-posted-even-when-previous-post-didnt-go-through/105436 --- lib/post_creator.rb | 2 +- spec/components/post_creator_spec.rb | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/post_creator.rb b/lib/post_creator.rb index 173a30cf20..4c4c079325 100644 --- a/lib/post_creator.rb +++ b/lib/post_creator.rb @@ -167,7 +167,6 @@ class PostCreator create_topic save_post extract_links - store_unique_post_key track_topic update_topic_stats update_topic_auto_close @@ -182,6 +181,7 @@ class PostCreator end if @post && errors.blank? && !@opts[:import_mode] + store_unique_post_key # update counters etc. @post.topic.reload diff --git a/spec/components/post_creator_spec.rb b/spec/components/post_creator_spec.rb index 304491c59c..8774c6402e 100644 --- a/spec/components/post_creator_spec.rb +++ b/spec/components/post_creator_spec.rb @@ -580,8 +580,9 @@ describe PostCreator do it "returns blank for another post with the same content" do creator.create - new_post_creator.create - expect(new_post_creator.errors).to be_present + post = new_post_creator.create + + expect(post.errors[:raw]).to include(I18n.t(:just_posted_that)) end it "returns a post for admins" do From f947e3c6ccf307c5b3b778a1ccca3390825b14f3 Mon Sep 17 00:00:00 2001 From: Sam Date: Tue, 8 Jan 2019 19:51:33 +1100 Subject: [PATCH 060/157] FIX: always serve new avatar for previous version Previously we killed caching on old avatars cause we kept serving blank this meant we would front many more avatar requests after a version change This change ensures all old avatars do not cause a flood of requests on the server --- app/controllers/user_avatars_controller.rb | 6 ++++- spec/requests/user_avatars_controller_spec.rb | 23 +++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/app/controllers/user_avatars_controller.rb b/app/controllers/user_avatars_controller.rb index a7f32b1171..79c3715a09 100644 --- a/app/controllers/user_avatars_controller.rb +++ b/app/controllers/user_avatars_controller.rb @@ -99,7 +99,11 @@ class UserAvatarsController < ApplicationController upload_id, version = params[:version].split("_") version = (version || OptimizedImage::VERSION).to_i - return render_blank if version != OptimizedImage::VERSION + + # old versions simply get new avatar + if version > OptimizedImage::VERSION + return render_blank + end upload_id = upload_id.to_i return render_blank unless upload_id > 0 diff --git a/spec/requests/user_avatars_controller_spec.rb b/spec/requests/user_avatars_controller_spec.rb index 7341cd6b1a..030959c748 100644 --- a/spec/requests/user_avatars_controller_spec.rb +++ b/spec/requests/user_avatars_controller_spec.rb @@ -102,6 +102,29 @@ describe UserAvatarsController do expect(response.headers["Last-Modified"]).to eq(optimized_image.upload.created_at.httpdate) end + it 'serves new version for old urls' do + user = Fabricate(:user) + SiteSetting.avatar_sizes = "45" + + image = file_from_fixtures("cropped.png") + upload = UploadCreator.new(image, "image.png").create_for(user.id) + + user.update_columns(uploaded_avatar_id: upload.id) + + get "/user_avatar/default/#{user.username}/45/#{upload.id}_1.png" + + expect(response.status).to eq(200) + + image = response.body + optimized = upload.get_optimized_image(45, 45, {}) + + expect(optimized.filesize).to eq(body.length) + + # clean up images + upload.destroy + + end + it 'serves a correct last modified for render blank' do freeze_time From 8f602be2fe55799036eeb913bb707f5dc4781570 Mon Sep 17 00:00:00 2001 From: Vinoth Kannan Date: Tue, 8 Jan 2019 16:13:10 +0530 Subject: [PATCH 061/157] FEATURE: keep the topic in closed status until the community flags are handled --- app/jobs/regular/toggle_topic_closed.rb | 11 ++- app/models/post_action.rb | 11 ++- spec/models/post_action_spec.rb | 124 ++++++++++++++++-------- 3 files changed, 103 insertions(+), 43 deletions(-) diff --git a/app/jobs/regular/toggle_topic_closed.rb b/app/jobs/regular/toggle_topic_closed.rb index 7a77f02489..99a446af10 100644 --- a/app/jobs/regular/toggle_topic_closed.rb +++ b/app/jobs/regular/toggle_topic_closed.rb @@ -15,7 +15,16 @@ module Jobs user = topic_timer.user if Guardian.new(user).can_close?(topic) - topic.update_status('autoclosed', state, user) + if state == false && PostAction.auto_close_threshold_reached?(topic) + topic.set_or_create_timer( + TopicTimer.types[:open], + SiteSetting.num_hours_to_close_topic, + by_user: Discourse.system_user + ) + else + topic.update_status('autoclosed', state, user) + end + topic.inherit_auto_close_from_category if state == false end end diff --git a/app/models/post_action.rb b/app/models/post_action.rb index 1e533b0a9b..a042a6e218 100644 --- a/app/models/post_action.rb +++ b/app/models/post_action.rb @@ -564,9 +564,7 @@ class PostAction < ActiveRecord::Base MAXIMUM_FLAGS_PER_POST = 3 - def self.auto_close_if_threshold_reached(topic) - return if topic.nil? || topic.closed? - + def self.auto_close_threshold_reached?(topic) flags = PostAction.active .flags .joins(:post) @@ -580,6 +578,13 @@ class PostAction < ActiveRecord::Base # we need a minimum number of flags return if flags.sum { |f| f[1] } < SiteSetting.num_flags_to_close_topic + true + end + + def self.auto_close_if_threshold_reached(topic) + return if topic.nil? || topic.closed? + return unless auto_close_threshold_reached?(topic) + # the threshold has been reached, we will close the topic waiting for intervention topic.update_status("closed", true, Discourse.system_user, message: I18n.t( diff --git a/spec/models/post_action_spec.rb b/spec/models/post_action_spec.rb index 823128d1a8..11ad4f4696 100644 --- a/spec/models/post_action_spec.rb +++ b/spec/models/post_action_spec.rb @@ -589,54 +589,100 @@ describe PostAction do expect(post.hidden).to eq(false) end - it "will automatically pause a topic due to large community flagging" do - SiteSetting.flags_required_to_hide_post = 0 - SiteSetting.num_flags_to_close_topic = 3 - SiteSetting.num_flaggers_to_close_topic = 2 - SiteSetting.num_hours_to_close_topic = 1 + context "topic auto closing" do + let(:topic) { Fabricate(:topic) } + let(:post1) { create_post(topic: topic) } + let(:post2) { create_post(topic: topic) } + let(:post3) { create_post(topic: topic) } - topic = Fabricate(:topic) - post1 = create_post(topic: topic) - post2 = create_post(topic: topic) - post3 = create_post(topic: topic) + let(:flagger1) { Fabricate(:user) } + let(:flagger2) { Fabricate(:user) } - flagger1 = Fabricate(:user) - flagger2 = Fabricate(:user) - - # reaching `num_flaggers_to_close_topic` isn't enough - [flagger1, flagger2].each do |flagger| - PostAction.act(flagger, post1, PostActionType.types[:inappropriate]) + before do + SiteSetting.flags_required_to_hide_post = 0 + SiteSetting.num_flags_to_close_topic = 3 + SiteSetting.num_flaggers_to_close_topic = 2 + SiteSetting.num_hours_to_close_topic = 1 end - expect(topic.reload.closed).to eq(false) - - # clean up - PostAction.where(post: post1).delete_all - - # reaching `num_flags_to_close_topic` isn't enough - [post1, post2, post3].each do |post| - PostAction.act(flagger1, post, PostActionType.types[:inappropriate]) - end - - expect(topic.reload.closed).to eq(false) - - # clean up - PostAction.where(post: [post1, post2, post3]).delete_all - - # reaching both should close the topic - [flagger1, flagger2].each do |flagger| - [post1, post2, post3].each do |post| - PostAction.act(flagger, post, PostActionType.types[:inappropriate]) + it "will automatically pause a topic due to large community flagging" do + # reaching `num_flaggers_to_close_topic` isn't enough + [flagger1, flagger2].each do |flagger| + PostAction.act(flagger, post1, PostActionType.types[:inappropriate]) end + + expect(topic.reload.closed).to eq(false) + + # clean up + PostAction.where(post: post1).delete_all + + # reaching `num_flags_to_close_topic` isn't enough + [post1, post2, post3].each do |post| + PostAction.act(flagger1, post, PostActionType.types[:inappropriate]) + end + + expect(topic.reload.closed).to eq(false) + + # clean up + PostAction.where(post: [post1, post2, post3]).delete_all + + # reaching both should close the topic + [flagger1, flagger2].each do |flagger| + [post1, post2, post3].each do |post| + PostAction.act(flagger, post, PostActionType.types[:inappropriate]) + end + end + + expect(topic.reload.closed).to eq(true) + + topic_status_update = TopicTimer.last + + expect(topic_status_update.topic).to eq(topic) + expect(topic_status_update.execute_at).to be_within(1.second).of(1.hour.from_now) + expect(topic_status_update.status_type).to eq(TopicTimer.types[:open]) end - expect(topic.reload.closed).to eq(true) + it "will keep the topic in closed status until the community flags are handled" do + freeze_time - topic_status_update = TopicTimer.last + PostAction.stubs(:auto_close_threshold_reached?).returns(true) + PostAction.auto_close_if_threshold_reached(topic) - expect(topic_status_update.topic).to eq(topic) - expect(topic_status_update.execute_at).to be_within(1.second).of(1.hour.from_now) - expect(topic_status_update.status_type).to eq(TopicTimer.types[:open]) + expect(topic.reload.closed).to eq(true) + + timer = TopicTimer.last + expect(timer.execute_at).to eq(1.hour.from_now) + + freeze_time timer.execute_at + Jobs.expects(:enqueue_in).with(1.hour.to_i, :toggle_topic_closed, topic_timer_id: timer.id, state: false).returns(true) + Jobs::ToggleTopicClosed.new.execute(topic_timer_id: timer.id, state: false) + + expect(topic.reload.closed).to eq(true) + expect(timer.reload.execute_at).to eq(1.hour.from_now) + + freeze_time timer.execute_at + PostAction.stubs(:auto_close_threshold_reached?).returns(false) + Jobs::ToggleTopicClosed.new.execute(topic_timer_id: timer.id, state: false) + + expect(topic.reload.closed).to eq(false) + end + + it "will reopen topic after the flags are auto handled" do + freeze_time + [flagger1, flagger2].each do |flagger| + [post1, post2, post3].each do |post| + PostAction.act(flagger, post, PostActionType.types[:inappropriate]) + end + end + + expect(topic.reload.closed).to eq(true) + + freeze_time 61.days.from_now + Jobs::AutoQueueHandler.new.execute({}) + Jobs::ToggleTopicClosed.new.execute(topic_timer_id: TopicTimer.last.id, state: false) + + expect(topic.reload.closed).to eq(false) + end end end From 733a60e88875169e7fdb505dc24ea7eab6d0bf9f Mon Sep 17 00:00:00 2001 From: Rishabh Date: Tue, 8 Jan 2019 17:37:43 +0530 Subject: [PATCH 062/157] FIX: Remove trailing whitespace to fix build --- spec/models/post_action_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/models/post_action_spec.rb b/spec/models/post_action_spec.rb index 11ad4f4696..1650c8745c 100644 --- a/spec/models/post_action_spec.rb +++ b/spec/models/post_action_spec.rb @@ -649,7 +649,7 @@ describe PostAction do PostAction.auto_close_if_threshold_reached(topic) expect(topic.reload.closed).to eq(true) - + timer = TopicTimer.last expect(timer.execute_at).to eq(1.hour.from_now) From f181e9cc08a5d3c31598587957f8999b7e62ac50 Mon Sep 17 00:00:00 2001 From: Rishabh Date: Tue, 8 Jan 2019 20:04:48 +0530 Subject: [PATCH 063/157] FIX: Add compatibility for bucket folder paths in migrate_to_s3 task (#6855) * FIX: Add compatibility for bucket folder paths in migrate_to_s3 task * Refactor bucket_name split logic into S3Helper --- lib/s3_helper.rb | 10 +++++++--- lib/tasks/uploads.rake | 24 +++++++++++++++++------- 2 files changed, 24 insertions(+), 10 deletions(-) diff --git a/lib/s3_helper.rb b/lib/s3_helper.rb index 1ecd8223d6..fbdde9674a 100644 --- a/lib/s3_helper.rb +++ b/lib/s3_helper.rb @@ -12,7 +12,7 @@ class S3Helper @s3_bucket_name, @s3_bucket_folder_path = begin raise Discourse::InvalidParameters.new("s3_bucket_name") if s3_bucket_name.blank? - s3_bucket_name.downcase.split("/".freeze, 2) + self.class.get_bucket_and_folder_path(s3_bucket_name) end @tombstone_prefix = @@ -23,6 +23,10 @@ class S3Helper end end + def self.get_bucket_and_folder_path(s3_bucket_name) + s3_bucket_name.downcase.split("/".freeze, 2) + end + def upload(file, path, options = {}) path = get_path_for_s3_upload(path) obj = s3_bucket.object(path) @@ -62,10 +66,10 @@ class S3Helper options[:copy_source] = File.join(@s3_bucket_name, source) else if @s3_bucket_folder_path - bucket_folder, filename = begin + folder, filename = begin source.split("/".freeze, 2) end - options[:copy_source] = File.join(@s3_bucket_name, bucket_folder, multisite_upload_path, filename) + options[:copy_source] = File.join(@s3_bucket_name, folder, multisite_upload_path, filename) else options[:copy_source] = File.join(@s3_bucket_name, multisite_upload_path, source) end diff --git a/lib/tasks/uploads.rake b/lib/tasks/uploads.rake index a86d6b922b..106332794a 100644 --- a/lib/tasks/uploads.rake +++ b/lib/tasks/uploads.rake @@ -214,6 +214,7 @@ def migrate_to_s3 db = RailsMultisite::ConnectionManagement.current_db dry_run = !!ENV["DRY_RUN"] + bucket_has_folder_path = true if ENV["DISCOURSE_S3_BUCKET"].include? "/" puts "*" * 30 + " DRY RUN " + "*" * 30 if dry_run puts "Migrating uploads to S3 for '#{db}'..." @@ -246,11 +247,18 @@ def migrate_to_s3 s3 = Aws::S3::Client.new(S3Helper.s3_options(GlobalSetting)) + if bucket_has_folder_path + bucket, folder = S3Helper.get_bucket_and_folder_path(ENV["DISCOURSE_S3_BUCKET"]) + folder = File.join(folder, "/") + else + bucket, folder = GlobalSetting.s3_bucket, "" + end + begin - s3.head_bucket(bucket: GlobalSetting.s3_bucket) + s3.head_bucket(bucket: bucket) rescue Aws::S3::Errors::NotFound - puts "Bucket '#{GlobalSetting.s3_bucket}' not found. Creating it..." - s3.create_bucket(bucket: GlobalSetting.s3_bucket) unless dry_run + puts "Bucket '#{bucket}' not found. Creating it..." + s3.create_bucket(bucket: bucket) unless dry_run end puts "Uploading files to S3..." @@ -267,7 +275,7 @@ def migrate_to_s3 s3_objects = [] prefix = Rails.configuration.multisite ? "#{db}/original/" : "original/" - options = { bucket: GlobalSetting.s3_bucket, prefix: prefix } + options = { bucket: bucket, prefix: prefix } loop do response = s3.list_objects_v2(options) @@ -287,6 +295,8 @@ def migrate_to_s3 path = File.join("public", file) name = File.basename(path) etag = Digest::MD5.file(path).hexdigest + key = file[file.index(prefix)..-1] + key.prepend(folder) if bucket_has_folder_path if s3_object = s3_objects.find { |obj| file.ends_with?(obj.key) } next if File.size(path) == s3_object.size && s3_object.etag[etag] @@ -295,9 +305,9 @@ def migrate_to_s3 options = { acl: "public-read", body: File.open(path, "rb"), - bucket: GlobalSetting.s3_bucket, + bucket: bucket, content_type: MiniMime.lookup_by_filename(name)&.content_type, - key: file[file.index(prefix)..-1], + key: key, } if !FileHelper.is_supported_image?(name) @@ -337,7 +347,7 @@ def migrate_to_s3 } from = "/uploads/#{db}/original/(\\dX/(?:[a-f0-9]/)*[a-f0-9]{40}[a-z0-9\\.]*)" - to = "#{SiteSetting.Upload.s3_base_url}/#{prefix}\\1" + to = "#{SiteSetting.Upload.s3_base_url}/#{folder}#{prefix}\\1" if dry_run puts "REPLACING '#{from}' WITH '#{to}'" From a2d1babac69482c4cb0f3a90f745fa335cdb8f83 Mon Sep 17 00:00:00 2001 From: Joe <33972521+hnb-ku@users.noreply.github.com> Date: Tue, 8 Jan 2019 23:33:47 +0800 Subject: [PATCH 064/157] UX: tag container should wrap if there's a large number of tags --- app/assets/stylesheets/common/base/_topic-list.scss | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/app/assets/stylesheets/common/base/_topic-list.scss b/app/assets/stylesheets/common/base/_topic-list.scss index d276ea033c..54b49674d5 100644 --- a/app/assets/stylesheets/common/base/_topic-list.scss +++ b/app/assets/stylesheets/common/base/_topic-list.scss @@ -134,7 +134,14 @@ .link-bottom-line { font-size: $font-down-1; display: flex; + flex-wrap: wrap; align-items: center; + .discourse-tags { + flex-wrap: wrap; + // IE11 is being very stubborn but this is only protection for topics with + // a very excessive number of extra long tags - edge case. + -ms-flex: 1 0 0px; + } a.badge-wrapper.box, a.discourse-tag.box { padding-top: 0; From fe20cb4b56e1ac9b84c3e785a6fac879f10ac3f0 Mon Sep 17 00:00:00 2001 From: David Taylor Date: Tue, 8 Jan 2019 16:22:03 +0000 Subject: [PATCH 065/157] FIX: Enforce a fixed height on generic oneboxed videos This prevents 'jumping' as the video loads. This change will require posts to be rebaked before it takes effect. --- Gemfile | 2 +- Gemfile.lock | 4 ++-- app/assets/stylesheets/common/base/onebox.scss | 13 +++++++++++++ 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/Gemfile b/Gemfile index 88d2c4f81a..7184bbc61f 100644 --- a/Gemfile +++ b/Gemfile @@ -36,7 +36,7 @@ gem 'redis-namespace' gem 'active_model_serializers', '~> 0.8.3' -gem 'onebox', '1.8.73' +gem 'onebox', '1.8.74' gem 'http_accept_language', '~>2.0.5', require: false diff --git a/Gemfile.lock b/Gemfile.lock index 88e2d49cea..9ecacd4a27 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -258,7 +258,7 @@ GEM omniauth-twitter (1.4.0) omniauth-oauth (~> 1.1) rack - onebox (1.8.73) + onebox (1.8.74) htmlentities (~> 4.3) moneta (~> 1.0) multi_json (~> 1.11) @@ -512,7 +512,7 @@ DEPENDENCIES omniauth-oauth2 omniauth-openid omniauth-twitter - onebox (= 1.8.73) + onebox (= 1.8.74) openid-redis-store pg pry-nav diff --git a/app/assets/stylesheets/common/base/onebox.scss b/app/assets/stylesheets/common/base/onebox.scss index 6832098e44..2a9eaa8c15 100644 --- a/app/assets/stylesheets/common/base/onebox.scss +++ b/app/assets/stylesheets/common/base/onebox.scss @@ -565,3 +565,16 @@ aside.onebox.stackexchange .onebox-body { .onebox.google-photos-album { @extend .imgur-album; } + +// Force oneboxed videos to 16:9 aspect ratio +.onebox.video-onebox { + position: relative; + padding: 0 0 56.25% 0; + width: 100%; + + video { + position: absolute; + width: 100%; + height: 100%; + } +} From 97fd12e8af1ba3817a640c91ec26c3da46b385f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9gis=20Hanol?= Date: Tue, 8 Jan 2019 18:56:18 +0100 Subject: [PATCH 066/157] FIX: migrate_to_s3 rake task with folder path --- lib/tasks/uploads.rake | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/lib/tasks/uploads.rake b/lib/tasks/uploads.rake index 106332794a..7df7dccebc 100644 --- a/lib/tasks/uploads.rake +++ b/lib/tasks/uploads.rake @@ -251,14 +251,17 @@ def migrate_to_s3 bucket, folder = S3Helper.get_bucket_and_folder_path(ENV["DISCOURSE_S3_BUCKET"]) folder = File.join(folder, "/") else - bucket, folder = GlobalSetting.s3_bucket, "" + bucket = GlobalSetting.s3_bucket + folder = "" end - begin - s3.head_bucket(bucket: bucket) - rescue Aws::S3::Errors::NotFound - puts "Bucket '#{bucket}' not found. Creating it..." - s3.create_bucket(bucket: bucket) unless dry_run + unless bucket_has_folder_path + begin + s3.head_bucket(bucket: bucket) + rescue Aws::S3::Errors::NotFound + puts "Bucket '#{bucket}' not found. Creating it..." + s3.create_bucket(bucket: bucket) unless dry_run + end end puts "Uploading files to S3..." @@ -274,7 +277,7 @@ def migrate_to_s3 print " - Listing S3 files" s3_objects = [] - prefix = Rails.configuration.multisite ? "#{db}/original/" : "original/" + prefix = Rails.configuration.multisite ? "#{folder}#{db}/original/" : "#{folder}original/" options = { bucket: bucket, prefix: prefix } loop do From 4a01fee41beb9856098ce61567774bcf280e1a61 Mon Sep 17 00:00:00 2001 From: Kris Date: Tue, 8 Jan 2019 13:07:29 -0500 Subject: [PATCH 067/157] UX: use default H2 and H3 sizes on user profiles for better hierarchy --- app/assets/stylesheets/common/base/user.scss | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/app/assets/stylesheets/common/base/user.scss b/app/assets/stylesheets/common/base/user.scss index a7ac6f0b95..1379f9f880 100644 --- a/app/assets/stylesheets/common/base/user.scss +++ b/app/assets/stylesheets/common/base/user.scss @@ -86,9 +86,7 @@ } h2 { - font-size: $font-up-2; font-weight: normal; - margin-top: 10px; max-width: 100%; white-space: nowrap; overflow: hidden; @@ -97,9 +95,7 @@ h3 { font-weight: normal; - font-size: $font-0; - margin: 5px 0; - + margin-bottom: 0.5em; .d-icon:not(:first-of-type) { margin-left: 10px; } From 3ec38f5a3b79cf935e6b3d80bdc6de4500781c87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9gis=20Hanol?= Date: Tue, 8 Jan 2019 19:44:31 +0100 Subject: [PATCH 068/157] Revert "FIX: migrate_to_s3 rake task with folder path" This reverts commit 97fd12e8af1ba3817a640c91ec26c3da46b385f5. --- lib/tasks/uploads.rake | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/lib/tasks/uploads.rake b/lib/tasks/uploads.rake index 7df7dccebc..106332794a 100644 --- a/lib/tasks/uploads.rake +++ b/lib/tasks/uploads.rake @@ -251,17 +251,14 @@ def migrate_to_s3 bucket, folder = S3Helper.get_bucket_and_folder_path(ENV["DISCOURSE_S3_BUCKET"]) folder = File.join(folder, "/") else - bucket = GlobalSetting.s3_bucket - folder = "" + bucket, folder = GlobalSetting.s3_bucket, "" end - unless bucket_has_folder_path - begin - s3.head_bucket(bucket: bucket) - rescue Aws::S3::Errors::NotFound - puts "Bucket '#{bucket}' not found. Creating it..." - s3.create_bucket(bucket: bucket) unless dry_run - end + begin + s3.head_bucket(bucket: bucket) + rescue Aws::S3::Errors::NotFound + puts "Bucket '#{bucket}' not found. Creating it..." + s3.create_bucket(bucket: bucket) unless dry_run end puts "Uploading files to S3..." @@ -277,7 +274,7 @@ def migrate_to_s3 print " - Listing S3 files" s3_objects = [] - prefix = Rails.configuration.multisite ? "#{folder}#{db}/original/" : "#{folder}original/" + prefix = Rails.configuration.multisite ? "#{db}/original/" : "original/" options = { bucket: bucket, prefix: prefix } loop do From cfb8e157a2a1fdb8ab557cc63fb3a2b1201f5fbf Mon Sep 17 00:00:00 2001 From: Kris Date: Tue, 8 Jan 2019 16:25:11 -0500 Subject: [PATCH 069/157] Fixing tag alignment --- app/assets/stylesheets/common/base/header.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/stylesheets/common/base/header.scss b/app/assets/stylesheets/common/base/header.scss index 47acb5a2c3..70ef70888e 100644 --- a/app/assets/stylesheets/common/base/header.scss +++ b/app/assets/stylesheets/common/base/header.scss @@ -258,7 +258,7 @@ .badge-wrapper { margin-right: 8px; &.bullet { - padding-top: 2px; // alignment hack + padding-top: 1px; // alignment hack } } .badge-wrapper.bullet { From e08a3f719c695a6af9b4189832787fab17957bdb Mon Sep 17 00:00:00 2001 From: Sam Date: Wed, 9 Jan 2019 08:57:20 +1100 Subject: [PATCH 070/157] FEATURE: push post rebake regular task to low priority queue This allows us to run regular rebakes without starving the normal queue. It additionally adds the ability to specify queue with `Jobs.enqueue` so we can specifically queue a job with lower priority using the `queue` arg. --- app/jobs/base.rb | 22 ++++++++++++++++++---- app/jobs/scheduled/periodical_updates.rb | 4 ++-- app/models/post.rb | 21 ++++++++++++--------- spec/jobs/periodical_updates_spec.rb | 17 ++++++++++++++--- 4 files changed, 46 insertions(+), 18 deletions(-) diff --git a/app/jobs/base.rb b/app/jobs/base.rb index 0bd5b6fe35..a7d23994a0 100644 --- a/app/jobs/base.rb +++ b/app/jobs/base.rb @@ -197,14 +197,28 @@ module Jobs # If we are able to queue a job, do it if SiteSetting.queue_jobs? - if opts[:delay_for].present? - klass.perform_in(opts.delete(:delay_for), opts) - else - Sidekiq::Client.enqueue(klass, opts) + hash = { + 'class' => klass, + 'args' => [opts] + } + + if delay = opts.delete(:delay_for) + if delay.to_f > 0 + hash['at'] = Time.now.to_f + delay.to_f + end end + + if queue = opts.delete(:queue) + hash['queue'] = queue + end + + klass.client_push(hash) + else # Otherwise execute the job right away opts.delete(:delay_for) + opts.delete(:queue) + opts[:sync_exec] = true if Rails.env == "development" Scheduler::Defer.later("job") do diff --git a/app/jobs/scheduled/periodical_updates.rb b/app/jobs/scheduled/periodical_updates.rb index 2ab9517808..5701e6d6fd 100644 --- a/app/jobs/scheduled/periodical_updates.rb +++ b/app/jobs/scheduled/periodical_updates.rb @@ -15,7 +15,7 @@ module Jobs (@call_count % 24) == 1 end - def execute(args) + def execute(args = nil) # Feature topics in categories CategoryFeaturedTopic.feature_topics(batched: true) @@ -29,7 +29,7 @@ module Jobs # Forces rebake of old posts where needed, as long as no system avatars need updating if !SiteSetting.automatically_download_gravatars || !UserAvatar.where("last_gravatar_download_attempt IS NULL").limit(1).first - problems = Post.rebake_old(SiteSetting.rebake_old_posts_count) + problems = Post.rebake_old(SiteSetting.rebake_old_posts_count, priority: :low) problems.each do |hash| post_id = hash[:post].id Discourse.handle_job_exception(hash[:ex], error_context(args, "Rebaking post id #{post_id}", post_id: post_id)) diff --git a/app/models/post.rb b/app/models/post.rb index ff6c32890b..19d093d967 100644 --- a/app/models/post.rb +++ b/app/models/post.rb @@ -515,7 +515,7 @@ class Post < ActiveRecord::Base PostRevisor.new(self).revise!(updated_by, changes, opts) end - def self.rebake_old(limit) + def self.rebake_old(limit, priority: :normal) limiter = RateLimiter.new( nil, @@ -534,7 +534,7 @@ class Post < ActiveRecord::Base break if !limiter.can_perform? post = Post.find(id) - post.rebake! + post.rebake!(priority: priority) begin limiter.performed! @@ -560,15 +560,13 @@ class Post < ActiveRecord::Base problems end - def rebake!(opts = nil) - opts ||= {} - - new_cooked = cook(raw, topic_id: topic_id, invalidate_oneboxes: opts.fetch(:invalidate_oneboxes, false)) + def rebake!(invalidate_broken_images: false, invalidate_oneboxes: false, priority: nil) + new_cooked = cook(raw, topic_id: topic_id, invalidate_oneboxes: invalidate_oneboxes) old_cooked = cooked update_columns(cooked: new_cooked, baked_at: Time.new, baked_version: BAKED_VERSION) - if opts.fetch(:invalidate_broken_images, false) + if invalidate_broken_images custom_fields.delete(BROKEN_IMAGES) save_custom_fields end @@ -578,7 +576,7 @@ class Post < ActiveRecord::Base QuotedPost.extract_from(self) # make sure we trigger the post process - trigger_post_process(bypass_bump: true) + trigger_post_process(bypass_bump: true, priority: priority) publish_change_to_clients!(:rebaked) @@ -692,7 +690,7 @@ class Post < ActiveRecord::Base end # Enqueue post processing for this post - def trigger_post_process(bypass_bump: false) + def trigger_post_process(bypass_bump: false, priority: :normal) args = { post_id: id, bypass_bump: bypass_bump, @@ -700,6 +698,11 @@ class Post < ActiveRecord::Base args[:image_sizes] = image_sizes if image_sizes.present? args[:invalidate_oneboxes] = true if invalidate_oneboxes.present? args[:cooking_options] = self.cooking_options + + if priority == :low + args[:queue] = 'low' + end + Jobs.enqueue(:process_post, args) DiscourseEvent.trigger(:after_trigger_post_process, self) end diff --git a/spec/jobs/periodical_updates_spec.rb b/spec/jobs/periodical_updates_spec.rb index b2ba6c60de..8b0e88bfc6 100644 --- a/spec/jobs/periodical_updates_spec.rb +++ b/spec/jobs/periodical_updates_spec.rb @@ -13,13 +13,24 @@ describe Jobs::PeriodicalUpdates do SiteSetting.automatically_download_gravatars = false post = create_post post.update_columns(baked_at: Time.new(2000, 1, 1), baked_version: -1) - described_class.new.execute({}) + + Sidekiq::Testing.fake! do + Jobs::ProcessPost.jobs.clear + + Jobs::PeriodicalUpdates.new.execute + + jobs = Jobs::ProcessPost.jobs + expect(jobs.length).to eq(1) + + expect(jobs[0]["queue"]).to eq("low") + end post.reload expect(post.baked_at).to be > 1.day.ago - baked = post.baked_at - described_class.new.execute({}) + + # does not rebake + Jobs::PeriodicalUpdates.new.execute post.reload expect(post.baked_at).to eq(baked) end From 7e4a43afc6643b32aac1c43bede3f1838ab39e06 Mon Sep 17 00:00:00 2001 From: Sam Date: Wed, 9 Jan 2019 09:21:58 +1100 Subject: [PATCH 071/157] PERF: run ImageMagick conversions with nice 10 This lowers the priority of all image resizes so they do not clog CPU on machines running low on resources --- app/models/optimized_image.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/optimized_image.rb b/app/models/optimized_image.rb index 2b5f9864e8..5e67cb8404 100644 --- a/app/models/optimized_image.rb +++ b/app/models/optimized_image.rb @@ -330,7 +330,7 @@ class OptimizedImage < ActiveRecord::Base MAX_PNGQUANT_SIZE = 500_000 def self.convert_with(instructions, to, opts = {}) - Discourse::Utils.execute_command(*instructions) + Discourse::Utils.execute_command("nice", "-n", "10", *instructions) allow_pngquant = to.downcase.ends_with?(".png") && File.size(to) < MAX_PNGQUANT_SIZE FileHelper.optimize_image!(to, allow_pngquant: allow_pngquant) From df460b4abd6156567edb46ea8a673ddec44be3e5 Mon Sep 17 00:00:00 2001 From: Sam Date: Wed, 9 Jan 2019 09:29:14 +1100 Subject: [PATCH 072/157] PERF: run sidekiq with nice 5 This ensures that unicorn master forks of sidekiq run with a lower priority than the webs. It means that a busy sidekiq is less likely to impact web performance --- lib/demon/sidekiq.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/demon/sidekiq.rb b/lib/demon/sidekiq.rb index 0ca7ba9994..bfb25f2bf5 100644 --- a/lib/demon/sidekiq.rb +++ b/lib/demon/sidekiq.rb @@ -43,6 +43,10 @@ class Demon::Sidekiq < Demon::Base end end + # Sidekiq not as high priority as web, in this environment it is forked so a web is very + # likely running + Discourse::Utils.execute_command('renice', '-n', '5', '-p', Process.pid.to_s) + cli.parse(options) load Rails.root + "config/initializers/100-sidekiq.rb" cli.run From 824c3420e9047aea760112ecbeb9c715cb74a574 Mon Sep 17 00:00:00 2001 From: Sam Date: Wed, 9 Jan 2019 09:51:11 +1100 Subject: [PATCH 073/157] DEV: make Jobs.enqueue tests less fragile Previously we depended on non Sidekiq specific mocking which is not the official way of testing Sidekiq, this made these tests very fragile New testing is more robust and complete --- spec/jobs/jobs_spec.rb | 54 ++++++++++++++++++++++++++++++++++++------ 1 file changed, 47 insertions(+), 7 deletions(-) diff --git a/spec/jobs/jobs_spec.rb b/spec/jobs/jobs_spec.rb index 01cbe6a225..a058971ed1 100644 --- a/spec/jobs/jobs_spec.rb +++ b/spec/jobs/jobs_spec.rb @@ -11,13 +11,40 @@ describe Jobs do end it 'enqueues a job in sidekiq' do - Sidekiq::Client.expects(:enqueue).with(Jobs::ProcessPost, post_id: 1, current_site_id: 'default') - Jobs.enqueue(:process_post, post_id: 1) + Sidekiq::Testing.fake! do + jobs = Jobs::ProcessPost.jobs + + jobs.clear + Jobs.enqueue(:process_post, post_id: 1) + expect(jobs.length).to eq(1) + job = jobs.first + + expected = { + "class" => "Jobs::ProcessPost", + "args" => [{ "post_id" => 1, "current_site_id" => "default" }], + "queue" => "default" + } + expect(job.slice("class", "args", "queue")).to eq(expected) + end end it "does not pass current_site_id when 'all_sites' is present" do - Sidekiq::Client.expects(:enqueue).with(Jobs::ProcessPost, post_id: 1) - Jobs.enqueue(:process_post, post_id: 1, all_sites: true) + Sidekiq::Testing.fake! do + jobs = Jobs::ProcessPost.jobs + + jobs.clear + Jobs.enqueue(:process_post, post_id: 1, all_sites: true) + + expect(jobs.length).to eq(1) + job = jobs.first + + expected = { + "class" => "Jobs::ProcessPost", + "args" => [{ "post_id" => 1 }], + "queue" => "default" + } + expect(job.slice("class", "args", "queue")).to eq(expected) + end end it "doesn't execute the job" do @@ -27,10 +54,23 @@ describe Jobs do end it "should enqueue with the correct database id when the current_site_id option is given" do - Sidekiq::Client.expects(:enqueue).with do |arg1, arg2| - arg2[:current_site_id] == 'test_db' && arg2[:sync_exec].nil? + + Sidekiq::Testing.fake! do + jobs = Jobs::ProcessPost.jobs + + jobs.clear + Jobs.enqueue(:process_post, post_id: 1, current_site_id: 'test_db') + + expect(jobs.length).to eq(1) + job = jobs.first + + expected = { + "class" => "Jobs::ProcessPost", + "args" => [{ "post_id" => 1, "current_site_id" => "test_db" }], + "queue" => "default" + } + expect(job.slice("class", "args", "queue")).to eq(expected) end - Jobs.enqueue(:process_post, post_id: 1, current_site_id: 'test_db') end end From a2cb2f236649971c23f74592a3f4605181016151 Mon Sep 17 00:00:00 2001 From: Jeff Wong Date: Tue, 8 Jan 2019 15:09:50 -0800 Subject: [PATCH 074/157] FIX: ensure ember transitions do not get hijacked by discourse intercept-click explicit null checks as a blank string evaluates to false --- app/assets/javascripts/discourse/lib/intercept-click.js.es6 | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/assets/javascripts/discourse/lib/intercept-click.js.es6 b/app/assets/javascripts/discourse/lib/intercept-click.js.es6 index 1fe999912f..20ab927fca 100644 --- a/app/assets/javascripts/discourse/lib/intercept-click.js.es6 +++ b/app/assets/javascripts/discourse/lib/intercept-click.js.es6 @@ -27,9 +27,9 @@ export default function interceptClick(e) { !href || href === "#" || $currentTarget.attr("target") || - $currentTarget.data("ember-action") || - $currentTarget.data("auto-route") || - $currentTarget.data("share-url") || + $currentTarget.data("ember-action") == null || + $currentTarget.data("auto-route") == null || + $currentTarget.data("share-url") == null || $currentTarget.hasClass("widget-link") || $currentTarget.hasClass("raw-link") || $currentTarget.hasClass("mention") || From d75262046d02c8195f8cd3c0782875190344ac24 Mon Sep 17 00:00:00 2001 From: Jeff Wong Date: Tue, 8 Jan 2019 15:37:10 -0800 Subject: [PATCH 075/157] Revert "FIX: ensure ember transitions do not get hijacked by discourse intercept-click" This reverts commit a2cb2f236649971c23f74592a3f4605181016151. --- app/assets/javascripts/discourse/lib/intercept-click.js.es6 | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/assets/javascripts/discourse/lib/intercept-click.js.es6 b/app/assets/javascripts/discourse/lib/intercept-click.js.es6 index 20ab927fca..1fe999912f 100644 --- a/app/assets/javascripts/discourse/lib/intercept-click.js.es6 +++ b/app/assets/javascripts/discourse/lib/intercept-click.js.es6 @@ -27,9 +27,9 @@ export default function interceptClick(e) { !href || href === "#" || $currentTarget.attr("target") || - $currentTarget.data("ember-action") == null || - $currentTarget.data("auto-route") == null || - $currentTarget.data("share-url") == null || + $currentTarget.data("ember-action") || + $currentTarget.data("auto-route") || + $currentTarget.data("share-url") || $currentTarget.hasClass("widget-link") || $currentTarget.hasClass("raw-link") || $currentTarget.hasClass("mention") || From 09cbd08c64d0f405ecfbafee7203ac4946ddac15 Mon Sep 17 00:00:00 2001 From: Jeff Wong Date: Tue, 8 Jan 2019 15:38:13 -0800 Subject: [PATCH 076/157] FIX: ensure ember transitions do not get hijacked by discourse intercept-click explicit null checks as a blank string evaluates to false --- app/assets/javascripts/discourse/lib/intercept-click.js.es6 | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/assets/javascripts/discourse/lib/intercept-click.js.es6 b/app/assets/javascripts/discourse/lib/intercept-click.js.es6 index 1fe999912f..9d115ff9ab 100644 --- a/app/assets/javascripts/discourse/lib/intercept-click.js.es6 +++ b/app/assets/javascripts/discourse/lib/intercept-click.js.es6 @@ -27,9 +27,9 @@ export default function interceptClick(e) { !href || href === "#" || $currentTarget.attr("target") || - $currentTarget.data("ember-action") || - $currentTarget.data("auto-route") || - $currentTarget.data("share-url") || + $currentTarget.data("ember-action") != null || + $currentTarget.data("auto-route") != null || + $currentTarget.data("share-url") != null || $currentTarget.hasClass("widget-link") || $currentTarget.hasClass("raw-link") || $currentTarget.hasClass("mention") || From f73fe36772b3212b7b6ceccdfdcbbe334aa1c130 Mon Sep 17 00:00:00 2001 From: Rafael dos Santos Silva Date: Tue, 8 Jan 2019 22:46:11 -0200 Subject: [PATCH 077/157] FEATURE: PWA compatibility checks in the Dashboard (#6850) --- app/models/admin_dashboard_data.rb | 13 ++++- config/locales/server.en.yml | 2 + config/site_settings.yml | 1 + spec/fixtures/images/large_icon_correct.png | Bin 0 -> 8160 bytes spec/fixtures/images/large_icon_incorrect.png | Bin 0 -> 4850 bytes spec/models/admin_dashboard_data_spec.rb | 46 ++++++++++++++++++ 6 files changed, 60 insertions(+), 2 deletions(-) create mode 100644 spec/fixtures/images/large_icon_correct.png create mode 100644 spec/fixtures/images/large_icon_incorrect.png diff --git a/app/models/admin_dashboard_data.rb b/app/models/admin_dashboard_data.rb index 565a61f620..5b4ed57766 100644 --- a/app/models/admin_dashboard_data.rb +++ b/app/models/admin_dashboard_data.rb @@ -98,8 +98,8 @@ class AdminDashboardData add_problem_check :rails_env_check, :host_names_check, :force_https_check, :ram_check, :google_oauth2_config_check, :facebook_config_check, :twitter_config_check, - :github_config_check, :s3_config_check, :image_magick_check, - :failing_emails_check, + :github_config_check, :pwa_config_check, :s3_config_check, + :image_magick_check, :failing_emails_check, :subfolder_ends_in_slash_check, :pop3_polling_configuration, :email_polling_errored_recently, :out_of_date_themes, :unreachable_themes @@ -211,6 +211,15 @@ class AdminDashboardData end end + def pwa_config_check + unless SiteSetting.large_icon.present? && SiteSetting.large_icon.width == 512 && SiteSetting.large_icon.height == 512 + return I18n.t('dashboard.pwa_config_icon_warning', base_path: Discourse.base_path) + end + unless SiteSetting.short_title.present? && SiteSetting.short_title.size <= 12 + return I18n.t('dashboard.pwa_config_title_warning', base_path: Discourse.base_path) + end + end + def s3_config_check # if set via global setting it is validated during the `use_s3?` call if !GlobalSetting.use_s3? diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index 743a573e48..7c7eb7674d 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -1193,6 +1193,8 @@ en: facebook_config_warning: 'The server is configured to allow signup and log in with Facebook (enable_facebook_logins), but the app id and app secret values are not set. Go to the Site Settings and update the settings. See this guide to learn more.' twitter_config_warning: 'The server is configured to allow signup and log in with Twitter (enable_twitter_logins), but the key and secret values are not set. Go to the Site Settings and update the settings. See this guide to learn more.' github_config_warning: 'The server is configured to allow signup and log in with GitHub (enable_github_logins), but the client id and secret values are not set. Go to the Site Settings and update the settings. See this guide to learn more.' + pwa_config_icon_warning: 'The server is missing a proper large icon which allows users to add a homescreen shortcut to this site on Android devices. Go to the Site Settings and upload an icon of the recommended size.' + pwa_config_title_warning: 'The server is missing a short title which allows users to add a homescreen shortcut to this site on Android devices. Go to the Site Settings and configure a title of the recommended length.' 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, s3_use_iam_profile, 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, s3_use_iam_profile, 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.' diff --git a/config/site_settings.yml b/config/site_settings.yml index afaa044a15..546ba41581 100644 --- a/config/site_settings.yml +++ b/config/site_settings.yml @@ -285,6 +285,7 @@ basic: default: '' short_title: default: '' + max: 12 vapid_public_key_bytes: default: '' client: true diff --git a/spec/fixtures/images/large_icon_correct.png b/spec/fixtures/images/large_icon_correct.png new file mode 100644 index 0000000000000000000000000000000000000000..e453d6fde503c9409e88c0ce8989deaca0c4fb3e GIT binary patch literal 8160 zcma)hXHZkk8}FWykc5^Hq)P(QQ3!})0dhc4KuT1KQY3;(Q2~{Xk`q8cY^W%R3c*TI z!Om+-uz{5*h$tXI@ee9URGO6J=6&y%`|+NcbDrH_dG^_}Gdnx8dpUk9R4IBC0D!8O zr~7IEU_}!KBt#J&T`*}@1QEw4z(W!Le;yQq{*~!4fh`6+BSGkHkXQz?uY=>SK~)#1 z_6N03!KHq1Wf-ctq;%o1%Ee6e%WJ7MvF0Qat6_tmI*s#i+v+^`SbvM{bnWdg(+}$! z7QfHCU|?LGIRED7BM;0hpP5;T=UBgSvVIq4|8B6n&&%vbpvllk)vs{lUz_KSq%R#m zvTEYurpbG$)35Sox{KvQXXSsdq*{#bP!y(?wAybCAkpVIu1Y94bN5Z(uc;y1O@;s% zHhQ_c2Bdu)3Xfg(`}tdyEgcWP926LTUR-c0zo1ZiRk2)N7vDi|wx?gnG&}Kg{Kft8 znH>iMTf+mpS3MF}+5fYvlH>4d^@nQj^Q$ihZ3*hGc~=wIFPtZ9TK#OU7UgePyTF?^ zs5WqZ$N9K-jyI?LT{i_K$Cs+e3XZ^6~QV(%E zAMmnuBj>@jzme5pwMg;JIHk?}GJefhZp3Bf-D5I`s=$}dboAWgX=L#x>eI@EWqmOEXY*w2WE%@`wJ3MRgU~Op|$z)`vtm~riJ<0o=a&qQm z{+%K{NNw`now7~q)Ra7_F@>@#>O~f9Ix~UC_MKCDT>IdhKv+in3SXuXf-Z1^ynfH0 zgpcH&3E@=-`E%C=?fHn~m+IDfee6uV4ecN_xybsmg{yMgh>`xX$n)iSH77PwK99)U zpUFszIcK~C5yL$v(LVB`xZcUt!Czsh_T9kv%DrCd_M(W(7jM_NL#KPqDKYBuLOA!v zQ)!)Xq+4j{#QPmZ7WKg)jsE|L_``K=qqX+ub56F^g}uKv_aklYeEI6|&YB~d&80W* zybgR5O4&$xcjsr|+cdR4>*SgY-}hXLHOA89a>Q@^HYsykKX%2Z>hA2y_$-0vp@@Dn z?6dz54s)gAYlgM6AJZ-f$s2v%$}08G%=P81hQG}_x}N>$AnWFeoW&Wkt&Lo7bwcyh zmdgao$<|J)R$Q)CiI&fYtY#C>^qbtWPliydSnZ`Mn zn*P0bbGFIfy4$l~9-dZ;#Ow!|AK3R1zEgKr>??3hE;>xzCUZU2Gj1%b1aISvnubUepZs zCb9;`Gw;XWZuQ!nQ7ukc>pOkf@Ai9!N`%gH!;wri+L7f@ppAQ5r+krEeNg-7H6=CG zxshO4_I|bUluYuqN^k70*Ehm(ib`|!b@}kfR7cwwY;?4&~;^ zE~r~9Rqi9WtwscDZC&c^NZc)GWtncVE=X3g)rjj@b4}ILKIb=#`?X{p4p5Mmn>l6B z;7G)|c0sKn@?3+&X|En1_J^;*M^v!Xi8Joi=EV9vT%%rT=U9wGM#Q_czr5NVvfz_m z+>XLkU>)iC1RQ`)tNm1ye328{#^iSEIY!xJ1+{>FydGMjmDT@5s01_Cs;RrhaIMc1 zsPi{Wt76fKnq$Lu^{JzKxLPWKJJ2U>6j9Xvo&k%&`*S&6njwFaD42EpVAMXCeG2{V zzRWY7bL)6PudUAXJhIwi!9h)ni?&eVyP4A$(OXLAqL zj=#v~nUWko)F;i0`Fg||q0L|l%6=+q))u*OuYCHH zpYXv0Vf6J6Ew?sbA0Do&tNYg9pL-O()(x7eB3-aM?A!R_naT2*>DOn!k4KLUu{J!k zQp!KF2U}c;;Jc?8szPLDF#2_<@cQsK^L>`Z^U%V*2H>=j_qhC2R!dg@inf;V_t7SV zK6@<9AlDA8?Uvg~d0D#|duFa|j%f9&(k!wjtbC>qoCki&|1O}TvRkvzl{YM{wc=H>rfi=5=~qTCgtYyp^Rd2Uju&lu z=xWA5exjuNc%0Ica3y{P6uNaRLG$dND1oq7)K^b#-iVpnQHoS~vVQsBnWZZ(e;5&v zJvZX@5oTclu$-3qLw@4R`{D8a2eXTsfa?-!2z0HtrCMgzafscZy1&oEZL@|ZQ(qPp zXf909m)7vFIuJ?gC|~(^{mh?hqQcbYz*M|@g$4d&nb zPN}yIn(+_d^DK#Bnx&pcvR8zzM~k5%jZz$tQ}A1&$(i=~AkupatRiEDo$?Fi`to~` z!vvo|J?t^1*)Me};{CF-zZf>iCB2?-to_J*pfkpM7BBx{BwlY1l9z@Be4$ol4$6lA zq?TuA#5))m;TcA&SAC{hL<097o=^X`s(b>newD81k1|rYgNQlEA}kwww!`}}o;c(< z2^ZB^Z36p88hI|f5PJ#RhuC+%j&E$tRPB?0q9*F#F$C|oYb2jm5xMp&>9Rk#Opa80bMQGs(w+g)@-G2) z<|I=ne}#!C_AyWPW<5=C;3&mbfAQGmB~6uW^pi30=^ahbuQE^s)pNn`m8oox$pZ!dgPt z%_}ksCi0>~m0Nu05HIo;K9>ALBL_++8)4>GkcqO3Lc^40f@C9S3Vs6IFGaXDknFCO zWD&pppFv&ldyDHbH!So85Qh#8QDbg}MDK{k)b2vLs`y^Ri3CSjd3J|e0pc^FlcwVj zy&e(q?rMhRLYIU79D1n>c96vf9KnPBJ8f$ookv1TG@`*v?Z%w7l*AfUS!XNrVtfcG zNc8wLIK5UCqeBffO?jb8*~5Ek8Vm)DT4*{ZSNMW#bBTG6`Z6j5=c>mX1&b21Yqh{> z(s9-rLA-(TqIb%?5|ywJ*BD*SlP*Lha;@lD-DWiLndoVt@_OP#3%-Ak71U!1N!O7s zg@Qyq?&ofJG|~dEPMNs-R+Oxa`y$WNgtIm`*8vya=p50Cs5*kkhA=&xT~kUIU4W=m zg2Fern*&)B2md(Y)dwdsH;ZZrxEfz0NF`>cBmS)JIGWgkFQ@M!1swL_=J&w6T1MxN ztE_#>ulHjfih?t{RJk>lvT|MV`kSD6QB}blDDR#s)(X|Lk)V0NUd`&PC$5&NSc@&8 z>OZyWje+F!L?vzR96wy7nRdoj;jCYz8lovV5CeTVDK@H8Iu+{!#$CASdN(W@w7ABe z;3rwS`29a056rMgtCfPNL5<<-$rEQ6K;^qOGP|}F9OIXO-LHwPcY3_>aB&b3{vSWn z3!Fho#keqoE{Z}+`YQ!N;$k!C=|<@60ZE3DWUcEd6W|7qEHW3>{Y0e&_^2C*H{~Eg z!oVLdY)BsuMz=9_3#Qx*Jf8tpHCTZbagH#v*rx$4v@qXMRN(M z%C2-@P@>HZs_$U6kKNP+x2Y(nZPl)5+pb8sL8pRX_&~H8{zo@Nc~%v9eiy ziPveb&k!1iO`!CxA{&+5?b(|~m}x$SMK6eh`ECD@eresO; zJQ%zcz3QPp3LzY@KnpIVa5aD(V#Tdxrodv9qzs4s00kC;Jbh$;ESyM}XeALZ?c%Po zf@0uYQ`essq|Z^moZ4*an`)p~IM>Y8a2AP$P&jsq^NB)17hQ&-%^2;^BMHqH$SlOC z3Gf7c9-Qi{AaWEO(>Sr#tJR?EI($Z}QBOuS%jhe9%lSYeBcY@q=imKxNx5S#XE zAdZ9!7|_e`eAM$OIselMAPTWC71qk`}@oifAS1 z^}@*`x6f?7po9O%yW%l4?lCb*qK!~Lsh}~LBxZ(b7(AI->4OS9fHLIdRU}QubPK?; z;|2u|OgdC%NGymTGHG0QJI+0ej-5276Q$<435dna;INz;taK-+*d#7E=$cE#ZcvIn z!CTyAbeAWt&zV)qHxe~^6wUbQaY#_vLo&z12yd~%cZe3D)YF8`8%W|PCO82qGU9R* za&kNIxy_7y8;t)$_M6~b{i35QfIW1TlxR~1sE(B`6?q4~>EP!Ljme2d3S?qx*X(IO zo1*g{4;1XKk71NH8H-B8m@5|;FU72BXw`~Tu!HY1+KA!oy-a-e2dDsS^^!u+<4F-r zg@}h|V zhF6xaB{9d$ER;R)^^a4b8x*bslo{Q7rUc@m161g7Dd55z1tcyFYWVR=UB2A<1ll2A z_}C17+=>q&FezL~Pa(|b8Tz5zHhf%wRyEQD`VZ92U{;)HDF+VdC5g%A;51@zybf(3 zm|}#3U}~LYDUErYcn>ngbc;Za*rI4TI7vj!Fme&N&tZ6?i>jK4E2$R8ff*B8;tn7U z?yz>Q$zG6YjFYI*vj*r0Nj1B$2-V3YKaIWlu#J~c|FtRH0dsylt>9t=w=)oZtgYNp> zOxK^67uJ%g>6&*YPP*b~$NcQ5NgF&9?rPK!z&?0Z0mTGnm11lnp)wuS7yQ__SM8dIsysh_C+-A@jj~kqLf8P~clHM5%w3-F#3OZ2=T`;b^<02%L!m zF>u;Ofv+oAL6TTQxlcj53Y?VV4nE){^1y8pRUg@CN0J=o&$$K-YKRkP%r-)hswUj9 zQQ&;;C^dyVu#;xfjuvkxNxYDzjmX+HSOnc-L{H>yjM;LDMXIC(kY&cHB3m$N0y-Cs zovryD+HH=f(QynMq8l7l<%S{?{@`#e(>likKPXpW&eHh^cA4W+UqFGv=83)_o&;h( z2GOx|#JRmR!56jy$a4U4GSi9l)Cm8vbk(?$+c|id9zKjx%MdLpS^R4hDkcye@#V@P z$dzoa3EXf%RO$|vA<}N7;3af9khRjlO zu?@5&tEsVUA1+k&BU_9FBYlZv$w!ik7wWzhg55wbgLqa$G+1;H{BNcX;qEGt=z{&QjJXf(vK9veJ{_wX-X<1a&l(tGKHw*5@&i zF{|JsloBL1o1+s6Bogw=ET0R7$#}_Vs+`R)8)9 zm2`o{KG12zYD*_NJ&gs%=AslD?vKCDQWmq6DbMg6eO%1e4O?)I#?>dP+Em`i zyje3QQ<-@bbZQB@T_i?+2(=9U$A{?x*%$Ko(>uDDJ;eFndnrixXeU6->>soYe9C2zKy zPB?1HJGBbYq?1?@ta9^#ib^sr6VCFPuGqVM8~jVLyeGiqgy>M>G%`=np($PpsjSmW zJX491X5su)X7(D4u0xyj3*NNciD1!={PYFO4ib}gn_Ti$k#-eSgOfa9jvIL) z-&1uA;z4!^IO8n>Gc#M3pIqtcM_11za@}%l<c4VHk% zd04vwIv0ZtUCExKf^~p~svJMCtdQ8Yji$MWSC3FH12r?;$OC!sJy}h2k9;@X7Ya~0 zfe!qvfOl!gXb6^sSa_Ra{x6P7Jf{;vVpK&tm2>@Znao3K+spV4Jo54KEgUjhKOTwA z))TGKlx>frv?<-9g{FHAXfi zRY>vAa{y1>qPN6Dc;ORrtyx$Aun7%|W+YNW&|ePd_nUZ{GJpson-wECL#uy4J%*)H zVO;MLAYTA%psnMmXRXq%&9QPCBp5iv4>sZ~UkEMvNiJHg-h57Wwnx~pOj+=ac_|4V38OPl8y0!Z*hzShwAE4d8>-WwP^e=FNwj(CBG`Z&J^GOu<*(=s>avLoe4 zG03$74)90{ow?1h=;qXlAzmfQ6&vc5fE-1-5~b-WCY48mbl!k^5;wtD643IKkt|1c zBGki>geJIt5?KDm8H3&Ik1|9TuTo4QR`hPMG`didaQzEoo_vD}wh>`b7f800sX+~r zXdO9|%vy%9qz;19#_G*V>_-zy_V^oBk*F1a)Rv1mU9Z?Sozu&>Mtp37MK6W2Zsb%L z&G0F}Z5VLK;%*KyGIInA2>N6XB73k zrL^nInygFT8C1C-0TPqBb=t1vF`UtLsABXl@;So3jfq7&LuMa+vJUpE7x2yz9vfq+ z&d`G+g;`69)B9T##u#PAS#b~1Ajcp_=>c`~QLogFK zDacT=rmuB`(En?lkNHH}L3NiEmrvBrXFItp zb}G=LFcOse7zgvpnBz|R^mJ53m<%+S*K!P5Q2vqzG$7(WGHYM#b7OHrPtSVC&{roj_ey)N>$4|DqHm(l zEj>V|;y4QidV0DC4L^;ZzVwFoD&J+-RCE|zNfW)nx4Q|%rZ2zu4u7lZyft}<5jOKj z4JfvO(_~SmiecV^{@%>0CMIg#_)>XjWIElL zX#gj!U%u5)%$OMq-|uy|4Y^G{AaVI0+qX7(&+qBQDPS!+@W4mMNzn5odCKKyXr~%d zc>uqwp8iu5V$;_tuk^r*vIo;bYd z^~urN1j7UDc4@>t`L#+z=+2|qky!0zqU+<5neQ!&dtThTlhRyY)p;^-v3#cH!`8iX z-6x_JVJh&*`sm()+mDd)n658txJ`Ssd@5swf#@kmO%su1zRnD_BJ~O3-bt9ltx;pA zqWj(P-rMpG6+BVv{=IjzHL-0nWr$-RV1o6JQg>J6^OCvbMvf%{COMD+E9dwG*K9zq4>i1x+RcTXL2EC4D7i-g&$cJFw@ehvoQ z@#HyFE6PdjX!GCl%Y{T5-lRkNjs|<<5&dkq{WSQoJ<-QHW_@$Ql$(m+7P|JTq;GKg zNC~l|KiZrZFnN2?bMW04S5@_Ed2MTRNVweV3}gbQl_u`U$Vf@a%F4QG)6XuS7gS97 z{GdAI6BCXJJ5HM33!Xg@a;Z>~jokztIuhcML&6k|q`~6W)?BvQcMbWP+*fcEkmtw3BQ*@}UX2-w9 z49-v#6^I+AqE(FX2Z7IZ6NM5F&F#f(se zW@Udc>t9!0#Da;vK+ln9j9++qh{T*Ty2fP82==UwTNS7vbRI^GJuM+IuaB4f&U@=Y z{Whfl%h*}QVDL+eqG~MoL&EPBwpl4&}yX z%}+47PFhNfVsA0IMG}6wkhP=2g(ev&iCFkYcg{DDMNb2EJ-K(d(0J}Yj4Z{yu5Ps@ zT-@JX^2)8S-W--XPwi^=+(5fUvl50`X}u*o)0h+YgJ*a7UN36Vt+u^!@o~V-j=%$< z$GksOA6au@&6Dh$jHlwNYvs=qkGmd|PO=Y~<~u#=SLOVB0q^DE=YGYFoBe+P&3V)N literal 0 HcmV?d00001 diff --git a/spec/fixtures/images/large_icon_incorrect.png b/spec/fixtures/images/large_icon_incorrect.png new file mode 100644 index 0000000000000000000000000000000000000000..e750b90196048cb760bbaca1e03ed99cc0d80766 GIT binary patch literal 4850 zcmb`L=|7Z@_r~x0o*6TiVFqJl9s7_iB_x^bN=zuqHiV?IltPvnOZHHxghC|ynj$rW z4@t=yk}`H7jV#%QuiyXhI}hIHT(9fFd2qd6=fR05TbiP|M7RI|K%1KxSpxvLKY{=X zw%@Ln+1u_58rj0ucwhd17AFsY#REibK-UVe@B$pEz~y_u^@qUid?2b8Ncj)Q>;v+D z0>xH9$qZ070+cTT6@NfcS+F<)EKUVWJE7tmu+ltODIH$y057dWymaR%Ek%JKs?>)2 zz6%x(7pyujQD`ZNLQ2;;sG~R!H@K1PjvJ;Mn4o!2F|JLujrCmysAi0!kPohk=A z?W8}+D0O)eyURR#WDfSLD)wtD_rJI~xcF>DOJS6xFn&sI{G8(Sa=|ZanO}~?C41>* zC+WYJ6jrXOudEfX2J5cgHC=yj_FwkJjbhKu8lTOEfUV|`?T?W=y$S5mH15-nkpa|wF;(!YF+I3m9m`H86DJby!nh%d%T`(-kGVb=9UwmQI z=w56u`CykTWhFqPQD$VR;ML5l7~xA8zq4jzKZRVA42wSWt**pl0vs~K4{>>!N6XvH z;hSh2x^z`JUpEVY&%fNs80bFhOPnR-C3`eQda z`gUtq56eZovye`HKCXa?-et%WY*_-Kk{MgDvOGC|MZhBYAxQjXLM`?ss z3;ABvmoQgRL!JDymB#%pwtQ>fL*SV)q_>vm=XegIBWbGr_jOy2@I)RHgjvCs@iT^9 zZw-9w?r$7ETaNSc5J8V9F)xYmxfW$~({pL9BRhAMdtM6Fd2Llb$+VJ9D;`Lo*(zmP zeD3aXM!&nrzB!Y2P*UslKkuq{oBnb-l_`)&fRT^N{MfY{Yc`&hsv6JEr`3+6eE83) zI%L_fLN|&l#57pX#=a65{@J-MfvvZDrNpy=#)iYx-#&?8KK?eQujERlR?h5o z6wG$1AchgF3tQW!-zFR{V9|J!sCL}{(( z0jUa5Uca1>_geFKsJ}3Z-#spekaR>u>(XQD zMf{dAXSDvD%oQC}t}SU5>*rkkM=vs-y^>hEkd@^dJUZ&=;zVR?MZ%wP_5;&>7(I~x zyKoUTeC_vkO00r&%;+Th$M3ET$4CqLE4KVWn*Mw2$vab(?|JdhSi04{eir3x_CWK1_M&h&C72kc}<#2h|4VTen9W;XZ z%;#>&hHgDBW?mJQUDxs!2?bq4JkjK$4b<+aEd{e#V>XV60Yl&;$e%eovA1>BbT@QKa<6#}tQ696 zJ)G6OJfyO7T6=SwPW>Rt6M1nJsNwaZdoDK2-ZJrtVk>y+Gv$3D4^Fbpo|;6^kNXa8 z)V2lAjG?GAL>rniIK}?+RmKQxuu9Kj204-TFgTO;YxkzR~Hu*K}g4kdJ>< zH~_vk+F7$|csQcbe;qpGeumsw0gALgo7J*^$QDh8RE#e5G!1|r96{H`s3%?%iRvTe z#&UJ0{vpguH~-S(R89i*l7=8Y0wF(ti)0t`cXl%rmBbfte@rNFf`gG+ypt%U-fML& ziWiQ2nFh?JH1&4Gl0v6&HIT%jlV77H;*C}|H4jte+klVWcHNMt9DV*1+VwcTzbw^` z=~Na=L}EC`akr37<6Vr4&a~oTvX(h7!JScG0u%9}6;^9MwWB;)Q1}RLh@{&7iI5Ds zS(T2bXtb}5&ys8eD>+U<%t6`Rwo@IJelL#_M!14l%4B}PPNx>$(UoC9sp5W(j~u&O zH~=w6%itJRgi0gukN;O@9{C)w5ew-TBN*kuJEwKjafW9g-id^cQQd_wKxA?Q|su`j`-34bNLgl91h^MX9yz!~l#Stl<+~gzRdJYC6 zM)~W`Tgmiw9-!O-SaJeEpQP@R8U#>Gd(uM*WQWUrK}6gKj3Fa=kfTfKGHnxN%uak2 z6E-2j?|ywLep$59cS*#||Iqmo7}3U+d*pewVjIt!d>Zu|LSY5sY3?=(uk+|Aal^0T z(NGy4K{5TywVJP|iJ~5K&WYB@Kg5cMb{onc0R6!rGtdw#>WLjL>V!%?a>*zi-uL{+ zQbop-9w0_-q!@AsH9)v%0F098X4`z}*bD_b;U*w#lo^+&5Rn;NsLB)--#Oh+a#bV% zcMkQ%SR({ZNd#HN1BE8E9MY!<-t@D?VrIl_x;x2V%h|sfj6OCT*^Bg0ECs(>#^p?; z&Xa_vjr~tSJH}bSwK4**>is#U2U#eXz>raaLMd$?v=FpDUyHtIopp#pXtRw1DV#9n zX2^1=w&VflYH1}vzn47IAV`B!SkDz+WJPlmkCuo#j)H8DQyw*{lH4Po8-tYBtS$bto!eIw z?X6^d{c}{6BA)idoHka1&2@=IZ4mJU(|f>zCd4yGU?i0BX%~Hf9nA4-;G_NEmd>PD z4|4YPBd3+Z$PEI~zoW_UmjlgCPgL97&s9kS$nU7U*#;Qo>*O}_&_-jYo;@{RWK#!BcslL+C z#~oJm5+2t`Fym&qa*-Diw-q=1g3Hy$ zNj}!YJpCNMnUV+rO9!eaM88(Si9B~RZPFw&93R3NAyYhFGMHRu6mn+Q^VB@e=Bo&* zflv9JF~W*wz%OXPOiqTU!WpMGQ~?KC3$8gL^A^Qi6CfPUUG=$Ov3vJ*yt_p4!!F@)kHTmz5$Srq>};sp<+xuakz3j*7<{=MI2 ztMypJ(TVrPb*Sdt9nEyxeq$o3@iAyJ#JKk+doP5OI``~!!dOMhLIg7APxQ^JH?}5> zwfn4Iyo-jW^wh5gpqm^k#?@2+mah%ZcND@*5GiGNl{xN)-g|wh37#4rg6oF`qMh#! zyr80~b3+}*iv>^t_W{4_-Wx-#SZaCA>H_~`i6HMOY0BmC;N8}q$}jQ9%n+F4enSjn zMtsZi5G$pbPPSXmkUP%zS!8`TeRL}uqF!Jd24|Hi1L~C@=)&hsLwIZS^Dctgxp~j)ydTy@v z`Jv5CKB*>)3nMTlXKDwZ!LcF6=->kl_Qya+G&TLv{5H@4C`!Rr%Oe!7a(AwNdoAO> zgL|)?I2rNbj%usb;5l9w?cIN32dJC>3R&>s&m=WSRqNr*!=siMewUng6U#TF%v`c< zx7LrxASD=I8K3S=_rYs7To?E!=CmH-2fo!#UV63K0Lt#3eIx|QuGe73Y~PMveE7we z`_qDhcV4RQ=tYMomlj;-!z?1G`s;xnD7cx2rSH=<*7O_cdm9sdv9H$3%X3npiyiIGdc`Efs!Ab)X;~_!YJF$uEK}R;nmp6q_-}_dMLyax>)9oW$dI6(4q^ z(%Dnr<9oHSZ2vYUevxl&`-V740QA%-^8UmcO-^SI%xy>d@>yAp*QBH*`*#N2+sf>! zauNmk-*#$}JHu~;PULJb+f5Ywd0lkYSH9BmIydx4qM-GKLXV9vv54MgtAm1z!@5T3 z_Ke;1zst|5m+Qs7WJz0fp4cjzUyhS4&3vW1wGGwr{@VJO2d3tnMX&)o)DWylW|RgeMVa!X zoe-#jb-_2vwX0RRkk2Y_w!+wI+jpjs70S(@T$ACgl|Pxim-8)ibAY{1-3!nAVeCy9 zHR}AmMrR(ke#y|@E@u7#F9pVDdiXCKuh{lj--@r09BV?g$18^_ii)LsQeAb0C?jh`4%);sN$7 z{+SiX?FW@rNLgt63$!Xx;2<}q3bv0cAfpq4i|r%&9k6fl>Q>bNC$#_}Awr)UY@hS5 zVH8*JSTEBY{C2VZa;7sDX@hM@d(YSohD?!YCI& zgX@;L+#=EMxaSaCD<^s;fMbz9e{-%c$>*xgIo*VZs=oVZqeMC~{Byn@7DdAT@l)?v zT4`K7N|6oi&cjKlbmv|Gohv^~-hP{%EKlLu<; BikSca literal 0 HcmV?d00001 diff --git a/spec/models/admin_dashboard_data_spec.rb b/spec/models/admin_dashboard_data_spec.rb index 9ba85feb00..8df2a32f5f 100644 --- a/spec/models/admin_dashboard_data_spec.rb +++ b/spec/models/admin_dashboard_data_spec.rb @@ -192,6 +192,52 @@ describe AdminDashboardData do end end + describe 'pwa_config_check' do + subject { described_class.new.pwa_config_check } + + it 'alerts for large_icon missing' do + SiteSetting.large_icon = nil + expect(subject).to eq(I18n.t('dashboard.pwa_config_icon_warning', base_path: Discourse.base_path)) + end + + it 'alerts for incompatible large_icon' do + upload = UploadCreator.new( + file_from_fixtures('large_icon_incorrect.png'), + 'large_icon', + for_site_setting: true + ).create_for(Discourse.system_user.id) + SiteSetting.large_icon = upload + expect(subject).to eq(I18n.t('dashboard.pwa_config_icon_warning', base_path: Discourse.base_path)) + end + + context 'when large_icon is correct' do + before do + upload = UploadCreator.new( + file_from_fixtures('large_icon_correct.png'), + 'large_icon', + for_site_setting: true + ).create_for(Discourse.system_user.id) + SiteSetting.large_icon = upload + end + + it 'alerts for short_title missing' do + SiteSetting.short_title = nil + expect(subject).to eq(I18n.t('dashboard.pwa_config_title_warning', base_path: Discourse.base_path)) + end + + it 'returns nil when everything is ok' do + upload = UploadCreator.new( + file_from_fixtures('large_icon_correct.png'), + 'large_icon', + for_site_setting: true + ).create_for(Discourse.system_user.id) + SiteSetting.large_icon = upload + SiteSetting.short_title = 'title' + expect(subject).to be_nil + end + end + end + describe 's3_config_check' do shared_examples 'problem detection for s3-dependent setting' do subject { described_class.new.s3_config_check } From 4232d32699526dfe7592e39d009e529c519280a6 Mon Sep 17 00:00:00 2001 From: Sam Date: Wed, 9 Jan 2019 12:28:06 +1100 Subject: [PATCH 078/157] PERF: reduce workload when optimizing images Previously, we would initialize an ImageOptim object each time we resize. This object init is mega expensive (170ms on a VERY fast machine): ``` [1] pry(main)> Benchmark.measure { FileHelper.image_optim } => # ``` This happens cause during init it hunts for all the right binaries and sets up internals. We now memoize this object to avoid a huge amount of pointless work. --- lib/file_helper.rb | 53 +++++++++++++++++++++++++++++----------------- 1 file changed, 34 insertions(+), 19 deletions(-) diff --git a/lib/file_helper.rb b/lib/file_helper.rb index 3f148fc278..feed479b0b 100644 --- a/lib/file_helper.rb +++ b/lib/file_helper.rb @@ -83,28 +83,43 @@ class FileHelper end def self.optimize_image!(filename, allow_pngquant: false) - pngquant_options = false - if allow_pngquant - pngquant_options = { allow_lossy: true } - end - - ImageOptim.new( - # GLOBAL - timeout: 15, - skip_missing_workers: true, - # PNG - optipng: { level: 2, strip: SiteSetting.strip_image_metadata }, - advpng: false, - pngcrush: false, - pngout: false, - pngquant: pngquant_options, - # JPG - jpegoptim: { strip: SiteSetting.strip_image_metadata ? "all" : "none" }, - jpegtran: false, - jpegrecompress: false, + image_optim( + allow_pngquant: allow_pngquant, + strip_image_metadata: SiteSetting.strip_image_metadata ).optimize_image!(filename) end + def self.image_optim(allow_pngquant: false, strip_image_metadata: true) + # memoization is critical, initializing an ImageOptim object is very expensive + # sometimes up to 200ms searching for binaries and looking at versions + memoize("image_optim", allow_pngquant, strip_image_metadata) do + pngquant_options = false + if allow_pngquant + pngquant_options = { allow_lossy: true } + end + + ImageOptim.new( + # GLOBAL + timeout: 15, + skip_missing_workers: true, + # PNG + optipng: { level: 2, strip: strip_image_metadata }, + advpng: false, + pngcrush: false, + pngout: false, + pngquant: pngquant_options, + # JPG + jpegoptim: { strip: strip_image_metadata ? "all" : "none" }, + jpegtran: false, + jpegrecompress: false, + ) + end + end + + def self.memoize(*args) + (@memoized ||= {})[args] ||= yield + end + def self.supported_images @@supported_images ||= Set.new %w{jpg jpeg png gif svg ico} end From 6e7b383ed62db69d2883c63b5bd3d3de4bf99e28 Mon Sep 17 00:00:00 2001 From: "Justin W. Flory" <4721034+jwflory@users.noreply.github.com> Date: Tue, 8 Jan 2019 20:31:27 -0500 Subject: [PATCH 079/157] [docs] Add instructions for CentOS/RHEL users in install guide (#6861) --- docs/INSTALL-cloud.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/INSTALL-cloud.md b/docs/INSTALL-cloud.md index d6989479e8..1813c7955e 100644 --- a/docs/INSTALL-cloud.md +++ b/docs/INSTALL-cloud.md @@ -107,9 +107,11 @@ After completing the setup wizard, you should see Staff topics and **READ ME FIR ### Post-Install Maintenance -- We strongly suggest you turn on automatic security updates for your OS. In Ubuntu use the `dpkg-reconfigure -plow unattended-upgrades` command. -- If you are using a password and not a SSH key, be sure to enforce a strong root password. In Ubuntu use the `apt-get install libpam-cracklib` package. We also recommend `apt-get install fail2ban` which will default block any IP addresses for 10 minutes that attempt more than 3 password retries. -- If you need or want a default firewall, [turn on ufw](https://meta.discourse.org/t/configure-a-firewall-for-discourse/20584). +- We strongly suggest you turn on automatic security updates for your OS. In Ubuntu use the `dpkg-reconfigure -plow unattended-upgrades` command. In CentOS/RHEL, use the [`yum-cron`](https://www.cyberciti.biz/faq/fedora-automatic-update-retrieval-installation-with-cron/) package. +- If you are using a password and not a SSH key, be sure to enforce a strong root password. In Ubuntu use the `apt-get install libpam-cracklib` package. We also recommend `fail2ban` which blocks any IP addresses for 10 minutes that attempt more than 3 password retries. + - **Ubuntu**: `apt-get install fail2ban` + - **CentOS/RHEL**: `sudo yum install fail2ban` (requires [EPEL](https://support.rackspace.com/how-to/install-epel-and-additional-repositories-on-centos-and-red-hat/)) +- If you need or want a default firewall, [turn on ufw](https://meta.discourse.org/t/configure-a-firewall-for-discourse/20584) for Ubuntu or use `firewalld` for CentOS/RHEL 7 or later. You will get email reminders as new versions of Discourse are released. Please stay current to get the latest features and security fixes. To **upgrade Discourse to the latest version**, visit `/admin/upgrade` in your browser and click the Upgrade button. From 25269a37aa240f59c7929afd5c386d9050016614 Mon Sep 17 00:00:00 2001 From: Joffrey JAFFEUX Date: Wed, 9 Jan 2019 02:44:24 +0100 Subject: [PATCH 080/157] FIX: do not show #uncategorized in category drop if setting is disabled (#6856) --- .../select-kit/components/category-drop.js.es6 | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/app/assets/javascripts/select-kit/components/category-drop.js.es6 b/app/assets/javascripts/select-kit/components/category-drop.js.es6 index 0fd2d18751..3fa4470e96 100644 --- a/app/assets/javascripts/select-kit/components/category-drop.js.es6 +++ b/app/assets/javascripts/select-kit/components/category-drop.js.es6 @@ -159,6 +159,16 @@ export default ComboBoxComponent.extend({ } let results = Discourse.Category.search(filter); + + if (!this.siteSettings.allow_uncategorized_topics) { + results = results.filter(result => { + return ( + result.id !== + Discourse.Site.currentProp("uncategorized_category_id") + ); + }); + } + results = results.sort((a, b) => { if (a.parent_category_id && !b.parent_category_id) { return 1; From 2748822576ab0aa36ce35097c99b34a9f9f1b61e Mon Sep 17 00:00:00 2001 From: Zach Whitehead Date: Tue, 8 Jan 2019 21:17:50 -0500 Subject: [PATCH 081/157] FEATURE: Remove option for Google Plus sharing (#6864) * Remove option for Google Plus sharing * remove google+ share translations --- .../discourse/initializers/sharing-sources.js.es6 | 11 ----------- config/locales/client.ar.yml | 1 - config/locales/client.bg.yml | 1 - config/locales/client.bs_BA.yml | 1 - config/locales/client.ca.yml | 1 - config/locales/client.cs.yml | 1 - config/locales/client.da.yml | 1 - config/locales/client.de.yml | 1 - config/locales/client.el.yml | 1 - config/locales/client.en.yml | 1 - config/locales/client.es.yml | 1 - config/locales/client.et.yml | 1 - config/locales/client.fa_IR.yml | 1 - config/locales/client.fi.yml | 1 - config/locales/client.fr.yml | 1 - config/locales/client.gl.yml | 1 - config/locales/client.he.yml | 1 - config/locales/client.hu.yml | 1 - config/locales/client.id.yml | 1 - config/locales/client.it.yml | 1 - config/locales/client.ja.yml | 1 - config/locales/client.ko.yml | 1 - config/locales/client.lt.yml | 1 - config/locales/client.lv.yml | 1 - config/locales/client.nb_NO.yml | 1 - config/locales/client.nl.yml | 1 - config/locales/client.pl_PL.yml | 1 - config/locales/client.pt.yml | 1 - config/locales/client.pt_BR.yml | 1 - config/locales/client.ro.yml | 1 - config/locales/client.ru.yml | 1 - config/locales/client.sk.yml | 1 - config/locales/client.sl.yml | 1 - config/locales/client.sq.yml | 1 - config/locales/client.sr.yml | 1 - config/locales/client.sv.yml | 1 - config/locales/client.sw.yml | 1 - config/locales/client.te.yml | 1 - config/locales/client.th.yml | 1 - config/locales/client.tr_TR.yml | 1 - config/locales/client.uk.yml | 1 - config/locales/client.ur.yml | 1 - config/locales/client.vi.yml | 1 - config/locales/client.zh_CN.yml | 1 - config/locales/client.zh_TW.yml | 1 - config/site_settings.yml | 1 - test/javascripts/helpers/site-settings.js | 2 +- 47 files changed, 1 insertion(+), 57 deletions(-) diff --git a/app/assets/javascripts/discourse/initializers/sharing-sources.js.es6 b/app/assets/javascripts/discourse/initializers/sharing-sources.js.es6 index 953aed8cb7..b619969ac5 100644 --- a/app/assets/javascripts/discourse/initializers/sharing-sources.js.es6 +++ b/app/assets/javascripts/discourse/initializers/sharing-sources.js.es6 @@ -35,17 +35,6 @@ export default { shouldOpenInPopup: true }); - Sharing.addSource({ - id: "google+", - icon: "fab-google-plus-square", - title: I18n.t("share.google+"), - generateUrl: function(link) { - return "https://plus.google.com/share?url=" + encodeURIComponent(link); - }, - shouldOpenInPopup: true, - popupHeight: 600 - }); - Sharing.addSource({ id: "email", icon: "envelope-square", diff --git a/config/locales/client.ar.yml b/config/locales/client.ar.yml index 0237414737..152c6440cd 100644 --- a/config/locales/client.ar.yml +++ b/config/locales/client.ar.yml @@ -180,7 +180,6 @@ ar: close: 'أغلق' twitter: 'شارك هذا الرابط علي تويتر' facebook: 'شارك هذا الرابط علي الفيسبوك' - google+: 'شارك هذا الرابط علي جوجل+' email: 'شارك هذا الرابط علي بريد إلكتروني' action_codes: public_topic: "أجعل هذا الموضوع عامًّا %{when}" diff --git a/config/locales/client.bg.yml b/config/locales/client.bg.yml index e2bd18ab23..4fd922a87e 100644 --- a/config/locales/client.bg.yml +++ b/config/locales/client.bg.yml @@ -108,7 +108,6 @@ bg: close: 'затвори' twitter: 'споделете тази връзка в Twitter' facebook: 'споделете тази връзка във Facebook' - google+: 'споделете тази връзка в Google+' email: 'изпратете тази връзка с имейл' action_codes: public_topic: "направи тази тема публична на %{when}" diff --git a/config/locales/client.bs_BA.yml b/config/locales/client.bs_BA.yml index 073af3c6f6..08e63429fa 100644 --- a/config/locales/client.bs_BA.yml +++ b/config/locales/client.bs_BA.yml @@ -134,7 +134,6 @@ bs_BA: close: 'zatvori' twitter: 'podijeli link na Twitteru' facebook: 'podijeli link na Facebook-u' - google+: 'podijeli link na Google+' email: 'pošalji ovaj link na email' action_codes: public_topic: "postavio ovu temu kao javno %{when}" diff --git a/config/locales/client.ca.yml b/config/locales/client.ca.yml index 383e9d8924..265b37de72 100644 --- a/config/locales/client.ca.yml +++ b/config/locales/client.ca.yml @@ -114,7 +114,6 @@ ca: close: 'tanca' twitter: 'comparteix aquest enllaç a Twitter' facebook: 'comparteix aquest enllaç a Facebook' - google+: 'comparteix aquest enllaç a Google+' email: 'envia aquest enllaç per correu electrònic' action_codes: public_topic: "va fer públic aquest tema %{when}" diff --git a/config/locales/client.cs.yml b/config/locales/client.cs.yml index ef8d318511..58a2dd681d 100644 --- a/config/locales/client.cs.yml +++ b/config/locales/client.cs.yml @@ -154,7 +154,6 @@ cs: close: 'zavřít' twitter: 'sdílet odkaz na Twitteru' facebook: 'sdílet odkaz na Facebooku' - google+: 'sdílet odkaz na Google+' email: 'odeslat odkaz emailem' action_codes: public_topic: "Téma zveřejněno %{when}" diff --git a/config/locales/client.da.yml b/config/locales/client.da.yml index e175842c50..9289eb1993 100644 --- a/config/locales/client.da.yml +++ b/config/locales/client.da.yml @@ -108,7 +108,6 @@ da: close: 'luk' twitter: 'del dette link på Twitter' facebook: 'del dette link på Facebook' - google+: 'del dette link på Google+' email: 'send dette link i en e-mail' action_codes: public_topic: "offentliggjorde dette emne %{when}" diff --git a/config/locales/client.de.yml b/config/locales/client.de.yml index c015825a9a..f3f0046ff1 100644 --- a/config/locales/client.de.yml +++ b/config/locales/client.de.yml @@ -114,7 +114,6 @@ de: close: 'Schließen' twitter: 'diesen Link auf Twitter teilen' facebook: 'diesen Link auf Facebook teilen' - google+: 'diesen Link auf Google+ teilen' email: 'diesen Link per E-Mail senden' action_codes: public_topic: "hat das Thema öffentlich gemacht, %{when}" diff --git a/config/locales/client.el.yml b/config/locales/client.el.yml index 321f093676..a0b783de34 100644 --- a/config/locales/client.el.yml +++ b/config/locales/client.el.yml @@ -114,7 +114,6 @@ el: close: 'κλείσιμο' twitter: 'κοινοποίησε τον σύνδεσμο στο Twitter' facebook: 'κοινοποίησε τον σύνδεσμο στο Facebook' - google+: 'κοινοποίησε τον σύνδεσμο στο Google+' email: 'στείλε αυτόν τον σύνδεσμο με email' action_codes: public_topic: "έκανε το νήμα δημόσιο στις %{when}" diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 979520d084..ba301a68df 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -140,7 +140,6 @@ en: close: 'close' twitter: 'share this link on Twitter' facebook: 'share this link on Facebook' - google+: 'share this link on Google+' email: 'send this link in an email' action_codes: diff --git a/config/locales/client.es.yml b/config/locales/client.es.yml index bfbdd17467..25fff7202d 100644 --- a/config/locales/client.es.yml +++ b/config/locales/client.es.yml @@ -114,7 +114,6 @@ es: close: 'cerrar' twitter: 'comparte este enlace en Twitter' facebook: 'comparte este enlace en Facebook' - google+: 'comparte este enlace en Google+' email: 'comparte este enlace por email' action_codes: public_topic: "hizo este tema público %{when}" diff --git a/config/locales/client.et.yml b/config/locales/client.et.yml index deb744b8ee..219912e2b1 100644 --- a/config/locales/client.et.yml +++ b/config/locales/client.et.yml @@ -114,7 +114,6 @@ et: close: 'Sulge' twitter: 'Jaga seda viidet Twitteris' facebook: 'jaga seda viidet Facebookis' - google+: 'jaga seda viidet Google-plussis' email: 'Jaga viidet meiliga' action_codes: public_topic: "muutsin selle teema avalikuks %{when}" diff --git a/config/locales/client.fa_IR.yml b/config/locales/client.fa_IR.yml index b323fa47c1..727ad346ed 100644 --- a/config/locales/client.fa_IR.yml +++ b/config/locales/client.fa_IR.yml @@ -114,7 +114,6 @@ fa_IR: close: 'بسته' twitter: 'این پیوند را در توییتر به اشتراک بگذارید.' facebook: 'این پیوند را در فیسبوک به اشتراک بگذارید.' - google+: 'این پیوند را در Google+‎ به اشتراک بگذارید.' email: 'این پیوند را با ایمیل بفرستید' action_codes: public_topic: "این موضوع در %{when} عمومی شده" diff --git a/config/locales/client.fi.yml b/config/locales/client.fi.yml index 3dc3919eb5..c582650f1d 100644 --- a/config/locales/client.fi.yml +++ b/config/locales/client.fi.yml @@ -114,7 +114,6 @@ fi: close: 'sulje' twitter: 'jaa tämä linkki Twitterissä' facebook: 'jaa tämä linkki Facebookissa' - google+: 'jaa tämä linkki Google+:ssa' email: 'lähetä tämä linkki sähköpostissa' action_codes: public_topic: "teki ketjusta julkisen %{when}" diff --git a/config/locales/client.fr.yml b/config/locales/client.fr.yml index ddbca91a6d..8982b8cf65 100644 --- a/config/locales/client.fr.yml +++ b/config/locales/client.fr.yml @@ -114,7 +114,6 @@ fr: close: 'fermer' twitter: 'partager ce lien sur Twitter' facebook: 'partager ce lien sur Facebook' - google+: 'partager ce lien sur Google+' email: 'envoyer ce lien dans un courriel' action_codes: public_topic: "a rendu ce sujet public %{when}" diff --git a/config/locales/client.gl.yml b/config/locales/client.gl.yml index 43f11e81fc..8fcdb1cf6e 100644 --- a/config/locales/client.gl.yml +++ b/config/locales/client.gl.yml @@ -108,7 +108,6 @@ gl: close: 'pechar' twitter: 'compartir esta ligazón no Twitter' facebook: 'compartir esta ligazón no Facebook' - google+: 'compartir esta ligazón no Google+' email: 'enviar esta ligazón nun correo electrónico' action_codes: public_topic: "fixo esta publicación pública no %{when}" diff --git a/config/locales/client.he.yml b/config/locales/client.he.yml index 4b0f915e39..bb327f0024 100644 --- a/config/locales/client.he.yml +++ b/config/locales/client.he.yml @@ -144,7 +144,6 @@ he: close: 'סגור' twitter: 'שתפו קישור זה בטוויטר' facebook: 'שתפו קישור זה בפייסבוק' - google+: 'שתפו קישור זה בגוגל+' email: 'שליחת קישור בדוא״ל' action_codes: public_topic: "הפכו נושא זה לפומבי %{when}" diff --git a/config/locales/client.hu.yml b/config/locales/client.hu.yml index 527769264e..083ba0fde0 100644 --- a/config/locales/client.hu.yml +++ b/config/locales/client.hu.yml @@ -114,7 +114,6 @@ hu: close: 'bezár' twitter: 'a hivatkozás megosztása a Twitteren' facebook: 'a hivatkozás megosztása a Facebookon' - google+: 'a hivatkozás megosztása a Google+ -on' email: 'a hivatkozás elküldése e-mailben' action_codes: public_topic: "publikussá tette ezt a témát ekkor: %{when}" diff --git a/config/locales/client.id.yml b/config/locales/client.id.yml index 3dea5240d1..51f9fee2c8 100644 --- a/config/locales/client.id.yml +++ b/config/locales/client.id.yml @@ -94,7 +94,6 @@ id: close: 'tutup' twitter: 'share link ini di Twitter' facebook: 'share link ini di Facebook' - google+: 'share link ini di Google+' email: 'kirim link ini melalui email' action_codes: public_topic: "membuat topik ini umum %{when}" diff --git a/config/locales/client.it.yml b/config/locales/client.it.yml index f9bd160c6a..4c994d8691 100644 --- a/config/locales/client.it.yml +++ b/config/locales/client.it.yml @@ -114,7 +114,6 @@ it: close: 'chiudi' twitter: 'condividi questo collegamento su Twitter' facebook: 'condividi questo collegamento su Facebook' - google+: 'condividi questo collegamento su Google+' email: 'invia questo collegamento via email' action_codes: public_topic: "ha reso questo argomento pubblico %{when}" diff --git a/config/locales/client.ja.yml b/config/locales/client.ja.yml index e2a155afe4..4802853917 100644 --- a/config/locales/client.ja.yml +++ b/config/locales/client.ja.yml @@ -94,7 +94,6 @@ ja: close: '閉じる' twitter: 'Twitter でこのリンクを共有する' facebook: 'Facebook でこのリンクを共有する' - google+: 'Google+ でこのリンクを共有する' email: 'メールでこのリンクを送る' action_codes: public_topic: "トピックを公開しました: %{when} " diff --git a/config/locales/client.ko.yml b/config/locales/client.ko.yml index e2b474fa86..392f4aec3e 100644 --- a/config/locales/client.ko.yml +++ b/config/locales/client.ko.yml @@ -94,7 +94,6 @@ ko: close: '닫기' twitter: '트위터로 공유' facebook: '페이스북으로 공유' - google+: 'Google+로 공유' email: '이메일로 공유' action_codes: public_topic: "이 글을 %{when} 에 공개" diff --git a/config/locales/client.lt.yml b/config/locales/client.lt.yml index c6f36339dc..0c382c5aaf 100644 --- a/config/locales/client.lt.yml +++ b/config/locales/client.lt.yml @@ -154,7 +154,6 @@ lt: close: 'uždaryti' twitter: 'pasidalinkite šią nuorodą per Twitter' facebook: 'pasidalinkite šią nuorodą per Facebook' - google+: 'pasidalinkite šią nuorodą per Google+' email: 'siųskite šią nuorodą į el. paštą' action_codes: public_topic: "nustatė diskusijas viešom %{when}" diff --git a/config/locales/client.lv.yml b/config/locales/client.lv.yml index 857b02b887..eeae19eb46 100644 --- a/config/locales/client.lv.yml +++ b/config/locales/client.lv.yml @@ -126,7 +126,6 @@ lv: close: 'aizvērt' twitter: 'padalīties ar šo saiti Twitter' facebook: 'padalīties ar šo saiti Facebook' - google+: 'padalīties ar šo saiti Google+' email: 'nosūtīt šo saiti e-pastā' action_codes: public_topic: "padarīja tēmu publisku %{when}" diff --git a/config/locales/client.nb_NO.yml b/config/locales/client.nb_NO.yml index 596bb9a4b4..bf41a7b83f 100644 --- a/config/locales/client.nb_NO.yml +++ b/config/locales/client.nb_NO.yml @@ -114,7 +114,6 @@ nb_NO: close: 'lukk' twitter: 'del denne lenken på Twitter' facebook: 'del denne lenken på Facebook' - google+: 'del denne lenken på Google+' email: 'del denne lenken i en e-post' action_codes: public_topic: "gjorde dette emnet offentlig %{when}" diff --git a/config/locales/client.nl.yml b/config/locales/client.nl.yml index 4a15c7ffa2..c0d21a2323 100644 --- a/config/locales/client.nl.yml +++ b/config/locales/client.nl.yml @@ -108,7 +108,6 @@ nl: close: 'sluiten' twitter: 'deze koppeling delen op Twitter' facebook: 'deze koppeling delen op Facebook' - google+: 'deze koppeling delen op Google+' email: 'deze koppeling delen via e-mail' action_codes: public_topic: "heeft dit topic openbaar gemaakt op %{when}" diff --git a/config/locales/client.pl_PL.yml b/config/locales/client.pl_PL.yml index 308bbfa040..9969051a2d 100644 --- a/config/locales/client.pl_PL.yml +++ b/config/locales/client.pl_PL.yml @@ -154,7 +154,6 @@ pl_PL: close: 'zamknij' twitter: 'udostępnij ten odnośnik na Twitterze' facebook: 'udostępnij ten odnośnik na Facebooku' - google+: 'udostępnij ten odnośnik na Google+' email: 'wyślij ten odnośnik przez email' action_codes: public_topic: "Upublicznij ten temat %{when}" diff --git a/config/locales/client.pt.yml b/config/locales/client.pt.yml index 28669077bd..06280c543f 100644 --- a/config/locales/client.pt.yml +++ b/config/locales/client.pt.yml @@ -114,7 +114,6 @@ pt: close: 'fechar' twitter: 'partilhar esta hiperligação no Twitter' facebook: 'partilhar esta hiperligação no Facebook' - google+: 'partilhar esta hiperligação no Google+' email: 'enviar esta hiperligação por e-mail' action_codes: public_topic: "tornei este tópico publico %{when}" diff --git a/config/locales/client.pt_BR.yml b/config/locales/client.pt_BR.yml index df48ccfcda..aa43eefdbd 100644 --- a/config/locales/client.pt_BR.yml +++ b/config/locales/client.pt_BR.yml @@ -114,7 +114,6 @@ pt_BR: close: 'fechar' twitter: 'compartilhe este link no Twitter' facebook: 'compartilhe este link no Facebook' - google+: 'compartilhe este link no Google+' email: 'enviar esse link para um email' action_codes: public_topic: "tornou este tópico público em %{when}" diff --git a/config/locales/client.ro.yml b/config/locales/client.ro.yml index 455690297a..f707a23b20 100644 --- a/config/locales/client.ro.yml +++ b/config/locales/client.ro.yml @@ -134,7 +134,6 @@ ro: close: 'Închide' twitter: 'Distribuie pe Twitter' facebook: 'Distribuie pe Facebook' - google+: 'Distribuie pe Google+' email: 'Drimite pe email' action_codes: public_topic: "a făcut acest subiect public %{when}" diff --git a/config/locales/client.ru.yml b/config/locales/client.ru.yml index e9158a36ac..6b7bfbf61d 100644 --- a/config/locales/client.ru.yml +++ b/config/locales/client.ru.yml @@ -154,7 +154,6 @@ ru: close: 'Закрыть' twitter: 'Поделиться ссылкой через Twitter' facebook: 'Поделиться ссылкой через Facebook' - google+: 'Поделиться ссылкой через Google+' email: 'Поделиться ссылкой по электронной почте' action_codes: public_topic: "Сделал эту тему публичной %{when}" diff --git a/config/locales/client.sk.yml b/config/locales/client.sk.yml index 69deaad8e7..94eb5ed505 100644 --- a/config/locales/client.sk.yml +++ b/config/locales/client.sk.yml @@ -154,7 +154,6 @@ sk: close: 'zatvoriť' twitter: 'zdieľaj odkaz na Twitteri' facebook: 'zdieľaj odkaz na Facebooku' - google+: 'zdieľaj odkaz na Google+' email: 'pošli odkaz emailom' action_codes: public_topic: "téma je verejná%{when}" diff --git a/config/locales/client.sl.yml b/config/locales/client.sl.yml index 057908c6e0..4ebb405d83 100644 --- a/config/locales/client.sl.yml +++ b/config/locales/client.sl.yml @@ -154,7 +154,6 @@ sl: close: 'zapri' twitter: 'deli povezavo na Twitterju' facebook: 'deli povezao na Facebooku' - google+: 'deli povezavo na Google+' email: 'pošlji povezavo preko e-pošte' action_codes: public_topic: "je naredil temo javno %{when}" diff --git a/config/locales/client.sq.yml b/config/locales/client.sq.yml index a98ec436ee..5a917c8fae 100644 --- a/config/locales/client.sq.yml +++ b/config/locales/client.sq.yml @@ -107,7 +107,6 @@ sq: close: 'mbylle' twitter: 'shpërndaje këtë lidhje në Twitter' facebook: 'postojeni këtë lidhje në Facebook' - google+: 'shpërndaje këtë lidhje në Google+' email: 'dërgoje këtë lidhje me email' action_codes: public_topic: "e bëri këtë temë publike %{when}" diff --git a/config/locales/client.sr.yml b/config/locales/client.sr.yml index 2e9bc71b1e..f0bcccac5a 100644 --- a/config/locales/client.sr.yml +++ b/config/locales/client.sr.yml @@ -134,7 +134,6 @@ sr: close: 'zatvori' twitter: 'podeli ovaj link na Twitteru' facebook: 'podeli ovaj link na Facebooku' - google+: 'podeli ovaj link na Google+' email: 'podeli ovaj link preko e-pošte' action_codes: public_topic: "objavi ovu temu javno %{when}" diff --git a/config/locales/client.sv.yml b/config/locales/client.sv.yml index 44f1688030..b9544c259b 100644 --- a/config/locales/client.sv.yml +++ b/config/locales/client.sv.yml @@ -108,7 +108,6 @@ sv: close: 'stäng' twitter: 'dela denna länk på Twitter' facebook: 'dela denna länk på Facebook' - google+: 'dela denna länk på Google+' email: 'skicka denna länk i ett e-postmeddelande' action_codes: public_topic: "gjorde det här ämnet publikt %{when}" diff --git a/config/locales/client.sw.yml b/config/locales/client.sw.yml index fc303413ff..81bbc85554 100644 --- a/config/locales/client.sw.yml +++ b/config/locales/client.sw.yml @@ -114,7 +114,6 @@ sw: close: 'funga' twitter: 'Shiriki hiki kiungo kwenye Twitter' facebook: 'Shiriki hiki kiungo kwenye Facebook' - google+: 'Kishiriki hiki kiungo kwenye Google+, mtandao wa kijamii' email: 'tuma kiungo hiki kwenye barua pepe' action_codes: public_topic: "ameifanya hii mada isiwe ya siri %{when}" diff --git a/config/locales/client.te.yml b/config/locales/client.te.yml index 6af53b23cc..50c974ac9d 100644 --- a/config/locales/client.te.yml +++ b/config/locales/client.te.yml @@ -96,7 +96,6 @@ te: close: 'మూసివేయి' twitter: 'ఈ లింక్ ను ట్విట్టరు లో పంచుకొండి' facebook: 'ఈ లింక్ ను ఫేస్ బుక్ లో పంచుకొండి' - google+: 'ఈ లింక్ ను గూగుల్ ప్లస్ లో పంచుకొండి' email: 'ఈ లింక్ ను ఈమెయిల్ లో పంచుకొండి' topic_admin_menu: "విషయపు అధికార చర్యలు" emails_are_disabled: "బయటకు వెళ్లే అన్ని ఈమెయిల్లూ అధికారి నిశేధించాడు. ఇప్పుడు ఎటువంటి ఈమెయిల్ ప్రకటనలూ పంపవీలవదు." diff --git a/config/locales/client.th.yml b/config/locales/client.th.yml index d1b38d8693..fce6af8364 100644 --- a/config/locales/client.th.yml +++ b/config/locales/client.th.yml @@ -90,7 +90,6 @@ th: close: 'ปิด' twitter: 'แบ่งปันลิงก์นี้บน Twitter' facebook: 'แบ่งปันลิงก์นี้บน Facebook' - google+: 'แบ่งปันลิงก์บน Google+' email: 'ส่งลิงก์นี้ทางอีเมล' action_codes: public_topic: "ทำให้กระทู้นี้เป็นสาธารณะ %{when} " diff --git a/config/locales/client.tr_TR.yml b/config/locales/client.tr_TR.yml index 49e9accadc..1c2ef497cc 100644 --- a/config/locales/client.tr_TR.yml +++ b/config/locales/client.tr_TR.yml @@ -114,7 +114,6 @@ tr_TR: close: 'kapat' twitter: 'bu bağlantıyı Twitter''da paylaş' facebook: 'bu bağlantıyı Facebook''da paylaş' - google+: 'bu bağlantıyı Google+''da paylaş' email: 'bu bağlantıyı e-posta ile gönder' action_codes: public_topic: "bu konuyu %{when} herkese açık yaptı" diff --git a/config/locales/client.uk.yml b/config/locales/client.uk.yml index 5d2f2d7e81..76f530664d 100644 --- a/config/locales/client.uk.yml +++ b/config/locales/client.uk.yml @@ -67,7 +67,6 @@ uk: close: 'сховати' twitter: 'поширити це посилання в Twitter' facebook: 'поширити це посилання в Facebook' - google+: 'поширити це посилання в Google+' email: 'надіслати це посилання електронною поштою' topic_admin_menu: "керування темою" emails_are_disabled: "Надсилання повідомлень електронною поштою було глобально вимкнено адміністратором. Жодне сповіщення електронною поштою не буде надіслано." diff --git a/config/locales/client.ur.yml b/config/locales/client.ur.yml index 9271de72f8..b8e5f2d24d 100644 --- a/config/locales/client.ur.yml +++ b/config/locales/client.ur.yml @@ -114,7 +114,6 @@ ur: close: 'بند کریں' twitter: 'ٹوئٹر پر اس لنک کو شیئرکریں' facebook: 'فیس بک پر اس لنک کو شیئرکریں' - google+: 'گُوگَل+ پر اس لنک کو شیئرکریں' email: 'اس لنک کو ای میل میں بھیجیں' action_codes: public_topic: "اس ٹاپک کو پبلک بنایا گیا %{when}" diff --git a/config/locales/client.vi.yml b/config/locales/client.vi.yml index ab5aae9259..5547964199 100644 --- a/config/locales/client.vi.yml +++ b/config/locales/client.vi.yml @@ -94,7 +94,6 @@ vi: close: 'đóng' twitter: 'chia sẻ lên Twitter' facebook: 'chia sẻ lên Facebook' - google+: 'chia sẻ lên Google+' email: 'Gửi liên kết này qua email' action_codes: public_topic: "hiển thị chủ đề này công khai lúc %{when}" diff --git a/config/locales/client.zh_CN.yml b/config/locales/client.zh_CN.yml index 033cf65169..dfa4a231aa 100644 --- a/config/locales/client.zh_CN.yml +++ b/config/locales/client.zh_CN.yml @@ -94,7 +94,6 @@ zh_CN: close: '关闭' twitter: '分享至 Twitter' facebook: '分享至 Facebook' - google+: '分享至 Google+' email: '在电子邮件里发送该链接' action_codes: public_topic: "于%{when}设置为公共主题" diff --git a/config/locales/client.zh_TW.yml b/config/locales/client.zh_TW.yml index 575d3d3b26..0c7c0178d6 100644 --- a/config/locales/client.zh_TW.yml +++ b/config/locales/client.zh_TW.yml @@ -94,7 +94,6 @@ zh_TW: close: '關閉' twitter: '在 Twitter 分享此連結' facebook: '在 Facebook 分享此連結' - google+: '在 Google+ 分享此連結' email: '以電子郵件分享此連結' action_codes: public_topic: "於 %{when}發佈這個話題" diff --git a/config/site_settings.yml b/config/site_settings.yml index 546ba41581..46f838f1cc 100644 --- a/config/site_settings.yml +++ b/config/site_settings.yml @@ -212,7 +212,6 @@ basic: choices: - twitter - facebook - - google+ - email desktop_category_page_style: client: true diff --git a/test/javascripts/helpers/site-settings.js b/test/javascripts/helpers/site-settings.js index 3065d01a68..c620c54804 100644 --- a/test/javascripts/helpers/site-settings.js +++ b/test/javascripts/helpers/site-settings.js @@ -13,7 +13,7 @@ Discourse.SiteSettingsOriginal = { top_menu: "latest|new|unread|categories|top", post_menu: "like-count|like|share|flag|edit|bookmark|delete|admin|reply", post_menu_hidden_items: "flag|edit|delete|admin", - share_links: "twitter|facebook|google+|email", + share_links: "twitter|facebook|email", category_colors: "BF1E2E|F1592A|F7941D|9EB83B|3AB54A|12A89D|25AAE2|0E76BD|652D90|92278F|ED207B|8C6238|231F20|27AA5B|B3B5B4|E45735", enable_mobile_theme: true, From 4ddd8fad20d3e0bfec06dbaee494266ee4c20a0b Mon Sep 17 00:00:00 2001 From: Guo Xiang Tan Date: Wed, 9 Jan 2019 10:53:38 +0800 Subject: [PATCH 082/157] DEV: Raise error with stats. --- spec/rails_helper.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb index 1367319521..c820d2f4b2 100644 --- a/spec/rails_helper.rb +++ b/spec/rails_helper.rb @@ -135,7 +135,9 @@ RSpec.configure do |config| unfreeze_time ActionMailer::Base.deliveries.clear - raise if ActiveRecord::Base.connection_pool.stat[:busy] > 1 + if ActiveRecord::Base.connection_pool.stat[:busy] > 1 + raise ActiveRecord::Base.connection_pool.stat.inspect + end end config.before :each do |x| From b7c501bf52f07ad55f2bbb96975e9738fefc1231 Mon Sep 17 00:00:00 2001 From: Vinoth Kannan Date: Wed, 9 Jan 2019 10:59:37 +0530 Subject: [PATCH 083/157] FIX: full page search results are unclickable data-ember-action can have empty value in favor of data-ember-action-ID attribute --- app/assets/javascripts/discourse/lib/intercept-click.js.es6 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/discourse/lib/intercept-click.js.es6 b/app/assets/javascripts/discourse/lib/intercept-click.js.es6 index 9d115ff9ab..c4daa17a74 100644 --- a/app/assets/javascripts/discourse/lib/intercept-click.js.es6 +++ b/app/assets/javascripts/discourse/lib/intercept-click.js.es6 @@ -27,7 +27,7 @@ export default function interceptClick(e) { !href || href === "#" || $currentTarget.attr("target") || - $currentTarget.data("ember-action") != null || + $currentTarget.data("ember-action") || $currentTarget.data("auto-route") != null || $currentTarget.data("share-url") != null || $currentTarget.hasClass("widget-link") || From 2684ecaecf2158cffdbb8606a990b1da87baa690 Mon Sep 17 00:00:00 2001 From: Vinoth Kannan Date: Wed, 9 Jan 2019 14:49:28 +0530 Subject: [PATCH 084/157] minor copyedit Topics will be in closed status until the community flags are handled --- config/locales/server.en.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index 7c7eb7674d..76b66617fc 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -2347,8 +2347,8 @@ en: deferred_and_deleted: "Thanks for letting us know. We've removed the post." temporarily_closed_due_to_flags: - one: "This topic is temporarily closed for 1 hour due to a large number of community flags." - other: "This topic is temporarily closed for %{count} hours due to a large number of community flags." + one: "This topic is temporarily closed for at least 1 hour due to a large number of community flags." + other: "This topic is temporarily closed for at least %{count} hours due to a large number of community flags." system_messages: private_topic_title: "Topic #%{id}" From abee39ecd08cfd02e982ae056c0cf471bc790dbc Mon Sep 17 00:00:00 2001 From: Osama Sayegh Date: Wed, 9 Jan 2019 13:50:48 +0300 Subject: [PATCH 085/157] Bump logster to 1.4.0.pre (#6866) 2 new features and a few fixes. More details here: https://github.com/discourse/logster/pull/76 https://github.com/discourse/logster/pull/77 --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index 9ecacd4a27..8effa6a1e3 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -184,7 +184,7 @@ GEM logstash-event (1.2.02) logstash-logger (0.26.1) logstash-event (~> 1.2) - logster (1.3.4) + logster (1.4.0.pre) loofah (2.2.3) crass (~> 1.0.2) nokogiri (>= 1.5.9) From 1f12a377fabace8aafc780dc9b6818f1e017b64f Mon Sep 17 00:00:00 2001 From: Vinoth Kannan Date: Wed, 9 Jan 2019 19:32:30 +0530 Subject: [PATCH 086/157] minor refactoring of card-contents-base mixin --- .../discourse/mixins/card-contents-base.js.es6 | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/assets/javascripts/discourse/mixins/card-contents-base.js.es6 b/app/assets/javascripts/discourse/mixins/card-contents-base.js.es6 index 1e9160d3f7..931d2356fe 100644 --- a/app/assets/javascripts/discourse/mixins/card-contents-base.js.es6 +++ b/app/assets/javascripts/discourse/mixins/card-contents-base.js.es6 @@ -124,8 +124,7 @@ export default Ember.Mixin.create({ }); this.appEvents.on(`topic-header:trigger-${id}`, (username, $target) => { - this.set("isFixed", true); - this.set("isDocked", true); + this.setProperties({ isFixed: true, isDocked: true }); return this._show(username, $target); }); }, @@ -188,8 +187,9 @@ export default Ember.Mixin.create({ } } - if (isDocked && position.top < 44) { - position.top = 44; + const avatarOverflowSize = 44; + if (isDocked && position.top < avatarOverflowSize) { + position.top = avatarOverflowSize; } this.$().css(position); From 8b3ddcf646b1d82e4c96371dffb2d6779a8e8ce5 Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Wed, 9 Jan 2019 10:59:35 -0500 Subject: [PATCH 087/157] FIX: Add topic status to flagged topics list Previously at a glance it was impossible to tell if a topic was closed before digging deeper. --- .../admin/templates/flags-topics-index.hbs | 5 ++++- app/assets/stylesheets/common/admin/flagging.scss | 11 +++++++++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/app/assets/javascripts/admin/templates/flags-topics-index.hbs b/app/assets/javascripts/admin/templates/flags-topics-index.hbs index 9a37bc78fc..73cd022fc1 100644 --- a/app/assets/javascripts/admin/templates/flags-topics-index.hbs +++ b/app/assets/javascripts/admin/templates/flags-topics-index.hbs @@ -16,7 +16,10 @@ {{plugin-outlet name="flagged-topic-row" noTags=true args=(hash topic=ft.topic)}} - {{replace-emoji ft.topic.fancy_title}} +
+ {{topic-status topic=ft.topic}} + {{replace-emoji ft.topic.fancy_title}} +
{{#each ft.flag_counts as |fc|}} diff --git a/app/assets/stylesheets/common/admin/flagging.scss b/app/assets/stylesheets/common/admin/flagging.scss index c42b85f44b..f1010ddc35 100644 --- a/app/assets/stylesheets/common/admin/flagging.scss +++ b/app/assets/stylesheets/common/admin/flagging.scss @@ -191,7 +191,7 @@ } } -.flag-counts { +div.flag-counts { display: inline-block; margin-right: 0.5em; @@ -204,9 +204,16 @@ .flagged-topic { td.topic-title { width: 400px; + + .combined-title { + .d-icon { + margin-right: 0.5em; + } + display: flex; + } + a { width: 400px; - display: inline-block; text-overflow: ellipsis; overflow: hidden; white-space: nowrap; From 286cc72c8bd1690672db5200156b6cd88e1f077d Mon Sep 17 00:00:00 2001 From: David Taylor Date: Wed, 9 Jan 2019 17:15:15 +0000 Subject: [PATCH 088/157] FIX: Gyfcat onebox should have fixed aspect ratio videos (Fixed upstream in the onebox gem) --- Gemfile | 2 +- Gemfile.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Gemfile b/Gemfile index 7184bbc61f..fdec382587 100644 --- a/Gemfile +++ b/Gemfile @@ -36,7 +36,7 @@ gem 'redis-namespace' gem 'active_model_serializers', '~> 0.8.3' -gem 'onebox', '1.8.74' +gem 'onebox', '1.8.75' gem 'http_accept_language', '~>2.0.5', require: false diff --git a/Gemfile.lock b/Gemfile.lock index 8effa6a1e3..b2f12dbf06 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -258,7 +258,7 @@ GEM omniauth-twitter (1.4.0) omniauth-oauth (~> 1.1) rack - onebox (1.8.74) + onebox (1.8.75) htmlentities (~> 4.3) moneta (~> 1.0) multi_json (~> 1.11) @@ -512,7 +512,7 @@ DEPENDENCIES omniauth-oauth2 omniauth-openid omniauth-twitter - onebox (= 1.8.74) + onebox (= 1.8.75) openid-redis-store pg pry-nav From 2d3e50ae7ca29994d11412a7a36626e772bd74f5 Mon Sep 17 00:00:00 2001 From: David Taylor Date: Wed, 9 Jan 2019 17:36:51 +0000 Subject: [PATCH 089/157] FIX: Match default `` padding to highlightjs padding Without this, the height of posts changes once highlightjs is lazy-loaded, potentially causing scrolling issues --- app/assets/stylesheets/common/base/topic-post.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/stylesheets/common/base/topic-post.scss b/app/assets/stylesheets/common/base/topic-post.scss index bb27732d2d..77298b6341 100644 --- a/app/assets/stylesheets/common/base/topic-post.scss +++ b/app/assets/stylesheets/common/base/topic-post.scss @@ -548,7 +548,7 @@ pre { code { word-wrap: normal; display: block; - padding: 5px 10px; + padding: 0.5em; color: $primary; background: blend-primary-secondary(5%); max-height: 500px; From 439eff47099e457a977afe526e98a97ecec62295 Mon Sep 17 00:00:00 2001 From: Jeff Wong Date: Wed, 9 Jan 2019 09:44:30 -0800 Subject: [PATCH 090/157] revert null checking around interceptClick --- app/assets/javascripts/discourse/lib/intercept-click.js.es6 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/discourse/lib/intercept-click.js.es6 b/app/assets/javascripts/discourse/lib/intercept-click.js.es6 index c4daa17a74..1fe999912f 100644 --- a/app/assets/javascripts/discourse/lib/intercept-click.js.es6 +++ b/app/assets/javascripts/discourse/lib/intercept-click.js.es6 @@ -28,8 +28,8 @@ export default function interceptClick(e) { href === "#" || $currentTarget.attr("target") || $currentTarget.data("ember-action") || - $currentTarget.data("auto-route") != null || - $currentTarget.data("share-url") != null || + $currentTarget.data("auto-route") || + $currentTarget.data("share-url") || $currentTarget.hasClass("widget-link") || $currentTarget.hasClass("raw-link") || $currentTarget.hasClass("mention") || From 312e282b6ae3916ac77f0f54c5407dc9e23d1cec Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Wed, 9 Jan 2019 12:54:21 -0500 Subject: [PATCH 091/157] FIX: Apply classes when lazily loading images If an image had extra classes (for example oneboxes), then while loading the copy of the image would lose those classes and look differently until the image had loaded fully. This fix copies the classes while loading. --- app/assets/javascripts/discourse/lib/lazy-load-images.js.es6 | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/discourse/lib/lazy-load-images.js.es6 b/app/assets/javascripts/discourse/lib/lazy-load-images.js.es6 index b291fcdc94..8e0a2563e0 100644 --- a/app/assets/javascripts/discourse/lib/lazy-load-images.js.es6 +++ b/app/assets/javascripts/discourse/lib/lazy-load-images.js.es6 @@ -19,7 +19,8 @@ function hide(image) { src: image.src, srcset: image.srcset, width: image.width, - height: image.height + height: image.height, + className: image.className }); image.removeAttribute("srcset"); @@ -51,6 +52,7 @@ function show(image) { copyImg.style.left = `${image.offsetLeft}px`; copyImg.style.width = imageData.width; copyImg.style.height = imageData.height; + copyImg.className = imageData.className; image.parentNode.appendChild(copyImg); } else { From 1f0708981f6358ef9efc7010ec3e7a888e7ee2d9 Mon Sep 17 00:00:00 2001 From: David Taylor Date: Wed, 9 Jan 2019 18:00:28 +0000 Subject: [PATCH 092/157] FIX: Bump onebox version for gfycat aspect ratio fix --- Gemfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile b/Gemfile index fdec382587..d715161cce 100644 --- a/Gemfile +++ b/Gemfile @@ -36,7 +36,7 @@ gem 'redis-namespace' gem 'active_model_serializers', '~> 0.8.3' -gem 'onebox', '1.8.75' +gem 'onebox', '1.8.76' gem 'http_accept_language', '~>2.0.5', require: false From af227cada55a250c2cf6b3f94042bd0c92d0e382 Mon Sep 17 00:00:00 2001 From: David Taylor Date: Wed, 9 Jan 2019 18:08:46 +0000 Subject: [PATCH 093/157] FIX: Bump onebox version for gfycat aspect ratio fix Including the `Gemfile.lock` changes this time --- Gemfile.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index b2f12dbf06..35e73230f0 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -258,7 +258,7 @@ GEM omniauth-twitter (1.4.0) omniauth-oauth (~> 1.1) rack - onebox (1.8.75) + onebox (1.8.76) htmlentities (~> 4.3) moneta (~> 1.0) multi_json (~> 1.11) @@ -512,7 +512,7 @@ DEPENDENCIES omniauth-oauth2 omniauth-openid omniauth-twitter - onebox (= 1.8.75) + onebox (= 1.8.76) openid-redis-store pg pry-nav From dec8e5879a56a94424eccf642f12f88cf33f3bc9 Mon Sep 17 00:00:00 2001 From: Kyle Zhao Date: Wed, 9 Jan 2019 15:04:50 -0500 Subject: [PATCH 094/157] FEATURE: set CSP base-uri and object-src to none (#6863) --- lib/content_security_policy/default.rb | 2 ++ spec/lib/content_security_policy_spec.rb | 14 ++++++++++++++ 2 files changed, 16 insertions(+) diff --git a/lib/content_security_policy/default.rb b/lib/content_security_policy/default.rb index d92285af43..1e056f78bb 100644 --- a/lib/content_security_policy/default.rb +++ b/lib/content_security_policy/default.rb @@ -7,6 +7,8 @@ class ContentSecurityPolicy def initialize @directives = {}.tap do |directives| + directives[:base_uri] = [:none] + directives[:object_src] = [:none] directives[:script_src] = script_src directives[:worker_src] = worker_src directives[:report_uri] = report_uri if SiteSetting.content_security_policy_collect_reports diff --git a/spec/lib/content_security_policy_spec.rb b/spec/lib/content_security_policy_spec.rb index 79f190e588..b220579b67 100644 --- a/spec/lib/content_security_policy_spec.rb +++ b/spec/lib/content_security_policy_spec.rb @@ -16,6 +16,20 @@ describe ContentSecurityPolicy do end end + describe 'base-uri' do + it 'is set to none' do + base_uri = parse(policy)['base-uri'] + expect(base_uri).to eq(["'none'"]) + end + end + + describe 'object-src' do + it 'is set to none' do + object_srcs = parse(policy)['object-src'] + expect(object_srcs).to eq(["'none'"]) + end + end + describe 'worker-src' do it 'always has self and blob' do worker_srcs = parse(policy)['worker-src'] From 9ba8bfb1aaa46e97874c2d262d561a27750ae1ee Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Wed, 9 Jan 2019 15:13:02 -0500 Subject: [PATCH 095/157] FIX: Multisite DB was leaving old data in test mode This commit introduces a new helper to enable transactional fixtures when testing multisite. This would show up as tests that passed the first time then failed the second time due to stale data being leftover. --- .../shared_examples_for_backup_store.rb | 16 +++++++-------- spec/multisite/distributed_cache_spec.rb | 4 +--- spec/multisite/s3_store_spec.rb | 20 ++++++++++--------- spec/multisite/site_settings_spec.rb | 14 ++++--------- spec/rails_helper.rb | 16 +++++++++++++++ 5 files changed, 40 insertions(+), 30 deletions(-) diff --git a/spec/lib/backup_restore/shared_examples_for_backup_store.rb b/spec/lib/backup_restore/shared_examples_for_backup_store.rb index 921139a2c8..2c2770c73c 100644 --- a/spec/lib/backup_restore/shared_examples_for_backup_store.rb +++ b/spec/lib/backup_restore/shared_examples_for_backup_store.rb @@ -57,7 +57,7 @@ shared_examples "backup store" do end it "works with multisite", type: :multisite do - RailsMultisite::ConnectionManagement.with_connection("second") do + test_multisite_connection("second") do expect(store.files).to eq([backup5, backup4]) end end @@ -74,7 +74,7 @@ shared_examples "backup store" do end it "works with multisite", type: :multisite do - RailsMultisite::ConnectionManagement.with_connection("second") do + test_multisite_connection("second") do expect(store.latest_file).to eq(backup5) end end @@ -110,7 +110,7 @@ shared_examples "backup store" do it "works with multisite", type: :multisite do SiteSetting.maximum_backups = 1 - RailsMultisite::ConnectionManagement.with_connection("second") do + test_multisite_connection("second") do store.delete_old expect(store.files).to eq([backup5]) end @@ -132,7 +132,7 @@ shared_examples "backup store" do end it "works with multisite", type: :multisite do - RailsMultisite::ConnectionManagement.with_connection("second") do + test_multisite_connection("second") do file = store.file(backup4.filename, include_download_source: true) expect(file.source).to match(source_regex("second", backup4.filename, multisite: true)) end @@ -153,7 +153,7 @@ shared_examples "backup store" do end it "works with multisite", type: :multisite do - RailsMultisite::ConnectionManagement.with_connection("second") do + test_multisite_connection("second") do expect(store.files).to include(backup5) store.delete_file(backup5.filename) expect(store.files).to_not include(backup5) @@ -182,7 +182,7 @@ shared_examples "backup store" do end it "works with multisite", type: :multisite do - RailsMultisite::ConnectionManagement.with_connection("second") do + test_multisite_connection("second") do expect(store.files).to include(backup5) store.delete_file(backup5.filename) expect(store.files).to_not include(backup5) @@ -239,7 +239,7 @@ shared_examples "remote backup store" do end it "works with multisite", type: :multisite do - RailsMultisite::ConnectionManagement.with_connection("second") do + test_multisite_connection("second") do upload_file end end @@ -266,7 +266,7 @@ shared_examples "remote backup store" do end it "works with multisite", type: :multisite do - RailsMultisite::ConnectionManagement.with_connection("second") do + test_multisite_connection("second") do filename = "foo.tar.gz" url = store.generate_upload_url(filename) diff --git a/spec/multisite/distributed_cache_spec.rb b/spec/multisite/distributed_cache_spec.rb index cdd25dbfaf..7306fc20ec 100644 --- a/spec/multisite/distributed_cache_spec.rb +++ b/spec/multisite/distributed_cache_spec.rb @@ -1,8 +1,6 @@ require 'rails_helper' RSpec.describe 'Multisite SiteSettings', type: :multisite do - let(:conn) { RailsMultisite::ConnectionManagement } - def cache(name, namespace: true) DistributedCache.new(name, namespace: namespace) end @@ -15,7 +13,7 @@ RSpec.describe 'Multisite SiteSettings', type: :multisite do expect(cache1.hash).to eq('default' => true) - conn.with_connection('second') do + test_multisite_connection('second') do message = MessageBus.track_publish(DistributedCache::Manager::CHANNEL_NAME) do cache1['second'] = true end.first diff --git a/spec/multisite/s3_store_spec.rb b/spec/multisite/s3_store_spec.rb index 4ac8527eb6..7939f32c9f 100644 --- a/spec/multisite/s3_store_spec.rb +++ b/spec/multisite/s3_store_spec.rb @@ -2,9 +2,9 @@ require 'rails_helper' require 'file_store/s3_store' RSpec.describe 'Multisite s3 uploads', type: :multisite do - let(:conn) { RailsMultisite::ConnectionManagement } let(:uploaded_file) { file_from_fixtures("smallest.png") } - let(:upload) { Fabricate(:upload, sha1: Digest::SHA1.hexdigest(File.read(uploaded_file))) } + let(:upload_sha1) { Digest::SHA1.hexdigest(File.read(uploaded_file)) } + let(:upload) { Fabricate(:upload, sha1: upload_sha1) } context 'uploading to s3' do before(:each) do @@ -20,15 +20,17 @@ RSpec.describe 'Multisite s3 uploads', type: :multisite do let(:store) { FileStore::S3Store.new(s3_helper) } it "returns the correct url for default and second multisite db" do - conn.with_connection('default') do - expect(store.store_upload(uploaded_file, upload)).to eq( + test_multisite_connection('default') do + test_upload = Fabricate(:upload, sha1: upload_sha1) + expect(store.store_upload(uploaded_file, test_upload)).to eq( "//#{SiteSetting.s3_upload_bucket}.s3.dualstack.us-east-1.amazonaws.com/uploads/default/original/1X/c530c06cf89c410c0355d7852644a73fc3ec8c04.png" ) expect(upload.etag).to eq("ETag") end - conn.with_connection('second') do - expect(store.store_upload(uploaded_file, upload)).to eq( + test_multisite_connection('second') do + test_upload = Fabricate(:upload, sha1: upload_sha1) + expect(store.store_upload(uploaded_file, test_upload)).to eq( "//#{SiteSetting.s3_upload_bucket}.s3.dualstack.us-east-1.amazonaws.com/uploads/second/original/1X/c530c06cf89c410c0355d7852644a73fc3ec8c04.png" ) expect(upload.etag).to eq("ETag") @@ -54,7 +56,7 @@ RSpec.describe 'Multisite s3 uploads', type: :multisite do let(:s3_helper) { store.s3_helper } it "removes the file from s3 on multisite" do - conn.with_connection('default') do + test_multisite_connection('default') do store.expects(:get_depth_for).with(upload.id).returns(0) s3_helper.expects(:s3_bucket).returns(s3_bucket).at_least_once upload.update_attributes!(url: "//s3-upload-bucket.s3.dualstack.us-west-1.amazonaws.com/uploads/default/original/1X/#{upload.sha1}.png") @@ -70,7 +72,7 @@ RSpec.describe 'Multisite s3 uploads', type: :multisite do end it "removes the file from s3 on another multisite db" do - conn.with_connection('second') do + test_multisite_connection('second') do store.expects(:get_depth_for).with(upload.id).returns(0) s3_helper.expects(:s3_bucket).returns(s3_bucket).at_least_once upload.update_attributes!(url: "//s3-upload-bucket.s3.dualstack.us-west-1.amazonaws.com/uploads/second/original/1X/#{upload.sha1}.png") @@ -91,7 +93,7 @@ RSpec.describe 'Multisite s3 uploads', type: :multisite do end it "removes the file from s3 on multisite" do - conn.with_connection('default') do + test_multisite_connection('default') do store.expects(:get_depth_for).with(upload.id).returns(0) s3_helper.expects(:s3_bucket).returns(s3_bucket).at_least_once upload.update_attributes!(url: "//s3-upload-bucket.s3.dualstack.us-west-1.amazonaws.com/discourse-uploads/uploads/default/original/1X/#{upload.sha1}.png") diff --git a/spec/multisite/site_settings_spec.rb b/spec/multisite/site_settings_spec.rb index 5a0d7b4719..a01182e3b5 100644 --- a/spec/multisite/site_settings_spec.rb +++ b/spec/multisite/site_settings_spec.rb @@ -1,34 +1,28 @@ require 'rails_helper' RSpec.describe 'Multisite SiteSettings', type: :multisite do - let(:conn) { RailsMultisite::ConnectionManagement } - before do @original_provider = SiteSetting.provider SiteSetting.provider = SiteSettings::DbProvider.new(SiteSetting) end after do - ['default', 'second'].each do |db| - conn.with_connection(db) { SiteSetting.where(name: 'default_locale').destroy_all } - end - SiteSetting.provider = @original_provider end describe '#default_locale' do it 'should return the right locale' do - conn.with_connection('default') do + test_multisite_connection('default') do expect(SiteSetting.default_locale).to eq('en') end - conn.with_connection('second') do + test_multisite_connection('second') do SiteSetting.default_locale = 'zh_TW' expect(SiteSetting.default_locale).to eq('zh_TW') end - conn.with_connection('default') do + test_multisite_connection('default') do expect(SiteSetting.default_locale).to eq('en') SiteSetting.default_locale = 'ja' @@ -36,7 +30,7 @@ RSpec.describe 'Multisite SiteSettings', type: :multisite do expect(SiteSetting.default_locale).to eq('ja') end - conn.with_connection('second') do + test_multisite_connection('second') do expect(SiteSetting.default_locale).to eq('zh_TW') end end diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb index c820d2f4b2..a0fad312fd 100644 --- a/spec/rails_helper.rb +++ b/spec/rails_helper.rb @@ -202,6 +202,22 @@ RSpec.configure do |config| end end + # Normally we `use_transactional_fixtures` to clear out a database after a test + # runs. However, this does not apply to tests done for multisite. The second time + # a test runs you can end up with stale data that breaks things. This method will + # force a rollback after using a multisite connection. + def test_multisite_connection(name) + RailsMultisite::ConnectionManagement.with_connection(name) do + ActiveRecord::Base.transaction do + begin + yield + ensure + throw raise ActiveRecord::Rollback + end + end + end + end + end class TrackTimeStub From e11c6ffa89ee4b9416bc18a6eb41109109549e6b Mon Sep 17 00:00:00 2001 From: Penar Musaraj Date: Wed, 9 Jan 2019 15:33:42 -0500 Subject: [PATCH 096/157] FEATURE: allow extending CSP base-uri and object-src Plus, ensure :none is stripped, it cannot be combined with other sources --- lib/content_security_policy/builder.rb | 6 ++++-- spec/fixtures/plugins/csp_extension/plugin.rb | 3 ++- spec/lib/content_security_policy_spec.rb | 2 ++ 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/lib/content_security_policy/builder.rb b/lib/content_security_policy/builder.rb index c108988990..f7cf1b690f 100644 --- a/lib/content_security_policy/builder.rb +++ b/lib/content_security_policy/builder.rb @@ -4,13 +4,14 @@ require_dependency 'content_security_policy/default' class ContentSecurityPolicy class Builder EXTENDABLE_DIRECTIVES = %i[ + base_uri + object_src script_src worker_src ].freeze # Make extending these directives no-op, until core includes them in default CSP TO_BE_EXTENDABLE = %i[ - base_uri connect_src default_src font_src @@ -20,7 +21,6 @@ class ContentSecurityPolicy img_src manifest_src media_src - object_src prefetch_src style_src ].freeze @@ -65,6 +65,8 @@ class ContentSecurityPolicy else @directives[directive] << sources end + + @directives[directive].delete(:none) if @directives[directive].count > 1 end def extendable?(directive) diff --git a/spec/fixtures/plugins/csp_extension/plugin.rb b/spec/fixtures/plugins/csp_extension/plugin.rb index f66915a06b..c0b2332ca3 100644 --- a/spec/fixtures/plugins/csp_extension/plugin.rb +++ b/spec/fixtures/plugins/csp_extension/plugin.rb @@ -4,5 +4,6 @@ # authors: xrav3nz extend_content_security_policy( - script_src: ['https://from-plugin.com'] + script_src: ['https://from-plugin.com'], + object_src: ['https://test-stripping.com'] ) diff --git a/spec/lib/content_security_policy_spec.rb b/spec/lib/content_security_policy_spec.rb index b220579b67..9795698864 100644 --- a/spec/lib/content_security_policy_spec.rb +++ b/spec/lib/content_security_policy_spec.rb @@ -111,6 +111,8 @@ describe ContentSecurityPolicy do plugin.enabled = true expect(parse(policy)['script-src']).to include('https://from-plugin.com') + expect(parse(policy)['object-src']).to include('https://test-stripping.com') + expect(parse(policy)['object-src']).to_not include("'none'") plugin.enabled = false expect(parse(policy)['script-src']).to_not include('https://from-plugin.com') From 6f867660bcff58080fb770c5706f3ca75bde15f1 Mon Sep 17 00:00:00 2001 From: Gerhard Schlager Date: Wed, 9 Jan 2019 22:07:10 +0100 Subject: [PATCH 097/157] FIX: Push notifications didn't work anymore --- Gemfile.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 35e73230f0..f51e6459e7 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -435,9 +435,9 @@ GEM addressable (>= 2.3.6) crack (>= 0.3.2) hashdiff - webpush (0.3.2) + webpush (0.3.6) hkdf (~> 0.2) - jwt + jwt (~> 2.0) PLATFORMS ruby @@ -557,4 +557,4 @@ DEPENDENCIES webpush BUNDLED WITH - 1.17.2 + 1.17.3 From c85b9c6ed3a847ce0df7d58340d6680536d92b90 Mon Sep 17 00:00:00 2001 From: Michael Brown Date: Wed, 9 Jan 2019 18:51:58 -0500 Subject: [PATCH 098/157] FIX: searching email logs by reply key (#6868) * you can't use LIKE or ILIKE on a UUID --- app/controllers/admin/email_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/admin/email_controller.rb b/app/controllers/admin/email_controller.rb index 04b015b3c0..476f0aff8d 100644 --- a/app/controllers/admin/email_controller.rb +++ b/app/controllers/admin/email_controller.rb @@ -32,7 +32,7 @@ class Admin::EmailController < Admin::AdminController if params[:reply_key].present? email_logs = email_logs.where( - "post_reply_keys.reply_key ILIKE ?", "%#{params[:reply_key]}%" + "CAST (post_reply_keys.reply_key AS VARCHAR) ILIKE ?", "%#{params[:reply_key]}%" ) end From 35b59cfa78c25eb53f76c21c47576ce30734fc07 Mon Sep 17 00:00:00 2001 From: Sam Date: Thu, 10 Jan 2019 12:02:05 +1100 Subject: [PATCH 099/157] SECURITY: escape title HTML for inline onebox --- lib/cooked_post_processor.rb | 2 +- spec/components/cooked_post_processor_spec.rb | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/cooked_post_processor.rb b/lib/cooked_post_processor.rb index 1e8b0a0f31..9ab9ca6b15 100644 --- a/lib/cooked_post_processor.rb +++ b/lib/cooked_post_processor.rb @@ -655,7 +655,7 @@ class CookedPostProcessor ) if title = inline_onebox&.dig(:title) - element.children = title + element.children = CGI.escapeHTML(title) element.add_class(INLINE_ONEBOX_CSS_CLASS) end diff --git a/spec/components/cooked_post_processor_spec.rb b/spec/components/cooked_post_processor_spec.rb index a8bedc1a73..9e2628a4c5 100644 --- a/spec/components/cooked_post_processor_spec.rb +++ b/spec/components/cooked_post_processor_spec.rb @@ -185,7 +185,8 @@ describe CookedPostProcessor do ] end - let(:title) { 'some title' } + let(:title) { 'some title' } + let(:escaped_title) { CGI.escapeHTML(title) } let(:post) do Fabricate(:post, raw: <<~RAW) @@ -203,7 +204,7 @@ describe CookedPostProcessor do urls.each do |url| stub_request(:get, url).to_return( status: 200, - body: "#{title}" + body: "#{escaped_title}" ) end end From e9b2018bc8bc1254075bec597ce30a84f38b456e Mon Sep 17 00:00:00 2001 From: Guo Xiang Tan Date: Thu, 10 Jan 2019 09:21:28 +0800 Subject: [PATCH 100/157] FIX: Partial reply key search in email sent logs. Follow up to c85b9c6ed3a847ce0df7d58340d6680536d92b90 --- app/controllers/admin/email_controller.rb | 2 +- app/models/email_log.rb | 4 --- app/models/post_reply_key.rb | 6 +---- app/serializers/email_log_serializer.rb | 2 +- spec/models/email_log_spec.rb | 3 +-- spec/models/post_reply_key_spec.rb | 5 ++-- spec/requests/admin/email_controller_spec.rb | 26 ++++++++++++++++++++ 7 files changed, 32 insertions(+), 16 deletions(-) diff --git a/app/controllers/admin/email_controller.rb b/app/controllers/admin/email_controller.rb index 476f0aff8d..b1f28d4693 100644 --- a/app/controllers/admin/email_controller.rb +++ b/app/controllers/admin/email_controller.rb @@ -32,7 +32,7 @@ class Admin::EmailController < Admin::AdminController if params[:reply_key].present? email_logs = email_logs.where( - "CAST (post_reply_keys.reply_key AS VARCHAR) ILIKE ?", "%#{params[:reply_key]}%" + "post_reply_keys.reply_key::TEXT ILIKE ?", "%#{params[:reply_key]}%" ) end diff --git a/app/models/email_log.rb b/app/models/email_log.rb index 769436ea01..0aeae600ff 100644 --- a/app/models/email_log.rb +++ b/app/models/email_log.rb @@ -73,10 +73,6 @@ class EmailLog < ActiveRecord::Base .first end - def bounce_key - super&.delete('-') - end - end # == Schema Information diff --git a/app/models/post_reply_key.rb b/app/models/post_reply_key.rb index 3f1479f6a0..2c85058556 100644 --- a/app/models/post_reply_key.rb +++ b/app/models/post_reply_key.rb @@ -8,12 +8,8 @@ class PostReplyKey < ActiveRecord::Base validates :user_id, presence: true validates :reply_key, presence: true - def reply_key - super&.delete('-') - end - def self.generate_reply_key - SecureRandom.hex(16) + SecureRandom.uuid end end diff --git a/app/serializers/email_log_serializer.rb b/app/serializers/email_log_serializer.rb index 75c7010298..e4d120a9f2 100644 --- a/app/serializers/email_log_serializer.rb +++ b/app/serializers/email_log_serializer.rb @@ -12,6 +12,6 @@ class EmailLogSerializer < ApplicationSerializer end def reply_key - @options[:reply_keys][[object.post_id, object.user_id]].delete("-") + @options[:reply_keys][[object.post_id, object.user_id]] end end diff --git a/spec/models/email_log_spec.rb b/spec/models/email_log_spec.rb index f0f60dabb0..6124d32071 100644 --- a/spec/models/email_log_spec.rb +++ b/spec/models/email_log_spec.rb @@ -103,8 +103,7 @@ describe EmailLog do .pluck("bounce_key::text") .first - expect(raw_key).to_not eq(hex) - expect(raw_key.delete('-')).to eq(hex) + expect(raw_key).to eq(hex) expect(EmailLog.find(email_log.id).bounce_key).to eq(hex) end end diff --git a/spec/models/post_reply_key_spec.rb b/spec/models/post_reply_key_spec.rb index 631e6cd6a2..45f6d5ee14 100644 --- a/spec/models/post_reply_key_spec.rb +++ b/spec/models/post_reply_key_spec.rb @@ -3,7 +3,7 @@ require 'rails_helper' RSpec.describe PostReplyKey do describe "#reply_key" do it "should format the reply_key correctly" do - hex = SecureRandom.hex + hex = SecureRandom.uuid post_reply_key = Fabricate(:post_reply_key, reply_key: hex ) @@ -12,8 +12,7 @@ RSpec.describe PostReplyKey do .pluck("reply_key::text") .first - expect(raw_key).to_not eq(hex) - expect(raw_key.delete('-')).to eq(hex) + expect(raw_key).to eq(hex) expect(PostReplyKey.find(post_reply_key.id).reply_key).to eq(hex) end end diff --git a/spec/requests/admin/email_controller_spec.rb b/spec/requests/admin/email_controller_spec.rb index 34b2faff5b..8bee246ae1 100644 --- a/spec/requests/admin/email_controller_spec.rb +++ b/spec/requests/admin/email_controller_spec.rb @@ -58,6 +58,32 @@ describe Admin::EmailController do expect(log["id"]).to eq(email_log.id) expect(log["reply_key"]).to eq(post_reply_key.reply_key) end + + it 'should be able to filter by reply key' do + email_log_2 = Fabricate(:email_log, post: post) + + post_reply_key_2 = Fabricate(:post_reply_key, + post: post, + user: email_log_2.user, + reply_key: "2d447423-c625-4fb9-8717-ff04ac60eee8" + ) + + [ + "17-ff04", + "2d447423-c625-4fb9-8717-ff04ac60eee8" + ].each do |reply_key| + get "/admin/email/sent.json", params: { + reply_key: reply_key + } + + expect(response.status).to eq(200) + + logs = JSON.parse(response.body) + + expect(logs.size).to eq(1) + expect(logs.first["reply_key"]).to eq(post_reply_key_2.reply_key) + end + end end describe '#skipped' do From a52baf4b286d9088512456c3720d3651c70fb070 Mon Sep 17 00:00:00 2001 From: Saurabh Patel Date: Thu, 10 Jan 2019 07:03:13 +0530 Subject: [PATCH 101/157] FEAT: use category logo image as meta image (#6865) --- app/views/list/list.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/list/list.erb b/app/views/list/list.erb index 99abddc494..143f9ef4ca 100644 --- a/app/views/list/list.erb +++ b/app/views/list/list.erb @@ -90,7 +90,7 @@ <% if @category %> <% content_for :head do %> <%= auto_discovery_link_tag(:rss, { action: :category_feed }, title: t('rss_topics_in_category', category: @category.name)) %> - <%= raw crawlable_meta_data(title: @category.name, description: @category.description) %> + <%= raw crawlable_meta_data(title: @category.name, description: @category.description, image: @category.uploaded_logo&.url.presence) %> <% end %> <% elsif @tag_id %> <% content_for :head do %> From b63b3997999bfbee9b8eac0dd1ba87b24325f840 Mon Sep 17 00:00:00 2001 From: Saurabh Patel Date: Thu, 10 Jan 2019 07:07:21 +0530 Subject: [PATCH 102/157] DEV: remove uploaded_meta_id column from category (#6725) * DEV: remove uploaded_meta_id column from category * remove uploaded_meta part --- app/controllers/categories_controller.rb | 1 - app/jobs/scheduled/clean_up_uploads.rb | 2 +- app/models/category.rb | 2 -- app/models/category_list.rb | 1 - app/models/site.rb | 2 +- app/serializers/basic_category_serializer.rb | 1 - ...190103065652_remove_uploaded_meta_id_from_category.rb | 9 +++++++++ spec/requests/categories_controller_spec.rb | 4 +--- 8 files changed, 12 insertions(+), 10 deletions(-) create mode 100644 db/post_migrate/20190103065652_remove_uploaded_meta_id_from_category.rb diff --git a/app/controllers/categories_controller.rb b/app/controllers/categories_controller.rb index c1bb2dda9e..96d358b0ab 100644 --- a/app/controllers/categories_controller.rb +++ b/app/controllers/categories_controller.rb @@ -280,7 +280,6 @@ class CategoriesController < ApplicationController :auto_close_based_on_last_post, :uploaded_logo_id, :uploaded_background_id, - :uploaded_meta_id, :slug, :allow_badges, :topic_template, diff --git a/app/jobs/scheduled/clean_up_uploads.rb b/app/jobs/scheduled/clean_up_uploads.rb index 13dc588b35..3c4f37cc60 100644 --- a/app/jobs/scheduled/clean_up_uploads.rb +++ b/app/jobs/scheduled/clean_up_uploads.rb @@ -55,7 +55,7 @@ module Jobs .joins("LEFT JOIN users u ON u.uploaded_avatar_id = uploads.id") .joins("LEFT JOIN user_avatars ua ON ua.gravatar_upload_id = uploads.id OR ua.custom_upload_id = uploads.id") .joins("LEFT JOIN user_profiles up ON up.profile_background = uploads.url OR up.card_background = uploads.url") - .joins("LEFT JOIN categories c ON c.uploaded_logo_id = uploads.id OR c.uploaded_background_id = uploads.id OR c.uploaded_meta_id = uploads.id") + .joins("LEFT JOIN categories c ON c.uploaded_logo_id = uploads.id OR c.uploaded_background_id = uploads.id") .joins("LEFT JOIN custom_emojis ce ON ce.upload_id = uploads.id") .joins("LEFT JOIN theme_fields tf ON tf.upload_id = uploads.id") .joins("LEFT JOIN user_exports ue ON ue.upload_id = uploads.id") diff --git a/app/models/category.rb b/app/models/category.rb index d7a4c0fa6d..06adced29a 100644 --- a/app/models/category.rb +++ b/app/models/category.rb @@ -28,7 +28,6 @@ class Category < ActiveRecord::Base belongs_to :latest_post, class_name: "Post" belongs_to :uploaded_logo, class_name: "Upload" belongs_to :uploaded_background, class_name: "Upload" - belongs_to :uploaded_meta, class_name: "Upload" has_many :topics has_many :category_users @@ -666,7 +665,6 @@ end # sort_ascending :boolean # uploaded_logo_id :integer # uploaded_background_id :integer -# uploaded_meta_id :integer # topic_featured_link_allowed :boolean default(TRUE) # all_topics_wiki :boolean default(FALSE), not null # show_subcategory_list :boolean default(FALSE) diff --git a/app/models/category_list.rb b/app/models/category_list.rb index 86631af4c0..f59f36c3cd 100644 --- a/app/models/category_list.rb +++ b/app/models/category_list.rb @@ -69,7 +69,6 @@ class CategoryList @categories = Category.includes( :uploaded_background, :uploaded_logo, - :uploaded_meta, :topic_only_relative_url, subcategories: [:topic_only_relative_url] ).secured(@guardian) diff --git a/app/models/site.rb b/app/models/site.rb index cdc34c2ef8..74d08c9912 100644 --- a/app/models/site.rb +++ b/app/models/site.rb @@ -28,7 +28,7 @@ class Site def categories @categories ||= begin categories = Category - .includes(:uploaded_logo, :uploaded_background, :uploaded_meta) + .includes(:uploaded_logo, :uploaded_background) .secured(@guardian) .joins('LEFT JOIN topics t on t.id = categories.topic_id') .select('categories.*, t.slug topic_slug') diff --git a/app/serializers/basic_category_serializer.rb b/app/serializers/basic_category_serializer.rb index 2074314a3e..6be2df27ff 100644 --- a/app/serializers/basic_category_serializer.rb +++ b/app/serializers/basic_category_serializer.rb @@ -30,7 +30,6 @@ class BasicCategorySerializer < ApplicationSerializer has_one :uploaded_logo, embed: :object, serializer: CategoryUploadSerializer has_one :uploaded_background, embed: :object, serializer: CategoryUploadSerializer - has_one :uploaded_meta, embed: :object, serializer: CategoryUploadSerializer def include_parent_category_id? parent_category_id diff --git a/db/post_migrate/20190103065652_remove_uploaded_meta_id_from_category.rb b/db/post_migrate/20190103065652_remove_uploaded_meta_id_from_category.rb new file mode 100644 index 0000000000..08889fd0aa --- /dev/null +++ b/db/post_migrate/20190103065652_remove_uploaded_meta_id_from_category.rb @@ -0,0 +1,9 @@ +class RemoveUploadedMetaIdFromCategory < ActiveRecord::Migration[5.2] + def up + Migration::ColumnDropper.execute_drop(:categories, %i{uploaded_meta_id}) + end + + def down + raise ActiveRecord::IrreversibleMigration + end +end diff --git a/spec/requests/categories_controller_spec.rb b/spec/requests/categories_controller_spec.rb index 428b876b3e..df387c56ab 100644 --- a/spec/requests/categories_controller_spec.rb +++ b/spec/requests/categories_controller_spec.rb @@ -144,8 +144,7 @@ describe CategoriesController do permissions: { "everyone" => readonly, "staff" => create_post - }, - uploaded_meta_id: 2 + } } expect(response.status).to eq(200) @@ -158,7 +157,6 @@ describe CategoriesController do expect(category.color).to eq("ff0") expect(category.auto_close_hours).to eq(72) expect(UserHistory.count).to eq(4) # 1 + 3 (bootstrap mode) - expect(category.uploaded_meta_id).to eq(2) end end end From cb0f6d653b872e1f0cbc1588df34ae74e83fa97a Mon Sep 17 00:00:00 2001 From: Guo Xiang Tan Date: Thu, 10 Jan 2019 09:38:22 +0800 Subject: [PATCH 103/157] DEV: Minor fixes to b63b3997999bfbee9b8eac0dd1ba87b24325f840. --- app/models/category.rb | 4 ++++ .../20190103065652_remove_uploaded_meta_id_from_category.rb | 2 ++ 2 files changed, 6 insertions(+) diff --git a/app/models/category.rb b/app/models/category.rb index 06adced29a..955fce7476 100644 --- a/app/models/category.rb +++ b/app/models/category.rb @@ -3,6 +3,10 @@ require_dependency 'distributed_cache' class Category < ActiveRecord::Base + self.ignored_columns = %w{ + uploaded_meta_id + } + include Searchable include Positionable include HasCustomFields diff --git a/db/post_migrate/20190103065652_remove_uploaded_meta_id_from_category.rb b/db/post_migrate/20190103065652_remove_uploaded_meta_id_from_category.rb index 08889fd0aa..997ae5e580 100644 --- a/db/post_migrate/20190103065652_remove_uploaded_meta_id_from_category.rb +++ b/db/post_migrate/20190103065652_remove_uploaded_meta_id_from_category.rb @@ -1,3 +1,5 @@ +require 'migration/column_dropper' + class RemoveUploadedMetaIdFromCategory < ActiveRecord::Migration[5.2] def up Migration::ColumnDropper.execute_drop(:categories, %i{uploaded_meta_id}) From 51b13ec86f3e416a6b81bb7db1726fbd293631ca Mon Sep 17 00:00:00 2001 From: Maja Komel Date: Thu, 10 Jan 2019 02:48:05 +0100 Subject: [PATCH 104/157] FIX: show lock glyph to a user without permissions to see quote (#6854) --- app/assets/javascripts/discourse/widgets/post-cooked.js.es6 | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/discourse/widgets/post-cooked.js.es6 b/app/assets/javascripts/discourse/widgets/post-cooked.js.es6 index bf3a323752..84c6f70d6b 100644 --- a/app/assets/javascripts/discourse/widgets/post-cooked.js.es6 +++ b/app/assets/javascripts/discourse/widgets/post-cooked.js.es6 @@ -163,9 +163,10 @@ export default class PostCooked { $blockQuote.showHtml(div, "fast", finished); }) .catch(e => { - if (e.jqXHR.status === 404) { + if ([403, 404].includes(e.jqXHR.status)) { + const icon = e.jqXHR.status === 403 ? "lock" : "trash-o"; $blockQuote.showHtml( - $(`
${iconHTML("trash-o")}
`), + $(`
${iconHTML(icon)}
`), "fast", finished ); From 798e98a7cc85b20d397df7934a1eefc6542cc208 Mon Sep 17 00:00:00 2001 From: Angus McLeod Date: Thu, 10 Jan 2019 13:02:55 +1100 Subject: [PATCH 105/157] remove safari check from isAppleDevice (#6869) --- app/assets/javascripts/discourse/lib/utilities.js.es6 | 1 - 1 file changed, 1 deletion(-) diff --git a/app/assets/javascripts/discourse/lib/utilities.js.es6 b/app/assets/javascripts/discourse/lib/utilities.js.es6 index 0ab5c4c933..cb87d2639e 100644 --- a/app/assets/javascripts/discourse/lib/utilities.js.es6 +++ b/app/assets/javascripts/discourse/lib/utilities.js.es6 @@ -546,7 +546,6 @@ export function isAppleDevice() { // This will apply hack on all iDevices return ( navigator.userAgent.match(/(iPad|iPhone|iPod)/g) && - navigator.userAgent.match(/Safari/g) && !navigator.userAgent.match(/Trident/g) ); } From d10694150e866455027a9a386fb4fb1d62332953 Mon Sep 17 00:00:00 2001 From: Guo Xiang Tan Date: Thu, 10 Jan 2019 09:56:03 +0800 Subject: [PATCH 106/157] Revert "FIX: Partial reply key search in email sent logs." This reverts commit e9b2018bc8bc1254075bec597ce30a84f38b456e. --- app/controllers/admin/email_controller.rb | 14 ++++++++++---- app/models/email_log.rb | 4 ++++ app/models/post_reply_key.rb | 6 +++++- app/serializers/email_log_serializer.rb | 2 +- spec/models/email_log_spec.rb | 3 ++- spec/models/post_reply_key_spec.rb | 5 +++-- spec/requests/admin/email_controller_spec.rb | 4 ++-- 7 files changed, 27 insertions(+), 11 deletions(-) diff --git a/app/controllers/admin/email_controller.rb b/app/controllers/admin/email_controller.rb index b1f28d4693..8325070125 100644 --- a/app/controllers/admin/email_controller.rb +++ b/app/controllers/admin/email_controller.rb @@ -30,10 +30,16 @@ class Admin::EmailController < Admin::AdminController email_logs = filter_logs(email_logs, params) - if params[:reply_key].present? - email_logs = email_logs.where( - "post_reply_keys.reply_key::TEXT ILIKE ?", "%#{params[:reply_key]}%" - ) + if (reply_key = params[:reply_key]).present? + email_logs = + if reply_key.length == 32 + email_logs.where("post_reply_keys.reply_key = ?", reply_key) + else + email_logs.where( + "replace(post_reply_keys.reply_key::VARCHAR, '-', '') ILIKE ?", + "%#{reply_key}%" + ) + end end email_logs = email_logs.to_a diff --git a/app/models/email_log.rb b/app/models/email_log.rb index 0aeae600ff..769436ea01 100644 --- a/app/models/email_log.rb +++ b/app/models/email_log.rb @@ -73,6 +73,10 @@ class EmailLog < ActiveRecord::Base .first end + def bounce_key + super&.delete('-') + end + end # == Schema Information diff --git a/app/models/post_reply_key.rb b/app/models/post_reply_key.rb index 2c85058556..3f1479f6a0 100644 --- a/app/models/post_reply_key.rb +++ b/app/models/post_reply_key.rb @@ -8,8 +8,12 @@ class PostReplyKey < ActiveRecord::Base validates :user_id, presence: true validates :reply_key, presence: true + def reply_key + super&.delete('-') + end + def self.generate_reply_key - SecureRandom.uuid + SecureRandom.hex(16) end end diff --git a/app/serializers/email_log_serializer.rb b/app/serializers/email_log_serializer.rb index e4d120a9f2..75c7010298 100644 --- a/app/serializers/email_log_serializer.rb +++ b/app/serializers/email_log_serializer.rb @@ -12,6 +12,6 @@ class EmailLogSerializer < ApplicationSerializer end def reply_key - @options[:reply_keys][[object.post_id, object.user_id]] + @options[:reply_keys][[object.post_id, object.user_id]].delete("-") end end diff --git a/spec/models/email_log_spec.rb b/spec/models/email_log_spec.rb index 6124d32071..f0f60dabb0 100644 --- a/spec/models/email_log_spec.rb +++ b/spec/models/email_log_spec.rb @@ -103,7 +103,8 @@ describe EmailLog do .pluck("bounce_key::text") .first - expect(raw_key).to eq(hex) + expect(raw_key).to_not eq(hex) + expect(raw_key.delete('-')).to eq(hex) expect(EmailLog.find(email_log.id).bounce_key).to eq(hex) end end diff --git a/spec/models/post_reply_key_spec.rb b/spec/models/post_reply_key_spec.rb index 45f6d5ee14..631e6cd6a2 100644 --- a/spec/models/post_reply_key_spec.rb +++ b/spec/models/post_reply_key_spec.rb @@ -3,7 +3,7 @@ require 'rails_helper' RSpec.describe PostReplyKey do describe "#reply_key" do it "should format the reply_key correctly" do - hex = SecureRandom.uuid + hex = SecureRandom.hex post_reply_key = Fabricate(:post_reply_key, reply_key: hex ) @@ -12,7 +12,8 @@ RSpec.describe PostReplyKey do .pluck("reply_key::text") .first - expect(raw_key).to eq(hex) + expect(raw_key).to_not eq(hex) + expect(raw_key.delete('-')).to eq(hex) expect(PostReplyKey.find(post_reply_key.id).reply_key).to eq(hex) end end diff --git a/spec/requests/admin/email_controller_spec.rb b/spec/requests/admin/email_controller_spec.rb index 8bee246ae1..97cdef075c 100644 --- a/spec/requests/admin/email_controller_spec.rb +++ b/spec/requests/admin/email_controller_spec.rb @@ -69,8 +69,8 @@ describe Admin::EmailController do ) [ - "17-ff04", - "2d447423-c625-4fb9-8717-ff04ac60eee8" + "17ff04", + "2d447423c6254fb98717ff04ac60eee8" ].each do |reply_key| get "/admin/email/sent.json", params: { reply_key: reply_key From c2bca9cabed1c2ee73d89fc2f7c9b2963670eb61 Mon Sep 17 00:00:00 2001 From: Guo Xiang Tan Date: Thu, 10 Jan 2019 10:52:15 +0800 Subject: [PATCH 107/157] Make rubocop happy. --- app/controllers/admin/email_controller.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/controllers/admin/email_controller.rb b/app/controllers/admin/email_controller.rb index 8325070125..2415546005 100644 --- a/app/controllers/admin/email_controller.rb +++ b/app/controllers/admin/email_controller.rb @@ -57,8 +57,8 @@ class Admin::EmailController < Admin::AdminController *tuples ) .pluck(:post_id, :user_id, "reply_key::text") - .each do |post_id, user_id, reply_key| - reply_keys[[post_id, user_id]] = reply_key + .each do |post_id, user_id, key| + reply_keys[[post_id, user_id]] = key end end From 7896c74c2bd161f85c976fcd8468797721df30d9 Mon Sep 17 00:00:00 2001 From: Guo Xiang Tan Date: Thu, 10 Jan 2019 11:10:21 +0800 Subject: [PATCH 108/157] DEV: Remove use of ActiveRecord in migration. It makes the migration brittle to changes. If I could I would go back in time and tell the 2016 version of me that. --- ...0161202034856_add_uploads_to_categories.rb | 24 +++++++++++-------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/db/migrate/20161202034856_add_uploads_to_categories.rb b/db/migrate/20161202034856_add_uploads_to_categories.rb index b799e0c23a..7aa3b64628 100644 --- a/db/migrate/20161202034856_add_uploads_to_categories.rb +++ b/db/migrate/20161202034856_add_uploads_to_categories.rb @@ -3,16 +3,20 @@ class AddUploadsToCategories < ActiveRecord::Migration[4.2] add_column :categories, :uploaded_logo_id, :integer, index: true add_column :categories, :uploaded_background_id, :integer, index: true - transaction do - Category.find_each do |category| - logo_upload = Upload.find_by(url: category.logo_url) - background_upload = Upload.find_by(url: category.background_url) + execute <<~SQL + UPDATE categories + SET uploaded_logo_id = u.id + FROM categories c + LEFT JOIN uploads u ON u.url = c.logo_url + WHERE u.url IS NOT NULL + SQL - category.update_columns( - uploaded_logo_id: logo_upload&.id, - uploaded_background_id: background_upload&.id - ) - end - end + execute <<~SQL + UPDATE categories + SET uploaded_background_id = u.id + FROM categories c + LEFT JOIN uploads u ON u.url = c.background_url + WHERE u.url IS NOT NULL + SQL end end From f9648de8972dea4661e75b3f25bfeb1535e9c6c4 Mon Sep 17 00:00:00 2001 From: Joffrey JAFFEUX Date: Thu, 10 Jan 2019 11:06:01 +0100 Subject: [PATCH 109/157] DEV: upgrades from Ember 2.13 to Ember 3.5.1 (#6808) Co-Authored-By: Bianca Nenciu Co-Authored-By: David Taylor --- Gemfile | 4 +- Gemfile.lock | 13 +- .../components/admin-user-field-item.js.es6 | 14 +- .../components/admin-watched-word.js.es6 | 2 +- .../admin/components/embeddable-host.js.es6 | 4 +- .../components/inline-edit-checkbox.js.es6 | 12 +- .../admin/components/permalink-form.js.es6 | 2 +- .../admin/components/resumable-upload.js.es6 | 8 +- .../admin/components/save-controls.js.es6 | 6 - .../screened-ip-address-form.js.es6 | 7 +- .../admin/components/site-text-summary.js.es6 | 6 - .../admin/components/tags-uploader.js.es6 | 4 +- .../admin/components/watched-word-form.js.es6 | 2 +- .../components/watched-word-uploader.js.es6 | 2 +- .../admin-customize-themes-show.js.es6 | 2 +- .../controllers/admin-web-hooks-show.js.es6 | 18 +- .../javascripts/admin/models/theme.js.es6 | 6 +- .../admin/models/watched-word.js.es6 | 2 +- .../javascripts/admin/templates/api-keys.hbs | 4 +- .../admin/templates/backups-index.hbs | 18 +- .../javascripts/admin/templates/backups.hbs | 6 +- .../components/admin-report-table.hbs | 2 +- .../templates/components/admin-report.hbs | 6 +- .../components/admin-user-field-item.hbs | 13 +- .../components/admin-web-hook-event.hbs | 8 +- .../templates/components/embeddable-host.hbs | 16 +- .../templates/components/flagged-post.hbs | 6 +- .../components/inline-edit-checkbox.hbs | 4 +- .../templates/components/permalink-form.hbs | 2 +- .../templates/components/save-controls.hbs | 2 +- .../components/screened-ip-address-form.hbs | 2 +- .../components/secret-value-list.hbs | 4 +- .../templates/components/site-setting.hbs | 8 +- .../site-settings/uploaded-image-list.hbs | 2 +- .../components/site-text-summary.hbs | 2 +- .../templates/components/tags-uploader.hbs | 12 +- .../admin/templates/components/value-list.hbs | 2 +- .../components/watched-word-form.hbs | 2 +- .../customize-email-templates-edit.hbs | 4 +- .../admin/templates/customize-themes-edit.hbs | 2 +- .../admin/templates/customize-themes-show.hbs | 30 +- .../admin/templates/customize-themes.hbs | 4 +- .../admin/templates/dashboard-problems.hbs | 2 +- .../templates/dashboard_next_general.hbs | 2 +- .../templates/dashboard_next_moderation.hbs | 2 +- .../admin/templates/email-bounced.hbs | 2 +- .../admin/templates/email-received.hbs | 2 +- .../admin/templates/email-rejected.hbs | 2 +- .../admin/templates/email-sent.hbs | 2 +- .../admin/templates/email-skipped.hbs | 2 +- .../javascripts/admin/templates/embedding.hbs | 6 +- .../javascripts/admin/templates/emojis.hbs | 2 +- .../admin/templates/logs/screened-emails.hbs | 2 +- .../templates/logs/screened-ip-addresses.hbs | 16 +- .../templates/logs/staff-action-logs.hbs | 2 +- .../templates/modal/admin-add-upload.hbs | 4 +- .../templates/modal/admin-create-theme.hbs | 4 +- .../modal/admin-edit-badge-groupings.hbs | 2 +- .../templates/modal/admin-import-theme.hbs | 4 +- .../modal/admin-moderation-history.hbs | 2 +- .../templates/modal/admin-silence-user.hbs | 4 +- .../modal/admin-staff-action-log-details.hbs | 2 +- .../templates/modal/admin-suspend-user.hbs | 4 +- .../templates/modal/admin-theme-change.hbs | 2 +- .../modal/admin-uploaded-image-list.hbs | 2 +- .../admin/templates/permalinks.hbs | 4 +- .../admin/templates/plugins-index.hbs | 2 +- .../javascripts/admin/templates/plugins.hbs | 6 +- .../admin/templates/site-settings.hbs | 4 +- .../admin/templates/site-text-edit.hbs | 4 +- .../admin/templates/site-text-index.hbs | 6 +- .../admin/templates/user-fields.hbs | 8 +- .../admin/templates/user-index.hbs | 64 +-- .../admin/templates/users-list.hbs | 4 +- .../admin/templates/watched-words-action.hbs | 6 +- .../admin/templates/watched-words.hbs | 4 +- .../admin/templates/web-hooks-show-events.hbs | 4 +- .../admin/templates/web-hooks-show.hbs | 5 +- .../javascripts/admin/templates/web-hooks.hbs | 4 +- .../lib/raw-handlebars.js.es6 | 4 - .../components/auth-token-dropdown.es6 | 4 +- .../components/avatar-uploader.js.es6 | 2 +- .../components/backup-uploader.js.es6 | 2 +- .../components/basic-topic-list.js.es6 | 6 - .../components/bulk-select-button.js.es6 | 6 +- .../components/color-picker-choice.js.es6 | 2 +- .../discourse/components/composer-body.js.es6 | 4 +- .../components/composer-editor.js.es6 | 36 +- .../components/composer-message.js.es6 | 2 +- .../components/composer-messages.js.es6 | 7 +- .../components/create-account.js.es6 | 2 +- .../discourse/components/d-button.js.es6 | 7 +- .../discourse/components/d-checkbox.js.es6 | 2 +- .../components/d-editor-modal.js.es6 | 2 +- .../discourse/components/d-editor.js.es6 | 70 +-- .../desktop-notification-config.js.es6 | 6 +- .../components/discourse-linked-text.js.es6 | 2 +- .../components/edit-category-images.js.es6 | 47 +- .../components/edit-topic-timer-form.js.es6 | 4 +- .../discourse/components/emoji-picker.js.es6 | 2 +- .../components/emoji-uploader.js.es6 | 2 +- .../components/flag-action-type.js.es6 | 2 +- .../components/group-card-contents.js.es6 | 15 +- .../group-manage-logs-filter.js.es6 | 2 +- .../components/group-member-dropdown.js.es6 | 6 +- .../discourse/components/group-member.js.es6 | 2 +- .../components/group-membership-button.js.es6 | 2 +- .../components/image-uploader.js.es6 | 14 +- .../components/images-uploader.js.es6 | 2 +- .../discourse/components/load-more.js.es6 | 5 +- .../discourse/components/login-buttons.js.es6 | 6 +- .../components/preference-checkbox.js.es6 | 2 +- .../discourse/components/queued-post.js.es6 | 2 +- .../components/scrolling-post-stream.js.es6 | 8 +- .../discourse/components/share-popup.js.es6 | 2 +- .../discourse/components/share-source.js.es6 | 2 +- .../discourse/components/signup-cta.js.es6 | 3 - .../components/tags-admin-dropdown.js.es6 | 24 +- .../components/top-period-buttons.js.es6 | 2 +- .../components/topic-list-item.js.es6 | 3 +- .../discourse/components/topic-list.js.es6 | 4 +- .../components/user-card-contents.js.es6 | 11 +- .../discourse/components/user-selector.js.es6 | 2 +- .../discourse/controllers/composer.js.es6 | 38 +- .../controllers/create-account.js.es6 | 9 +- .../controllers/discovery/topics.js.es6 | 2 +- .../controllers/edit-topic-timer.js.es6 | 2 +- .../discourse/controllers/group.js.es6 | 3 +- .../discourse/controllers/login.js.es6 | 26 +- .../controllers/preferences/account.js.es6 | 4 + .../controllers/preferences/email.js.es6 | 31 +- .../preferences/second-factor-backup.js.es6 | 6 +- .../preferences/second-factor.js.es6 | 11 +- .../controllers/preferences/username.js.es6 | 4 +- .../controllers/reorder-categories.js.es6 | 2 +- .../discourse/controllers/tags-index.js.es6 | 9 + .../discourse/controllers/topic.js.es6 | 54 +- .../initializers/enable-emoji.js.es6 | 2 +- .../discourse/initializers/message-bus.js.es6 | 2 +- .../register-service-worker.js.es6 | 3 +- .../discourse/lib/ajax-error.js.es6 | 9 +- .../discourse/lib/keyboard-shortcuts.js.es6 | 7 +- .../lib/posts-with-placeholders.js.es6 | 6 +- .../discourse/lib/push-notifications.js.es6 | 15 +- .../javascripts/discourse/lib/text.js.es6 | 7 +- .../mixins/grant-badge-controller.js.es6 | 4 +- .../discourse/mixins/key-enter-escape.js.es6 | 4 +- .../mixins/password-validation.js.es6 | 2 +- .../discourse/mixins/url-refresh.js.es6 | 10 +- .../discourse/models/post-stream.js.es6 | 4 +- .../javascripts/discourse/models/post.js.es6 | 17 +- .../discourse/models/user-stream.js.es6 | 2 +- .../javascripts/discourse/models/user.js.es6 | 5 +- .../inject-discourse-objects.js.es6 | 6 +- .../pre-initializers/map-routes.js.es6 | 1 + .../register-dom-templates.js.es6 | 1 - .../sniff-capabilities.js.es6 | 10 +- .../discourse/routes/discourse.js.es6 | 4 +- .../discourse/routes/discovery.js.es6 | 3 +- .../javascripts/discourse/routes/topic.js.es6 | 16 +- .../discourse/routes/user-summary.js.es6 | 2 +- .../templates/account-created/edit-email.hbs | 2 +- .../discourse/templates/badges/show.hbs | 10 +- .../templates/components/backup-codes.hbs | 2 +- .../templates/components/basic-topic-list.hbs | 4 +- .../components/bulk-select-button.hbs | 2 +- .../templates/components/color-picker.hbs | 2 +- .../components/composer-action-title.hbs | 2 + .../templates/components/composer-editor.hbs | 12 +- .../components/composer-messages.hbs | 2 +- .../components/composer-user-selector.hbs | 2 +- .../templates/components/d-editor-modal.hbs | 4 +- .../templates/components/d-editor.hbs | 2 +- .../templates/components/d-modal.hbs | 4 +- .../desktop-notification-config.hbs | 6 +- .../components/edit-category-general.hbs | 2 +- .../components/edit-category-images.hbs | 16 +- .../components/group-card-contents.hbs | 10 +- .../components/group-manage-logs-filter.hbs | 2 +- .../components/group-manage-logs-row.hbs | 8 +- .../components/group-manage-save-button.hbs | 2 +- .../components/group-members-input.hbs | 4 +- .../components/group-membership-button.hbs | 6 +- .../templates/components/login-buttons.hbs | 4 +- .../components/modal-footer-close.hbs | 2 +- .../templates/components/queued-post.hbs | 12 +- .../components/second-factor-form.hbs | 2 +- .../templates/components/share-popup.hbs | 2 +- .../templates/components/signup-cta.hbs | 4 +- .../components/top-period-buttons.hbs | 2 +- .../templates/components/topic-entrance.hbs | 4 +- .../templates/components/topic-progress.hbs | 2 +- .../components/user-card-contents.hbs | 9 +- .../discourse/templates/composer.hbs | 29 +- .../templates/discovery/categories.hbs | 2 +- .../discourse/templates/discovery/topics.hbs | 12 +- .../discourse/templates/full-page-search.hbs | 16 +- .../templates/group-activity-posts.hbs | 2 +- .../templates/group-activity-topics.hbs | 2 +- .../discourse/templates/group-index.hbs | 12 +- .../javascripts/discourse/templates/group.hbs | 6 +- .../discourse/templates/group/manage/logs.hbs | 10 +- .../discourse/templates/groups/index.hbs | 6 +- .../discourse/templates/groups/new.hbs | 2 +- .../discourse/templates/login-preferences.hbs | 4 +- .../mobile/components/basic-topic-list.hbs | 2 +- .../templates/mobile/discovery/categories.hbs | 4 +- .../templates/mobile/discovery/topics.hbs | 6 +- .../templates/mobile/group-index.hbs | 14 +- .../templates/mobile/modal/login.hbs | 24 +- .../discourse/templates/mobile/users.hbs | 2 +- .../templates/modal/activation-edit.hbs | 4 +- .../templates/modal/activation-resent.hbs | 2 +- .../discourse/templates/modal/auth-token.hbs | 4 +- .../templates/modal/avatar-selector.hbs | 8 +- .../templates/modal/bulk-change-category.hbs | 2 +- .../templates/modal/create-account.hbs | 19 +- .../modal/delete-topic-disallowed.hbs | 2 +- .../templates/modal/edit-category.hbs | 6 +- .../templates/modal/edit-topic-timer.hbs | 13 +- .../templates/modal/feature-topic.hbs | 12 +- .../discourse/templates/modal/flag.hbs | 4 +- .../templates/modal/forgot-password.hbs | 6 +- .../templates/modal/group-add-members.hbs | 2 +- .../templates/modal/group-bulk-add.hbs | 2 +- .../discourse/templates/modal/history.hbs | 24 +- .../discourse/templates/modal/invite.hbs | 8 +- .../templates/modal/jump-to-post.hbs | 6 +- .../discourse/templates/modal/login.hbs | 8 +- .../discourse/templates/modal/merge-topic.hbs | 0 .../templates/modal/post-enqueued.hbs | 2 +- .../discourse/templates/modal/raw-email.hbs | 6 +- .../discourse/templates/modal/rename-tag.hbs | 2 +- .../templates/modal/reorder-categories.hbs | 10 +- .../modal/request-group-membership-form.hbs | 4 +- .../discourse/templates/modal/split-topic.hbs | 0 .../discourse/templates/modal/tag-upload.hbs | 2 +- .../templates/modal/upload-selector.hbs | 4 +- .../discourse/templates/password-reset.hbs | 4 +- .../discourse/templates/preferences-about.hbs | 2 +- .../discourse/templates/preferences-email.hbs | 4 +- .../preferences-second-factor-backup.hbs | 4 +- .../templates/preferences-second-factor.hbs | 8 +- .../templates/preferences/account.hbs | 12 +- .../discourse/templates/preferences/apps.hbs | 6 +- .../discourse/templates/queued-posts.hbs | 2 +- .../discourse/templates/selected-posts.hbs | 6 +- .../discourse/templates/static.hbs | 6 +- .../discourse/templates/tag-groups-show.hbs | 6 +- .../discourse/templates/tags/index.hbs | 2 +- .../discourse/templates/tags/show.hbs | 14 +- .../javascripts/discourse/templates/topic.hbs | 50 +- .../discourse/templates/user-card.hbs | 15 +- .../discourse/templates/user-invited-show.hbs | 12 +- .../discourse/templates/user-topics-list.hbs | 4 +- .../javascripts/discourse/templates/user.hbs | 10 +- .../discourse/templates/user/activity.hbs | 2 +- .../discourse/templates/user/messages.hbs | 4 +- .../templates/user/notifications.hbs | 2 +- .../user/preferences/_save-button.hbs | 2 +- .../javascripts/discourse/templates/users.hbs | 2 +- .../widgets/component-connector.js.es6 | 6 + .../javascripts/discourse/widgets/glue.js.es6 | 10 + .../discourse/widgets/widget.js.es6 | 4 +- .../javascripts/ember-addons/fmt.js.es6 | 9 +- app/assets/javascripts/ember_jquery.js | 1 + app/assets/javascripts/env.js | 3 +- app/assets/javascripts/polyfills.js | 1 - .../components/composer-actions.js.es6 | 36 +- .../multi-select/selected-name.js.es6 | 2 +- .../components/notifications-button.js.es6 | 18 +- .../components/period-chooser.js.es6 | 4 +- .../select-kit/components/select-kit.js.es6 | 6 +- .../select-kit/select-kit-header.js.es6 | 2 +- .../select-kit/select-kit-row.js.es6 | 4 +- .../tag-notifications-button.js.es6 | 2 +- .../topic-footer-mobile-dropdown.js.es6 | 78 +-- .../javascripts/set-prototype-polyfill.js | 8 + app/assets/javascripts/vendor.js | 1 - .../wizard/components/invite-list-user.js.es6 | 6 - .../wizard/components/radio-button.js.es6 | 2 +- .../wizard/components/theme-preview.js.es6 | 2 +- .../wizard/components/wizard-step.js.es6 | 5 +- .../templates/components/invite-list-user.hbs | 2 +- .../templates/components/invite-list.hbs | 2 +- .../templates/components/theme-previews.hbs | 4 +- .../components/wizard-field-radio.hbs | 2 +- .../javascripts/wizard/templates/step.hbs | 2 +- app/models/theme_field.rb | 7 + ...3317_force_theme_compilation_for_ember3.rb | 6 + lib/backup_restore/restorer.rb | 5 +- package.json | 2 +- .../discourse-local-dates-create-form.hbs | 2 +- .../composer-presence-display.js.es6 | 2 - .../templates/modal/poll-ui-builder.hbs | 2 +- .../initializers/add-poll-ui-builder.js.es6 | 2 +- .../create-account-user-fields-test.js.es6 | 16 +- .../acceptance/jump-to-test.js.es6 | 3 +- .../acceptance/preferences-test.js.es6 | 9 +- .../acceptance/sign-in-test.js.es6 | 6 +- .../acceptance/topic-edit-timer-test.js.es6 | 7 + test/javascripts/acceptance/topic-test.js.es6 | 7 + test/javascripts/acceptance/user-test.js.es6 | 1 + .../components/d-editor-test.js.es6 | 2 +- .../components/keyboard-shortcuts-test.js.es6 | 14 +- .../topic-notifications-options-test.js.es6 | 3 +- .../controllers/create-account-test.js.es6 | 40 +- test/javascripts/fixtures/topic.js.es6 | 2 +- .../helpers/create-pretender.js.es6 | 4 + .../widgets/actions-summary-test.js.es6 | 2 +- test/javascripts/widgets/header-test.js.es6 | 6 +- test/javascripts/widgets/post-test.js.es6 | 71 ++- .../javascripts/widgets/user-menu-test.js.es6 | 6 +- vendor/assets/javascripts/buffered-proxy.js | 7 +- vendor/assets/javascripts/handlebars.js | 518 +++++++++++++----- vendor/assets/javascripts/i18n-patches.js | 2 +- .../javascripts/modernizr.custom.00874.js | 4 - yarn.lock | 362 ++++-------- 318 files changed, 1684 insertions(+), 1462 deletions(-) create mode 100644 app/assets/javascripts/discourse/templates/modal/merge-topic.hbs create mode 100644 app/assets/javascripts/discourse/templates/modal/split-topic.hbs create mode 100644 app/assets/javascripts/set-prototype-polyfill.js create mode 100644 db/migrate/20181218143317_force_theme_compilation_for_ember3.rb delete mode 100644 vendor/assets/javascripts/modernizr.custom.00874.js diff --git a/Gemfile b/Gemfile index d715161cce..345d8e7d82 100644 --- a/Gemfile +++ b/Gemfile @@ -41,8 +41,8 @@ gem 'onebox', '1.8.76' gem 'http_accept_language', '~>2.0.5', require: false gem 'ember-rails', '0.18.5' -gem 'ember-source', '2.13.3' -gem 'ember-handlebars-template', '0.7.5' +gem 'discourse-ember-source', '~> 3.5.1' +gem 'ember-handlebars-template', '0.8.0' gem 'barber' # message bus 2.2.0 should be very stable diff --git a/Gemfile.lock b/Gemfile.lock index f51e6459e7..26d066bc47 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -83,7 +83,7 @@ GEM open4 (~> 1.3) coderay (1.1.2) colored2 (3.1.2) - concurrent-ruby (1.1.3) + concurrent-ruby (1.1.4) connection_pool (2.2.2) cork (0.3.0) colored2 (~> 3.1) @@ -105,6 +105,7 @@ GEM terminal-table (~> 1) debug_inspector (0.0.3) diff-lcs (1.3) + discourse-ember-source (3.5.1.3) discourse_image_optim (0.26.2) exifr (~> 1.2, >= 1.2.2) fspath (~> 3.0) @@ -114,9 +115,9 @@ GEM email_reply_trimmer (0.1.12) ember-data-source (3.0.2) ember-source (>= 2, < 3.0) - ember-handlebars-template (0.7.5) + ember-handlebars-template (0.8.0) barber (>= 0.11.0) - sprockets (>= 3.3, < 4) + sprockets (>= 3.3, < 4.1) ember-rails (0.18.5) active_model_serializers ember-data-source (>= 1.0.0.beta.5) @@ -124,7 +125,7 @@ GEM ember-source (>= 1.1.0) jquery-rails (>= 1.0.17) railties (>= 3.1) - ember-source (2.13.3) + ember-source (2.18.2) erubi (1.7.1) excon (0.62.0) execjs (2.7.0) @@ -462,11 +463,11 @@ DEPENDENCIES colored2 cppjieba_rb danger + discourse-ember-source (~> 3.5.1) discourse_image_optim email_reply_trimmer (~> 0.1) - ember-handlebars-template (= 0.7.5) + ember-handlebars-template (= 0.8.0) ember-rails (= 0.18.5) - ember-source (= 2.13.3) excon execjs fabrication diff --git a/app/assets/javascripts/admin/components/admin-user-field-item.js.es6 b/app/assets/javascripts/admin/components/admin-user-field-item.js.es6 index 64e95cecf9..bdb22e258c 100644 --- a/app/assets/javascripts/admin/components/admin-user-field-item.js.es6 +++ b/app/assets/javascripts/admin/components/admin-user-field-item.js.es6 @@ -83,26 +83,14 @@ export default Ember.Component.extend(bufferedProperty("userField"), { .catch(popupAjaxError); }, - moveUp() { - this.sendAction("moveUpAction", this.get("userField")); - }, - - moveDown() { - this.sendAction("moveDownAction", this.get("userField")); - }, - edit() { this.set("editing", true); }, - destroy() { - this.sendAction("destroyAction", this.get("userField")); - }, - cancel() { const id = this.get("userField.id"); if (Ember.isEmpty(id)) { - this.sendAction("destroyAction", this.get("userField")); + this.destroyAction(this.get("userField")); } else { this.rollbackBuffer(); this.set("editing", false); diff --git a/app/assets/javascripts/admin/components/admin-watched-word.js.es6 b/app/assets/javascripts/admin/components/admin-watched-word.js.es6 index d1283e8b3a..53f7a8a056 100644 --- a/app/assets/javascripts/admin/components/admin-watched-word.js.es6 +++ b/app/assets/javascripts/admin/components/admin-watched-word.js.es6 @@ -14,7 +14,7 @@ export default Ember.Component.extend( this.get("word") .destroy() .then(() => { - this.sendAction("action", this.get("word")); + this.action(this.get("word")); }) .catch(e => { bootbox.alert( diff --git a/app/assets/javascripts/admin/components/embeddable-host.js.es6 b/app/assets/javascripts/admin/components/embeddable-host.js.es6 index 7264d42c1c..8dda74d221 100644 --- a/app/assets/javascripts/admin/components/embeddable-host.js.es6 +++ b/app/assets/javascripts/admin/components/embeddable-host.js.es6 @@ -61,7 +61,7 @@ export default Ember.Component.extend(bufferedProperty("host"), { this.get("host") .destroyRecord() .then(() => { - this.sendAction("deleteHost", this.get("host")); + this.deleteHost(this.get("host")); }); } }); @@ -70,7 +70,7 @@ export default Ember.Component.extend(bufferedProperty("host"), { cancel() { const host = this.get("host"); if (host.get("isNew")) { - this.sendAction("deleteHost", host); + this.deleteHost(host); } else { this.rollbackBuffer(); this.set("editToggled", false); diff --git a/app/assets/javascripts/admin/components/inline-edit-checkbox.js.es6 b/app/assets/javascripts/admin/components/inline-edit-checkbox.js.es6 index f14ba62201..fb93625716 100644 --- a/app/assets/javascripts/admin/components/inline-edit-checkbox.js.es6 +++ b/app/assets/javascripts/admin/components/inline-edit-checkbox.js.es6 @@ -4,13 +4,17 @@ import { } from "ember-addons/ember-computed-decorators"; export default Ember.Component.extend({ + classNames: ["inline-edit"], + + checked: null, + checkedInternal: null, + init() { - this._super(); + this._super(...arguments); + this.set("checkedInternal", this.get("checked")); }, - classNames: ["inline-edit"], - @observes("checked") checkedChanged() { this.set("checkedInternal", this.get("checked")); @@ -33,7 +37,7 @@ export default Ember.Component.extend({ finished() { this.set("checked", this.get("checkedInternal")); - this.sendAction(); + this.action(); } } }); diff --git a/app/assets/javascripts/admin/components/permalink-form.js.es6 b/app/assets/javascripts/admin/components/permalink-form.js.es6 index 969840a308..0bd547cf25 100644 --- a/app/assets/javascripts/admin/components/permalink-form.js.es6 +++ b/app/assets/javascripts/admin/components/permalink-form.js.es6 @@ -33,7 +33,7 @@ export default Ember.Component.extend({ self.set("url", ""); self.set("permalink_type_value", ""); self.set("formSubmitted", false); - self.sendAction("action", Permalink.create(result.permalink)); + self.action(Permalink.create(result.permalink)); Em.run.schedule("afterRender", function() { self.$(".permalink-url").focus(); }); diff --git a/app/assets/javascripts/admin/components/resumable-upload.js.es6 b/app/assets/javascripts/admin/components/resumable-upload.js.es6 index 4a8545b3ab..00730478c3 100644 --- a/app/assets/javascripts/admin/components/resumable-upload.js.es6 +++ b/app/assets/javascripts/admin/components/resumable-upload.js.es6 @@ -8,8 +8,8 @@ import { bufferedRender } from "discourse-common/lib/buffered-render"; {{resumable-upload target="/admin/backups/upload" - success="successAction" - error="errorAction" + success=(action "successAction") + error=(action "errorAction") uploadText="UPLOAD" }} **/ @@ -100,7 +100,7 @@ export default Ember.Component.extend( // mark as not uploading anymore self._reset(); // fire an event to allow the parent route to reload its model - self.sendAction("success", file.fileName); + self.success(file.fileName); }); }); @@ -109,7 +109,7 @@ export default Ember.Component.extend( // mark as not uploading anymore self._reset(); // fire an event to allow the parent route to display the error message - self.sendAction("error", file.fileName, message); + self.error(file.fileName, message); }); }); }.on("init"), diff --git a/app/assets/javascripts/admin/components/save-controls.js.es6 b/app/assets/javascripts/admin/components/save-controls.js.es6 index 51adbaf3c5..cade010e5b 100644 --- a/app/assets/javascripts/admin/components/save-controls.js.es6 +++ b/app/assets/javascripts/admin/components/save-controls.js.es6 @@ -8,11 +8,5 @@ export default Ember.Component.extend({ @computed("model.isSaving") savingText(saving) { return saving ? "saving" : "save"; - }, - - actions: { - saveChanges() { - this.sendAction(); - } } }); diff --git a/app/assets/javascripts/admin/components/screened-ip-address-form.js.es6 b/app/assets/javascripts/admin/components/screened-ip-address-form.js.es6 index 6197bfc320..64fd7eeb38 100644 --- a/app/assets/javascripts/admin/components/screened-ip-address-form.js.es6 +++ b/app/assets/javascripts/admin/components/screened-ip-address-form.js.es6 @@ -2,7 +2,7 @@ A form to create an IP address that will be blocked or whitelisted. Example usage: - {{screened-ip-address-form action="recordAdded"}} + {{screened-ip-address-form action=(action "recordAdded")}} where action is a callback on the controller or route that will get called after the new record is successfully saved. It is called with the new ScreenedIpAddress record @@ -60,10 +60,7 @@ export default Ember.Component.extend({ .save() .then(result => { this.setProperties({ ip_address: "", formSubmitted: false }); - this.sendAction( - "action", - ScreenedIpAddress.create(result.screened_ip_address) - ); + this.action(ScreenedIpAddress.create(result.screened_ip_address)); Ember.run.schedule("afterRender", () => this.$(".ip-address-input").focus() ); diff --git a/app/assets/javascripts/admin/components/site-text-summary.js.es6 b/app/assets/javascripts/admin/components/site-text-summary.js.es6 index 48ceb2fefd..177448b102 100644 --- a/app/assets/javascripts/admin/components/site-text-summary.js.es6 +++ b/app/assets/javascripts/admin/components/site-text-summary.js.es6 @@ -30,11 +30,5 @@ export default Ember.Component.extend({ } return this.get("term"); - }, - - actions: { - edit() { - this.sendAction("editAction", this.get("siteText")); - } } }); diff --git a/app/assets/javascripts/admin/components/tags-uploader.js.es6 b/app/assets/javascripts/admin/components/tags-uploader.js.es6 index 621045b731..60b4297a48 100644 --- a/app/assets/javascripts/admin/components/tags-uploader.js.es6 +++ b/app/assets/javascripts/admin/components/tags-uploader.js.es6 @@ -12,8 +12,8 @@ export default Em.Component.extend(UploadMixin, { uploadDone() { bootbox.alert(I18n.t("tagging.upload_successful"), () => { - this.sendAction("refresh"); - this.sendAction("closeModal"); + this.refresh(); + this.closeModal(); }); } }); diff --git a/app/assets/javascripts/admin/components/watched-word-form.js.es6 b/app/assets/javascripts/admin/components/watched-word-form.js.es6 index c9a61965e8..bdfafc12af 100644 --- a/app/assets/javascripts/admin/components/watched-word-form.js.es6 +++ b/app/assets/javascripts/admin/components/watched-word-form.js.es6 @@ -64,7 +64,7 @@ export default Ember.Component.extend({ showMessage: true, message: I18n.t("admin.watched_words.form.success") }); - this.sendAction("action", WatchedWord.create(result)); + this.action(WatchedWord.create(result)); Ember.run.schedule("afterRender", () => this.$(".watched-word-input").focus() ); diff --git a/app/assets/javascripts/admin/components/watched-word-uploader.js.es6 b/app/assets/javascripts/admin/components/watched-word-uploader.js.es6 index e5ff9a1fd7..6b35fd465e 100644 --- a/app/assets/javascripts/admin/components/watched-word-uploader.js.es6 +++ b/app/assets/javascripts/admin/components/watched-word-uploader.js.es6 @@ -19,7 +19,7 @@ export default Em.Component.extend(UploadMixin, { uploadDone() { if (this) { bootbox.alert(I18n.t("admin.watched_words.form.upload_successful")); - this.sendAction("done"); + this.done(); } } }); diff --git a/app/assets/javascripts/admin/controllers/admin-customize-themes-show.js.es6 b/app/assets/javascripts/admin/controllers/admin-customize-themes-show.js.es6 index 78a98c5560..45f1b3359d 100644 --- a/app/assets/javascripts/admin/controllers/admin-customize-themes-show.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-customize-themes-show.js.es6 @@ -48,7 +48,7 @@ export default Ember.Controller.extend({ return colorSchemeId !== existingId; }, - @computed("availableChildThemes", "model.childThemes.@each", "model") + @computed("availableChildThemes", "model.childThemes.[]", "model") selectableChildThemes(available, childThemes) { if (available) { const themes = !childThemes diff --git a/app/assets/javascripts/admin/controllers/admin-web-hooks-show.js.es6 b/app/assets/javascripts/admin/controllers/admin-web-hooks-show.js.es6 index 55e6b007fd..1f28c96723 100644 --- a/app/assets/javascripts/admin/controllers/admin-web-hooks-show.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-web-hooks-show.js.es6 @@ -62,9 +62,21 @@ export default Ember.Controller.extend({ } }, - @computed("model.isSaving", "secretValidation", "eventTypeValidation") - saveButtonDisabled(isSaving, secretValidation, eventTypeValidation) { - return isSaving ? false : secretValidation || eventTypeValidation; + @computed( + "model.isSaving", + "secretValidation", + "eventTypeValidation", + "model.payload_url" + ) + saveButtonDisabled( + isSaving, + secretValidation, + eventTypeValidation, + payloadUrl + ) { + return isSaving + ? false + : secretValidation || eventTypeValidation || Ember.isEmpty(payloadUrl); }, actions: { diff --git a/app/assets/javascripts/admin/models/theme.js.es6 b/app/assets/javascripts/admin/models/theme.js.es6 index da881abf9b..e81fc7e147 100644 --- a/app/assets/javascripts/admin/models/theme.js.es6 +++ b/app/assets/javascripts/admin/models/theme.js.es6 @@ -30,7 +30,7 @@ const Theme = RestModel.extend({ return hash; }, - @computed("theme_fields", "theme_fields.@each") + @computed("theme_fields", "theme_fields.[]") uploads(fields) { if (!fields) { return []; @@ -47,7 +47,7 @@ const Theme = RestModel.extend({ ); }, - @computed("theme_fields.@each") + @computed("theme_fields.[]") editedFields(fields) { return fields.filter( field => !Em.isBlank(field.value) && field.type_id !== SETTINGS_TYPE_ID @@ -130,7 +130,7 @@ const Theme = RestModel.extend({ } }, - @computed("childThemes.@each") + @computed("childThemes.[]") child_theme_ids(childThemes) { if (childThemes) { return childThemes.map(theme => Ember.get(theme, "id")); diff --git a/app/assets/javascripts/admin/models/watched-word.js.es6 b/app/assets/javascripts/admin/models/watched-word.js.es6 index cdf5e29e8a..d7d781227d 100644 --- a/app/assets/javascripts/admin/models/watched-word.js.es6 +++ b/app/assets/javascripts/admin/models/watched-word.js.es6 @@ -21,7 +21,7 @@ const WatchedWord = Discourse.Model.extend({ WatchedWord.reopenClass({ findAll() { - return ajax("/admin/logs/watched_words").then(list => { + return ajax("/admin/logs/watched_words.json").then(list => { const actions = {}; list.words.forEach(s => { if (!actions[s.action]) { diff --git a/app/assets/javascripts/admin/templates/api-keys.hbs b/app/assets/javascripts/admin/templates/api-keys.hbs index e256e127cf..1b85311b6e 100644 --- a/app/assets/javascripts/admin/templates/api-keys.hbs +++ b/app/assets/javascripts/admin/templates/api-keys.hbs @@ -19,8 +19,8 @@ {{/if}} - {{d-button class="btn-default" action="regenerateKey" actionParam=k icon="undo" label='admin.api.regenerate'}} - {{d-button class="btn-default" action="revokeKey" actionParam=k icon="times" label='admin.api.revoke'}} + {{d-button class="btn-default" action=(action "regenerateKey") actionParam=k icon="undo" label='admin.api.regenerate'}} + {{d-button class="btn-default" action=(action "revokeKey") actionParam=k icon="times" label='admin.api.revoke'}} {{/each}} diff --git a/app/assets/javascripts/admin/templates/backups-index.hbs b/app/assets/javascripts/admin/templates/backups-index.hbs index 3e69cbff2c..46a0dafaf2 100644 --- a/app/assets/javascripts/admin/templates/backups-index.hbs +++ b/app/assets/javascripts/admin/templates/backups-index.hbs @@ -1,14 +1,14 @@
{{#if localBackupStorage}} - {{resumable-upload target="/admin/backups/upload" success="uploadSuccess" error="uploadError" uploadText=uploadLabel title="admin.backups.upload.title" class="btn-default"}} + {{resumable-upload target="/admin/backups/upload" success=(route-action "uploadSuccess") error=(route-action "uploadError") uploadText=uploadLabel title="admin.backups.upload.title" class="btn-default"}} {{else}} - {{backup-uploader done="remoteUploadSuccess"}} + {{backup-uploader done=(route-action "remoteUploadSuccess")}} {{/if}} {{#if site.isReadOnly}} - {{d-button class="btn-default" icon="far-eye" action="toggleReadOnlyMode" disabled=status.isOperationRunning title="admin.backups.read_only.disable.title" label="admin.backups.read_only.disable.label"}} + {{d-button class="btn-default" icon="far-eye" action=(action "toggleReadOnlyMode") disabled=status.isOperationRunning title="admin.backups.read_only.disable.title" label="admin.backups.read_only.disable.label"}} {{else}} - {{d-button class="btn-default" icon="far-eye" action="toggleReadOnlyMode" disabled=status.isOperationRunning title="admin.backups.read_only.enable.title" label="admin.backups.read_only.enable.label"}} + {{d-button class="btn-default" icon="far-eye" action=(action "toggleReadOnlyMode") disabled=status.isOperationRunning title="admin.backups.read_only.enable.title" label="admin.backups.read_only.enable.label"}} {{/if}}
@@ -25,17 +25,17 @@ diff --git a/app/assets/javascripts/admin/templates/backups.hbs b/app/assets/javascripts/admin/templates/backups.hbs index 21b104a9b2..38810c41ec 100644 --- a/app/assets/javascripts/admin/templates/backups.hbs +++ b/app/assets/javascripts/admin/templates/backups.hbs @@ -7,7 +7,7 @@ {{plugin-outlet name="downloader" tagName=""}}
{{#if model.canRollback}} - {{d-button action="rollback" + {{d-button action=(route-action "rollback") class="btn-default btn-rollback" label="admin.backups.operations.rollback.label" title="admin.backups.operations.rollback.title" @@ -15,13 +15,13 @@ disabled=rollbackDisabled}} {{/if}} {{#if model.isOperationRunning}} - {{d-button action="cancelOperation" + {{d-button action=(route-action "cancelOperation") class="btn-danger" title="admin.backups.operations.cancel.title" label="admin.backups.operations.cancel.label" icon="times"}} {{else}} - {{d-button action="showStartBackupModal" + {{d-button action=(route-action "showStartBackupModal") class="btn-primary" title="admin.backups.operations.backup.title" label="admin.backups.operations.backup.label" diff --git a/app/assets/javascripts/admin/templates/components/admin-report-table.hbs b/app/assets/javascripts/admin/templates/components/admin-report-table.hbs index 2e6c528acc..6011407673 100644 --- a/app/assets/javascripts/admin/templates/components/admin-report-table.hbs +++ b/app/assets/javascripts/admin/templates/components/admin-report-table.hbs @@ -55,7 +55,7 @@ {{#each pages as |pageState|}} {{d-button translatedLabel=pageState.page - action="changePage" + action=(action "changePage") actionParam=pageState.index class=pageState.class}} {{/each}} diff --git a/app/assets/javascripts/admin/templates/components/admin-report.hbs b/app/assets/javascripts/admin/templates/components/admin-report.hbs index 69ec7a3fd8..1c82ee5a20 100644 --- a/app/assets/javascripts/admin/templates/components/admin-report.hbs +++ b/app/assets/javascripts/admin/templates/components/admin-report.hbs @@ -115,7 +115,7 @@
{{#each displayedModes as |displayedMode|}} {{d-button - action="changeMode" + action=(action "changeMode") actionParam=displayedMode.mode class=displayedMode.cssClass icon=displayedMode.icon}} @@ -178,7 +178,7 @@
{{d-button class="btn-default export-csv-btn" - action="exportCsv" + action=(action "exportCsv") label="admin.export_csv.button_text" icon="download"}}
@@ -190,7 +190,7 @@
{{d-button class="refresh-report-btn btn-primary" - action="refreshReport" + action=(action "refreshReport") label="admin.dashboard.reports.refresh_report" icon="refresh"}}
diff --git a/app/assets/javascripts/admin/templates/components/admin-user-field-item.hbs b/app/assets/javascripts/admin/templates/components/admin-user-field-item.hbs index 0074c99f1a..46c6ac46ae 100644 --- a/app/assets/javascripts/admin/templates/components/admin-user-field-item.hbs +++ b/app/assets/javascripts/admin/templates/components/admin-user-field-item.hbs @@ -34,8 +34,8 @@ {{/admin-form-row}} {{#admin-form-row}} - {{d-button action="save" class="btn-primary" icon="check" label="admin.user_fields.save"}} - {{d-button action="cancel" class="btn-danger" icon="times" label="admin.user_fields.cancel"}} + {{d-button action=(action "save") class="btn-primary" icon="check" label="admin.user_fields.save"}} + {{d-button action=(action "cancel") class="btn-danger" icon="times" label="admin.user_fields.cancel"}} {{/admin-form-row}} {{else}}
@@ -46,10 +46,11 @@
{{fieldName}}
- {{d-button action="edit" class="btn-default" icon="pencil" label="admin.user_fields.edit"}} - {{d-button action="destroy" class="btn-danger" icon="trash-o" label="admin.user_fields.delete"}} - {{d-button action="moveUp" class="btn-default" icon="arrow-up" disabled=cantMoveUp}} - {{d-button action="moveDown" class="btn-default" icon="arrow-down" disabled=cantMoveDown}} + {{d-button action=(action "edit") class="btn-default" icon="pencil" label="admin.user_fields.edit"}} + + {{d-button action=destroyAction actionParam=userField class="btn-danger" icon="trash-o" label="admin.user_fields.delete"}} + {{d-button action=moveUpAction actionParam=userField class="btn-default" icon="arrow-up" disabled=cantMoveUp}} + {{d-button action=moveDownAction actionParam=userField class="btn-default" icon="arrow-down" disabled=cantMoveDown}}
{{flags}}
diff --git a/app/assets/javascripts/admin/templates/components/admin-web-hook-event.hbs b/app/assets/javascripts/admin/templates/components/admin-web-hook-event.hbs index 026b6daae2..a98f8684f0 100644 --- a/app/assets/javascripts/admin/templates/components/admin-web-hook-event.hbs +++ b/app/assets/javascripts/admin/templates/components/admin-web-hook-event.hbs @@ -5,13 +5,13 @@
{{createdAt}}
{{completion}}
- {{d-button icon='ellipsis-v' action='toggleRequest' label='admin.web_hooks.events.request'}} - {{d-button icon='ellipsis-v' action='toggleResponse' label='admin.web_hooks.events.response'}} - {{d-button icon='refresh' action='redeliver' label='admin.web_hooks.events.redeliver'}} + {{d-button icon="ellipsis-v" action=(action "toggleRequest") label="admin.web_hooks.events.request"}} + {{d-button icon="ellipsis-v" action=(action "toggleResponse") label="admin.web_hooks.events.response"}} + {{d-button icon="refresh" action=(action "redeliver") label="admin.web_hooks.events.redeliver"}}
{{#if expandDetails}}
-

{{i18n 'admin.web_hooks.events.headers'}}

+

{{i18n "admin.web_hooks.events.headers"}}

{{headers}}

{{bodyLabel}}

{{body}}
diff --git a/app/assets/javascripts/admin/templates/components/embeddable-host.hbs b/app/assets/javascripts/admin/templates/components/embeddable-host.hbs index 07f635bcf6..0c5acf8322 100644 --- a/app/assets/javascripts/admin/templates/components/embeddable-host.hbs +++ b/app/assets/javascripts/admin/templates/components/embeddable-host.hbs @@ -1,23 +1,23 @@ {{#if editing}}
{{else}} @@ -25,7 +25,7 @@ -{{/if}} \ No newline at end of file +{{/if}} diff --git a/app/assets/javascripts/admin/templates/components/flagged-post.hbs b/app/assets/javascripts/admin/templates/components/flagged-post.hbs index 0ef0f3d8a4..98d0120bd6 100644 --- a/app/assets/javascripts/admin/templates/components/flagged-post.hbs +++ b/app/assets/javascripts/admin/templates/components/flagged-post.hbs @@ -78,14 +78,14 @@ {{d-button title="admin.flags.disagree_flag_unhide_post_title" class="btn-default disagree-flag" - action="disagree" + action=(action "disagree") icon="thumbs-o-down" label="admin.flags.disagree_flag_unhide_post"}} {{else}} {{d-button title="admin.flags.disagree_flag_title" class="btn-default disagree-flag" - action="disagree" + action=(action "disagree") icon="thumbs-o-down" label="admin.flags.disagree_flag"}} {{/if}} @@ -93,7 +93,7 @@ {{d-button class="btn-default defer-flag" title="admin.flags.ignore_flag_title" - action="defer" + action=(action "defer") icon="external-link" label="admin.flags.ignore_flag"}} diff --git a/app/assets/javascripts/admin/templates/components/inline-edit-checkbox.hbs b/app/assets/javascripts/admin/templates/components/inline-edit-checkbox.hbs index 3a651ad0df..7c8e0f280e 100644 --- a/app/assets/javascripts/admin/templates/components/inline-edit-checkbox.hbs +++ b/app/assets/javascripts/admin/templates/components/inline-edit-checkbox.hbs @@ -3,6 +3,6 @@ {{label}} {{#if changed}} - {{d-button action="finished" class="btn-primary btn-small submit-edit" icon="check"}} - {{d-button action="cancelled" class="btn-small cancel-edit" icon="times"}} + {{d-button action=(action "finished") class="btn-primary btn-small submit-edit" icon="check"}} + {{d-button action=(action "cancelled") class="btn-small cancel-edit" icon="times"}} {{/if}} diff --git a/app/assets/javascripts/admin/templates/components/permalink-form.hbs b/app/assets/javascripts/admin/templates/components/permalink-form.hbs index dc37c00991..7357ede526 100644 --- a/app/assets/javascripts/admin/templates/components/permalink-form.hbs +++ b/app/assets/javascripts/admin/templates/components/permalink-form.hbs @@ -2,4 +2,4 @@ {{text-field value=url disabled=formSubmitted class="permalink-url" placeholderKey="admin.permalink.url" autocorrect="off" autocapitalize="off"}} {{combo-box content=permalinkTypes value=permalinkType}} {{text-field value=permalink_type_value disabled=formSubmitted class="external-url" placeholderKey=permalinkTypePlaceholder autocorrect="off" autocapitalize="off"}} -{{d-button class="btn-default" action="submit" disabled=formSubmitted label="admin.permalink.form.add"}} +{{d-button class="btn-default" action=(action "submit") disabled=formSubmitted label="admin.permalink.form.add"}} diff --git a/app/assets/javascripts/admin/templates/components/save-controls.hbs b/app/assets/javascripts/admin/templates/components/save-controls.hbs index 00ac91331f..d6974ea108 100644 --- a/app/assets/javascripts/admin/templates/components/save-controls.hbs +++ b/app/assets/javascripts/admin/templates/components/save-controls.hbs @@ -1,4 +1,4 @@ -{{d-button action="saveChanges" disabled=buttonDisabled label=savingText class="btn-primary save-changes"}} +{{d-button action=action disabled=buttonDisabled label=savingText class="btn-primary save-changes"}} {{yield}}
{{#if saved}} diff --git a/app/assets/javascripts/admin/templates/components/screened-ip-address-form.hbs b/app/assets/javascripts/admin/templates/components/screened-ip-address-form.hbs index 82fb3162e2..6bc99fdbd1 100644 --- a/app/assets/javascripts/admin/templates/components/screened-ip-address-form.hbs +++ b/app/assets/javascripts/admin/templates/components/screened-ip-address-form.hbs @@ -1,4 +1,4 @@ {{i18n 'admin.logs.screened_ips.form.label'}} {{text-field value=ip_address disabled=formSubmitted class="ip-address-input" placeholderKey="admin.logs.screened_ips.form.ip_address" autocorrect="off" autocapitalize="off"}} {{combo-box content=actionNames value=actionName}} -{{d-button class="btn-default" action="submit" disabled=formSubmitted label="admin.logs.screened_ips.form.add"}} +{{d-button class="btn-default" action=(action "submit") disabled=formSubmitted label="admin.logs.screened_ips.form.add"}} diff --git a/app/assets/javascripts/admin/templates/components/secret-value-list.hbs b/app/assets/javascripts/admin/templates/components/secret-value-list.hbs index a2f44f1553..5674d6d7d7 100644 --- a/app/assets/javascripts/admin/templates/components/secret-value-list.hbs +++ b/app/assets/javascripts/admin/templates/components/secret-value-list.hbs @@ -2,7 +2,7 @@
{{#each collection as |value index|}}
- {{d-button action="removeValue" + {{d-button action=(action "removeValue") actionParam=value icon="times" class="remove-value-btn btn-small"}} @@ -16,7 +16,7 @@
{{text-field value=newKey class="new-value-input key" placeholder=setting.placeholder.key}} {{input type="password" value=newSecret class="new-value-input secret" placeholder=setting.placeholder.value}} - {{d-button action="addValue" + {{d-button action=(action "addValue") icon="plus" class="add-value-btn btn-small"}}
diff --git a/app/assets/javascripts/admin/templates/components/site-setting.hbs b/app/assets/javascripts/admin/templates/components/site-setting.hbs index a832080ea7..03f6342cda 100644 --- a/app/assets/javascripts/admin/templates/components/site-setting.hbs +++ b/app/assets/javascripts/admin/templates/components/site-setting.hbs @@ -6,12 +6,12 @@
{{#if dirty}}
- {{d-button class="ok" action="save" icon="check"}} - {{d-button class="cancel" action="cancel" icon="times"}} + {{d-button class="ok" action=(action "save") icon="check"}} + {{d-button class="cancel" action=(action "cancel") icon="times"}}
{{else if setting.overridden}} {{#if setting.secret}} - {{d-button action="toggleSecret" icon="eye-slash"}} + {{d-button action=(action "toggleSecret") icon="eye-slash"}} {{/if}} - {{d-button class="btn-default undo" action="resetDefault" icon="undo" label="admin.settings.reset"}} + {{d-button class="btn-default undo" action=(action "resetDefault") icon="undo" label="admin.settings.reset"}} {{/if}} diff --git a/app/assets/javascripts/admin/templates/components/site-settings/uploaded-image-list.hbs b/app/assets/javascripts/admin/templates/components/site-settings/uploaded-image-list.hbs index 3de2abc489..86750e634d 100644 --- a/app/assets/javascripts/admin/templates/components/site-settings/uploaded-image-list.hbs +++ b/app/assets/javascripts/admin/templates/components/site-settings/uploaded-image-list.hbs @@ -1,2 +1,2 @@ -{{d-button label="admin.site_settings.uploaded_image_list.label" action="showUploadModal" actionParam=(hash value=value setting=setting)}} +{{d-button label="admin.site_settings.uploaded_image_list.label" action=(action "showUploadModal") actionParam=(hash value=value setting=setting)}}
{{{unbound setting.description}}}
diff --git a/app/assets/javascripts/admin/templates/components/site-text-summary.hbs b/app/assets/javascripts/admin/templates/components/site-text-summary.hbs index 60cfc9843d..c0c7063309 100644 --- a/app/assets/javascripts/admin/templates/components/site-text-summary.hbs +++ b/app/assets/javascripts/admin/templates/components/site-text-summary.hbs @@ -1,4 +1,4 @@ -{{d-button label="admin.site_text.edit" class='btn-default edit' action="edit"}} +{{d-button label="admin.site_text.edit" class='btn-default edit' action=editAction actionParam=siteText}}

{{siteText.id}}

{{siteText.value}}
diff --git a/app/assets/javascripts/admin/templates/components/tags-uploader.hbs b/app/assets/javascripts/admin/templates/components/tags-uploader.hbs index dab22c2ed7..db3c9aa301 100644 --- a/app/assets/javascripts/admin/templates/components/tags-uploader.hbs +++ b/app/assets/javascripts/admin/templates/components/tags-uploader.hbs @@ -1,6 +1,6 @@ - - {{i18n 'tagging.upload_instructions'}} + +{{i18n 'tagging.upload_instructions'}} diff --git a/app/assets/javascripts/admin/templates/components/value-list.hbs b/app/assets/javascripts/admin/templates/components/value-list.hbs index d74b600ca4..372de2ac1d 100644 --- a/app/assets/javascripts/admin/templates/components/value-list.hbs +++ b/app/assets/javascripts/admin/templates/components/value-list.hbs @@ -2,7 +2,7 @@
{{#each collection as |value index|}}
- {{d-button action="removeValue" + {{d-button action=(action "removeValue") actionParam=value icon="times" class="btn-default remove-value-btn btn-small"}} diff --git a/app/assets/javascripts/admin/templates/components/watched-word-form.hbs b/app/assets/javascripts/admin/templates/components/watched-word-form.hbs index 2b109e7385..13454f21a5 100644 --- a/app/assets/javascripts/admin/templates/components/watched-word-form.hbs +++ b/app/assets/javascripts/admin/templates/components/watched-word-form.hbs @@ -1,6 +1,6 @@ {{i18n 'admin.watched_words.form.label'}} {{text-field value=word disabled=formSubmitted class="watched-word-input" autocorrect="off" autocapitalize="off" placeholderKey=placeholderKey}} -{{d-button class="btn-default" action="submit" disabled=formSubmitted label="admin.watched_words.form.add"}} +{{d-button class="btn-default" action=(action "submit") disabled=formSubmitted label="admin.watched_words.form.add"}} {{#if showMessage}} {{message}} diff --git a/app/assets/javascripts/admin/templates/customize-email-templates-edit.hbs b/app/assets/javascripts/admin/templates/customize-email-templates-edit.hbs index c23e3caef0..a1e3910e11 100644 --- a/app/assets/javascripts/admin/templates/customize-email-templates-edit.hbs +++ b/app/assets/javascripts/admin/templates/customize-email-templates-edit.hbs @@ -10,9 +10,9 @@ {{d-editor value=buffered.body}} - {{#save-controls model=emailTemplate action="saveChanges" saved=saved}} + {{#save-controls model=emailTemplate action=(action "saveChanges") saved=saved}} {{#if emailTemplate.can_revert}} - {{d-button action="revertChanges" label="admin.customize.email_templates.revert"}} + {{d-button action=(action "revertChanges") label="admin.customize.email_templates.revert"}} {{/if}} {{/save-controls}}
diff --git a/app/assets/javascripts/admin/templates/customize-themes-edit.hbs b/app/assets/javascripts/admin/templates/customize-themes-edit.hbs index 0ec171dfa8..4807209639 100644 --- a/app/assets/javascripts/admin/templates/customize-themes-edit.hbs +++ b/app/assets/javascripts/admin/templates/customize-themes-edit.hbs @@ -69,7 +69,7 @@
- {{#d-button action="save" disabled=saveDisabled class='btn-primary'}} + {{#d-button action=(action "save") disabled=saveDisabled class='btn-primary'}} {{saveButtonText}} {{/d-button}}
diff --git a/app/assets/javascripts/admin/templates/customize-themes-show.hbs b/app/assets/javascripts/admin/templates/customize-themes-show.hbs index fd540a694e..60615f6e14 100644 --- a/app/assets/javascripts/admin/templates/customize-themes-show.hbs +++ b/app/assets/javascripts/admin/templates/customize-themes-show.hbs @@ -2,8 +2,8 @@
{{#if editingName}} {{text-field value=model.name autofocus="true"}} - {{d-button action="finishedEditingName" class="btn-primary btn-small submit-edit" icon="check"}} - {{d-button action="cancelEditingName" class="btn-small cancel-edit" icon="times"}} + {{d-button action=(action "finishedEditingName") class="btn-primary btn-small submit-edit" icon="check"}} + {{d-button action=(action "cancelEditingName") class="btn-small cancel-edit" icon="times"}} {{else}} {{model.name}} {{d-icon "pencil"}} {{/if}} @@ -36,8 +36,8 @@ {{#unless model.component}}
- {{inline-edit-checkbox action="applyDefault" labelKey="admin.customize.theme.is_default" checked=model.default}} - {{inline-edit-checkbox action="applyUserSelectable" labelKey="admin.customize.theme.user_selectable" checked=model.user_selectable}} + {{inline-edit-checkbox action=(action "applyDefault") labelKey="admin.customize.theme.is_default" checked=model.default}} + {{inline-edit-checkbox action=(action "applyUserSelectable") labelKey="admin.customize.theme.user_selectable" checked=model.user_selectable}}
@@ -49,8 +49,8 @@ value=colorSchemeId icon="paint-brush"}} {{#if colorSchemeChanged}} - {{d-button action="changeScheme" class="btn-primary btn-small submit-edit" icon="check"}} - {{d-button action="cancelChangeScheme" class="btn-default btn-small cancel-edit" icon="times"}} + {{d-button action=(action "changeScheme") class="btn-primary btn-small submit-edit" icon="check"}} + {{d-button action=(action "cancelChangeScheme") class="btn-default btn-small cancel-edit" icon="times"}} {{/if}}
{{#link-to 'adminCustomize.colors' class="btn btn-default edit"}}{{i18n 'admin.customize.colors.edit'}}{{/link-to}} @@ -74,13 +74,13 @@ {{#if model.remote_theme}} {{#if model.remote_theme.commits_behind}} - {{#d-button action="updateToLatest" icon="download" class='btn-primary'}}{{i18n "admin.customize.theme.update_to_latest"}}{{/d-button}} + {{#d-button action=(action "updateToLatest") icon="download" class='btn-primary'}}{{i18n "admin.customize.theme.update_to_latest"}}{{/d-button}} {{else}} - {{#d-button action="checkForThemeUpdates" icon="refresh" class="btn-default"}}{{i18n "admin.customize.theme.check_for_updates"}}{{/d-button}} + {{#d-button action=(action "checkForThemeUpdates") icon="refresh" class="btn-default"}}{{i18n "admin.customize.theme.check_for_updates"}}{{/d-button}} {{/if}} {{/if}} - {{#d-button action="editTheme" class="btn btn-default edit"}}{{i18n 'admin.customize.theme.edit_css_html'}}{{/d-button}} + {{#d-button action=(action "editTheme") class="btn btn-default edit"}}{{i18n 'admin.customize.theme.edit_css_html'}}{{/d-button}} {{#if model.remote_theme}} {{#if updatingRemote}} @@ -119,7 +119,7 @@
  • ${{upload.name}}: {{upload.filename}} - {{d-button action="removeUpload" actionParam=upload class="second btn-default btn-small cancel-edit" icon="times"}} + {{d-button action=(action "removeUpload") actionParam=upload class="second btn-default btn-small cancel-edit" icon="times"}}
  • {{/each}} @@ -127,7 +127,7 @@ {{else}}
    {{i18n "admin.customize.theme.no_uploads"}}
    {{/if}} - {{#d-button action="addUploadModal" class="btn-default" icon="plus"}}{{i18n "admin.customize.theme.add"}}{{/d-button}} + {{#d-button action=(action "addUploadModal") class="btn-default" icon="plus"}}{{i18n "admin.customize.theme.add"}}{{/d-button}}
    {{#if hasSettings}} @@ -147,14 +147,14 @@ {{#if model.childThemes.length}}
      {{#each model.childThemes as |child|}} -
    • {{#link-to 'adminCustomizeThemes.show' child replace=true class='col'}}{{child.name}}{{/link-to}} {{d-button action="removeChildTheme" actionParam=child class="btn-default btn-small cancel-edit col" icon="times"}}
    • +
    • {{#link-to 'adminCustomizeThemes.show' child replace=true class='col'}}{{child.name}}{{/link-to}} {{d-button action=(action "removeChildTheme") actionParam=child class="btn-default btn-small cancel-edit col" icon="times"}}
    • {{/each}}
    {{/if}} {{#if selectableChildThemes}}
    {{combo-box forceEscape=true filterable=true content=selectableChildThemes value=selectedChildThemeId none="admin.customize.theme.select_component"}} - {{#d-button action="addChildTheme" icon="plus" disabled=addButtonDisabled class="btn-default add-component-button"}}{{i18n "admin.customize.theme.add"}}{{/d-button}} + {{#d-button action=(action "addChildTheme") icon="plus" disabled=addButtonDisabled class="btn-default add-component-button"}}{{i18n "admin.customize.theme.add"}}{{/d-button}}
    {{/if}}
    @@ -163,6 +163,6 @@ {{d-icon 'desktop'}}{{i18n 'admin.customize.theme.preview'}} {{d-icon "download"}} {{i18n 'admin.export_json.button_text'}} - {{d-button action="switchType" label="admin.customize.theme.convert" icon=convertIcon class="btn-default btn-normal" title=convertTooltip}} - {{d-button action="destroy" label="admin.customize.delete" icon="trash" class="btn-danger"}} + {{d-button action=(action "switchType") label="admin.customize.theme.convert" icon=convertIcon class="btn-default btn-normal" title=convertTooltip}} + {{d-button action=(action "destroy") label="admin.customize.delete" icon="trash" class="btn-danger"}}
    diff --git a/app/assets/javascripts/admin/templates/customize-themes.hbs b/app/assets/javascripts/admin/templates/customize-themes.hbs index 82985568c2..268febd435 100644 --- a/app/assets/javascripts/admin/templates/customize-themes.hbs +++ b/app/assets/javascripts/admin/templates/customize-themes.hbs @@ -3,8 +3,8 @@
    - {{d-button label="admin.customize.new" icon="plus" action="showCreateModal" class="btn-primary"}} - {{d-button action="importModal" icon="upload" label="admin.customize.import" class="btn-default"}} + {{d-button label="admin.customize.new" icon="plus" action=(route-action "showCreateModal") class="btn-primary"}} + {{d-button action=(route-action "importModal") icon="upload" label="admin.customize.import" class="btn-default"}}
    {{themes-list themes=fullThemes components=childThemes currentTab=currentTab}} diff --git a/app/assets/javascripts/admin/templates/dashboard-problems.hbs b/app/assets/javascripts/admin/templates/dashboard-problems.hbs index c0c6b9ca0b..a945b8d536 100644 --- a/app/assets/javascripts/admin/templates/dashboard-problems.hbs +++ b/app/assets/javascripts/admin/templates/dashboard-problems.hbs @@ -19,7 +19,7 @@

    {{i18n 'admin.dashboard.last_checked'}}: {{problemsTimestamp}} - {{d-button action="refreshProblems" class="btn-default btn-small" icon="refresh" label="admin.dashboard.refresh_problems"}} + {{d-button action=(action "refreshProblems") class="btn-default btn-small" icon="refresh" label="admin.dashboard.refresh_problems"}}

    {{/conditional-loading-section}} diff --git a/app/assets/javascripts/admin/templates/dashboard_next_general.hbs b/app/assets/javascripts/admin/templates/dashboard_next_general.hbs index a9bfd44c19..000fcf152c 100644 --- a/app/assets/javascripts/admin/templates/dashboard_next_general.hbs +++ b/app/assets/javascripts/admin/templates/dashboard_next_general.hbs @@ -9,7 +9,7 @@ {{i18n "admin.dashboard.community_health"}} - {{period-chooser period=period action="changePeriod" content=availablePeriods fullDay=true}} + {{period-chooser period=period action=(action "changePeriod") content=availablePeriods fullDay=true}}
    diff --git a/app/assets/javascripts/admin/templates/dashboard_next_moderation.hbs b/app/assets/javascripts/admin/templates/dashboard_next_moderation.hbs index 8959bac021..300b7c36e7 100644 --- a/app/assets/javascripts/admin/templates/dashboard_next_moderation.hbs +++ b/app/assets/javascripts/admin/templates/dashboard_next_moderation.hbs @@ -10,7 +10,7 @@ {{period-chooser period=period - action="changePeriod" + action=(action "changePeriod") content=availablePeriods fullDay=true}}
    diff --git a/app/assets/javascripts/admin/templates/email-bounced.hbs b/app/assets/javascripts/admin/templates/email-bounced.hbs index 9ebbb7d316..dc9c2e2b7f 100644 --- a/app/assets/javascripts/admin/templates/email-bounced.hbs +++ b/app/assets/javascripts/admin/templates/email-bounced.hbs @@ -1,4 +1,4 @@ -{{#load-more selector=".email-list tr" action="loadMore"}} +{{#load-more selector=".email-list tr" action=(action "loadMore")}}
    {{d-button class="btn-default download" - action="download" + action=(action "download") actionParam=backup icon="download" title="admin.backups.operations.download.title" label="admin.backups.operations.download.label"}} {{#if status.isOperationRunning}} - {{d-button icon="far-trash-alt" 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.restoreDisabled class="btn-default" title=restoreTitle label="admin.backups.operations.restore.label"}} + {{d-button icon="far-trash-alt" action=(route-action "destroyBackup") actionParam=backup class="btn-danger" disabled="true" title="admin.backups.operations.is_running"}} + {{d-button icon="play" action=(route-action "startRestore") actionParam=backup disabled=status.restoreDisabled class="btn-default" title=restoreTitle label="admin.backups.operations.restore.label"}} {{else}} - {{d-button icon="far-trash-alt" action="destroyBackup" actionParam=backup class="btn-danger" title="admin.backups.operations.destroy.title"}} - {{d-button icon="play" action="startRestore" actionParam=backup disabled=status.restoreDisabled class="btn-default" title=restoreTitle label="admin.backups.operations.restore.label"}} + {{d-button icon="far-trash-alt" action=(route-action "destroyBackup") actionParam=backup class="btn-danger" title="admin.backups.operations.destroy.title"}} + {{d-button icon="play" action=(route-action "startRestore") actionParam=backup disabled=status.restoreDisabled class="btn-default" title=restoreTitle label="admin.backups.operations.restore.label"}} {{/if}}
    {{i18n "admin.embedding.host"}}
    - {{input value=buffered.host placeholder="example.com" enter="save" class="host-name"}} + {{input value=buffered.host placeholder="example.com" enter=(action "save") class="host-name"}}
    {{i18n "admin.embedding.class_name"}}
    - {{input value=buffered.class_name placeholder="class" enter="save" class="class-name"}} + {{input value=buffered.class_name placeholder="class" enter=(action "save") class="class-name"}}
    {{i18n "admin.embedding.path_whitelist"}}
    - {{input value=buffered.path_whitelist placeholder="/blog/.*" enter="save" class="path-whitelist"}} + {{input value=buffered.path_whitelist placeholder="/blog/.*" enter=(action "save") class="path-whitelist"}}
    {{i18n "admin.embedding.category"}}
    {{category-chooser value=categoryId class="small"}}
    - {{d-button icon="check" action="save" class="btn-primary" disabled=cantSave}} - {{d-button icon="times" action="cancel" class="btn-danger" disabled=host.isSaving}} + {{d-button icon="check" action=(action "save") class="btn-primary" disabled=cantSave}} + {{d-button icon="times" action=(action "cancel") class="btn-danger" disabled=host.isSaving}}
    {{i18n "admin.embedding.host"}}
    {{host.host}}
    {{i18n "admin.embedding.path_whitelist"}}
    {{host.path_whitelist}}
    {{i18n "admin.embedding.category"}}
    {{category-badge host.category}}
    - {{d-button icon="pencil" action="edit"}} - {{d-button icon="trash-o" action="delete" class='btn-danger'}} + {{d-button icon="pencil" action=(action "edit")}} + {{d-button icon="trash-o" action=(action "delete") class='btn-danger'}}
    diff --git a/app/assets/javascripts/admin/templates/email-received.hbs b/app/assets/javascripts/admin/templates/email-received.hbs index c1e679b179..8a164e3ff8 100644 --- a/app/assets/javascripts/admin/templates/email-received.hbs +++ b/app/assets/javascripts/admin/templates/email-received.hbs @@ -1,4 +1,4 @@ -{{#load-more selector=".email-list tr" action="loadMore"}} +{{#load-more selector=".email-list tr" action=(action "loadMore")}} diff --git a/app/assets/javascripts/admin/templates/email-rejected.hbs b/app/assets/javascripts/admin/templates/email-rejected.hbs index 310a5bba16..43744a919b 100644 --- a/app/assets/javascripts/admin/templates/email-rejected.hbs +++ b/app/assets/javascripts/admin/templates/email-rejected.hbs @@ -1,4 +1,4 @@ -{{#load-more selector=".email-list tr" action="loadMore"}} +{{#load-more selector=".email-list tr" action=(action "loadMore")}} diff --git a/app/assets/javascripts/admin/templates/email-sent.hbs b/app/assets/javascripts/admin/templates/email-sent.hbs index a8b2d57fae..83bb5b56d8 100644 --- a/app/assets/javascripts/admin/templates/email-sent.hbs +++ b/app/assets/javascripts/admin/templates/email-sent.hbs @@ -1,4 +1,4 @@ -{{#load-more selector=".email-list tr" action="loadMore"}} +{{#load-more selector=".email-list tr" action=(action "loadMore")}} diff --git a/app/assets/javascripts/admin/templates/email-skipped.hbs b/app/assets/javascripts/admin/templates/email-skipped.hbs index ce5bbaf874..0ca7fed0a7 100644 --- a/app/assets/javascripts/admin/templates/email-skipped.hbs +++ b/app/assets/javascripts/admin/templates/email-skipped.hbs @@ -1,4 +1,4 @@ -{{#load-more selector=".email-list tr" action="loadMore"}} +{{#load-more selector=".email-list tr" action=(action "loadMore")}} diff --git a/app/assets/javascripts/admin/templates/embedding.hbs b/app/assets/javascripts/admin/templates/embedding.hbs index d9e97c1c5a..45f86da9f7 100644 --- a/app/assets/javascripts/admin/templates/embedding.hbs +++ b/app/assets/javascripts/admin/templates/embedding.hbs @@ -10,7 +10,7 @@ {{#each embedding.embeddable_hosts as |host|}} - {{embeddable-host host=host deleteHost="deleteHost"}} + {{embeddable-host host=host deleteHost=(action "deleteHost")}} {{/each}} @@ -18,7 +18,7 @@

    {{i18n "admin.embedding.get_started"}}

    {{/if}} - {{d-button label="admin.embedding.add_host" action="addHost" icon="plus" class="btn-primary add-host"}} + {{d-button label="admin.embedding.add_host" action=(action "addHost") icon="plus" class="btn-primary add-host"}}
    {{#if showSecondary}} @@ -69,7 +69,7 @@
    {{d-button label="admin.embedding.save" - action="saveChanges" + action=(action "saveChanges") class="btn-primary embed-save" disabled=embedding.isSaving}} diff --git a/app/assets/javascripts/admin/templates/emojis.hbs b/app/assets/javascripts/admin/templates/emojis.hbs index 4cf568fd45..479b7421d6 100644 --- a/app/assets/javascripts/admin/templates/emojis.hbs +++ b/app/assets/javascripts/admin/templates/emojis.hbs @@ -3,7 +3,7 @@

    {{i18n 'admin.emoji.help'}}

    -

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

    +

    {{emoji-uploader done=(action "emojiUploaded")}}

    {{#if sortedEmojis}}
    diff --git a/app/assets/javascripts/admin/templates/logs/screened-emails.hbs b/app/assets/javascripts/admin/templates/logs/screened-emails.hbs index 2d96b2a38e..532e68c29c 100644 --- a/app/assets/javascripts/admin/templates/logs/screened-emails.hbs +++ b/app/assets/javascripts/admin/templates/logs/screened-emails.hbs @@ -30,7 +30,7 @@
    {{i18n 'admin.logs.created_at'}}
    {{age-with-tooltip item.created_at}} {{item.ip_address}} - {{d-button action="clearBlock" actionParam=item icon="check" label="admin.logs.screened_emails.actions.allow"}} + {{d-button action=(action "clearBlock") actionParam=item icon="check" label="admin.logs.screened_emails.actions.allow"}} {{/each}} diff --git a/app/assets/javascripts/admin/templates/logs/screened-ip-addresses.hbs b/app/assets/javascripts/admin/templates/logs/screened-ip-addresses.hbs index ea7bc1da7d..68a7bbc568 100644 --- a/app/assets/javascripts/admin/templates/logs/screened-ip-addresses.hbs +++ b/app/assets/javascripts/admin/templates/logs/screened-ip-addresses.hbs @@ -3,10 +3,10 @@
    {{text-field value=filter class="ip-address-input" placeholderKey="admin.logs.screened_ips.form.filter" autocorrect="off" autocapitalize="off"}} - {{d-button class="btn-default" action="rollUp" title="admin.logs.screened_ips.roll_up.title" label="admin.logs.screened_ips.roll_up.text"}} - {{d-button class="btn-default" action="exportScreenedIpList" icon="download" title="admin.export_csv.button_title.screened_ip" label="admin.export_csv.button_text"}} + {{d-button class="btn-default" action=(action "rollUp") title="admin.logs.screened_ips.roll_up.title" label="admin.logs.screened_ips.roll_up.text"}} + {{d-button class="btn-default" action=(action "exportScreenedIpList") icon="download" title="admin.export_csv.button_title.screened_ip" label="admin.export_csv.button_text"}}
    - {{screened-ip-address-form action="recordAdded"}} + {{screened-ip-address-form action=(action "recordAdded")}}
    @@ -57,15 +57,15 @@ {{#unless item.editing}} - {{d-button class="btn-default" action="destroy" actionParam=item icon="trash-o" class="btn-danger"}} - {{d-button class="btn-default"action="edit" actionParam=item icon="pencil"}} + {{d-button class="btn-default" action=(action "destroy") actionParam=item icon="trash-o" class="btn-danger"}} + {{d-button class="btn-default"action=(action "edit") actionParam=item icon="pencil"}} {{#if item.isBlocked}} - {{d-button class="btn-default" action="allow" actionParam=item icon="check" label="admin.logs.screened_ips.actions.do_nothing"}} + {{d-button class="btn-default" action=(action "allow") actionParam=item icon="check" label="admin.logs.screened_ips.actions.do_nothing"}} {{else}} - {{d-button class="btn-default" action="block" actionParam=item icon="ban" label="admin.logs.screened_ips.actions.block"}} + {{d-button class="btn-default" action=(action "block") actionParam=item icon="ban" label="admin.logs.screened_ips.actions.block"}} {{/if}} {{else}} - {{d-button class="btn-default" action="save" actionParam=item label="admin.logs.save"}} + {{d-button class="btn-default" action=(action "save") actionParam=item label="admin.logs.save"}} {{i18n 'cancel'}} {{/unless}} diff --git a/app/assets/javascripts/admin/templates/logs/staff-action-logs.hbs b/app/assets/javascripts/admin/templates/logs/staff-action-logs.hbs index 63ce3d41ab..822dfce0a7 100644 --- a/app/assets/javascripts/admin/templates/logs/staff-action-logs.hbs +++ b/app/assets/javascripts/admin/templates/logs/staff-action-logs.hbs @@ -33,7 +33,7 @@ {{i18n "admin.logs.staff_actions.filter"}} {{combo-box content=userHistoryActions value=filterActionId none="admin.logs.staff_actions.all"}} {{/if}} - {{d-button class="btn-default" action="exportStaffActionLogs" label="admin.export_csv.button_text" icon="download"}} + {{d-button class="btn-default" action=(action "exportStaffActionLogs") label="admin.export_csv.button_text" icon="download"}}
    diff --git a/app/assets/javascripts/admin/templates/modal/admin-add-upload.hbs b/app/assets/javascripts/admin/templates/modal/admin-add-upload.hbs index decedce611..f59c73b89e 100644 --- a/app/assets/javascripts/admin/templates/modal/admin-add-upload.hbs +++ b/app/assets/javascripts/admin/templates/modal/admin-add-upload.hbs @@ -17,6 +17,6 @@ {{/d-modal-body}} diff --git a/app/assets/javascripts/admin/templates/modal/admin-create-theme.hbs b/app/assets/javascripts/admin/templates/modal/admin-create-theme.hbs index 2d5c8d6939..788aa03a8d 100644 --- a/app/assets/javascripts/admin/templates/modal/admin-create-theme.hbs +++ b/app/assets/javascripts/admin/templates/modal/admin-create-theme.hbs @@ -25,6 +25,6 @@ {{/d-modal-body}} diff --git a/app/assets/javascripts/admin/templates/modal/admin-edit-badge-groupings.hbs b/app/assets/javascripts/admin/templates/modal/admin-edit-badge-groupings.hbs index 3776f949fb..958467e6b7 100644 --- a/app/assets/javascripts/admin/templates/modal/admin-edit-badge-groupings.hbs +++ b/app/assets/javascripts/admin/templates/modal/admin-edit-badge-groupings.hbs @@ -26,5 +26,5 @@ diff --git a/app/assets/javascripts/admin/templates/modal/admin-import-theme.hbs b/app/assets/javascripts/admin/templates/modal/admin-import-theme.hbs index b5fbe4a078..e252638589 100644 --- a/app/assets/javascripts/admin/templates/modal/admin-import-theme.hbs +++ b/app/assets/javascripts/admin/templates/modal/admin-import-theme.hbs @@ -44,6 +44,6 @@ {{/d-modal-body}} diff --git a/app/assets/javascripts/admin/templates/modal/admin-moderation-history.hbs b/app/assets/javascripts/admin/templates/modal/admin-moderation-history.hbs index 4abc13e7f3..b79f4bdf30 100644 --- a/app/assets/javascripts/admin/templates/modal/admin-moderation-history.hbs +++ b/app/assets/javascripts/admin/templates/modal/admin-moderation-history.hbs @@ -19,5 +19,5 @@ {{/conditional-loading-spinner}} {{/d-modal-body}} diff --git a/app/assets/javascripts/admin/templates/modal/admin-silence-user.hbs b/app/assets/javascripts/admin/templates/modal/admin-silence-user.hbs index 035c773897..fc4d223ac7 100644 --- a/app/assets/javascripts/admin/templates/modal/admin-silence-user.hbs +++ b/app/assets/javascripts/admin/templates/modal/admin-silence-user.hbs @@ -27,10 +27,10 @@ diff --git a/app/assets/javascripts/admin/templates/modal/admin-staff-action-log-details.hbs b/app/assets/javascripts/admin/templates/modal/admin-staff-action-log-details.hbs index e8e8cd8d2e..3039e95b13 100644 --- a/app/assets/javascripts/admin/templates/modal/admin-staff-action-log-details.hbs +++ b/app/assets/javascripts/admin/templates/modal/admin-staff-action-log-details.hbs @@ -2,5 +2,5 @@
    {{model.details}}
    {{/d-modal-body}} diff --git a/app/assets/javascripts/admin/templates/modal/admin-suspend-user.hbs b/app/assets/javascripts/admin/templates/modal/admin-suspend-user.hbs index cd7031bb8a..6a1c9c28f4 100644 --- a/app/assets/javascripts/admin/templates/modal/admin-suspend-user.hbs +++ b/app/assets/javascripts/admin/templates/modal/admin-suspend-user.hbs @@ -34,10 +34,10 @@ diff --git a/app/assets/javascripts/admin/templates/modal/admin-theme-change.hbs b/app/assets/javascripts/admin/templates/modal/admin-theme-change.hbs index 3fbaf0ac86..1b6b06870a 100644 --- a/app/assets/javascripts/admin/templates/modal/admin-theme-change.hbs +++ b/app/assets/javascripts/admin/templates/modal/admin-theme-change.hbs @@ -3,6 +3,6 @@ {{{diff}}} {{/d-modal-body}}
    diff --git a/app/assets/javascripts/admin/templates/modal/admin-uploaded-image-list.hbs b/app/assets/javascripts/admin/templates/modal/admin-uploaded-image-list.hbs index 72d0fe94b8..10af81306c 100644 --- a/app/assets/javascripts/admin/templates/modal/admin-uploaded-image-list.hbs +++ b/app/assets/javascripts/admin/templates/modal/admin-uploaded-image-list.hbs @@ -11,5 +11,5 @@ {{/d-modal-body}} diff --git a/app/assets/javascripts/admin/templates/permalinks.hbs b/app/assets/javascripts/admin/templates/permalinks.hbs index 4dd4300599..75d3d6edce 100644 --- a/app/assets/javascripts/admin/templates/permalinks.hbs +++ b/app/assets/javascripts/admin/templates/permalinks.hbs @@ -4,7 +4,7 @@ -{{permalink-form action="recordAdded"}} +{{permalink-form action=(action "recordAdded")}}
    {{#conditional-loading-spinner condition=loading}} @@ -42,7 +42,7 @@ {{/if}} - {{d-button action="destroy" actionParam=pl icon="trash-o" class="btn-danger"}} + {{d-button action=(action "destroy") actionParam=pl icon="trash-o" class="btn-danger"}} {{/each}} diff --git a/app/assets/javascripts/admin/templates/plugins-index.hbs b/app/assets/javascripts/admin/templates/plugins-index.hbs index 5704ca76e8..873053a6e2 100644 --- a/app/assets/javascripts/admin/templates/plugins-index.hbs +++ b/app/assets/javascripts/admin/templates/plugins-index.hbs @@ -49,7 +49,7 @@ {{#if currentUser.admin}} {{#if plugin.enabled_setting}} - {{d-button class="btn-default" action="showSettings" actionParam=plugin icon="gear" label="admin.plugins.change_settings_short"}} + {{d-button class="btn-default" action=(route-action "showSettings") actionParam=plugin icon="gear" label="admin.plugins.change_settings_short"}} {{/if}} {{/if}} diff --git a/app/assets/javascripts/admin/templates/plugins.hbs b/app/assets/javascripts/admin/templates/plugins.hbs index 12d9c8c8f6..b2e0d77548 100644 --- a/app/assets/javascripts/admin/templates/plugins.hbs +++ b/app/assets/javascripts/admin/templates/plugins.hbs @@ -1,16 +1,16 @@
    - {{d-button action="toggleMenu" class="menu-toggle" icon="bars"}} + {{d-button action=(action "toggleMenu") class="menu-toggle" icon="bars"}} {{#if currentUser.admin}} {{d-button label="admin.plugins.change_settings" icon="gear" class="btn-default settings-button" - action="showSettings"}} + action=(route-action "showSettings")}} {{/if}}
    - +
    diff --git a/app/assets/javascripts/admin/templates/site-settings.hbs b/app/assets/javascripts/admin/templates/site-settings.hbs index 4cdf577962..56ddbcd551 100644 --- a/app/assets/javascripts/admin/templates/site-settings.hbs +++ b/app/assets/javascripts/admin/templates/site-settings.hbs @@ -1,9 +1,9 @@
    - {{d-button action="toggleMenu" class="menu-toggle" icon="bars"}} + {{d-button action=(action "toggleMenu") class="menu-toggle" icon="bars"}} {{text-field id="setting-filter" value=filter placeholderKey="type_to_filter" class="no-blur"}} - {{d-button class="btn-default" id="clear-filter" action="clearFilter" label="admin.site_settings.clear_filter"}} + {{d-button class="btn-default" id="clear-filter" action=(action "clearFilter") label="admin.site_settings.clear_filter"}}
    @@ -18,6 +18,6 @@ {{/if}} {{#each siteTexts as |siteText|}} - {{site-text-summary siteText=siteText editAction="edit" term=q searchRegex=siteTexts.extras.regex}} + {{site-text-summary siteText=siteText editAction=(action "edit") term=q searchRegex=siteTexts.extras.regex}} {{/each}} {{/conditional-loading-spinner}} diff --git a/app/assets/javascripts/admin/templates/user-fields.hbs b/app/assets/javascripts/admin/templates/user-fields.hbs index 72e4933052..a766d86269 100644 --- a/app/assets/javascripts/admin/templates/user-fields.hbs +++ b/app/assets/javascripts/admin/templates/user-fields.hbs @@ -9,15 +9,15 @@ fieldTypes=fieldTypes firstField=sortedFields.firstObject lastField=sortedFields.lastObject - destroyAction="destroy" - moveUpAction="moveUp" - moveDownAction="moveDown"}} + destroyAction=(action "destroy") + moveUpAction=(action "moveUp") + moveDownAction=(action "moveDown")}} {{/each}} {{/if}} {{d-button disabled=createDisabled class="btn-primary" - action="createField" + action=(action "createField") label="admin.user_fields.create" icon="plus"}} diff --git a/app/assets/javascripts/admin/templates/user-index.hbs b/app/assets/javascripts/admin/templates/user-index.hbs index c5584645fe..f736dd988d 100644 --- a/app/assets/javascripts/admin/templates/user-index.hbs +++ b/app/assets/javascripts/admin/templates/user-index.hbs @@ -8,11 +8,11 @@ {{/if}} {{#if model.can_view_action_logs}} - {{d-button action="viewActionLogs" class="btn-default" actionParam=model.username icon="list-alt" label="admin.user.action_logs"}} + {{d-button action=(action "viewActionLogs") class="btn-default" actionParam=model.username icon="list-alt" label="admin.user.action_logs"}} {{/if}} {{#if model.active}} {{#if currentUser.admin}} - {{d-button class="btn-default" action="logOut" icon="power-off" label="admin.user.log_out"}} + {{d-button class="btn-default" action=(action "logOut") icon="power-off" label="admin.user.log_out"}} {{/if}} {{/if}} {{plugin-outlet name="admin-user-controls-after" args=(hash model=model) tagName="" connectorTagName=""}} @@ -44,7 +44,7 @@ {{#if model.email}} {{model.email}} {{else}} - {{d-button class="btn-default" action="checkEmail" actionParam=model icon="envelope-o" label="admin.users.check_email.text" title="admin.users.check_email.title"}} + {{d-button class="btn-default" action=(route-action "checkEmail") actionParam=model icon="envelope-o" label="admin.users.check_email.text" title="admin.users.check_email.title"}} {{/if}}
    @@ -64,7 +64,7 @@ {{i18n 'user.email.no_secondary'}} {{/if}} {{else}} - {{d-button action="checkEmail" + {{d-button action=(route-action "checkEmail") class="btn-default" actionParam=model icon="envelope-o" @@ -79,7 +79,7 @@
    {{model.bounceScore}}
    {{#if model.canResetBounceScore}} - {{d-button class="btn-default" action="resetBounceScore" label="admin.user.reset_bounce_score.label" title="admin.user.reset_bounce_score.title"}} + {{d-button class="btn-default" action=(action "resetBounceScore") label="admin.user.reset_bounce_score.label" title="admin.user.reset_bounce_score.title"}} {{/if}} {{model.bounceScoreExplanation}}
    @@ -91,7 +91,7 @@ {{#if associatedAccountsLoaded}} {{associatedAccounts}} {{else}} - {{d-button class="btn-default" action="checkEmail" actionParam=model icon="envelope-o" label="admin.users.check_email.text" title="admin.users.check_email.title"}} + {{d-button class="btn-default" action=(route-action "checkEmail") actionParam=model icon="envelope-o" label="admin.users.check_email.text" title="admin.users.check_email.title"}} {{/if}}
    @@ -117,7 +117,7 @@
    {{model.ip_address}}
    {{#if currentUser.staff}} - {{d-button class="btn-default" action="refreshBrowsers" label="admin.user.refresh_browsers"}} + {{d-button class="btn-default" action=(action "refreshBrowsers") label="admin.user.refresh_browsers"}} {{ip-lookup ip=model.ip_address userId=model.id}} {{/if}}
    @@ -156,7 +156,7 @@
    {{#if canDisableSecondFactor}} - {{d-button class="btn-default" action="disableSecondFactor" icon="unlock-alt" label="user.second_factor.disable"}} + {{d-button class="btn-default" action=(action "disableSecondFactor") icon="unlock-alt" label="user.second_factor.disable"}} {{/if}}
    @@ -201,7 +201,7 @@ {{i18n 'admin.user.approve_success'}} {{else}} {{#if model.can_approve}} - {{d-button class="btn-default" action="approve" icon="check" label="admin.user.approve"}} + {{d-button class="btn-default" action=(action "approve") icon="check" label="admin.user.approve"}} {{/if}} {{/if}}
    @@ -214,15 +214,15 @@
    {{#if model.active}} {{#if model.can_deactivate}} - {{d-button class="btn-default" action="deactivate" label="admin.user.deactivate_account"}} + {{d-button class="btn-default" action=(action "deactivate") label="admin.user.deactivate_account"}} {{i18n 'admin.user.deactivate_explanation'}} {{/if}} {{else}} {{#if model.can_send_activation_email}} - {{d-button class="btn-default" action="sendActivationEmail" icon="envelope" label="admin.user.send_activation_email"}} + {{d-button class="btn-default" action=(action "sendActivationEmail") icon="envelope" label="admin.user.send_activation_email"}} {{/if}} {{#if model.can_activate}} - {{d-button class="btn-default" action="activate" icon="check" label="admin.user.activate"}} + {{d-button class="btn-default" action=(action "activate") icon="check" label="admin.user.activate"}} {{/if}} {{/if}}
    @@ -240,15 +240,15 @@ {{#if model.api_key}}
    {{model.api_key.key}} - {{d-button class="btn-default" action="regenerateApiKey" icon="undo" label="admin.api.regenerate"}} - {{d-button class="btn-default" action="revokeApiKey" icon="times" label="admin.api.revoke"}} + {{d-button class="btn-default" action=(action "regenerateApiKey") icon="undo" label="admin.api.regenerate"}} + {{d-button class="btn-default" action=(action "revokeApiKey") icon="times" label="admin.api.revoke"}}
    {{else}}
    - {{d-button class="btn-default" action="generateApiKey" icon="key" label="admin.api.generate"}} + {{d-button class="btn-default" action=(action "generateApiKey") icon="key" label="admin.api.generate"}}
    {{/if}}
    @@ -259,10 +259,10 @@
    {{i18n-yes-no model.admin}}
    {{#if model.can_revoke_admin}} - {{d-button class="btn-default" action="revokeAdmin" icon="shield" label="admin.user.revoke_admin"}} + {{d-button class="btn-default" action=(action "revokeAdmin") icon="shield" label="admin.user.revoke_admin"}} {{/if}} {{#if model.can_grant_admin}} - {{d-button class="btn-default" action="grantAdmin" icon="shield" label="admin.user.grant_admin"}} + {{d-button class="btn-default" action=(action "grantAdmin") icon="shield" label="admin.user.grant_admin"}} {{/if}}
    @@ -272,10 +272,10 @@
    {{i18n-yes-no model.moderator}}
    {{#if model.can_revoke_moderation}} - {{d-button class="btn-default" action="revokeModeration" icon="shield" label="admin.user.revoke_moderation"}} + {{d-button class="btn-default" action=(action "revokeModeration") icon="shield" label="admin.user.revoke_moderation"}} {{/if}} {{#if model.can_grant_moderation}} - {{d-button class="btn-default" action="grantModeration" icon="shield" label="admin.user.grant_moderation"}} + {{d-button class="btn-default" action=(action "grantModeration") icon="shield" label="admin.user.grant_moderation"}} {{/if}}
    @@ -286,8 +286,8 @@ {{combo-box content=site.trustLevels value=model.trust_level nameProperty="detailedName"}} {{#if model.dirty}}
    - {{d-button class="ok no-text" action="saveTrustLevel" icon="check"}} - {{d-button class="cancel no-text" action="restoreTrustLevel" icon="times"}} + {{d-button class="ok no-text" action=(action "saveTrustLevel") icon="check"}} + {{d-button class="cancel no-text" action=(action "restoreTrustLevel") icon="times"}}
    {{/if}} @@ -295,10 +295,10 @@ {{#if model.canLockTrustLevel}} {{#if hasLockedTrustLevel}} {{d-icon "lock" title="admin.user.trust_level_locked_tip"}} - {{d-button class="btn-default" action="lockTrustLevel" actionParam=false label="admin.user.unlock_trust_level"}} + {{d-button class="btn-default" action=(action "lockTrustLevel") actionParam=false label="admin.user.unlock_trust_level"}} {{else}} {{d-icon "unlock" title="admin.user.trust_level_unlocked_tip"}} - {{d-button class="btn-default" action="lockTrustLevel" actionParam=true label="admin.user.lock_trust_level"}} + {{d-button class="btn-default" action=(action "lockTrustLevel") actionParam=true label="admin.user.lock_trust_level"}} {{/if}} {{/if}} {{#if model.tl3Requirements}} @@ -367,7 +367,7 @@ {{#if model.silenced}} {{d-button class="btn-danger unsilence-user" - action="unsilence" + action=(action "unsilence") icon="microphone-slash" label="admin.user.unsilence"}} {{i18n 'admin.user.silence_explanation'}} @@ -429,8 +429,8 @@ {{#if customGroupsDirty}}
    - {{d-button icon="check" class="ok" action="saveCustomGroups"}} - {{d-button icon="times" class="cancel" action="resetCustomGroups"}} + {{d-button icon="check" class="ok" action=(action "saveCustomGroups")}} + {{d-button icon="times" class="cancel" action=(action "resetCustomGroups")}}
    {{/if}} @@ -442,8 +442,8 @@ {{#if primaryGroupDirty}}
    - {{d-button icon="check" class="ok" action="savePrimaryGroup"}} - {{d-button icon="times" class="cancel" action="resetPrimaryGroup"}} + {{d-button icon="check" class="ok" action=(action "savePrimaryGroup")}} + {{d-button icon="times" class="cancel" action=(action "resetPrimaryGroup")}}
    {{/if}} @@ -480,7 +480,7 @@
    {{#if model.can_delete_all_posts}} {{#if model.post_count}} - {{d-button class="btn-danger" action="deleteAllPosts" icon="trash-o" label="admin.user.delete_all_posts"}} + {{d-button class="btn-danger" action=(action "deleteAllPosts") icon="trash-o" label="admin.user.delete_all_posts"}} {{/if}} {{else}} {{deleteAllPostsExplanation}} @@ -563,7 +563,7 @@
    {{#if model.active}} {{#if model.can_impersonate}} - {{d-button class="btn-danger" action="impersonate" icon="crosshairs" label="admin.impersonate.title" title="admin.impersonate.help"}} + {{d-button class="btn-danger" action=(action "impersonate") icon="crosshairs" label="admin.impersonate.title" title="admin.impersonate.help"}} {{/if}} {{/if}} @@ -571,14 +571,14 @@ {{d-button label="admin.user.anonymize" icon="exclamation-triangle" class="btn-danger" - action="anonymize"}} + action=(action "anonymize")}} {{/if}} {{#if model.canBeDeleted}} {{d-button label="admin.user.delete" icon="exclamation-triangle" class="btn-danger" - action="destroy"}} + action=(action "destroy")}} {{/if}}
    diff --git a/app/assets/javascripts/admin/templates/users-list.hbs b/app/assets/javascripts/admin/templates/users-list.hbs index 92c5c689ad..d4d5be049f 100644 --- a/app/assets/javascripts/admin/templates/users-list.hbs +++ b/app/assets/javascripts/admin/templates/users-list.hbs @@ -14,10 +14,10 @@ {{nav-item route='groups' label='groups.index.title'}}
    {{#unless siteSettings.enable_sso}} - {{d-button class="btn-default" action="sendInvites" title="admin.invite.button_title" icon="user-plus" label="admin.invite.button_text"}} + {{d-button class="btn-default" action=(route-action "sendInvites") title="admin.invite.button_title" icon="user-plus" label="admin.invite.button_text"}} {{/unless}} {{#if currentUser.admin}} - {{d-button class="btn-default" action="exportUsers" title="admin.export_csv.button_title.user" icon="download" label="admin.export_csv.button_text"}} + {{d-button class="btn-default" action=(route-action "exportUsers") title="admin.export_csv.button_title.user" icon="download" label="admin.export_csv.button_text"}} {{/if}}
    diff --git a/app/assets/javascripts/admin/templates/watched-words-action.hbs b/app/assets/javascripts/admin/templates/watched-words-action.hbs index a9481675c0..ad04b20c5b 100644 --- a/app/assets/javascripts/admin/templates/watched-words-action.hbs +++ b/app/assets/javascripts/admin/templates/watched-words-action.hbs @@ -5,11 +5,11 @@
    {{watched-word-form actionKey=actionNameKey - action="recordAdded" + action=(action "recordAdded") filteredContent=filteredContent regularExpressions=adminWatchedWords.regularExpressions}} -{{watched-word-uploader uploading=uploading actionKey=actionNameKey done="uploadComplete"}} +{{watched-word-uploader uploading=uploading actionKey=actionNameKey done=(action "uploadComplete")}}