diff --git a/Gemfile b/Gemfile index 6c72e48bab..a80f23dbc0 100644 --- a/Gemfile +++ b/Gemfile @@ -127,10 +127,6 @@ gem 'mini_racer' # TODO: determine why highline is being held back and upgrade to latest gem 'highline', '~> 1.7.0', require: false -# TODO: Upgrading breaks Sidekiq Web -# This is a bit of a hornets nest cause in an ideal world we much prefer -# if Sidekiq reused session and CSRF mitigation with Discourse on the -# _forum_session cookie instead of a rack.session cookie gem 'rack', '2.2.2' gem 'rack-protection' # security @@ -252,3 +248,5 @@ end gem 'webpush', require: false gem 'colored2', require: false gem 'maxminddb' + +gem 'rails_failover', require: false diff --git a/Gemfile.lock b/Gemfile.lock index 752d48c3c9..db4b88d863 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -195,7 +195,7 @@ GEM mini_sql (0.2.5) mini_suffix (0.3.0) ffi (~> 1.9) - minitest (5.14.0) + minitest (5.14.1) mocha (1.11.2) mock_redis (0.23.0) msgpack (1.3.3) @@ -262,7 +262,7 @@ GEM pry-rails (0.3.9) pry (>= 0.10.4) public_suffix (4.0.5) - puma (4.3.3) + puma (4.3.5) nio4r (~> 2.0) r2 (0.2.7) rack (2.2.2) @@ -277,6 +277,8 @@ GEM nokogiri (>= 1.6) rails-html-sanitizer (1.3.0) loofah (~> 2.3) + rails_failover (0.2.0) + redis (~> 4) rails_multisite (2.1.2) activerecord (> 5.0, < 7) railties (> 5.0, < 7) @@ -294,7 +296,7 @@ GEM rb-fsevent (0.10.4) rb-inotify (0.10.1) ffi (~> 1.0) - rbtrace (0.4.12) + rbtrace (0.4.13) ffi (>= 1.0.6) msgpack (>= 0.4.3) optimist (>= 3.0.0) @@ -341,13 +343,16 @@ GEM json-schema (~> 2.2) railties (>= 3.1, < 7.0) rtlit (0.0.5) - rubocop (0.83.0) + rubocop (0.84.0) parallel (~> 1.10) parser (>= 2.7.0.1) rainbow (>= 2.2.2, < 4.0) rexml + rubocop-ast (>= 0.0.3) ruby-progressbar (~> 1.7) unicode-display_width (>= 1.4.0, < 2.0) + rubocop-ast (0.0.3) + parser (>= 2.7.0.1) rubocop-discourse (2.1.2) rubocop (>= 0.69.0) rubocop-rspec (>= 1.39.0) @@ -509,6 +514,7 @@ DEPENDENCIES rack (= 2.2.2) rack-mini-profiler rack-protection + rails_failover rails_multisite railties (= 6.0.3) rake diff --git a/app/assets/javascripts/admin/models/admin-user.js b/app/assets/javascripts/admin/models/admin-user.js index 71b8078f6e..9977fa8dd4 100644 --- a/app/assets/javascripts/admin/models/admin-user.js +++ b/app/assets/javascripts/admin/models/admin-user.js @@ -77,12 +77,6 @@ const AdminUser = User.extend({ }); }, - revokeApiKey() { - return ajax(`/admin/users/${this.id}/revoke_api_key`, { - type: "DELETE" - }).then(() => this.set("api_key", null)); - }, - deleteAllPosts() { let deletedPosts = 0; const user = this; diff --git a/app/assets/javascripts/admin/routes/admin-web-hooks-show.js b/app/assets/javascripts/admin/routes/admin-web-hooks-show.js index 34aed775cc..f0860e79f7 100644 --- a/app/assets/javascripts/admin/routes/admin-web-hooks-show.js +++ b/app/assets/javascripts/admin/routes/admin-web-hooks-show.js @@ -1,5 +1,4 @@ import { get } from "@ember/object"; -import { isEmpty } from "@ember/utils"; import DiscourseRoute from "discourse/routes/discourse"; export default DiscourseRoute.extend({ @@ -15,7 +14,7 @@ export default DiscourseRoute.extend({ }, setupController(controller, model) { - if (model.get("isNew") || isEmpty(model.get("web_hook_event_types"))) { + if (model.get("isNew")) { model.set("web_hook_event_types", controller.get("defaultEventTypes")); } diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index 4445fd7986..40f1d275bb 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -1,6 +1,6 @@ //= require_tree ./discourse-common/addon //= require ./polyfills -//= require_tree ./select-kit/app +//= require_tree ./select-kit/addon //= require ./discourse/app/app //= require ./app-boot diff --git a/app/assets/javascripts/discourse-loader.js b/app/assets/javascripts/discourse-loader.js index d11217a985..fc829ae12f 100644 --- a/app/assets/javascripts/discourse-loader.js +++ b/app/assets/javascripts/discourse-loader.js @@ -36,7 +36,6 @@ var define, requirejs; default: Ember.Object, get: Ember.get, getProperties: Ember.getProperties, - guidFor: Ember.guidFor, set: Ember.set, setProperties: Ember.setProperties, computed: Ember.computed, diff --git a/app/assets/javascripts/discourse/app/controllers/preferences/interface.js b/app/assets/javascripts/discourse/app/controllers/preferences/interface.js index 1ad85b7d7b..2fcc5781c3 100644 --- a/app/assets/javascripts/discourse/app/controllers/preferences/interface.js +++ b/app/assets/javascripts/discourse/app/controllers/preferences/interface.js @@ -2,13 +2,10 @@ import I18n from "I18n"; import { inject } from "@ember/controller"; import Controller from "@ember/controller"; import { setDefaultHomepage } from "discourse/lib/utilities"; -import discourseComputed, { observes } from "discourse-common/utils/decorators"; -import { - listThemes, - previewTheme, - setLocalTheme -} from "discourse/lib/theme-selector"; +import discourseComputed from "discourse-common/utils/decorators"; +import { listThemes, setLocalTheme } from "discourse/lib/theme-selector"; import { popupAjaxError } from "discourse/lib/ajax-error"; +import pageReloader from "discourse/helpers/page-reloader"; import { safariHacksDisabled, isiPad, @@ -28,6 +25,9 @@ const TEXT_SIZES = ["smaller", "normal", "larger", "largest"]; const TITLE_COUNT_MODES = ["notifications", "contextual"]; export default Controller.extend({ + currentThemeId: -1, + preferencesController: inject("preferences"), + @discourseComputed("makeThemeDefault") saveAttrNames(makeDefault) { let attrs = [ @@ -51,8 +51,6 @@ export default Controller.extend({ return attrs; }, - preferencesController: inject("preferences"), - @discourseComputed() isiPad() { // TODO: remove this preference checkbox when iOS adoption > 90% @@ -105,10 +103,14 @@ export default Controller.extend({ return themes && themes.length > 1; }, - @observes("themeId") - themeIdChanged() { - const id = this.themeId; - previewTheme([id]); + @discourseComputed("themeId") + themeIdChanged(themeId) { + if (this.currentThemeId === -1) { + this.set("currentThemeId", themeId); + return false; + } else { + return this.currentThemeId !== themeId; + } }, @discourseComputed("model.user_option.theme_ids", "themeId") @@ -189,6 +191,10 @@ export default Controller.extend({ this.disableSafariHacks.toString() ); } + + if (this.themeId !== this.currentThemeId) { + pageReloader.reload(); + } }) .catch(popupAjaxError); }, diff --git a/app/assets/javascripts/discourse/app/controllers/topic.js b/app/assets/javascripts/discourse/app/controllers/topic.js index 2c2ebdc7fe..ba7caf554d 100644 --- a/app/assets/javascripts/discourse/app/controllers/topic.js +++ b/app/assets/javascripts/discourse/app/controllers/topic.js @@ -185,6 +185,13 @@ export default Controller.extend(bufferedProperty("model"), { ); }, + @discourseComputed("model.category") + minimumRequiredTags(category) { + return category && category.minimum_required_tags > 0 + ? category.minimum_required_tags + : null; + }, + _forceRefreshPostStream() { this.appEvents.trigger("post-stream:refresh", { force: true }); }, diff --git a/app/assets/javascripts/discourse/app/controllers/user.js b/app/assets/javascripts/discourse/app/controllers/user.js index cc2db042fb..b466f084ed 100644 --- a/app/assets/javascripts/discourse/app/controllers/user.js +++ b/app/assets/javascripts/discourse/app/controllers/user.js @@ -51,13 +51,15 @@ export default Controller.extend(CanCheckEmails, { hasDeletedPosts: gt("model.number_of_deleted_posts", 0), hasBeenSuspended: gt("model.number_of_suspensions", 0), hasReceivedWarnings: gt("model.warnings_received_count", 0), + hasRejectedPosts: gt("model.number_of_rejected_posts", 0), showStaffCounters: or( "hasGivenFlags", "hasFlaggedPosts", "hasDeletedPosts", "hasBeenSuspended", - "hasReceivedWarnings" + "hasReceivedWarnings", + "hasRejectedPosts" ), showFeaturedTopic: and( diff --git a/app/assets/javascripts/discourse/app/helpers/page-reloader.js b/app/assets/javascripts/discourse/app/helpers/page-reloader.js new file mode 100644 index 0000000000..6429be3a3c --- /dev/null +++ b/app/assets/javascripts/discourse/app/helpers/page-reloader.js @@ -0,0 +1,10 @@ +import EmberObject from "@ember/object"; +import Ember from "ember"; + +export default EmberObject.create({ + reload: function() { + if (!Ember.testing) { + location.reload(); + } + } +}); diff --git a/app/assets/javascripts/discourse/app/initializers/copy-codeblocks.js b/app/assets/javascripts/discourse/app/initializers/copy-codeblocks.js index a0b0d50962..f6fcef5e17 100644 --- a/app/assets/javascripts/discourse/app/initializers/copy-codeblocks.js +++ b/app/assets/javascripts/discourse/app/initializers/copy-codeblocks.js @@ -3,6 +3,7 @@ import { cancel, later } from "@ember/runloop"; import { Promise } from "rsvp"; import { iconHTML } from "discourse-common/lib/icon-library"; import I18n from "I18n"; +import { guidFor } from "@ember/object/internals"; // http://github.com/feross/clipboard-copy function clipboardCopy(text) { @@ -90,7 +91,7 @@ export default { const state = button.innerHTML; button.innerHTML = I18n.t("copy_codeblock.copied"); - const commandId = Ember.guidFor(button); + const commandId = guidFor(button); if (_fadeCopyCodeblocksRunners[commandId]) { cancel(_fadeCopyCodeblocksRunners[commandId]); diff --git a/app/assets/javascripts/discourse/app/initializers/topic-footer-buttons.js b/app/assets/javascripts/discourse/app/initializers/topic-footer-buttons.js index a13ccd2219..a2de76d3e9 100644 --- a/app/assets/javascripts/discourse/app/initializers/topic-footer-buttons.js +++ b/app/assets/javascripts/discourse/app/initializers/topic-footer-buttons.js @@ -3,6 +3,13 @@ import showModal from "discourse/lib/show-modal"; import { registerTopicFooterButton } from "discourse/lib/register-topic-footer-button"; import { formattedReminderTime } from "discourse/lib/bookmark"; +const SHARE_PRIORITY = 1000; +const BOOKMARK_PRIORITY = 900; +const ARCHIVE_PRIORITY = 800; +const FLAG_PRIORITY = 700; +const EDIT_MESSAGE_PRIORITY = 600; +const DEFER_PRIORITY = 500; + export default { name: "topic-footer-buttons", @@ -11,8 +18,12 @@ export default { registerTopicFooterButton({ id: "share-and-invite", icon: "link", - priority: 999, - label: "topic.share.title", + priority: SHARE_PRIORITY, + label() { + if (!this.get("topic.isPrivateMessage") || this.site.mobileView) { + return "topic.share.title"; + } + }, title: "topic.share.help", action() { const panels = [ @@ -67,7 +78,7 @@ export default { registerTopicFooterButton({ id: "flag", icon: "flag", - priority: 998, + priority: FLAG_PRIORITY, label: "topic.flag_topic.title", title: "topic.flag_topic.help", action: "showFlagTopic", @@ -93,14 +104,16 @@ export default { } return "bookmark"; }, - priority: 1000, + priority: BOOKMARK_PRIORITY, classNames() { const bookmarked = this.get("topic.bookmarked"); return bookmarked ? ["bookmark", "bookmarked"] : ["bookmark"]; }, label() { - const bookmarked = this.get("topic.bookmarked"); - return bookmarked ? "bookmarked.clear_bookmarks" : "bookmarked.title"; + if (!this.get("topic.isPrivateMessage") || this.site.mobileView) { + const bookmarked = this.get("topic.bookmarked"); + return bookmarked ? "bookmarked.clear_bookmarks" : "bookmarked.title"; + } }, translatedTitle() { const bookmarked = this.get("topic.bookmarked"); @@ -126,7 +139,7 @@ export default { registerTopicFooterButton({ id: "archive", - priority: 996, + priority: ARCHIVE_PRIORITY, icon() { return this.archiveIcon; }, @@ -155,13 +168,16 @@ export default { registerTopicFooterButton({ id: "edit-message", - priority: 750, + priority: EDIT_MESSAGE_PRIORITY, icon: "pencil-alt", label: "topic.edit_message.title", title: "topic.edit_message.help", action: "editFirstPost", classNames: ["edit-message"], dependentKeys: ["editFirstPost", "showEditOnFooter"], + dropdown() { + return this.site.mobileView && this.get("topic.isPrivateMessage"); + }, displayed() { return this.showEditOnFooter; } @@ -170,7 +186,7 @@ export default { registerTopicFooterButton({ id: "defer", icon: "circle", - priority: 300, + priority: DEFER_PRIORITY, label: "topic.defer.title", title: "topic.defer.help", action: "deferTopic", diff --git a/app/assets/javascripts/discourse/app/lib/theme-selector.js b/app/assets/javascripts/discourse/app/lib/theme-selector.js index 9d293fe312..48f63c31fb 100644 --- a/app/assets/javascripts/discourse/app/lib/theme-selector.js +++ b/app/assets/javascripts/discourse/app/lib/theme-selector.js @@ -1,5 +1,4 @@ import I18n from "I18n"; -import { ajax } from "discourse/lib/ajax"; import deprecated from "discourse-common/lib/deprecated"; const keySelector = "meta[name=discourse_theme_ids]"; @@ -79,31 +78,6 @@ export function refreshCSS(node, hash, newHref) { $orig.data("copy", reloaded); } -export function previewTheme(ids = []) { - ids = ids.reject(id => !id); - if (!ids.includes(currentThemeId())) { - Discourse.set("assetVersion", "forceRefresh"); - - ajax(`/themes/assets/${ids.length > 0 ? ids.join("-") : "default"}`).then( - results => { - const elem = _.first($(keySelector)); - if (elem) { - elem.content = ids.join(","); - } - - results.themes.forEach(theme => { - const node = $( - `link[rel=stylesheet][data-target=${theme.target}]` - )[0]; - if (node) { - refreshCSS(node, null, theme.new_href); - } - }); - } - ); - } -} - export function listThemes(site) { let themes = site.get("user_themes"); diff --git a/app/assets/javascripts/discourse/app/models/nav-item.js b/app/assets/javascripts/discourse/app/models/nav-item.js index 412cd8ec2e..955e536e1e 100644 --- a/app/assets/javascripts/discourse/app/models/nav-item.js +++ b/app/assets/javascripts/discourse/app/models/nav-item.js @@ -273,12 +273,17 @@ export function addNavItem(item) { NavItem.extraNavItemDescriptors.push(item); } -Object.defineProperty(Discourse, "NavItem", { - get() { - deprecated("Import the NavItem class instead of using Discourse.NavItem", { - since: "2.4.0", - dropFrom: "2.5.0" - }); - return NavItem; - } -}); +if (typeof Discourse !== "undefined") { + Object.defineProperty(Discourse, "NavItem", { + get() { + deprecated( + "Import the NavItem class instead of using Discourse.NavItem", + { + since: "2.4.0", + dropFrom: "2.5.0" + } + ); + return NavItem; + } + }); +} diff --git a/app/assets/javascripts/discourse/app/models/topic-details.js b/app/assets/javascripts/discourse/app/models/topic-details.js index 50449aa0e3..d8d5ea0036 100644 --- a/app/assets/javascripts/discourse/app/models/topic-details.js +++ b/app/assets/javascripts/discourse/app/models/topic-details.js @@ -61,12 +61,15 @@ const TopicDetails = RestModel.extend({ } }, - updateNotifications(v) { - this.set("notification_level", v); - this.set("notifications_reason_id", null); - return ajax("/t/" + this.get("topic.id") + "/notifications", { + updateNotifications(level) { + return ajax(`/t/${this.get("topic.id")}/notifications`, { type: "POST", - data: { notification_level: v } + data: { notification_level: level } + }).then(() => { + this.setProperties({ + notification_level: level, + notifications_reason_id: null + }); }); }, diff --git a/app/assets/javascripts/discourse/app/templates/about.hbs b/app/assets/javascripts/discourse/app/templates/about.hbs index 63cae2c260..2659b0ebac 100644 --- a/app/assets/javascripts/discourse/app/templates/about.hbs +++ b/app/assets/javascripts/discourse/app/templates/about.hbs @@ -64,50 +64,52 @@ {{/each}} {{/if}} -
-

{{d-icon "far-chart-bar"}} {{i18n "about.stats"}}

+ {{#if model.can_see_about_stats}} +
+

{{d-icon "far-chart-bar"}} {{i18n "about.stats"}}

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 {{i18n "about.stat.last_7_days"}}{{i18n "about.stat.last_30_days"}}{{i18n "about.stat.all_time"}}
{{i18n "about.topic_count"}}{{number model.stats.topics_7_days}}{{number model.stats.topics_30_days}}{{number model.stats.topic_count}}
{{i18n "about.post_count"}}{{number model.stats.posts_7_days}}{{number model.stats.posts_30_days}}{{number model.stats.post_count}}
{{i18n "about.user_count"}}{{number model.stats.users_7_days}}{{number model.stats.users_30_days}}{{number model.stats.user_count}}
{{i18n "about.active_user_count"}}{{number model.stats.active_users_7_days}}{{number model.stats.active_users_30_days}}
{{i18n "about.like_count"}}{{number model.stats.likes_7_days}}{{number model.stats.likes_30_days}}{{number model.stats.like_count}}
-
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 {{i18n "about.stat.last_7_days"}}{{i18n "about.stat.last_30_days"}}{{i18n "about.stat.all_time"}}
{{i18n "about.topic_count"}}{{number model.stats.topics_7_days}}{{number model.stats.topics_30_days}}{{number model.stats.topic_count}}
{{i18n "about.post_count"}}{{number model.stats.posts_7_days}}{{number model.stats.posts_30_days}}{{number model.stats.post_count}}
{{i18n "about.user_count"}}{{number model.stats.users_7_days}}{{number model.stats.users_30_days}}{{number model.stats.user_count}}
{{i18n "about.active_user_count"}}{{number model.stats.active_users_7_days}}{{number model.stats.active_users_30_days}}
{{i18n "about.like_count"}}{{number model.stats.likes_7_days}}{{number model.stats.likes_30_days}}{{number model.stats.like_count}}
+
+ {{/if}} {{#if contactInfo}}
diff --git a/app/assets/javascripts/discourse/app/templates/components/d-navigation.hbs b/app/assets/javascripts/discourse/app/templates/components/d-navigation.hbs index e9e0b9a34f..2ac52d5f71 100644 --- a/app/assets/javascripts/discourse/app/templates/components/d-navigation.hbs +++ b/app/assets/javascripts/discourse/app/templates/components/d-navigation.hbs @@ -3,6 +3,9 @@ {{#if showCategoryAdmin}} {{categories-admin-dropdown onChange=(action "selectCategoryAdminDropdownAction") + options=(hash + triggerOnChangeOnTab=false + ) }} {{/if}} diff --git a/app/assets/javascripts/discourse/app/templates/components/topic-footer-buttons.hbs b/app/assets/javascripts/discourse/app/templates/components/topic-footer-buttons.hbs index 007ec9c19d..aa5b44a716 100644 --- a/app/assets/javascripts/discourse/app/templates/components/topic-footer-buttons.hbs +++ b/app/assets/javascripts/discourse/app/templates/components/topic-footer-buttons.hbs @@ -50,10 +50,10 @@ args=(hash topic=topic) tagName="" connectorTagName="span"}} - - {{pinned-button pinned=topic.pinned topic=topic}} +{{pinned-button pinned=topic.pinned topic=topic}} + {{#if showNotificationsButton}} {{topic-notifications-button notificationLevel=topic.details.notification_level diff --git a/app/assets/javascripts/discourse/app/templates/preferences/interface.hbs b/app/assets/javascripts/discourse/app/templates/preferences/interface.hbs index 45607c8cb7..4f1f13c614 100644 --- a/app/assets/javascripts/discourse/app/templates/preferences/interface.hbs +++ b/app/assets/javascripts/discourse/app/templates/preferences/interface.hbs @@ -5,9 +5,11 @@ {{combo-box content=userSelectableThemes value=themeId - onChange=(action (mut themeId)) }} + {{#if themeIdChanged}} +

{{i18n "user.save_to_change_theme" save_text=(i18n "save") }}

+ {{/if}} {{#if showThemeSetDefault}}
{{preference-checkbox labelKey="user.theme_default_on_all_devices" checked=makeThemeDefault}} diff --git a/app/assets/javascripts/discourse/app/templates/topic.hbs b/app/assets/javascripts/discourse/app/templates/topic.hbs index 488970c10e..d5e22f5437 100644 --- a/app/assets/javascripts/discourse/app/templates/topic.hbs +++ b/app/assets/javascripts/discourse/app/templates/topic.hbs @@ -36,6 +36,7 @@ options=(hash filterable=true categoryId=buffered.category_id + minimum=minimumRequiredTags ) }} {{/if}} diff --git a/app/assets/javascripts/discourse/app/widgets/header.js b/app/assets/javascripts/discourse/app/widgets/header.js index ca4d73c925..8e27a15a03 100644 --- a/app/assets/javascripts/discourse/app/widgets/header.js +++ b/app/assets/javascripts/discourse/app/widgets/header.js @@ -125,6 +125,7 @@ createWidget( { attributes: { href: attrs.user.get("path"), + title: attrs.user.get("name"), "data-auto-route": true } }, diff --git a/app/assets/javascripts/select-kit/addon/.gitkeep b/app/assets/javascripts/select-kit/addon/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/app/assets/javascripts/select-kit/app/components/admin-group-selector.js b/app/assets/javascripts/select-kit/addon/components/admin-group-selector.js similarity index 100% rename from app/assets/javascripts/select-kit/app/components/admin-group-selector.js rename to app/assets/javascripts/select-kit/addon/components/admin-group-selector.js diff --git a/app/assets/javascripts/select-kit/app/components/categories-admin-dropdown.js b/app/assets/javascripts/select-kit/addon/components/categories-admin-dropdown.js similarity index 100% rename from app/assets/javascripts/select-kit/app/components/categories-admin-dropdown.js rename to app/assets/javascripts/select-kit/addon/components/categories-admin-dropdown.js diff --git a/app/assets/javascripts/select-kit/app/components/category-chooser.js b/app/assets/javascripts/select-kit/addon/components/category-chooser.js similarity index 99% rename from app/assets/javascripts/select-kit/app/components/category-chooser.js rename to app/assets/javascripts/select-kit/addon/components/category-chooser.js index f36e8be2d3..72d0167ac0 100644 --- a/app/assets/javascripts/select-kit/app/components/category-chooser.js +++ b/app/assets/javascripts/select-kit/addon/components/category-chooser.js @@ -67,6 +67,7 @@ export default ComboBoxComponent.extend({ search(filter) { if (filter) { + filter = filter.toLowerCase(); return this.content.filter(item => { const category = Category.findById(this.getValue(item)); const categoryName = this.getName(item); diff --git a/app/assets/javascripts/select-kit/app/components/category-drop.js b/app/assets/javascripts/select-kit/addon/components/category-drop.js similarity index 100% rename from app/assets/javascripts/select-kit/app/components/category-drop.js rename to app/assets/javascripts/select-kit/addon/components/category-drop.js diff --git a/app/assets/javascripts/select-kit/app/components/category-drop/category-drop-header.js b/app/assets/javascripts/select-kit/addon/components/category-drop/category-drop-header.js similarity index 100% rename from app/assets/javascripts/select-kit/app/components/category-drop/category-drop-header.js rename to app/assets/javascripts/select-kit/addon/components/category-drop/category-drop-header.js diff --git a/app/assets/javascripts/select-kit/app/components/category-notifications-button.js b/app/assets/javascripts/select-kit/addon/components/category-notifications-button.js similarity index 100% rename from app/assets/javascripts/select-kit/app/components/category-notifications-button.js rename to app/assets/javascripts/select-kit/addon/components/category-notifications-button.js diff --git a/app/assets/javascripts/select-kit/app/components/category-row.js b/app/assets/javascripts/select-kit/addon/components/category-row.js similarity index 100% rename from app/assets/javascripts/select-kit/app/components/category-row.js rename to app/assets/javascripts/select-kit/addon/components/category-row.js diff --git a/app/assets/javascripts/select-kit/app/components/category-selector.js b/app/assets/javascripts/select-kit/addon/components/category-selector.js similarity index 100% rename from app/assets/javascripts/select-kit/app/components/category-selector.js rename to app/assets/javascripts/select-kit/addon/components/category-selector.js diff --git a/app/assets/javascripts/select-kit/app/components/color-palettes.js b/app/assets/javascripts/select-kit/addon/components/color-palettes.js similarity index 100% rename from app/assets/javascripts/select-kit/app/components/color-palettes.js rename to app/assets/javascripts/select-kit/addon/components/color-palettes.js diff --git a/app/assets/javascripts/select-kit/app/components/color-palettes/color-palettes-row.js b/app/assets/javascripts/select-kit/addon/components/color-palettes/color-palettes-row.js similarity index 100% rename from app/assets/javascripts/select-kit/app/components/color-palettes/color-palettes-row.js rename to app/assets/javascripts/select-kit/addon/components/color-palettes/color-palettes-row.js diff --git a/app/assets/javascripts/select-kit/app/components/combo-box.js b/app/assets/javascripts/select-kit/addon/components/combo-box.js similarity index 100% rename from app/assets/javascripts/select-kit/app/components/combo-box.js rename to app/assets/javascripts/select-kit/addon/components/combo-box.js diff --git a/app/assets/javascripts/select-kit/app/components/combo-box/combo-box-header.js b/app/assets/javascripts/select-kit/addon/components/combo-box/combo-box-header.js similarity index 100% rename from app/assets/javascripts/select-kit/app/components/combo-box/combo-box-header.js rename to app/assets/javascripts/select-kit/addon/components/combo-box/combo-box-header.js diff --git a/app/assets/javascripts/select-kit/app/components/composer-actions.js b/app/assets/javascripts/select-kit/addon/components/composer-actions.js similarity index 100% rename from app/assets/javascripts/select-kit/app/components/composer-actions.js rename to app/assets/javascripts/select-kit/addon/components/composer-actions.js diff --git a/app/assets/javascripts/select-kit/app/components/create-color-row.js b/app/assets/javascripts/select-kit/addon/components/create-color-row.js similarity index 100% rename from app/assets/javascripts/select-kit/app/components/create-color-row.js rename to app/assets/javascripts/select-kit/addon/components/create-color-row.js diff --git a/app/assets/javascripts/select-kit/app/components/dropdown-select-box.js b/app/assets/javascripts/select-kit/addon/components/dropdown-select-box.js similarity index 100% rename from app/assets/javascripts/select-kit/app/components/dropdown-select-box.js rename to app/assets/javascripts/select-kit/addon/components/dropdown-select-box.js diff --git a/app/assets/javascripts/select-kit/app/components/dropdown-select-box/dropdown-select-box-header.js b/app/assets/javascripts/select-kit/addon/components/dropdown-select-box/dropdown-select-box-header.js similarity index 100% rename from app/assets/javascripts/select-kit/app/components/dropdown-select-box/dropdown-select-box-header.js rename to app/assets/javascripts/select-kit/addon/components/dropdown-select-box/dropdown-select-box-header.js diff --git a/app/assets/javascripts/select-kit/app/components/dropdown-select-box/dropdown-select-box-row.js b/app/assets/javascripts/select-kit/addon/components/dropdown-select-box/dropdown-select-box-row.js similarity index 100% rename from app/assets/javascripts/select-kit/app/components/dropdown-select-box/dropdown-select-box-row.js rename to app/assets/javascripts/select-kit/addon/components/dropdown-select-box/dropdown-select-box-row.js diff --git a/app/assets/javascripts/select-kit/app/components/future-date-input-selector.js b/app/assets/javascripts/select-kit/addon/components/future-date-input-selector.js similarity index 100% rename from app/assets/javascripts/select-kit/app/components/future-date-input-selector.js rename to app/assets/javascripts/select-kit/addon/components/future-date-input-selector.js diff --git a/app/assets/javascripts/select-kit/app/components/future-date-input-selector/future-date-input-selector-header.js b/app/assets/javascripts/select-kit/addon/components/future-date-input-selector/future-date-input-selector-header.js similarity index 100% rename from app/assets/javascripts/select-kit/app/components/future-date-input-selector/future-date-input-selector-header.js rename to app/assets/javascripts/select-kit/addon/components/future-date-input-selector/future-date-input-selector-header.js diff --git a/app/assets/javascripts/select-kit/app/components/future-date-input-selector/future-date-input-selector-row.js b/app/assets/javascripts/select-kit/addon/components/future-date-input-selector/future-date-input-selector-row.js similarity index 100% rename from app/assets/javascripts/select-kit/app/components/future-date-input-selector/future-date-input-selector-row.js rename to app/assets/javascripts/select-kit/addon/components/future-date-input-selector/future-date-input-selector-row.js diff --git a/app/assets/javascripts/select-kit/app/components/future-date-input-selector/mixin.js b/app/assets/javascripts/select-kit/addon/components/future-date-input-selector/mixin.js similarity index 100% rename from app/assets/javascripts/select-kit/app/components/future-date-input-selector/mixin.js rename to app/assets/javascripts/select-kit/addon/components/future-date-input-selector/mixin.js diff --git a/app/assets/javascripts/select-kit/app/components/group-dropdown.js b/app/assets/javascripts/select-kit/addon/components/group-dropdown.js similarity index 100% rename from app/assets/javascripts/select-kit/app/components/group-dropdown.js rename to app/assets/javascripts/select-kit/addon/components/group-dropdown.js diff --git a/app/assets/javascripts/select-kit/app/components/group-members-dropdown.js b/app/assets/javascripts/select-kit/addon/components/group-members-dropdown.js similarity index 100% rename from app/assets/javascripts/select-kit/app/components/group-members-dropdown.js rename to app/assets/javascripts/select-kit/addon/components/group-members-dropdown.js diff --git a/app/assets/javascripts/select-kit/app/components/group-notifications-button.js b/app/assets/javascripts/select-kit/addon/components/group-notifications-button.js similarity index 100% rename from app/assets/javascripts/select-kit/app/components/group-notifications-button.js rename to app/assets/javascripts/select-kit/addon/components/group-notifications-button.js diff --git a/app/assets/javascripts/select-kit/app/components/icon-picker.js b/app/assets/javascripts/select-kit/addon/components/icon-picker.js similarity index 100% rename from app/assets/javascripts/select-kit/app/components/icon-picker.js rename to app/assets/javascripts/select-kit/addon/components/icon-picker.js diff --git a/app/assets/javascripts/select-kit/app/components/list-setting.js b/app/assets/javascripts/select-kit/addon/components/list-setting.js similarity index 100% rename from app/assets/javascripts/select-kit/app/components/list-setting.js rename to app/assets/javascripts/select-kit/addon/components/list-setting.js diff --git a/app/assets/javascripts/select-kit/app/components/mini-tag-chooser.js b/app/assets/javascripts/select-kit/addon/components/mini-tag-chooser.js similarity index 100% rename from app/assets/javascripts/select-kit/app/components/mini-tag-chooser.js rename to app/assets/javascripts/select-kit/addon/components/mini-tag-chooser.js diff --git a/app/assets/javascripts/select-kit/app/components/mini-tag-chooser/mini-tag-chooser-header.js b/app/assets/javascripts/select-kit/addon/components/mini-tag-chooser/mini-tag-chooser-header.js similarity index 100% rename from app/assets/javascripts/select-kit/app/components/mini-tag-chooser/mini-tag-chooser-header.js rename to app/assets/javascripts/select-kit/addon/components/mini-tag-chooser/mini-tag-chooser-header.js diff --git a/app/assets/javascripts/select-kit/app/components/mini-tag-chooser/selected-collection.js b/app/assets/javascripts/select-kit/addon/components/mini-tag-chooser/selected-collection.js similarity index 100% rename from app/assets/javascripts/select-kit/app/components/mini-tag-chooser/selected-collection.js rename to app/assets/javascripts/select-kit/addon/components/mini-tag-chooser/selected-collection.js diff --git a/app/assets/javascripts/select-kit/app/components/multi-select.js b/app/assets/javascripts/select-kit/addon/components/multi-select.js similarity index 100% rename from app/assets/javascripts/select-kit/app/components/multi-select.js rename to app/assets/javascripts/select-kit/addon/components/multi-select.js diff --git a/app/assets/javascripts/select-kit/app/components/multi-select/multi-select-filter.js b/app/assets/javascripts/select-kit/addon/components/multi-select/multi-select-filter.js similarity index 100% rename from app/assets/javascripts/select-kit/app/components/multi-select/multi-select-filter.js rename to app/assets/javascripts/select-kit/addon/components/multi-select/multi-select-filter.js diff --git a/app/assets/javascripts/select-kit/app/components/multi-select/multi-select-header.js b/app/assets/javascripts/select-kit/addon/components/multi-select/multi-select-header.js similarity index 100% rename from app/assets/javascripts/select-kit/app/components/multi-select/multi-select-header.js rename to app/assets/javascripts/select-kit/addon/components/multi-select/multi-select-header.js diff --git a/app/assets/javascripts/select-kit/app/components/multi-select/selected-category.js b/app/assets/javascripts/select-kit/addon/components/multi-select/selected-category.js similarity index 100% rename from app/assets/javascripts/select-kit/app/components/multi-select/selected-category.js rename to app/assets/javascripts/select-kit/addon/components/multi-select/selected-category.js diff --git a/app/assets/javascripts/select-kit/app/components/multi-select/selected-color.js b/app/assets/javascripts/select-kit/addon/components/multi-select/selected-color.js similarity index 100% rename from app/assets/javascripts/select-kit/app/components/multi-select/selected-color.js rename to app/assets/javascripts/select-kit/addon/components/multi-select/selected-color.js diff --git a/app/assets/javascripts/select-kit/app/components/none-category-row.js b/app/assets/javascripts/select-kit/addon/components/none-category-row.js similarity index 100% rename from app/assets/javascripts/select-kit/app/components/none-category-row.js rename to app/assets/javascripts/select-kit/addon/components/none-category-row.js diff --git a/app/assets/javascripts/select-kit/app/components/notifications-button.js b/app/assets/javascripts/select-kit/addon/components/notifications-button.js similarity index 96% rename from app/assets/javascripts/select-kit/app/components/notifications-button.js rename to app/assets/javascripts/select-kit/addon/components/notifications-button.js index f5423b9e64..32dc2ad7ba 100644 --- a/app/assets/javascripts/select-kit/app/components/notifications-button.js +++ b/app/assets/javascripts/select-kit/addon/components/notifications-button.js @@ -13,8 +13,7 @@ export default DropdownSelectBoxComponent.extend({ autoFilterable: false, filterable: false, i18nPrefix: "", - i18nPostfix: "", - showCaret: true + i18nPostfix: "" }, modifyComponentForRow() { diff --git a/app/assets/javascripts/select-kit/app/components/notifications-button/notifications-button-row.js b/app/assets/javascripts/select-kit/addon/components/notifications-button/notifications-button-row.js similarity index 100% rename from app/assets/javascripts/select-kit/app/components/notifications-button/notifications-button-row.js rename to app/assets/javascripts/select-kit/addon/components/notifications-button/notifications-button-row.js diff --git a/app/assets/javascripts/select-kit/app/components/notifications-filter.js b/app/assets/javascripts/select-kit/addon/components/notifications-filter.js similarity index 100% rename from app/assets/javascripts/select-kit/app/components/notifications-filter.js rename to app/assets/javascripts/select-kit/addon/components/notifications-filter.js diff --git a/app/assets/javascripts/select-kit/app/components/notifications-filter/notifications-filter-header.js b/app/assets/javascripts/select-kit/addon/components/notifications-filter/notifications-filter-header.js similarity index 100% rename from app/assets/javascripts/select-kit/app/components/notifications-filter/notifications-filter-header.js rename to app/assets/javascripts/select-kit/addon/components/notifications-filter/notifications-filter-header.js diff --git a/app/assets/javascripts/select-kit/app/components/period-chooser.js b/app/assets/javascripts/select-kit/addon/components/period-chooser.js similarity index 100% rename from app/assets/javascripts/select-kit/app/components/period-chooser.js rename to app/assets/javascripts/select-kit/addon/components/period-chooser.js diff --git a/app/assets/javascripts/select-kit/app/components/period-chooser/period-chooser-header.js b/app/assets/javascripts/select-kit/addon/components/period-chooser/period-chooser-header.js similarity index 100% rename from app/assets/javascripts/select-kit/app/components/period-chooser/period-chooser-header.js rename to app/assets/javascripts/select-kit/addon/components/period-chooser/period-chooser-header.js diff --git a/app/assets/javascripts/select-kit/app/components/period-chooser/period-chooser-row.js b/app/assets/javascripts/select-kit/addon/components/period-chooser/period-chooser-row.js similarity index 100% rename from app/assets/javascripts/select-kit/app/components/period-chooser/period-chooser-row.js rename to app/assets/javascripts/select-kit/addon/components/period-chooser/period-chooser-row.js diff --git a/app/assets/javascripts/select-kit/app/components/pinned-button.js b/app/assets/javascripts/select-kit/addon/components/pinned-button.js similarity index 100% rename from app/assets/javascripts/select-kit/app/components/pinned-button.js rename to app/assets/javascripts/select-kit/addon/components/pinned-button.js diff --git a/app/assets/javascripts/select-kit/app/components/pinned-options.js b/app/assets/javascripts/select-kit/addon/components/pinned-options.js similarity index 100% rename from app/assets/javascripts/select-kit/app/components/pinned-options.js rename to app/assets/javascripts/select-kit/addon/components/pinned-options.js diff --git a/app/assets/javascripts/select-kit/app/components/search-advanced-category-chooser.js b/app/assets/javascripts/select-kit/addon/components/search-advanced-category-chooser.js similarity index 100% rename from app/assets/javascripts/select-kit/app/components/search-advanced-category-chooser.js rename to app/assets/javascripts/select-kit/addon/components/search-advanced-category-chooser.js diff --git a/app/assets/javascripts/select-kit/app/components/select-kit.js b/app/assets/javascripts/select-kit/addon/components/select-kit.js similarity index 99% rename from app/assets/javascripts/select-kit/app/components/select-kit.js rename to app/assets/javascripts/select-kit/addon/components/select-kit.js index f2fbbbf1c2..02acfb5a2e 100644 --- a/app/assets/javascripts/select-kit/app/components/select-kit.js +++ b/app/assets/javascripts/select-kit/addon/components/select-kit.js @@ -1,5 +1,6 @@ import I18n from "I18n"; -import EmberObject, { computed, get, guidFor } from "@ember/object"; +import EmberObject, { computed, get } from "@ember/object"; +import { guidFor } from "@ember/object/internals"; import Component from "@ember/component"; import deprecated from "discourse-common/lib/deprecated"; import { makeArray } from "discourse-common/lib/helpers"; @@ -271,7 +272,8 @@ export default Component.extend( selectedNameComponent: "selected-name", castInteger: false, preventsClickPropagation: false, - focusAfterOnChange: true + focusAfterOnChange: true, + triggerOnChangeOnTab: true }, autoFilterable: computed("content.[]", "selectKit.filter", function() { diff --git a/app/assets/javascripts/select-kit/app/components/select-kit/errors-collection.js b/app/assets/javascripts/select-kit/addon/components/select-kit/errors-collection.js similarity index 100% rename from app/assets/javascripts/select-kit/app/components/select-kit/errors-collection.js rename to app/assets/javascripts/select-kit/addon/components/select-kit/errors-collection.js diff --git a/app/assets/javascripts/select-kit/app/components/select-kit/select-kit-body.js b/app/assets/javascripts/select-kit/addon/components/select-kit/select-kit-body.js similarity index 100% rename from app/assets/javascripts/select-kit/app/components/select-kit/select-kit-body.js rename to app/assets/javascripts/select-kit/addon/components/select-kit/select-kit-body.js diff --git a/app/assets/javascripts/select-kit/app/components/select-kit/select-kit-collection.js b/app/assets/javascripts/select-kit/addon/components/select-kit/select-kit-collection.js similarity index 100% rename from app/assets/javascripts/select-kit/app/components/select-kit/select-kit-collection.js rename to app/assets/javascripts/select-kit/addon/components/select-kit/select-kit-collection.js diff --git a/app/assets/javascripts/select-kit/app/components/select-kit/select-kit-create-row.js b/app/assets/javascripts/select-kit/addon/components/select-kit/select-kit-create-row.js similarity index 100% rename from app/assets/javascripts/select-kit/app/components/select-kit/select-kit-create-row.js rename to app/assets/javascripts/select-kit/addon/components/select-kit/select-kit-create-row.js diff --git a/app/assets/javascripts/select-kit/app/components/select-kit/select-kit-filter.js b/app/assets/javascripts/select-kit/addon/components/select-kit/select-kit-filter.js similarity index 100% rename from app/assets/javascripts/select-kit/app/components/select-kit/select-kit-filter.js rename to app/assets/javascripts/select-kit/addon/components/select-kit/select-kit-filter.js diff --git a/app/assets/javascripts/select-kit/app/components/select-kit/select-kit-header.js b/app/assets/javascripts/select-kit/addon/components/select-kit/select-kit-header.js similarity index 97% rename from app/assets/javascripts/select-kit/app/components/select-kit/select-kit-header.js rename to app/assets/javascripts/select-kit/addon/components/select-kit/select-kit-header.js index 785b2a4702..5697826f59 100644 --- a/app/assets/javascripts/select-kit/app/components/select-kit/select-kit-header.js +++ b/app/assets/javascripts/select-kit/addon/components/select-kit/select-kit-header.js @@ -140,7 +140,11 @@ export default Component.extend(UtilsMixin, { this._focusFilterInput(); } else if (event.keyCode === 9) { // Tab - if (this.selectKit.highlighted && this.selectKit.isExpanded) { + if ( + this.selectKit.highlighted && + this.selectKit.isExpanded && + this.selectKit.options.triggerOnChangeOnTab + ) { this.selectKit.select( this.getValue(this.selectKit.highlighted), this.selectKit.highlighted diff --git a/app/assets/javascripts/select-kit/app/components/select-kit/select-kit-none-row.js b/app/assets/javascripts/select-kit/addon/components/select-kit/select-kit-none-row.js similarity index 100% rename from app/assets/javascripts/select-kit/app/components/select-kit/select-kit-none-row.js rename to app/assets/javascripts/select-kit/addon/components/select-kit/select-kit-none-row.js diff --git a/app/assets/javascripts/select-kit/app/components/select-kit/select-kit-row.js b/app/assets/javascripts/select-kit/addon/components/select-kit/select-kit-row.js similarity index 100% rename from app/assets/javascripts/select-kit/app/components/select-kit/select-kit-row.js rename to app/assets/javascripts/select-kit/addon/components/select-kit/select-kit-row.js diff --git a/app/assets/javascripts/select-kit/app/components/select-kit/single-select-header.js b/app/assets/javascripts/select-kit/addon/components/select-kit/single-select-header.js similarity index 100% rename from app/assets/javascripts/select-kit/app/components/select-kit/single-select-header.js rename to app/assets/javascripts/select-kit/addon/components/select-kit/single-select-header.js diff --git a/app/assets/javascripts/select-kit/app/components/selected-color.js b/app/assets/javascripts/select-kit/addon/components/selected-color.js similarity index 100% rename from app/assets/javascripts/select-kit/app/components/selected-color.js rename to app/assets/javascripts/select-kit/addon/components/selected-color.js diff --git a/app/assets/javascripts/select-kit/app/components/selected-name.js b/app/assets/javascripts/select-kit/addon/components/selected-name.js similarity index 100% rename from app/assets/javascripts/select-kit/app/components/selected-name.js rename to app/assets/javascripts/select-kit/addon/components/selected-name.js diff --git a/app/assets/javascripts/select-kit/app/components/single-select.js b/app/assets/javascripts/select-kit/addon/components/single-select.js similarity index 100% rename from app/assets/javascripts/select-kit/app/components/single-select.js rename to app/assets/javascripts/select-kit/addon/components/single-select.js diff --git a/app/assets/javascripts/select-kit/app/components/tag-chooser-row.js b/app/assets/javascripts/select-kit/addon/components/tag-chooser-row.js similarity index 100% rename from app/assets/javascripts/select-kit/app/components/tag-chooser-row.js rename to app/assets/javascripts/select-kit/addon/components/tag-chooser-row.js diff --git a/app/assets/javascripts/select-kit/app/components/tag-chooser.js b/app/assets/javascripts/select-kit/addon/components/tag-chooser.js similarity index 100% rename from app/assets/javascripts/select-kit/app/components/tag-chooser.js rename to app/assets/javascripts/select-kit/addon/components/tag-chooser.js diff --git a/app/assets/javascripts/select-kit/app/components/tag-drop.js b/app/assets/javascripts/select-kit/addon/components/tag-drop.js similarity index 100% rename from app/assets/javascripts/select-kit/app/components/tag-drop.js rename to app/assets/javascripts/select-kit/addon/components/tag-drop.js diff --git a/app/assets/javascripts/select-kit/app/components/tag-drop/tag-drop-header.js b/app/assets/javascripts/select-kit/addon/components/tag-drop/tag-drop-header.js similarity index 100% rename from app/assets/javascripts/select-kit/app/components/tag-drop/tag-drop-header.js rename to app/assets/javascripts/select-kit/addon/components/tag-drop/tag-drop-header.js diff --git a/app/assets/javascripts/select-kit/app/components/tag-group-chooser.js b/app/assets/javascripts/select-kit/addon/components/tag-group-chooser.js similarity index 100% rename from app/assets/javascripts/select-kit/app/components/tag-group-chooser.js rename to app/assets/javascripts/select-kit/addon/components/tag-group-chooser.js diff --git a/app/assets/javascripts/select-kit/app/components/tag-notifications-button.js b/app/assets/javascripts/select-kit/addon/components/tag-notifications-button.js similarity index 100% rename from app/assets/javascripts/select-kit/app/components/tag-notifications-button.js rename to app/assets/javascripts/select-kit/addon/components/tag-notifications-button.js diff --git a/app/assets/javascripts/select-kit/app/components/tag-row.js b/app/assets/javascripts/select-kit/addon/components/tag-row.js similarity index 100% rename from app/assets/javascripts/select-kit/app/components/tag-row.js rename to app/assets/javascripts/select-kit/addon/components/tag-row.js diff --git a/app/assets/javascripts/select-kit/app/components/timezone-input.js b/app/assets/javascripts/select-kit/addon/components/timezone-input.js similarity index 100% rename from app/assets/javascripts/select-kit/app/components/timezone-input.js rename to app/assets/javascripts/select-kit/addon/components/timezone-input.js diff --git a/app/assets/javascripts/select-kit/app/components/toolbar-popup-menu-options.js b/app/assets/javascripts/select-kit/addon/components/toolbar-popup-menu-options.js similarity index 100% rename from app/assets/javascripts/select-kit/app/components/toolbar-popup-menu-options.js rename to app/assets/javascripts/select-kit/addon/components/toolbar-popup-menu-options.js diff --git a/app/assets/javascripts/select-kit/app/components/toolbar-popup-menu-options/toolbar-popup-menu-options-heading.js b/app/assets/javascripts/select-kit/addon/components/toolbar-popup-menu-options/toolbar-popup-menu-options-heading.js similarity index 100% rename from app/assets/javascripts/select-kit/app/components/toolbar-popup-menu-options/toolbar-popup-menu-options-heading.js rename to app/assets/javascripts/select-kit/addon/components/toolbar-popup-menu-options/toolbar-popup-menu-options-heading.js diff --git a/app/assets/javascripts/select-kit/app/components/topic-footer-mobile-dropdown.js b/app/assets/javascripts/select-kit/addon/components/topic-footer-mobile-dropdown.js similarity index 100% rename from app/assets/javascripts/select-kit/app/components/topic-footer-mobile-dropdown.js rename to app/assets/javascripts/select-kit/addon/components/topic-footer-mobile-dropdown.js diff --git a/app/assets/javascripts/select-kit/app/components/topic-notifications-button.js b/app/assets/javascripts/select-kit/addon/components/topic-notifications-button.js similarity index 55% rename from app/assets/javascripts/select-kit/app/components/topic-notifications-button.js rename to app/assets/javascripts/select-kit/addon/components/topic-notifications-button.js index 58329393e7..d9c0900182 100644 --- a/app/assets/javascripts/select-kit/app/components/topic-notifications-button.js +++ b/app/assets/javascripts/select-kit/addon/components/topic-notifications-button.js @@ -1,20 +1,28 @@ import Component from "@ember/component"; -import { action } from "@ember/object"; +import { action, computed } from "@ember/object"; export default Component.extend({ layoutName: "select-kit/templates/components/topic-notifications-button", classNames: ["topic-notifications-button"], + classNameBindings: ["isLoading"], appendReason: true, showFullTitle: true, placement: "bottom-start", notificationLevel: null, topic: null, showCaret: true, + isLoading: false, + icon: computed("isLoading", function() { + return this.isLoading ? "spinner" : null; + }), @action changeTopicNotificationLevel(levelId) { if (levelId !== this.notificationLevel) { - this.topic.details.updateNotifications(levelId); + this.set("isLoading", true); + this.topic.details + .updateNotifications(levelId) + .finally(() => this.set("isLoading", false)); } } }); diff --git a/app/assets/javascripts/select-kit/app/components/topic-notifications-options.js b/app/assets/javascripts/select-kit/addon/components/topic-notifications-options.js similarity index 100% rename from app/assets/javascripts/select-kit/app/components/topic-notifications-options.js rename to app/assets/javascripts/select-kit/addon/components/topic-notifications-options.js diff --git a/app/assets/javascripts/select-kit/app/components/user-chooser.js b/app/assets/javascripts/select-kit/addon/components/user-chooser.js similarity index 100% rename from app/assets/javascripts/select-kit/app/components/user-chooser.js rename to app/assets/javascripts/select-kit/addon/components/user-chooser.js diff --git a/app/assets/javascripts/select-kit/app/components/user-chooser/user-row.js b/app/assets/javascripts/select-kit/addon/components/user-chooser/user-row.js similarity index 100% rename from app/assets/javascripts/select-kit/app/components/user-chooser/user-row.js rename to app/assets/javascripts/select-kit/addon/components/user-chooser/user-row.js diff --git a/app/assets/javascripts/select-kit/app/components/user-notifications-dropdown.js b/app/assets/javascripts/select-kit/addon/components/user-notifications-dropdown.js similarity index 100% rename from app/assets/javascripts/select-kit/app/components/user-notifications-dropdown.js rename to app/assets/javascripts/select-kit/addon/components/user-notifications-dropdown.js diff --git a/app/assets/javascripts/select-kit/app/mixins/plugin-api.js b/app/assets/javascripts/select-kit/addon/mixins/plugin-api.js similarity index 100% rename from app/assets/javascripts/select-kit/app/mixins/plugin-api.js rename to app/assets/javascripts/select-kit/addon/mixins/plugin-api.js diff --git a/app/assets/javascripts/select-kit/app/mixins/tags.js b/app/assets/javascripts/select-kit/addon/mixins/tags.js similarity index 100% rename from app/assets/javascripts/select-kit/app/mixins/tags.js rename to app/assets/javascripts/select-kit/addon/mixins/tags.js diff --git a/app/assets/javascripts/select-kit/app/mixins/utils.js b/app/assets/javascripts/select-kit/addon/mixins/utils.js similarity index 100% rename from app/assets/javascripts/select-kit/app/mixins/utils.js rename to app/assets/javascripts/select-kit/addon/mixins/utils.js diff --git a/app/assets/javascripts/select-kit/app/templates/components/category-drop/category-drop-header.hbs b/app/assets/javascripts/select-kit/addon/templates/components/category-drop/category-drop-header.hbs similarity index 100% rename from app/assets/javascripts/select-kit/app/templates/components/category-drop/category-drop-header.hbs rename to app/assets/javascripts/select-kit/addon/templates/components/category-drop/category-drop-header.hbs diff --git a/app/assets/javascripts/select-kit/app/templates/components/category-row.hbs b/app/assets/javascripts/select-kit/addon/templates/components/category-row.hbs similarity index 100% rename from app/assets/javascripts/select-kit/app/templates/components/category-row.hbs rename to app/assets/javascripts/select-kit/addon/templates/components/category-row.hbs diff --git a/app/assets/javascripts/select-kit/app/templates/components/color-palettes/color-palettes-row.hbs b/app/assets/javascripts/select-kit/addon/templates/components/color-palettes/color-palettes-row.hbs similarity index 100% rename from app/assets/javascripts/select-kit/app/templates/components/color-palettes/color-palettes-row.hbs rename to app/assets/javascripts/select-kit/addon/templates/components/color-palettes/color-palettes-row.hbs diff --git a/app/assets/javascripts/select-kit/app/templates/components/combo-box/combo-box-header.hbs b/app/assets/javascripts/select-kit/addon/templates/components/combo-box/combo-box-header.hbs similarity index 100% rename from app/assets/javascripts/select-kit/app/templates/components/combo-box/combo-box-header.hbs rename to app/assets/javascripts/select-kit/addon/templates/components/combo-box/combo-box-header.hbs diff --git a/app/assets/javascripts/select-kit/app/templates/components/create-color-row.hbs b/app/assets/javascripts/select-kit/addon/templates/components/create-color-row.hbs similarity index 100% rename from app/assets/javascripts/select-kit/app/templates/components/create-color-row.hbs rename to app/assets/javascripts/select-kit/addon/templates/components/create-color-row.hbs diff --git a/app/assets/javascripts/select-kit/app/templates/components/dropdown-select-box/dropdown-select-box-header.hbs b/app/assets/javascripts/select-kit/addon/templates/components/dropdown-select-box/dropdown-select-box-header.hbs similarity index 100% rename from app/assets/javascripts/select-kit/app/templates/components/dropdown-select-box/dropdown-select-box-header.hbs rename to app/assets/javascripts/select-kit/addon/templates/components/dropdown-select-box/dropdown-select-box-header.hbs diff --git a/app/assets/javascripts/select-kit/app/templates/components/dropdown-select-box/dropdown-select-box-row.hbs b/app/assets/javascripts/select-kit/addon/templates/components/dropdown-select-box/dropdown-select-box-row.hbs similarity index 100% rename from app/assets/javascripts/select-kit/app/templates/components/dropdown-select-box/dropdown-select-box-row.hbs rename to app/assets/javascripts/select-kit/addon/templates/components/dropdown-select-box/dropdown-select-box-row.hbs diff --git a/app/assets/javascripts/select-kit/app/templates/components/future-date-input-selector/future-date-input-selector-header.hbs b/app/assets/javascripts/select-kit/addon/templates/components/future-date-input-selector/future-date-input-selector-header.hbs similarity index 100% rename from app/assets/javascripts/select-kit/app/templates/components/future-date-input-selector/future-date-input-selector-header.hbs rename to app/assets/javascripts/select-kit/addon/templates/components/future-date-input-selector/future-date-input-selector-header.hbs diff --git a/app/assets/javascripts/select-kit/app/templates/components/future-date-input-selector/future-date-input-selector-row.hbs b/app/assets/javascripts/select-kit/addon/templates/components/future-date-input-selector/future-date-input-selector-row.hbs similarity index 100% rename from app/assets/javascripts/select-kit/app/templates/components/future-date-input-selector/future-date-input-selector-row.hbs rename to app/assets/javascripts/select-kit/addon/templates/components/future-date-input-selector/future-date-input-selector-row.hbs diff --git a/app/assets/javascripts/select-kit/app/templates/components/mini-tag-chooser/mini-tag-chooser-header.hbs b/app/assets/javascripts/select-kit/addon/templates/components/mini-tag-chooser/mini-tag-chooser-header.hbs similarity index 100% rename from app/assets/javascripts/select-kit/app/templates/components/mini-tag-chooser/mini-tag-chooser-header.hbs rename to app/assets/javascripts/select-kit/addon/templates/components/mini-tag-chooser/mini-tag-chooser-header.hbs diff --git a/app/assets/javascripts/select-kit/app/templates/components/mini-tag-chooser/selected-collection.hbs b/app/assets/javascripts/select-kit/addon/templates/components/mini-tag-chooser/selected-collection.hbs similarity index 100% rename from app/assets/javascripts/select-kit/app/templates/components/mini-tag-chooser/selected-collection.hbs rename to app/assets/javascripts/select-kit/addon/templates/components/mini-tag-chooser/selected-collection.hbs diff --git a/app/assets/javascripts/select-kit/app/templates/components/multi-select.hbs b/app/assets/javascripts/select-kit/addon/templates/components/multi-select.hbs similarity index 100% rename from app/assets/javascripts/select-kit/app/templates/components/multi-select.hbs rename to app/assets/javascripts/select-kit/addon/templates/components/multi-select.hbs diff --git a/app/assets/javascripts/select-kit/app/templates/components/multi-select/multi-select-header.hbs b/app/assets/javascripts/select-kit/addon/templates/components/multi-select/multi-select-header.hbs similarity index 100% rename from app/assets/javascripts/select-kit/app/templates/components/multi-select/multi-select-header.hbs rename to app/assets/javascripts/select-kit/addon/templates/components/multi-select/multi-select-header.hbs diff --git a/app/assets/javascripts/select-kit/app/templates/components/multi-select/selected-category.hbs b/app/assets/javascripts/select-kit/addon/templates/components/multi-select/selected-category.hbs similarity index 100% rename from app/assets/javascripts/select-kit/app/templates/components/multi-select/selected-category.hbs rename to app/assets/javascripts/select-kit/addon/templates/components/multi-select/selected-category.hbs diff --git a/app/assets/javascripts/select-kit/app/templates/components/notifications-filter/notifications-filter-header.hbs b/app/assets/javascripts/select-kit/addon/templates/components/notifications-filter/notifications-filter-header.hbs similarity index 100% rename from app/assets/javascripts/select-kit/app/templates/components/notifications-filter/notifications-filter-header.hbs rename to app/assets/javascripts/select-kit/addon/templates/components/notifications-filter/notifications-filter-header.hbs diff --git a/app/assets/javascripts/select-kit/app/templates/components/period-chooser/period-chooser-header.hbs b/app/assets/javascripts/select-kit/addon/templates/components/period-chooser/period-chooser-header.hbs similarity index 100% rename from app/assets/javascripts/select-kit/app/templates/components/period-chooser/period-chooser-header.hbs rename to app/assets/javascripts/select-kit/addon/templates/components/period-chooser/period-chooser-header.hbs diff --git a/app/assets/javascripts/select-kit/app/templates/components/period-chooser/period-chooser-row.hbs b/app/assets/javascripts/select-kit/addon/templates/components/period-chooser/period-chooser-row.hbs similarity index 100% rename from app/assets/javascripts/select-kit/app/templates/components/period-chooser/period-chooser-row.hbs rename to app/assets/javascripts/select-kit/addon/templates/components/period-chooser/period-chooser-row.hbs diff --git a/app/assets/javascripts/select-kit/addon/templates/components/pinned-button.hbs b/app/assets/javascripts/select-kit/addon/templates/components/pinned-button.hbs new file mode 100644 index 0000000000..91bfe147ff --- /dev/null +++ b/app/assets/javascripts/select-kit/addon/templates/components/pinned-button.hbs @@ -0,0 +1,4 @@ +

+ {{pinned-options value=pinned topic=topic}} + {{html-safe reasonText}} +

diff --git a/app/assets/javascripts/select-kit/app/templates/components/select-kit/errors-collection.hbs b/app/assets/javascripts/select-kit/addon/templates/components/select-kit/errors-collection.hbs similarity index 100% rename from app/assets/javascripts/select-kit/app/templates/components/select-kit/errors-collection.hbs rename to app/assets/javascripts/select-kit/addon/templates/components/select-kit/errors-collection.hbs diff --git a/app/assets/javascripts/select-kit/app/templates/components/select-kit/select-kit-body.hbs b/app/assets/javascripts/select-kit/addon/templates/components/select-kit/select-kit-body.hbs similarity index 100% rename from app/assets/javascripts/select-kit/app/templates/components/select-kit/select-kit-body.hbs rename to app/assets/javascripts/select-kit/addon/templates/components/select-kit/select-kit-body.hbs diff --git a/app/assets/javascripts/select-kit/app/templates/components/select-kit/select-kit-collection.hbs b/app/assets/javascripts/select-kit/addon/templates/components/select-kit/select-kit-collection.hbs similarity index 100% rename from app/assets/javascripts/select-kit/app/templates/components/select-kit/select-kit-collection.hbs rename to app/assets/javascripts/select-kit/addon/templates/components/select-kit/select-kit-collection.hbs diff --git a/app/assets/javascripts/select-kit/app/templates/components/select-kit/select-kit-filter.hbs b/app/assets/javascripts/select-kit/addon/templates/components/select-kit/select-kit-filter.hbs similarity index 100% rename from app/assets/javascripts/select-kit/app/templates/components/select-kit/select-kit-filter.hbs rename to app/assets/javascripts/select-kit/addon/templates/components/select-kit/select-kit-filter.hbs diff --git a/app/assets/javascripts/select-kit/app/templates/components/select-kit/select-kit-row.hbs b/app/assets/javascripts/select-kit/addon/templates/components/select-kit/select-kit-row.hbs similarity index 100% rename from app/assets/javascripts/select-kit/app/templates/components/select-kit/select-kit-row.hbs rename to app/assets/javascripts/select-kit/addon/templates/components/select-kit/select-kit-row.hbs diff --git a/app/assets/javascripts/select-kit/app/templates/components/select-kit/single-select-header.hbs b/app/assets/javascripts/select-kit/addon/templates/components/select-kit/single-select-header.hbs similarity index 100% rename from app/assets/javascripts/select-kit/app/templates/components/select-kit/single-select-header.hbs rename to app/assets/javascripts/select-kit/addon/templates/components/select-kit/single-select-header.hbs diff --git a/app/assets/javascripts/select-kit/app/templates/components/selected-name.hbs b/app/assets/javascripts/select-kit/addon/templates/components/selected-name.hbs similarity index 100% rename from app/assets/javascripts/select-kit/app/templates/components/selected-name.hbs rename to app/assets/javascripts/select-kit/addon/templates/components/selected-name.hbs diff --git a/app/assets/javascripts/select-kit/app/templates/components/single-select.hbs b/app/assets/javascripts/select-kit/addon/templates/components/single-select.hbs similarity index 100% rename from app/assets/javascripts/select-kit/app/templates/components/single-select.hbs rename to app/assets/javascripts/select-kit/addon/templates/components/single-select.hbs diff --git a/app/assets/javascripts/select-kit/app/templates/components/tag-chooser-row.hbs b/app/assets/javascripts/select-kit/addon/templates/components/tag-chooser-row.hbs similarity index 100% rename from app/assets/javascripts/select-kit/app/templates/components/tag-chooser-row.hbs rename to app/assets/javascripts/select-kit/addon/templates/components/tag-chooser-row.hbs diff --git a/app/assets/javascripts/select-kit/app/templates/components/tag-drop/tag-drop-header.hbs b/app/assets/javascripts/select-kit/addon/templates/components/tag-drop/tag-drop-header.hbs similarity index 100% rename from app/assets/javascripts/select-kit/app/templates/components/tag-drop/tag-drop-header.hbs rename to app/assets/javascripts/select-kit/addon/templates/components/tag-drop/tag-drop-header.hbs diff --git a/app/assets/javascripts/select-kit/app/templates/components/tag-row.hbs b/app/assets/javascripts/select-kit/addon/templates/components/tag-row.hbs similarity index 100% rename from app/assets/javascripts/select-kit/app/templates/components/tag-row.hbs rename to app/assets/javascripts/select-kit/addon/templates/components/tag-row.hbs diff --git a/app/assets/javascripts/select-kit/app/templates/components/toolbar-popup-menu-options/toolbar-popup-menu-options-heading.hbs b/app/assets/javascripts/select-kit/addon/templates/components/toolbar-popup-menu-options/toolbar-popup-menu-options-heading.hbs similarity index 100% rename from app/assets/javascripts/select-kit/app/templates/components/toolbar-popup-menu-options/toolbar-popup-menu-options-heading.hbs rename to app/assets/javascripts/select-kit/addon/templates/components/toolbar-popup-menu-options/toolbar-popup-menu-options-heading.hbs diff --git a/app/assets/javascripts/select-kit/addon/templates/components/topic-notifications-button.hbs b/app/assets/javascripts/select-kit/addon/templates/components/topic-notifications-button.hbs new file mode 100644 index 0000000000..f21111ebbd --- /dev/null +++ b/app/assets/javascripts/select-kit/addon/templates/components/topic-notifications-button.hbs @@ -0,0 +1,30 @@ +{{#if appendReason}} +

+ {{topic-notifications-options + value=notificationLevel + topic=topic + onChange=(action "changeTopicNotificationLevel") + options=(hash + icon=icon + showFullTitle=showFullTitle + placement=placement + preventsClickPropagation=true + showCaret=showCaret + ) + }} + {{html-safe topic.details.notificationReasonText}} +

+{{else}} + {{topic-notifications-options + value=notificationLevel + topic=topic + onChange=(action "changeTopicNotificationLevel") + options=(hash + icon=icon + showFullTitle=showFullTitle + placement=placement + preventsClickPropagation=true + showCaret=showCaret + ) + }} +{{/if}} diff --git a/app/assets/javascripts/select-kit/app/templates/components/user-chooser/user-row.hbs b/app/assets/javascripts/select-kit/addon/templates/components/user-chooser/user-row.hbs similarity index 100% rename from app/assets/javascripts/select-kit/app/templates/components/user-chooser/user-row.hbs rename to app/assets/javascripts/select-kit/addon/templates/components/user-chooser/user-row.hbs diff --git a/app/assets/javascripts/select-kit/app/templates/components/pinned-button.hbs b/app/assets/javascripts/select-kit/app/templates/components/pinned-button.hbs deleted file mode 100644 index f54bdb6b5f..0000000000 --- a/app/assets/javascripts/select-kit/app/templates/components/pinned-button.hbs +++ /dev/null @@ -1,5 +0,0 @@ -{{pinned-options value=pinned topic=topic}} - -

- {{html-safe reasonText}} -

diff --git a/app/assets/javascripts/select-kit/app/templates/components/topic-notifications-button.hbs b/app/assets/javascripts/select-kit/app/templates/components/topic-notifications-button.hbs deleted file mode 100644 index 50dd20e56e..0000000000 --- a/app/assets/javascripts/select-kit/app/templates/components/topic-notifications-button.hbs +++ /dev/null @@ -1,17 +0,0 @@ -{{topic-notifications-options - value=notificationLevel - topic=topic - onChange=(action "changeTopicNotificationLevel") - options=(hash - showFullTitle=showFullTitle - placement=placement - preventsClickPropagation=true - showCaret=showCaret - ) -}} - -{{#if appendReason}} -

- {{html-safe topic.details.notificationReasonText}} -

-{{/if}} diff --git a/app/assets/javascripts/wizard-application.js b/app/assets/javascripts/wizard-application.js index b8e8797f0c..4e2a7adc07 100644 --- a/app/assets/javascripts/wizard-application.js +++ b/app/assets/javascripts/wizard-application.js @@ -1,6 +1,6 @@ //= require_tree ./discourse-common/addon //= require i18n-patches -//= require_tree ./select-kit/app +//= require_tree ./select-kit/addon //= require wizard/router //= require wizard/wizard //= require_tree ./wizard/templates diff --git a/app/assets/stylesheets/common/base/_topic-list.scss b/app/assets/stylesheets/common/base/_topic-list.scss index aa51fccce1..a80e54c4fd 100644 --- a/app/assets/stylesheets/common/base/_topic-list.scss +++ b/app/assets/stylesheets/common/base/_topic-list.scss @@ -53,7 +53,6 @@ color: $primary-medium; } span.badge-category { - font-weight: normal; color: $primary-medium; } a.discourse-tag { diff --git a/app/assets/stylesheets/common/base/alert.scss b/app/assets/stylesheets/common/base/alert.scss index d729cdc742..05a2902905 100644 --- a/app/assets/stylesheets/common/base/alert.scss +++ b/app/assets/stylesheets/common/base/alert.scss @@ -3,6 +3,7 @@ background-color: $danger-low; color: $primary; position: relative; + margin-bottom: 1em; .close { font-size: $font-up-3; diff --git a/app/assets/stylesheets/common/base/crawler_layout.scss b/app/assets/stylesheets/common/base/crawler_layout.scss index c279aceff4..bff0addac1 100644 --- a/app/assets/stylesheets/common/base/crawler_layout.scss +++ b/app/assets/stylesheets/common/base/crawler_layout.scss @@ -27,6 +27,36 @@ body.crawler { .topic-list { margin-bottom: 1em; + @media (max-width: 850px) { + td { + word-break: break-all; + &.posters { + a:not(:last-of-type) { + display: none; + } + a:last-of-type { + display: block; + } + } + } + + td, + th { + &.views { + display: none; + } + } + + .link-top-line { + a.title { + padding: 0; + } + } + + .link-bottom-line { + margin-top: 0.25em; + } + } } footer { diff --git a/app/assets/stylesheets/common/base/topic-post.scss b/app/assets/stylesheets/common/base/topic-post.scss index 6e465bbc6b..48c361c164 100644 --- a/app/assets/stylesheets/common/base/topic-post.scss +++ b/app/assets/stylesheets/common/base/topic-post.scss @@ -1005,10 +1005,51 @@ a.mention-group { } #topic-footer-buttons { - .reason { - color: $primary-high; - display: inline; - margin: 0 0 0 8px; - line-height: $line-height-medium; + padding: 0.5em 0; + + .topic-footer-main-buttons { + margin: 0 0 -0.5em 0; + display: flex; + flex-wrap: wrap; + + > .btn { + margin: 0 0.5em 0.5em 0; + display: inline-flex; + align-items: center; + + .d-button-label { + display: flex; + flex: 1 0 auto; + align-items: center; + } + } + + .topic-admin-menu-button-container { + display: inline-flex; + } + + .topic-admin-menu-button-container > span:not(:empty) { + margin: 0 0.5em 0.5em 0; + } + } + + .pinned-button:not(.is-hidden) + .topic-notifications-button { + margin-top: 0; + } + + .pinned-button, + .topic-notifications-button { + margin: 1em 0; + + .reason { + color: $primary-high; + display: inline-flex; + margin: 0; + align-items: center; + + .text { + margin-left: 0.5em; + } + } } } diff --git a/app/assets/stylesheets/common/base/user.scss b/app/assets/stylesheets/common/base/user.scss index 587af3c353..4d5e9f4288 100644 --- a/app/assets/stylesheets/common/base/user.scss +++ b/app/assets/stylesheets/common/base/user.scss @@ -653,6 +653,9 @@ padding: 5px 0; font-weight: bold; } + .save-theme-alert { + font-size: $font-down-1; + } } .paginated-topics-list { diff --git a/app/assets/stylesheets/common/components/badges.scss b/app/assets/stylesheets/common/components/badges.scss index 3881ee7b70..20ed535931 100644 --- a/app/assets/stylesheets/common/components/badges.scss +++ b/app/assets/stylesheets/common/components/badges.scss @@ -17,7 +17,6 @@ .badge-wrapper { font-size: $font-down-1; - font-weight: bold; white-space: nowrap; position: relative; display: inline-flex; diff --git a/app/assets/stylesheets/common/select-kit/category-row.scss b/app/assets/stylesheets/common/select-kit/category-row.scss index 2bbe33c96e..df43c95603 100644 --- a/app/assets/stylesheets/common/select-kit/category-row.scss +++ b/app/assets/stylesheets/common/select-kit/category-row.scss @@ -9,6 +9,9 @@ -ms-flex: 1 1 auto; flex: 1 1 auto; } + .category-desc p { + margin: 0; + } .category-status { .badge-wrapper.box { margin-bottom: 1px; diff --git a/app/assets/stylesheets/common/select-kit/pinned-button.scss b/app/assets/stylesheets/common/select-kit/pinned-button.scss index f080b8a016..3e51092ae3 100644 --- a/app/assets/stylesheets/common/select-kit/pinned-button.scss +++ b/app/assets/stylesheets/common/select-kit/pinned-button.scss @@ -1,28 +1,7 @@ #topic-footer-buttons { .pinned-button { - min-width: auto; - margin: 1em 0; - &.is-hidden { display: none; } - - .btn { - margin: 0; - } - - .reason { - display: inline; - line-height: $line-height-medium; - } - } -} - -.pinned-button { - margin: 0; - min-width: auto; - - .pinned-options { - display: inline; } } diff --git a/app/assets/stylesheets/common/select-kit/select-kit.scss b/app/assets/stylesheets/common/select-kit/select-kit.scss index 130bad52f8..a9769b5971 100644 --- a/app/assets/stylesheets/common/select-kit/select-kit.scss +++ b/app/assets/stylesheets/common/select-kit/select-kit.scss @@ -63,6 +63,11 @@ flex-direction: row; min-height: 30px; + .d-icon-spinner { + -webkit-animation: rotate-forever 1s infinite linear; + animation: rotate-forever 1s infinite linear; + } + .selected-name { text-align: left; flex: 0 1 auto; diff --git a/app/assets/stylesheets/common/select-kit/topic-notifications-button.scss b/app/assets/stylesheets/common/select-kit/topic-notifications-button.scss index 1e7c797205..b3808031a1 100644 --- a/app/assets/stylesheets/common/select-kit/topic-notifications-button.scss +++ b/app/assets/stylesheets/common/select-kit/topic-notifications-button.scss @@ -1,27 +1,18 @@ -#topic-footer-buttons { - .topic-notifications-button { - min-width: auto; - margin: 1em 0; +.topic-notifications-button { + &.is-loading { + pointer-events: none; + user-select: none; - .btn { + .d-icon-spinner { margin: 0; } - .reason { - display: inline; - line-height: $line-height-medium; + .selected-name .d-icon { + display: none; + } + + .topic-notifications-options { + opacity: 0.5; } } } - -.topic-notifications-button .topic-notifications-options { - min-width: auto; -} - -.topic-notifications-button { - margin: 0; - - .topic-notifications-options { - display: inline-flex; - } -} diff --git a/app/assets/stylesheets/desktop.scss b/app/assets/stylesheets/desktop.scss index 70229afbbc..e9213e897b 100644 --- a/app/assets/stylesheets/desktop.scss +++ b/app/assets/stylesheets/desktop.scss @@ -1,24 +1,6 @@ @import "common"; -/* @import "desktop/*"; TODO: get this working again */ - -@import "desktop/alert"; -@import "desktop/banner"; -@import "desktop/compose"; -@import "desktop/discourse"; -@import "desktop/header"; -@import "desktop/login"; -@import "desktop/modal"; -@import "desktop/category-list"; -@import "desktop/latest-topic-list"; -@import "desktop/topic-list"; -@import "desktop/topic-post"; -@import "desktop/topic"; -@import "desktop/upload"; -@import "desktop/user"; -@import "desktop/history"; -@import "desktop/group"; -@import "desktop/admin_customize"; +@import "desktop/*"; // Import all component-specific files @import "desktop/components/*"; diff --git a/app/assets/stylesheets/desktop/alert.scss b/app/assets/stylesheets/desktop/alert.scss deleted file mode 100644 index f3f57eac66..0000000000 --- a/app/assets/stylesheets/desktop/alert.scss +++ /dev/null @@ -1,3 +0,0 @@ -.alert { - margin-bottom: 1em; -} diff --git a/app/assets/stylesheets/desktop/topic-post.scss b/app/assets/stylesheets/desktop/topic-post.scss index ca304b8e95..5adc17d3eb 100644 --- a/app/assets/stylesheets/desktop/topic-post.scss +++ b/app/assets/stylesheets/desktop/topic-post.scss @@ -451,20 +451,15 @@ pre.copy-codeblocks:hover .copy-cmd { } } -@mixin topic-footer-button { - margin-bottom: 5px; - margin-right: 10px; -} - #topic-footer-buttons { - padding: 10px 10px 0 0; - float: left; - .btn { - @include topic-footer-button; + max-width: calc(690px + (11px * 2) + 45px); + + .bookmark { .d-icon-bookmark.bookmarked { color: $tertiary; } } + .bookmark.bookmarked .d-icon-bookmark, .bookmark.bookmarked .d-icon-discourse-bookmark-clock { color: $tertiary; @@ -474,10 +469,6 @@ pre.copy-codeblocks:hover .copy-cmd { } } -#topic-footer-button { - width: 757px; -} - .suggested-topics { clear: left; padding: 20px 0 15px 0; diff --git a/app/assets/stylesheets/mobile.scss b/app/assets/stylesheets/mobile.scss index ee9c3ed92d..a3202ca43c 100644 --- a/app/assets/stylesheets/mobile.scss +++ b/app/assets/stylesheets/mobile.scss @@ -1,37 +1,6 @@ @import "common"; -/* @import "mobile/*"; TODO: get this working again */ - -@import "mobile/buttons"; -@import "mobile/alert"; -@import "mobile/banner"; -@import "mobile/compose"; -@import "mobile/discourse"; -@import "mobile/header"; -@import "mobile/login"; -@import "mobile/modal"; -@import "mobile/topic-list"; -@import "mobile/topic-post"; -@import "mobile/topic"; -@import "mobile/upload"; -@import "mobile/user"; -@import "mobile/history"; -@import "mobile/lightbox"; -@import "mobile/directory"; -@import "mobile/search"; -@import "mobile/emoji"; -@import "mobile/ring"; -@import "mobile/group"; -@import "mobile/dashboard"; -@import "mobile/admin_badges"; -@import "mobile/admin_customize"; -@import "mobile/admin_reports"; -@import "mobile/admin_report"; -@import "mobile/admin_report_table"; -@import "mobile/admin_report_counters"; -@import "mobile/admin_emojis"; -@import "mobile/menu-panel"; -@import "mobile/reviewables"; +@import "mobile/*"; // Import all component-specific files @import "mobile/components/*"; diff --git a/app/assets/stylesheets/mobile/alert.scss b/app/assets/stylesheets/mobile/alert.scss index 0ee9b44ee0..12e60245e5 100644 --- a/app/assets/stylesheets/mobile/alert.scss +++ b/app/assets/stylesheets/mobile/alert.scss @@ -1,7 +1,8 @@ -// there are (n) new or updated topics, click to show .alert.alert-info { - margin: 0; + margin-bottom: 0.5em; &.clickable { + // there are (n) new or updated topics, click to show + margin-bottom: 0; padding: 1em; } } diff --git a/app/assets/stylesheets/mobile/banner.scss b/app/assets/stylesheets/mobile/banner.scss index 852b284736..fc308f0588 100644 --- a/app/assets/stylesheets/mobile/banner.scss +++ b/app/assets/stylesheets/mobile/banner.scss @@ -3,11 +3,6 @@ // -------------------------------------------------- #banner { - // go full width on mobile, by extending into the 10px wrap - // borders on left and right - margin: 0 -10px; max-height: 180px; - @media all and (max-height: 499px) { - max-height: 100px; - } + height: 20vh; } diff --git a/app/assets/stylesheets/mobile/discourse.scss b/app/assets/stylesheets/mobile/discourse.scss index 260f8d4b13..c9406abca6 100644 --- a/app/assets/stylesheets/mobile/discourse.scss +++ b/app/assets/stylesheets/mobile/discourse.scss @@ -63,11 +63,6 @@ blockquote { margin-bottom: 9px; } -// categories should not be bold on mobile; they fight with the topic title too much -.badge-wrapper { - font-weight: normal; -} - .mobile-nav { margin: 0; padding: 0; diff --git a/app/assets/stylesheets/mobile/topic-post.scss b/app/assets/stylesheets/mobile/topic-post.scss index 9cd4931ce8..d84b4a727c 100644 --- a/app/assets/stylesheets/mobile/topic-post.scss +++ b/app/assets/stylesheets/mobile/topic-post.scss @@ -36,6 +36,7 @@ span.badge-posts { .double-button { display: flex; flex: 0 1 auto; + align-items: center; button { &.like, &.read-indicator, @@ -235,20 +236,16 @@ a.reply-to-tab { } #topic-footer-buttons { - @include clearfix; - padding: 20px 0 0 0; .d-icon-bookmark.bookmarked, .d-icon-discourse-bookmark-clock.bookmarked { color: $tertiary; } - .combobox { - margin-right: 0.5em; - width: 160px; - margin-bottom: 0.5em; - } -} -#topic-footer-buttons { + .topic-footer-mobile-dropdown { + margin: 0 0.5em 0.5em 0; + width: 160px; + } + .topic-notifications-button, .pinned-button { display: flex; @@ -263,11 +260,6 @@ a.reply-to-tab { } } -#topic-footer-button { - width: 100px; - margin: 0 auto; -} - .suggested-topics { clear: left; padding: 20px 0 15px 0; @@ -292,13 +284,6 @@ span.post-count { opacity: 0.8; } -#topic-footer-buttons { - .btn { - margin-bottom: 0.5em; - margin-right: 0.5em; - } -} - #topic-title { z-index: z("base") + 1; margin: 0 0 0 0 !important; diff --git a/app/controllers/about_controller.rb b/app/controllers/about_controller.rb index fe2d3a2327..3f3f9e15a3 100644 --- a/app/controllers/about_controller.rb +++ b/app/controllers/about_controller.rb @@ -16,7 +16,7 @@ class AboutController < ApplicationController render :index end format.json do - render_serialized(@about, AboutSerializer) + render_json_dump(AboutSerializer.new(@about, scope: guardian)) end end end diff --git a/app/controllers/list_controller.rb b/app/controllers/list_controller.rb index 81511387f7..5eadbbcf6d 100644 --- a/app/controllers/list_controller.rb +++ b/app/controllers/list_controller.rb @@ -331,7 +331,13 @@ class ListController < ApplicationController params[:category] = @category.id.to_s - @description_meta = @category.description_text + @description_meta = if @category.uncategorized? + I18n.t('category.uncategorized_description', locale: SiteSetting.default_locale) + else + @category.description_text + end + @description_meta = SiteSetting.site_description if @description_meta.blank? + if !guardian.can_see?(@category) if SiteSetting.detailed_404 raise Discourse::InvalidAccess diff --git a/app/controllers/themes_controller.rb b/app/controllers/themes_controller.rb deleted file mode 100644 index 6520ec7097..0000000000 --- a/app/controllers/themes_controller.rb +++ /dev/null @@ -1,31 +0,0 @@ -# frozen_string_literal: true - -class ThemesController < ::ApplicationController - def assets - theme_ids = params[:ids].to_s.split("-").map(&:to_i) - - if params[:ids] == "default" - theme_ids = nil - else - raise Discourse::NotFound unless guardian.allow_themes?(theme_ids) - end - - targets = view_context.mobile_view? ? [:mobile, :mobile_theme] : [:desktop, :desktop_theme] - targets << :admin if guardian.is_staff? - targets.append(*Discourse.find_plugin_css_assets(mobile_view: true, desktop_view: true)) - - object = targets.map do |target| - Stylesheet::Manager.stylesheet_data(target, theme_ids).map do |hash| - next hash unless Rails.env.development? - - dup_hash = hash.dup - dup_hash[:new_href] = dup_hash[:new_href].dup - dup_hash[:new_href] << (dup_hash[:new_href].include?("?") ? "&" : "?") - dup_hash[:new_href] << SecureRandom.hex - dup_hash - end - end.flatten - - render json: object.as_json - end -end diff --git a/app/helpers/email_helper.rb b/app/helpers/email_helper.rb index c579473a4d..14364d45c3 100644 --- a/app/helpers/email_helper.rb +++ b/app/helpers/email_helper.rb @@ -25,12 +25,8 @@ module EmailHelper raw "#{title}" end - def email_html_template(binding_arg) - template = EmailStyle.new.html.sub( - '%{email_content}', - '<%= yield %><% if defined?(html_body) %><%= html_body %><% end %>' - ) - ERB.new(template).result(binding_arg) + def email_html_template + EmailStyle.new.html.sub('%{email_content}', yield).html_safe end protected diff --git a/app/models/application_request.rb b/app/models/application_request.rb index 52a6a8a87e..3d0d8ae905 100644 --- a/app/models/application_request.rb +++ b/app/models/application_request.rb @@ -16,15 +16,16 @@ class ApplicationRequest < ActiveRecord::Base include CachedCounting def self.disable - @enabled = false + @disabled = true end def self.enable - @enabled = true + @disabled = false end def self.increment!(type, opts = nil) - perform_increment!(redis_key(type), opts) if @enabled + return if @disabled + perform_increment!(redis_key(type), opts) end def self.write_cache!(date = nil) diff --git a/app/models/category.rb b/app/models/category.rb index 63db814752..6ecfe9d3af 100644 --- a/app/models/category.rb +++ b/app/models/category.rb @@ -709,37 +709,29 @@ class Category < ActiveRecord::Base ].include? id end - @@url_cache = DistributedCache.new('category_url') + def full_slug(separator = "-") + start_idx = "#{Discourse.base_uri}/c/".size + url[start_idx..-1].gsub("/", separator) + end + + @@url_cache = DistributedCache.new("category_url") def clear_url_cache @@url_cache.clear end - def full_slug(separator = "-") - start_idx = "#{Discourse.base_uri}/c/".length - url[start_idx..-1].gsub("/", separator) - end - def url - url = @@url_cache[self.id] - unless url - url = "#{Discourse.base_uri}/c/#{slug_path.join('/')}" - - @@url_cache[self.id] = url - end - - url + @@url_cache[self.id] ||= "#{Discourse.base_uri}/c/#{slug_path.join('/')}" end def url_with_id self.parent_category ? "#{url}/#{self.id}" : "#{Discourse.base_uri}/c/#{self.slug}/#{self.id}" end - # If the name changes, try and update the category definition topic too if it's - # an exact match + # If the name changes, try and update the category definition topic too if it's an exact match def rename_category_definition - old_name = saved_changes.transform_values(&:first)["name"] return unless topic.present? + old_name = saved_changes.transform_values(&:first)["name"] if topic.title == I18n.t("category.topic_prefix", category: old_name) topic.update_attribute(:title, I18n.t("category.topic_prefix", category: name)) end diff --git a/app/models/global_setting.rb b/app/models/global_setting.rb index f4425919ed..21e5a137dc 100644 --- a/app/models/global_setting.rb +++ b/app/models/global_setting.rb @@ -164,9 +164,15 @@ class GlobalSetting c[:port] = redis_port if redis_port if redis_slave_host && redis_slave_port - c[:slave_host] = redis_slave_host - c[:slave_port] = redis_slave_port - c[:connector] = DiscourseRedis::Connector + if ENV["RAILS_FAILOVER"] + c[:replica_host] = redis_slave_host + c[:replica_port] = redis_slave_port + c[:connector] = RailsFailover::Redis::Connector + else + c[:slave_host] = redis_slave_host + c[:slave_port] = redis_slave_port + c[:connector] = DiscourseRedis::Connector + end end c[:password] = redis_password if redis_password.present? @@ -188,9 +194,15 @@ class GlobalSetting c[:port] = message_bus_redis_port if message_bus_redis_port if message_bus_redis_slave_host && message_bus_redis_slave_port - c[:slave_host] = message_bus_redis_slave_host - c[:slave_port] = message_bus_redis_slave_port - c[:connector] = DiscourseRedis::Connector + if ENV["RAILS_FAILOVER"] + c[:replica_host] = message_bus_redis_slave_host + c[:replica_port] = message_bus_redis_slave_port + c[:connector] = RailsFailover::Redis::Connector + else + c[:slave_host] = message_bus_redis_slave_host + c[:slave_port] = message_bus_redis_slave_port + c[:connector] = DiscourseRedis::Connector + end end c[:password] = message_bus_redis_password if message_bus_redis_password.present? diff --git a/app/models/group_archived_message.rb b/app/models/group_archived_message.rb index 1715a57585..8199a53cf6 100644 --- a/app/models/group_archived_message.rb +++ b/app/models/group_archived_message.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true class GroupArchivedMessage < ActiveRecord::Base - belongs_to :user + belongs_to :group belongs_to :topic def self.move_to_inbox!(group_id, topic) diff --git a/app/models/post.rb b/app/models/post.rb index 89dbbcfebb..e1a3a1f391 100644 --- a/app/models/post.rb +++ b/app/models/post.rb @@ -159,14 +159,6 @@ class Post < ActiveRecord::Base includes(:post_details).find_by(post_details: { key: key, value: value }) end - def self.excerpt_size=(sz) - @excerpt_size = sz - end - - def self.excerpt_size - @excerpt_size || 220 - end - def whisper? post_type == Post.types[:whisper] end @@ -482,7 +474,7 @@ class Post < ActiveRecord::Base end def excerpt_for_topic - Post.excerpt(cooked, Post.excerpt_size, strip_links: true, strip_images: true, post: self) + Post.excerpt(cooked, SiteSetting.topic_excerpt_maxlength, strip_links: true, strip_images: true, post: self) end def is_first_post? @@ -658,6 +650,10 @@ class Post < ActiveRecord::Base baked_version: BAKED_VERSION ) + if is_first_post? + topic.update_excerpt(excerpt_for_topic) + end + if invalidate_broken_images custom_fields.delete(BROKEN_IMAGES) save_custom_fields diff --git a/app/models/topic.rb b/app/models/topic.rb index d331a30806..38affdb327 100644 --- a/app/models/topic.rb +++ b/app/models/topic.rb @@ -33,11 +33,11 @@ class Topic < ActiveRecord::Base end def self.thumbnail_sizes - [ self.share_thumbnail_size ] + [ self.share_thumbnail_size ] + DiscoursePluginRegistry.topic_thumbnail_sizes end - def thumbnail_job_redis_key(extra_sizes) - "generate_topic_thumbnail_enqueue_#{id}_#{extra_sizes.inspect}" + def thumbnail_job_redis_key(sizes) + "generate_topic_thumbnail_enqueue_#{id}_#{sizes.inspect}" end def filtered_topic_thumbnails(extra_sizes: []) @@ -79,7 +79,7 @@ class Topic < ActiveRecord::Base if SiteSetting.create_thumbnails && enqueue_if_missing && records.length < thumbnail_sizes.length && - Discourse.redis.set(thumbnail_job_redis_key(extra_sizes), 1, nx: true, ex: 1.minute) + Discourse.redis.set(thumbnail_job_redis_key(thumbnail_sizes), 1, nx: true, ex: 1.minute) Jobs.enqueue(:generate_topic_thumbnails, { topic_id: id, extra_sizes: extra_sizes }) end @@ -1476,6 +1476,13 @@ class Topic < ActiveRecord::Base private_topic end + def update_excerpt(excerpt) + update_column(:excerpt, excerpt) + if archetype == "banner" + ApplicationController.banner_json_cache.clear + end + end + def pm_with_non_human_user? sql = <<~SQL SELECT 1 FROM topics diff --git a/app/models/topic_embed.rb b/app/models/topic_embed.rb index a18e5b85de..362ee79f4a 100644 --- a/app/models/topic_embed.rb +++ b/app/models/topic_embed.rb @@ -109,6 +109,8 @@ class TopicEmbed < ActiveRecord::Base url = UrlHelper.escape_uri(url) original_uri = URI.parse(url) + raise URI::InvalidURIError unless original_uri.is_a?(URI::HTTP) + opts = { tags: %w[div p code pre h1 h2 h3 b em i strong a img ul li ol blockquote], attributes: %w[href src class], diff --git a/app/models/upload.rb b/app/models/upload.rb index bb4e7976f5..31c931c172 100644 --- a/app/models/upload.rb +++ b/app/models/upload.rb @@ -448,7 +448,3 @@ end # index_uploads_on_url (url) # index_uploads_on_user_id (user_id) # -# Foreign Keys -# -# fk_rails_... (access_control_post_id => posts.id) -# diff --git a/app/models/user.rb b/app/models/user.rb index 9d682a62bd..d9678d1a1c 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -7,102 +7,95 @@ class User < ActiveRecord::Base include SecondFactorManager include HasDestroyedWebHook + DEFAULT_FEATURED_BADGE_COUNT = 3 + + # not deleted on user delete has_many :posts - has_many :notifications, dependent: :delete_all - has_many :topic_users, dependent: :delete_all + has_many :topics + has_many :uploads + has_many :category_users, dependent: :destroy has_many :tag_users, dependent: :destroy has_many :user_api_keys, dependent: :destroy - has_many :topics - has_many :bookmarks + has_many :topic_allowed_users, dependent: :destroy + has_many :user_archived_messages, dependent: :destroy + has_many :email_change_requests, dependent: :destroy + has_many :email_tokens, dependent: :destroy + has_many :invites, dependent: :destroy + has_many :topic_links, dependent: :destroy + has_many :user_uploads, dependent: :destroy + has_many :user_emails, dependent: :destroy + has_many :user_associated_accounts, dependent: :destroy + has_many :oauth2_user_infos, dependent: :destroy + has_many :user_second_factors, dependent: :destroy + has_many :user_badges, -> { for_enabled_badges }, dependent: :destroy + has_many :user_auth_tokens, dependent: :destroy + has_many :group_users, dependent: :destroy + has_many :user_warnings, dependent: :destroy + has_many :api_keys, dependent: :destroy + has_many :push_subscriptions, dependent: :destroy + has_many :acting_group_histories, dependent: :destroy, foreign_key: :acting_user_id, class_name: 'GroupHistory' + has_many :targeted_group_histories, dependent: :destroy, foreign_key: :target_user_id, class_name: 'GroupHistory' + has_many :reviewable_scores, dependent: :destroy - # dependent deleting handled via before_destroy + has_one :user_option, dependent: :destroy + has_one :user_avatar, dependent: :destroy + has_one :github_user_info, dependent: :destroy + has_one :primary_email, -> { where(primary: true) }, class_name: 'UserEmail', dependent: :destroy + has_one :user_stat, dependent: :destroy + has_one :user_profile, dependent: :destroy, inverse_of: :user + has_one :single_sign_on_record, dependent: :destroy + has_one :anonymous_user_master, class_name: 'AnonymousUser', dependent: :destroy + has_one :anonymous_user_shadow, ->(record) { where(active: true) }, foreign_key: :master_user_id, class_name: 'AnonymousUser', dependent: :destroy + + # delete all is faster but bypasses callbacks + has_many :bookmarks, dependent: :delete_all + has_many :notifications, dependent: :delete_all + has_many :topic_users, dependent: :delete_all + has_many :email_logs, dependent: :delete_all + has_many :incoming_emails, dependent: :delete_all + has_many :user_visits, dependent: :delete_all + has_many :user_auth_token_logs, dependent: :delete_all + has_many :group_requests, dependent: :delete_all + has_many :muted_user_records, class_name: 'MutedUser', dependent: :delete_all + has_many :ignored_user_records, class_name: 'IgnoredUser', dependent: :delete_all + + # dependent deleting handled via before_destroy (special cases) has_many :user_actions has_many :post_actions + has_many :post_timings + has_many :directory_items + has_many :security_keys, -> { + where(enabled: true) + }, class_name: "UserSecurityKey" - DEFAULT_FEATURED_BADGE_COUNT = 3 - - has_many :user_badges, -> { for_enabled_badges }, dependent: :destroy has_many :badges, through: :user_badges has_many :default_featured_user_badges, -> { for_enabled_badges.grouped_with_count.where("featured_rank <= ?", DEFAULT_FEATURED_BADGE_COUNT) }, class_name: "UserBadge" - has_many :email_logs, dependent: :delete_all - has_many :incoming_emails, dependent: :delete_all - has_many :post_timings - has_many :topic_allowed_users, dependent: :destroy has_many :topics_allowed, through: :topic_allowed_users, source: :topic - has_many :email_tokens, dependent: :destroy - has_many :user_visits, dependent: :destroy - has_many :invites, dependent: :destroy - has_many :topic_links, dependent: :destroy - has_many :uploads - has_many :user_warnings - has_many :user_archived_messages, dependent: :destroy - has_many :email_change_requests, dependent: :destroy - - # see before_destroy - has_many :directory_items - has_many :user_auth_tokens, dependent: :destroy - has_many :user_auth_token_logs, dependent: :destroy - - has_many :group_users, dependent: :destroy has_many :groups, through: :group_users - has_many :group_requests, dependent: :destroy has_many :secure_categories, through: :groups, source: :categories - has_many :user_uploads, dependent: :destroy - has_many :user_emails, dependent: :destroy - - has_one :primary_email, -> { where(primary: true) }, class_name: 'UserEmail', dependent: :destroy - - has_one :user_option, dependent: :destroy - has_one :user_avatar, dependent: :destroy - has_many :user_associated_accounts, dependent: :destroy - has_one :github_user_info, dependent: :destroy - has_many :oauth2_user_infos, dependent: :destroy - has_many :user_second_factors, dependent: :destroy - + # deleted in user_second_factors relationship has_many :totps, -> { where(method: UserSecondFactor.methods[:totp], enabled: true) }, class_name: "UserSecondFactor" - has_many :security_keys, -> { - where(enabled: true) - }, class_name: "UserSecurityKey" - - has_one :anonymous_user_master, class_name: 'AnonymousUser' - has_one :anonymous_user_shadow, ->(record) { where(active: true) }, foreign_key: :master_user_id, class_name: 'AnonymousUser' - has_one :master_user, through: :anonymous_user_master has_one :shadow_user, through: :anonymous_user_shadow, source: :user - has_one :user_stat, dependent: :destroy - has_one :user_profile, dependent: :destroy, inverse_of: :user has_one :profile_background_upload, through: :user_profile has_one :card_background_upload, through: :user_profile - has_one :single_sign_on_record, dependent: :destroy belongs_to :approved_by, class_name: 'User' belongs_to :primary_group, class_name: 'Group' - has_many :muted_user_records, class_name: 'MutedUser' has_many :muted_users, through: :muted_user_records - - has_many :ignored_user_records, class_name: 'IgnoredUser' has_many :ignored_users, through: :ignored_user_records - has_many :api_keys, dependent: :destroy - - has_many :push_subscriptions, dependent: :destroy - belongs_to :uploaded_avatar, class_name: 'Upload' - has_many :acting_group_histories, dependent: :destroy, foreign_key: :acting_user_id, class_name: 'GroupHistory' - has_many :targeted_group_histories, dependent: :destroy, foreign_key: :target_user_id, class_name: 'GroupHistory' - - has_many :reviewable_scores, dependent: :destroy - delegate :last_sent_email_address, to: :email_logs validates_presence_of :username @@ -164,6 +157,9 @@ class User < ActiveRecord::Base DirectoryItem.where(user_id: self.id) .where('period_type in (?)', DirectoryItem.period_types.values) .delete_all + + # our relationship filters on enabled, this makes sure everything is deleted + UserSecurityKey.where(user_id: self.id).delete_all end # Skip validating email, for example from a particular auth provider plugin @@ -933,7 +929,7 @@ class User < ActiveRecord::Base def activate if email_token = self.email_tokens.active.where(email: self.email).first - user = EmailToken.confirm(email_token.token, skip_reviewable: true) + EmailToken.confirm(email_token.token, skip_reviewable: true) end self.update!(active: true) create_reviewable @@ -1119,10 +1115,9 @@ class User < ActiveRecord::Base end def number_of_rejected_posts - Post.with_deleted - .where(user_id: self.id) - .joins('INNER JOIN reviewables r ON posts.id = r.target_id') - .where(r: { status: Reviewable.statuses[:rejected], type: ReviewableQueuedPost.name }) + ReviewableQueuedPost + .where(status: Reviewable.statuses[:rejected]) + .where(created_by_id: self.id) .count end diff --git a/app/models/user_avatar.rb b/app/models/user_avatar.rb index f6dbf13312..bd2751d6b4 100644 --- a/app/models/user_avatar.rb +++ b/app/models/user_avatar.rb @@ -5,6 +5,14 @@ class UserAvatar < ActiveRecord::Base belongs_to :gravatar_upload, class_name: 'Upload' belongs_to :custom_upload, class_name: 'Upload' + @@custom_user_gravatar_email_hash = { + Discourse::SYSTEM_USER_ID => User.email_hash("info@discourse.org") + } + + def self.register_custom_user_gravatar_email_hash(user_id, email) + @@custom_user_gravatar_email_hash[user_id] = User.email_hash(email) + end + def contains_upload?(id) gravatar_upload_id == id || custom_upload_id == id end @@ -12,14 +20,14 @@ class UserAvatar < ActiveRecord::Base def update_gravatar! DistributedMutex.synchronize("update_gravatar_#{user_id}") do begin - self.update!(last_gravatar_download_attempt: Time.now) + self.update!(last_gravatar_download_attempt: Time.zone.now) max = Discourse.avatar_sizes.max # The user could be deleted before this executes return if user.blank? || user.primary_email.blank? - email_hash = user_id == Discourse::SYSTEM_USER_ID ? User.email_hash("info@discourse.org") : user.email_hash + email_hash = @@custom_user_gravatar_email_hash[user_id] || user.email_hash gravatar_url = "https://#{SiteSetting.gravatar_base_url}/avatar/#{email_hash}.png?s=#{max}&d=404&reset_cache=#{SecureRandom.urlsafe_base64(5)}" # follow redirects in case gravatar change rules on us diff --git a/app/models/web_hook_event_type.rb b/app/models/web_hook_event_type.rb index 952c1c2062..442f14cb8c 100644 --- a/app/models/web_hook_event_type.rb +++ b/app/models/web_hook_event_type.rb @@ -7,8 +7,6 @@ class WebHookEventType < ActiveRecord::Base GROUP = 4 CATEGORY = 5 TAG = 6 - FLAG = 7 - QUEUED_POST = 8 REVIEWABLE = 9 NOTIFICATION = 10 SOLVED = 11 diff --git a/app/serializers/about_serializer.rb b/app/serializers/about_serializer.rb index 308f990fd6..8230c590a4 100644 --- a/app/serializers/about_serializer.rb +++ b/app/serializers/about_serializer.rb @@ -21,7 +21,16 @@ class AboutSerializer < ApplicationSerializer :title, :locale, :version, - :https + :https, + :can_see_about_stats + + def can_see_about_stats + scope.can_see_about_stats? + end + + def include_stats? + can_see_about_stats + end def stats object.class.fetch_cached_stats || Jobs::AboutStats.new.execute({}) diff --git a/app/services/badge_granter.rb b/app/services/badge_granter.rb index e402fb880c..a5027c5303 100644 --- a/app/services/badge_granter.rb +++ b/app/services/badge_granter.rb @@ -3,11 +3,11 @@ class BadgeGranter def self.disable_queue - @queue_enabled = false + @queue_disabled = true end def self.enable_queue - @queue_enabled = true + @queue_disabled = false end def initialize(badge, user, opts = {}) @@ -124,7 +124,7 @@ class BadgeGranter end def self.queue_badge_grant(type, opt) - return if !SiteSetting.enable_badges || !@queue_enabled + return if !SiteSetting.enable_badges || @queue_disabled payload = nil case type diff --git a/app/views/email/default_template.html b/app/views/email/default_template.html index 382f939273..99b2e94995 100644 --- a/app/views/email/default_template.html +++ b/app/views/email/default_template.html @@ -3,6 +3,8 @@ + + diff --git a/app/views/layouts/email_template.html.erb b/app/views/layouts/email_template.html.erb index 80da92efc2..611eba0df4 100644 --- a/app/views/layouts/email_template.html.erb +++ b/app/views/layouts/email_template.html.erb @@ -2,5 +2,8 @@ <%= yield %> <% if defined?(html_body) %><%= html_body %><% end %> <% else %> - <%= email_html_template(binding).html_safe %> + <%= email_html_template do %> + <%= yield %> + <% if defined?(html_body) %><%= html_body %><% end %> + <% end %> <% end %> diff --git a/app/views/layouts/publish.html.erb b/app/views/layouts/publish.html.erb index 5bb0f957c5..4a249e8f32 100644 --- a/app/views/layouts/publish.html.erb +++ b/app/views/layouts/publish.html.erb @@ -2,13 +2,8 @@ - - + <%= render partial: "layouts/head" %> <%= render partial: "common/discourse_publish_stylesheet" %> - - <%- if @canonical_url -%> - - <%- end -%> <%= yield %> diff --git a/app/views/list/list.erb b/app/views/list/list.erb index 2d32efb77b..2338d39c6a 100644 --- a/app/views/list/list.erb +++ b/app/views/list/list.erb @@ -47,8 +47,8 @@ <%= t 'js.topic.title' %> - <%= t 'js.replies' %> - <%= t 'js.views' %> + <%= t 'js.replies' %> + <%= t 'js.views' %> <%= t 'js.activity' %> @@ -101,10 +101,10 @@ <% end %> <% end %> - + '><%= t.posts_count %> - + '><%= t.views %> diff --git a/config/application.rb b/config/application.rb index 43a8bf3091..0157dc8289 100644 --- a/config/application.rb +++ b/config/application.rb @@ -27,6 +27,10 @@ require_relative '../lib/discourse_plugin_registry' require_relative '../lib/plugin_gem' +if ENV['RAILS_FAILOVER'] + require 'rails_failover' +end + # Global config require_relative '../app/models/global_setting' GlobalSetting.configure! @@ -250,7 +254,7 @@ module Discourse # Our templates shouldn't start with 'discourse/app/templates' config.handlebars.templates_root = { 'discourse/app/templates' => '', - 'select-kit/app/templates' => 'select-kit/templates/' + 'select-kit/addon/templates' => 'select-kit/templates/' } config.handlebars.raw_template_namespace = "__DISCOURSE_RAW_TEMPLATES" diff --git a/config/initializers/001-redis.rb b/config/initializers/001-redis.rb index e48441594e..f28c59a3b1 100644 --- a/config/initializers/001-redis.rb +++ b/config/initializers/001-redis.rb @@ -2,5 +2,20 @@ if Rails.env.development? && ENV['DISCOURSE_FLUSH_REDIS'] puts "Flushing redis (development mode)" - Discourse.redis.flushall + Discourse.redis.flushdb +end + +if ENV['RAILS_FAILOVER'] + message_bus_keepalive_interval = MessageBus.keepalive_interval + + RailsFailover::Redis.register_master_up_callback do + MessageBus.keepalive_interval = message_bus_keepalive_interval + Discourse.clear_readonly! + Discourse.request_refresh! + end + + RailsFailover::Redis.register_master_down_callback do + # Disables MessageBus keepalive when Redis is in readonly mode + MessageBus.keepalive_interval = 0 + end end diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 9746f37f37..3c3618ef96 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -963,6 +963,7 @@ en: api_approved: "Approved:" api_last_used_at: "Last used at:" theme: "Theme" + save_to_change_theme: 'Theme will be updated after you click "%{save_text}"' home: "Default Home Page" staged: "Staged" @@ -2100,7 +2101,7 @@ en: help: "Move message back to Inbox" edit_message: help: "Edit first post of the message" - title: "Edit Message" + title: "Edit" defer: help: "Mark as unread" title: "Defer" @@ -3598,12 +3599,6 @@ en: tag_event: name: "Tag Event" details: "When a tag is created, updated or destroyed." - flag_event: - name: "Flag Event" - details: "When a flag is created, agreed, disagreed or ignored." - queued_post_event: - name: "Post Approval Event" - details: "When a new queued post is created, approved or rejected." reviewable_event: name: "Reviewable Event" details: "When a new item is ready for review and when its status is updated." diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index 06f02313eb..2341f05c72 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -1467,6 +1467,7 @@ en: exclude_rel_nofollow_domains: "A list of domains where nofollow should not be added to links. example.com will automatically allow sub.example.com as well. As a minimum, you should add the domain of this site to help web crawlers find all content. If other parts of your website are at other domains, add those too." post_excerpt_maxlength: "Maximum length of a post excerpt / summary." + topic_excerpt_maxlength: "Maximum length of a topic excerpt / summary, generated from the first post in a topic." show_pinned_excerpt_mobile: "Show excerpt on pinned topics in mobile view." show_pinned_excerpt_desktop: "Show excerpt on pinned topics in desktop view." post_onebox_maxlength: "Maximum length of a oneboxed Discourse post in characters." diff --git a/config/routes.rb b/config/routes.rb index d3a1082d43..69ade71938 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -23,6 +23,7 @@ Discourse::Application.routes.draw do post "webhooks/sparkpost" => "webhooks#sparkpost" scope path: nil, constraints: { format: /.*/ } do + Sidekiq::Web.set :sessions, Rails.application.config.session_options if Rails.env.development? mount Sidekiq::Web => "/sidekiq" mount Logster::Web => "/logs" @@ -120,7 +121,6 @@ Discourse::Application.routes.draw do put "unsuspend" put "revoke_admin", constraints: AdminConstraint.new put "grant_admin", constraints: AdminConstraint.new - post "generate_api_key", constraints: AdminConstraint.new put "revoke_moderation", constraints: AdminConstraint.new put "grant_moderation", constraints: AdminConstraint.new put "approve" @@ -950,8 +950,6 @@ Discourse::Application.routes.draw do get "/safe-mode" => "safe_mode#index" post "/safe-mode" => "safe_mode#enter", as: "safe_mode_enter" - get "/themes/assets/:ids" => "themes#assets" - unless Rails.env.production? get "/qunit" => "qunit#index" get "/wizard/qunit" => "wizard#qunit" diff --git a/config/site_settings.yml b/config/site_settings.yml index d49bb0e4cf..6209cc621a 100644 --- a/config/site_settings.yml +++ b/config/site_settings.yml @@ -394,7 +394,7 @@ login: default: false github_client_id: default: "" - regex: "^[a-f0-9]+$" + regex: "^[a-zA-Z0-9\\.]+$" github_client_secret: default: "" regex: "^[a-f0-9]+$" @@ -819,6 +819,12 @@ posting: ja: 120 zh_CN: 120 zh_TW: 120 + topic_excerpt_maxlength: + default: 220 + locale_default: + ja: 120 + zh_CN: 120 + zh_TW: 120 show_pinned_excerpt_mobile: client: true default: true @@ -1382,7 +1388,8 @@ security: - Lax - Strict - Disabled - regex: "^(Lax|Strict|Disabled)$" + - None + regex: "^(Lax|Strict|Disabled|None)$" enable_escaped_fragments: true allow_index_in_robots_txt: true moderators_create_categories: false diff --git a/db/fixtures/007_web_hook_event_types.rb b/db/fixtures/007_web_hook_event_types.rb index 8bf8e93805..ec666ec5ad 100644 --- a/db/fixtures/007_web_hook_event_types.rb +++ b/db/fixtures/007_web_hook_event_types.rb @@ -30,16 +30,6 @@ WebHookEventType.seed do |b| b.name = "tag" end -WebHookEventType.seed do |b| - b.id = WebHookEventType::FLAG - b.name = "flag" -end - -WebHookEventType.seed do |b| - b.id = WebHookEventType::QUEUED_POST - b.name = "queued_post" -end - WebHookEventType.seed do |b| b.id = WebHookEventType::REVIEWABLE b.name = "reviewable" diff --git a/db/migrate/20200409033412_create_bookmarks_from_post_action_bookmarks.rb b/db/migrate/20200409033412_create_bookmarks_from_post_action_bookmarks.rb index 092a0e7b99..57b1885e35 100644 --- a/db/migrate/20200409033412_create_bookmarks_from_post_action_bookmarks.rb +++ b/db/migrate/20200409033412_create_bookmarks_from_post_action_bookmarks.rb @@ -16,6 +16,7 @@ class CreateBookmarksFromPostActionBookmarks < ActiveRecord::Migration[6.0] INNER JOIN posts ON posts.id = post_actions.post_id LEFT JOIN bookmarks ON bookmarks.post_id = post_actions.post_id AND bookmarks.user_id = post_actions.user_id INNER JOIN topics ON topics.id = posts.topic_id + INNER JOIN users ON users.id = post_actions.user_id WHERE bookmarks.id IS NULL AND post_action_type_id = :type_id AND post_actions.deleted_at IS NULL AND posts.deleted_at IS NULL LIMIT 2000 SQL diff --git a/db/migrate/20200514075356_remove_flag_web_hooks.rb b/db/migrate/20200514075356_remove_flag_web_hooks.rb new file mode 100644 index 0000000000..448b7fb771 --- /dev/null +++ b/db/migrate/20200514075356_remove_flag_web_hooks.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +class RemoveFlagWebHooks < ActiveRecord::Migration[6.0] + def up + flag_event_type_id = 7 + + DB.exec <<~SQL + DELETE FROM web_hook_event_types_hooks + WHERE web_hook_event_type_id = #{flag_event_type_id} + SQL + + DB.exec <<~SQL + DELETE FROM web_hook_event_types + WHERE id = #{flag_event_type_id} + SQL + end + + def down + raise ActiveRecord::IrreversibleMigration + end +end diff --git a/db/migrate/20200514075407_remove_queued_post_web_hooks.rb b/db/migrate/20200514075407_remove_queued_post_web_hooks.rb new file mode 100644 index 0000000000..632e5013dc --- /dev/null +++ b/db/migrate/20200514075407_remove_queued_post_web_hooks.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +class RemoveQueuedPostWebHooks < ActiveRecord::Migration[6.0] + def up + queued_post_event_type_id = 8 + + DB.exec <<~SQL + DELETE FROM web_hook_event_types_hooks + WHERE web_hook_event_type_id = #{queued_post_event_type_id} + SQL + + DB.exec <<~SQL + DELETE FROM web_hook_event_types + WHERE id = #{queued_post_event_type_id} + SQL + end + + def down + raise ActiveRecord::IrreversibleMigration + end +end diff --git a/db/post_migrate/20200520001619_remove_fks_from_bookmarks.rb b/db/post_migrate/20200520001619_remove_fks_from_bookmarks.rb new file mode 100644 index 0000000000..80fee6dab4 --- /dev/null +++ b/db/post_migrate/20200520001619_remove_fks_from_bookmarks.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +class RemoveFksFromBookmarks < ActiveRecord::Migration[6.0] + def change + remove_foreign_key :bookmarks, :topics + remove_foreign_key :bookmarks, :posts + remove_foreign_key :bookmarks, :users + end +end diff --git a/db/post_migrate/20200522004855_remove_access_control_post_fk.rb b/db/post_migrate/20200522004855_remove_access_control_post_fk.rb new file mode 100644 index 0000000000..36c8d0fbb7 --- /dev/null +++ b/db/post_migrate/20200522004855_remove_access_control_post_fk.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class RemoveAccessControlPostFk < ActiveRecord::Migration[6.0] + def change + remove_foreign_key :uploads, column: :access_control_post_id + end +end diff --git a/lib/backup_restore/backup_file_handler.rb b/lib/backup_restore/backup_file_handler.rb index c5e3702488..7b26356989 100644 --- a/lib/backup_restore/backup_file_handler.rb +++ b/lib/backup_restore/backup_file_handler.rb @@ -65,9 +65,10 @@ module BackupRestore return if !@is_archive log "Unzipping archive, this may take a while..." - pipeline = Compression::Pipeline.new([Compression::Tar.new, Compression::Gzip.new]) - unzipped_path = pipeline.decompress(@tmp_directory, @archive_path, available_size) - pipeline.strip_directory(unzipped_path, @tmp_directory) + Discourse::Utils.execute_command( + 'tar', '--extract', '--gzip', '--file', @archive_path, '--directory', @tmp_directory, + failure_message: "Failed to decompress archive." + ) end def extract_db_dump diff --git a/lib/discourse_plugin_registry.rb b/lib/discourse_plugin_registry.rb index ae260e2ce1..ab382a4d15 100644 --- a/lib/discourse_plugin_registry.rb +++ b/lib/discourse_plugin_registry.rb @@ -75,6 +75,8 @@ class DiscoursePluginRegistry define_filtered_register :editable_group_custom_fields + define_filtered_register :topic_thumbnail_sizes + def self.register_auth_provider(auth_provider) self.auth_providers << auth_provider end diff --git a/lib/discourse_redis.rb b/lib/discourse_redis.rb index ff946cacfb..6b630b7b34 100644 --- a/lib/discourse_redis.rb +++ b/lib/discourse_redis.rb @@ -266,12 +266,6 @@ class DiscourseRedis end end - def flushdb - DiscourseRedis.ignore_readonly do - keys.each { |k| del(k) } - end - end - def reconnect @redis._client.reconnect end diff --git a/lib/file_store/s3_store.rb b/lib/file_store/s3_store.rb index 6a26ebd7ee..f42fa73057 100644 --- a/lib/file_store/s3_store.rb +++ b/lib/file_store/s3_store.rb @@ -79,12 +79,35 @@ module FileStore def has_been_uploaded?(url) return false if url.blank? + begin + parsed_url = URI.parse(URI.encode(url)) + rescue URI::InvalidURIError + return false + end + base_hostname = URI.parse(absolute_base_url).hostname - return true if url[base_hostname] + if url[base_hostname] + # if the hostnames match it means the upload is in the same + # bucket on s3. however, the bucket folder path may differ in + # some cases, and we do not want to assume the url is uploaded + # here. e.g. the path of the current site could be /prod and the + # other site could be /staging + if s3_bucket_folder_path.present? + return parsed_url.path.starts_with?("/#{s3_bucket_folder_path}") + else + return true + end + return false + end return false if SiteSetting.Upload.s3_cdn_url.blank? cdn_hostname = URI.parse(SiteSetting.Upload.s3_cdn_url || "").hostname - cdn_hostname.presence && url[cdn_hostname] + return true if cdn_hostname.presence && url[cdn_hostname] + false + end + + def s3_bucket_folder_path + @s3_helper.s3_bucket_folder_path end def s3_bucket_name diff --git a/lib/freedom_patches/translate_accelerator.rb b/lib/freedom_patches/translate_accelerator.rb index ee724d56d1..5a82623f8f 100644 --- a/lib/freedom_patches/translate_accelerator.rb +++ b/lib/freedom_patches/translate_accelerator.rb @@ -164,7 +164,7 @@ module I18n by_site[locale].with_indifferent_access rescue ActiveRecord::StatementInvalid => e - if PG::UndefinedTable === e.cause + if PG::UndefinedTable === e.cause || PG::UndefinedColumn === e.cause {} else raise diff --git a/lib/guardian.rb b/lib/guardian.rb index 00d496e46e..456461fead 100644 --- a/lib/guardian.rb +++ b/lib/guardian.rb @@ -493,6 +493,10 @@ class Guardian is_staff? end + def can_see_about_stats? + true + end + def auth_token if cookie = request&.cookies[Auth::DefaultCurrentUserProvider::TOKEN_COOKIE] UserAuthToken.hash_token(cookie) diff --git a/lib/plugin/instance.rb b/lib/plugin/instance.rb index a993ce52f0..ab48f7cbea 100644 --- a/lib/plugin/instance.rb +++ b/lib/plugin/instance.rb @@ -165,6 +165,16 @@ class Plugin::Instance DiscoursePluginRegistry.register_editable_group_custom_field(field, self) end + # Request a new size for topic thumbnails + # Will respect plugin enabled setting is enabled + # Size should be an array with two elements [max_width, max_height] + def register_topic_thumbnail_size(size) + if !(size.kind_of?(Array) && size.length == 2) + raise ArgumentError.new("Topic thumbnail dimension is not valid") + end + DiscoursePluginRegistry.register_topic_thumbnail_size(size, self) + end + def custom_avatar_column(column) reloadable_patch do |plugin| AvatarLookup.lookup_columns << column diff --git a/lib/plugin_initialization_guard.rb b/lib/plugin_initialization_guard.rb index a5a164e9e2..42c1bda864 100644 --- a/lib/plugin_initialization_guard.rb +++ b/lib/plugin_initialization_guard.rb @@ -6,33 +6,44 @@ def plugin_initialization_guard(&block) rescue => error plugins_directory = Rails.root + 'plugins' - plugin_path = error.backtrace_locations.lazy.map do |location| - Pathname.new(location.absolute_path) - .ascend - .lazy - .find { |path| path.parent == plugins_directory } - end.next + if error.backtrace && error.backtrace_locations + plugin_path = error.backtrace_locations.lazy.map do |location| + Pathname.new(location.absolute_path) + .ascend + .lazy + .find { |path| path.parent == plugins_directory } + end.next - raise unless plugin_path + raise unless plugin_path - stack_trace = error.backtrace.each_with_index.inject([]) do |messages, (line, index)| - if index == 0 - messages << "#{line}: #{error} (#{error.class})" - else - messages << "\t#{index}: from #{line}" - end - end.reverse.join("\n") + stack_trace = error.backtrace.each_with_index.inject([]) do |messages, (line, index)| + if index == 0 + messages << "#{line}: #{error} (#{error.class})" + else + messages << "\t#{index}: from #{line}" + end + end.reverse.join("\n") - STDERR.puts <<~MESSAGE - #{stack_trace} + STDERR.puts <<~MESSAGE + #{stack_trace} - ** INCOMPATIBLE PLUGIN ** + ** INCOMPATIBLE PLUGIN ** - You are unable to build Discourse due to errors in the plugin at - #{plugin_path} + You are unable to build Discourse due to errors in the plugin at + #{plugin_path} - Please try removing this plugin and rebuilding again! - MESSAGE + Please try removing this plugin and rebuilding again! + MESSAGE + else + STDERR.puts <<~MESSAGE + ** PLUGIN FAILURE ** + + You are unable to build Discourse due to this error during plugin + initialization: + + #{error} + MESSAGE + end exit 1 end end diff --git a/lib/post_creator.rb b/lib/post_creator.rb index 8a37e4c9c6..c3b9556df2 100644 --- a/lib/post_creator.rb +++ b/lib/post_creator.rb @@ -374,6 +374,10 @@ class PostCreator # discourse post. def create_embedded_topic return unless @opts[:embed_url].present? + + original_uri = URI.parse(@opts[:embed_url]) + raise Discourse::InvalidParameters.new(:embed_url) unless original_uri.is_a?(URI::HTTP) + embed = TopicEmbed.new(topic_id: @post.topic_id, post_id: @post.id, embed_url: @opts[:embed_url]) rollback_from_errors!(embed) unless embed.save end diff --git a/lib/post_revisor.rb b/lib/post_revisor.rb index 3c9ec277cd..d610fccf37 100644 --- a/lib/post_revisor.rb +++ b/lib/post_revisor.rb @@ -567,11 +567,7 @@ class PostRevisor end def update_topic_excerpt - excerpt = @post.excerpt_for_topic - @topic.update_column(:excerpt, excerpt) - if @topic.archetype == "banner" - ApplicationController.banner_json_cache.clear - end + @topic.update_excerpt(@post.excerpt_for_topic) end def update_category_description diff --git a/lib/tasks/bookmarks.rake b/lib/tasks/bookmarks.rake index d3cf545d2d..87fe005b07 100644 --- a/lib/tasks/bookmarks.rake +++ b/lib/tasks/bookmarks.rake @@ -22,6 +22,7 @@ task "bookmarks:sync_to_table" => :environment do |_t, args| INNER JOIN posts ON posts.id = post_actions.post_id LEFT JOIN bookmarks ON bookmarks.post_id = post_actions.post_id AND bookmarks.user_id = post_actions.user_id INNER JOIN topics ON topics.id = posts.topic_id + INNER JOIN users ON users.id = post_actions.user_id WHERE bookmarks.id IS NULL AND post_action_type_id = :type_id AND post_actions.deleted_at IS NULL AND posts.deleted_at IS NULL LIMIT 2000 SQL diff --git a/lib/topic_query_params.rb b/lib/topic_query_params.rb index e1bb23cb9f..bab30135a1 100644 --- a/lib/topic_query_params.rb +++ b/lib/topic_query_params.rb @@ -3,7 +3,7 @@ module TopicQueryParams def build_topic_list_options options = {} - params[:tags] = [params[:tag_id].parameterize] if params[:tag_id].present? && guardian.can_tag_pms? + params[:tags] = [params[:tag_id]] if params[:tag_id].present? && guardian.can_tag_pms? TopicQuery.public_valid_options.each do |key| if params.key?(key) diff --git a/plugins/discourse-narrative-bot/db/fixtures/001_discobot.rb b/plugins/discourse-narrative-bot/db/fixtures/001_discobot.rb index 3972a350fb..fda50b4829 100644 --- a/plugins/discourse-narrative-bot/db/fixtures/001_discobot.rb +++ b/plugins/discourse-narrative-bot/db/fixtures/001_discobot.rb @@ -4,20 +4,20 @@ discobot_username = 'discobot' def seed_primary_email UserEmail.seed do |ue| - ue.id = -2 + ue.id = DiscourseNarrativeBot::BOT_USER_ID ue.email = "discobot_email" ue.primary = true - ue.user_id = -2 + ue.user_id = DiscourseNarrativeBot::BOT_USER_ID end end -unless user = User.find_by(id: -2) +unless user = User.find_by(id: DiscourseNarrativeBot::BOT_USER_ID) suggested_username = UserNameSuggester.suggest(discobot_username) seed_primary_email User.seed do |u| - u.id = -2 + u.id = DiscourseNarrativeBot::BOT_USER_ID u.name = discobot_username u.username = suggested_username u.username_lower = suggested_username.downcase @@ -26,22 +26,9 @@ unless user = User.find_by(id: -2) u.approved = true u.trust_level = TrustLevel[4] end - - # TODO Pull the user avatar from that thread for now. In the future, pull it from a local file or from some central discobot repo. - if !Rails.env.test? - begin - UserAvatar.import_url_for_user( - "https://cdn.discourse.org/dev/uploads/default/original/2X/e/edb63d57a720838a7ce6a68f02ba4618787f2299.png", - User.find(-2), - override_gravatar: true - ) - rescue - # In case the avatar can't be downloaded, don't fail seed - end - end end -bot = User.find(-2) +bot = User.find(DiscourseNarrativeBot::BOT_USER_ID) # ensure discobot has a primary email unless bot.primary_email @@ -62,4 +49,4 @@ if !bot.user_profile.bio_raw ) end -Group.user_trust_level_change!(-2, TrustLevel[4]) +Group.user_trust_level_change!(DiscourseNarrativeBot::BOT_USER_ID, TrustLevel[4]) diff --git a/plugins/discourse-narrative-bot/db/post_migrate/20200520015508_clear_last_gravatar_download_attempt_on_user_avatars.rb b/plugins/discourse-narrative-bot/db/post_migrate/20200520015508_clear_last_gravatar_download_attempt_on_user_avatars.rb new file mode 100644 index 0000000000..c902973081 --- /dev/null +++ b/plugins/discourse-narrative-bot/db/post_migrate/20200520015508_clear_last_gravatar_download_attempt_on_user_avatars.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +class ClearLastGravatarDownloadAttemptOnUserAvatars < ActiveRecord::Migration[6.0] + def up + execute <<~SQL + UPDATE user_avatars + SET last_gravatar_download_attempt = null + WHERE user_id = -2 AND custom_upload_id IS NULL AND gravatar_upload_id IS NULL + SQL + end + + def down + raise ActiveRecord::IrreversibleMigration + end +end diff --git a/plugins/discourse-narrative-bot/plugin.rb b/plugins/discourse-narrative-bot/plugin.rb index a6be5dd056..61047c0e8b 100644 --- a/plugins/discourse-narrative-bot/plugin.rb +++ b/plugins/discourse-narrative-bot/plugin.rb @@ -55,6 +55,7 @@ after_initialize do module ::DiscourseNarrativeBot PLUGIN_NAME = "discourse-narrative-bot".freeze + BOT_USER_ID = -2 class Engine < ::Rails::Engine engine_name PLUGIN_NAME @@ -271,4 +272,9 @@ after_initialize do end end end + + UserAvatar.register_custom_user_gravatar_email_hash( + DiscourseNarrativeBot::BOT_USER_ID, + "discobot@discourse.org" + ) end diff --git a/plugins/discourse-narrative-bot/spec/discourse_narrative_bot/track_selector_spec.rb b/plugins/discourse-narrative-bot/spec/discourse_narrative_bot/track_selector_spec.rb index 4e24b9feae..6914dff1d2 100644 --- a/plugins/discourse-narrative-bot/spec/discourse_narrative_bot/track_selector_spec.rb +++ b/plugins/discourse-narrative-bot/spec/discourse_narrative_bot/track_selector_spec.rb @@ -472,7 +472,7 @@ describe DiscourseNarrativeBot::TrackSelector do let(:post) { Fabricate(:post, topic: topic) } after do - Discourse.redis.flushall + Discourse.redis.flushdb end describe 'when random reply massage has been displayed in the last 6 hours' do diff --git a/plugins/discourse-narrative-bot/spec/jobs/send_advanced_tutorial_message_spec.rb b/plugins/discourse-narrative-bot/spec/jobs/send_advanced_tutorial_message_spec.rb index 5d3674cae0..874e40cfe8 100644 --- a/plugins/discourse-narrative-bot/spec/jobs/send_advanced_tutorial_message_spec.rb +++ b/plugins/discourse-narrative-bot/spec/jobs/send_advanced_tutorial_message_spec.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +require 'rails_helper' + RSpec.describe Jobs::SendAdvancedTutorialMessage do before do Jobs.run_immediately! diff --git a/spec/components/auth/default_current_user_provider_spec.rb b/spec/components/auth/default_current_user_provider_spec.rb index 1b698cbf36..8c14233a5b 100644 --- a/spec/components/auth/default_current_user_provider_spec.rb +++ b/spec/components/auth/default_current_user_provider_spec.rb @@ -237,7 +237,7 @@ describe Auth::DefaultCurrentUserProvider do end after do - Discourse.redis.flushall + Discourse.redis.flushdb end it "should not update last seen for suspended users" do diff --git a/spec/components/discourse_redis_spec.rb b/spec/components/discourse_redis_spec.rb index 6d4af1d817..abc9a5917a 100644 --- a/spec/components/discourse_redis_spec.rb +++ b/spec/components/discourse_redis_spec.rb @@ -21,11 +21,11 @@ describe DiscourseRedis do let(:raw_redis) { Redis.new(DiscourseRedis.config) } before do - raw_redis.flushall + raw_redis.flushdb end after do - raw_redis.flushall + raw_redis.flushdb end describe 'when namespace is enabled' do diff --git a/spec/components/email/processor_spec.rb b/spec/components/email/processor_spec.rb index 3fe4ab41b7..606955551a 100644 --- a/spec/components/email/processor_spec.rb +++ b/spec/components/email/processor_spec.rb @@ -5,7 +5,7 @@ require "email/processor" describe Email::Processor do after do - Discourse.redis.flushall + Discourse.redis.flushdb end let(:from) { "foo@bar.com" } diff --git a/spec/components/file_store/s3_store_spec.rb b/spec/components/file_store/s3_store_spec.rb index 99f57c7104..15c36b05be 100644 --- a/spec/components/file_store/s3_store_spec.rb +++ b/spec/components/file_store/s3_store_spec.rb @@ -251,7 +251,7 @@ describe FileStore::S3Store do before do optimized_image.update!( - url: "//s3-upload-bucket.s3.dualstack.us-west-1.amazonaws.com#{image_path}" + url: "//s3-upload-bucket.s3.dualstack.us-west-1.amazonaws.com/#{image_path}" ) end @@ -272,6 +272,12 @@ describe FileStore::S3Store do SiteSetting.s3_upload_bucket = "s3-upload-bucket/discourse-uploads" end + before do + optimized_image.update!( + url: "//s3-upload-bucket.s3.dualstack.us-west-1.amazonaws.com/discourse-uploads/#{image_path}" + ) + end + it "removes the file from s3 with the right paths" do s3_helper.expects(:s3_bucket).returns(s3_bucket).at_least_once s3_object = stub @@ -298,6 +304,15 @@ describe FileStore::S3Store do describe ".has_been_uploaded?" do + it "doesn't crash for invalid URLs" do + expect(store.has_been_uploaded?("https://site.discourse.com/#bad#6")).to eq(false) + end + + it "doesn't crash if URL contains non-ascii characters" do + expect(store.has_been_uploaded?("//s3-upload-bucket.s3.dualstack.us-east-1.amazonaws.com/漢1337.png")).to eq(true) + expect(store.has_been_uploaded?("//s3-upload-bucket.s3.amazonaws.com/漢1337.png")).to eq(false) + end + it "identifies S3 uploads" do expect(store.has_been_uploaded?("//s3-upload-bucket.s3.dualstack.us-east-1.amazonaws.com/1337.png")).to eq(true) end diff --git a/spec/components/guardian/user_guardian_spec.rb b/spec/components/guardian/user_guardian_spec.rb index b812fa9526..10b6afc941 100644 --- a/spec/components/guardian/user_guardian_spec.rb +++ b/spec/components/guardian/user_guardian_spec.rb @@ -25,13 +25,13 @@ describe UserGuardian do end let :already_uploaded do - u = Upload.new(user_id: 999, id: 2) + u = Upload.new(user_id: 9999, id: 2) user_avatar.custom_upload_id = u.id u end let :not_my_upload do - Upload.new(user_id: 999, id: 3) + Upload.new(user_id: 9999, id: 3) end let(:moderator_upload) do diff --git a/spec/components/pretty_text_spec.rb b/spec/components/pretty_text_spec.rb index 8969a526bd..c5ef73c4d2 100644 --- a/spec/components/pretty_text_spec.rb +++ b/spec/components/pretty_text_spec.rb @@ -508,7 +508,7 @@ describe PrettyText do ['apple', 'banana'].each { |w| Fabricate(:watched_word, word: w, action: WatchedWord.actions[:censor]) } expect(PrettyText.cook("# banana")).not_to include('banana') ensure - Discourse.redis.flushall + Discourse.redis.flushdb end end end @@ -1166,7 +1166,7 @@ HTML end describe "censoring" do - after(:all) { Discourse.redis.flushall } + after(:all) { Discourse.redis.flushdb } def expect_cooked_match(raw, expected_cooked) expect(PrettyText.cook(raw)).to eq(expected_cooked) diff --git a/spec/components/search_spec.rb b/spec/components/search_spec.rb index a455d81ec1..599ea3c5aa 100644 --- a/spec/components/search_spec.rb +++ b/spec/components/search_spec.rb @@ -444,7 +444,7 @@ describe Search do end let(:expected_blurb) do - "...to satisfy any test conditions that require content longer than the typical test post raw content. elephant" + "...quire content longer than the typical test post raw content. It really is some long content, folks. elephant" end it 'returns the post' do diff --git a/spec/fabricators/post_fabricator.rb b/spec/fabricators/post_fabricator.rb index 42235cb94f..77a234d981 100644 --- a/spec/fabricators/post_fabricator.rb +++ b/spec/fabricators/post_fabricator.rb @@ -10,7 +10,7 @@ end Fabricator(:post_with_long_raw_content, from: :post) do raw 'This is a sample post with semi-long raw content. The raw content is also more than two hundred characters to satisfy any test conditions that require content longer - than the typical test post raw content.' + than the typical test post raw content. It really is some long content, folks.' end Fabricator(:post_with_youtube, from: :post) do diff --git a/spec/fabricators/web_hook_fabricator.rb b/spec/fabricators/web_hook_fabricator.rb index 4647251080..9edc591791 100644 --- a/spec/fabricators/web_hook_fabricator.rb +++ b/spec/fabricators/web_hook_fabricator.rb @@ -71,22 +71,6 @@ Fabricator(:tag_web_hook, from: :web_hook) do end end -Fabricator(:flag_web_hook, from: :web_hook) do - transient flag_hook: WebHookEventType.find_by(name: 'flag') - - after_build do |web_hook, transients| - web_hook.web_hook_event_types = [transients[:flag_hook]] - end -end - -Fabricator(:queued_post_web_hook, from: :web_hook) do - transient queued_post_hook: WebHookEventType.find_by(name: 'queued_post') - - after_build do |web_hook, transients| - web_hook.web_hook_event_types = [transients[:queued_post_hook]] - end -end - Fabricator(:reviewable_web_hook, from: :web_hook) do transient reviewable_hook: WebHookEventType.find_by(name: 'reviewable') diff --git a/spec/integration/email_style_spec.rb b/spec/integration/email_style_spec.rb index ef69850891..395dd19df1 100644 --- a/spec/integration/email_style_spec.rb +++ b/spec/integration/email_style_spec.rb @@ -3,128 +3,139 @@ require "rails_helper" describe EmailStyle do - before do - SiteSetting.email_custom_template = "

FOR YOU

%{email_content}
" - SiteSetting.email_custom_css = 'h1 { color: red; } div.body { color: #FAB; }' - SiteSetting.email_custom_css_compiled = SiteSetting.email_custom_css - end - after do - SiteSetting.remove_override!(:email_custom_template) - SiteSetting.remove_override!(:email_custom_css) - end - - context 'invite' do - fab!(:invite) { Fabricate(:invite) } - let(:invite_mail) { InviteMailer.send_invite(invite) } - - subject(:mail_html) { Email::Renderer.new(invite_mail).html } - - it 'applies customizations' do - expect(mail_html.scan('

FOR YOU

').count).to eq(1) - expect(mail_html).to match("#{Discourse.base_url}/invites/#{invite.invite_key}") - end - - it 'applies customizations if compiled is missing' do - SiteSetting.remove_override!(:email_custom_css_compiled) - expect(mail_html.scan('

FOR YOU

').count).to eq(1) - expect(mail_html).to match("#{Discourse.base_url}/invites/#{invite.invite_key}") - end - - it 'can apply RTL attrs' do - SiteSetting.default_locale = 'he' - body_attrs = mail_html.match(/])+/) - expect(body_attrs[0]&.downcase).to match(/text-align:\s*right/) - expect(body_attrs[0]&.downcase).to include('dir="rtl"') + context "ERB evaluation" do + it "does not evaluate ERB outside of the email itself" do + SiteSetting.email_custom_template = "
%{email_content}
<%= (111 * 333) %>" + html = Email::Renderer.new(UserNotifications.signup(Fabricate(:user))).html + expect(html).not_to match("36963") end end - context 'user_replied' do - let(:response_by_user) { Fabricate(:user, name: "John Doe") } - let(:category) { Fabricate(:category, name: 'India') } - let(:topic) { Fabricate(:topic, category: category, title: "Super cool topic") } - let(:post) { Fabricate(:post, topic: topic, raw: 'This is My super duper cool topic') } - let(:response) { Fabricate(:basic_reply, topic: post.topic, user: response_by_user) } - let(:user) { Fabricate(:user) } - let(:notification) { Fabricate(:replied_notification, user: user, post: response) } - - let(:mail) do - UserNotifications.user_replied( - user, - post: response, - notification_type: notification.notification_type, - notification_data_hash: notification.data_hash - ) + context "with a custom template" do + before do + SiteSetting.email_custom_template = "

FOR YOU

%{email_content}
" + SiteSetting.email_custom_css = 'h1 { color: red; } div.body { color: #FAB; }' + SiteSetting.email_custom_css_compiled = SiteSetting.email_custom_css end - subject(:mail_html) { Email::Renderer.new(mail).html } - - it "customizations are applied to html part of emails" do - expect(mail_html.scan('

FOR YOU

').count).to eq(1) - matches = mail_html.match(/
#{post.raw}/) - expect(matches[1]).to include('color: #FAB;') # custom - expect(matches[1]).to include('padding-top:5px;') # div.body + after do + SiteSetting.remove_override!(:email_custom_template) + SiteSetting.remove_override!(:email_custom_css) end - # TODO: translation override - end + context 'invite' do + fab!(:invite) { Fabricate(:invite) } + let(:invite_mail) { InviteMailer.send_invite(invite) } - context 'signup' do - let(:signup_mail) { UserNotifications.signup(Fabricate(:user)) } - subject(:mail_html) { Email::Renderer.new(signup_mail).html } + subject(:mail_html) { Email::Renderer.new(invite_mail).html } - it "customizations are applied to html part of emails" do - expect(mail_html.scan('

FOR YOU

').count).to eq(1) - expect(mail_html).to include('activate-account') + it 'applies customizations' do + expect(mail_html.scan('

FOR YOU

').count).to eq(1) + expect(mail_html).to match("#{Discourse.base_url}/invites/#{invite.invite_key}") + end + + it 'applies customizations if compiled is missing' do + SiteSetting.remove_override!(:email_custom_css_compiled) + expect(mail_html.scan('

FOR YOU

').count).to eq(1) + expect(mail_html).to match("#{Discourse.base_url}/invites/#{invite.invite_key}") + end + + it 'can apply RTL attrs' do + SiteSetting.default_locale = 'he' + body_attrs = mail_html.match(/])+/) + expect(body_attrs[0]&.downcase).to match(/text-align:\s*right/) + expect(body_attrs[0]&.downcase).to include('dir="rtl"') + end end - context 'translation override' do - before do - TranslationOverride.upsert!( - 'en', - 'user_notifications.signup.text_body_template', - "CLICK THAT LINK: %{base_url}/u/activate-account/%{email_token}" + context 'user_replied' do + let(:response_by_user) { Fabricate(:user, name: "John Doe") } + let(:category) { Fabricate(:category, name: 'India') } + let(:topic) { Fabricate(:topic, category: category, title: "Super cool topic") } + let(:post) { Fabricate(:post, topic: topic, raw: 'This is My super duper cool topic') } + let(:response) { Fabricate(:basic_reply, topic: post.topic, user: response_by_user) } + let(:user) { Fabricate(:user) } + let(:notification) { Fabricate(:replied_notification, user: user, post: response) } + + let(:mail) do + UserNotifications.user_replied( + user, + post: response, + notification_type: notification.notification_type, + notification_data_hash: notification.data_hash ) end - after do - TranslationOverride.revert!('en', ['user_notifications.signup.text_body_template']) + subject(:mail_html) { Email::Renderer.new(mail).html } + + it "customizations are applied to html part of emails" do + expect(mail_html.scan('

FOR YOU

').count).to eq(1) + matches = mail_html.match(/
#{post.raw}/) + expect(matches[1]).to include('color: #FAB;') # custom + expect(matches[1]).to include('padding-top:5px;') # div.body end - it "applies customizations when translation override exists" do - expect(mail_html.scan('

FOR YOU

').count).to eq(1) - expect(mail_html.scan('CLICK THAT LINK').count).to eq(1) - end + # TODO: translation override end - context 'with some bad css' do - before do - SiteSetting.email_custom_css = '@import "nope.css"; h1 {{{ size: really big; ' - SiteSetting.email_custom_css_compiled = SiteSetting.email_custom_css - end + context 'signup' do + let(:signup_mail) { UserNotifications.signup(Fabricate(:user)) } + subject(:mail_html) { Email::Renderer.new(signup_mail).html } - it "can render the html" do - expect(mail_html.scan(/FOR YOU<\/h1>/).count).to eq(1) + it "customizations are applied to html part of emails" do + expect(mail_html.scan('

FOR YOU

').count).to eq(1) expect(mail_html).to include('activate-account') end - end - end - context 'digest' do - fab!(:popular_topic) { Fabricate(:topic, user: Fabricate(:coding_horror), created_at: 1.hour.ago) } - let(:summary_email) { UserNotifications.digest(Fabricate(:user)) } - subject(:mail_html) { Email::Renderer.new(summary_email).html } + context 'translation override' do + before do + TranslationOverride.upsert!( + 'en', + 'user_notifications.signup.text_body_template', + "CLICK THAT LINK: %{base_url}/u/activate-account/%{email_token}" + ) + end - it "customizations are applied to html part of emails" do - expect(mail_html.scan('

FOR YOU

').count).to eq(1) - expect(mail_html).to include(popular_topic.title) + after do + TranslationOverride.revert!('en', ['user_notifications.signup.text_body_template']) + end + + it "applies customizations when translation override exists" do + expect(mail_html.scan('

FOR YOU

').count).to eq(1) + expect(mail_html.scan('CLICK THAT LINK').count).to eq(1) + end + end + + context 'with some bad css' do + before do + SiteSetting.email_custom_css = '@import "nope.css"; h1 {{{ size: really big; ' + SiteSetting.email_custom_css_compiled = SiteSetting.email_custom_css + end + + it "can render the html" do + expect(mail_html.scan(/FOR YOU<\/h1>/).count).to eq(1) + expect(mail_html).to include('activate-account') + end + end end - it "doesn't apply customizations if apply_custom_styles_to_digest is disabled" do - SiteSetting.apply_custom_styles_to_digest = false - expect(mail_html).to_not include('

FOR YOU

') - expect(mail_html).to_not include('FOR YOU') - expect(mail_html).to include(popular_topic.title) + context 'digest' do + fab!(:popular_topic) { Fabricate(:topic, user: Fabricate(:coding_horror), created_at: 1.hour.ago) } + let(:summary_email) { UserNotifications.digest(Fabricate(:user)) } + subject(:mail_html) { Email::Renderer.new(summary_email).html } + + it "customizations are applied to html part of emails" do + expect(mail_html.scan('

FOR YOU

').count).to eq(1) + expect(mail_html).to include(popular_topic.title) + end + + it "doesn't apply customizations if apply_custom_styles_to_digest is disabled" do + SiteSetting.apply_custom_styles_to_digest = false + expect(mail_html).to_not include('

FOR YOU

') + expect(mail_html).to_not include('FOR YOU') + expect(mail_html).to include(popular_topic.title) + end end end end diff --git a/spec/integration/topic_thumbnail_spec.rb b/spec/integration/topic_thumbnail_spec.rb index 9ba35cbe97..dcdbb084d5 100644 --- a/spec/integration/topic_thumbnail_spec.rb +++ b/spec/integration/topic_thumbnail_spec.rb @@ -10,7 +10,7 @@ describe "Topic Thumbnails" do context 'latest' do def get_topic - Discourse.redis.del(topic.thumbnail_job_redis_key([])) + Discourse.redis.del(topic.thumbnail_job_redis_key(Topic.thumbnail_sizes)) get '/latest.json' response.parsed_body["topic_list"]["topics"][0] end @@ -84,5 +84,38 @@ describe "Topic Thumbnails" do expect(thumbnails.length).to eq(5) end end + + context "with a plugin" do + before do + plugin = Plugin::Instance.new + plugin.register_topic_thumbnail_size [512, 512] + end + + after do + DiscoursePluginRegistry.reset! + end + + it "includes the theme specified resolutions" do + topic_json = nil + + expect do + topic_json = get_topic + end.to change { Jobs::GenerateTopicThumbnails.jobs.size }.by(1) + + # Run the job + args = Jobs::GenerateTopicThumbnails.jobs.last["args"].first + Jobs::GenerateTopicThumbnails.new.execute(args.with_indifferent_access) + + # Request again + expect do + topic_json = get_topic + end.to change { Jobs::GenerateTopicThumbnails.jobs.size }.by(0) + + thumbnails = topic_json["thumbnails"] + + # Original + Optimized + 1 plugin request + expect(thumbnails.length).to eq(3) + end + end end end diff --git a/spec/jobs/bookmark_reminder_notifications_spec.rb b/spec/jobs/bookmark_reminder_notifications_spec.rb index 0bcfa0b3d2..25379d0ba3 100644 --- a/spec/jobs/bookmark_reminder_notifications_spec.rb +++ b/spec/jobs/bookmark_reminder_notifications_spec.rb @@ -22,7 +22,7 @@ RSpec.describe Jobs::BookmarkReminderNotifications do bookmark1.update_column(:reminder_at, five_minutes_ago - 10.minutes) bookmark2.update_column(:reminder_at, five_minutes_ago - 5.minutes) bookmark3.update_column(:reminder_at, five_minutes_ago) - Discourse.redis.flushall + Discourse.redis.flushdb end it "sends every reminder and marks the reminder_at to nil for all bookmarks, as well as last sent date" do diff --git a/spec/lib/bookmark_reminder_notification_handler_spec.rb b/spec/lib/bookmark_reminder_notification_handler_spec.rb index 6af860f2f0..3d9a6d77f6 100644 --- a/spec/lib/bookmark_reminder_notification_handler_spec.rb +++ b/spec/lib/bookmark_reminder_notification_handler_spec.rb @@ -8,7 +8,7 @@ RSpec.describe BookmarkReminderNotificationHandler do fab!(:user) { Fabricate(:user) } before do - Discourse.redis.flushall + Discourse.redis.flushdb end describe "#send_notification" do diff --git a/spec/models/post_spec.rb b/spec/models/post_spec.rb index 4cc716d691..5bc5206bce 100644 --- a/spec/models/post_spec.rb +++ b/spec/models/post_spec.rb @@ -1158,6 +1158,25 @@ describe Post do expect(post.custom_fields).to eq("Tommy" => "Hanks", "Vincent" => "Vega") end + describe "#excerpt_for_topic" do + it "returns a topic excerpt, defaulting to 220 chars" do + expected_excerpt = "This is a sample post with semi-long raw content. The raw content is also more than \ntwo hundred characters to satisfy any test conditions that require content longer \nthan the typical test post raw content. It really is…" + post = Fabricate(:post_with_long_raw_content) + post.rebake! + excerpt = post.excerpt_for_topic + expect(excerpt).to eq(expected_excerpt) + end + + it "respects the site setting for topic excerpt" do + SiteSetting.topic_excerpt_maxlength = 10 + expected_excerpt = "This is a …" + post = Fabricate(:post_with_long_raw_content) + post.rebake! + excerpt = post.excerpt_for_topic + expect(excerpt).to eq(expected_excerpt) + end + end + describe "#rebake!" do it "will rebake a post correctly" do post = create_post @@ -1176,6 +1195,25 @@ describe Post do expect(post.cooked).to eq(first_cooked) expect(result).to eq(true) end + + it "updates the topic excerpt at the same time if it is the OP" do + post = create_post + post.topic.update(excerpt: "test") + DB.exec("UPDATE posts SET cooked = 'frogs' WHERE id = ?", [ post.id ]) + post.reload + result = post.rebake! + post.topic.reload + expect(post.topic.excerpt).not_to eq("test") + end + + it "does not update the topic excerpt if the post is not the OP" do + post = create_post + post2 = create_post + post.topic.update(excerpt: "test") + result = post2.rebake! + post.topic.reload + expect(post.topic.excerpt).to eq("test") + end end describe "#set_owner" do diff --git a/spec/models/topic_embed_spec.rb b/spec/models/topic_embed_spec.rb index ddfbe115ec..c91781e6dd 100644 --- a/spec/models/topic_embed_spec.rb +++ b/spec/models/topic_embed_spec.rb @@ -308,6 +308,14 @@ describe TopicEmbed do end end + context "non-http URL" do + let(:url) { '/test.txt' } + + it "throws an error" do + expect { TopicEmbed.find_remote(url) }.to raise_error(URI::InvalidURIError) + end + end + context "emails" do let(:url) { 'http://example.com/foo' } let(:contents) { '

URL encoded @ symbol

normal mailto link

' } diff --git a/spec/models/topic_spec.rb b/spec/models/topic_spec.rb index d381579300..d1d5d1cc91 100644 --- a/spec/models/topic_spec.rb +++ b/spec/models/topic_spec.rb @@ -33,7 +33,7 @@ describe Topic do describe 'censored words' do after do - Discourse.redis.flushall + Discourse.redis.flushdb end describe 'when title contains censored words' do diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 89d5f62704..4d2592c788 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -1578,20 +1578,15 @@ describe User do describe '#number_of_rejected_posts' do it 'counts rejected posts' do - post = Fabricate(:post, user: user) - - Fabricate(:reviewable_queued_post, target: post, status: Reviewable.statuses[:rejected]) + Fabricate(:reviewable_queued_post, created_by: user, status: Reviewable.statuses[:rejected]) expect(user.number_of_rejected_posts).to eq(1) end it 'ignore non-rejected posts' do - post = Fabricate(:post, user: user) - - Fabricate(:reviewable_queued_post, target: post, status: Reviewable.statuses[:approved]) + Fabricate(:reviewable_queued_post, created_by: user, status: Reviewable.statuses[:approved]) expect(user.number_of_rejected_posts).to eq(0) - end end end diff --git a/spec/multisite/s3_store_spec.rb b/spec/multisite/s3_store_spec.rb index 922c5f433b..064c699a12 100644 --- a/spec/multisite/s3_store_spec.rb +++ b/spec/multisite/s3_store_spec.rb @@ -217,4 +217,47 @@ RSpec.describe 'Multisite s3 uploads', type: :multisite do end end end + + describe "#has_been_uploaded?" do + before do + SiteSetting.s3_region = 'us-west-1' + SiteSetting.s3_upload_bucket = "s3-upload-bucket/test" + SiteSetting.s3_access_key_id = "s3-access-key-id" + SiteSetting.s3_secret_access_key = "s3-secret-access-key" + SiteSetting.enable_s3_uploads = true + end + + let(:store) { FileStore::S3Store.new } + let(:client) { Aws::S3::Client.new(stub_responses: true) } + let(:resource) { Aws::S3::Resource.new(client: client) } + let(:s3_bucket) { resource.bucket(SiteSetting.s3_upload_bucket) } + let(:s3_helper) { store.s3_helper } + + it "returns false for blank urls" do + url = "" + expect(store.has_been_uploaded?(url)).to eq(false) + end + + it "returns true if the base hostname is the same for both urls" do + url = "https://s3-upload-bucket.s3.dualstack.us-west-1.amazonaws.com/test/original/2X/d/dd7964f5fd13e1103c5244ca30abe1936c0a4b88.png" + expect(store.has_been_uploaded?(url)).to eq(true) + end + + it "returns false if the base hostname is the same for both urls BUT the bucket name is different in the path" do + bucket = "someotherbucket" + url = "https://s3-upload-bucket.s3.dualstack.us-west-1.amazonaws.com/#{bucket}/original/2X/d/dd7964f5fd13e1103c5244ca30abe1936c0a4b88.png" + expect(store.has_been_uploaded?(url)).to eq(false) + end + + it "returns false if the hostnames do not match and the s3_cdn_url is blank" do + url = "https://www.someotherhostname.com/test/original/2X/d/dd7964f5fd13e1103c5244ca30abe1936c0a4b88.png" + expect(store.has_been_uploaded?(url)).to eq(false) + end + + it "returns true if the s3_cdn_url is present and matches the url hostname" do + SiteSetting.s3_cdn_url = "https://www.someotherhostname.com" + url = "https://www.someotherhostname.com/test/original/2X/d/dd7964f5fd13e1103c5244ca30abe1936c0a4b88.png" + expect(store.has_been_uploaded?(url)).to eq(true) + end + end end diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb index a0ce4b71b7..256de29658 100644 --- a/spec/rails_helper.rb +++ b/spec/rails_helper.rb @@ -128,6 +128,12 @@ module TestSetup # code that runs inside jobs. run_later! means they are put on the redis # queue and never processed. Jobs.run_later! + + # Don't track ApplicationRequests in test mode unless opted in + ApplicationRequest.disable + + # Don't queue badge grant in test mode + BadgeGranter.disable_queue end end diff --git a/spec/requests/about_controller_spec.rb b/spec/requests/about_controller_spec.rb index 6c58284979..a10b766304 100644 --- a/spec/requests/about_controller_spec.rb +++ b/spec/requests/about_controller_spec.rb @@ -36,5 +36,21 @@ describe AboutController do expect(response.body).to include("About - Discourse") end end + + it "serializes stats when 'Guardian#can_see_about_stats?' is true" do + Guardian.any_instance.stubs(:can_see_about_stats?).returns(true) + get "/about.json" + + expect(response.status).to eq(200) + expect(response.parsed_body["about"].keys).to include("stats") + end + + it "does not serialize stats when 'Guardian#can_see_about_stats?' is false" do + Guardian.any_instance.stubs(:can_see_about_stats?).returns(false) + get "/about.json" + + expect(response.status).to eq(200) + expect(response.parsed_body["about"].keys).not_to include("stats") + end end end diff --git a/spec/requests/admin/backups_controller_spec.rb b/spec/requests/admin/backups_controller_spec.rb index 466d4a938b..03b9245744 100644 --- a/spec/requests/admin/backups_controller_spec.rb +++ b/spec/requests/admin/backups_controller_spec.rb @@ -35,7 +35,7 @@ RSpec.describe Admin::BackupsController do end after do - Discourse.redis.flushall + Discourse.redis.flushdb @paths&.each { |path| File.delete(path) if File.exists?(path) } @paths = nil diff --git a/spec/requests/admin/users_controller_spec.rb b/spec/requests/admin/users_controller_spec.rb index f62407d956..099372cad0 100644 --- a/spec/requests/admin/users_controller_spec.rb +++ b/spec/requests/admin/users_controller_spec.rb @@ -293,7 +293,7 @@ RSpec.describe Admin::UsersController do fab!(:another_user) { Fabricate(:coding_horror) } after do - Discourse.redis.flushall + Discourse.redis.flushdb end it "raises an error when the user doesn't have permission" do diff --git a/spec/requests/list_controller_spec.rb b/spec/requests/list_controller_spec.rb index d1238c1f00..bc913c58a0 100644 --- a/spec/requests/list_controller_spec.rb +++ b/spec/requests/list_controller_spec.rb @@ -128,7 +128,7 @@ RSpec.describe ListController do let(:moderator) { Fabricate(:moderator) } let(:admin) { Fabricate(:admin) } let(:tag) { Fabricate(:tag) } - let(:private_message) { Fabricate(:private_message_topic) } + let(:private_message) { Fabricate(:private_message_topic, user: admin) } before do SiteSetting.tagging_enabled = true @@ -149,6 +149,17 @@ RSpec.describe ListController do expect(response.status).to eq(200) end end + + it 'should work for tag with unicode name' do + unicode_tag = Fabricate(:tag, name: 'hello-🇺🇸') + Fabricate(:topic_tag, tag: unicode_tag, topic: private_message) + + sign_in(admin) + get "/topics/private-messages-tags/#{admin.username}/#{UrlHelper.encode_component(unicode_tag.name)}.json" + expect(response.status).to eq(200) + expect(response.parsed_body["topic_list"]["topics"].first["id"]) + .to eq(private_message.id) + end end describe '#private_messages_group' do diff --git a/spec/requests/posts_controller_spec.rb b/spec/requests/posts_controller_spec.rb index 909db8c509..ff355ada96 100644 --- a/spec/requests/posts_controller_spec.rb +++ b/spec/requests/posts_controller_spec.rb @@ -675,6 +675,17 @@ describe PostsController do I18n.t("invalid_params", message: "category") ) end + + it 'will raise an error if specified embed_url is invalid' do + user = Fabricate(:admin) + master_key = Fabricate(:api_key).key + + post "/posts.json", + params: { title: 'this is a test title', raw: 'this is test body', embed_url: '/test.txt' }, + headers: { HTTP_API_USERNAME: user.username, HTTP_API_KEY: master_key } + + expect(response.status).to eq(422) + end end describe "when logged in" do diff --git a/spec/requests/search_controller_spec.rb b/spec/requests/search_controller_spec.rb index 947bd8754a..dac11690d3 100644 --- a/spec/requests/search_controller_spec.rb +++ b/spec/requests/search_controller_spec.rb @@ -26,11 +26,11 @@ describe SearchController do before do # TODO be a bit more strategic here instead of junking # all of redis - Discourse.redis.flushall + Discourse.redis.flushdb end after do - Discourse.redis.flushall + Discourse.redis.flushdb end context "when overloaded" do diff --git a/spec/requests/topics_controller_spec.rb b/spec/requests/topics_controller_spec.rb index 726b92228f..9649f57f5b 100644 --- a/spec/requests/topics_controller_spec.rb +++ b/spec/requests/topics_controller_spec.rb @@ -2127,7 +2127,7 @@ RSpec.describe TopicsController do let(:topic) { post.topic } after do - Discourse.redis.flushall + Discourse.redis.flushdb end it 'returns first post of the topic' do diff --git a/spec/requests/webhooks_controller_spec.rb b/spec/requests/webhooks_controller_spec.rb index 7772e0eb18..580c0c72f0 100644 --- a/spec/requests/webhooks_controller_spec.rb +++ b/spec/requests/webhooks_controller_spec.rb @@ -3,7 +3,7 @@ require "rails_helper" describe WebhooksController do - before { Discourse.redis.flushall } + before { Discourse.redis.flushdb } let(:email) { "em@il.com" } let(:message_id) { "12345@il.com" } diff --git a/spec/serializers/topic_view_serializer_spec.rb b/spec/serializers/topic_view_serializer_spec.rb index 5da26a674e..f6249b275b 100644 --- a/spec/serializers/topic_view_serializer_spec.rb +++ b/spec/serializers/topic_view_serializer_spec.rb @@ -60,7 +60,7 @@ describe TopicViewSerializer do it 'should have thumbnails' do SiteSetting.create_thumbnails = true - Discourse.redis.del(topic.thumbnail_job_redis_key([])) + Discourse.redis.del(topic.thumbnail_job_redis_key(Topic.thumbnail_sizes)) json = nil expect do diff --git a/spec/services/word_watcher_spec.rb b/spec/services/word_watcher_spec.rb index 0f6a2b7bb3..5cf70b6583 100644 --- a/spec/services/word_watcher_spec.rb +++ b/spec/services/word_watcher_spec.rb @@ -7,7 +7,7 @@ describe WordWatcher do let(:raw) { "Do you like liquorice?\n\nI really like them. One could even say that I am *addicted* to liquorice. Anf if\nyou can mix it up with some anise, then I'm in heaven ;)" } after do - Discourse.redis.flushall + Discourse.redis.flushdb end describe '.word_matcher_regexp' do diff --git a/test/javascripts/acceptance/category-chooser-test.js b/test/javascripts/acceptance/category-chooser-test.js index b6772ab771..2247c8cbee 100644 --- a/test/javascripts/acceptance/category-chooser-test.js +++ b/test/javascripts/acceptance/category-chooser-test.js @@ -29,3 +29,19 @@ QUnit.test("prefill category when category_id is set", async assert => { 1 ); }); + +QUnit.test("filter is case insensitive", async assert => { + const categoryChooser = selectKit(".category-chooser"); + + await visit("/"); + await click("#create-topic"); + await categoryChooser.expand(); + await categoryChooser.fillInFilter("bug"); + + assert.ok(categoryChooser.rows().length, 1); + + await categoryChooser.emptyFilter(); + await categoryChooser.fillInFilter("Bug"); + + assert.ok(categoryChooser.rows().length, 1); +}); diff --git a/test/javascripts/acceptance/emoji-picker-test.js b/test/javascripts/acceptance/emoji-picker-test.js index 6a4922ed5c..a3d8472b53 100644 --- a/test/javascripts/acceptance/emoji-picker-test.js +++ b/test/javascripts/acceptance/emoji-picker-test.js @@ -168,19 +168,6 @@ QUnit.test( } ); -QUnit.test("emoji picker lazy loads emojis", async assert => { - await visit("/t/internationalization-localization/280"); - await click("#topic-footer-buttons .btn.create"); - - await click("button.emoji.btn"); - - assert.equal( - find('.emoji-picker button[title="massage_woman"]').css("background-image"), - "none", - "it doesn't load invisible emojis" - ); -}); - QUnit.test("emoji picker persists state", async assert => { await visit("/t/internationalization-localization/280"); await click("#topic-footer-buttons .btn.create"); diff --git a/test/javascripts/acceptance/preferences-test.js b/test/javascripts/acceptance/preferences-test.js index a9db1f08e5..20afdcd167 100644 --- a/test/javascripts/acceptance/preferences-test.js +++ b/test/javascripts/acceptance/preferences-test.js @@ -1,7 +1,6 @@ import I18n from "I18n"; import { acceptance, updateCurrentUser } from "helpers/qunit-helpers"; import selectKit from "helpers/select-kit-helper"; - import User from "discourse/models/user"; acceptance("User Preferences", { diff --git a/test/javascripts/fixtures/about.js b/test/javascripts/fixtures/about.js index 7f9f17cbba..4c7de6a87a 100644 --- a/test/javascripts/fixtures/about.js +++ b/test/javascripts/fixtures/about.js @@ -1,6 +1,7 @@ export default { "about.json": { about: { + can_see_about_stats: true, stats: { topic_count: 27480, post_count: 490358, diff --git a/test/javascripts/helpers/select-kit-helper.js b/test/javascripts/helpers/select-kit-helper.js index 5d80b8f0b3..f795991326 100644 --- a/test/javascripts/helpers/select-kit-helper.js +++ b/test/javascripts/helpers/select-kit-helper.js @@ -32,6 +32,11 @@ async function selectKitFillInFilter(filter, selector) { ); } +async function selectKitEmptyFilter(selector) { + checkSelectKitIsNotCollapsed(selector); + await fillIn(`${selector} .filter-input`, ""); +} + async function selectKitSelectRowByValue(value, selector) { checkSelectKitIsNotCollapsed(selector); await click(`${selector} .select-kit-row[data-value='${value}']`); @@ -180,6 +185,10 @@ export default function selectKit(selector) { await selectKitFillInFilter(filter, selector); }, + async emptyFilter() { + await selectKitEmptyFilter(selector); + }, + async keyboard(value, target) { await keyboardHelper(value, target, selector); },